Colors in Event Calendar using color picker (#3857)
* Add Color field to Event, show colored event in calendar * [minor] handle edge cases * Fix test boilerplate * Add test for event * fix codacy * fix test
This commit is contained in:
parent
c9df03b60d
commit
44ee24aa75
11 changed files with 196 additions and 29 deletions
|
|
@ -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",
|
||||
|
|
|
|||
36
frappe/desk/doctype/event/test_event.js
Normal file
36
frappe/desk/doctype/event/test_event.js
Normal file
|
|
@ -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;
|
||||
|
|
|
|||
121
frappe/public/js/frappe/ui/colors.js
Normal file
121
frappe/public/js/frappe/ui/colors.js
Normal file
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue