* Add Color field to Event, show colored event in calendar * [minor] handle edge cases * Fix test boilerplate * Add test for event * fix codacy * fix testversion-14
@@ -8,9 +8,9 @@ QUnit.test("test: {doctype}", function (assert) {{ | |||||
// number of asserts | // number of asserts | ||||
assert.expect(1); | assert.expect(1); | ||||
frappe.run_serially('{doctype}', [ | |||||
frappe.run_serially([ | |||||
// insert a new {doctype} | // insert a new {doctype} | ||||
() => frappe.tests.make([ | |||||
() => frappe.tests.make('{doctype}', [ | |||||
// values to be set | // values to be set | ||||
{{key: 'value'}} | {{key: 'value'}} | ||||
]), | ]), | ||||
@@ -14,12 +14,13 @@ class TestVersion(unittest.TestCase): | |||||
new_doc = copy.deepcopy(old_doc) | new_doc = copy.deepcopy(old_doc) | ||||
old_doc.color = None | old_doc.color = None | ||||
new_doc.color = '#fafafa' | |||||
diff = get_diff(old_doc, new_doc)['changed'] | diff = get_diff(old_doc, new_doc)['changed'] | ||||
self.assertEquals(get_fieldnames(diff)[0], 'color') | self.assertEquals(get_fieldnames(diff)[0], 'color') | ||||
self.assertTrue(get_old_values(diff)[0] is None) | 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" | new_doc.starts_on = "2017-07-20" | ||||
@@ -312,9 +312,9 @@ | |||||
"bold": 0, | "bold": 0, | ||||
"collapsible": 0, | "collapsible": 0, | ||||
"columns": 0, | "columns": 0, | ||||
"default": "blue", | |||||
"default": "", | |||||
"fieldname": "color", | "fieldname": "color", | ||||
"fieldtype": "Select", | |||||
"fieldtype": "Color", | |||||
"hidden": 0, | "hidden": 0, | ||||
"ignore_user_permissions": 0, | "ignore_user_permissions": 0, | ||||
"ignore_xss_filter": 0, | "ignore_xss_filter": 0, | ||||
@@ -325,7 +325,7 @@ | |||||
"label": "Color", | "label": "Color", | ||||
"length": 0, | "length": 0, | ||||
"no_copy": 0, | "no_copy": 0, | ||||
"options": "red\ngreen\nblue\nyellow\nskyblue\norange", | |||||
"options": "", | |||||
"permlevel": 0, | "permlevel": 0, | ||||
"precision": "", | "precision": "", | ||||
"print_hide": 0, | "print_hide": 0, | ||||
@@ -895,8 +895,8 @@ | |||||
"issingle": 0, | "issingle": 0, | ||||
"istable": 0, | "istable": 0, | ||||
"max_attachments": 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", | "module": "Desk", | ||||
"name": "Event", | "name": "Event", | ||||
"owner": "Administrator", | "owner": "Administrator", | ||||
@@ -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() | |||||
]); | |||||
}); |
@@ -87,6 +87,7 @@ | |||||
"public/js/frappe/ui/messages.js", | "public/js/frappe/ui/messages.js", | ||||
"public/js/frappe/ui/keyboard.js", | "public/js/frappe/ui/keyboard.js", | ||||
"public/js/frappe/ui/emoji.js", | "public/js/frappe/ui/emoji.js", | ||||
"public/js/frappe/ui/colors.js", | |||||
"public/js/frappe/request.js", | "public/js/frappe/request.js", | ||||
"public/js/frappe/socketio_client.js", | "public/js/frappe/socketio_client.js", | ||||
@@ -73,7 +73,6 @@ th.fc-day-header { | |||||
background: #cfdce5 !important; | background: #cfdce5 !important; | ||||
} | } | ||||
.fc-day-grid-event { | .fc-day-grid-event { | ||||
background-color: rgba(94, 100, 255, 0.2) !important; | |||||
border: none !important; | border: none !important; | ||||
margin: 5px 4px 0 !important; | margin: 5px 4px 0 !important; | ||||
padding: 1px 5px !important; | padding: 1px 5px !important; | ||||
@@ -688,6 +688,8 @@ frappe.ui.form.ControlColor = frappe.ui.form.ControlData.extend({ | |||||
}, | }, | ||||
set_formatted_input: function(value) { | set_formatted_input: function(value) { | ||||
this._super(value); | this._super(value); | ||||
if(!value) value = '#ffffff'; | |||||
this.$input.css({ | this.$input.css({ | ||||
"background-color": value | "background-color": value | ||||
}); | }); | ||||
@@ -721,6 +723,9 @@ frappe.ui.form.ControlColor = frappe.ui.form.ControlData.extend({ | |||||
}); | }); | ||||
}, | }, | ||||
validate: function (value) { | validate: function (value) { | ||||
if(value === '') { | |||||
return ''; | |||||
} | |||||
var is_valid = /^#[0-9A-F]{6}$/i.test(value); | var is_valid = /^#[0-9A-F]{6}$/i.test(value); | ||||
if(is_valid) { | if(is_valid) { | ||||
return value; | return value; | ||||
@@ -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; | |||||
} | |||||
}; |
@@ -100,7 +100,8 @@ frappe.views.Calendar = Class.extend({ | |||||
color_map: { | color_map: { | ||||
"danger": "red", | "danger": "red", | ||||
"success": "green", | "success": "green", | ||||
"warning": "orange" | |||||
"warning": "orange", | |||||
"default": "blue" | |||||
}, | }, | ||||
get_system_datetime: function(date) { | get_system_datetime: function(date) { | ||||
date._offset = moment.user_utc_offset; | 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); | d.end = frappe.datetime.convert_to_user_tz(d.end); | ||||
me.fix_end_date_for_event_render(d); | 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; | 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) { | update_event: function(event, revertFunc) { | ||||
var me = this; | var me = this; | ||||
frappe.model.remove_from_locals(me.doctype, event.name); | frappe.model.remove_from_locals(me.doctype, event.name); | ||||
@@ -27,7 +27,7 @@ th.fc-widget-header { | |||||
.fc-unthemed .fc-today { | .fc-unthemed .fc-today { | ||||
background-color: #FFF !important; | background-color: #FFF !important; | ||||
.fc-day-number { | .fc-day-number { | ||||
background-color: @brand-primary; | background-color: @brand-primary; | ||||
min-width: 20px; | min-width: 20px; | ||||
@@ -90,7 +90,6 @@ th.fc-day-header { | |||||
} | } | ||||
.fc-day-grid-event { | .fc-day-grid-event { | ||||
background-color: rgba(94, 100, 255, 0.2) !important; | |||||
border: none !important; | border: none !important; | ||||
margin: 5px 4px 0 !important; | margin: 5px 4px 0 !important; | ||||
padding: 1px 5px !important; | padding: 1px 5px !important; | ||||
@@ -9,4 +9,5 @@ frappe/tests/ui/test_kanban/test_kanban_filters.js | |||||
frappe/tests/ui/test_kanban/test_kanban_column.js | frappe/tests/ui/test_kanban/test_kanban_column.js | ||||
frappe/core/doctype/report/test_query_report.js | frappe/core/doctype/report/test_query_report.js | ||||
frappe/tests/ui/test_linked_with.js | frappe/tests/ui/test_linked_with.js | ||||
frappe/custom/doctype/customize_form/test_customize_form.js | |||||
frappe/custom/doctype/customize_form/test_customize_form.js | |||||
frappe/desk/doctype/event/test_event.js |