diff --git a/frappe/core/doctype/doctype/boilerplate/test_controller.js b/frappe/core/doctype/doctype/boilerplate/test_controller.js index 6749c53bb0..ed27ac02f6 100644 --- a/frappe/core/doctype/doctype/boilerplate/test_controller.js +++ b/frappe/core/doctype/doctype/boilerplate/test_controller.js @@ -8,9 +8,9 @@ QUnit.test("test: {doctype}", function (assert) {{ // number of asserts assert.expect(1); - frappe.run_serially('{doctype}', [ + frappe.run_serially([ // insert a new {doctype} - () => frappe.tests.make([ + () => frappe.tests.make('{doctype}', [ // values to be set {{key: 'value'}} ]), diff --git a/frappe/core/doctype/version/test_version.py b/frappe/core/doctype/version/test_version.py index 82f13242ae..be721b3271 100644 --- a/frappe/core/doctype/version/test_version.py +++ b/frappe/core/doctype/version/test_version.py @@ -14,12 +14,13 @@ class TestVersion(unittest.TestCase): new_doc = copy.deepcopy(old_doc) old_doc.color = None + new_doc.color = '#fafafa' diff = get_diff(old_doc, new_doc)['changed'] self.assertEquals(get_fieldnames(diff)[0], 'color') self.assertTrue(get_old_values(diff)[0] is None) - self.assertEquals(get_new_values(diff)[0], 'blue') + self.assertEquals(get_new_values(diff)[0], '#fafafa') new_doc.starts_on = "2017-07-20" diff --git a/frappe/desk/doctype/event/event.json b/frappe/desk/doctype/event/event.json index 75e949e90f..12fcf5d0af 100644 --- a/frappe/desk/doctype/event/event.json +++ b/frappe/desk/doctype/event/event.json @@ -312,9 +312,9 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "blue", + "default": "", "fieldname": "color", - "fieldtype": "Select", + "fieldtype": "Color", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -325,7 +325,7 @@ "label": "Color", "length": 0, "no_copy": 0, - "options": "red\ngreen\nblue\nyellow\nskyblue\norange", + "options": "", "permlevel": 0, "precision": "", "print_hide": 0, @@ -895,8 +895,8 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-07-06 12:37:44.036819", - "modified_by": "Administrator", + "modified": "2017-08-03 16:34:54.657796", + "modified_by": "faris@erpnext.com", "module": "Desk", "name": "Event", "owner": "Administrator", diff --git a/frappe/desk/doctype/event/test_event.js b/frappe/desk/doctype/event/test_event.js new file mode 100644 index 0000000000..5d824800ed --- /dev/null +++ b/frappe/desk/doctype/event/test_event.js @@ -0,0 +1,36 @@ + +QUnit.test("test: Event", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(4); + + const subject = '_Test Event 1'; + const datetime = frappe.datetime.now_datetime(); + const hex = '#6be273'; + const rgb = 'rgb(107, 226, 115)'; + + frappe.run_serially([ + // insert a new Event + () => frappe.tests.make('Event', [ + // values to be set + {subject: subject}, + {starts_on: datetime}, + {color: hex} + ]), + () => { + assert.equal(cur_frm.doc.subject, subject, 'Subject correctly set'); + assert.equal(cur_frm.doc.starts_on, datetime, 'Date correctly set'); + assert.equal(cur_frm.doc.color, hex, 'Color correctly set'); + }, + () => frappe.set_route('List', 'Event', 'Calendar'), + () => frappe.timeout(2), + () => { + const bg_color = $(`.result-list:visible .fc-day-grid-event:contains("${subject}")`) + .css('background-color'); + assert.equal(bg_color, rgb, 'Event background color is set correctly'); + }, + () => done() + ]); + +}); diff --git a/frappe/public/build.json b/frappe/public/build.json index 054421286e..75dbf5063a 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -87,6 +87,7 @@ "public/js/frappe/ui/messages.js", "public/js/frappe/ui/keyboard.js", "public/js/frappe/ui/emoji.js", + "public/js/frappe/ui/colors.js", "public/js/frappe/request.js", "public/js/frappe/socketio_client.js", diff --git a/frappe/public/css/calendar.css b/frappe/public/css/calendar.css index a46ab7227f..df530f7c30 100644 --- a/frappe/public/css/calendar.css +++ b/frappe/public/css/calendar.css @@ -73,7 +73,6 @@ th.fc-day-header { background: #cfdce5 !important; } .fc-day-grid-event { - background-color: rgba(94, 100, 255, 0.2) !important; border: none !important; margin: 5px 4px 0 !important; padding: 1px 5px !important; diff --git a/frappe/public/js/frappe/form/control.js b/frappe/public/js/frappe/form/control.js index 54bb4e0595..f979bb2cb5 100755 --- a/frappe/public/js/frappe/form/control.js +++ b/frappe/public/js/frappe/form/control.js @@ -688,6 +688,8 @@ frappe.ui.form.ControlColor = frappe.ui.form.ControlData.extend({ }, set_formatted_input: function(value) { this._super(value); + + if(!value) value = '#ffffff'; this.$input.css({ "background-color": value }); @@ -721,6 +723,9 @@ frappe.ui.form.ControlColor = frappe.ui.form.ControlData.extend({ }); }, validate: function (value) { + if(value === '') { + return ''; + } var is_valid = /^#[0-9A-F]{6}$/i.test(value); if(is_valid) { return value; diff --git a/frappe/public/js/frappe/ui/colors.js b/frappe/public/js/frappe/ui/colors.js new file mode 100644 index 0000000000..1b3e41ff27 --- /dev/null +++ b/frappe/public/js/frappe/ui/colors.js @@ -0,0 +1,121 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors +// MIT License. See license.txt + +frappe.provide("frappe.ui"); + +frappe.ui.color_map = { + red: ["#ffc4c4", "#ff8989", "#ff4d4d", "#a83333"], + brown: ["#ffe8cd", "#ffd19c", "#ffb868", "#a87945"], + orange: ["#ffd2c2", "#ffa685", "#ff7846", "#a85b5b"], + peach: ["#ffd7d7", "#ffb1b1", "#ff8989", "#a84f2e"], + yellow: ["#fffacd", "#fff168", "#fff69c", "#a89f45"], + yellowgreen: ["#ebf8cc", "#d9f399", "#c5ec63", "#7b933d"], + green: ["#cef6d1", "#9deca2", "#6be273", "#428b46"], + cyan: ["#d2f8ed", "#a4f3dd", "#77ecca", "#49937e"], + skyblue: ["#d2f1ff", "#a6e4ff", "#78d6ff", "#4f8ea8"], + blue: ["#d2d2ff", "#a3a3ff", "#7575ff", "#4d4da8"], + purple: ["#dac7ff", "#b592ff", "#8e58ff", "#5e3aa8"], + pink: ["#f8d4f8", "#f3aaf0", "#ec7dea", "#934f92"] +}; + +frappe.ui.color = { + get: function(color_name, shade) { + if(color_name && shade) return this.get_color_shade(color_name, shade); + if(color_name) return this.get_color_shade(color_name, 'default'); + return frappe.ui.color_map; + }, + get_color: function(color_name) { + const color_names = Object.keys(frappe.ui.color_map); + if(color_names.includes(color_name)) { + return frappe.ui.color_map[color_name]; + } else { + throw new RangeError(`${color_name} can be one of ${color_names}`); + } + }, + get_color_shade: function(color_name, shade) { + const shades = { + 'default': 2, + 'light': 1, + 'extra-light': 0, + 'dark': 3 + }; + + if(Object.keys(shades).includes(shade)) { + return frappe.ui.color_map[color_name][shades[shade]]; + } else { + throw new RangeError(`${shade} can be one of ${Object.keys(shades)}`); + } + }, + all: function() { + return Object.values(frappe.ui.color_map) + .reduce((acc, curr) => acc.concat(curr) , []); + }, + names: function() { + return Object.keys(frappe.ui.color_map); + }, + validate: function(color_name) { + if(!color_name) return false; + if(color_name.startsWith('#')) { + return this.all().includes(color_name); + } + return this.names().includes(color_name); + }, + get_color_name: function(hex) { + for (const key in frappe.ui.color_map) { + const colors = frappe.ui.color_map[key]; + if (colors.includes(hex)) return key; + } + }, + get_contrast_color: function(hex) { + if(!this.validate(hex)) { + const brightness = this.brightness(hex); + if(brightness < 128) { + return this.lighten(hex, 0.5); + } + return this.lighten(hex, -0.5); + } + + const color_name = this.get_color_name(hex); + const colors = this.get_color(color_name); + const shade_value = colors.indexOf(hex); + if(shade_value <= 1) { + return this.get(color_name, 'dark'); + } + return this.get(color_name, 'extra-light'); + }, + + lighten(color, percent) { + // https://stackoverflow.com/a/13542669/5353542 + var f = parseInt(color.slice(1), 16), + t = percent < 0 ? 0 : 255, + p = percent < 0 ? percent * -1 : percent, + R = f >> 16, + G = f >> 8 & 0x00FF, + B = f & 0x0000FF; + return "#" + + (0x1000000 + + (Math.round((t - R) * p) + R) * + 0x10000 + + (Math.round((t - G) * p) + G) * + 0x100 + (Math.round((t - B) * p) + B) + ).toString(16).slice(1); + }, + + hex_to_rgb(hex) { + if(hex.startsWith('#')) { + hex = hex.substring(1); + } + const r = parseInt(hex.substring(0, 2), 16); + const g = parseInt(hex.substring(2, 4), 16); + const b = parseInt(hex.substring(4, 6), 16); + return {r, g, b}; + }, + + brightness(hex) { + const rgb = this.hex_to_rgb(hex); + // https://www.w3.org/TR/AERT#color-contrast + // 255 - brightest (#fff) + // 0 - darkest (#000) + return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000; + } +}; diff --git a/frappe/public/js/frappe/views/calendar/calendar.js b/frappe/public/js/frappe/views/calendar/calendar.js index ce49e5c33d..4b8b4febf9 100644 --- a/frappe/public/js/frappe/views/calendar/calendar.js +++ b/frappe/public/js/frappe/views/calendar/calendar.js @@ -100,7 +100,8 @@ frappe.views.Calendar = Class.extend({ color_map: { "danger": "red", "success": "green", - "warning": "orange" + "warning": "orange", + "default": "blue" }, get_system_datetime: function(date) { date._offset = moment.user_utc_offset; @@ -232,25 +233,28 @@ frappe.views.Calendar = Class.extend({ d.end = frappe.datetime.convert_to_user_tz(d.end); me.fix_end_date_for_event_render(d); - - let color; - if(me.get_css_class) { - color = me.color_map[me.get_css_class(d)]; - // if invalid, fallback to blue color - if(!Object.values(me.color_map).includes(color)) { - color = "blue"; - } - } else { - // color field can be set in {doctype}_calendar.js - // see event_calendar.js - color = d.color; - } - - if(!color) color = "blue"; - d.className = "fc-bg-" + color; + me.prepare_colors(d); return d; }); }, + prepare_colors: function(d) { + let color, color_name; + if(this.get_css_class) { + color_name = this.color_map[this.get_css_class(d)]; + color_name = + frappe.ui.color.validate(color_name) ? + color_name : + 'blue'; + d.backgroundColor = frappe.ui.color.get(color_name, 'extra-light'); + d.textColor = frappe.ui.color.get(color_name, 'dark'); + } else { + color = d.color; + if(!color) color = frappe.ui.color.get('blue', 'extra-light'); + d.backgroundColor = color; + d.textColor = frappe.ui.color.get_contrast_color(color); + } + return d; + }, update_event: function(event, revertFunc) { var me = this; frappe.model.remove_from_locals(me.doctype, event.name); diff --git a/frappe/public/less/calendar.less b/frappe/public/less/calendar.less index f69a7323e3..482beb3cfe 100644 --- a/frappe/public/less/calendar.less +++ b/frappe/public/less/calendar.less @@ -27,7 +27,7 @@ th.fc-widget-header { .fc-unthemed .fc-today { background-color: #FFF !important; - + .fc-day-number { background-color: @brand-primary; min-width: 20px; @@ -90,7 +90,6 @@ th.fc-day-header { } .fc-day-grid-event { - background-color: rgba(94, 100, 255, 0.2) !important; border: none !important; margin: 5px 4px 0 !important; padding: 1px 5px !important; diff --git a/frappe/tests/ui/tests.txt b/frappe/tests/ui/tests.txt index 93907305f7..221257babc 100644 --- a/frappe/tests/ui/tests.txt +++ b/frappe/tests/ui/tests.txt @@ -9,4 +9,5 @@ frappe/tests/ui/test_kanban/test_kanban_filters.js frappe/tests/ui/test_kanban/test_kanban_column.js frappe/core/doctype/report/test_query_report.js frappe/tests/ui/test_linked_with.js -frappe/custom/doctype/customize_form/test_customize_form.js \ No newline at end of file +frappe/custom/doctype/customize_form/test_customize_form.js +frappe/desk/doctype/event/test_event.js