* 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 | |||
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'}} | |||
]), | |||
@@ -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" | |||
@@ -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", | |||
@@ -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/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", | |||
@@ -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; | |||
@@ -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; | |||
@@ -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: { | |||
"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); | |||
@@ -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; | |||
@@ -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 | |||
frappe/custom/doctype/customize_form/test_customize_form.js | |||
frappe/desk/doctype/event/test_event.js |