From 233b6ff95fbe3f1d78cdaa4efba35df7794d625d Mon Sep 17 00:00:00 2001 From: Sam Hemelryk Date: Wed, 16 Jun 2010 08:36:06 +0000 Subject: [PATCH] admin MDL-22806 Added a colourpicker admin setting + supporting JavaScript --- lang/en/admin.php | 1 + lib/adminlib.php | 93 +++++++++++++++++++++ lib/javascript-static.js | 160 +++++++++++++++++++++++++++++++++++++ pix/.cvsignore | 1 + pix/i/colourpicker.png | Bin 0 -> 3675 bytes theme/base/style/admin.css | 11 ++- 6 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 pix/.cvsignore create mode 100644 pix/i/colourpicker.png diff --git a/lang/en/admin.php b/lang/en/admin.php index 504f59dcfde..81b6e55caa9 100755 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -631,6 +631,7 @@ $string['latexpreamble'] = 'LaTeX preamble'; $string['latexsettings'] = 'LaTeX renderer Settings'; $string['latinexcelexport'] = 'Excel encoding'; $string['licensesettings'] = 'License settings'; +$string['loading'] = 'Loading'; $string['localetext'] = 'Sitewide locale'; $string['localstringcustomization'] = 'Local string customization'; $string['location'] = 'Location'; diff --git a/lib/adminlib.php b/lib/adminlib.php index 35b54027bad..7f8d29bc7c8 100644 --- a/lib/adminlib.php +++ b/lib/adminlib.php @@ -6940,3 +6940,96 @@ class admin_setting_managewebservicetokens extends admin_setting { return highlight($query, $return); } } + +/** + * Colour picker + * + * @copyright 2010 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class admin_setting_configcolourpicker extends admin_setting { + + /** + * Information for previewing the colour + * + * @var array|null + */ + protected $previewconfig = null; + + /** + * + * @param string $name + * @param string $visiblename + * @param string $description + * @param string $defaultsetting + * @param array $previewconfig Array('selector'=>'.some .css .selector','style'=>'backgroundColor'); + */ + public function __construct($name, $visiblename, $description, $defaultsetting, array $previewconfig=null) { + $this->previewconfig = $previewconfig; + parent::__construct($name, $visiblename, $description, $defaultsetting); + } + + /** + * Return the setting + * + * @return mixed returns config if successful else null + */ + public function get_setting() { + return $this->config_read($this->name); + } + + /** + * Saves the setting + * + * @param string $data + * @return bool + */ + public function write_setting($data) { + $data = $this->validate($data); + if ($data === false) { + return get_string('validateerror', 'admin'); + } + return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin')); + } + + /** + * Validates the colour that was entered by the user + * + * @param string $data + * @return string|false + */ + protected function validate($data) { + if (preg_match('/^#?([a-fA-F0-9]{3}){1,2}$/', $data)) { + if (strpos($data, '#')!==0) { + $data = '#'.$data; + } + return $data; + } else if (preg_match('/^[a-zA-Z]{3, 25}$/')) { + return $data; + } else { + return false; + } + } + + /** + * Generates the HTML for the setting + * + * @global moodle_page $PAGE + * @global core_renderer $OUTPUT + * @param string $data + * @param string $query + */ + public function output_html($data, $query = '') { + global $PAGE, $OUTPUT; + $PAGE->requires->js_init_call('M.util.init_colour_picker', array($this->get_id(), $this->previewconfig)); + $content = html_writer::start_tag('div', array('class'=>'form-colourpicker defaultsnext')); + $content .= html_writer::tag('div', $OUTPUT->pix_icon('i/loading', get_string('loading', 'admin'), 'moodle', array('class'=>'loadingicon')), array('class'=>'admin_colourpicker clearfix')); + $content .= html_writer::empty_tag('input', array('type'=>'text','id'=>$this->get_id(), 'name'=>$this->get_full_name(), 'value'=>$this->get_setting(), 'size'=>'12')); + if (!empty($this->previewconfig)) { + $content .= html_writer::empty_tag('input', array('type'=>'button','id'=>$this->get_id().'_preview', 'value'=>get_string('preview'), 'class'=>'admin_colourpicker_preview')); + } + $content .= html_writer::end_tag('div'); + return format_admin_setting($this, $this->visiblename, $content, $this->description, false, '', $this->get_defaultsetting(), $query); + } + +} \ No newline at end of file diff --git a/lib/javascript-static.js b/lib/javascript-static.js index 3a346ac304c..4a81f9e360c 100644 --- a/lib/javascript-static.js +++ b/lib/javascript-static.js @@ -427,6 +427,166 @@ M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname }, node); } +/** + * Initialises a colour picker + * + * Designed to be used with admin_setting_configcolourpicker although could be used + * anywhere, just give a text input an id and insert a div with the class admin_colourpicker + * above or below the input (must have the same parent) and then call this with the + * id. + * + * This code was mostly taken from my [Sam Hemelryk] css theme tool available in + * contrib/blocks. For better docs refer to that. + * + * @param {YUI} Y + * @param {int} id + * @param {object} previewconf + */ +M.util.init_colour_picker = function(Y, id, previewconf) { + /** + * We need node and event-mouseenter + */ + Y.use('node', 'event-mouseenter', function(){ + /** + * The colour picker object + */ + var colourpicker = { + box : null, + input : null, + image : null, + preview : null, + current : null, + eventClick : null, + eventMouseEnter : null, + eventMouseLeave : null, + eventMouseMove : null, + width : 300, + height : 100, + factor : 5, + /** + * Initalises the colour picker by putting everything together and wiring the events + */ + init : function() { + this.input = Y.one('#'+id); + this.box = this.input.ancestor().one('.admin_colourpicker'); + this.image = Y.Node.create(''); + this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle')); + this.preview = Y.Node.create('
'); + this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value')); + this.current = Y.Node.create('
'); + this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value')); + this.box.setContent('').append(this.image).append(this.preview).append(this.current); + + if (typeof(previewconf) === 'object' && previewconf !== null) { + Y.one('#'+id+'_preview').on('click', function(e){ + Y.one(previewconf.selector).setStyle(previewconf.style, this.input.get('value')); + }, this); + } + + this.eventClick = this.image.on('click', this.pickColour, this); + this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this); + }, + /** + * Starts to follow the mouse once it enter the image + */ + startFollow : function(e) { + this.eventMouseEnter.detach(); + this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this); + this.eventMouseMove = this.image.on('mousemove', function(e){ + this.preview.setStyle('backgroundColor', this.determineColour(e)); + }, this); + }, + /** + * Stops following the mouse + */ + endFollow : function(e) { + this.eventMouseMove.detach(); + this.eventMouseLeave.detach(); + this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this); + }, + /** + * Picks the colour the was clicked on + */ + pickColour : function(e) { + var colour = this.determineColour(e); + this.input.set('value', colour); + this.current.setStyle('backgroundColor', colour); + }, + /** + * Calculates the colour fromthe given co-ordinates + */ + determineColour : function(e) { + var eventx = Math.floor(e.pageX-e.target.getX()); + var eventy = Math.floor(e.pageY-e.target.getY()); + + var imagewidth = this.width; + var imageheight = this.height; + var factor = this.factor; + var colour = [255,0,0]; + + var matrices = [ + [ 0, 1, 0], + [ -1, 0, 0], + [ 0, 0, 1], + [ 0, -1, 0], + [ 1, 0, 0], + [ 0, 0, -1] + ]; + + var matrixcount = matrices.length; + var limit = Math.round(imagewidth/matrixcount); + var heightbreak = Math.round(imageheight/2); + + for (var x = 0; x < imagewidth; x++) { + var divisor = Math.floor(x / limit); + var matrix = matrices[divisor]; + + colour[0] += matrix[0]*factor; + colour[1] += matrix[1]*factor; + colour[2] += matrix[2]*factor; + + if (eventx==x) { + break; + } + } + + var pixel = [colour[0], colour[1], colour[2]]; + if (eventy < heightbreak) { + pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy)); + pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy)); + pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy)); + } else if (eventy > heightbreak) { + pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak)); + pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak)); + pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak)); + } + + return this.convert_rgb_to_hex(pixel); + }, + /** + * Converts an RGB value to Hex + */ + convert_rgb_to_hex : function(rgb) { + var hex = '#'; + var hexchars = "0123456789ABCDEF"; + for (var i=0; i<3; i++) { + var number = Math.abs(rgb[i]); + if (number == 0 || isNaN(number)) { + hex += '00'; + } else { + hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16); + } + } + return hex; + } + } + /** + * Initialise the colour picker :) Hoorah + */ + colourpicker.init(); + }); +} + //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code === function popupchecker(msg) { diff --git a/pix/.cvsignore b/pix/.cvsignore new file mode 100644 index 00000000000..e43b0f98895 --- /dev/null +++ b/pix/.cvsignore @@ -0,0 +1 @@ +.DS_Store diff --git a/pix/i/colourpicker.png b/pix/i/colourpicker.png new file mode 100644 index 0000000000000000000000000000000000000000..d90661444ad9ad754794bd86f684b9eea85fb05a GIT binary patch literal 3675 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 ggHQ+lfPOtmyLDtHB