From 91861c776978b25bb92556ec4b1b71956360f7ca Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 23 Oct 2017 17:48:35 +0530 Subject: [PATCH 01/51] [minor] - watch js files - add frappe.db.get_list in js --- frappe/build.js | 10 +++++----- frappe/model/db_query.py | 7 +++++++ frappe/public/js/frappe/db.js | 21 +++++++++++++++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/frappe/build.js b/frappe/build.js index de12e31ca0..32bffe750b 100644 --- a/frappe/build.js +++ b/frappe/build.js @@ -60,11 +60,11 @@ function watch() { io.emit('reload_css', filename); } }); - // watch_js(function (filename) { - // if(socket_connection) { - // io.emit('reload_js', filename); - // } - // }); + watch_js(function (filename) { + // if(socket_connection) { + // io.emit('reload_js', filename); + // } + }); watch_build_json(); }); diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 0cd71684c9..3ef9be5b56 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -571,3 +571,10 @@ def get_order_by(doctype, meta): order_by = "`tab{0}`.docstatus asc, {1}".format(doctype, order_by) return order_by + + +@frappe.whitelist() +def get_list(doctype, *args, **kwargs): + '''wrapper for DatabaseQuery''' + kwargs.pop('cmd', None) + return DatabaseQuery(doctype).execute(None, *args, **kwargs) diff --git a/frappe/public/js/frappe/db.js b/frappe/public/js/frappe/db.js index 3cc34e3f79..30303f61b2 100644 --- a/frappe/public/js/frappe/db.js +++ b/frappe/public/js/frappe/db.js @@ -2,6 +2,27 @@ // MIT License. See license.txt frappe.db = { + get_list: function(doctype, args) { + if (!args) { + args = {}; + } + args.doctype = doctype; + if (!args.fields) { + args.fields = ['name']; + } + if (!args.limit) { + args.limit = 20; + } + return new Promise ((resolve) => { + frappe.call({ + method: 'frappe.model.db_query.get_list', + args: args, + callback: function(r) { + resolve(r.message); + } + }); + }); + }, exists: function(doctype, name) { return new Promise ((resolve) => { frappe.db.get_value(doctype, {name: name}, 'name').then((r) => { From 29e72a92f13a7d5c265f329c581071ec1057b03b Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 23 Oct 2017 17:49:14 +0530 Subject: [PATCH 02/51] Add Calendar View Doctype - get_events method - show dropdown in list view - save last calendar view --- frappe/desk/calendar.py | 17 ++ frappe/desk/doctype/calendar_view/__init__.py | 0 .../doctype/calendar_view/calendar_view.js | 29 +++ .../doctype/calendar_view/calendar_view.json | 184 ++++++++++++++++++ .../doctype/calendar_view/calendar_view.py | 10 + frappe/public/js/frappe/list/list_sidebar.js | 40 ++++ .../js/frappe/views/calendar/calendar.js | 57 +++++- 7 files changed, 328 insertions(+), 9 deletions(-) create mode 100644 frappe/desk/doctype/calendar_view/__init__.py create mode 100644 frappe/desk/doctype/calendar_view/calendar_view.js create mode 100644 frappe/desk/doctype/calendar_view/calendar_view.json create mode 100644 frappe/desk/doctype/calendar_view/calendar_view.py diff --git a/frappe/desk/calendar.py b/frappe/desk/calendar.py index d9cd03004a..efe4f3ef20 100644 --- a/frappe/desk/calendar.py +++ b/frappe/desk/calendar.py @@ -24,3 +24,20 @@ def get_event_conditions(doctype, filters=None): frappe.throw(_("Not Permitted"), frappe.PermissionError) return get_filters_cond(doctype, filters, [], with_match_conditions = True) + +@frappe.whitelist() +def get_events(doctype, start, end, field_map, filters=None, fields=None): + field_map = frappe._dict(json.loads(field_map)) + + if filters: + filters = json.loads(filters or '') + + if not fields: + fields = [field_map.start, field_map.end, field_map.title, 'name'] + + filters += [ + [doctype, field_map.start, '<=', end], + [doctype, field_map.end, '>=', start], + ] + + return frappe.get_list(doctype, fields=fields, filters=filters) diff --git a/frappe/desk/doctype/calendar_view/__init__.py b/frappe/desk/doctype/calendar_view/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/desk/doctype/calendar_view/calendar_view.js b/frappe/desk/doctype/calendar_view/calendar_view.js new file mode 100644 index 0000000000..2087932643 --- /dev/null +++ b/frappe/desk/doctype/calendar_view/calendar_view.js @@ -0,0 +1,29 @@ +// Copyright (c) 2017, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Calendar View', { + onload: function(frm) { + frm.trigger('reference_doctype'); + }, + reference_doctype: function(frm) { + const { reference_doctype } = frm.doc; + if (!reference_doctype) return; + + frappe.model.with_doctype(reference_doctype, () => { + const meta = frappe.get_meta(reference_doctype); + + const subject_options = meta.fields.filter( + df => !frappe.model.no_value_type.includes(df.fieldtype) + ).map(df => df.fieldname); + + const date_options = meta.fields.filter( + df => ['Date', 'Datetime'].includes(df.fieldtype) + ).map(df => df.fieldname) + + frm.set_df_property('subject_field', 'options', subject_options); + frm.set_df_property('start_date_field', 'options', date_options); + frm.set_df_property('end_date_field', 'options', date_options); + frm.refresh(); + }); + } +}); diff --git a/frappe/desk/doctype/calendar_view/calendar_view.json b/frappe/desk/doctype/calendar_view/calendar_view.json new file mode 100644 index 0000000000..bc6f356e56 --- /dev/null +++ b/frappe/desk/doctype/calendar_view/calendar_view.json @@ -0,0 +1,184 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "Prompt", + "beta": 0, + "creation": "2017-10-23 13:02:10.295824", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "reference_doctype", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Reference DocType", + "length": 0, + "no_copy": 0, + "options": "DocType", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subject_field", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Subject Field", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "start_date_field", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Start Date Field", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "end_date_field", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "End Date Field", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-10-23 13:32:33.994308", + "modified_by": "Administrator", + "module": "Desk", + "name": "Calendar View", + "name_case": "", + "owner": "faris@erpnext.com", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/desk/doctype/calendar_view/calendar_view.py b/frappe/desk/doctype/calendar_view/calendar_view.py new file mode 100644 index 0000000000..6bb9f6e90a --- /dev/null +++ b/frappe/desk/doctype/calendar_view/calendar_view.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class CalendarView(Document): + pass diff --git a/frappe/public/js/frappe/list/list_sidebar.js b/frappe/public/js/frappe/list/list_sidebar.js index 744a19f770..68f250da9d 100644 --- a/frappe/public/js/frappe/list/list_sidebar.js +++ b/frappe/public/js/frappe/list/list_sidebar.js @@ -27,6 +27,7 @@ frappe.views.ListSidebar = Class.extend({ this.setup_assigned_to_me(); this.setup_views(); this.setup_kanban_boards(); + this.setup_calendar_view(); this.setup_email_inbox(); let limits = frappe.boot.limits; @@ -271,6 +272,45 @@ frappe.views.ListSidebar = Class.extend({ } }); }, + setup_calendar_view: function() { + const doctype = this.doctype; + + frappe.db.get_list('Calendar View', { + filters: { + reference_doctype: doctype + } + }) + .then(result => { + if (!result) return; + const calendar_views = result; + const $link_calendar = this.sidebar.find('.list-link[data-view="Calendar"]'); + + let default_link = ''; + if (frappe.views.calendar[this.doctype]) { + // has standard calendar view + default_link = `
  • ${ __("Default") }
  • ` + } + const other_links = calendar_views.map( + calendar_view => `
  • + ${ __(calendar_view.name) } +
  • ` + ).join(''); + + const dropdown_html = ` +
    + + +
    + `; + + $link_calendar.html(dropdown_html); + }); + }, setup_email_inbox: function() { // get active email account for the user and add in dropdown if(this.doctype != "Communication") diff --git a/frappe/public/js/frappe/views/calendar/calendar.js b/frappe/public/js/frappe/views/calendar/calendar.js index f43e721cfb..508be3bcf0 100644 --- a/frappe/public/js/frappe/views/calendar/calendar.js +++ b/frappe/public/js/frappe/views/calendar/calendar.js @@ -8,14 +8,10 @@ frappe.views.CalendarView = frappe.views.ListRenderer.extend({ name: 'Calendar', render_view: function() { var me = this; - var options = { - doctype: this.doctype, - parent: this.wrapper, - page: this.list_view.page, - list_view: this.list_view - } - $.extend(options, frappe.views.calendar[this.doctype]); - this.calendar = new frappe.views.Calendar(options); + this.get_calendar_options() + .then(options => { + this.calendar = new frappe.views.Calendar(options); + }); }, set_defaults: function() { this._super(); @@ -27,6 +23,47 @@ frappe.views.CalendarView = frappe.views.ListRenderer.extend({ get_header_html: function() { return null; }, + get_calendar_options: function() { + let calendar_view = this.user_settings.last_calendar_view; + + if (!calendar_view) { + calendar_view = frappe.get_route()[3] || 'Default'; + } + + // save in user_settings + frappe.model.user_settings.save(this.doctype, 'Calendar', { + last_calendar_view: calendar_view + }); + + const options = { + doctype: this.doctype, + parent: this.wrapper, + page: this.list_view.page, + list_view: this.list_view + } + + return new Promise(resolve => { + if (calendar_view === 'Default') { + Object.assign(options, frappe.views.calendar[this.doctype]) + resolve(options); + } else { + + frappe.model.with_doc('Calendar View', calendar_view, (name, r) => { + const doc = frappe.get_doc('Calendar View', calendar_view); + Object.assign(options, { + field_map: { + id: "name", + start: doc.start_date_field, + end: doc.end_date_field, + title: doc.subject_field + } + }); + + resolve(options); + }); + } + }) + }, required_libs: [ 'assets/frappe/js/lib/fullcalendar/fullcalendar.min.css', 'assets/frappe/js/lib/fullcalendar/fullcalendar.min.js', @@ -127,6 +164,7 @@ frappe.views.Calendar = Class.extend({ args: me.get_args(start, end), callback: function(r) { var events = r.message; + console.log(events); events = me.prepare_events(events); callback(events); } @@ -202,7 +240,8 @@ frappe.views.Calendar = Class.extend({ doctype: this.doctype, start: this.get_system_datetime(start), end: this.get_system_datetime(end), - filters: this.list_view.filter_list.get_filters() + filters: this.list_view.filter_list.get_filters(), + field_map: this.field_map }; return args; }, From 2d156345aea6f6daff24685ba63b9b7ae27da4a9 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 23 Oct 2017 18:06:05 +0530 Subject: [PATCH 03/51] [minor] fallback for invalid start or end date --- frappe/public/js/frappe/views/calendar/calendar.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frappe/public/js/frappe/views/calendar/calendar.js b/frappe/public/js/frappe/views/calendar/calendar.js index 508be3bcf0..9126c4b0e3 100644 --- a/frappe/public/js/frappe/views/calendar/calendar.js +++ b/frappe/public/js/frappe/views/calendar/calendar.js @@ -271,6 +271,15 @@ frappe.views.Calendar = Class.extend({ d.start = frappe.datetime.convert_to_user_tz(d.start); d.end = frappe.datetime.convert_to_user_tz(d.end); + // show event on single day if start or end date is invalid + if (!frappe.datetime.validate(d.start) && d.end) { + d.start = frappe.datetime.add_days(d.end, -1); + } + + if (d.start && !frappe.datetime.validate(d.end)) { + d.end = frappe.datetime.add_days(d.start, 1); + } + me.fix_end_date_for_event_render(d); me.prepare_colors(d); return d; From 8c291c9b20ab56274fa89f8e8314df9b942f62df Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 23 Oct 2017 19:00:57 +0530 Subject: [PATCH 04/51] List renderer - load_last_view - refresh calendar view on route change --- frappe/public/js/frappe/list/list_renderer.js | 7 ++++ frappe/public/js/frappe/list/list_sidebar.js | 4 +-- frappe/public/js/frappe/list/list_view.js | 7 ++++ .../js/frappe/views/calendar/calendar.js | 36 +++++++++++++++---- 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/frappe/public/js/frappe/list/list_renderer.js b/frappe/public/js/frappe/list/list_renderer.js index cd844f6238..9c5e74fbab 100644 --- a/frappe/public/js/frappe/list/list_renderer.js +++ b/frappe/public/js/frappe/list/list_renderer.js @@ -74,6 +74,13 @@ frappe.views.ListRenderer = Class.extend({ should_refresh: function() { return this.list_view.current_view !== this.list_view.last_view; }, + load_last_view: function() { + // this function should handle loading the last view of your list_renderer, + // If you have a last view (for e.g last kanban board in Kanban View), + // load it using frappe.set_route and return true + // else return false + return false; + }, set_wrapper: function () { this.wrapper = this.list_view.wrapper && this.list_view.wrapper.find('.result-list'); }, diff --git a/frappe/public/js/frappe/list/list_sidebar.js b/frappe/public/js/frappe/list/list_sidebar.js index 68f250da9d..3cdd21a227 100644 --- a/frappe/public/js/frappe/list/list_sidebar.js +++ b/frappe/public/js/frappe/list/list_sidebar.js @@ -288,7 +288,7 @@ frappe.views.ListSidebar = Class.extend({ let default_link = ''; if (frappe.views.calendar[this.doctype]) { // has standard calendar view - default_link = `
  • ${ __("Default") }
  • ` + default_link = `
  • ${ __("Default") }
  • ` } const other_links = calendar_views.map( calendar_view => `
  • @@ -307,7 +307,7 @@ frappe.views.ListSidebar = Class.extend({ `; - + $link_calendar.removeClass('hide'); $link_calendar.html(dropdown_html); }); }, diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 42f2bd3aa2..533ca903c4 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -399,6 +399,13 @@ frappe.views.ListView = frappe.ui.BaseList.extend({ if (this.list_renderer.should_refresh()) { this.setup_list_renderer(); + + if (this.list_renderer.load_last_view && this.list_renderer.load_last_view()) { + // let the list_renderer load the last view for the current view + // for e.g last kanban board for kanban view + return; + } + this.refresh_surroundings(); this.dirty = true; } diff --git a/frappe/public/js/frappe/views/calendar/calendar.js b/frappe/public/js/frappe/views/calendar/calendar.js index 9126c4b0e3..024d2dbbde 100644 --- a/frappe/public/js/frappe/views/calendar/calendar.js +++ b/frappe/public/js/frappe/views/calendar/calendar.js @@ -13,6 +13,21 @@ frappe.views.CalendarView = frappe.views.ListRenderer.extend({ this.calendar = new frappe.views.Calendar(options); }); }, + load_last_view: function() { + const route = frappe.get_route(); + + if (!route[3]) { + // routed to Calendar view, check last calendar_view + let calendar_view = this.user_settings.last_calendar_view; + + if (calendar_view) { + frappe.set_route('List', this.doctype, 'Calendar', calendar_view); + return true; + } + } + + return false; + }, set_defaults: function() { this._super(); this.page_title = this.page_title + ' ' + __('Calendar'); @@ -23,12 +38,22 @@ frappe.views.CalendarView = frappe.views.ListRenderer.extend({ get_header_html: function() { return null; }, - get_calendar_options: function() { - let calendar_view = this.user_settings.last_calendar_view; - - if (!calendar_view) { - calendar_view = frappe.get_route()[3] || 'Default'; + should_refresh: function() { + var should_refresh = this._super(); + if(!should_refresh) { + this.last_calendar_view = this.current_calendar_view || ''; + this.current_calendar_view = this.get_calendar_view(); + this.page_title = __(this.get_calendar_view()); + + should_refresh = this.current_calendar_view !== this.last_calendar_view; } + return should_refresh; + }, + get_calendar_view: function() { + return frappe.get_route()[3]; + }, + get_calendar_options: function() { + const calendar_view = frappe.get_route()[3] || 'Default'; // save in user_settings frappe.model.user_settings.save(this.doctype, 'Calendar', { @@ -164,7 +189,6 @@ frappe.views.Calendar = Class.extend({ args: me.get_args(start, end), callback: function(r) { var events = r.message; - console.log(events); events = me.prepare_events(events); callback(events); } From 1fbc8ff448c138d220ae2d97a314dd2e7a1b4ab8 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 23 Oct 2017 19:06:59 +0530 Subject: [PATCH 05/51] Add Show Calendar button --- frappe/desk/doctype/calendar_view/calendar_view.js | 6 ++++++ frappe/public/js/frappe/views/calendar/calendar.js | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/frappe/desk/doctype/calendar_view/calendar_view.js b/frappe/desk/doctype/calendar_view/calendar_view.js index 2087932643..e93473f61e 100644 --- a/frappe/desk/doctype/calendar_view/calendar_view.js +++ b/frappe/desk/doctype/calendar_view/calendar_view.js @@ -5,6 +5,12 @@ frappe.ui.form.on('Calendar View', { onload: function(frm) { frm.trigger('reference_doctype'); }, + refresh: function(frm) { + if (!frm.is_new()) { + frm.add_custom_button(__('Show Calendar'), + () => frappe.set_route('List', frm.doc.reference_doctype, 'Calendar', frm.doc.name)) + } + }, reference_doctype: function(frm) { const { reference_doctype } = frm.doc; if (!reference_doctype) return; diff --git a/frappe/public/js/frappe/views/calendar/calendar.js b/frappe/public/js/frappe/views/calendar/calendar.js index 024d2dbbde..54d2bb2b6c 100644 --- a/frappe/public/js/frappe/views/calendar/calendar.js +++ b/frappe/public/js/frappe/views/calendar/calendar.js @@ -43,7 +43,12 @@ frappe.views.CalendarView = frappe.views.ListRenderer.extend({ if(!should_refresh) { this.last_calendar_view = this.current_calendar_view || ''; this.current_calendar_view = this.get_calendar_view(); - this.page_title = __(this.get_calendar_view()); + + if (this.current_calendar_view !== 'Default') { + this.page_title = __(this.current_calendar_view); + } else { + this.page_title = this.doctype + ' ' + __('Calendar'); + } should_refresh = this.current_calendar_view !== this.last_calendar_view; } From bc45beb8689217ffc2b1880feb6121efd9de1ba9 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 23 Oct 2017 20:10:15 +0530 Subject: [PATCH 06/51] Show record count in list header --- frappe/model/db_query.py | 10 ++++++++ frappe/public/js/frappe/list/list_renderer.js | 23 +++++++++++++++++++ frappe/utils/data.py | 10 ++++++++ 3 files changed, 43 insertions(+) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 3ef9be5b56..26075f4daa 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -578,3 +578,13 @@ def get_list(doctype, *args, **kwargs): '''wrapper for DatabaseQuery''' kwargs.pop('cmd', None) return DatabaseQuery(doctype).execute(None, *args, **kwargs) + +@frappe.whitelist() +def get_count(doctype, filters=None): + if filters: + filters = json.loads(filters) + + if isinstance(filters, list): + filters = frappe.utils.make_filter_dict(filters) + + return frappe.db.count(doctype, filters=filters) diff --git a/frappe/public/js/frappe/list/list_renderer.js b/frappe/public/js/frappe/list/list_renderer.js index 9c5e74fbab..446fadbdd7 100644 --- a/frappe/public/js/frappe/list/list_renderer.js +++ b/frappe/public/js/frappe/list/list_renderer.js @@ -355,6 +355,29 @@ frappe.views.ListRenderer = Class.extend({ this.render_tags($item_container, value); }); + this.render_count(values.length); + }, + + render_count: function(current_count) { + console.log(this) + const $header_right = this.list_view.list_header.find('.list-item__content--activity'); + + frappe.call({ + method: 'frappe.model.db_query.get_count', + args: { + doctype: this.doctype, + filters: this.list_view.get_filters_args() + } + }).then(r => { + const count = r.message; + const $html = $(`${current_count} of ${count}`); + + $html.css({ + marginRight: '10px' + }) + $header_right.addClass('text-muted'); + $header_right.html($html); + }) }, // returns html for a data item, diff --git a/frappe/utils/data.py b/frappe/utils/data.py index c1e498fc27..e0ed483fad 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -781,6 +781,16 @@ def make_filter_tuple(doctype, key, value): else: return [doctype, key, "=", value] +def make_filter_dict(filters): + '''convert this [[doctype, key, operator, value], ..] + to this { key: (operator, value), .. } + ''' + _filter = frappe._dict() + for f in filters: + _filter[f[1]] = (f[2], f[3]) + + return _filter + def scrub_urls(html): html = expand_relative_urls(html) # encoding should be responsibility of the composer From b2a6a86e1517badf9ec81b0ee61bf6fcb5b2fc1e Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 24 Oct 2017 12:35:23 +0530 Subject: [PATCH 07/51] minor --- frappe/public/js/frappe/views/calendar/calendar.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/views/calendar/calendar.js b/frappe/public/js/frappe/views/calendar/calendar.js index 54d2bb2b6c..3845404907 100644 --- a/frappe/public/js/frappe/views/calendar/calendar.js +++ b/frappe/public/js/frappe/views/calendar/calendar.js @@ -318,10 +318,8 @@ frappe.views.Calendar = Class.extend({ 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_hex(color_name) ? - color_name : - 'blue'; + color_name = frappe.ui.color.validate_hex(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 { From 0176e202fa245d89deea51072bdf3664ce26697d Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 24 Oct 2017 18:40:17 +0530 Subject: [PATCH 08/51] fix codacy --- frappe/build.js | 5 +++-- frappe/desk/doctype/calendar_view/calendar_view.js | 4 ++-- frappe/desk/doctype/calendar_view/calendar_view.py | 1 - frappe/public/js/frappe/list/list_renderer.js | 1 - frappe/public/js/frappe/list/list_sidebar.js | 3 +-- frappe/public/js/frappe/views/calendar/calendar.js | 4 ++-- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/frappe/build.js b/frappe/build.js index 32bffe750b..9323860906 100644 --- a/frappe/build.js +++ b/frappe/build.js @@ -60,11 +60,12 @@ function watch() { io.emit('reload_css', filename); } }); - watch_js(function (filename) { + watch_js(//function (filename) { // if(socket_connection) { // io.emit('reload_js', filename); // } - }); + //} + ); watch_build_json(); }); diff --git a/frappe/desk/doctype/calendar_view/calendar_view.js b/frappe/desk/doctype/calendar_view/calendar_view.js index e93473f61e..a58a9555db 100644 --- a/frappe/desk/doctype/calendar_view/calendar_view.js +++ b/frappe/desk/doctype/calendar_view/calendar_view.js @@ -8,7 +8,7 @@ frappe.ui.form.on('Calendar View', { refresh: function(frm) { if (!frm.is_new()) { frm.add_custom_button(__('Show Calendar'), - () => frappe.set_route('List', frm.doc.reference_doctype, 'Calendar', frm.doc.name)) + () => frappe.set_route('List', frm.doc.reference_doctype, 'Calendar', frm.doc.name)); } }, reference_doctype: function(frm) { @@ -24,7 +24,7 @@ frappe.ui.form.on('Calendar View', { const date_options = meta.fields.filter( df => ['Date', 'Datetime'].includes(df.fieldtype) - ).map(df => df.fieldname) + ).map(df => df.fieldname); frm.set_df_property('subject_field', 'options', subject_options); frm.set_df_property('start_date_field', 'options', date_options); diff --git a/frappe/desk/doctype/calendar_view/calendar_view.py b/frappe/desk/doctype/calendar_view/calendar_view.py index 6bb9f6e90a..ae8ab1eb46 100644 --- a/frappe/desk/doctype/calendar_view/calendar_view.py +++ b/frappe/desk/doctype/calendar_view/calendar_view.py @@ -3,7 +3,6 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe from frappe.model.document import Document class CalendarView(Document): diff --git a/frappe/public/js/frappe/list/list_renderer.js b/frappe/public/js/frappe/list/list_renderer.js index 446fadbdd7..7a831b6606 100644 --- a/frappe/public/js/frappe/list/list_renderer.js +++ b/frappe/public/js/frappe/list/list_renderer.js @@ -359,7 +359,6 @@ frappe.views.ListRenderer = Class.extend({ }, render_count: function(current_count) { - console.log(this) const $header_right = this.list_view.list_header.find('.list-item__content--activity'); frappe.call({ diff --git a/frappe/public/js/frappe/list/list_sidebar.js b/frappe/public/js/frappe/list/list_sidebar.js index 3cdd21a227..7c6ba5c14d 100644 --- a/frappe/public/js/frappe/list/list_sidebar.js +++ b/frappe/public/js/frappe/list/list_sidebar.js @@ -279,8 +279,7 @@ frappe.views.ListSidebar = Class.extend({ filters: { reference_doctype: doctype } - }) - .then(result => { + }).then(result => { if (!result) return; const calendar_views = result; const $link_calendar = this.sidebar.find('.list-link[data-view="Calendar"]'); diff --git a/frappe/public/js/frappe/views/calendar/calendar.js b/frappe/public/js/frappe/views/calendar/calendar.js index 3845404907..45c15990bf 100644 --- a/frappe/public/js/frappe/views/calendar/calendar.js +++ b/frappe/public/js/frappe/views/calendar/calendar.js @@ -74,11 +74,11 @@ frappe.views.CalendarView = frappe.views.ListRenderer.extend({ return new Promise(resolve => { if (calendar_view === 'Default') { - Object.assign(options, frappe.views.calendar[this.doctype]) + Object.assign(options, frappe.views.calendar[this.doctype]); resolve(options); } else { - frappe.model.with_doc('Calendar View', calendar_view, (name, r) => { + frappe.model.with_doc('Calendar View', calendar_view, () => { const doc = frappe.get_doc('Calendar View', calendar_view); Object.assign(options, { field_map: { From 4d26930bba51b0adac42afcc3a2323e991f80e7d Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 25 Oct 2017 12:10:05 +0530 Subject: [PATCH 09/51] fix codacy --- frappe/public/js/frappe/list/list_sidebar.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/list/list_sidebar.js b/frappe/public/js/frappe/list/list_sidebar.js index 7c6ba5c14d..e7ff2b3330 100644 --- a/frappe/public/js/frappe/list/list_sidebar.js +++ b/frappe/public/js/frappe/list/list_sidebar.js @@ -287,7 +287,8 @@ frappe.views.ListSidebar = Class.extend({ let default_link = ''; if (frappe.views.calendar[this.doctype]) { // has standard calendar view - default_link = `
  • ${ __("Default") }
  • ` + default_link = `
  • + ${ __("Default") }
  • `; } const other_links = calendar_views.map( calendar_view => `
  • From b5c4278341e1780b1325c603bacc2d352031c87d Mon Sep 17 00:00:00 2001 From: Achilles Rasquinha Date: Wed, 25 Oct 2017 15:33:30 +0530 Subject: [PATCH 10/51] BCC in Emails (#4377) * added blank carbon copy * [FEAT] BCC in emails * removed test_communication, fixed comment * fixed codacy --- frappe/__init__.py | 4 +- .../doctype/communication/communication.json | 101 ++++++++++++++---- .../doctype/communication/communication.py | 8 +- frappe/core/doctype/communication/email.py | 68 ++++++++++-- frappe/email/email_body.py | 11 +- frappe/email/queue.py | 10 +- .../public/js/frappe/views/communication.js | 10 +- 7 files changed, 166 insertions(+), 46 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 0d77a099df..1245228fd8 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -378,7 +378,7 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message as_markdown=False, delayed=True, reference_doctype=None, reference_name=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, attachments=None, content=None, doctype=None, name=None, reply_to=None, - cc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None, + cc=[], bcc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None, send_priority=1, communication=None, retry=1, now=None, read_receipt=None, is_notification=False, inline_images=None, template=None, args=None, header=None): """Send email using user's default **Email Account** or global default **Email Account**. @@ -426,7 +426,7 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message subject=subject, message=message, text_content=text_content, reference_doctype = doctype or reference_doctype, reference_name = name or reference_name, unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message, - attachments=attachments, reply_to=reply_to, cc=cc, message_id=message_id, in_reply_to=in_reply_to, + attachments=attachments, reply_to=reply_to, cc=cc, bcc=bcc, message_id=message_id, in_reply_to=in_reply_to, send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority, communication=communication, now=now, read_receipt=read_receipt, is_notification=is_notification, inline_images=inline_images, header=header) diff --git a/frappe/core/doctype/communication/communication.json b/frappe/core/doctype/communication/communication.json index f706f1ea77..c9c1715ae0 100644 --- a/frappe/core/doctype/communication/communication.json +++ b/frappe/core/doctype/communication/communication.json @@ -15,6 +15,7 @@ "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -43,6 +44,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -73,6 +75,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -104,6 +107,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -134,6 +138,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -162,6 +167,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -192,6 +198,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -223,6 +230,39 @@ "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.communication_medium===\"Email\"", + "fieldname": "bcc", + "fieldtype": "Code", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "BCC", + "length": 0, + "no_copy": 0, + "options": "Email", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -252,6 +292,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -283,6 +324,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -311,6 +353,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -340,6 +383,7 @@ "width": "400" }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -369,6 +413,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -398,6 +443,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -429,6 +475,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -459,6 +506,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -487,6 +535,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -518,6 +567,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -548,6 +598,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -576,6 +627,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -605,6 +657,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -634,6 +687,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -662,6 +716,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -691,6 +746,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -720,6 +776,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -750,6 +807,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -780,6 +838,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -810,6 +869,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -841,6 +901,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -871,6 +932,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -901,6 +963,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -929,6 +992,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -959,6 +1023,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -989,6 +1054,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1019,6 +1085,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1049,6 +1116,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1079,6 +1147,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1109,6 +1178,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1138,6 +1208,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1166,6 +1237,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -1195,6 +1267,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1224,6 +1297,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -1253,6 +1327,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1283,6 +1358,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1312,6 +1388,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, @@ -1342,6 +1419,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1371,6 +1449,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -1411,7 +1490,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-03-29 23:06:16.469149", + "modified": "2017-10-25 12:53:49.547620", "modified_by": "Administrator", "module": "Core", "name": "Communication", @@ -1477,26 +1556,6 @@ "submit": 0, "user_permission_doctypes": "[\"Email Account\"]", "write": 0 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Super Email User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 } ], "quick_entry": 0, diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 5a63381d91..c5b6d0c77e 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -189,7 +189,7 @@ class Communication(Document): self.notify(print_html, print_format, attachments, recipients) def notify(self, print_html=None, print_format=None, attachments=None, - recipients=None, cc=None, fetched_from_email_account=False): + recipients=None, cc=None, bcc=None,fetched_from_email_account=False): """Calls a delayed task 'sendmail' that enqueus email in Email Queue queue :param print_html: Send given value as HTML attachment @@ -200,13 +200,13 @@ class Communication(Document): :param fetched_from_email_account: True when pulling email, the notification shouldn't go to the main recipient """ - notify(self, print_html, print_format, attachments, recipients, cc, + notify(self, print_html, print_format, attachments, recipients, cc, bcc, fetched_from_email_account) def _notify(self, print_html=None, print_format=None, attachments=None, - recipients=None, cc=None): + recipients=None, cc=None, bcc=None): - _notify(self, print_html, print_format, attachments, recipients, cc) + _notify(self, print_html, print_format, attachments, recipients, cc, bcc) def bot_reply(self): if self.comment_type == 'Bot' and self.communication_type == 'Chat': diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index 28c2e5ff0d..ac69cc24e2 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -22,7 +22,7 @@ from frappe.utils.background_jobs import enqueue @frappe.whitelist() def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", sender=None, sender_full_name=None, recipients=None, communication_medium="Email", send_email=False, - print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, cc=None, flags=None,read_receipt=None): + print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, cc=None, bcc=None, flags=None,read_receipt=None): """Make a new communication. :param doctype: Reference DocType. @@ -58,6 +58,7 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "sender_full_name":sender_full_name, "recipients": recipients, "cc": cc or None, + "bcc": bcc or None, "communication_medium": communication_medium, "sent_or_received": sent_or_received, "reference_doctype": doctype, @@ -102,10 +103,13 @@ def validate_email(doc): for email in split_emails(doc.cc): validate_email_add(email, throw=True) + for email in split_emails(doc.bcc): + validate_email_add(email, throw=True) + # validate sender def notify(doc, print_html=None, print_format=None, attachments=None, - recipients=None, cc=None, fetched_from_email_account=False): + recipients=None, cc=None, bcc=None, fetched_from_email_account=False): """Calls a delayed task 'sendmail' that enqueus email in Email Queue queue :param print_html: Send given value as HTML attachment @@ -113,10 +117,11 @@ def notify(doc, print_html=None, print_format=None, attachments=None, :param attachments: A list of filenames that should be attached when sending this email :param recipients: Email recipients :param cc: Send email as CC to + :param bcc: Send email as BCC to :param fetched_from_email_account: True when pulling email, the notification shouldn't go to the main recipient """ - recipients, cc = get_recipients_and_cc(doc, recipients, cc, + recipients, cc, bcc = get_recipients_cc_and_bcc(doc, recipients, cc, bcc, fetched_from_email_account=fetched_from_email_account) if not recipients: @@ -127,16 +132,16 @@ def notify(doc, print_html=None, print_format=None, attachments=None, if frappe.flags.in_test: # for test cases, run synchronously doc._notify(print_html=print_html, print_format=print_format, attachments=attachments, - recipients=recipients, cc=cc) + recipients=recipients, cc=cc, bcc=None) else: check_email_limit(list(set(doc.sent_email_addresses))) enqueue(sendmail, queue="default", timeout=300, event="sendmail", communication_name=doc.name, print_html=print_html, print_format=print_format, attachments=attachments, - recipients=recipients, cc=cc, lang=frappe.local.lang, session=frappe.local.session) + recipients=recipients, cc=cc, bcc=bcc, lang=frappe.local.lang, session=frappe.local.session) def _notify(doc, print_html=None, print_format=None, attachments=None, - recipients=None, cc=None): + recipients=None, cc=None, bcc=None): prepare_to_notify(doc, print_html, print_format, attachments) @@ -148,6 +153,7 @@ def _notify(doc, print_html=None, print_format=None, attachments=None, frappe.sendmail( recipients=(recipients or []), cc=(cc or []), + bcc=(bcc or []), expose_recipients="header", sender=doc.sender, reply_to=doc.incoming_email_account, @@ -190,7 +196,7 @@ def update_parent_mins_to_first_response(doc): parent.run_method('notify_communication', doc) parent.notify_update() -def get_recipients_and_cc(doc, recipients, cc, fetched_from_email_account=False): +def get_recipients_cc_and_bcc(doc, recipients, cc, bcc, fetched_from_email_account=False): doc.all_email_addresses = [] doc.sent_email_addresses = [] doc.previous_email_sender = None @@ -201,6 +207,9 @@ def get_recipients_and_cc(doc, recipients, cc, fetched_from_email_account=False) if not cc: cc = get_cc(doc, recipients, fetched_from_email_account=fetched_from_email_account) + if not bcc: + bcc = get_bcc(doc, recipients, fetched_from_email_account=fetched_from_email_account) + if fetched_from_email_account: # email was already sent to the original recipient by the sender's email service original_recipients, recipients = recipients, [] @@ -216,10 +225,13 @@ def get_recipients_and_cc(doc, recipients, cc, fetched_from_email_account=False) # don't cc to people who already received the mail from sender's email service cc = list(set(cc) - set(original_cc) - set(original_recipients)) + original_bcc = split_emails(doc.bcc) + bcc = list(set(bcc) - set(original_bcc) - set(original_recipients)) + if 'Administrator' in recipients: recipients.remove('Administrator') - return recipients, cc + return recipients, cc, bcc def prepare_to_notify(doc, print_html=None, print_format=None, attachments=None): """Prepare to make multipart MIME Email @@ -345,6 +357,34 @@ def get_cc(doc, recipients=None, fetched_from_email_account=False): return cc +def get_bcc(doc, recipients=None, fetched_from_email_account=False): + """Build a list of email addresses for BCC""" + bcc = split_emails(doc.bcc) + + if doc.reference_doctype and doc.reference_name: + if fetched_from_email_account: + bcc.append(get_owner_email(doc)) + bcc += get_assignees(doc) + + if getattr(doc, "send_me_a_copy", False) and doc.sender not in bcc: + bcc.append(doc.sender) + + if bcc: + exclude = [] + exclude += [d[0] for d in frappe.db.get_all("User", ["name"], {"thread_notify": 0}, as_list=True)] + exclude += [(parse_addr(email)[1] or "").lower() for email in recipients] + + if fetched_from_email_account: + # exclude sender when pulling email + exclude += [parse_addr(doc.sender)[1]] + + if doc.reference_doctype and doc.reference_name: + exclude += [d[0] for d in frappe.db.get_all("Email Unsubscribe", ["email"], + {"reference_doctype": doc.reference_doctype, "reference_name": doc.reference_name}, as_list=True)] + + bcc = filter_email_list(doc, bcc, exclude, is_bcc=True) + + return bcc def add_attachments(name, attachments): '''Add attachments to the given Communiction''' @@ -360,7 +400,7 @@ def add_attachments(name, attachments): save_url(attach.file_url, attach.file_name, "Communication", name, "Home/Attachments", attach.is_private) -def filter_email_list(doc, email_list, exclude, is_cc=False): +def filter_email_list(doc, email_list, exclude, is_cc=False, is_bcc=False): # temp variables filtered = [] email_address_list = [] @@ -382,6 +422,11 @@ def filter_email_list(doc, email_list, exclude, is_cc=False): # don't send to disabled users continue + if is_bcc: + is_user_enabled = frappe.db.get_value("User", email_address, "enabled") + if is_user_enabled==0: + continue + # make sure of case-insensitive uniqueness of email address if email_address not in email_address_list: # append the full email i.e. "Human " @@ -416,7 +461,7 @@ def get_attach_link(doc, print_format): }) def sendmail(communication_name, print_html=None, print_format=None, attachments=None, - recipients=None, cc=None, lang=None, session=None): + recipients=None, cc=None, bcc=None, lang=None, session=None): try: if lang: @@ -432,7 +477,7 @@ def sendmail(communication_name, print_html=None, print_format=None, attachments try: communication = frappe.get_doc("Communication", communication_name) communication._notify(print_html=print_html, print_format=print_format, attachments=attachments, - recipients=recipients, cc=cc) + recipients=recipients, cc=cc, bcc=bcc) except MySQLdb.OperationalError as e: # deadlock, try again @@ -453,6 +498,7 @@ def sendmail(communication_name, print_html=None, print_format=None, attachments "attachments": attachments, "recipients": recipients, "cc": cc, + "bcc": bcc, "lang": lang })) frappe.logger(__name__).error(traceback) diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py index b3a947312f..799ba2eeec 100755 --- a/frappe/email/email_body.py +++ b/frappe/email/email_body.py @@ -15,7 +15,7 @@ from email.header import Header def get_email(recipients, sender='', msg='', subject='[No Subject]', text_content = None, footer=None, print_html=None, formatted=None, attachments=None, - content=None, reply_to=None, cc=[], email_account=None, expose_recipients=None, + content=None, reply_to=None, cc=[], bcc=[], email_account=None, expose_recipients=None, inline_images=[], header=None): """ Prepare an email with the following format: - multipart/mixed @@ -27,7 +27,7 @@ def get_email(recipients, sender='', msg='', subject='[No Subject]', - attachment """ content = content or msg - emailobj = EMail(sender, recipients, subject, reply_to=reply_to, cc=cc, email_account=email_account, expose_recipients=expose_recipients) + emailobj = EMail(sender, recipients, subject, reply_to=reply_to, cc=cc, bcc=bcc, email_account=email_account, expose_recipients=expose_recipients) if not content.strip().startswith("<"): content = markdown(content) @@ -51,7 +51,7 @@ class EMail: Also provides a clean way to add binary `FileData` attachments Also sets all messages as multipart/alternative for cleaner reading in text-only clients """ - def __init__(self, sender='', recipients=(), subject='', alternative=0, reply_to=None, cc=(), email_account=None, expose_recipients=None): + def __init__(self, sender='', recipients=(), subject='', alternative=0, reply_to=None, cc=(), bcc=(), email_account=None, expose_recipients=None): from email import charset as Charset Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8') @@ -72,6 +72,7 @@ class EMail: self.msg_alternative = MIMEMultipart('alternative') self.msg_root.attach(self.msg_alternative) self.cc = cc or [] + self.bcc = bcc or [] self.html_set = False self.email_account = email_account or get_outgoing_email_account(sender=sender) @@ -176,8 +177,9 @@ class EMail: self.recipients = [strip(r) for r in self.recipients] self.cc = [strip(r) for r in self.cc] + self.bcc = [strip(r) for r in self.bcc] - for e in self.recipients + (self.cc or []): + for e in self.recipients + (self.cc or []) + (self.bcc or []): validate_email_add(e, True) def replace_sender(self): @@ -207,6 +209,7 @@ class EMail: "To": ', '.join(self.recipients) if self.expose_recipients=="header" else "", "Date": email.utils.formatdate(), "Reply-To": self.reply_to if self.reply_to else None, + "Bcc": ', '.join(self.bcc) if self.bcc else None, "CC": ', '.join(self.cc) if self.cc and self.expose_recipients=="header" else None, 'X-Frappe-Site': get_url(), } diff --git a/frappe/email/queue.py b/frappe/email/queue.py index d34592f055..91ac4cf06a 100755 --- a/frappe/email/queue.py +++ b/frappe/email/queue.py @@ -21,7 +21,7 @@ class EmailLimitCrossedError(frappe.ValidationError): pass def send(recipients=None, sender=None, subject=None, message=None, text_content=None, reference_doctype=None, reference_name=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, - attachments=None, reply_to=None, cc=[], message_id=None, in_reply_to=None, send_after=None, + attachments=None, reply_to=None, cc=[], bcc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None, send_priority=1, communication=None, now=False, read_receipt=None, queue_separately=False, is_notification=False, add_unsubscribe_link=1, inline_images=None, header=None): @@ -61,6 +61,9 @@ def send(recipients=None, sender=None, subject=None, message=None, text_content= if isinstance(cc, string_types): cc = split_emails(cc) + if isinstance(bcc, string_types): + bcc = split_emails(bcc) + if isinstance(send_after, int): send_after = add_days(nowdate(), send_after) @@ -112,6 +115,7 @@ def send(recipients=None, sender=None, subject=None, message=None, text_content= attachments=attachments, reply_to=reply_to, cc=cc, + bcc=bcc, message_id=message_id, in_reply_to=in_reply_to, send_after=send_after, @@ -174,6 +178,7 @@ def get_email_queue(recipients, sender, subject, **kwargs): attachments=kwargs.get('attachments'), reply_to=kwargs.get('reply_to'), cc=kwargs.get('cc'), + bcc=kwargs.get('bcc'), email_account=kwargs.get('email_account'), expose_recipients=kwargs.get('expose_recipients'), inline_images=kwargs.get('inline_images'), @@ -194,7 +199,7 @@ def get_email_queue(recipients, sender, subject, **kwargs): frappe.log_error('Invalid Email ID Sender: {0}, Recipients: {1}'.format(mail.sender, ', '.join(mail.recipients)), 'Email Not Sent') - e.set_recipients(recipients + kwargs.get('cc', [])) + e.set_recipients(recipients + kwargs.get('cc', []) + kwargs.get('bcc', [])) e.reference_doctype = kwargs.get('reference_doctype') e.reference_name = kwargs.get('reference_name') e.add_unsubscribe_link = kwargs.get("add_unsubscribe_link") @@ -204,6 +209,7 @@ def get_email_queue(recipients, sender, subject, **kwargs): e.communication = kwargs.get('communication') e.send_after = kwargs.get('send_after') e.show_as_cc = ",".join(kwargs.get('cc', [])) + e.show_as_bcc = ",".join(kwargs.get('bcc', [])) e.insert(ignore_permissions=True) return e diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 42924f47b9..ea02974f28 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -48,8 +48,9 @@ frappe.views.CommunicationComposer = Class.extend({ get_fields: function() { var fields= [ {label:__("To"), fieldtype:"Data", reqd: 0, fieldname:"recipients",length:524288}, - {fieldtype: "Section Break", collapsible: 1, label: __("CC & Standard Reply")}, + {fieldtype: "Section Break", collapsible: 1, label: __("CC, BCC & Standard Reply")}, {label:__("CC"), fieldtype:"Data", fieldname:"cc", length:524288}, + {label:__("BCC"), fieldtype:"Data", fieldname:"bcc", length:524288}, {label:__("Standard Reply"), fieldtype:"Link", options:"Standard Reply", fieldname:"standard_reply"}, {fieldtype: "Section Break"}, @@ -109,6 +110,7 @@ frappe.views.CommunicationComposer = Class.extend({ this.dialog.fields_dict.recipients.set_value(this.recipients || ''); this.dialog.fields_dict.cc.set_value(this.cc || ''); + this.dialog.fields_dict.bcc.set_value(this.bcc || ''); if(this.dialog.fields_dict.sender) { this.dialog.fields_dict.sender.set_value(this.sender || ''); @@ -123,6 +125,7 @@ frappe.views.CommunicationComposer = Class.extend({ if(!this.forward && !this.recipients && this.last_email) { this.recipients = this.last_email.sender; this.cc = this.last_email.cc; + this.bcc = this.last_email.bcc; } if(!this.forward && !this.recipients) { @@ -446,6 +449,7 @@ frappe.views.CommunicationComposer = Class.extend({ // concat in cc if ( form_values[df.fieldname] ) { form_values.cc = ( form_values.cc ? (form_values.cc + ", ") : "" ) + df.fieldname; + form_values.bcc = ( form_values.bcc ? (form_values.bcc + ", ") : "" ) + df.fieldname; } delete form_values[df.fieldname]; @@ -484,6 +488,7 @@ frappe.views.CommunicationComposer = Class.extend({ args: { recipients: form_values.recipients, cc: form_values.cc, + bcc: form_values.bcc, subject: form_values.subject, content: form_values.content, doctype: me.doc.doctype, @@ -594,7 +599,8 @@ frappe.views.CommunicationComposer = Class.extend({ var me = this; [ this.dialog.fields_dict.recipients.input, - this.dialog.fields_dict.cc.input + this.dialog.fields_dict.cc.input, + this.dialog.fields_dict.bcc.input ].map(function(input) { me.setup_awesomplete_for_input(input); }); From b51d9a04b1735359a891c56851980a8a8bb0fc6f Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 25 Oct 2017 18:02:06 +0530 Subject: [PATCH 11/51] [enhance] build only changed files, get others from cache (#4380) --- frappe/build.js | 56 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/frappe/build.js b/frappe/build.js index 9323860906..7f4f446644 100644 --- a/frappe/build.js +++ b/frappe/build.js @@ -20,6 +20,7 @@ const apps = apps_contents.split('\n'); const app_paths = apps.map(app => path_join(apps_path, app, app)) // base_path of each app const assets_path = path_join(sites_path, 'assets'); let build_map = make_build_map(); +let compiled_js_cache = {}; // cache each js file after it is compiled const file_watcher_port = get_conf().file_watcher_port; // command line args @@ -78,9 +79,7 @@ function watch() { }); } -function pack(output_path, inputs, minify) { - const output_type = output_path.split('.').pop(); - +function pack(output_path, inputs, minify, file_changed) { let output_txt = ''; for (const file of inputs) { @@ -89,25 +88,18 @@ function pack(output_path, inputs, minify) { continue; } - let file_content = fs.readFileSync(file, 'utf-8'); - - if (file.endsWith('.html') && output_type === 'js') { - file_content = html_to_js_template(file, file_content); - } - - if(file.endsWith('class.js')) { - file_content = minify_js(file_content, file); + let force_compile = false; + if (file_changed) { + // if file_changed is passed and is equal to file, force_compile it + force_compile = file_changed === file; } - if (file.endsWith('.js') && !file.includes('/lib/') && output_type === 'js' && !file.endsWith('class.js')) { - file_content = babelify(file_content, file, minify); - } + let file_content = get_compiled_file(file, output_path, minify, force_compile); if(!minify) { output_txt += `\n/*\n *\t${file}\n */\n` } output_txt += file_content; - output_txt = output_txt.replace(/['"]use strict['"];/, ''); } @@ -123,6 +115,38 @@ function pack(output_path, inputs, minify) { } } +function get_compiled_file(file, output_path, minify, force_compile) { + const output_type = output_path.split('.').pop(); + + let file_content; + + if (force_compile === false) { + // force compile is false + // attempt to get from cache + file_content = compiled_js_cache[file]; + if (file_content) { + return file_content; + } + } + + file_content = fs.readFileSync(file, 'utf-8'); + + if (file.endsWith('.html') && output_type === 'js') { + file_content = html_to_js_template(file, file_content); + } + + if(file.endsWith('class.js')) { + file_content = minify_js(file_content, file); + } + + if (file.endsWith('.js') && !file.includes('/lib/') && output_type === 'js' && !file.endsWith('class.js')) { + file_content = babelify(file_content, file, minify); + } + + compiled_js_cache[file] = file_content; + return file_content; +} + function babelify(content, path, minify) { let presets = ['env']; // Minification doesn't work when loading Frappe Desk @@ -263,7 +287,7 @@ function watch_js(ondirty) { for (const target in build_map) { const sources = build_map[target]; if (sources.includes(filename)) { - pack(target, sources); + pack(target, sources, null, filename); ondirty && ondirty(target); // break; } From ebf35547548a030de9318e3027c718ede16728e4 Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Wed, 25 Oct 2017 18:30:25 +0530 Subject: [PATCH 12/51] Geolocation control (#4327) * added static map field * leaflet draw plugin added to map control * Editable layer working no icons * Save and load data in form field * images and icons working * Locate plugin added, and loads on geolocation * organized map control code * loads layer as per form * new form clears editableLayers * update: leaflet 1.2.0 and leaflet-draw 0.4.2 * changed to ERPNext colors * Multiple map fields and fixes * fixes codacy suggestions * loaded geojson data editable * Map layers editable * add only one layer on draw:create * code organized with helper functions * Replaced Leaflet Draw with Leaflet Editable * read values from database * layers saved in db * Using Patched Leaflet Draw (mobile-friendly) * Working Map field with multiple forms open * Leaflet Draw css image path fix * Leaflet Draw Fixes Leaflet draw css image paths Fixes Circle marker and Circle * locate to geolocation or set featurecollection as center * [fix] leaflet fitBounds padding 50,50 * [Fix] Leaflet (auto) locate * Map field basic test * added refresh button on map * Mute map geojson in print format * renamed Map field to Geolocation * Suggested changes and fixes * eslint disable * csslint allow important --- .eslintrc | 3 +- frappe/core/doctype/docfield/docfield.json | 4 +- .../doctype/custom_field/custom_field.json | 4 +- .../doctype/custom_field/test_custom_field.js | 23 + .../doctype/customize_form/customize_form.py | 2 +- .../customize_form_field.json | 4 +- frappe/model/db_schema.py | 19 +- frappe/public/build.json | 16 +- frappe/public/images/leaflet/layers-2x.png | Bin 0 -> 1259 bytes frappe/public/images/leaflet/layers.png | Bin 0 -> 696 bytes .../images/leaflet/leafletmarker-icon.png | Bin 0 -> 1466 bytes .../images/leaflet/leafletmarker-shadow.png | Bin 0 -> 618 bytes frappe/public/images/leaflet/lego.png | Bin 0 -> 226340 bytes .../public/images/leaflet/marker-icon-2x.png | Bin 0 -> 2586 bytes frappe/public/images/leaflet/marker-icon.png | Bin 0 -> 1466 bytes .../public/images/leaflet/marker-shadow.png | Bin 0 -> 618 bytes .../public/images/leaflet/spritesheet-2x.png | Bin 0 -> 3581 bytes frappe/public/images/leaflet/spritesheet.png | Bin 0 -> 1906 bytes frappe/public/images/leaflet/spritesheet.svg | 156 ++ frappe/public/js/frappe/dom.js | 5 + .../js/frappe/form/controls/geolocation.js | 183 ++ .../js/lib/leaflet/L.Control.Locate.css | 12 + .../public/js/lib/leaflet/L.Control.Locate.js | 591 ++++++ frappe/public/js/lib/leaflet/easy-button.css | 56 + frappe/public/js/lib/leaflet/easy-button.js | 370 ++++ frappe/public/js/lib/leaflet/leaflet.css | 632 ++++++ frappe/public/js/lib/leaflet/leaflet.draw.css | 10 + frappe/public/js/lib/leaflet/leaflet.draw.js | 1702 +++++++++++++++++ frappe/public/js/lib/leaflet/leaflet.js | 5 + .../print_formats/standard_macros.html | 6 + frappe/tests/ui/test_control_geolocation.js | 39 + frappe/tests/ui/tests.txt | 1 + 32 files changed, 3823 insertions(+), 20 deletions(-) create mode 100644 frappe/custom/doctype/custom_field/test_custom_field.js create mode 100644 frappe/public/images/leaflet/layers-2x.png create mode 100644 frappe/public/images/leaflet/layers.png create mode 100644 frappe/public/images/leaflet/leafletmarker-icon.png create mode 100644 frappe/public/images/leaflet/leafletmarker-shadow.png create mode 100644 frappe/public/images/leaflet/lego.png create mode 100644 frappe/public/images/leaflet/marker-icon-2x.png create mode 100644 frappe/public/images/leaflet/marker-icon.png create mode 100644 frappe/public/images/leaflet/marker-shadow.png create mode 100644 frappe/public/images/leaflet/spritesheet-2x.png create mode 100644 frappe/public/images/leaflet/spritesheet.png create mode 100644 frappe/public/images/leaflet/spritesheet.svg create mode 100644 frappe/public/js/frappe/form/controls/geolocation.js create mode 100644 frappe/public/js/lib/leaflet/L.Control.Locate.css create mode 100644 frappe/public/js/lib/leaflet/L.Control.Locate.js create mode 100644 frappe/public/js/lib/leaflet/easy-button.css create mode 100644 frappe/public/js/lib/leaflet/easy-button.js create mode 100644 frappe/public/js/lib/leaflet/leaflet.css create mode 100644 frappe/public/js/lib/leaflet/leaflet.draw.css create mode 100644 frappe/public/js/lib/leaflet/leaflet.draw.js create mode 100644 frappe/public/js/lib/leaflet/leaflet.js create mode 100644 frappe/tests/ui/test_control_geolocation.js diff --git a/.eslintrc b/.eslintrc index 84cdc6bb85..a956ffdd47 100644 --- a/.eslintrc +++ b/.eslintrc @@ -119,6 +119,7 @@ "getCookies": true, "get_url_arg": true, "QUnit": true, - "JsBarcode": true + "JsBarcode": true, + "L": true } } diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index b00b0c7b07..32dd8031f4 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -96,7 +96,7 @@ "no_copy": 0, "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHeading\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime\nSignature", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime\nSignature", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -1364,7 +1364,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-10-07 19:20:15.888708", + "modified": "2017-10-24 11:39:56.795852", "modified_by": "Administrator", "module": "Core", "name": "DocField", diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index a4bec7ec1f..16e9408d3f 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -220,7 +220,7 @@ "no_copy": 0, "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime\nSignature", + "options": "Attach\nAttach Image\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime\nSignature", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -1161,7 +1161,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-07-06 17:23:43.835189", + "modified": "2017-10-24 11:40:37.986457", "modified_by": "Administrator", "module": "Custom", "name": "Custom Field", diff --git a/frappe/custom/doctype/custom_field/test_custom_field.js b/frappe/custom/doctype/custom_field/test_custom_field.js new file mode 100644 index 0000000000..4ca743a395 --- /dev/null +++ b/frappe/custom/doctype/custom_field/test_custom_field.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Custom Field", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Custom Field + () => frappe.tests.make('Custom Field', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 70251711f3..0392b46ee4 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -66,7 +66,7 @@ docfield_properties = { allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'), ('Text', 'Data'), ('Text', 'Text Editor', 'Code', 'Signature'), ('Data', 'Select'), - ('Text', 'Small Text'), ('Text', 'Data', 'Barcode')) + ('Text', 'Small Text'), ('Text', 'Data', 'Barcode'), ('Code', 'Geolocation')) allowed_fieldtype_for_options_change = ('Read Only', 'HTML', 'Select',) diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json index 8dc0a6a3fa..98a9fe0db3 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -94,7 +94,7 @@ "no_copy": 0, "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHeading\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nText\nText Editor\nTime", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nText\nText Editor\nTime", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -1202,7 +1202,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-10-11 06:45:20.172291", + "modified": "2017-10-24 11:41:31.075929", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form Field", diff --git a/frappe/model/db_schema.py b/frappe/model/db_schema.py index fb3de07332..f275cc7e57 100644 --- a/frappe/model/db_schema.py +++ b/frappe/model/db_schema.py @@ -26,25 +26,26 @@ type_map = { ,'Float': ('decimal', '18,6') ,'Percent': ('decimal', '18,6') ,'Check': ('int', '1') - ,'Small Text': ('text', '') - ,'Long Text': ('longtext', '') + ,'Small Text': ('text', '') + ,'Long Text': ('longtext', '') ,'Code': ('longtext', '') - ,'Text Editor': ('longtext', '') + ,'Text Editor': ('longtext', '') ,'Date': ('date', '') - ,'Datetime': ('datetime', '6') + ,'Datetime': ('datetime', '6') ,'Time': ('time', '6') ,'Text': ('text', '') ,'Data': ('varchar', varchar_len) ,'Link': ('varchar', varchar_len) - ,'Dynamic Link':('varchar', varchar_len) - ,'Password': ('varchar', varchar_len) + ,'Dynamic Link': ('varchar', varchar_len) + ,'Password': ('varchar', varchar_len) ,'Select': ('varchar', varchar_len) - ,'Read Only': ('varchar', varchar_len) + ,'Read Only': ('varchar', varchar_len) ,'Attach': ('text', '') - ,'Attach Image':('text', '') - ,'Signature': ('longtext', '') + ,'Attach Image': ('text', '') + ,'Signature': ('longtext', '') ,'Color': ('varchar', varchar_len) ,'Barcode': ('longtext', '') + ,'Geolocation': ('longtext', '') } default_columns = ['name', 'creation', 'modified', 'modified_by', 'owner', diff --git a/frappe/public/build.json b/frappe/public/build.json index 1a418248dd..7e02a8fe16 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -53,7 +53,8 @@ "public/js/frappe/form/controls/html.js", "public/js/frappe/form/controls/heading.js", "public/js/frappe/form/controls/autocomplete.js", - "public/js/frappe/form/controls/barcode.js" + "public/js/frappe/form/controls/barcode.js", + "public/js/frappe/form/controls/geolocation.js" ], "js/dialog.min.js": [ "public/js/frappe/dom.js", @@ -94,12 +95,17 @@ "public/js/frappe/form/controls/read_only.js", "public/js/frappe/form/controls/button.js", "public/js/frappe/form/controls/html.js", - "public/js/frappe/form/controls/heading.js" + "public/js/frappe/form/controls/heading.js", + "public/js/frappe/form/controls/geolocation.js" ], "css/desk.min.css": [ "public/js/lib/datepicker/datepicker.min.css", "public/js/lib/awesomplete/awesomplete.css", "public/js/lib/summernote/summernote.css", + "public/js/lib/leaflet/leaflet.css", + "public/js/lib/leaflet/leaflet.draw.css", + "public/js/lib/leaflet/L.Control.Locate.css", + "public/js/lib/leaflet/easy-button.css", "public/css/bootstrap.css", "public/css/font-awesome.css", "public/css/octicons/octicons.css", @@ -137,7 +143,11 @@ "public/js/lib/datepicker/datepicker.min.js", "public/js/lib/datepicker/locale-all.js", "public/js/lib/jquery.jrumble.min.js", - "public/js/lib/webcam.min.js" + "public/js/lib/webcam.min.js", + "public/js/lib/leaflet/leaflet.js", + "public/js/lib/leaflet/leaflet.draw.js", + "public/js/lib/leaflet/L.Control.Locate.js", + "public/js/lib/leaflet/easy-button.js" ], "js/desk.min.js": [ "public/js/frappe/class.js", diff --git a/frappe/public/images/leaflet/layers-2x.png b/frappe/public/images/leaflet/layers-2x.png new file mode 100644 index 0000000000000000000000000000000000000000..200c333dca9652ac4cba004d609e5af4eee168c1 GIT binary patch literal 1259 zcmVFhCYNy;#0irRPomHqW|G1C*;4?@4#E?jH>?v@U%cy?3dQAc-DchXVErpOh~ z-jbon+tNbnl6hoEb;)TVk+%hTDDi_G%i3*RZ&15!$Fjr^f;Ke&A@|?=`2&+{zr+3a z{D*=t(`AXyS%X7N z%a#RZw6vD^t_rnM`L4E>m=U&R!A-&}nZIi$BOPvkhrCuUe@BN~-lRD)f44;J%TwgE zcze8u!PQ_NR7?o(NylLXVTfDO zxs5=@|GsYEsNo4M#nT%N!UE(?dnS)t2+{ELYAFp*3=iF=|EQnTp`#vlSXuGVraYo? z+RCzXo6h3qA8{KG?S4nE(lM+;Eb4nT3XV;7gcAxUi5m)`k5tv}cPy()8ZR3TLW3I- zAS^}cq-IJvL7a4RgR!yk@~RT%$lA7{L5ES*hyx)M4(yxI$Ub(4f)K|^v1>zvwQY!_ zIrWw8q9GS^!Dp~}+?mbnB6jDF8mVlbQ!jFKDY;w=7;XO{9bq7>LXGK24WA`;rL)_Z z)&j}pbV(;6gY;VMhbxgvn`X;6x}VUEE-7 z%)7j-%t8S=ZL3yc)HbXDAqJZvBTPoiW_A-+a8m3_Z?v{DN7Tnr#O_VUMT0UBt$;p` zDh6JbGHN8JJ*JN%y2%msb97@_S>9!%Egwk;?PEkU9ntz&3uR}%Fj5d$JHQbQb3}a{ zSzFT^#n=VInPpcAS}CNxj?_ zVscANk5Cfz(51EI1pz};AWWb|kgbYNb4wCEGUn3+eMUMV?1-{=I4TlmLJMot@rd07 zZuo2hk1ccu{YmGkcYdWAVdk{Z4Nm?^cTD&}jGm+Q1SYIXMwmG*oO*83&#>l%nbR`G zhh=lZ%xIb7kU3#;TBbfECrnC9P=-XpL|TG2BoZdj61*XiFbW8?1Z_wp%#;>${SUIy V$8qr;L*)Pf002ovPDHLkV1hYLS~36t literal 0 HcmV?d00001 diff --git a/frappe/public/images/leaflet/layers.png b/frappe/public/images/leaflet/layers.png new file mode 100644 index 0000000000000000000000000000000000000000..1a72e5784b2b456eac5d7670738db80697af3377 GIT binary patch literal 696 zcmV;p0!RIcP)*@&l2<6p=!C&s@#ZL+%BQvF&b?w6S%wp=I>1QHj7AP5C)IWy#b znXXB;g;j=$a-tW89K%FbDceHVq&unY*Wx3L#=EGWH=rjqnp|4c_Ulec!ql3#G-5ZF zVlbBA@XP=)C8U&+Lrc)S4O5%1$&{(;7R^K(CSnvSr$v;+B$8q&7Bf|h$#PARo1^%M zf1H^nG-EiXVXr07OH(*8R)xa|FD;lXUlg_-%)~ZGsL2cX0NXaAzN2q%jqLRR6ruVk8`Jb7n#{`T;o@`F= z#3YcynIR^s83UNF3D!f5m#Mg)NJ24&Qfrqb&_z=yF;=B)#9Iq7u-@^O!(mW{D;qvr zPc)gVb%aowtS8m@ElL4A9G>w#ffQ~q{i&_i)*6f^)Sz|C?C>zb4Uo?H<-&Hz@a?J; z$ml@zGygWofb9$ZBj6aLjpLhsT2AzjOu=-*u_gSCUP001cn1^@s6z>|W`000GnNklGNuHDcIX17Zdjl&3`L?0sTjIws<{((Dh&g-s0<@jYQyl?D*X^?%13;ml^gy> ziMrY_^1WI=(g@LMizu=zCoA>C`6|QEq1eV92k*7m>G65*&@&6)aC&e}G zI)pf-Za|N`DT&Cn1J|o`19mumxW~hiKiKyc-P`S@q)rdTo84@QI@;0yXrG%9uhI>A zG5QHb6s4=<6xy{1 z@NMxEkryp{LS44%z$3lP^cX!9+2-;CTt3wM4(k*#C{aiIiLuB>jJj;KPhPzIC00bL zU3a#;aJld94lCW=`4&aAy8M7PY=HQ>O%$YEP4c4UY#CRxfgbE~(|uiI=YS8q;O9y6 zmIkXzR`}p7ti|PrM3a}WMnR=3NVnWdAAR>b9X@)DKL6=YsvmH%?I24wdq?Gh54_;# z$?_LvgjEdspdQlft#4CQ z`2Zyvy?*)N1Ftw|{_hakhG9WjS?Az@I@+IZ8JbWewR!XUK4&6346+d#~gsE0SY(LX8&JfY>Aj)RxGy96nwhs2rv zzW6pTnMpFkDSkT*a*6Dx|u@ds6ISVn0@^RmIsKZ5Y;bazbc;tTSq(kg(=481ODrPyNB6n z-$+U}(w$m6U6H$w17Bw+wDaFIe~GvNMYvnw31MpY0eQKT9l>SU``8k7w4)z!GZKMI z#_cEKq7k~i%nlK@6c-K?+R;B#5$?T#YpKD`t_4bAs^#E+@5QW$@OX3*`;(#{U^d-vY)&xEE>n5lYl&T?Amke9$Lam@{1K@O ze*LXqlKQHiv=gx+V^Cbb2?z@ISBQ*3amF;9UJ3SBg(N|710TLamQmYZ&Qjn2LuO<* zCZlB4n%@pc&7NNnY1}x+NWpHlq`OJEo|`aYN9<`RBUB+79g;>dgb6YlfN#kGL?lO_ z!6~M^7sOnbsUkKk<@Ysie&`G>ruxH&Mgy&8;i=A zB9OO!xR{AyODw>DS-q5YM{0ExFEAzt zm>RdS+ssW(-8|?xr0(?$vBVB*%(xDLtq3Hf0I5yFm<_g=W2`QWAax{1rWVH=I!VrP zs(rTFX@W#t$hXNvbgX`gK&^w_YD;CQ!B@e0QbLIWaKAXQe2-kkloo;{iF#6}z!4=W zi$giRj1{ zt;2w`VSCF#WE&*ev7jpsC=6175@(~nTE2;7M-L((0bH@yG}-TB$R~WXd?tA$s3|%y zA`9$sA(>F%J3ioz<-LJl*^o1|w84l>HBR`>3l9c8$5Xr@xCiIQ7{x$fMCzOk_-M=% z+{a_Q#;42`#KfUte@$NT77uaTz?b-fBe)1s5XE$yA79fm?KqM^VgLXD07*qoM6N<$ Ef<_J(9smFU literal 0 HcmV?d00001 diff --git a/frappe/public/images/leaflet/lego.png b/frappe/public/images/leaflet/lego.png new file mode 100644 index 0000000000000000000000000000000000000000..f173107335ab28024011d0c45bef7f7a98339306 GIT binary patch literal 226340 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

    bMH=; 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 UQO-*oc+)|Q_08}VdXD6O0C*xx%>V!Z literal 0 HcmV?d00001 diff --git a/frappe/public/images/leaflet/marker-icon.png b/frappe/public/images/leaflet/marker-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..950edf24677ded147df13b26f91baa2b0fa70513 GIT binary patch literal 1466 zcmV;r1x5OaP)P001cn1^@s6z>|W`000GnNklGNuHDcIX17Zdjl&3`L?0sTjIws<{((Dh&g-s0<@jYQyl?D*X^?%13;ml^gy> ziMrY_^1WI=(g@LMizu=zCoA>C`6|QEq1eV92k*7m>G65*&@&6)aC&e}G zI)pf-Za|N`DT&Cn1J|o`19mumxW~hiKiKyc-P`S@q)rdTo84@QI@;0yXrG%9uhI>A zG5QHb6s4=<6xy{1 z@NMxEkryp{LS44%z$3lP^cX!9+2-;CTt3wM4(k*#C{aiIiLuB>jJj;KPhPzIC00bL zU3a#;aJld94lCW=`4&aAy8M7PY=HQ>O%$YEP4c4UY#CRxfgbE~(|uiI=YS8q;O9y6 zmIkXzR`}p7ti|PrM3a}WMnR=3NVnWdAAR>b9X@)DKL6=YsvmH%?I24wdq?Gh54_;# z$?_LvgjEdspdQlft#4CQ z`2Zyvy?*)N1Ftw|{_hakhG9WjS?Az@I@+IZ8JbWewR!XUK4&6346+d#~gsE0SY(LX8&JfY>Aj)RxGy96nwhs2rv zzW6pTnMpFkDSkT*a*6Dx|u@ds6ISVn0@^RmIsKZ5Y;bazbc;tTSq(kg(=481ODrPyNB6n z-$+U}(w$m6U6H$w17Bw+wDaFIe~GvNMYvnw31MpY0eQKT9l>SU``8k7w4)z!GZKMI z#_cEKq7k~i%nlK@6c-K?+R;B#5$?T#YpKD`t_4bAs^#E+@5QW$@OX3*`;(#{U^d-vY)&xEE>n5lYl&T?Amke9$Lam@{1K@O ze*LXqlKQHiv=gx+V^Cbb2?z@ISBQ*3amF;9UJ3SBg(N|710TLamQmYZ&Qjn2LuO<* zCZlB4n%@pc&7NNnY1}x+NWpHlq`OJEo|`aYN9<`RBUB+79g;>dgb6YlfN#kGL?lO_ z!6~M^7sOnbsUkKk<@Ysie&`G>ruxH&Mgy&8;i=A zB9OO!xR{AyODw>DS-q5YM{0ExFEAzt zm>RdS+ssW(-8|?xr0(?$vBVB*%(xDLtq3Hf0I5yFm<_g=W2`QWAax{1rWVH=I!VrP zs(rTFX@W#t$hXNvbgX`gK&^w_YD;CQ!B@e0QbLIWaKAXQe2-kkloo;{iF#6}z!4=W zi$giRj1{ zt;2w`VSCF#WE&*ev7jpsC=6175@(~nTE2;7M-L((0bH@yG}-TB$R~WXd?tA$s3|%y zA`9$sA(>F%J3ioz<-LJl*^o1|w84l>HBR`>3l9c8$5Xr@xCiIQ7{x$fMCzOk_-M=% z+{a_Q#;42`#KfUte@$NT77uaTz?b-fBe)1s5XE$yA79fm?KqM^VgLXD07*qoM6N<$ Ef<_J(9smFU literal 0 HcmV?d00001 diff --git a/frappe/public/images/leaflet/spritesheet-2x.png b/frappe/public/images/leaflet/spritesheet-2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c45231aff86b1344333414cdfa7a2a74d38d5304 GIT binary patch literal 3581 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 i{eP_FzxHA`84t@4c6%tZ%} + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frappe/public/js/frappe/dom.js b/frappe/public/js/frappe/dom.js index b4ceac66bd..ec7a4b9e06 100644 --- a/frappe/public/js/frappe/dom.js +++ b/frappe/public/js/frappe/dom.js @@ -10,6 +10,11 @@ frappe.dom = { by_id: function(id) { return document.getElementById(id); }, + get_unique_id: function() { + const id = 'unique-' + frappe.dom.id_count; + frappe.dom.id_count++; + return id; + }, set_unique_id: function(ele) { var $ele = $(ele); if($ele.attr('id')) { diff --git a/frappe/public/js/frappe/form/controls/geolocation.js b/frappe/public/js/frappe/form/controls/geolocation.js new file mode 100644 index 0000000000..515620da21 --- /dev/null +++ b/frappe/public/js/frappe/form/controls/geolocation.js @@ -0,0 +1,183 @@ +frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlData.extend({ + make_wrapper() { + // Create the elements for map area + this._super(); + + let $input_wrapper = this.$wrapper.find('.control-input-wrapper'); + this.map_id = frappe.dom.get_unique_id(); + this.map_area = $( + `

    +
    +
    ` + ); + this.map_area.prependTo($input_wrapper); + this.$wrapper.find('.control-input').addClass("hidden"); + this.bind_leaflet_map(); + this.bind_leaflet_draw_control(); + this.bind_leaflet_locate_control(); + this.bind_leaflet_refresh_button(); + }, + + format_for_input(value) { + // render raw value from db into map + this.clear_editable_layers(); + if(value) { + var data_layers = new L.FeatureGroup() + .addLayer(L.geoJson(JSON.parse(value),{ + pointToLayer: function(geoJsonPoint, latlng) { + if (geoJsonPoint.properties.point_type == "circle"){ + return L.circle(latlng, {radius: geoJsonPoint.properties.radius}); + } else if (geoJsonPoint.properties.point_type == "circlemarker") { + return L.circleMarker(latlng, {radius: geoJsonPoint.properties.radius}); + } + else { + return L.marker(latlng); + } + } + })); + this.add_non_group_layers(data_layers, this.editableLayers); + try { + this.map.flyToBounds(this.editableLayers.getBounds(), { + padding: [50,50] + }); + } + catch(err) { + // suppress error if layer has a point. + } + this.editableLayers.addTo(this.map); + } else if ((value===undefined) || (value == JSON.stringify(new L.FeatureGroup().toGeoJSON()))) { + this.locate_control.start(); + } + }, + + bind_leaflet_map() { + + var circleToGeoJSON = L.Circle.prototype.toGeoJSON; + L.Circle.include({ + toGeoJSON: function() { + var feature = circleToGeoJSON.call(this); + feature.properties = { + point_type: 'circle', + radius: this.getRadius() + }; + return feature; + } + }); + + L.CircleMarker.include({ + toGeoJSON: function() { + var feature = circleToGeoJSON.call(this); + feature.properties = { + point_type: 'circlemarker', + radius: this.getRadius() + }; + return feature; + } + }); + + L.Icon.Default.imagePath = '/assets/frappe/images/leaflet/'; + this.map = L.map(this.map_id).setView([19.0800, 72.8961], 13); + + L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', { + attribution: '©
    OpenStreetMap contributors' + }).addTo(this.map); + }, + + bind_leaflet_locate_control() { + // To request location update and set location, sets current geolocation on load + this.locate_control = L.control.locate({position:'topright'}); + this.locate_control.addTo(this.map); + }, + + bind_leaflet_draw_control() { + this.editableLayers = new L.FeatureGroup(); + + var options = { + position: 'topleft', + draw: { + polyline: { + shapeOptions: { + color: frappe.ui.color.get('blue'), + weight: 10 + } + }, + polygon: { + allowIntersection: false, // Restricts shapes to simple polygons + drawError: { + color: frappe.ui.color.get('orange'), // Color the shape will turn when intersects + message: 'Oh snap! you can\'t draw that!' // Message that will show when intersect + }, + shapeOptions: { + color: frappe.ui.color.get('blue') + } + }, + circle: true, + rectangle: { + shapeOptions: { + clickable: false + } + } + }, + edit: { + featureGroup: this.editableLayers, //REQUIRED!! + remove: true + } + }; + + // create control and add to map + var drawControl = new L.Control.Draw(options); + + this.map.addControl(drawControl); + + this.map.on('draw:created', (e) => { + var type = e.layerType, + layer = e.layer; + if (type === 'marker') { + layer.bindPopup('Marker'); + } + this.editableLayers.addLayer(layer); + this.set_value(JSON.stringify(this.editableLayers.toGeoJSON())); + }); + + this.map.on('draw:deleted draw:edited', (e) => { + var layer = e.layer; + this.editableLayers.removeLayer(layer); + this.set_value(JSON.stringify(this.editableLayers.toGeoJSON())); + }); + }, + + bind_leaflet_refresh_button() { + L.easyButton({ + id: 'refresh-map-'+this.df.fieldname, + position: 'topright', + type: 'replace', + leafletClasses: true, + states:[{ + stateName: 'refresh-map', + onClick: function(button, map){ + map._onResize(); + }, + title: 'Refresh map', + icon: 'fa fa-refresh' + }] + }).addTo(this.map); + }, + + add_non_group_layers(source_layer, target_group) { + // https://gis.stackexchange.com/a/203773 + // Would benefit from https://github.com/Leaflet/Leaflet/issues/4461 + if (source_layer instanceof L.LayerGroup) { + source_layer.eachLayer((layer)=>{ + this.add_non_group_layers(layer, target_group); + }); + } else { + target_group.addLayer(source_layer); + } + }, + + clear_editable_layers() { + this.editableLayers.eachLayer((l)=>{ + this.editableLayers.removeLayer(l); + }); + } +}); diff --git a/frappe/public/js/lib/leaflet/L.Control.Locate.css b/frappe/public/js/lib/leaflet/L.Control.Locate.css new file mode 100644 index 0000000000..1fa5e7b67b --- /dev/null +++ b/frappe/public/js/lib/leaflet/L.Control.Locate.css @@ -0,0 +1,12 @@ +/* Compatible with Leaflet 0.7 */ +.leaflet-control-locate a { + font-size: 1.4em; + color: #444; + cursor: pointer; +} +.leaflet-control-locate.active a { + color: #2074B6; +} +.leaflet-control-locate.active.following a { + color: #FC8428; +} diff --git a/frappe/public/js/lib/leaflet/L.Control.Locate.js b/frappe/public/js/lib/leaflet/L.Control.Locate.js new file mode 100644 index 0000000000..8544e17a04 --- /dev/null +++ b/frappe/public/js/lib/leaflet/L.Control.Locate.js @@ -0,0 +1,591 @@ +/*! +Copyright (c) 2016 Dominik Moritz + +This file is part of the leaflet locate control. It is licensed under the MIT license. +You can find the project at: https://github.com/domoritz/leaflet-locatecontrol +*/ +(function (factory, window) { + // see https://github.com/Leaflet/Leaflet/blob/master/PLUGIN-GUIDE.md#module-loaders + // for details on how to structure a leaflet plugin. + + // define an AMD module that relies on 'leaflet' + if (typeof define === 'function' && define.amd) { + define(['leaflet'], factory); + + // define a Common JS module that relies on 'leaflet' + } else if (typeof exports === 'object') { + if (typeof window !== 'undefined' && window.L) { + module.exports = factory(L); + } else { + module.exports = factory(require('leaflet')); + } + } + + // attach your plugin to the global 'L' variable + if (typeof window !== 'undefined' && window.L){ + window.L.Control.Locate = factory(L); + } +} (function (L) { + var LDomUtilApplyClassesMethod = function(method, element, classNames) { + classNames = classNames.split(' '); + classNames.forEach(function(className) { + L.DomUtil[method].call(this, element, className); + }); + }; + + var addClasses = function(el, names) { LDomUtilApplyClassesMethod('addClass', el, names); }; + var removeClasses = function(el, names) { LDomUtilApplyClassesMethod('removeClass', el, names); }; + + var LocateControl = L.Control.extend({ + options: { + /** Position of the control */ + position: 'topleft', + /** The layer that the user's location should be drawn on. By default creates a new layer. */ + layer: undefined, + /** + * Automatically sets the map view (zoom and pan) to the user's location as it updates. + * While the map is following the user's location, the control is in the `following` state, + * which changes the style of the control and the circle marker. + * + * Possible values: + * - false: never updates the map view when location changes. + * - 'once': set the view when the location is first determined + * - 'always': always updates the map view when location changes. + * The map view follows the users location. + * - 'untilPan': (default) like 'always', except stops updating the + * view if the user has manually panned the map. + * The map view follows the users location until she pans. + */ + setView: 'untilPan', + /** Keep the current map zoom level when setting the view and only pan. */ + keepCurrentZoomLevel: false, + /** Smooth pan and zoom to the location of the marker. Only works in Leaflet 1.0+. */ + flyTo: false, + /** + * The user location can be inside and outside the current view when the user clicks on the + * control that is already active. Both cases can be configures separately. + * Possible values are: + * - 'setView': zoom and pan to the current location + * - 'stop': stop locating and remove the location marker + */ + clickBehavior: { + /** What should happen if the user clicks on the control while the location is within the current view. */ + inView: 'stop', + /** What should happen if the user clicks on the control while the location is outside the current view. */ + outOfView: 'setView', + }, + /** + * If set, save the map bounds just before centering to the user's + * location. When control is disabled, set the view back to the + * bounds that were saved. + */ + returnToPrevBounds: false, + /** + * Keep a cache of the location after the user deactivates the control. If set to false, the user has to wait + * until the locate API returns a new location before they see where they are again. + */ + cacheLocation: true, + /** If set, a circle that shows the location accuracy is drawn. */ + drawCircle: true, + /** If set, the marker at the users' location is drawn. */ + drawMarker: true, + /** The class to be used to create the marker. For example L.CircleMarker or L.Marker */ + markerClass: L.CircleMarker, + /** Accuracy circle style properties. */ + circleStyle: { + color: '#136AEC', + fillColor: '#136AEC', + fillOpacity: 0.15, + weight: 2, + opacity: 0.5 + }, + /** Inner marker style properties. Only works if your marker class supports `setStyle`. */ + markerStyle: { + color: '#136AEC', + fillColor: '#2A93EE', + fillOpacity: 0.7, + weight: 2, + opacity: 0.9, + radius: 5 + }, + /** + * Changes to accuracy circle and inner marker while following. + * It is only necessary to provide the properties that should change. + */ + followCircleStyle: {}, + followMarkerStyle: { + // color: '#FFA500', + // fillColor: '#FFB000' + }, + /** The CSS class for the icon. For example fa-location-arrow or fa-map-marker */ + icon: 'fa fa-map-marker', + iconLoading: 'fa fa-spinner fa-spin', + /** The element to be created for icons. For example span or i */ + iconElementTag: 'span', + /** Padding around the accuracy circle. */ + circlePadding: [0, 0], + /** Use metric units. */ + metric: true, + /** + * This callback can be used in case you would like to override button creation behavior. + * This is useful for DOM manipulation frameworks such as angular etc. + * This function should return an object with HtmlElement for the button (link property) and the icon (icon property). + */ + createButtonCallback: function (container, options) { + var link = L.DomUtil.create('a', 'leaflet-bar-part leaflet-bar-part-single', container); + link.title = options.strings.title; + var icon = L.DomUtil.create(options.iconElementTag, options.icon, link); + return { link: link, icon: icon }; + }, + /** This event is called in case of any location error that is not a time out error. */ + onLocationError: function(err, control) { + alert(err.message); + }, + /** + * This even is called when the user's location is outside the bounds set on the map. + * The event is called repeatedly when the location changes. + */ + onLocationOutsideMapBounds: function(control) { + control.stop(); + alert(control.options.strings.outsideMapBoundsMsg); + }, + /** Display a pop-up when the user click on the inner marker. */ + showPopup: true, + strings: { + title: "Show me where I am", + metersUnit: "meters", + feetUnit: "feet", + popup: "You are within {distance} {unit} from this point", + outsideMapBoundsMsg: "You seem located outside the boundaries of the map" + }, + /** The default options passed to leaflets locate method. */ + locateOptions: { + maxZoom: Infinity, + watch: true, // if you overwrite this, visualization cannot be updated + setView: false // have to set this to false because we have to + // do setView manually + } + }, + + initialize: function (options) { + // set default options if nothing is set (merge one step deep) + for (var i in options) { + if (typeof this.options[i] === 'object') { + L.extend(this.options[i], options[i]); + } else { + this.options[i] = options[i]; + } + } + + // extend the follow marker style and circle from the normal style + this.options.followMarkerStyle = L.extend({}, this.options.markerStyle, this.options.followMarkerStyle); + this.options.followCircleStyle = L.extend({}, this.options.circleStyle, this.options.followCircleStyle); + }, + + /** + * Add control to map. Returns the container for the control. + */ + onAdd: function (map) { + var container = L.DomUtil.create('div', + 'leaflet-control-locate leaflet-bar leaflet-control'); + + this._layer = this.options.layer || new L.LayerGroup(); + this._layer.addTo(map); + this._event = undefined; + this._prevBounds = null; + + var linkAndIcon = this.options.createButtonCallback(container, this.options); + this._link = linkAndIcon.link; + this._icon = linkAndIcon.icon; + + L.DomEvent + .on(this._link, 'click', L.DomEvent.stopPropagation) + .on(this._link, 'click', L.DomEvent.preventDefault) + .on(this._link, 'click', this._onClick, this) + .on(this._link, 'dblclick', L.DomEvent.stopPropagation); + + this._resetVariables(); + + this._map.on('unload', this._unload, this); + + return container; + }, + + /** + * This method is called when the user clicks on the control. + */ + _onClick: function() { + this._justClicked = true; + this._userPanned = false; + + if (this._active && !this._event) { + // click while requesting + this.stop(); + } else if (this._active && this._event !== undefined) { + var behavior = this._map.getBounds().contains(this._event.latlng) ? + this.options.clickBehavior.inView : this.options.clickBehavior.outOfView; + switch (behavior) { + case 'setView': + this.setView(); + break; + case 'stop': + this.stop(); + if (this.options.returnToPrevBounds) { + var f = this.options.flyTo ? this._map.flyToBounds : this._map.fitBounds; + f.bind(this._map)(this._prevBounds); + } + break; + } + } else { + if (this.options.returnToPrevBounds) { + this._prevBounds = this._map.getBounds(); + } + this.start(); + } + + this._updateContainerStyle(); + }, + + /** + * Starts the plugin: + * - activates the engine + * - draws the marker (if coordinates available) + */ + start: function() { + this._activate(); + + if (this._event) { + this._drawMarker(this._map); + + // if we already have a location but the user clicked on the control + if (this.options.setView) { + this.setView(); + } + } + this._updateContainerStyle(); + }, + + /** + * Stops the plugin: + * - deactivates the engine + * - reinitializes the button + * - removes the marker + */ + stop: function() { + this._deactivate(); + + this._cleanClasses(); + this._resetVariables(); + + this._removeMarker(); + }, + + /** + * This method launches the location engine. + * It is called before the marker is updated, + * event if it does not mean that the event will be ready. + * + * Override it if you want to add more functionalities. + * It should set the this._active to true and do nothing if + * this._active is true. + */ + _activate: function() { + if (!this._active) { + this._map.locate(this.options.locateOptions); + this._active = true; + + // bind event listeners + this._map.on('locationfound', this._onLocationFound, this); + this._map.on('locationerror', this._onLocationError, this); + this._map.on('dragstart', this._onDrag, this); + } + }, + + /** + * Called to stop the location engine. + * + * Override it to shutdown any functionalities you added on start. + */ + _deactivate: function() { + this._map.stopLocate(); + this._active = false; + + if (!this.options.cacheLocation) { + this._event = undefined; + } + + // unbind event listeners + this._map.off('locationfound', this._onLocationFound, this); + this._map.off('locationerror', this._onLocationError, this); + this._map.off('dragstart', this._onDrag, this); + }, + + /** + * Zoom (unless we should keep the zoom level) and an to the current view. + */ + setView: function() { + this._drawMarker(); + if (this._isOutsideMapBounds()) { + this._event = undefined; // clear the current location so we can get back into the bounds + this.options.onLocationOutsideMapBounds(this); + } else { + if (this.options.keepCurrentZoomLevel) { + var f = this.options.flyTo ? this._map.flyTo : this._map.panTo; + f.bind(this._map)([this._event.latitude, this._event.longitude]); + } else { + var f = this.options.flyTo ? this._map.flyToBounds : this._map.fitBounds; + f.bind(this._map)(this._event.bounds, { + padding: this.options.circlePadding, + maxZoom: this.options.locateOptions.maxZoom + }); + } + } + }, + + /** + * Draw the marker and accuracy circle on the map. + * + * Uses the event retrieved from onLocationFound from the map. + */ + _drawMarker: function() { + if (this._event.accuracy === undefined) { + this._event.accuracy = 0; + } + + var radius = this._event.accuracy; + var latlng = this._event.latlng; + + // circle with the radius of the location's accuracy + if (this.options.drawCircle) { + var style = this._isFollowing() ? this.options.followCircleStyle : this.options.circleStyle; + + if (!this._circle) { + this._circle = L.circle(latlng, radius, style).addTo(this._layer); + } else { + this._circle.setLatLng(latlng).setRadius(radius).setStyle(style); + } + } + + var distance, unit; + if (this.options.metric) { + distance = radius.toFixed(0); + unit = this.options.strings.metersUnit; + } else { + distance = (radius * 3.2808399).toFixed(0); + unit = this.options.strings.feetUnit; + } + + // small inner marker + if (this.options.drawMarker) { + var mStyle = this._isFollowing() ? this.options.followMarkerStyle : this.options.markerStyle; + if (!this._marker) { + this._marker = new this.options.markerClass(latlng, mStyle).addTo(this._layer); + } else { + this._marker.setLatLng(latlng); + // If the markerClass can be updated with setStyle, update it. + if (this._marker.setStyle) { + this._marker.setStyle(mStyle); + } + } + } + + var t = this.options.strings.popup; + if (this.options.showPopup && t && this._marker) { + this._marker + .bindPopup(L.Util.template(t, {distance: distance, unit: unit})) + ._popup.setLatLng(latlng); + } + }, + + /** + * Remove the marker from map. + */ + _removeMarker: function() { + this._layer.clearLayers(); + this._marker = undefined; + this._circle = undefined; + }, + + /** + * Unload the plugin and all event listeners. + * Kind of the opposite of onAdd. + */ + _unload: function() { + this.stop(); + this._map.off('unload', this._unload, this); + }, + + /** + * Calls deactivate and dispatches an error. + */ + _onLocationError: function(err) { + // ignore time out error if the location is watched + if (err.code == 3 && this.options.locateOptions.watch) { + return; + } + + this.stop(); + this.options.onLocationError(err, this); + }, + + /** + * Stores the received event and updates the marker. + */ + _onLocationFound: function(e) { + // no need to do anything if the location has not changed + if (this._event && + (this._event.latlng.lat === e.latlng.lat && + this._event.latlng.lng === e.latlng.lng && + this._event.accuracy === e.accuracy)) { + return; + } + + if (!this._active) { + // we may have a stray event + return; + } + + this._event = e; + + this._drawMarker(); + this._updateContainerStyle(); + + switch (this.options.setView) { + case 'once': + if (this._justClicked) { + this.setView(); + } + break; + case 'untilPan': + if (!this._userPanned) { + this.setView(); + } + break; + case 'always': + this.setView(); + break; + case false: + // don't set the view + break; + } + + this._justClicked = false; + }, + + /** + * When the user drags. Need a separate even so we can bind and unbind even listeners. + */ + _onDrag: function() { + // only react to drags once we have a location + if (this._event) { + this._userPanned = true; + this._updateContainerStyle(); + this._drawMarker(); + } + }, + + /** + * Compute whether the map is following the user location with pan and zoom. + */ + _isFollowing: function() { + if (!this._active) { + return false; + } + + if (this.options.setView === 'always') { + return true; + } else if (this.options.setView === 'untilPan') { + return !this._userPanned; + } + }, + + /** + * Check if location is in map bounds + */ + _isOutsideMapBounds: function() { + if (this._event === undefined) { + return false; + } + return this._map.options.maxBounds && + !this._map.options.maxBounds.contains(this._event.latlng); + }, + + /** + * Toggles button class between following and active. + */ + _updateContainerStyle: function() { + if (!this._container) { + return; + } + + if (this._active && !this._event) { + // active but don't have a location yet + this._setClasses('requesting'); + } else if (this._isFollowing()) { + this._setClasses('following'); + } else if (this._active) { + this._setClasses('active'); + } else { + this._cleanClasses(); + } + }, + + /** + * Sets the CSS classes for the state. + */ + _setClasses: function(state) { + if (state == 'requesting') { + removeClasses(this._container, "active following"); + addClasses(this._container, "requesting"); + + removeClasses(this._icon, this.options.icon); + addClasses(this._icon, this.options.iconLoading); + } else if (state == 'active') { + removeClasses(this._container, "requesting following"); + addClasses(this._container, "active"); + + removeClasses(this._icon, this.options.iconLoading); + addClasses(this._icon, this.options.icon); + } else if (state == 'following') { + removeClasses(this._container, "requesting"); + addClasses(this._container, "active following"); + + removeClasses(this._icon, this.options.iconLoading); + addClasses(this._icon, this.options.icon); + } + }, + + /** + * Removes all classes from button. + */ + _cleanClasses: function() { + L.DomUtil.removeClass(this._container, "requesting"); + L.DomUtil.removeClass(this._container, "active"); + L.DomUtil.removeClass(this._container, "following"); + + removeClasses(this._icon, this.options.iconLoading); + addClasses(this._icon, this.options.icon); + }, + + /** + * Reinitializes state variables. + */ + _resetVariables: function() { + // whether locate is active or not + this._active = false; + + // true if the control was clicked for the first time + // we need this so we can pan and zoom once we have the location + this._justClicked = false; + + // true if the user has panned the map after clicking the control + this._userPanned = false; + } + }); + + L.control.locate = function (options) { + return new L.Control.Locate(options); + }; + + return LocateControl; +}, window)); diff --git a/frappe/public/js/lib/leaflet/easy-button.css b/frappe/public/js/lib/leaflet/easy-button.css new file mode 100644 index 0000000000..18ce9ac161 --- /dev/null +++ b/frappe/public/js/lib/leaflet/easy-button.css @@ -0,0 +1,56 @@ +.leaflet-bar button, +.leaflet-bar button:hover { + background-color: #fff; + border: none; + border-bottom: 1px solid #ccc; + width: 26px; + height: 26px; + line-height: 26px; + display: block; + text-align: center; + text-decoration: none; + color: black; +} + +.leaflet-bar button { + background-position: 50% 50%; + background-repeat: no-repeat; + overflow: hidden; + display: block; +} + +.leaflet-bar button:hover { + background-color: #f4f4f4; +} + +.leaflet-bar button:first-of-type { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} + +.leaflet-bar button:last-of-type { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom: none; +} + +.leaflet-bar.disabled, +.leaflet-bar button.disabled { + cursor: default; + pointer-events: none; + opacity: .4; +} + +.easy-button-button .button-state{ + display: block; + width: 100%; + height: 100%; + position: relative; +} + + +.leaflet-touch .leaflet-bar button { + width: 30px; + height: 30px; + line-height: 30px; +} diff --git a/frappe/public/js/lib/leaflet/easy-button.js b/frappe/public/js/lib/leaflet/easy-button.js new file mode 100644 index 0000000000..579dca6913 --- /dev/null +++ b/frappe/public/js/lib/leaflet/easy-button.js @@ -0,0 +1,370 @@ +(function(){ + +// This is for grouping buttons into a bar +// takes an array of `L.easyButton`s and +// then the usual `.addTo(map)` +L.Control.EasyBar = L.Control.extend({ + + options: { + position: 'topleft', // part of leaflet's defaults + id: null, // an id to tag the Bar with + leafletClasses: true // use leaflet classes? + }, + + + initialize: function(buttons, options){ + + if(options){ + L.Util.setOptions( this, options ); + } + + this._buildContainer(); + this._buttons = []; + + for(var i = 0; i < buttons.length; i++){ + buttons[i]._bar = this; + buttons[i]._container = buttons[i].button; + this._buttons.push(buttons[i]); + this.container.appendChild(buttons[i].button); + } + + }, + + + _buildContainer: function(){ + this._container = this.container = L.DomUtil.create('div', ''); + this.options.leafletClasses && L.DomUtil.addClass(this.container, 'leaflet-bar easy-button-container leaflet-control'); + this.options.id && (this.container.id = this.options.id); + }, + + + enable: function(){ + L.DomUtil.addClass(this.container, 'enabled'); + L.DomUtil.removeClass(this.container, 'disabled'); + this.container.setAttribute('aria-hidden', 'false'); + return this; + }, + + + disable: function(){ + L.DomUtil.addClass(this.container, 'disabled'); + L.DomUtil.removeClass(this.container, 'enabled'); + this.container.setAttribute('aria-hidden', 'true'); + return this; + }, + + + onAdd: function () { + return this.container; + }, + + addTo: function (map) { + this._map = map; + + for(var i = 0; i < this._buttons.length; i++){ + this._buttons[i]._map = map; + } + + var container = this._container = this.onAdd(map), + pos = this.getPosition(), + corner = map._controlCorners[pos]; + + L.DomUtil.addClass(container, 'leaflet-control'); + + if (pos.indexOf('bottom') !== -1) { + corner.insertBefore(container, corner.firstChild); + } else { + corner.appendChild(container); + } + + return this; + } + +}); + +L.easyBar = function(){ + var args = [L.Control.EasyBar]; + for(var i = 0; i < arguments.length; i++){ + args.push( arguments[i] ); + } + return new (Function.prototype.bind.apply(L.Control.EasyBar, args)); +}; + +// L.EasyButton is the actual buttons +// can be called without being grouped into a bar +L.Control.EasyButton = L.Control.extend({ + + options: { + position: 'topleft', // part of leaflet's defaults + + id: null, // an id to tag the button with + + type: 'replace', // [(replace|animate)] + // replace swaps out elements + // animate changes classes with all elements inserted + + states: [], // state names look like this + // { + // stateName: 'untracked', + // onClick: function(){ handle_nav_manually(); }; + // title: 'click to make inactive', + // icon: 'fa-circle', // wrapped with + // } + + leafletClasses: true, // use leaflet styles for the button + tagName: 'button', + }, + + + + initialize: function(icon, onClick, title, id){ + + // clear the states manually + this.options.states = []; + + // add id to options + if(id != null){ + this.options.id = id; + } + + // storage between state functions + this.storage = {}; + + // is the last item an object? + if( typeof arguments[arguments.length-1] === 'object' ){ + + // if so, it should be the options + L.Util.setOptions( this, arguments[arguments.length-1] ); + } + + // if there aren't any states in options + // use the early params + if( this.options.states.length === 0 && + typeof icon === 'string' && + typeof onClick === 'function'){ + + // turn the options object into a state + this.options.states.push({ + icon: icon, + onClick: onClick, + title: typeof title === 'string' ? title : '' + }); + } + + // curate and move user's states into + // the _states for internal use + this._states = []; + + for(var i = 0; i < this.options.states.length; i++){ + this._states.push( new State(this.options.states[i], this) ); + } + + this._buildButton(); + + this._activateState(this._states[0]); + + }, + + _buildButton: function(){ + + this.button = L.DomUtil.create(this.options.tagName, ''); + + if (this.options.tagName === 'button') { + this.button.setAttribute('type', 'button'); + } + + if (this.options.id ){ + this.button.id = this.options.id; + } + + if (this.options.leafletClasses){ + L.DomUtil.addClass(this.button, 'easy-button-button leaflet-bar-part leaflet-interactive'); + } + + // don't let double clicks and mousedown get to the map + L.DomEvent.addListener(this.button, 'dblclick', L.DomEvent.stop); + L.DomEvent.addListener(this.button, 'mousedown', L.DomEvent.stop); + + // take care of normal clicks + L.DomEvent.addListener(this.button,'click', function(e){ + L.DomEvent.stop(e); + this._currentState.onClick(this, this._map ? this._map : null ); + this._map && this._map.getContainer().focus(); + }, this); + + // prep the contents of the control + if(this.options.type == 'replace'){ + this.button.appendChild(this._currentState.icon); + } else { + for(var i=0;i"']/) ){ + + // if so, the user should have put in html + // so move forward as such + tmpIcon = ambiguousIconString; + + // then it wasn't html, so + // it's a class list, figure out what kind + } else { + ambiguousIconString = ambiguousIconString.replace(/(^\s*|\s*$)/g,''); + tmpIcon = L.DomUtil.create('span', ''); + + if( ambiguousIconString.indexOf('fa-') === 0 ){ + L.DomUtil.addClass(tmpIcon, 'fa ' + ambiguousIconString) + } else if ( ambiguousIconString.indexOf('glyphicon-') === 0 ) { + L.DomUtil.addClass(tmpIcon, 'glyphicon ' + ambiguousIconString) + } else { + L.DomUtil.addClass(tmpIcon, /*rollwithit*/ ambiguousIconString) + } + + // make this a string so that it's easy to set innerHTML below + tmpIcon = tmpIcon.outerHTML; + } + + return tmpIcon; +} + +})(); diff --git a/frappe/public/js/lib/leaflet/leaflet.css b/frappe/public/js/lib/leaflet/leaflet.css new file mode 100644 index 0000000000..0d01a59ea3 --- /dev/null +++ b/frappe/public/js/lib/leaflet/leaflet.css @@ -0,0 +1,632 @@ +/* required styles */ + +.leaflet-pane, +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow, +.leaflet-tile-container, +.leaflet-pane > svg, +.leaflet-pane > canvas, +.leaflet-zoom-box, +.leaflet-image-layer, +.leaflet-layer { + position: absolute; + left: 0; + top: 0; + } +.leaflet-container { + overflow: hidden; + } +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-user-drag: none; + } +/* Safari renders non-retina tile on retina better with this, but Chrome is worse */ +.leaflet-safari .leaflet-tile { + image-rendering: -webkit-optimize-contrast; + } +/* hack that prevents hw layers "stretching" when loading new tiles */ +.leaflet-safari .leaflet-tile-container { + width: 1600px; + height: 1600px; + -webkit-transform-origin: 0 0; + } +.leaflet-marker-icon, +.leaflet-marker-shadow { + display: block; + } +/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ +/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ +.leaflet-container .leaflet-overlay-pane svg, +.leaflet-container .leaflet-marker-pane img, +.leaflet-container .leaflet-shadow-pane img, +.leaflet-container .leaflet-tile-pane img, +.leaflet-container img.leaflet-image-layer { + max-width: none !important; /* csslint allow: important */ + } + +.leaflet-container.leaflet-touch-zoom { + -ms-touch-action: pan-x pan-y; + touch-action: pan-x pan-y; + } +.leaflet-container.leaflet-touch-drag { + -ms-touch-action: pinch-zoom; + } +.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { + -ms-touch-action: none; + touch-action: none; +} +.leaflet-container { + -webkit-tap-highlight-color: transparent; +} +.leaflet-container a { + -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); +} +.leaflet-tile { + filter: inherit; + visibility: hidden; + } +.leaflet-tile-loaded { + visibility: inherit; + } +.leaflet-zoom-box { + width: 0; + height: 0; + -moz-box-sizing: border-box; + box-sizing: border-box; + z-index: 800; + } +/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ +.leaflet-overlay-pane svg { + -moz-user-select: none; + } + +.leaflet-pane { z-index: 400; } + +.leaflet-tile-pane { z-index: 200; } +.leaflet-overlay-pane { z-index: 400; } +.leaflet-shadow-pane { z-index: 500; } +.leaflet-marker-pane { z-index: 600; } +.leaflet-tooltip-pane { z-index: 650; } +.leaflet-popup-pane { z-index: 700; } + +.leaflet-map-pane canvas { z-index: 100; } +.leaflet-map-pane svg { z-index: 200; } + +.leaflet-vml-shape { + width: 1px; + height: 1px; + } +.lvml { + behavior: url(#default#VML); + display: inline-block; + position: absolute; + } + + +/* control positioning */ + +.leaflet-control { + position: relative; + z-index: 800; + pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: auto; + } +.leaflet-top, +.leaflet-bottom { + position: absolute; + z-index: 1000; + pointer-events: none; + } +.leaflet-top { + top: 0; + } +.leaflet-right { + right: 0; + } +.leaflet-bottom { + bottom: 0; + } +.leaflet-left { + left: 0; + } +.leaflet-control { + float: left; + clear: both; + } +.leaflet-right .leaflet-control { + float: right; + } +.leaflet-top .leaflet-control { + margin-top: 10px; + } +.leaflet-bottom .leaflet-control { + margin-bottom: 10px; + } +.leaflet-left .leaflet-control { + margin-left: 10px; + } +.leaflet-right .leaflet-control { + margin-right: 10px; + } + + +/* zoom and fade animations */ + +.leaflet-fade-anim .leaflet-tile { + will-change: opacity; + } +.leaflet-fade-anim .leaflet-popup { + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; + } +.leaflet-fade-anim .leaflet-map-pane .leaflet-popup { + opacity: 1; + } +.leaflet-zoom-animated { + -webkit-transform-origin: 0 0; + -ms-transform-origin: 0 0; + transform-origin: 0 0; + } +.leaflet-zoom-anim .leaflet-zoom-animated { + will-change: transform; + } +.leaflet-zoom-anim .leaflet-zoom-animated { + -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); + -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); + -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1); + transition: transform 0.25s cubic-bezier(0,0,0.25,1); + } +.leaflet-zoom-anim .leaflet-tile, +.leaflet-pan-anim .leaflet-tile { + -webkit-transition: none; + -moz-transition: none; + -o-transition: none; + transition: none; + } + +.leaflet-zoom-anim .leaflet-zoom-hide { + visibility: hidden; + } + + +/* cursors */ + +.leaflet-interactive { + cursor: pointer; + } +.leaflet-grab { + cursor: -webkit-grab; + cursor: -moz-grab; + } +.leaflet-crosshair, +.leaflet-crosshair .leaflet-interactive { + cursor: crosshair; + } +.leaflet-popup-pane, +.leaflet-control { + cursor: auto; + } +.leaflet-dragging .leaflet-grab, +.leaflet-dragging .leaflet-grab .leaflet-interactive, +.leaflet-dragging .leaflet-marker-draggable { + cursor: move; + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + } + +/* marker & overlays interactivity */ +.leaflet-marker-icon, +.leaflet-marker-shadow, +.leaflet-image-layer, +.leaflet-pane > svg path, +.leaflet-tile-container { + pointer-events: none; + } + +.leaflet-marker-icon.leaflet-interactive, +.leaflet-image-layer.leaflet-interactive, +.leaflet-pane > svg path.leaflet-interactive { + pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: auto; + } + +/* visual tweaks */ + +.leaflet-container { + background: #ddd; + outline: 0; + } +.leaflet-container a { + color: #0078A8; + } +.leaflet-container a.leaflet-active { + outline: 2px solid orange; + } +.leaflet-zoom-box { + border: 2px dotted #38f; + background: rgba(255,255,255,0.5); + } + + +/* general typography */ +.leaflet-container { + font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; + } + + +/* general toolbar styles */ + +.leaflet-bar { + box-shadow: 0 1px 5px rgba(0,0,0,0.65); + border-radius: 4px; + } +.leaflet-bar a, +.leaflet-bar a:hover { + background-color: #fff; + border-bottom: 1px solid #ccc; + width: 26px; + height: 26px; + line-height: 26px; + display: block; + text-align: center; + text-decoration: none; + color: black; + } +.leaflet-bar a, +.leaflet-control-layers-toggle { + background-position: 50% 50%; + background-repeat: no-repeat; + display: block; + } +.leaflet-bar a:hover { + background-color: #f4f4f4; + } +.leaflet-bar a:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } +.leaflet-bar a:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom: none; + } +.leaflet-bar a.leaflet-disabled { + cursor: default; + background-color: #f4f4f4; + color: #bbb; + } + +.leaflet-touch .leaflet-bar a { + width: 30px; + height: 30px; + line-height: 30px; + } +.leaflet-touch .leaflet-bar a:first-child { + border-top-left-radius: 2px; + border-top-right-radius: 2px; + } +.leaflet-touch .leaflet-bar a:last-child { + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; + } + +/* zoom control */ + +.leaflet-control-zoom-in, +.leaflet-control-zoom-out { + font: bold 18px 'Lucida Console', Monaco, monospace; + text-indent: 1px; + } + +.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { + font-size: 22px; + } + + +/* layers control */ + +.leaflet-control-layers { + box-shadow: 0 1px 5px rgba(0,0,0,0.4); + background: #fff; + border-radius: 5px; + } +.leaflet-control-layers-toggle { + background-image: url(/assets/frappe/images/leaflet/layers.png); + width: 36px; + height: 36px; + } +.leaflet-retina .leaflet-control-layers-toggle { + background-image: url(/assets/frappe/images/leaflet/layers-2x.png); + background-size: 26px 26px; + } +.leaflet-touch .leaflet-control-layers-toggle { + width: 44px; + height: 44px; + } +.leaflet-control-layers .leaflet-control-layers-list, +.leaflet-control-layers-expanded .leaflet-control-layers-toggle { + display: none; + } +.leaflet-control-layers-expanded .leaflet-control-layers-list { + display: block; + position: relative; + } +.leaflet-control-layers-expanded { + padding: 6px 10px 6px 6px; + color: #333; + background: #fff; + } +.leaflet-control-layers-scrollbar { + overflow-y: scroll; + overflow-x: hidden; + padding-right: 5px; + } +.leaflet-control-layers-selector { + margin-top: 2px; + position: relative; + top: 1px; + } +.leaflet-control-layers label { + display: block; + } +.leaflet-control-layers-separator { + height: 0; + border-top: 1px solid #ddd; + margin: 5px -10px 5px -6px; + } + +/* Default icon URLs */ +.leaflet-default-icon-path { + background-image: url(/assets/frappe/images/leaflet/marker-icon.png); + } + + +/* attribution and scale controls */ + +.leaflet-container .leaflet-control-attribution { + background: #fff; + background: rgba(255, 255, 255, 0.7); + margin: 0; + } +.leaflet-control-attribution, +.leaflet-control-scale-line { + padding: 0 5px; + color: #333; + } +.leaflet-control-attribution a { + text-decoration: none; + } +.leaflet-control-attribution a:hover { + text-decoration: underline; + } +.leaflet-container .leaflet-control-attribution, +.leaflet-container .leaflet-control-scale { + font-size: 11px; + } +.leaflet-left .leaflet-control-scale { + margin-left: 5px; + } +.leaflet-bottom .leaflet-control-scale { + margin-bottom: 5px; + } +.leaflet-control-scale-line { + border: 2px solid #777; + border-top: none; + line-height: 1.1; + padding: 2px 5px 1px; + font-size: 11px; + white-space: nowrap; + overflow: hidden; + -moz-box-sizing: border-box; + box-sizing: border-box; + + background: #fff; + background: rgba(255, 255, 255, 0.5); + } +.leaflet-control-scale-line:not(:first-child) { + border-top: 2px solid #777; + border-bottom: none; + margin-top: -2px; + } +.leaflet-control-scale-line:not(:first-child):not(:last-child) { + border-bottom: 2px solid #777; + } + +.leaflet-touch .leaflet-control-attribution, +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + box-shadow: none; + } +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + border: 2px solid rgba(0,0,0,0.2); + background-clip: padding-box; + } + + +/* popup */ + +.leaflet-popup { + position: absolute; + text-align: center; + margin-bottom: 20px; + } +.leaflet-popup-content-wrapper { + padding: 1px; + text-align: left; + border-radius: 12px; + } +.leaflet-popup-content { + margin: 13px 19px; + line-height: 1.4; + } +.leaflet-popup-content p { + margin: 18px 0; + } +.leaflet-popup-tip-container { + width: 40px; + height: 20px; + position: absolute; + left: 50%; + margin-left: -20px; + overflow: hidden; + pointer-events: none; + } +.leaflet-popup-tip { + width: 17px; + height: 17px; + padding: 1px; + + margin: -10px auto 0; + + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + -o-transform: rotate(45deg); + transform: rotate(45deg); + } +.leaflet-popup-content-wrapper, +.leaflet-popup-tip { + background: white; + color: #333; + box-shadow: 0 3px 14px rgba(0,0,0,0.4); + } +.leaflet-container a.leaflet-popup-close-button { + position: absolute; + top: 0; + right: 0; + padding: 4px 4px 0 0; + border: none; + text-align: center; + width: 18px; + height: 14px; + font: 16px/14px Tahoma, Verdana, sans-serif; + color: #c3c3c3; + text-decoration: none; + font-weight: bold; + background: transparent; + } +.leaflet-container a.leaflet-popup-close-button:hover { + color: #999; + } +.leaflet-popup-scrolled { + overflow: auto; + border-bottom: 1px solid #ddd; + border-top: 1px solid #ddd; + } + +.leaflet-oldie .leaflet-popup-content-wrapper { + zoom: 1; + } +.leaflet-oldie .leaflet-popup-tip { + width: 24px; + margin: 0 auto; + + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; + filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); + } +.leaflet-oldie .leaflet-popup-tip-container { + margin-top: -1px; + } + +.leaflet-oldie .leaflet-control-zoom, +.leaflet-oldie .leaflet-control-layers, +.leaflet-oldie .leaflet-popup-content-wrapper, +.leaflet-oldie .leaflet-popup-tip { + border: 1px solid #999; + } + + +/* div icon */ + +.leaflet-div-icon { + background: #fff; + border: 1px solid #666; + } + + +/* Tooltip */ +/* Base styles for the element that has a tooltip */ +.leaflet-tooltip { + position: absolute; + padding: 6px; + background-color: #fff; + border: 1px solid #fff; + border-radius: 3px; + color: #222; + white-space: nowrap; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + pointer-events: none; + box-shadow: 0 1px 3px rgba(0,0,0,0.4); + } +.leaflet-tooltip.leaflet-clickable { + cursor: pointer; + pointer-events: auto; + } +.leaflet-tooltip-top:before, +.leaflet-tooltip-bottom:before, +.leaflet-tooltip-left:before, +.leaflet-tooltip-right:before { + position: absolute; + pointer-events: none; + border: 6px solid transparent; + background: transparent; + content: ""; + } + +/* Directions */ + +.leaflet-tooltip-bottom { + margin-top: 6px; +} +.leaflet-tooltip-top { + margin-top: -6px; +} +.leaflet-tooltip-bottom:before, +.leaflet-tooltip-top:before { + left: 50%; + margin-left: -6px; + } +.leaflet-tooltip-top:before { + bottom: 0; + margin-bottom: -12px; + border-top-color: #fff; + } +.leaflet-tooltip-bottom:before { + top: 0; + margin-top: -12px; + margin-left: -6px; + border-bottom-color: #fff; + } +.leaflet-tooltip-left { + margin-left: -6px; +} +.leaflet-tooltip-right { + margin-left: 6px; +} +.leaflet-tooltip-left:before, +.leaflet-tooltip-right:before { + top: 50%; + margin-top: -6px; + } +.leaflet-tooltip-left:before { + right: 0; + margin-right: -12px; + border-left-color: #fff; + } +.leaflet-tooltip-right:before { + left: 0; + margin-left: -12px; + border-right-color: #fff; + } diff --git a/frappe/public/js/lib/leaflet/leaflet.draw.css b/frappe/public/js/lib/leaflet/leaflet.draw.css new file mode 100644 index 0000000000..d1accd63bf --- /dev/null +++ b/frappe/public/js/lib/leaflet/leaflet.draw.css @@ -0,0 +1,10 @@ +.leaflet-draw-section{position:relative}.leaflet-draw-toolbar{margin-top:12px}.leaflet-draw-toolbar-top{margin-top:0}.leaflet-draw-toolbar-notop a:first-child{border-top-right-radius:0}.leaflet-draw-toolbar-nobottom a:last-child{border-bottom-right-radius:0}.leaflet-draw-toolbar a{background-image:url('/assets/frappe/images/leaflet/spritesheet.png');background-image:linear-gradient(transparent,transparent),url('/assets/frappe/images/leaflet/spritesheet.svg');background-repeat:no-repeat;background-size:300px 30px;background-clip:padding-box}.leaflet-retina .leaflet-draw-toolbar a{background-image:url('/assets/frappe/images/leaflet/spritesheet-2x.png');background-image:linear-gradient(transparent,transparent),url('/assets/frappe/images/leaflet/spritesheet.svg')} +.leaflet-draw a{display:block;text-align:center;text-decoration:none}.leaflet-draw a .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.leaflet-draw-actions{display:none;list-style:none;margin:0;padding:0;position:absolute;left:26px;top:0;white-space:nowrap}.leaflet-touch .leaflet-draw-actions{left:32px}.leaflet-right .leaflet-draw-actions{right:26px;left:auto}.leaflet-touch .leaflet-right .leaflet-draw-actions{right:32px;left:auto}.leaflet-draw-actions li{display:inline-block} +.leaflet-draw-actions li:first-child a{border-left:0}.leaflet-draw-actions li:last-child a{-webkit-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.leaflet-right .leaflet-draw-actions li:last-child a{-webkit-border-radius:0;border-radius:0}.leaflet-right .leaflet-draw-actions li:first-child a{-webkit-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.leaflet-draw-actions a{background-color:#919187;border-left:1px solid #AAA;color:#FFF;font:11px/19px "Helvetica Neue",Arial,Helvetica,sans-serif;line-height:28px;text-decoration:none;padding-left:10px;padding-right:10px;height:28px} +.leaflet-touch .leaflet-draw-actions a{font-size:12px;line-height:30px;height:30px}.leaflet-draw-actions-bottom{margin-top:0}.leaflet-draw-actions-top{margin-top:1px}.leaflet-draw-actions-top a,.leaflet-draw-actions-bottom a{height:27px;line-height:27px}.leaflet-draw-actions a:hover{background-color:#a0a098}.leaflet-draw-actions-top.leaflet-draw-actions-bottom a{height:26px;line-height:26px}.leaflet-draw-toolbar .leaflet-draw-draw-polyline{background-position:-2px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-polyline{background-position:0 -1px} +.leaflet-draw-toolbar .leaflet-draw-draw-polygon{background-position:-31px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-polygon{background-position:-29px -1px}.leaflet-draw-toolbar .leaflet-draw-draw-rectangle{background-position:-62px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-rectangle{background-position:-60px -1px}.leaflet-draw-toolbar .leaflet-draw-draw-circle{background-position:-92px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-circle{background-position:-90px -1px} +.leaflet-draw-toolbar .leaflet-draw-draw-marker{background-position:-122px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-marker{background-position:-120px -1px}.leaflet-draw-toolbar .leaflet-draw-draw-circlemarker{background-position:-273px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-circlemarker{background-position:-271px -1px}.leaflet-draw-toolbar .leaflet-draw-edit-edit{background-position:-152px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-edit{background-position:-150px -1px} +.leaflet-draw-toolbar .leaflet-draw-edit-remove{background-position:-182px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-remove{background-position:-180px -1px}.leaflet-draw-toolbar .leaflet-draw-edit-edit.leaflet-disabled{background-position:-212px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-edit.leaflet-disabled{background-position:-210px -1px}.leaflet-draw-toolbar .leaflet-draw-edit-remove.leaflet-disabled{background-position:-242px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-remove.leaflet-disabled{background-position:-240px -2px} +.leaflet-mouse-marker{background-color:#fff;cursor:crosshair}.leaflet-draw-tooltip{background:#363636;background:rgba(0,0,0,0.5);border:1px solid transparent;-webkit-border-radius:4px;border-radius:4px;color:#fff;font:12px/18px "Helvetica Neue",Arial,Helvetica,sans-serif;margin-left:20px;margin-top:-21px;padding:4px 8px;position:absolute;visibility:hidden;white-space:nowrap;z-index:6}.leaflet-draw-tooltip:before{border-right:6px solid black;border-right-color:rgba(0,0,0,0.5);border-top:6px solid transparent;border-bottom:6px solid transparent;content:"";position:absolute;top:7px;left:-7px} +.leaflet-error-draw-tooltip{background-color:#f2dede;border:1px solid #e6b6bd;color:#b94a48}.leaflet-error-draw-tooltip:before{border-right-color:#e6b6bd}.leaflet-draw-tooltip-single{margin-top:-12px}.leaflet-draw-tooltip-subtext{color:#f8d5e4}.leaflet-draw-guide-dash{font-size:1%;opacity:.6;position:absolute;width:5px;height:5px}.leaflet-edit-marker-selected{background-color:rgba(254,87,161,0.1);border:4px dashed rgba(254,87,161,0.6);-webkit-border-radius:4px;border-radius:4px;box-sizing:content-box} +.leaflet-edit-move{cursor:move}.leaflet-edit-resize{cursor:pointer}.leaflet-oldie .leaflet-draw-toolbar{border:1px solid #999} \ No newline at end of file diff --git a/frappe/public/js/lib/leaflet/leaflet.draw.js b/frappe/public/js/lib/leaflet/leaflet.draw.js new file mode 100644 index 0000000000..9fb5892a0c --- /dev/null +++ b/frappe/public/js/lib/leaflet/leaflet.draw.js @@ -0,0 +1,1702 @@ +/* + Leaflet.draw 0.4.12, a plugin that adds drawing and editing tools to Leaflet powered maps. + (c) 2012-2017, Jacob Toye, Jon West, Smartrak, Leaflet + + https://github.com/Leaflet/Leaflet.draw + http://leafletjs.com + */ +! function(t, e, i) { + function o(t, e) { + for (; + (t = t.parentElement) && !t.classList.contains(e);); + return t + } + L.drawVersion = "0.4.12", L.Draw = {}, L.drawLocal = { + draw: { + toolbar: { + actions: { + title: "Cancel drawing", + text: "Cancel" + }, + finish: { + title: "Finish drawing", + text: "Finish" + }, + undo: { + title: "Delete last point drawn", + text: "Delete last point" + }, + buttons: { + polyline: "Draw a polyline", + polygon: "Draw a polygon", + rectangle: "Draw a rectangle", + circle: "Draw a circle", + marker: "Draw a marker", + circlemarker: "Draw a circlemarker" + } + }, + handlers: { + circle: { + tooltip: { + start: "Click and drag to draw circle." + }, + radius: "Radius" + }, + circlemarker: { + tooltip: { + start: "Click map to place circle marker." + } + }, + marker: { + tooltip: { + start: "Click map to place marker." + } + }, + polygon: { + tooltip: { + start: "Click to start drawing shape.", + cont: "Click to continue drawing shape.", + end: "Click first point to close this shape." + } + }, + polyline: { + error: "Error: shape edges cannot cross!", + tooltip: { + start: "Click to start drawing line.", + cont: "Click to continue drawing line.", + end: "Click last point to finish line." + } + }, + rectangle: { + tooltip: { + start: "Click and drag to draw rectangle." + } + }, + simpleshape: { + tooltip: { + end: "Release mouse to finish drawing." + } + } + } + }, + edit: { + toolbar: { + actions: { + save: { + title: "Save changes", + text: "Save" + }, + cancel: { + title: "Cancel editing, discards all changes", + text: "Cancel" + }, + clearAll: { + title: "Clear all layers", + text: "Clear All" + } + }, + buttons: { + edit: "Edit layers", + editDisabled: "No layers to edit", + remove: "Delete layers", + removeDisabled: "No layers to delete" + } + }, + handlers: { + edit: { + tooltip: { + text: "Drag handles or markers to edit features.", + subtext: "Click cancel to undo changes." + } + }, + remove: { + tooltip: { + text: "Click on a feature to remove." + } + } + } + } + }, L.Draw.Event = {}, L.Draw.Event.CREATED = "draw:created", L.Draw.Event.EDITED = "draw:edited", L.Draw.Event.DELETED = "draw:deleted", L.Draw.Event.DRAWSTART = "draw:drawstart", L.Draw.Event.DRAWSTOP = "draw:drawstop", L.Draw.Event.DRAWVERTEX = "draw:drawvertex", L.Draw.Event.EDITSTART = "draw:editstart", L.Draw.Event.EDITMOVE = "draw:editmove", L.Draw.Event.EDITRESIZE = "draw:editresize", L.Draw.Event.EDITVERTEX = "draw:editvertex", L.Draw.Event.EDITSTOP = "draw:editstop", L.Draw.Event.DELETESTART = "draw:deletestart", L.Draw.Event.DELETESTOP = "draw:deletestop", L.Draw = L.Draw || {}, L.Draw.Feature = L.Handler.extend({ + initialize: function(t, e) { + this._map = t, this._container = t._container, this._overlayPane = t._panes.overlayPane, this._popupPane = t._panes.popupPane, e && e.shapeOptions && (e.shapeOptions = L.Util.extend({}, this.options.shapeOptions, e.shapeOptions)), L.setOptions(this, e); + var version = L.version.split("."); + if(parseInt(version[0],10) === 1 && parseInt(version[1],10) >= 2 ) { + L.Draw.Feature.include(L.Evented.prototype); + } else { + L.Draw.Feature.include(L.Mixin.Events); + } + + }, + enable: function() { + this._enabled || (L.Handler.prototype.enable.call(this), this.fire("enabled", { + handler: this.type + }), this._map.fire(L.Draw.Event.DRAWSTART, { + layerType: this.type + })) + }, + disable: function() { + this._enabled && (L.Handler.prototype.disable.call(this), this._map.fire(L.Draw.Event.DRAWSTOP, { + layerType: this.type + }), this.fire("disabled", { + handler: this.type + })) + }, + addHooks: function() { + var t = this._map; + t && (L.DomUtil.disableTextSelection(), t.getContainer().focus(), this._tooltip = new L.Draw.Tooltip(this._map), L.DomEvent.on(this._container, "keyup", this._cancelDrawing, this)) + }, + removeHooks: function() { + this._map && (L.DomUtil.enableTextSelection(), this._tooltip.dispose(), this._tooltip = null, L.DomEvent.off(this._container, "keyup", this._cancelDrawing, this)) + }, + setOptions: function(t) { + L.setOptions(this, t) + }, + _fireCreatedEvent: function(t) { + this._map.fire(L.Draw.Event.CREATED, { + layer: t, + layerType: this.type + }) + }, + _cancelDrawing: function(t) { + 27 === t.keyCode && (this._map.fire("draw:canceled", { + layerType: this.type + }), this.disable()) + } + }), L.Draw.Polyline = L.Draw.Feature.extend({ + statics: { + TYPE: "polyline" + }, + Poly: L.Polyline, + options: { + allowIntersection: !0, + repeatMode: !1, + drawError: { + color: "#b00b00", + timeout: 2500 + }, + icon: new L.DivIcon({ + iconSize: new L.Point(8, 8), + className: "leaflet-div-icon leaflet-editing-icon" + }), + touchIcon: new L.DivIcon({ + iconSize: new L.Point(20, 20), + className: "leaflet-div-icon leaflet-editing-icon leaflet-touch-icon" + }), + guidelineDistance: 20, + maxGuideLineLength: 4e3, + shapeOptions: { + stroke: !0, + color: "#3388ff", + weight: 4, + opacity: .5, + fill: !1, + clickable: !0 + }, + metric: !0, + feet: !0, + nautic: !1, + showLength: !0, + zIndexOffset: 2e3, + factor: 1 + }, + initialize: function(t, e) { + L.Browser.touch && (this.options.icon = this.options.touchIcon), this.options.drawError.message = L.drawLocal.draw.handlers.polyline.error, e && e.drawError && (e.drawError = L.Util.extend({}, this.options.drawError, e.drawError)), this.type = L.Draw.Polyline.TYPE, L.Draw.Feature.prototype.initialize.call(this, t, e) + }, + addHooks: function() { + L.Draw.Feature.prototype.addHooks.call(this), this._map && (this._markers = [], this._markerGroup = new L.LayerGroup, this._map.addLayer(this._markerGroup), this._poly = new L.Polyline([], this.options.shapeOptions), this._tooltip.updateContent(this._getTooltipText()), this._mouseMarker || (this._mouseMarker = L.marker(this._map.getCenter(), { + icon: L.divIcon({ + className: "leaflet-mouse-marker", + iconAnchor: [20, 20], + iconSize: [40, 40] + }), + opacity: 0, + zIndexOffset: this.options.zIndexOffset + })), this._mouseMarker.on("mouseout", this._onMouseOut, this).on("mousemove", this._onMouseMove, this).on("mousedown", this._onMouseDown, this).on("mouseup", this._onMouseUp, this).addTo(this._map), this._map.on("mouseup", this._onMouseUp, this).on("mousemove", this._onMouseMove, this).on("zoomlevelschange", this._onZoomEnd, this).on("touchstart", this._onTouch, this).on("zoomend", this._onZoomEnd, this)) + }, + removeHooks: function() { + L.Draw.Feature.prototype.removeHooks.call(this), this._clearHideErrorTimeout(), this._cleanUpShape(), this._map.removeLayer(this._markerGroup), delete this._markerGroup, delete this._markers, this._map.removeLayer(this._poly), delete this._poly, this._mouseMarker.off("mousedown", this._onMouseDown, this).off("mouseout", this._onMouseOut, this).off("mouseup", this._onMouseUp, this).off("mousemove", this._onMouseMove, this), this._map.removeLayer(this._mouseMarker), delete this._mouseMarker, this._clearGuides(), this._map.off("mouseup", this._onMouseUp, this).off("mousemove", this._onMouseMove, this).off("zoomlevelschange", this._onZoomEnd, this).off("zoomend", this._onZoomEnd, this).off("touchstart", this._onTouch, this).off("click", this._onTouch, this) + }, + deleteLastVertex: function() { + if (!(this._markers.length <= 1)) { + var t = this._markers.pop(), + e = this._poly, + i = e.getLatLngs(), + o = i.splice(-1, 1)[0]; + this._poly.setLatLngs(i), this._markerGroup.removeLayer(t), e.getLatLngs().length < 2 && this._map.removeLayer(e), this._vertexChanged(o, !1) + } + }, + addVertex: function(t) { + if (this._markers.length >= 2 && !this.options.allowIntersection && this._poly.newLatLngIntersects(t)) return void this._showErrorTooltip(); + this._errorShown && this._hideErrorTooltip(), this._markers.push(this._createMarker(t)), this._poly.addLatLng(t), 2 === this._poly.getLatLngs().length && this._map.addLayer(this._poly), this._vertexChanged(t, !0) + }, + completeShape: function() { + this._markers.length <= 1 || (this._fireCreatedEvent(), this.disable(), this.options.repeatMode && this.enable()) + }, + _finishShape: function() { + var t = this._poly._defaultShape ? this._poly._defaultShape() : this._poly.getLatLngs(), + e = this._poly.newLatLngIntersects(t[t.length - 1]); + if (!this.options.allowIntersection && e || !this._shapeIsValid()) return void this._showErrorTooltip(); + this._fireCreatedEvent(), this.disable(), this.options.repeatMode && this.enable() + }, + _shapeIsValid: function() { + return !0 + }, + _onZoomEnd: function() { + null !== this._markers && this._updateGuide() + }, + _onMouseMove: function(t) { + var e = this._map.mouseEventToLayerPoint(t.originalEvent), + i = this._map.layerPointToLatLng(e); + this._currentLatLng = i, this._updateTooltip(i), this._updateGuide(e), this._mouseMarker.setLatLng(i), L.DomEvent.preventDefault(t.originalEvent) + }, + _vertexChanged: function(t, e) { + this._map.fire(L.Draw.Event.DRAWVERTEX, { + layers: this._markerGroup + }), this._updateFinishHandler(), this._updateRunningMeasure(t, e), this._clearGuides(), this._updateTooltip() + }, + _onMouseDown: function(t) { + if (!this._clickHandled && !this._touchHandled && !this._disableMarkers) { + this._onMouseMove(t), this._clickHandled = !0, this._disableNewMarkers(); + var e = t.originalEvent, + i = e.clientX, + o = e.clientY; + this._startPoint.call(this, i, o) + } + }, + _startPoint: function(t, e) { + this._mouseDownOrigin = L.point(t, e) + }, + _onMouseUp: function(t) { + var e = t.originalEvent, + i = e.clientX, + o = e.clientY; + this._endPoint.call(this, i, o, t), this._clickHandled = null + }, + _endPoint: function(e, i, o) { + if (this._mouseDownOrigin) { + var n = L.point(e, i).distanceTo(this._mouseDownOrigin); + this._calculateFinishDistance(o.latlng) < 10 && L.Browser.touch ? this._finishShape() : Math.abs(n) < 9 * (t.devicePixelRatio || 1) && this.addVertex(o.latlng), this._enableNewMarkers() + } + this._mouseDownOrigin = null + }, + _onTouch: function(t) { + var e, i, o = t.originalEvent; + !o.touches || !o.touches[0] || this._clickHandled || this._touchHandled || this._disableMarkers || (e = o.touches[0].clientX, i = o.touches[0].clientY, this._disableNewMarkers(), this._touchHandled = !0, this._startPoint.call(this, e, i), this._endPoint.call(this, e, i, t), this._touchHandled = null), this._clickHandled = null + }, + _onMouseOut: function() { + this._tooltip && this._tooltip._onMouseOut.call(this._tooltip) + }, + _calculateFinishDistance: function(t) { + var e; + if (this._markers.length > 0) { + var i; + if (this.type === L.Draw.Polyline.TYPE) i = this._markers[this._markers.length - 1]; + else { + if (this.type !== L.Draw.Polygon.TYPE) return 1 / 0; + i = this._markers[0] + } + var o = this._map.latLngToContainerPoint(i.getLatLng()), + n = new L.Marker(t, { + icon: this.options.icon, + zIndexOffset: 2 * this.options.zIndexOffset + }), + a = this._map.latLngToContainerPoint(n.getLatLng()); + e = o.distanceTo(a) + } else e = 1 / 0; + return e + }, + _updateFinishHandler: function() { + var t = this._markers.length; + t > 1 && this._markers[t - 1].on("click", this._finishShape, this), t > 2 && this._markers[t - 2].off("click", this._finishShape, this) + }, + _createMarker: function(t) { + var e = new L.Marker(t, { + icon: this.options.icon, + zIndexOffset: 2 * this.options.zIndexOffset + }); + return this._markerGroup.addLayer(e), e + }, + _updateGuide: function(t) { + var e = this._markers ? this._markers.length : 0; + e > 0 && (t = t || this._map.latLngToLayerPoint(this._currentLatLng), this._clearGuides(), this._drawGuide(this._map.latLngToLayerPoint(this._markers[e - 1].getLatLng()), t)) + }, + _updateTooltip: function(t) { + var e = this._getTooltipText(); + t && this._tooltip.updatePosition(t), this._errorShown || this._tooltip.updateContent(e) + }, + _drawGuide: function(t, e) { + var i, o, n, a = Math.floor(Math.sqrt(Math.pow(e.x - t.x, 2) + Math.pow(e.y - t.y, 2))), + s = this.options.guidelineDistance, + r = this.options.maxGuideLineLength, + l = a > r ? a - r : s; + for (this._guidesContainer || (this._guidesContainer = L.DomUtil.create("div", "leaflet-draw-guides", this._overlayPane)); l < a; l += this.options.guidelineDistance) i = l / a, o = { + x: Math.floor(t.x * (1 - i) + i * e.x), + y: Math.floor(t.y * (1 - i) + i * e.y) + }, n = L.DomUtil.create("div", "leaflet-draw-guide-dash", this._guidesContainer), n.style.backgroundColor = this._errorShown ? this.options.drawError.color : this.options.shapeOptions.color, L.DomUtil.setPosition(n, o) + }, + _updateGuideColor: function(t) { + if (this._guidesContainer) + for (var e = 0, i = this._guidesContainer.childNodes.length; e < i; e++) this._guidesContainer.childNodes[e].style.backgroundColor = t + }, + _clearGuides: function() { + if (this._guidesContainer) + for (; this._guidesContainer.firstChild;) this._guidesContainer.removeChild(this._guidesContainer.firstChild) + }, + _getTooltipText: function() { + var t, e, i = this.options.showLength; + return L.Browser.touch && (i = !1), 0 === this._markers.length ? t = { + text: L.drawLocal.draw.handlers.polyline.tooltip.start + } : (e = i ? this._getMeasurementString() : "", t = 1 === this._markers.length ? { + text: L.drawLocal.draw.handlers.polyline.tooltip.cont, + subtext: e + } : { + text: L.drawLocal.draw.handlers.polyline.tooltip.end, + subtext: e + }), t + }, + _updateRunningMeasure: function(t, e) { + var i, o, n = this._markers.length; + 1 === this._markers.length ? this._measurementRunningTotal = 0 : (i = n - (e ? 2 : 1), o = this._map.distance(t, this._markers[i].getLatLng()) * (this.options.factor || 1), this._measurementRunningTotal += o * (e ? 1 : -1)) + }, + _getMeasurementString: function() { + var t, e = this._currentLatLng, + i = this._markers[this._markers.length - 1].getLatLng(); + return t = i && e ? this._measurementRunningTotal + this._map.distance(e, i) * (this.options.factor || 1) : this._measurementRunningTotal || 0, L.GeometryUtil.readableDistance(t, this.options.metric, this.options.feet, this.options.nautic, this.options.precision) + }, + _showErrorTooltip: function() { + this._errorShown = !0, this._tooltip.showAsError().updateContent({ + text: this.options.drawError.message + }), this._updateGuideColor(this.options.drawError.color), this._poly.setStyle({ + color: this.options.drawError.color + }), this._clearHideErrorTimeout(), this._hideErrorTimeout = setTimeout(L.Util.bind(this._hideErrorTooltip, this), this.options.drawError.timeout) + }, + _hideErrorTooltip: function() { + this._errorShown = !1, this._clearHideErrorTimeout(), this._tooltip.removeError().updateContent(this._getTooltipText()), this._updateGuideColor(this.options.shapeOptions.color), this._poly.setStyle({ + color: this.options.shapeOptions.color + }) + }, + _clearHideErrorTimeout: function() { + this._hideErrorTimeout && (clearTimeout(this._hideErrorTimeout), this._hideErrorTimeout = null) + }, + _disableNewMarkers: function() { + this._disableMarkers = !0 + }, + _enableNewMarkers: function() { + setTimeout(function() { + this._disableMarkers = !1 + }.bind(this), 50) + }, + _cleanUpShape: function() { + this._markers.length > 1 && this._markers[this._markers.length - 1].off("click", this._finishShape, this) + }, + _fireCreatedEvent: function() { + var t = new this.Poly(this._poly.getLatLngs(), this.options.shapeOptions); + L.Draw.Feature.prototype._fireCreatedEvent.call(this, t) + } + }), L.Draw.Polygon = L.Draw.Polyline.extend({ + statics: { + TYPE: "polygon" + }, + Poly: L.Polygon, + options: { + showArea: !1, + showLength: !1, + shapeOptions: { + stroke: !0, + color: "#3388ff", + weight: 4, + opacity: .5, + fill: !0, + fillColor: null, + fillOpacity: .2, + clickable: !0 + }, + metric: !0, + feet: !0, + nautic: !1, + precision: {} + }, + initialize: function(t, e) { + L.Draw.Polyline.prototype.initialize.call(this, t, e), this.type = L.Draw.Polygon.TYPE + }, + _updateFinishHandler: function() { + var t = this._markers.length; + 1 === t && this._markers[0].on("click", this._finishShape, this), t > 2 && (this._markers[t - 1].on("dblclick", this._finishShape, this), t > 3 && this._markers[t - 2].off("dblclick", this._finishShape, this)) + }, + _getTooltipText: function() { + var t, e; + return 0 === this._markers.length ? t = L.drawLocal.draw.handlers.polygon.tooltip.start : this._markers.length < 3 ? (t = L.drawLocal.draw.handlers.polygon.tooltip.cont, e = this._getMeasurementString()) : (t = L.drawLocal.draw.handlers.polygon.tooltip.end, e = this._getMeasurementString()), { + text: t, + subtext: e + } + }, + _getMeasurementString: function() { + var t = this._area, + e = ""; + return t || this.options.showLength ? (this.options.showLength && (e = L.Draw.Polyline.prototype._getMeasurementString.call(this)), t && (e += "
    " + L.GeometryUtil.readableArea(t, this.options.metric, this.options.precision)), e) : null + }, + _shapeIsValid: function() { + return this._markers.length >= 3 + }, + _vertexChanged: function(t, e) { + var i; + !this.options.allowIntersection && this.options.showArea && (i = this._poly.getLatLngs(), this._area = L.GeometryUtil.geodesicArea(i)), L.Draw.Polyline.prototype._vertexChanged.call(this, t, e) + }, + _cleanUpShape: function() { + var t = this._markers.length; + t > 0 && (this._markers[0].off("click", this._finishShape, this), t > 2 && this._markers[t - 1].off("dblclick", this._finishShape, this)) + } + }), L.SimpleShape = {}, L.Draw.SimpleShape = L.Draw.Feature.extend({ + options: { + repeatMode: !1 + }, + initialize: function(t, e) { + this._endLabelText = L.drawLocal.draw.handlers.simpleshape.tooltip.end, L.Draw.Feature.prototype.initialize.call(this, t, e) + }, + addHooks: function() { + L.Draw.Feature.prototype.addHooks.call(this), this._map && (this._mapDraggable = this._map.dragging.enabled(), this._mapDraggable && this._map.dragging.disable(), this._container.style.cursor = "crosshair", this._tooltip.updateContent({ + text: this._initialLabelText + }), this._map.on("mousedown", this._onMouseDown, this).on("mousemove", this._onMouseMove, this).on("touchstart", this._onMouseDown, this).on("touchmove", this._onMouseMove, this)) + }, + removeHooks: function() { + L.Draw.Feature.prototype.removeHooks.call(this), this._map && (this._mapDraggable && this._map.dragging.enable(), this._container.style.cursor = "", this._map.off("mousedown", this._onMouseDown, this).off("mousemove", this._onMouseMove, this).off("touchstart", this._onMouseDown, this).off("touchmove", this._onMouseMove, this), L.DomEvent.off(e, "mouseup", this._onMouseUp, this), L.DomEvent.off(e, "touchend", this._onMouseUp, this), this._shape && (this._map.removeLayer(this._shape), delete this._shape)), this._isDrawing = !1 + }, + _getTooltipText: function() { + return { + text: this._endLabelText + } + }, + _onMouseDown: function(t) { + this._isDrawing = !0, this._startLatLng = t.latlng, L.DomEvent.on(e, "mouseup", this._onMouseUp, this).on(e, "touchend", this._onMouseUp, this).preventDefault(t.originalEvent) + }, + _onMouseMove: function(t) { + var e = t.latlng; + this._tooltip.updatePosition(e), this._isDrawing && (this._tooltip.updateContent(this._getTooltipText()), this._drawShape(e)) + }, + _onMouseUp: function() { + this._shape && this._fireCreatedEvent(), this.disable(), this.options.repeatMode && this.enable() + } + }), L.Draw.Rectangle = L.Draw.SimpleShape.extend({ + statics: { + TYPE: "rectangle" + }, + options: { + shapeOptions: { + stroke: !0, + color: "#3388ff", + weight: 4, + opacity: .5, + fill: !0, + fillColor: null, + fillOpacity: .2, + showArea: !0, + clickable: !0 + }, + metric: !0 + }, + initialize: function(t, e) { + this.type = L.Draw.Rectangle.TYPE, this._initialLabelText = L.drawLocal.draw.handlers.rectangle.tooltip.start, L.Draw.SimpleShape.prototype.initialize.call(this, t, e) + }, + disable: function() { + this._enabled && (this._isCurrentlyTwoClickDrawing = !1, L.Draw.SimpleShape.prototype.disable.call(this)) + }, + _onMouseUp: function(t) { + if (!this._shape && !this._isCurrentlyTwoClickDrawing) return void(this._isCurrentlyTwoClickDrawing = !0); + this._isCurrentlyTwoClickDrawing && !o(t.target, "leaflet-pane") || L.Draw.SimpleShape.prototype._onMouseUp.call(this) + }, + _drawShape: function(t) { + this._shape ? this._shape.setBounds(new L.LatLngBounds(this._startLatLng, t)) : (this._shape = new L.Rectangle(new L.LatLngBounds(this._startLatLng, t), this.options.shapeOptions), this._map.addLayer(this._shape)) + }, + _fireCreatedEvent: function() { + var t = new L.Rectangle(this._shape.getBounds(), this.options.shapeOptions); + L.Draw.SimpleShape.prototype._fireCreatedEvent.call(this, t) + }, + _getTooltipText: function() { + var t, e, i, o = L.Draw.SimpleShape.prototype._getTooltipText.call(this), + n = this._shape, + a = this.options.showArea; + return n && (t = this._shape._defaultShape ? this._shape._defaultShape() : this._shape.getLatLngs(), e = L.GeometryUtil.geodesicArea(t), i = a ? L.GeometryUtil.readableArea(e, this.options.metric) : ""), { + text: o.text, + subtext: i + } + } + }), L.Draw.Marker = L.Draw.Feature.extend({ + statics: { + TYPE: "marker" + }, + options: { + icon: new L.Icon.Default, + repeatMode: !1, + zIndexOffset: 2e3 + }, + initialize: function(t, e) { + this.type = L.Draw.Marker.TYPE, this._initialLabelText = L.drawLocal.draw.handlers.marker.tooltip.start, L.Draw.Feature.prototype.initialize.call(this, t, e) + }, + addHooks: function() { + L.Draw.Feature.prototype.addHooks.call(this), this._map && (this._tooltip.updateContent({ + text: this._initialLabelText + }), this._mouseMarker || (this._mouseMarker = L.marker(this._map.getCenter(), { + icon: L.divIcon({ + className: "leaflet-mouse-marker", + iconAnchor: [20, 20], + iconSize: [40, 40] + }), + opacity: 0, + zIndexOffset: this.options.zIndexOffset + })), this._mouseMarker.on("click", this._onClick, this).addTo(this._map), this._map.on("mousemove", this._onMouseMove, this), this._map.on("click", this._onTouch, this)) + }, + removeHooks: function() { + L.Draw.Feature.prototype.removeHooks.call(this), this._map && (this._marker && (this._marker.off("click", this._onClick, this), this._map.off("click", this._onClick, this).off("click", this._onTouch, this).removeLayer(this._marker), delete this._marker), this._mouseMarker.off("click", this._onClick, this), this._map.removeLayer(this._mouseMarker), delete this._mouseMarker, this._map.off("mousemove", this._onMouseMove, this)) + }, + _onMouseMove: function(t) { + var e = t.latlng; + this._tooltip.updatePosition(e), this._mouseMarker.setLatLng(e), this._marker ? (e = this._mouseMarker.getLatLng(), this._marker.setLatLng(e)) : (this._marker = this._createMarker(e), this._marker.on("click", this._onClick, this), this._map.on("click", this._onClick, this).addLayer(this._marker)) + }, + _createMarker: function(t) { + return new L.Marker(t, { + icon: this.options.icon, + zIndexOffset: this.options.zIndexOffset + }) + }, + _onClick: function() { + this._fireCreatedEvent(), this.disable(), this.options.repeatMode && this.enable() + }, + _onTouch: function(t) { + this._onMouseMove(t), this._onClick() + }, + _fireCreatedEvent: function() { + var t = new L.Marker.Touch(this._marker.getLatLng(), { + icon: this.options.icon + }); + L.Draw.Feature.prototype._fireCreatedEvent.call(this, t) + } + }), L.Draw.CircleMarker = L.Draw.Marker.extend({ + statics: { + TYPE: "circlemarker" + }, + options: { + stroke: !0, + color: "#3388ff", + weight: 4, + opacity: .5, + fill: !0, + fillColor: null, + fillOpacity: .2, + clickable: !0, + zIndexOffset: 2e3 + }, + initialize: function(t, e) { + this.type = L.Draw.CircleMarker.TYPE, this._initialLabelText = L.drawLocal.draw.handlers.circlemarker.tooltip.start, L.Draw.Feature.prototype.initialize.call(this, t, e) + }, + _fireCreatedEvent: function() { + var t = new L.CircleMarker(this._marker.getLatLng(), this.options); + L.Draw.Feature.prototype._fireCreatedEvent.call(this, t) + }, + _createMarker: function(t) { + return new L.CircleMarker(t, this.options) + } + }), L.Draw.Circle = L.Draw.SimpleShape.extend({ + statics: { + TYPE: "circle" + }, + options: { + shapeOptions: { + stroke: !0, + color: "#3388ff", + weight: 4, + opacity: .5, + fill: !0, + fillColor: null, + fillOpacity: .2, + clickable: !0 + }, + showRadius: !0, + metric: !0, + feet: !0, + nautic: !1 + }, + initialize: function(t, e) { + this.type = L.Draw.Circle.TYPE, this._initialLabelText = L.drawLocal.draw.handlers.circle.tooltip.start, L.Draw.SimpleShape.prototype.initialize.call(this, t, e) + }, + _drawShape: function(t) { + var e = this._map.distance(this._startLatLng, t); + this._shape ? this._shape.setRadius(e) : (this._shape = new L.Circle(this._startLatLng, e, this.options.shapeOptions), this._map.addLayer(this._shape)) + }, + _fireCreatedEvent: function() { + var t = new L.Circle(this._startLatLng, this._shape.getRadius(), this.options.shapeOptions); + L.Draw.SimpleShape.prototype._fireCreatedEvent.call(this, t) + }, + _onMouseMove: function(t) { + var e, i = t.latlng, + o = this.options.showRadius, + n = this.options.metric; + if (this._tooltip.updatePosition(i), this._isDrawing) { + this._drawShape(i), e = this._shape.getRadius().toFixed(1); + var a = ""; + o && (a = L.drawLocal.draw.handlers.circle.radius + ": " + L.GeometryUtil.readableDistance(e, n, this.options.feet, this.options.nautic)), this._tooltip.updateContent({ + text: this._endLabelText, + subtext: a + }) + } + } + }), L.Edit = L.Edit || {}, L.Edit.Marker = L.Handler.extend({ + initialize: function(t, e) { + this._marker = t, L.setOptions(this, e) + }, + addHooks: function() { + var t = this._marker; + t.dragging.enable(), t.on("dragend", this._onDragEnd, t), this._toggleMarkerHighlight() + }, + removeHooks: function() { + var t = this._marker; + t.dragging.disable(), t.off("dragend", this._onDragEnd, t), this._toggleMarkerHighlight() + }, + _onDragEnd: function(t) { + var e = t.target; + e.edited = !0, this._map.fire(L.Draw.Event.EDITMOVE, { + layer: e + }) + }, + _toggleMarkerHighlight: function() { + var t = this._marker._icon; + t && (t.style.display = "none", L.DomUtil.hasClass(t, "leaflet-edit-marker-selected") ? (L.DomUtil.removeClass(t, "leaflet-edit-marker-selected"), this._offsetMarker(t, -4)) : (L.DomUtil.addClass(t, "leaflet-edit-marker-selected"), this._offsetMarker(t, 4)), t.style.display = "") + }, + _offsetMarker: function(t, e) { + var i = parseInt(t.style.marginTop, 10) - e, + o = parseInt(t.style.marginLeft, 10) - e; + t.style.marginTop = i + "px", t.style.marginLeft = o + "px" + } + }), L.Marker.addInitHook(function() { + L.Edit.Marker && (this.editing = new L.Edit.Marker(this), this.options.editable && this.editing.enable()) + }), L.Edit = L.Edit || {}, L.Edit.Poly = L.Handler.extend({ + options: {}, + initialize: function(t, e) { + this.latlngs = [t._latlngs], t._holes && (this.latlngs = this.latlngs.concat(t._holes)), this._poly = t, L.setOptions(this, e), this._poly.on("revert-edited", this._updateLatLngs, this) + }, + _defaultShape: function() { + return L.Polyline._flat ? L.Polyline._flat(this._poly._latlngs) ? this._poly._latlngs : this._poly._latlngs[0] : this._poly._latlngs + }, + _eachVertexHandler: function(t) { + for (var e = 0; e < this._verticesHandlers.length; e++) t(this._verticesHandlers[e]) + }, + addHooks: function() { + this._initHandlers(), this._eachVertexHandler(function(t) { + t.addHooks() + }) + }, + removeHooks: function() { + this._eachVertexHandler(function(t) { + t.removeHooks() + }) + }, + updateMarkers: function() { + this._eachVertexHandler(function(t) { + t.updateMarkers() + }) + }, + _initHandlers: function() { + this._verticesHandlers = []; + for (var t = 0; t < this.latlngs.length; t++) this._verticesHandlers.push(new L.Edit.PolyVerticesEdit(this._poly, this.latlngs[t], this.options)) + }, + _updateLatLngs: function(t) { + this.latlngs = [t.layer._latlngs], t.layer._holes && (this.latlngs = this.latlngs.concat(t.layer._holes)) + } + }), L.Edit.PolyVerticesEdit = L.Handler.extend({ + options: { + icon: new L.DivIcon({ + iconSize: new L.Point(8, 8), + className: "leaflet-div-icon leaflet-editing-icon" + }), + touchIcon: new L.DivIcon({ + iconSize: new L.Point(20, 20), + className: "leaflet-div-icon leaflet-editing-icon leaflet-touch-icon" + }), + drawError: { + color: "#b00b00", + timeout: 1e3 + } + }, + initialize: function(t, e, i) { + L.Browser.touch && (this.options.icon = this.options.touchIcon), this._poly = t, i && i.drawError && (i.drawError = L.Util.extend({}, this.options.drawError, i.drawError)), this._latlngs = e, L.setOptions(this, i) + }, + _defaultShape: function() { + return L.Polyline._flat ? L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0] : this._latlngs + }, + addHooks: function() { + var t = this._poly; + t instanceof L.Polygon || (t.options.fill = !1, t.options.editing && (t.options.editing.fill = !1)), t.setStyle(t.options.editing), this._poly._map && (this._map = this._poly._map, this._markerGroup || this._initMarkers(), this._poly._map.addLayer(this._markerGroup)) + }, + removeHooks: function() { + var t = this._poly; + t.setStyle(t.options.original), t._map && (t._map.removeLayer(this._markerGroup), delete this._markerGroup, delete this._markers) + }, + updateMarkers: function() { + this._markerGroup.clearLayers(), this._initMarkers() + }, + _initMarkers: function() { + this._markerGroup || (this._markerGroup = new L.LayerGroup), this._markers = []; + var t, e, i, o, n = this._defaultShape(); + for (t = 0, i = n.length; t < i; t++) o = this._createMarker(n[t], t), o.on("click", this._onMarkerClick, this), this._markers.push(o); + var a, s; + for (t = 0, e = i - 1; t < i; e = t++)(0 !== t || L.Polygon && this._poly instanceof L.Polygon) && (a = this._markers[e], s = this._markers[t], this._createMiddleMarker(a, s), this._updatePrevNext(a, s)) + }, + _createMarker: function(t, e) { + var i = new L.Marker.Touch(t, { + draggable: !0, + icon: this.options.icon + }); + return i._origLatLng = t, i._index = e, i.on("dragstart", this._onMarkerDragStart, this).on("drag", this._onMarkerDrag, this).on("dragend", this._fireEdit, this).on("touchmove", this._onTouchMove, this).on("touchend", this._fireEdit, this).on("MSPointerMove", this._onTouchMove, this).on("MSPointerUp", this._fireEdit, this), this._markerGroup.addLayer(i), i + }, + _onMarkerDragStart: function() { + this._poly.fire("editstart") + }, + _spliceLatLngs: function() { + var t = this._defaultShape(), + e = [].splice.apply(t, arguments); + return this._poly._convertLatLngs(t, !0), this._poly.redraw(), e + }, + _removeMarker: function(t) { + var e = t._index; + this._markerGroup.removeLayer(t), this._markers.splice(e, 1), this._spliceLatLngs(e, 1), this._updateIndexes(e, -1), t.off("dragstart", this._onMarkerDragStart, this).off("drag", this._onMarkerDrag, this).off("dragend", this._fireEdit, this).off("touchmove", this._onMarkerDrag, this).off("touchend", this._fireEdit, this).off("click", this._onMarkerClick, this).off("MSPointerMove", this._onTouchMove, this).off("MSPointerUp", this._fireEdit, this) + }, + _fireEdit: function() { + this._poly.edited = !0, this._poly.fire("edit"), this._poly._map.fire(L.Draw.Event.EDITVERTEX, { + layers: this._markerGroup, + poly: this._poly + }) + }, + _onMarkerDrag: function(t) { + var e = t.target, + i = this._poly; + if (L.extend(e._origLatLng, e._latlng), e._middleLeft && e._middleLeft.setLatLng(this._getMiddleLatLng(e._prev, e)), e._middleRight && e._middleRight.setLatLng(this._getMiddleLatLng(e, e._next)), i.options.poly) { + var o = i._map._editTooltip; + if (!i.options.poly.allowIntersection && i.intersects()) { + var n = i.options.color; + i.setStyle({ + color: this.options.drawError.color + }), 0 !== L.version.indexOf("0.7") && e.dragging._draggable._onUp(t), this._onMarkerClick(t), o && o.updateContent({ + text: L.drawLocal.draw.handlers.polyline.error + }), setTimeout(function() { + i.setStyle({ + color: n + }), o && o.updateContent({ + text: L.drawLocal.edit.handlers.edit.tooltip.text, + subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext + }) + }, 1e3) + } + } + this._poly.redraw(), this._poly.fire("editdrag") + }, + _onMarkerClick: function(t) { + var e = L.Polygon && this._poly instanceof L.Polygon ? 4 : 3, + i = t.target; + this._defaultShape().length < e || (this._removeMarker(i), this._updatePrevNext(i._prev, i._next), i._middleLeft && this._markerGroup.removeLayer(i._middleLeft), i._middleRight && this._markerGroup.removeLayer(i._middleRight), i._prev && i._next ? this._createMiddleMarker(i._prev, i._next) : i._prev ? i._next || (i._prev._middleRight = null) : i._next._middleLeft = null, this._fireEdit()) + }, + _onTouchMove: function(t) { + var e = this._map.mouseEventToLayerPoint(t.originalEvent.touches[0]), + i = this._map.layerPointToLatLng(e), + o = t.target; + L.extend(o._origLatLng, i), o._middleLeft && o._middleLeft.setLatLng(this._getMiddleLatLng(o._prev, o)), o._middleRight && o._middleRight.setLatLng(this._getMiddleLatLng(o, o._next)), this._poly.redraw(), this.updateMarkers() + }, + _updateIndexes: function(t, e) { + this._markerGroup.eachLayer(function(i) { + i._index > t && (i._index += e) + }) + }, + _createMiddleMarker: function(t, e) { + var i, o, n, a = this._getMiddleLatLng(t, e), + s = this._createMarker(a); + s.setOpacity(.6), t._middleRight = e._middleLeft = s, o = function() { + s.off("touchmove", o, this); + var n = e._index; + s._index = n, s.off("click", i, this).on("click", this._onMarkerClick, this), a.lat = s.getLatLng().lat, a.lng = s.getLatLng().lng, this._spliceLatLngs(n, 0, a), this._markers.splice(n, 0, s), s.setOpacity(1), this._updateIndexes(n, 1), e._index++, this._updatePrevNext(t, s), this._updatePrevNext(s, e), this._poly.fire("editstart") + }, n = function() { + s.off("dragstart", o, this), s.off("dragend", n, this), s.off("touchmove", o, this), this._createMiddleMarker(t, s), this._createMiddleMarker(s, e) + }, i = function() { + o.call(this), n.call(this), this._fireEdit() + }, s.on("click", i, this).on("dragstart", o, this).on("dragend", n, this).on("touchmove", o, this), this._markerGroup.addLayer(s) + }, + _updatePrevNext: function(t, e) { + t && (t._next = e), e && (e._prev = t) + }, + _getMiddleLatLng: function(t, e) { + var i = this._poly._map, + o = i.project(t.getLatLng()), + n = i.project(e.getLatLng()); + return i.unproject(o._add(n)._divideBy(2)) + } + }), L.Polyline.addInitHook(function() { + this.editing || (L.Edit.Poly && (this.editing = new L.Edit.Poly(this, this.options.poly), this.options.editable && this.editing.enable()), this.on("add", function() { + this.editing && this.editing.enabled() && this.editing.addHooks() + }), this.on("remove", function() { + this.editing && this.editing.enabled() && this.editing.removeHooks() + })) + }), L.Edit = L.Edit || {}, L.Edit.SimpleShape = L.Handler.extend({ + options: { + moveIcon: new L.DivIcon({ + iconSize: new L.Point(8, 8), + className: "leaflet-div-icon leaflet-editing-icon leaflet-edit-move" + }), + resizeIcon: new L.DivIcon({ + iconSize: new L.Point(8, 8), + className: "leaflet-div-icon leaflet-editing-icon leaflet-edit-resize" + }), + touchMoveIcon: new L.DivIcon({ + iconSize: new L.Point(20, 20), + className: "leaflet-div-icon leaflet-editing-icon leaflet-edit-move leaflet-touch-icon" + }), + touchResizeIcon: new L.DivIcon({ + iconSize: new L.Point(20, 20), + className: "leaflet-div-icon leaflet-editing-icon leaflet-edit-resize leaflet-touch-icon" + }) + }, + initialize: function(t, e) { + L.Browser.touch && (this.options.moveIcon = this.options.touchMoveIcon, this.options.resizeIcon = this.options.touchResizeIcon), this._shape = t, L.Util.setOptions(this, e) + }, + addHooks: function() { + var t = this._shape; + this._shape._map && (this._map = this._shape._map, t.setStyle(t.options.editing), t._map && (this._map = t._map, this._markerGroup || this._initMarkers(), this._map.addLayer(this._markerGroup))) + }, + removeHooks: function() { + var t = this._shape; + if (t.setStyle(t.options.original), t._map) { + this._unbindMarker(this._moveMarker); + for (var e = 0, i = this._resizeMarkers.length; e < i; e++) this._unbindMarker(this._resizeMarkers[e]); + this._resizeMarkers = null, this._map.removeLayer(this._markerGroup), delete this._markerGroup + } + this._map = null + }, + updateMarkers: function() { + this._markerGroup.clearLayers(), this._initMarkers() + }, + _initMarkers: function() { + this._markerGroup || (this._markerGroup = new L.LayerGroup), this._createMoveMarker(), this._createResizeMarker() + }, + _createMoveMarker: function() {}, + _createResizeMarker: function() {}, + _createMarker: function(t, e) { + var i = new L.Marker.Touch(t, { + draggable: !0, + icon: e, + zIndexOffset: 10 + }); + return this._bindMarker(i), this._markerGroup.addLayer(i), i + }, + _bindMarker: function(t) { + t.on("dragstart", this._onMarkerDragStart, this).on("drag", this._onMarkerDrag, this).on("dragend", this._onMarkerDragEnd, this).on("touchstart", this._onTouchStart, this).on("touchmove", this._onTouchMove, this).on("MSPointerMove", this._onTouchMove, this).on("touchend", this._onTouchEnd, this).on("MSPointerUp", this._onTouchEnd, this) + }, + _unbindMarker: function(t) { + t.off("dragstart", this._onMarkerDragStart, this).off("drag", this._onMarkerDrag, this).off("dragend", this._onMarkerDragEnd, this).off("touchstart", this._onTouchStart, this).off("touchmove", this._onTouchMove, this).off("MSPointerMove", this._onTouchMove, this).off("touchend", this._onTouchEnd, this).off("MSPointerUp", this._onTouchEnd, this) + }, + _onMarkerDragStart: function(t) { + t.target.setOpacity(0), this._shape.fire("editstart") + }, + _fireEdit: function() { + this._shape.edited = !0, this._shape.fire("edit") + }, + _onMarkerDrag: function(t) { + var e = t.target, + i = e.getLatLng(); + e === this._moveMarker ? this._move(i) : this._resize(i), this._shape.redraw(), this._shape.fire("editdrag") + }, + _onMarkerDragEnd: function(t) { + t.target.setOpacity(1), this._fireEdit() + }, + _onTouchStart: function(t) { + if (L.Edit.SimpleShape.prototype._onMarkerDragStart.call(this, t), "function" == typeof this._getCorners) { + var e = this._getCorners(), + i = t.target, + o = i._cornerIndex; + i.setOpacity(0), this._oppositeCorner = e[(o + 2) % 4], this._toggleCornerMarkers(0, o) + } + this._shape.fire("editstart") + }, + _onTouchMove: function(t) { + var e = this._map.mouseEventToLayerPoint(t.originalEvent.touches[0]), + i = this._map.layerPointToLatLng(e); + return t.target === this._moveMarker ? this._move(i) : this._resize(i), this._shape.redraw(), !1 + }, + _onTouchEnd: function(t) { + t.target.setOpacity(1), this.updateMarkers(), this._fireEdit() + }, + _move: function() {}, + _resize: function() {} + }), L.Edit = L.Edit || {}, L.Edit.Rectangle = L.Edit.SimpleShape.extend({ + _createMoveMarker: function() { + var t = this._shape.getBounds(), + e = t.getCenter(); + this._moveMarker = this._createMarker(e, this.options.moveIcon) + }, + _createResizeMarker: function() { + var t = this._getCorners(); + this._resizeMarkers = []; + for (var e = 0, i = t.length; e < i; e++) this._resizeMarkers.push(this._createMarker(t[e], this.options.resizeIcon)), this._resizeMarkers[e]._cornerIndex = e + }, + _onMarkerDragStart: function(t) { + L.Edit.SimpleShape.prototype._onMarkerDragStart.call(this, t); + var e = this._getCorners(), + i = t.target, + o = i._cornerIndex; + this._oppositeCorner = e[(o + 2) % 4], this._toggleCornerMarkers(0, o) + }, + _onMarkerDragEnd: function(t) { + var e, i, o = t.target; + o === this._moveMarker && (e = this._shape.getBounds(), i = e.getCenter(), o.setLatLng(i)), this._toggleCornerMarkers(1), this._repositionCornerMarkers(), L.Edit.SimpleShape.prototype._onMarkerDragEnd.call(this, t) + }, + _move: function(t) { + for (var e, i = this._shape._defaultShape ? this._shape._defaultShape() : this._shape.getLatLngs(), o = this._shape.getBounds(), n = o.getCenter(), a = [], s = 0, r = i.length; s < r; s++) e = [i[s].lat - n.lat, i[s].lng - n.lng], a.push([t.lat + e[0], t.lng + e[1]]); + this._shape.setLatLngs(a), this._repositionCornerMarkers(), this._map.fire(L.Draw.Event.EDITMOVE, { + layer: this._shape + }) + }, + _resize: function(t) { + var e; + this._shape.setBounds(L.latLngBounds(t, this._oppositeCorner)), e = this._shape.getBounds(), this._moveMarker.setLatLng(e.getCenter()), this._map.fire(L.Draw.Event.EDITRESIZE, { + layer: this._shape + }) + }, + _getCorners: function() { + var t = this._shape.getBounds(); + return [t.getNorthWest(), t.getNorthEast(), t.getSouthEast(), t.getSouthWest()] + }, + _toggleCornerMarkers: function(t) { + for (var e = 0, i = this._resizeMarkers.length; e < i; e++) this._resizeMarkers[e].setOpacity(t) + }, + _repositionCornerMarkers: function() { + for (var t = this._getCorners(), e = 0, i = this._resizeMarkers.length; e < i; e++) this._resizeMarkers[e].setLatLng(t[e]) + } + }), L.Rectangle.addInitHook(function() { + L.Edit.Rectangle && (this.editing = new L.Edit.Rectangle(this), this.options.editable && this.editing.enable()) + }), L.Edit = L.Edit || {}, L.Edit.CircleMarker = L.Edit.SimpleShape.extend({ + _createMoveMarker: function() { + var t = this._shape.getLatLng(); + this._moveMarker = this._createMarker(t, this.options.moveIcon) + }, + _createResizeMarker: function() { + this._resizeMarkers = [] + }, + _move: function(t) { + if (this._resizeMarkers.length) { + var e = this._getResizeMarkerPoint(t); + this._resizeMarkers[0].setLatLng(e) + } + this._shape.setLatLng(t), this._map.fire(L.Draw.Event.EDITMOVE, { + layer: this._shape + }) + } + }), L.CircleMarker.addInitHook(function() { + L.Edit.CircleMarker && (this.editing = new L.Edit.CircleMarker(this), this.options.editable && this.editing.enable()), this.on("add", function() { + this.editing && this.editing.enabled() && this.editing.addHooks() + }), this.on("remove", function() { + this.editing && this.editing.enabled() && this.editing.removeHooks() + }) + }), L.Edit = L.Edit || {}, L.Edit.Circle = L.Edit.CircleMarker.extend({ + _createResizeMarker: function() { + var t = this._shape.getLatLng(), + e = this._getResizeMarkerPoint(t); + this._resizeMarkers = [], this._resizeMarkers.push(this._createMarker(e, this.options.resizeIcon)) + }, + _getResizeMarkerPoint: function(t) { + var e = this._shape._radius * Math.cos(Math.PI / 4), + i = this._map.project(t); + return this._map.unproject([i.x + e, i.y - e]) + }, + _resize: function(t) { + var e = this._moveMarker.getLatLng(), + i = this._map.distance(e, t); + this._shape.setRadius(i), this._map.fire(L.Draw.Event.EDITRESIZE, { + layer: this._shape + }) + } + }), L.Circle.addInitHook(function() { + L.Edit.Circle && (this.editing = new L.Edit.Circle(this), this.options.editable && this.editing.enable()), this.on("add", function() { + this.editing && this.editing.enabled() && this.editing.addHooks() + }), this.on("remove", function() { + this.editing && this.editing.enabled() && this.editing.removeHooks() + }) + }), L.Map.mergeOptions({ + touchExtend: !0 + }), L.Map.TouchExtend = L.Handler.extend({ + initialize: function(t) { + this._map = t, this._container = t._container, this._pane = t._panes.overlayPane + }, + addHooks: function() { + L.DomEvent.on(this._container, "touchstart", this._onTouchStart, this), L.DomEvent.on(this._container, "touchend", this._onTouchEnd, this), L.DomEvent.on(this._container, "touchmove", this._onTouchMove, this), this._detectIE() ? (L.DomEvent.on(this._container, "MSPointerDown", this._onTouchStart, this), L.DomEvent.on(this._container, "MSPointerUp", this._onTouchEnd, this), L.DomEvent.on(this._container, "MSPointerMove", this._onTouchMove, this), L.DomEvent.on(this._container, "MSPointerCancel", this._onTouchCancel, this)) : (L.DomEvent.on(this._container, "touchcancel", this._onTouchCancel, this), L.DomEvent.on(this._container, "touchleave", this._onTouchLeave, this)) + }, + removeHooks: function() { + L.DomEvent.off(this._container, "touchstart", this._onTouchStart), L.DomEvent.off(this._container, "touchend", this._onTouchEnd), L.DomEvent.off(this._container, "touchmove", this._onTouchMove), this._detectIE() ? (L.DomEvent.off(this._container, "MSPointerDowm", this._onTouchStart), L.DomEvent.off(this._container, "MSPointerUp", this._onTouchEnd), L.DomEvent.off(this._container, "MSPointerMove", this._onTouchMove), L.DomEvent.off(this._container, "MSPointerCancel", this._onTouchCancel)) : (L.DomEvent.off(this._container, "touchcancel", this._onTouchCancel), L.DomEvent.off(this._container, "touchleave", this._onTouchLeave)) + }, + _touchEvent: function(t, e) { + var i = {}; + if (void 0 !== t.touches) { + if (!t.touches.length) return; + i = t.touches[0] + } else { + if ("touch" !== t.pointerType) return; + if (i = t, !this._filterClick(t)) return + } + var o = this._map.mouseEventToContainerPoint(i), + n = this._map.mouseEventToLayerPoint(i), + a = this._map.layerPointToLatLng(n); + this._map.fire(e, { + latlng: a, + layerPoint: n, + containerPoint: o, + pageX: i.pageX, + pageY: i.pageY, + originalEvent: t + }) + }, + _filterClick: function(t) { + var e = t.timeStamp || t.originalEvent.timeStamp, + i = L.DomEvent._lastClick && e - L.DomEvent._lastClick; + return i && i > 100 && i < 500 || t.target._simulatedClick && !t._simulated ? (L.DomEvent.stop(t), !1) : (L.DomEvent._lastClick = e, !0) + }, + _onTouchStart: function(t) { + if (this._map._loaded) { + this._touchEvent(t, "touchstart") + } + }, + _onTouchEnd: function(t) { + if (this._map._loaded) { + this._touchEvent(t, "touchend") + } + }, + _onTouchCancel: function(t) { + if (this._map._loaded) { + var e = "touchcancel"; + this._detectIE() && (e = "pointercancel"), this._touchEvent(t, e) + } + }, + _onTouchLeave: function(t) { + if (this._map._loaded) { + this._touchEvent(t, "touchleave") + } + }, + _onTouchMove: function(t) { + if (this._map._loaded) { + this._touchEvent(t, "touchmove") + } + }, + _detectIE: function() { + var e = t.navigator.userAgent, + i = e.indexOf("MSIE "); + if (i > 0) return parseInt(e.substring(i + 5, e.indexOf(".", i)), 10); + if (e.indexOf("Trident/") > 0) { + var o = e.indexOf("rv:"); + return parseInt(e.substring(o + 3, e.indexOf(".", o)), 10) + } + var n = e.indexOf("Edge/"); + return n > 0 && parseInt(e.substring(n + 5, e.indexOf(".", n)), 10) + } + }), L.Map.addInitHook("addHandler", "touchExtend", L.Map.TouchExtend), L.Marker.Touch = L.Marker.extend({ + _initInteraction: function() { + return this.addInteractiveTarget ? L.Marker.prototype._initInteraction.apply(this) : this._initInteractionLegacy() + }, + _initInteractionLegacy: function() { + if (this.options.clickable) { + var t = this._icon, + e = ["dblclick", "mousedown", "mouseover", "mouseout", "contextmenu", "touchstart", "touchend", "touchmove"]; + this._detectIE ? e.concat(["MSPointerDown", "MSPointerUp", "MSPointerMove", "MSPointerCancel"]) : e.concat(["touchcancel"]), L.DomUtil.addClass(t, "leaflet-clickable"), L.DomEvent.on(t, "click", this._onMouseClick, this), L.DomEvent.on(t, "keypress", this._onKeyPress, this); + for (var i = 0; i < e.length; i++) L.DomEvent.on(t, e[i], this._fireMouseEvent, this); + L.Handler.MarkerDrag && (this.dragging = new L.Handler.MarkerDrag(this), this.options.draggable && this.dragging.enable()) + } + }, + _detectIE: function() { + var e = t.navigator.userAgent, + i = e.indexOf("MSIE "); + if (i > 0) return parseInt(e.substring(i + 5, e.indexOf(".", i)), 10); + if (e.indexOf("Trident/") > 0) { + var o = e.indexOf("rv:"); + return parseInt(e.substring(o + 3, e.indexOf(".", o)), 10) + } + var n = e.indexOf("Edge/"); + return n > 0 && parseInt(e.substring(n + 5, e.indexOf(".", n)), 10) + } + }), L.LatLngUtil = { + cloneLatLngs: function(t) { + for (var e = [], i = 0, o = t.length; i < o; i++) Array.isArray(t[i]) ? e.push(L.LatLngUtil.cloneLatLngs(t[i])) : e.push(this.cloneLatLng(t[i])); + return e + }, + cloneLatLng: function(t) { + return L.latLng(t.lat, t.lng) + } + }, + function() { + var t = { + km: 2, + ha: 2, + m: 0, + mi: 2, + ac: 2, + yd: 0, + ft: 0, + nm: 2 + }; + L.GeometryUtil = L.extend(L.GeometryUtil || {}, { + geodesicArea: function(t) { + var e, i, o = t.length, + n = 0, + a = Math.PI / 180; + if (o > 2) { + for (var s = 0; s < o; s++) e = t[s], i = t[(s + 1) % o], n += (i.lng - e.lng) * a * (2 + Math.sin(e.lat * a) + Math.sin(i.lat * a)); + n = 6378137 * n * 6378137 / 2 + } + return Math.abs(n) + }, + formattedNumber: function(t, e) { + var i = parseFloat(t).toFixed(e), + o = L.drawLocal.format && L.drawLocal.format.numeric, + n = o && o.delimiters, + a = n && n.thousands, + s = n && n.decimal; + if (a || s) { + var r = i.split("."); + i = a ? r[0].replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1" + a) : r[0], s = s || ".", r.length > 1 && (i = i + s + r[1]) + } + return i + }, + readableArea: function(e, i, o) { + var n, a, o = L.Util.extend({}, t, o); + return i ? (a = ["ha", "m"], type = typeof i, "string" === type ? a = [i] : "boolean" !== type && (a = i), n = e >= 1e6 && -1 !== a.indexOf("km") ? L.GeometryUtil.formattedNumber(1e-6 * e, o.km) + " km²" : e >= 1e4 && -1 !== a.indexOf("ha") ? L.GeometryUtil.formattedNumber(1e-4 * e, o.ha) + " ha" : L.GeometryUtil.formattedNumber(e, o.m) + " m²") : (e /= .836127, n = e >= 3097600 ? L.GeometryUtil.formattedNumber(e / 3097600, o.mi) + " mi²" : e >= 4840 ? L.GeometryUtil.formattedNumber(e / 4840, o.ac) + " acres" : L.GeometryUtil.formattedNumber(e, o.yd) + " yd²"), n + }, + readableDistance: function(e, i, o, n, a) { + var s, a = L.Util.extend({}, t, a); + switch (i ? "string" == typeof i ? i : "metric" : o ? "feet" : n ? "nauticalMile" : "yards") { + case "metric": + s = e > 1e3 ? L.GeometryUtil.formattedNumber(e / 1e3, a.km) + " km" : L.GeometryUtil.formattedNumber(e, a.m) + " m"; + break; + case "feet": + e *= 3.28083, s = L.GeometryUtil.formattedNumber(e, a.ft) + " ft"; + break; + case "nauticalMile": + e *= .53996, s = L.GeometryUtil.formattedNumber(e / 1e3, a.nm) + " nm"; + break; + case "yards": + default: + e *= 1.09361, s = e > 1760 ? L.GeometryUtil.formattedNumber(e / 1760, a.mi) + " miles" : L.GeometryUtil.formattedNumber(e, a.yd) + " yd" + } + return s + } + }) + }(), L.Util.extend(L.LineUtil, { + segmentsIntersect: function(t, e, i, o) { + return this._checkCounterclockwise(t, i, o) !== this._checkCounterclockwise(e, i, o) && this._checkCounterclockwise(t, e, i) !== this._checkCounterclockwise(t, e, o) + }, + _checkCounterclockwise: function(t, e, i) { + return (i.y - t.y) * (e.x - t.x) > (e.y - t.y) * (i.x - t.x) + } + }), L.Polyline.include({ + intersects: function() { + var t, e, i, o = this._getProjectedPoints(), + n = o ? o.length : 0; + if (this._tooFewPointsForIntersection()) return !1; + for (t = n - 1; t >= 3; t--) + if (e = o[t - 1], i = o[t], this._lineSegmentsIntersectsRange(e, i, t - 2)) return !0; + return !1 + }, + newLatLngIntersects: function(t, e) { + return !!this._map && this.newPointIntersects(this._map.latLngToLayerPoint(t), e) + }, + newPointIntersects: function(t, e) { + var i = this._getProjectedPoints(), + o = i ? i.length : 0, + n = i ? i[o - 1] : null, + a = o - 2; + return !this._tooFewPointsForIntersection(1) && this._lineSegmentsIntersectsRange(n, t, a, e ? 1 : 0) + }, + _tooFewPointsForIntersection: function(t) { + var e = this._getProjectedPoints(), + i = e ? e.length : 0; + return i += t || 0, !e || i <= 3 + }, + _lineSegmentsIntersectsRange: function(t, e, i, o) { + var n, a, s = this._getProjectedPoints(); + o = o || 0; + for (var r = i; r > o; r--) + if (n = s[r - 1], a = s[r], L.LineUtil.segmentsIntersect(t, e, n, a)) return !0; + return !1 + }, + _getProjectedPoints: function() { + if (!this._defaultShape) return this._originalPoints; + for (var t = [], e = this._defaultShape(), i = 0; i < e.length; i++) t.push(this._map.latLngToLayerPoint(e[i])); + return t + } + }), L.Polygon.include({ + intersects: function() { + var t, e, i, o, n = this._getProjectedPoints(); + return !this._tooFewPointsForIntersection() && (!!L.Polyline.prototype.intersects.call(this) || (t = n.length, e = n[0], i = n[t - 1], o = t - 2, this._lineSegmentsIntersectsRange(i, e, o, 1))) + } + }), L.Control.Draw = L.Control.extend({ + options: { + position: "topleft", + draw: {}, + edit: !1 + }, + initialize: function(t) { + if (L.version < "0.7") throw new Error("Leaflet.draw 0.2.3+ requires Leaflet 0.7.0+. Download latest from https://github.com/Leaflet/Leaflet/"); + L.Control.prototype.initialize.call(this, t); + var e; + this._toolbars = {}, L.DrawToolbar && this.options.draw && (e = new L.DrawToolbar(this.options.draw), this._toolbars[L.DrawToolbar.TYPE] = e, this._toolbars[L.DrawToolbar.TYPE].on("enable", this._toolbarEnabled, this)), L.EditToolbar && this.options.edit && (e = new L.EditToolbar(this.options.edit), this._toolbars[L.EditToolbar.TYPE] = e, this._toolbars[L.EditToolbar.TYPE].on("enable", this._toolbarEnabled, this)), L.toolbar = this + }, + onAdd: function(t) { + var e, i = L.DomUtil.create("div", "leaflet-draw"), + o = !1; + for (var n in this._toolbars) this._toolbars.hasOwnProperty(n) && (e = this._toolbars[n].addToolbar(t)) && (o || (L.DomUtil.hasClass(e, "leaflet-draw-toolbar-top") || L.DomUtil.addClass(e.childNodes[0], "leaflet-draw-toolbar-top"), o = !0), i.appendChild(e)); + return i + }, + onRemove: function() { + for (var t in this._toolbars) this._toolbars.hasOwnProperty(t) && this._toolbars[t].removeToolbar() + }, + setDrawingOptions: function(t) { + for (var e in this._toolbars) this._toolbars[e] instanceof L.DrawToolbar && this._toolbars[e].setOptions(t) + }, + _toolbarEnabled: function(t) { + var e = t.target; + for (var i in this._toolbars) this._toolbars[i] !== e && this._toolbars[i].disable() + } + }), L.Map.mergeOptions({ + drawControlTooltips: !0, + drawControl: !1 + }), L.Map.addInitHook(function() { + this.options.drawControl && (this.drawControl = new L.Control.Draw, this.addControl(this.drawControl)) + }), L.Toolbar = L.Class.extend({ + initialize: function(t) { + L.setOptions(this, t); + this._modes = {}; + this._actionButtons = []; + this._activeMode = null; + var version = L.version.split("."); + if (parseInt(version[0],10) === 1 && parseInt(version[1],10) >= 2 ) { + L.Toolbar.include(L.Evented.prototype); + } else { + L.Toolbar.include(L.Mixin.Events); + } + }, + enabled: function() { + return this._activeMode !== null; + }, + disable: function() { + this.enabled() && this._activeMode.handler.disable() + }, + addToolbar: function(t) { + var e, i = L.DomUtil.create("div", "leaflet-draw-section"), + o = 0, + n = this._toolbarClass || "", + a = this.getModeHandlers(t); + for (this._toolbarContainer = L.DomUtil.create("div", "leaflet-draw-toolbar leaflet-bar"), this._map = t, e = 0; e < a.length; e++) a[e].enabled && this._initModeHandler(a[e].handler, this._toolbarContainer, o++, n, a[e].title); + if (o) return this._lastButtonIndex = --o, this._actionsContainer = L.DomUtil.create("ul", "leaflet-draw-actions"), i.appendChild(this._toolbarContainer), i.appendChild(this._actionsContainer), i + }, + removeToolbar: function() { + for (var t in this._modes) this._modes.hasOwnProperty(t) && (this._disposeButton(this._modes[t].button, this._modes[t].handler.enable, this._modes[t].handler), this._modes[t].handler.disable(), this._modes[t].handler.off("enabled", this._handlerActivated, this).off("disabled", this._handlerDeactivated, this)); + this._modes = {}; + for (var e = 0, i = this._actionButtons.length; e < i; e++) this._disposeButton(this._actionButtons[e].button, this._actionButtons[e].callback, this); + this._actionButtons = [], this._actionsContainer = null + }, + _initModeHandler: function(t, e, i, o, n) { + var a = t.type; + this._modes[a] = {}, this._modes[a].handler = t, this._modes[a].button = this._createButton({ + type: a, + title: n, + className: o + "-" + a, + container: e, + callback: this._modes[a].handler.enable, + context: this._modes[a].handler + }), this._modes[a].buttonIndex = i, this._modes[a].handler.on("enabled", this._handlerActivated, this).on("disabled", this._handlerDeactivated, this) + }, + _detectIOS: function() { + return /iPad|iPhone|iPod/.test(navigator.userAgent) && !t.MSStream + }, + _createButton: function(t) { + var e = L.DomUtil.create("a", t.className || "", t.container), + i = L.DomUtil.create("span", "sr-only", t.container); + e.href = "#", e.appendChild(i), t.title && (e.title = t.title, i.innerHTML = t.title), t.text && (e.innerHTML = t.text, i.innerHTML = t.text); + var o = this._detectIOS() ? "touchstart" : "click"; + return L.DomEvent.on(e, "click", L.DomEvent.stopPropagation).on(e, "mousedown", L.DomEvent.stopPropagation).on(e, "dblclick", L.DomEvent.stopPropagation).on(e, "touchstart", L.DomEvent.stopPropagation).on(e, "click", L.DomEvent.preventDefault).on(e, o, t.callback, t.context), e + }, + _disposeButton: function(t, e) { + var i = this._detectIOS() ? "touchstart" : "click"; + L.DomEvent.off(t, "click", L.DomEvent.stopPropagation).off(t, "mousedown", L.DomEvent.stopPropagation).off(t, "dblclick", L.DomEvent.stopPropagation).off(t, "touchstart", L.DomEvent.stopPropagation).off(t, "click", L.DomEvent.preventDefault).off(t, i, e) + }, + _handlerActivated: function(t) { + this.disable(), this._activeMode = this._modes[t.handler], L.DomUtil.addClass(this._activeMode.button, "leaflet-draw-toolbar-button-enabled"), this._showActionsToolbar(), this.fire("enable") + }, + _handlerDeactivated: function() { + this._hideActionsToolbar(), L.DomUtil.removeClass(this._activeMode.button, "leaflet-draw-toolbar-button-enabled"), this._activeMode = null, this.fire("disable") + }, + _createActions: function(t) { + var e, i, o, n, a = this._actionsContainer, + s = this.getActions(t), + r = s.length; + for (i = 0, o = this._actionButtons.length; i < o; i++) this._disposeButton(this._actionButtons[i].button, this._actionButtons[i].callback); + for (this._actionButtons = []; a.firstChild;) a.removeChild(a.firstChild); + for (var l = 0; l < r; l++) "enabled" in s[l] && !s[l].enabled || (e = L.DomUtil.create("li", "", a), n = this._createButton({ + title: s[l].title, + text: s[l].text, + container: e, + callback: s[l].callback, + context: s[l].context + }), this._actionButtons.push({ + button: n, + callback: s[l].callback + })) + }, + _showActionsToolbar: function() { + var t = this._activeMode.buttonIndex, + e = this._lastButtonIndex, + i = this._activeMode.button.offsetTop - 1; + this._createActions(this._activeMode.handler), this._actionsContainer.style.top = i + "px", 0 === t && (L.DomUtil.addClass(this._toolbarContainer, "leaflet-draw-toolbar-notop"), L.DomUtil.addClass(this._actionsContainer, "leaflet-draw-actions-top")), t === e && (L.DomUtil.addClass(this._toolbarContainer, "leaflet-draw-toolbar-nobottom"), L.DomUtil.addClass(this._actionsContainer, "leaflet-draw-actions-bottom")), this._actionsContainer.style.display = "block" + }, + _hideActionsToolbar: function() { + this._actionsContainer.style.display = "none", L.DomUtil.removeClass(this._toolbarContainer, "leaflet-draw-toolbar-notop"), L.DomUtil.removeClass(this._toolbarContainer, "leaflet-draw-toolbar-nobottom"), L.DomUtil.removeClass(this._actionsContainer, "leaflet-draw-actions-top"), L.DomUtil.removeClass(this._actionsContainer, "leaflet-draw-actions-bottom") + } + }), L.Draw = L.Draw || {}, L.Draw.Tooltip = L.Class.extend({ + initialize: function(t) { + this._map = t, this._popupPane = t._panes.popupPane, this._visible = !1, this._container = t.options.drawControlTooltips ? L.DomUtil.create("div", "leaflet-draw-tooltip", this._popupPane) : null, this._singleLineLabel = !1, this._map.on("mouseout", this._onMouseOut, this) + }, + dispose: function() { + this._map.off("mouseout", this._onMouseOut, this), this._container && (this._popupPane.removeChild(this._container), this._container = null) + }, + updateContent: function(t) { + return this._container ? (t.subtext = t.subtext || "", 0 !== t.subtext.length || this._singleLineLabel ? t.subtext.length > 0 && this._singleLineLabel && (L.DomUtil.removeClass(this._container, "leaflet-draw-tooltip-single"), this._singleLineLabel = !1) : (L.DomUtil.addClass(this._container, "leaflet-draw-tooltip-single"), this._singleLineLabel = !0), this._container.innerHTML = (t.subtext.length > 0 ? '' + t.subtext + "
    " : "") + "" + t.text + "", t.text || t.subtext ? (this._visible = !0, this._container.style.visibility = "inherit") : (this._visible = !1, this._container.style.visibility = "hidden"), this) : this + }, + updatePosition: function(t) { + var e = this._map.latLngToLayerPoint(t), + i = this._container; + return this._container && (this._visible && (i.style.visibility = "inherit"), L.DomUtil.setPosition(i, e)), this + }, + showAsError: function() { + return this._container && L.DomUtil.addClass(this._container, "leaflet-error-draw-tooltip"), this + }, + removeError: function() { + return this._container && L.DomUtil.removeClass(this._container, "leaflet-error-draw-tooltip"), this + }, + _onMouseOut: function() { + this._container && (this._container.style.visibility = "hidden") + } + }), L.DrawToolbar = L.Toolbar.extend({ + statics: { + TYPE: "draw" + }, + options: { + polyline: {}, + polygon: {}, + rectangle: {}, + circle: {}, + marker: {}, + circlemarker: {} + }, + initialize: function(t) { + for (var e in this.options) this.options.hasOwnProperty(e) && t[e] && (t[e] = L.extend({}, this.options[e], t[e])); + this._toolbarClass = "leaflet-draw-draw", L.Toolbar.prototype.initialize.call(this, t) + }, + getModeHandlers: function(t) { + return [{ + enabled: this.options.polyline, + handler: new L.Draw.Polyline(t, this.options.polyline), + title: L.drawLocal.draw.toolbar.buttons.polyline + }, { + enabled: this.options.polygon, + handler: new L.Draw.Polygon(t, this.options.polygon), + title: L.drawLocal.draw.toolbar.buttons.polygon + }, { + enabled: this.options.rectangle, + handler: new L.Draw.Rectangle(t, this.options.rectangle), + title: L.drawLocal.draw.toolbar.buttons.rectangle + }, { + enabled: this.options.circle, + handler: new L.Draw.Circle(t, this.options.circle), + title: L.drawLocal.draw.toolbar.buttons.circle + }, { + enabled: this.options.marker, + handler: new L.Draw.Marker(t, this.options.marker), + title: L.drawLocal.draw.toolbar.buttons.marker + }, { + enabled: this.options.circlemarker, + handler: new L.Draw.CircleMarker(t, this.options.circlemarker), + title: L.drawLocal.draw.toolbar.buttons.circlemarker + }] + }, + getActions: function(t) { + return [{ + enabled: t.completeShape, + title: L.drawLocal.draw.toolbar.finish.title, + text: L.drawLocal.draw.toolbar.finish.text, + callback: t.completeShape, + context: t + }, { + enabled: t.deleteLastVertex, + title: L.drawLocal.draw.toolbar.undo.title, + text: L.drawLocal.draw.toolbar.undo.text, + callback: t.deleteLastVertex, + context: t + }, { + title: L.drawLocal.draw.toolbar.actions.title, + text: L.drawLocal.draw.toolbar.actions.text, + callback: this.disable, + context: this + }] + }, + setOptions: function(t) { + L.setOptions(this, t); + for (var e in this._modes) this._modes.hasOwnProperty(e) && t.hasOwnProperty(e) && this._modes[e].handler.setOptions(t[e]) + } + }), L.EditToolbar = L.Toolbar.extend({ + statics: { + TYPE: "edit" + }, + options: { + edit: { + selectedPathOptions: { + dashArray: "10, 10", + fill: !0, + fillColor: "#fe57a1", + fillOpacity: .1, + maintainColor: !1 + } + }, + remove: {}, + poly: null, + featureGroup: null + }, + initialize: function(t) { + t.edit && (void 0 === t.edit.selectedPathOptions && (t.edit.selectedPathOptions = this.options.edit.selectedPathOptions), t.edit.selectedPathOptions = L.extend({}, this.options.edit.selectedPathOptions, t.edit.selectedPathOptions)), t.remove && (t.remove = L.extend({}, this.options.remove, t.remove)), t.poly && (t.poly = L.extend({}, this.options.poly, t.poly)), this._toolbarClass = "leaflet-draw-edit", L.Toolbar.prototype.initialize.call(this, t), this._selectedFeatureCount = 0 + }, + getModeHandlers: function(t) { + var e = this.options.featureGroup; + return [{ + enabled: this.options.edit, + handler: new L.EditToolbar.Edit(t, { + featureGroup: e, + selectedPathOptions: this.options.edit.selectedPathOptions, + poly: this.options.poly + }), + title: L.drawLocal.edit.toolbar.buttons.edit + }, { + enabled: this.options.remove, + handler: new L.EditToolbar.Delete(t, { + featureGroup: e + }), + title: L.drawLocal.edit.toolbar.buttons.remove + }] + }, + getActions: function(t) { + var e = [{ + title: L.drawLocal.edit.toolbar.actions.save.title, + text: L.drawLocal.edit.toolbar.actions.save.text, + callback: this._save, + context: this + }, { + title: L.drawLocal.edit.toolbar.actions.cancel.title, + text: L.drawLocal.edit.toolbar.actions.cancel.text, + callback: this.disable, + context: this + }]; + return t.removeAllLayers && e.push({ + title: L.drawLocal.edit.toolbar.actions.clearAll.title, + text: L.drawLocal.edit.toolbar.actions.clearAll.text, + callback: this._clearAllLayers, + context: this + }), e + }, + addToolbar: function(t) { + var e = L.Toolbar.prototype.addToolbar.call(this, t); + return this._checkDisabled(), this.options.featureGroup.on("layeradd layerremove", this._checkDisabled, this), e + }, + removeToolbar: function() { + this.options.featureGroup.off("layeradd layerremove", this._checkDisabled, this), L.Toolbar.prototype.removeToolbar.call(this) + }, + disable: function() { + this.enabled() && (this._activeMode.handler.revertLayers(), L.Toolbar.prototype.disable.call(this)) + }, + _save: function() { + this._activeMode.handler.save(), this._activeMode && this._activeMode.handler.disable() + }, + _clearAllLayers: function() { + this._activeMode.handler.removeAllLayers(), this._activeMode && this._activeMode.handler.disable() + }, + _checkDisabled: function() { + var t, e = this.options.featureGroup, + i = 0 !== e.getLayers().length; + this.options.edit && (t = this._modes[L.EditToolbar.Edit.TYPE].button, i ? L.DomUtil.removeClass(t, "leaflet-disabled") : L.DomUtil.addClass(t, "leaflet-disabled"), t.setAttribute("title", i ? L.drawLocal.edit.toolbar.buttons.edit : L.drawLocal.edit.toolbar.buttons.editDisabled)), this.options.remove && (t = this._modes[L.EditToolbar.Delete.TYPE].button, i ? L.DomUtil.removeClass(t, "leaflet-disabled") : L.DomUtil.addClass(t, "leaflet-disabled"), t.setAttribute("title", i ? L.drawLocal.edit.toolbar.buttons.remove : L.drawLocal.edit.toolbar.buttons.removeDisabled)) + } + }), L.EditToolbar.Edit = L.Handler.extend({ + statics: { + TYPE: "edit" + }, + initialize: function(t, e) { + if (L.Handler.prototype.initialize.call(this, t), L.setOptions(this, e), this._featureGroup = e.featureGroup, !(this._featureGroup instanceof L.FeatureGroup)) throw new Error("options.featureGroup must be a L.FeatureGroup"); + this._uneditedLayerProps = {}, this.type = L.EditToolbar.Edit.TYPE; + var version = L.version.split("."); + if(parseInt(version[0],10) === 1 && parseInt(version[1],10) >= 2 ) { + L.EditToolbar.Edit.include(L.Evented.prototype); + } else { + L.EditToolbar.Edit.include(L.Mixin.Events); + } + }, + enable: function() { + !this._enabled && this._hasAvailableLayers() && (this.fire("enabled", { + handler: this.type + }), this._map.fire(L.Draw.Event.EDITSTART, { + handler: this.type + }), L.Handler.prototype.enable.call(this), this._featureGroup.on("layeradd", this._enableLayerEdit, this).on("layerremove", this._disableLayerEdit, this)) + }, + disable: function() { + this._enabled && (this._featureGroup.off("layeradd", this._enableLayerEdit, this).off("layerremove", this._disableLayerEdit, this), L.Handler.prototype.disable.call(this), this._map.fire(L.Draw.Event.EDITSTOP, { + handler: this.type + }), this.fire("disabled", { + handler: this.type + })) + }, + addHooks: function() { + var t = this._map; + t && (t.getContainer().focus(), this._featureGroup.eachLayer(this._enableLayerEdit, this), this._tooltip = new L.Draw.Tooltip(this._map), this._tooltip.updateContent({ + text: L.drawLocal.edit.handlers.edit.tooltip.text, + subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext + }), t._editTooltip = this._tooltip, this._updateTooltip(), this._map.on("mousemove", this._onMouseMove, this).on("touchmove", this._onMouseMove, this).on("MSPointerMove", this._onMouseMove, this).on(L.Draw.Event.EDITVERTEX, this._updateTooltip, this)) + }, + removeHooks: function() { + this._map && (this._featureGroup.eachLayer(this._disableLayerEdit, this), this._uneditedLayerProps = {}, this._tooltip.dispose(), this._tooltip = null, this._map.off("mousemove", this._onMouseMove, this).off("touchmove", this._onMouseMove, this).off("MSPointerMove", this._onMouseMove, this).off(L.Draw.Event.EDITVERTEX, this._updateTooltip, this)) + }, + revertLayers: function() { + this._featureGroup.eachLayer(function(t) { + this._revertLayer(t) + }, this) + }, + save: function() { + var t = new L.LayerGroup; + this._featureGroup.eachLayer(function(e) { + e.edited && (t.addLayer(e), e.edited = !1) + }), this._map.fire(L.Draw.Event.EDITED, { + layers: t + }) + }, + _backupLayer: function(t) { + var e = L.Util.stamp(t); + this._uneditedLayerProps[e] || (t instanceof L.Polyline || t instanceof L.Polygon || t instanceof L.Rectangle ? this._uneditedLayerProps[e] = { + latlngs: L.LatLngUtil.cloneLatLngs(t.getLatLngs()) + } : t instanceof L.Circle ? this._uneditedLayerProps[e] = { + latlng: L.LatLngUtil.cloneLatLng(t.getLatLng()), + radius: t.getRadius() + } : (t instanceof L.Marker || t instanceof L.CircleMarker) && (this._uneditedLayerProps[e] = { + latlng: L.LatLngUtil.cloneLatLng(t.getLatLng()) + })) + }, + _getTooltipText: function() { + return { + text: L.drawLocal.edit.handlers.edit.tooltip.text, + subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext + } + }, + _updateTooltip: function() { + this._tooltip.updateContent(this._getTooltipText()) + }, + _revertLayer: function(t) { + var e = L.Util.stamp(t); + t.edited = !1, this._uneditedLayerProps.hasOwnProperty(e) && (t instanceof L.Polyline || t instanceof L.Polygon || t instanceof L.Rectangle ? t.setLatLngs(this._uneditedLayerProps[e].latlngs) : t instanceof L.Circle ? (t.setLatLng(this._uneditedLayerProps[e].latlng), t.setRadius(this._uneditedLayerProps[e].radius)) : (t instanceof L.Marker || t instanceof L.CircleMarker) && t.setLatLng(this._uneditedLayerProps[e].latlng), t.fire("revert-edited", { + layer: t + })) + }, + _enableLayerEdit: function(t) { + var e, i, o = t.layer || t.target || t; + this._backupLayer(o), this.options.poly && (i = L.Util.extend({}, this.options.poly), o.options.poly = i), this.options.selectedPathOptions && (e = L.Util.extend({}, this.options.selectedPathOptions), e.maintainColor && (e.color = o.options.color, e.fillColor = o.options.fillColor), o.options.original = L.extend({}, o.options), o.options.editing = e), o instanceof L.Marker ? (o.editing && o.editing.enable(), o.dragging.enable(), o.on("dragend", this._onMarkerDragEnd).on("touchmove", this._onTouchMove, this).on("MSPointerMove", this._onTouchMove, this).on("touchend", this._onMarkerDragEnd, this).on("MSPointerUp", this._onMarkerDragEnd, this)) : o.editing.enable() + }, + _disableLayerEdit: function(t) { + var e = t.layer || t.target || t; + e.edited = !1, e.editing && e.editing.disable(), delete e.options.editing, delete e.options.original, this._selectedPathOptions && (e instanceof L.Marker ? this._toggleMarkerHighlight(e) : (e.setStyle(e.options.previousOptions), delete e.options.previousOptions)), e instanceof L.Marker ? (e.dragging.disable(), e.off("dragend", this._onMarkerDragEnd, this).off("touchmove", this._onTouchMove, this).off("MSPointerMove", this._onTouchMove, this).off("touchend", this._onMarkerDragEnd, this).off("MSPointerUp", this._onMarkerDragEnd, this)) : e.editing.disable() + }, + _onMouseMove: function(t) { + this._tooltip.updatePosition(t.latlng) + }, + _onMarkerDragEnd: function(t) { + var e = t.target; + e.edited = !0, this._map.fire(L.Draw.Event.EDITMOVE, { + layer: e + }) + }, + _onTouchMove: function(t) { + var e = t.originalEvent.changedTouches[0], + i = this._map.mouseEventToLayerPoint(e), + o = this._map.layerPointToLatLng(i); + t.target.setLatLng(o) + }, + _hasAvailableLayers: function() { + return 0 !== this._featureGroup.getLayers().length + } + }), L.EditToolbar.Delete = L.Handler.extend({ + statics: { + TYPE: "remove" + }, + initialize: function(t, e) { + if (L.Handler.prototype.initialize.call(this, t), L.Util.setOptions(this, e), this._deletableLayers = this.options.featureGroup, !(this._deletableLayers instanceof L.FeatureGroup)) throw new Error("options.featureGroup must be a L.FeatureGroup"); + this.type = L.EditToolbar.Delete.TYPE; + var version = L.version.split("."); + if(parseInt(version[0],10) === 1 && parseInt(version[1],10) >= 2 ) { + L.EditToolbar.Delete.include(L.Evented.prototype); + } else { + L.EditToolbar.Delete.include(L.Mixin.Events); + } + }, + enable: function() { + !this._enabled && this._hasAvailableLayers() && (this.fire("enabled", { + handler: this.type + }), this._map.fire(L.Draw.Event.DELETESTART, { + handler: this.type + }), L.Handler.prototype.enable.call(this), this._deletableLayers.on("layeradd", this._enableLayerDelete, this).on("layerremove", this._disableLayerDelete, this)) + }, + disable: function() { + this._enabled && (this._deletableLayers.off("layeradd", this._enableLayerDelete, this).off("layerremove", this._disableLayerDelete, this), L.Handler.prototype.disable.call(this), this._map.fire(L.Draw.Event.DELETESTOP, { + handler: this.type + }), this.fire("disabled", { + handler: this.type + })) + }, + addHooks: function() { + var t = this._map; + t && (t.getContainer().focus(), this._deletableLayers.eachLayer(this._enableLayerDelete, this), this._deletedLayers = new L.LayerGroup, this._tooltip = new L.Draw.Tooltip(this._map), this._tooltip.updateContent({ + text: L.drawLocal.edit.handlers.remove.tooltip.text + }), this._map.on("mousemove", this._onMouseMove, this)) + }, + removeHooks: function() { + this._map && (this._deletableLayers.eachLayer(this._disableLayerDelete, this), this._deletedLayers = null, this._tooltip.dispose(), this._tooltip = null, + this._map.off("mousemove", this._onMouseMove, this)) + }, + revertLayers: function() { + this._deletedLayers.eachLayer(function(t) { + this._deletableLayers.addLayer(t), t.fire("revert-deleted", { + layer: t + }) + }, this) + }, + save: function() { + this._map.fire(L.Draw.Event.DELETED, { + layers: this._deletedLayers + }) + }, + removeAllLayers: function() { + this._deletableLayers.eachLayer(function(t) { + this._removeLayer({ + layer: t + }) + }, this), this.save() + }, + _enableLayerDelete: function(t) { + (t.layer || t.target || t).on("click", this._removeLayer, this) + }, + _disableLayerDelete: function(t) { + var e = t.layer || t.target || t; + e.off("click", this._removeLayer, this), this._deletedLayers.removeLayer(e) + }, + _removeLayer: function(t) { + var e = t.layer || t.target || t; + this._deletableLayers.removeLayer(e), this._deletedLayers.addLayer(e), e.fire("deleted") + }, + _onMouseMove: function(t) { + this._tooltip.updatePosition(t.latlng) + }, + _hasAvailableLayers: function() { + return 0 !== this._deletableLayers.getLayers().length + } + }) +}(window, document); diff --git a/frappe/public/js/lib/leaflet/leaflet.js b/frappe/public/js/lib/leaflet/leaflet.js new file mode 100644 index 0000000000..b9fc9bcf58 --- /dev/null +++ b/frappe/public/js/lib/leaflet/leaflet.js @@ -0,0 +1,5 @@ +/* @preserve + * Leaflet 1.2.0, a JS library for interactive maps. http://leafletjs.com + * (c) 2010-2017 Vladimir Agafonkin, (c) 2010-2011 CloudMade + */ +!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?i(exports):"function"==typeof define&&define.amd?define(["exports"],i):i(t.L={})}(this,function(t){"use strict";function i(t){var i,e,n,o;for(e=1,n=arguments.length;e=0}function I(t,i,e,n){return"touchstart"===i?O(t,e,n):"touchmove"===i?W(t,e,n):"touchend"===i&&H(t,e,n),this}function A(t,i,e){var n=t["_leaflet_"+i+e];return"touchstart"===i?t.removeEventListener(Xi,n,!1):"touchmove"===i?t.removeEventListener(Ji,n,!1):"touchend"===i&&(t.removeEventListener($i,n,!1),t.removeEventListener(Qi,n,!1)),this}function O(t,i,n){var o=e(function(t){if("mouse"!==t.pointerType&&t.pointerType!==t.MSPOINTER_TYPE_MOUSE&&t.pointerType!==t.MSPOINTER_TYPE_MOUSE){if(!(te.indexOf(t.target.tagName)<0))return;$(t)}j(t,i)});t["_leaflet_touchstart"+n]=o,t.addEventListener(Xi,o,!1),ee||(document.documentElement.addEventListener(Xi,R,!0),document.documentElement.addEventListener(Ji,D,!0),document.documentElement.addEventListener($i,N,!0),document.documentElement.addEventListener(Qi,N,!0),ee=!0)}function R(t){ie[t.pointerId]=t,ne++}function D(t){ie[t.pointerId]&&(ie[t.pointerId]=t)}function N(t){delete ie[t.pointerId],ne--}function j(t,i){t.touches=[];for(var e in ie)t.touches.push(ie[e]);t.changedTouches=[t],i(t)}function W(t,i,e){var n=function(t){(t.pointerType!==t.MSPOINTER_TYPE_MOUSE&&"mouse"!==t.pointerType||0!==t.buttons)&&j(t,i)};t["_leaflet_touchmove"+e]=n,t.addEventListener(Ji,n,!1)}function H(t,i,e){var n=function(t){j(t,i)};t["_leaflet_touchend"+e]=n,t.addEventListener($i,n,!1),t.addEventListener(Qi,n,!1)}function F(t,i,e){function n(t){var i;if(Wi){if(!Li||"mouse"===t.pointerType)return;i=ne}else i=t.touches.length;if(!(i>1)){var e=Date.now(),n=e-(s||e);r=t.touches?t.touches[0]:t,a=n>0&&n<=h,s=e}}function o(t){if(a&&!r.cancelBubble){if(Wi){if(!Li||"mouse"===t.pointerType)return;var e,n,o={};for(n in r)e=r[n],o[n]=e&&e.bind?e.bind(r):e;r=o}r.type="dblclick",i(r),s=null}}var s,r,a=!1,h=250;return t[re+oe+e]=n,t[re+se+e]=o,t[re+"dblclick"+e]=i,t.addEventListener(oe,n,!1),t.addEventListener(se,o,!1),t.addEventListener("dblclick",i,!1),this}function U(t,i){var e=t[re+oe+i],n=t[re+se+i],o=t[re+"dblclick"+i];return t.removeEventListener(oe,e,!1),t.removeEventListener(se,n,!1),Li||t.removeEventListener("dblclick",o,!1),this}function V(t,i,e,n){if("object"==typeof i)for(var o in i)q(t,o,i[o],e);else for(var s=0,r=(i=u(i)).length;s100&&n<500||t.target._simulatedClick&&!t._simulated?Q(t):(di=e,i(t))}function rt(t){return"string"==typeof t?document.getElementById(t):t}function at(t,i){var e=t.style[i]||t.currentStyle&&t.currentStyle[i];if((!e||"auto"===e)&&document.defaultView){var n=document.defaultView.getComputedStyle(t,null);e=n?n[i]:null}return"auto"===e?null:e}function ht(t,i,e){var n=document.createElement(t);return n.className=i||"",e&&e.appendChild(n),n}function ut(t){var i=t.parentNode;i&&i.removeChild(t)}function lt(t){for(;t.firstChild;)t.removeChild(t.firstChild)}function ct(t){var i=t.parentNode;i.lastChild!==t&&i.appendChild(t)}function _t(t){var i=t.parentNode;i.firstChild!==t&&i.insertBefore(t,i.firstChild)}function dt(t,i){if(void 0!==t.classList)return t.classList.contains(i);var e=gt(t);return e.length>0&&new RegExp("(^|\\s)"+i+"(\\s|$)").test(e)}function pt(t,i){if(void 0!==t.classList)for(var e=u(i),n=0,o=e.length;nh&&(s=r,h=a);h>e&&(i[s]=1,St(t,i,e,n,s),St(t,i,e,s,o))}function kt(t,i){for(var e=[t[0]],n=1,o=0,s=t.length;ni&&(e.push(t[n]),o=n);return oi.max.x&&(e|=2),t.yi.max.y&&(e|=8),e}function Ot(t,i){var e=i.x-t.x,n=i.y-t.y;return e*e+n*n}function Rt(t,i,e,n){var o,s=i.x,r=i.y,a=e.x-s,h=e.y-r,u=a*a+h*h;return u>0&&((o=((t.x-s)*a+(t.y-r)*h)/u)>1?(s=e.x,r=e.y):o>0&&(s+=a*o,r+=h*o)),a=t.x-s,h=t.y-r,n?a*a+h*h:new x(s,r)}function Dt(t){return!ei(t[0])||"object"!=typeof t[0][0]&&void 0!==t[0][0]}function Nt(t){return console.warn("Deprecated use of _flat, please use L.LineUtil.isFlat instead."),Dt(t)}function jt(t,i,e){var n,o,s,r,a,h,u,l,c,_=[1,4,2,8];for(o=0,u=t.length;o=this.min.x&&e.x<=this.max.x&&i.y>=this.min.y&&e.y<=this.max.y},intersects:function(t){t=b(t);var i=this.min,e=this.max,n=t.min,o=t.max,s=o.x>=i.x&&n.x<=e.x,r=o.y>=i.y&&n.y<=e.y;return s&&r},overlaps:function(t){t=b(t);var i=this.min,e=this.max,n=t.min,o=t.max,s=o.x>i.x&&n.xi.y&&n.y=n.lat&&e.lat<=o.lat&&i.lng>=n.lng&&e.lng<=o.lng},intersects:function(t){t=z(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),o=t.getNorthEast(),s=o.lat>=i.lat&&n.lat<=e.lat,r=o.lng>=i.lng&&n.lng<=e.lng;return s&&r},overlaps:function(t){t=z(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),o=t.getNorthEast(),s=o.lat>i.lat&&n.lati.lng&&n.lng1,Gi=!!document.createElement("canvas").getContext,qi=!(!document.createElementNS||!S("svg").createSVGRect),Ki=!qi&&function(){try{var t=document.createElement("div");t.innerHTML='';var i=t.firstChild;return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(t){return!1}}(),Yi=(Object.freeze||Object)({ie:xi,ielt9:wi,edge:Li,webkit:Pi,android:bi,android23:Ti,opera:zi,chrome:Mi,gecko:Ci,safari:Zi,phantom:Ei,opera12:Si,win:ki,ie3d:Bi,webkit3d:Ii,gecko3d:Ai,any3d:Oi,mobile:Ri,mobileWebkit:Di,mobileWebkit3d:Ni,msPointer:ji,pointer:Wi,touch:Hi,mobileOpera:Fi,mobileGecko:Ui,retina:Vi,canvas:Gi,svg:qi,vml:Ki}),Xi=ji?"MSPointerDown":"pointerdown",Ji=ji?"MSPointerMove":"pointermove",$i=ji?"MSPointerUp":"pointerup",Qi=ji?"MSPointerCancel":"pointercancel",te=["INPUT","SELECT","OPTION"],ie={},ee=!1,ne=0,oe=ji?"MSPointerDown":Wi?"pointerdown":"touchstart",se=ji?"MSPointerUp":Wi?"pointerup":"touchend",re="_leaflet_",ae="_leaflet_events",he=ki&&Mi?2*window.devicePixelRatio:Ci?window.devicePixelRatio:1,ue={},le=(Object.freeze||Object)({on:V,off:G,stopPropagation:Y,disableScrollPropagation:X,disableClickPropagation:J,preventDefault:$,stop:Q,getMousePosition:tt,getWheelDelta:it,fakeStop:et,skipped:nt,isExternalTarget:ot,addListener:V,removeListener:G}),ce=xt(["transform","WebkitTransform","OTransform","MozTransform","msTransform"]),_e=xt(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),de="webkitTransition"===_e||"OTransition"===_e?_e+"End":"transitionend";if("onselectstart"in document)pi=function(){V(window,"selectstart",$)},mi=function(){G(window,"selectstart",$)};else{var pe=xt(["userSelect","WebkitUserSelect","OUserSelect","MozUserSelect","msUserSelect"]);pi=function(){if(pe){var t=document.documentElement.style;fi=t[pe],t[pe]="none"}},mi=function(){pe&&(document.documentElement.style[pe]=fi,fi=void 0)}}var me,fe,ge=(Object.freeze||Object)({TRANSFORM:ce,TRANSITION:_e,TRANSITION_END:de,get:rt,getStyle:at,create:ht,remove:ut,empty:lt,toFront:ct,toBack:_t,hasClass:dt,addClass:pt,removeClass:mt,setClass:ft,getClass:gt,setOpacity:vt,testProp:xt,setTransform:wt,setPosition:Lt,getPosition:Pt,disableTextSelection:pi,enableTextSelection:mi,disableImageDrag:bt,enableImageDrag:Tt,preventOutline:zt,restoreOutline:Mt}),ve=ui.extend({run:function(t,i,e,n){this.stop(),this._el=t,this._inProgress=!0,this._duration=e||.25,this._easeOutPower=1/Math.max(n||.5,.2),this._startPos=Pt(t),this._offset=i.subtract(this._startPos),this._startTime=+new Date,this.fire("start"),this._animate()},stop:function(){this._inProgress&&(this._step(!0),this._complete())},_animate:function(){this._animId=f(this._animate,this),this._step()},_step:function(t){var i=+new Date-this._startTime,e=1e3*this._duration;ithis.options.maxZoom?this.setZoom(t):this},panInsideBounds:function(t,i){this._enforcingBounds=!0;var e=this.getCenter(),n=this._limitCenter(e,this._zoom,z(t));return e.equals(n)||this.panTo(n,i),this._enforcingBounds=!1,this},invalidateSize:function(t){if(!this._loaded)return this;t=i({animate:!1,pan:!0},!0===t?{animate:!0}:t);var n=this.getSize();this._sizeChanged=!0,this._lastCenter=null;var o=this.getSize(),s=n.divideBy(2).round(),r=o.divideBy(2).round(),a=s.subtract(r);return a.x||a.y?(t.animate&&t.pan?this.panBy(a):(t.pan&&this._rawPanBy(a),this.fire("move"),t.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(e(this.fire,this,"moveend"),200)):this.fire("moveend")),this.fire("resize",{oldSize:n,newSize:o})):this},stop:function(){return this.setZoom(this._limitZoom(this._zoom)),this.options.zoomSnap||this.fire("viewreset"),this._stop()},locate:function(t){if(t=this._locateOptions=i({timeout:1e4,watch:!1},t),!("geolocation"in navigator))return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var n=e(this._handleGeolocationResponse,this),o=e(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(n,o,t):navigator.geolocation.getCurrentPosition(n,o,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var i=t.code,e=t.message||(1===i?"permission denied":2===i?"position unavailable":"timeout");this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:i,message:"Geolocation error: "+e+"."})},_handleGeolocationResponse:function(t){var i=new M(t.coords.latitude,t.coords.longitude),e=i.toBounds(t.coords.accuracy),n=this._locateOptions;if(n.setView){var o=this.getBoundsZoom(e);this.setView(i,n.maxZoom?Math.min(o,n.maxZoom):o)}var s={latlng:i,bounds:e,timestamp:t.timestamp};for(var r in t.coords)"number"==typeof t.coords[r]&&(s[r]=t.coords[r]);this.fire("locationfound",s)},addHandler:function(t,i){if(!i)return this;var e=this[t]=new i(this);return this._handlers.push(e),this.options[t]&&e.enable(),this},remove:function(){if(this._initEvents(!0),this._containerId!==this._container._leaflet_id)throw new Error("Map container is being reused by another instance");try{delete this._container._leaflet_id,delete this._containerId}catch(t){this._container._leaflet_id=void 0,this._containerId=void 0}ut(this._mapPane),this._clearControlPos&&this._clearControlPos(),this._clearHandlers(),this._loaded&&this.fire("unload");var t;for(t in this._layers)this._layers[t].remove();for(t in this._panes)ut(this._panes[t]);return this._layers=[],this._panes=[],delete this._mapPane,delete this._renderer,this},createPane:function(t,i){var e=ht("div","leaflet-pane"+(t?" leaflet-"+t.replace("Pane","")+"-pane":""),i||this._mapPane);return t&&(this._panes[t]=e),e},getCenter:function(){return this._checkIfLoaded(),this._lastCenter&&!this._moved()?this._lastCenter:this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds();return new T(this.unproject(t.getBottomLeft()),this.unproject(t.getTopRight()))},getMinZoom:function(){return void 0===this.options.minZoom?this._layersMinZoom||0:this.options.minZoom},getMaxZoom:function(){return void 0===this.options.maxZoom?void 0===this._layersMaxZoom?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(t,i,e){t=z(t),e=w(e||[0,0]);var n=this.getZoom()||0,o=this.getMinZoom(),s=this.getMaxZoom(),r=t.getNorthWest(),a=t.getSouthEast(),h=this.getSize().subtract(e),u=b(this.project(a,n),this.project(r,n)).getSize(),l=Oi?this.options.zoomSnap:1,c=h.x/u.x,_=h.y/u.y,d=i?Math.max(c,_):Math.min(c,_);return n=this.getScaleZoom(d,n),l&&(n=Math.round(n/(l/100))*(l/100),n=i?Math.ceil(n/l)*l:Math.floor(n/l)*l),Math.max(o,Math.min(s,n))},getSize:function(){return this._size&&!this._sizeChanged||(this._size=new x(this._container.clientWidth||0,this._container.clientHeight||0),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(t,i){var e=this._getTopLeftPoint(t,i);return new P(e,e.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._pixelOrigin},getPixelWorldBounds:function(t){return this.options.crs.getProjectedBounds(void 0===t?this.getZoom():t)},getPane:function(t){return"string"==typeof t?this._panes[t]:t},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t,i){var e=this.options.crs;return i=void 0===i?this._zoom:i,e.scale(t)/e.scale(i)},getScaleZoom:function(t,i){var e=this.options.crs;i=void 0===i?this._zoom:i;var n=e.zoom(t*e.scale(i));return isNaN(n)?1/0:n},project:function(t,i){return i=void 0===i?this._zoom:i,this.options.crs.latLngToPoint(C(t),i)},unproject:function(t,i){return i=void 0===i?this._zoom:i,this.options.crs.pointToLatLng(w(t),i)},layerPointToLatLng:function(t){var i=w(t).add(this.getPixelOrigin());return this.unproject(i)},latLngToLayerPoint:function(t){return this.project(C(t))._round()._subtract(this.getPixelOrigin())},wrapLatLng:function(t){return this.options.crs.wrapLatLng(C(t))},wrapLatLngBounds:function(t){return this.options.crs.wrapLatLngBounds(z(t))},distance:function(t,i){return this.options.crs.distance(C(t),C(i))},containerPointToLayerPoint:function(t){return w(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return w(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){var i=this.containerPointToLayerPoint(w(t));return this.layerPointToLatLng(i)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(C(t)))},mouseEventToContainerPoint:function(t){return tt(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){var i=this._container=rt(t);if(!i)throw new Error("Map container not found.");if(i._leaflet_id)throw new Error("Map container is already initialized.");V(i,"scroll",this._onScroll,this),this._containerId=n(i)},_initLayout:function(){var t=this._container;this._fadeAnimated=this.options.fadeAnimation&&Oi,pt(t,"leaflet-container"+(Hi?" leaflet-touch":"")+(Vi?" leaflet-retina":"")+(wi?" leaflet-oldie":"")+(Zi?" leaflet-safari":"")+(this._fadeAnimated?" leaflet-fade-anim":""));var i=at(t,"position");"absolute"!==i&&"relative"!==i&&"fixed"!==i&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._paneRenderers={},this._mapPane=this.createPane("mapPane",this._container),Lt(this._mapPane,new x(0,0)),this.createPane("tilePane"),this.createPane("shadowPane"),this.createPane("overlayPane"),this.createPane("markerPane"),this.createPane("tooltipPane"),this.createPane("popupPane"),this.options.markerZoomAnimation||(pt(t.markerPane,"leaflet-zoom-hide"),pt(t.shadowPane,"leaflet-zoom-hide"))},_resetView:function(t,i){Lt(this._mapPane,new x(0,0));var e=!this._loaded;this._loaded=!0,i=this._limitZoom(i),this.fire("viewprereset");var n=this._zoom!==i;this._moveStart(n)._move(t,i)._moveEnd(n),this.fire("viewreset"),e&&this.fire("load")},_moveStart:function(t){return t&&this.fire("zoomstart"),this.fire("movestart")},_move:function(t,i,e){void 0===i&&(i=this._zoom);var n=this._zoom!==i;return this._zoom=i,this._lastCenter=t,this._pixelOrigin=this._getNewPixelOrigin(t),(n||e&&e.pinch)&&this.fire("zoom",e),this.fire("move",e)},_moveEnd:function(t){return t&&this.fire("zoomend"),this.fire("moveend")},_stop:function(){return g(this._flyToFrame),this._panAnim&&this._panAnim.stop(),this},_rawPanBy:function(t){Lt(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_panInsideMaxBounds:function(){this._enforcingBounds||this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(t){this._targets={},this._targets[n(this._container)]=this;var i=t?G:V;i(this._container,"click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress",this._handleDOMEvent,this),this.options.trackResize&&i(window,"resize",this._onResize,this),Oi&&this.options.transform3DLimit&&(t?this.off:this.on).call(this,"moveend",this._onMoveEnd)},_onResize:function(){g(this._resizeRequest),this._resizeRequest=f(function(){this.invalidateSize({debounceMoveend:!0})},this)},_onScroll:function(){this._container.scrollTop=0,this._container.scrollLeft=0},_onMoveEnd:function(){var t=this._getMapPanePos();Math.max(Math.abs(t.x),Math.abs(t.y))>=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,i){for(var e,o=[],s="mouseout"===i||"mouseover"===i,r=t.target||t.srcElement,a=!1;r;){if((e=this._targets[n(r)])&&("click"===i||"preclick"===i)&&!t._simulated&&this._draggableMoved(e)){a=!0;break}if(e&&e.listens(i,!0)){if(s&&!ot(r,t))break;if(o.push(e),s)break}if(r===this._container)break;r=r.parentNode}return o.length||a||s||!ot(r,t)||(o=[this]),o},_handleDOMEvent:function(t){if(this._loaded&&!nt(t)){var i=t.type;"mousedown"!==i&&"keypress"!==i||zt(t.target||t.srcElement),this._fireDOMEvent(t,i)}},_mouseEvents:["click","dblclick","mouseover","mouseout","contextmenu"],_fireDOMEvent:function(t,e,n){if("click"===t.type){var o=i({},t);o.type="preclick",this._fireDOMEvent(o,o.type,n)}if(!t._stopped&&(n=(n||[]).concat(this._findEventTargets(t,e))).length){var s=n[0];"contextmenu"===e&&s.listens(e,!0)&&$(t);var r={originalEvent:t};if("keypress"!==t.type){var a=s.options&&"icon"in s.options;r.containerPoint=a?this.latLngToContainerPoint(s.getLatLng()):this.mouseEventToContainerPoint(t),r.layerPoint=this.containerPointToLayerPoint(r.containerPoint),r.latlng=a?s.getLatLng():this.layerPointToLatLng(r.layerPoint)}for(var h=0;h0?Math.round(t-i)/2:Math.max(0,Math.ceil(t))-Math.max(0,Math.floor(i))},_limitZoom:function(t){var i=this.getMinZoom(),e=this.getMaxZoom(),n=Oi?this.options.zoomSnap:1;return n&&(t=Math.round(t/n)*n),Math.max(i,Math.min(e,t))},_onPanTransitionStep:function(){this.fire("move")},_onPanTransitionEnd:function(){mt(this._mapPane,"leaflet-pan-anim"),this.fire("moveend")},_tryAnimatedPan:function(t,i){var e=this._getCenterOffset(t)._floor();return!(!0!==(i&&i.animate)&&!this.getSize().contains(e))&&(this.panBy(e,i),!0)},_createAnimProxy:function(){var t=this._proxy=ht("div","leaflet-proxy leaflet-zoom-animated");this._panes.mapPane.appendChild(t),this.on("zoomanim",function(t){var i=ce,e=this._proxy.style[i];wt(this._proxy,this.project(t.center,t.zoom),this.getZoomScale(t.zoom,1)),e===this._proxy.style[i]&&this._animatingZoom&&this._onZoomTransitionEnd()},this),this.on("load moveend",function(){var t=this.getCenter(),i=this.getZoom();wt(this._proxy,this.project(t,i),this.getZoomScale(i,1))},this),this._on("unload",this._destroyAnimProxy,this)},_destroyAnimProxy:function(){ut(this._proxy),delete this._proxy},_catchTransitionEnd:function(t){this._animatingZoom&&t.propertyName.indexOf("transform")>=0&&this._onZoomTransitionEnd()},_nothingToAnimate:function(){return!this._container.getElementsByClassName("leaflet-zoom-animated").length},_tryAnimatedZoom:function(t,i,e){if(this._animatingZoom)return!0;if(e=e||{},!this._zoomAnimated||!1===e.animate||this._nothingToAnimate()||Math.abs(i-this._zoom)>this.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(i),o=this._getCenterOffset(t)._divideBy(1-1/n);return!(!0!==e.animate&&!this.getSize().contains(o))&&(f(function(){this._moveStart(!0)._animateZoom(t,i,!0)},this),!0)},_animateZoom:function(t,i,n,o){n&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=i,pt(this._mapPane,"leaflet-zoom-anim")),this.fire("zoomanim",{center:t,zoom:i,noUpdate:o}),setTimeout(e(this._onZoomTransitionEnd,this),250)},_onZoomTransitionEnd:function(){this._animatingZoom&&(mt(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom),f(function(){this._moveEnd(!0)},this))}}),xe=v.extend({options:{position:"topright"},initialize:function(t){l(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var i=this._map;return i&&i.removeControl(this),this.options.position=t,i&&i.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this.remove(),this._map=t;var i=this._container=this.onAdd(t),e=this.getPosition(),n=t._controlCorners[e];return pt(i,"leaflet-control"),-1!==e.indexOf("bottom")?n.insertBefore(i,n.firstChild):n.appendChild(i),this},remove:function(){return this._map?(ut(this._container),this.onRemove&&this.onRemove(this._map),this._map=null,this):this},_refocusOnMap:function(t){this._map&&t&&t.screenX>0&&t.screenY>0&&this._map.getContainer().focus()}}),we=function(t){return new xe(t)};ye.include({addControl:function(t){return t.addTo(this),this},removeControl:function(t){return t.remove(),this},_initControlPos:function(){function t(t,o){var s=e+t+" "+e+o;i[t+o]=ht("div",s,n)}var i=this._controlCorners={},e="leaflet-",n=this._controlContainer=ht("div",e+"control-container",this._container);t("top","left"),t("top","right"),t("bottom","left"),t("bottom","right")},_clearControlPos:function(){for(var t in this._controlCorners)ut(this._controlCorners[t]);ut(this._controlContainer),delete this._controlCorners,delete this._controlContainer}});var Le=xe.extend({options:{collapsed:!0,position:"topright",autoZIndex:!0,hideSingleBase:!1,sortLayers:!1,sortFunction:function(t,i,e,n){return e1,this._baseLayersList.style.display=t?"":"none"),this._separator.style.display=i&&t?"":"none",this},_onLayerChange:function(t){this._handlingClick||this._update();var i=this._getLayer(n(t.target)),e=i.overlay?"add"===t.type?"overlayadd":"overlayremove":"add"===t.type?"baselayerchange":null;e&&this._map.fire(e,i)},_createRadioElement:function(t,i){var e='",n=document.createElement("div");return n.innerHTML=e,n.firstChild},_addItem:function(t){var i,e=document.createElement("label"),o=this._map.hasLayer(t.layer);t.overlay?((i=document.createElement("input")).type="checkbox",i.className="leaflet-control-layers-selector",i.defaultChecked=o):i=this._createRadioElement("leaflet-base-layers",o),this._layerControlInputs.push(i),i.layerId=n(t.layer),V(i,"click",this._onInputClick,this);var s=document.createElement("span");s.innerHTML=" "+t.name;var r=document.createElement("div");return e.appendChild(r),r.appendChild(i),r.appendChild(s),(t.overlay?this._overlaysList:this._baseLayersList).appendChild(e),this._checkDisabledLayers(),e},_onInputClick:function(){var t,i,e=this._layerControlInputs,n=[],o=[];this._handlingClick=!0;for(var s=e.length-1;s>=0;s--)t=e[s],i=this._getLayer(t.layerId).layer,t.checked?n.push(i):t.checked||o.push(i);for(s=0;s=0;o--)t=e[o],i=this._getLayer(t.layerId).layer,t.disabled=void 0!==i.options.minZoom&&ni.options.maxZoom},_expandIfNotCollapsed:function(){return this._map&&!this.options.collapsed&&this.expand(),this},_expand:function(){return this.expand()},_collapse:function(){return this.collapse()}}),Pe=xe.extend({options:{position:"topleft",zoomInText:"+",zoomInTitle:"Zoom in",zoomOutText:"−",zoomOutTitle:"Zoom out"},onAdd:function(t){var i="leaflet-control-zoom",e=ht("div",i+" leaflet-bar"),n=this.options;return this._zoomInButton=this._createButton(n.zoomInText,n.zoomInTitle,i+"-in",e,this._zoomIn),this._zoomOutButton=this._createButton(n.zoomOutText,n.zoomOutTitle,i+"-out",e,this._zoomOut),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),e},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoomthis._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,i,e,n,o){var s=ht("a",e,n);return s.innerHTML=t,s.href="#",s.title=i,s.setAttribute("role","button"),s.setAttribute("aria-label",i),J(s),V(s,"click",Q),V(s,"click",o,this),V(s,"click",this._refocusOnMap,this),s},_updateDisabled:function(){var t=this._map,i="leaflet-disabled";mt(this._zoomInButton,i),mt(this._zoomOutButton,i),(this._disabled||t._zoom===t.getMinZoom())&&pt(this._zoomOutButton,i),(this._disabled||t._zoom===t.getMaxZoom())&&pt(this._zoomInButton,i)}});ye.mergeOptions({zoomControl:!0}),ye.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new Pe,this.addControl(this.zoomControl))});var be=xe.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var i=ht("div","leaflet-control-scale"),e=this.options;return this._addScales(e,"leaflet-control-scale-line",i),t.on(e.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),i},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,i,e){t.metric&&(this._mScale=ht("div",i,e)),t.imperial&&(this._iScale=ht("div",i,e))},_update:function(){var t=this._map,i=t.getSize().y/2,e=t.distance(t.containerPointToLatLng([0,i]),t.containerPointToLatLng([this.options.maxWidth,i]));this._updateScales(e)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var i=this._getRoundNum(t),e=i<1e3?i+" m":i/1e3+" km";this._updateScale(this._mScale,e,i/t)},_updateImperial:function(t){var i,e,n,o=3.2808399*t;o>5280?(i=o/5280,e=this._getRoundNum(i),this._updateScale(this._iScale,e+" mi",e/i)):(n=this._getRoundNum(o),this._updateScale(this._iScale,n+" ft",n/o))},_updateScale:function(t,i,e){t.style.width=Math.round(this.options.maxWidth*e)+"px",t.innerHTML=i},_getRoundNum:function(t){var i=Math.pow(10,(Math.floor(t)+"").length-1),e=t/i;return e=e>=10?10:e>=5?5:e>=3?3:e>=2?2:1,i*e}}),Te=xe.extend({options:{position:"bottomright",prefix:'
    Leaflet'},initialize:function(t){l(this,t),this._attributions={}},onAdd:function(t){t.attributionControl=this,this._container=ht("div","leaflet-control-attribution"),J(this._container);for(var i in t._layers)t._layers[i].getAttribution&&this.addAttribution(t._layers[i].getAttribution());return this._update(),this._container},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t?(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update(),this):this},removeAttribution:function(t){return t?(this._attributions[t]&&(this._attributions[t]--,this._update()),this):this},_update:function(){if(this._map){var t=[];for(var i in this._attributions)this._attributions[i]&&t.push(i);var e=[];this.options.prefix&&e.push(this.options.prefix),t.length&&e.push(t.join(", ")),this._container.innerHTML=e.join(" | ")}}});ye.mergeOptions({attributionControl:!0}),ye.addInitHook(function(){this.options.attributionControl&&(new Te).addTo(this)});xe.Layers=Le,xe.Zoom=Pe,xe.Scale=be,xe.Attribution=Te,we.layers=function(t,i,e){return new Le(t,i,e)},we.zoom=function(t){return new Pe(t)},we.scale=function(t){return new be(t)},we.attribution=function(t){return new Te(t)};var ze,Me=v.extend({initialize:function(t){this._map=t},enable:function(){return this._enabled?this:(this._enabled=!0,this.addHooks(),this)},disable:function(){return this._enabled?(this._enabled=!1,this.removeHooks(),this):this},enabled:function(){return!!this._enabled}}),Ce={Events:hi},Ze=Hi?"touchstart mousedown":"mousedown",Ee={mousedown:"mouseup",touchstart:"touchend",pointerdown:"touchend",MSPointerDown:"touchend"},Se={mousedown:"mousemove",touchstart:"touchmove",pointerdown:"touchmove",MSPointerDown:"touchmove"},ke=ui.extend({options:{clickTolerance:3},initialize:function(t,i,e,n){l(this,n),this._element=t,this._dragStartTarget=i||t,this._preventOutline=e},enable:function(){this._enabled||(V(this._dragStartTarget,Ze,this._onDown,this),this._enabled=!0)},disable:function(){this._enabled&&(ke._dragging===this&&this.finishDrag(),G(this._dragStartTarget,Ze,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(t){if(!t._simulated&&this._enabled&&(this._moved=!1,!dt(this._element,"leaflet-zoom-anim")&&!(ke._dragging||t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||(ke._dragging=this,this._preventOutline&&zt(this._element),bt(),pi(),this._moving)))){this.fire("down");var i=t.touches?t.touches[0]:t;this._startPoint=new x(i.clientX,i.clientY),V(document,Se[t.type],this._onMove,this),V(document,Ee[t.type],this._onUp,this)}},_onMove:function(t){if(!t._simulated&&this._enabled)if(t.touches&&t.touches.length>1)this._moved=!0;else{var i=t.touches&&1===t.touches.length?t.touches[0]:t,e=new x(i.clientX,i.clientY).subtract(this._startPoint);(e.x||e.y)&&(Math.abs(e.x)+Math.abs(e.y)1e-7;h++)i=s*Math.sin(a),i=Math.pow((1-i)/(1+i),s/2),a+=u=Math.PI/2-2*Math.atan(r*i)-a;return new M(a*e,t.x*e/n)}},Re=(Object.freeze||Object)({LonLat:Ae,Mercator:Oe,SphericalMercator:_i}),De=i({},ci,{code:"EPSG:3395",projection:Oe,transformation:function(){var t=.5/(Math.PI*Oe.R);return E(t,.5,-t,.5)}()}),Ne=i({},ci,{code:"EPSG:4326",projection:Ae,transformation:E(1/180,1,-1/180,.5)}),je=i({},li,{projection:Ae,transformation:E(1,0,-1,0),scale:function(t){return Math.pow(2,t)},zoom:function(t){return Math.log(t)/Math.LN2},distance:function(t,i){var e=i.lng-t.lng,n=i.lat-t.lat;return Math.sqrt(e*e+n*n)},infinite:!0});li.Earth=ci,li.EPSG3395=De,li.EPSG3857=gi,li.EPSG900913=vi,li.EPSG4326=Ne,li.Simple=je;var We=ui.extend({options:{pane:"overlayPane",attribution:null,bubblingMouseEvents:!0},addTo:function(t){return t.addLayer(this),this},remove:function(){return this.removeFrom(this._map||this._mapToAdd)},removeFrom:function(t){return t&&t.removeLayer(this),this},getPane:function(t){return this._map.getPane(t?this.options[t]||t:this.options.pane)},addInteractiveTarget:function(t){return this._map._targets[n(t)]=this,this},removeInteractiveTarget:function(t){return delete this._map._targets[n(t)],this},getAttribution:function(){return this.options.attribution},_layerAdd:function(t){var i=t.target;if(i.hasLayer(this)){if(this._map=i,this._zoomAnimated=i._zoomAnimated,this.getEvents){var e=this.getEvents();i.on(e,this),this.once("remove",function(){i.off(e,this)},this)}this.onAdd(i),this.getAttribution&&i.attributionControl&&i.attributionControl.addAttribution(this.getAttribution()),this.fire("add"),i.fire("layeradd",{layer:this})}}});ye.include({addLayer:function(t){if(!t._layerAdd)throw new Error("The provided object is not a Layer.");var i=n(t);return this._layers[i]?this:(this._layers[i]=t,t._mapToAdd=this,t.beforeAdd&&t.beforeAdd(this),this.whenReady(t._layerAdd,t),this)},removeLayer:function(t){var i=n(t);return this._layers[i]?(this._loaded&&t.onRemove(this),t.getAttribution&&this.attributionControl&&this.attributionControl.removeAttribution(t.getAttribution()),delete this._layers[i],this._loaded&&(this.fire("layerremove",{layer:t}),t.fire("remove")),t._map=t._mapToAdd=null,this):this},hasLayer:function(t){return!!t&&n(t)in this._layers},eachLayer:function(t,i){for(var e in this._layers)t.call(i,this._layers[e]);return this},_addLayers:function(t){for(var i=0,e=(t=t?ei(t)?t:[t]:[]).length;ithis._layersMaxZoom&&this.setZoom(this._layersMaxZoom),void 0===this.options.minZoom&&this._layersMinZoom&&this.getZoom()i)return r=(n-i)/e,this._map.layerPointToLatLng([s.x-r*(s.x-o.x),s.y-r*(s.y-o.y)])},getBounds:function(){return this._bounds},addLatLng:function(t,i){return i=i||this._defaultShape(),t=C(t),i.push(t),this._bounds.extend(t),this.redraw()},_setLatLngs:function(t){this._bounds=new T,this._latlngs=this._convertLatLngs(t)},_defaultShape:function(){return Dt(this._latlngs)?this._latlngs:this._latlngs[0]},_convertLatLngs:function(t){for(var i=[],e=Dt(t),n=0,o=t.length;n=2&&i[0]instanceof M&&i[0].equals(i[e-1])&&i.pop(),i},_setLatLngs:function(t){Je.prototype._setLatLngs.call(this,t),Dt(this._latlngs)&&(this._latlngs=[this._latlngs])},_defaultShape:function(){return Dt(this._latlngs[0])?this._latlngs[0]:this._latlngs[0][0]},_clipPoints:function(){var t=this._renderer._bounds,i=this.options.weight,e=new x(i,i);if(t=new P(t.min.subtract(e),t.max.add(e)),this._parts=[],this._pxBounds&&this._pxBounds.intersects(t))if(this.options.noClip)this._parts=this._rings;else for(var n,o=0,s=this._rings.length;ot.y!=n.y>t.y&&t.x<(n.x-e.x)*(t.y-e.y)/(n.y-e.y)+e.x&&(u=!u);return u||Je.prototype._containsPoint.call(this,t,!0)}}),Qe=Fe.extend({initialize:function(t,i){l(this,i),this._layers={},t&&this.addData(t)},addData:function(t){var i,e,n,o=ei(t)?t:t.features;if(o){for(i=0,e=o.length;io?(i.height=o+"px",pt(t,"leaflet-popup-scrolled")):mt(t,"leaflet-popup-scrolled"),this._containerWidth=this._container.offsetWidth},_animateZoom:function(t){var i=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center),e=this._getAnchor();Lt(this._container,i.add(e))},_adjustPan:function(){if(!(!this.options.autoPan||this._map._panAnim&&this._map._panAnim._inProgress)){var t=this._map,i=parseInt(at(this._container,"marginBottom"),10)||0,e=this._container.offsetHeight+i,n=this._containerWidth,o=new x(this._containerLeft,-e-this._containerBottom);o._add(Pt(this._container));var s=t.layerPointToContainerPoint(o),r=w(this.options.autoPanPadding),a=w(this.options.autoPanPaddingTopLeft||r),h=w(this.options.autoPanPaddingBottomRight||r),u=t.getSize(),l=0,c=0;s.x+n+h.x>u.x&&(l=s.x+n-u.x+h.x),s.x-l-a.x<0&&(l=s.x-a.x),s.y+e+h.y>u.y&&(c=s.y+e-u.y+h.y),s.y-c-a.y<0&&(c=s.y-a.y),(l||c)&&t.fire("autopanstart").panBy([l,c])}},_onCloseButtonClick:function(t){this._close(),Q(t)},_getAnchor:function(){return w(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}});ye.mergeOptions({closePopupOnClick:!0}),ye.include({openPopup:function(t,i,e){return t instanceof rn||(t=new rn(e).setContent(t)),i&&t.setLatLng(i),this.hasLayer(t)?this:(this._popup&&this._popup.options.autoClose&&this.closePopup(),this._popup=t,this.addLayer(t))},closePopup:function(t){return t&&t!==this._popup||(t=this._popup,this._popup=null),t&&this.removeLayer(t),this}}),We.include({bindPopup:function(t,i){return t instanceof rn?(l(t,i),this._popup=t,t._source=this):(this._popup&&!i||(this._popup=new rn(i,this)),this._popup.setContent(t)),this._popupHandlersAdded||(this.on({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t,i){if(t instanceof We||(i=t,t=this),t instanceof Fe)for(var e in this._layers){t=this._layers[e];break}return i||(i=t.getCenter?t.getCenter():t.getLatLng()),this._popup&&this._map&&(this._popup._source=t,this._popup.update(),this._map.openPopup(this._popup,i)),this},closePopup:function(){return this._popup&&this._popup._close(),this},togglePopup:function(t){return this._popup&&(this._popup._map?this.closePopup():this.openPopup(t)),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var i=t.layer||t.target;this._popup&&this._map&&(Q(t),i instanceof Ke?this.openPopup(t.layer||t.target,t.latlng):this._map.hasLayer(this._popup)&&this._popup._source===i?this.closePopup():this.openPopup(i,t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)},_onKeyPress:function(t){13===t.originalEvent.keyCode&&this._openPopup(t)}});var an=sn.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,interactive:!1,opacity:.9},onAdd:function(t){sn.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&this._source.fire("tooltipopen",{tooltip:this},!0)},onRemove:function(t){sn.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&this._source.fire("tooltipclose",{tooltip:this},!0)},getEvents:function(){var t=sn.prototype.getEvents.call(this);return Hi&&!this.options.permanent&&(t.preclick=this._close),t},_close:function(){this._map&&this._map.closeTooltip(this)},_initLayout:function(){var t="leaflet-tooltip "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=ht("div",t)},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var i=this._map,e=this._container,n=i.latLngToContainerPoint(i.getCenter()),o=i.layerPointToContainerPoint(t),s=this.options.direction,r=e.offsetWidth,a=e.offsetHeight,h=w(this.options.offset),u=this._getAnchor();"top"===s?t=t.add(w(-r/2+h.x,-a+h.y+u.y,!0)):"bottom"===s?t=t.subtract(w(r/2-h.x,-h.y,!0)):"center"===s?t=t.subtract(w(r/2+h.x,a/2-u.y+h.y,!0)):"right"===s||"auto"===s&&o.xthis.options.maxZoom||en&&this._retainParent(o,s,r,n))},_retainChildren:function(t,i,e,n){for(var o=2*t;o<2*t+2;o++)for(var s=2*i;s<2*i+2;s++){var r=new x(o,s);r.z=e+1;var a=this._tileCoordsToKey(r),h=this._tiles[a];h&&h.active?h.retain=!0:(h&&h.loaded&&(h.retain=!0),e+1this.options.maxZoom||void 0!==this.options.minZoom&&o1)this._setView(t,e);else{for(var c=o.min.y;c<=o.max.y;c++)for(var _=o.min.x;_<=o.max.x;_++){var d=new x(_,c);d.z=this._tileZoom,this._isValidTile(d)&&(this._tiles[this._tileCoordsToKey(d)]||r.push(d))}if(r.sort(function(t,i){return t.distanceTo(s)-i.distanceTo(s)}),0!==r.length){this._loading||(this._loading=!0,this.fire("loading"));var p=document.createDocumentFragment();for(_=0;_e.max.x)||!i.wrapLat&&(t.ye.max.y))return!1}if(!this.options.bounds)return!0;var n=this._tileCoordsToBounds(t);return z(this.options.bounds).overlaps(n)},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToBounds:function(t){var i=this._map,e=this.getTileSize(),n=t.scaleBy(e),o=n.add(e),s=new T(i.unproject(n,t.z),i.unproject(o,t.z));return this.options.noWrap||i.wrapLatLngBounds(s),s},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var i=t.split(":"),e=new x(+i[0],+i[1]);return e.z=+i[2],e},_removeTile:function(t){var i=this._tiles[t];i&&(ut(i.el),delete this._tiles[t],this.fire("tileunload",{tile:i.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){pt(t,"leaflet-tile");var i=this.getTileSize();t.style.width=i.x+"px",t.style.height=i.y+"px",t.onselectstart=r,t.onmousemove=r,wi&&this.options.opacity<1&&vt(t,this.options.opacity),bi&&!Ti&&(t.style.WebkitBackfaceVisibility="hidden")},_addTile:function(t,i){var n=this._getTilePos(t),o=this._tileCoordsToKey(t),s=this.createTile(this._wrapCoords(t),e(this._tileReady,this,t));this._initTile(s),this.createTile.length<2&&f(e(this._tileReady,this,t,null,s)),Lt(s,n),this._tiles[o]={el:s,coords:t,current:!0},i.appendChild(s),this.fire("tileloadstart",{tile:s,coords:t})},_tileReady:function(t,i,n){if(this._map){i&&this.fire("tileerror",{error:i,tile:n,coords:t});var o=this._tileCoordsToKey(t);(n=this._tiles[o])&&(n.loaded=+new Date,this._map._fadeAnimated?(vt(n.el,0),g(this._fadeFrame),this._fadeFrame=f(this._updateOpacity,this)):(n.active=!0,this._pruneTiles()),i||(pt(n.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:n.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),wi||!this._map._fadeAnimated?f(this._pruneTiles,this):setTimeout(e(this._pruneTiles,this),250)))}},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var i=new x(this._wrapX?s(t.x,this._wrapX):t.x,this._wrapY?s(t.y,this._wrapY):t.y);return i.z=t.z,i},_pxBoundsToTileRange:function(t){var i=this.getTileSize();return new P(t.min.unscaleBy(i).floor(),t.max.unscaleBy(i).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}}),ln=un.extend({options:{minZoom:0,maxZoom:18,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1},initialize:function(t,i){this._url=t,(i=l(this,i)).detectRetina&&Vi&&i.maxZoom>0&&(i.tileSize=Math.floor(i.tileSize/2),i.zoomReverse?(i.zoomOffset--,i.minZoom++):(i.zoomOffset++,i.maxZoom--),i.minZoom=Math.max(0,i.minZoom)),"string"==typeof i.subdomains&&(i.subdomains=i.subdomains.split("")),bi||this.on("tileunload",this._onTileRemove)},setUrl:function(t,i){return this._url=t,i||this.redraw(),this},createTile:function(t,i){var n=document.createElement("img");return V(n,"load",e(this._tileOnLoad,this,i,n)),V(n,"error",e(this._tileOnError,this,i,n)),this.options.crossOrigin&&(n.crossOrigin=""),n.alt="",n.setAttribute("role","presentation"),n.src=this.getTileUrl(t),n},getTileUrl:function(t){var e={r:Vi?"@2x":"",s:this._getSubdomain(t),x:t.x,y:t.y,z:this._getZoomForUrl()};if(this._map&&!this._map.options.crs.infinite){var n=this._globalTileRange.max.y-t.y;this.options.tms&&(e.y=n),e["-y"]=n}return _(this._url,i(e,this.options))},_tileOnLoad:function(t,i){wi?setTimeout(e(t,this,null,i),0):t(null,i)},_tileOnError:function(t,i,e){var n=this.options.errorTileUrl;n&&i.src!==n&&(i.src=n),t(e,i)},_onTileRemove:function(t){t.tile.onload=null},_getZoomForUrl:function(){var t=this._tileZoom,i=this.options.maxZoom,e=this.options.zoomReverse,n=this.options.zoomOffset;return e&&(t=i-t),t+n},_getSubdomain:function(t){var i=Math.abs(t.x+t.y)%this.options.subdomains.length;return this.options.subdomains[i]},_abortLoading:function(){var t,i;for(t in this._tiles)this._tiles[t].coords.z!==this._tileZoom&&((i=this._tiles[t].el).onload=r,i.onerror=r,i.complete||(i.src=ni,ut(i)))}}),cn=ln.extend({defaultWmsParams:{service:"WMS",request:"GetMap",layers:"",styles:"",format:"image/jpeg",transparent:!1,version:"1.1.1"},options:{crs:null,uppercase:!1},initialize:function(t,e){this._url=t;var n=i({},this.defaultWmsParams);for(var o in e)o in this.options||(n[o]=e[o]);e=l(this,e),n.width=n.height=e.tileSize*(e.detectRetina&&Vi?2:1),this.wmsParams=n},onAdd:function(t){this._crs=this.options.crs||t.options.crs,this._wmsVersion=parseFloat(this.wmsParams.version);var i=this._wmsVersion>=1.3?"crs":"srs";this.wmsParams[i]=this._crs.code,ln.prototype.onAdd.call(this,t)},getTileUrl:function(t){var i=this._tileCoordsToBounds(t),e=this._crs.project(i.getNorthWest()),n=this._crs.project(i.getSouthEast()),o=(this._wmsVersion>=1.3&&this._crs===Ne?[n.y,e.x,e.y,n.x]:[e.x,n.y,n.x,e.y]).join(","),s=ln.prototype.getTileUrl.call(this,t);return s+c(this.wmsParams,s,this.options.uppercase)+(this.options.uppercase?"&BBOX=":"&bbox=")+o},setParams:function(t,e){return i(this.wmsParams,t),e||this.redraw(),this}});ln.WMS=cn,Yt.wms=function(t,i){return new cn(t,i)};var _n=We.extend({options:{padding:.1},initialize:function(t){l(this,t),n(this),this._layers=this._layers||{}},onAdd:function(){this._container||(this._initContainer(),this._zoomAnimated&&pt(this._container,"leaflet-zoom-animated")),this.getPane().appendChild(this._container),this._update(),this.on("update",this._updatePaths,this)},onRemove:function(){this.off("update",this._updatePaths,this),this._destroyContainer()},getEvents:function(){var t={viewreset:this._reset,zoom:this._onZoom,moveend:this._update,zoomend:this._onZoomEnd};return this._zoomAnimated&&(t.zoomanim=this._onAnimZoom),t},_onAnimZoom:function(t){this._updateTransform(t.center,t.zoom)},_onZoom:function(){this._updateTransform(this._map.getCenter(),this._map.getZoom())},_updateTransform:function(t,i){var e=this._map.getZoomScale(i,this._zoom),n=Pt(this._container),o=this._map.getSize().multiplyBy(.5+this.options.padding),s=this._map.project(this._center,i),r=this._map.project(t,i).subtract(s),a=o.multiplyBy(-e).add(n).add(o).subtract(r);Oi?wt(this._container,a,e):Lt(this._container,a)},_reset:function(){this._update(),this._updateTransform(this._center,this._zoom);for(var t in this._layers)this._layers[t]._reset()},_onZoomEnd:function(){for(var t in this._layers)this._layers[t]._project()},_updatePaths:function(){for(var t in this._layers)this._layers[t]._update()},_update:function(){var t=this.options.padding,i=this._map.getSize(),e=this._map.containerPointToLayerPoint(i.multiplyBy(-t)).round();this._bounds=new P(e,e.add(i.multiplyBy(1+2*t)).round()),this._center=this._map.getCenter(),this._zoom=this._map.getZoom()}}),dn=_n.extend({getEvents:function(){var t=_n.prototype.getEvents.call(this);return t.viewprereset=this._onViewPreReset,t},_onViewPreReset:function(){this._postponeUpdatePaths=!0},onAdd:function(){_n.prototype.onAdd.call(this),this._draw()},_initContainer:function(){var t=this._container=document.createElement("canvas");V(t,"mousemove",o(this._onMouseMove,32,this),this),V(t,"click dblclick mousedown mouseup contextmenu",this._onClick,this),V(t,"mouseout",this._handleMouseOut,this),this._ctx=t.getContext("2d")},_destroyContainer:function(){delete this._ctx,ut(this._container),G(this._container),delete this._container},_updatePaths:function(){if(!this._postponeUpdatePaths){this._redrawBounds=null;for(var t in this._layers)this._layers[t]._update();this._redraw()}},_update:function(){if(!this._map._animatingZoom||!this._bounds){this._drawnLayers={},_n.prototype._update.call(this);var t=this._bounds,i=this._container,e=t.getSize(),n=Vi?2:1;Lt(i,t.min),i.width=n*e.x,i.height=n*e.y,i.style.width=e.x+"px",i.style.height=e.y+"px",Vi&&this._ctx.scale(2,2),this._ctx.translate(-t.min.x,-t.min.y),this.fire("update")}},_reset:function(){_n.prototype._reset.call(this),this._postponeUpdatePaths&&(this._postponeUpdatePaths=!1,this._updatePaths())},_initPath:function(t){this._updateDashArray(t),this._layers[n(t)]=t;var i=t._order={layer:t,prev:this._drawLast,next:null};this._drawLast&&(this._drawLast.next=i),this._drawLast=i,this._drawFirst=this._drawFirst||this._drawLast},_addPath:function(t){this._requestRedraw(t)},_removePath:function(t){var i=t._order,e=i.next,n=i.prev;e?e.prev=n:this._drawLast=n,n?n.next=e:this._drawFirst=e,delete t._order,delete this._layers[L.stamp(t)],this._requestRedraw(t)},_updatePath:function(t){this._extendRedrawBounds(t),t._project(),t._update(),this._requestRedraw(t)},_updateStyle:function(t){this._updateDashArray(t),this._requestRedraw(t)},_updateDashArray:function(t){if(t.options.dashArray){var i,e=t.options.dashArray.split(","),n=[];for(i=0;i')}}catch(t){return function(t){return document.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}(),mn={_initContainer:function(){this._container=ht("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(_n.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var i=t._container=pn("shape");pt(i,"leaflet-vml-shape "+(this.options.className||"")),i.coordsize="1 1",t._path=pn("path"),i.appendChild(t._path),this._updateStyle(t),this._layers[n(t)]=t},_addPath:function(t){var i=t._container;this._container.appendChild(i),t.options.interactive&&t.addInteractiveTarget(i)},_removePath:function(t){var i=t._container;ut(i),t.removeInteractiveTarget(i),delete this._layers[n(t)]},_updateStyle:function(t){var i=t._stroke,e=t._fill,n=t.options,o=t._container;o.stroked=!!n.stroke,o.filled=!!n.fill,n.stroke?(i||(i=t._stroke=pn("stroke")),o.appendChild(i),i.weight=n.weight+"px",i.color=n.color,i.opacity=n.opacity,n.dashArray?i.dashStyle=ei(n.dashArray)?n.dashArray.join(" "):n.dashArray.replace(/( *, *)/g," "):i.dashStyle="",i.endcap=n.lineCap.replace("butt","flat"),i.joinstyle=n.lineJoin):i&&(o.removeChild(i),t._stroke=null),n.fill?(e||(e=t._fill=pn("fill")),o.appendChild(e),e.color=n.fillColor||n.color,e.opacity=n.fillOpacity):e&&(o.removeChild(e),t._fill=null)},_updateCircle:function(t){var i=t._point.round(),e=Math.round(t._radius),n=Math.round(t._radiusY||e);this._setPath(t,t._empty()?"M0 0":"AL "+i.x+","+i.y+" "+e+","+n+" 0,23592600")},_setPath:function(t,i){t._path.v=i},_bringToFront:function(t){ct(t._container)},_bringToBack:function(t){_t(t._container)}},fn=Ki?pn:S,gn=_n.extend({getEvents:function(){var t=_n.prototype.getEvents.call(this);return t.zoomstart=this._onZoomStart,t},_initContainer:function(){this._container=fn("svg"),this._container.setAttribute("pointer-events","none"),this._rootGroup=fn("g"),this._container.appendChild(this._rootGroup)},_destroyContainer:function(){ut(this._container),G(this._container),delete this._container,delete this._rootGroup},_onZoomStart:function(){this._update()},_update:function(){if(!this._map._animatingZoom||!this._bounds){_n.prototype._update.call(this);var t=this._bounds,i=t.getSize(),e=this._container;this._svgSize&&this._svgSize.equals(i)||(this._svgSize=i,e.setAttribute("width",i.x),e.setAttribute("height",i.y)),Lt(e,t.min),e.setAttribute("viewBox",[t.min.x,t.min.y,i.x,i.y].join(" ")),this.fire("update")}},_initPath:function(t){var i=t._path=fn("path");t.options.className&&pt(i,t.options.className),t.options.interactive&&pt(i,"leaflet-interactive"),this._updateStyle(t),this._layers[n(t)]=t},_addPath:function(t){this._rootGroup||this._initContainer(),this._rootGroup.appendChild(t._path),t.addInteractiveTarget(t._path)},_removePath:function(t){ut(t._path),t.removeInteractiveTarget(t._path),delete this._layers[n(t)]},_updatePath:function(t){t._project(),t._update()},_updateStyle:function(t){var i=t._path,e=t.options;i&&(e.stroke?(i.setAttribute("stroke",e.color),i.setAttribute("stroke-opacity",e.opacity),i.setAttribute("stroke-width",e.weight),i.setAttribute("stroke-linecap",e.lineCap),i.setAttribute("stroke-linejoin",e.lineJoin),e.dashArray?i.setAttribute("stroke-dasharray",e.dashArray):i.removeAttribute("stroke-dasharray"),e.dashOffset?i.setAttribute("stroke-dashoffset",e.dashOffset):i.removeAttribute("stroke-dashoffset")):i.setAttribute("stroke","none"),e.fill?(i.setAttribute("fill",e.fillColor||e.color),i.setAttribute("fill-opacity",e.fillOpacity),i.setAttribute("fill-rule",e.fillRule||"evenodd")):i.setAttribute("fill","none"))},_updatePoly:function(t,i){this._setPath(t,k(t._parts,i))},_updateCircle:function(t){var i=t._point,e=t._radius,n="a"+e+","+(t._radiusY||e)+" 0 1,0 ",o=t._empty()?"M0 0":"M"+(i.x-e)+","+i.y+n+2*e+",0 "+n+2*-e+",0 ";this._setPath(t,o)},_setPath:function(t,i){t._path.setAttribute("d",i)},_bringToFront:function(t){ct(t._path)},_bringToBack:function(t){_t(t._path)}});Ki&&gn.include(mn),ye.include({getRenderer:function(t){var i=t.options.renderer||this._getPaneRenderer(t.options.pane)||this.options.renderer||this._renderer;return i||(i=this._renderer=this.options.preferCanvas&&Xt()||Jt()),this.hasLayer(i)||this.addLayer(i),i},_getPaneRenderer:function(t){if("overlayPane"===t||void 0===t)return!1;var i=this._paneRenderers[t];return void 0===i&&(i=gn&&Jt({pane:t})||dn&&Xt({pane:t}),this._paneRenderers[t]=i),i}});var vn=$e.extend({initialize:function(t,i){$e.prototype.initialize.call(this,this._boundsToLatLngs(t),i)},setBounds:function(t){return this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return t=z(t),[t.getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}});gn.create=fn,gn.pointsToPath=k,Qe.geometryToLayer=Wt,Qe.coordsToLatLng=Ht,Qe.coordsToLatLngs=Ft,Qe.latLngToCoords=Ut,Qe.latLngsToCoords=Vt,Qe.getFeature=Gt,Qe.asFeature=qt,ye.mergeOptions({boxZoom:!0});var yn=Me.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._resetStateTimeout=0,t.on("unload",this._destroy,this)},addHooks:function(){V(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){G(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_destroy:function(){ut(this._pane),delete this._pane},_resetState:function(){this._resetStateTimeout=0,this._moved=!1},_clearDeferredResetState:function(){0!==this._resetStateTimeout&&(clearTimeout(this._resetStateTimeout),this._resetStateTimeout=0)},_onMouseDown:function(t){if(!t.shiftKey||1!==t.which&&1!==t.button)return!1;this._clearDeferredResetState(),this._resetState(),pi(),bt(),this._startPoint=this._map.mouseEventToContainerPoint(t),V(document,{contextmenu:Q,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=ht("div","leaflet-zoom-box",this._container),pt(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var i=new P(this._point,this._startPoint),e=i.getSize();Lt(this._box,i.min),this._box.style.width=e.x+"px",this._box.style.height=e.y+"px"},_finish:function(){this._moved&&(ut(this._box),mt(this._container,"leaflet-crosshair")),mi(),Tt(),G(document,{contextmenu:Q,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){if((1===t.which||1===t.button)&&(this._finish(),this._moved)){this._clearDeferredResetState(),this._resetStateTimeout=setTimeout(e(this._resetState,this),0);var i=new T(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point));this._map.fitBounds(i).fire("boxzoomend",{boxZoomBounds:i})}},_onKeyDown:function(t){27===t.keyCode&&this._finish()}});ye.addInitHook("addHandler","boxZoom",yn),ye.mergeOptions({doubleClickZoom:!0});var xn=Me.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var i=this._map,e=i.getZoom(),n=i.options.zoomDelta,o=t.originalEvent.shiftKey?e-n:e+n;"center"===i.options.doubleClickZoom?i.setZoom(o):i.setZoomAround(t.containerPoint,o)}});ye.addInitHook("addHandler","doubleClickZoom",xn),ye.mergeOptions({dragging:!0,inertia:!Ti,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,easeLinearity:.2,worldCopyJump:!1,maxBoundsViscosity:0});var wn=Me.extend({addHooks:function(){if(!this._draggable){var t=this._map;this._draggable=new ke(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),this._draggable.on("predrag",this._onPreDragLimit,this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDragWrap,this),t.on("zoomend",this._onZoomEnd,this),t.whenReady(this._onZoomEnd,this))}pt(this._map._container,"leaflet-grab leaflet-touch-drag"),this._draggable.enable(),this._positions=[],this._times=[]},removeHooks:function(){mt(this._map._container,"leaflet-grab"),mt(this._map._container,"leaflet-touch-drag"),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},moving:function(){return this._draggable&&this._draggable._moving},_onDragStart:function(){var t=this._map;if(t._stop(),this._map.options.maxBounds&&this._map.options.maxBoundsViscosity){var i=z(this._map.options.maxBounds);this._offsetLimit=b(this._map.latLngToContainerPoint(i.getNorthWest()).multiplyBy(-1),this._map.latLngToContainerPoint(i.getSouthEast()).multiplyBy(-1).add(this._map.getSize())),this._viscosity=Math.min(1,Math.max(0,this._map.options.maxBoundsViscosity))}else this._offsetLimit=null;t.fire("movestart").fire("dragstart"),t.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(t){if(this._map.options.inertia){var i=this._lastTime=+new Date,e=this._lastPos=this._draggable._absPos||this._draggable._newPos;this._positions.push(e),this._times.push(i),i-this._times[0]>50&&(this._positions.shift(),this._times.shift())}this._map.fire("move",t).fire("drag",t)},_onZoomEnd:function(){var t=this._map.getSize().divideBy(2),i=this._map.latLngToLayerPoint([0,0]);this._initialWorldOffset=i.subtract(t).x,this._worldWidth=this._map.getPixelWorldBounds().getSize().x},_viscousLimit:function(t,i){return t-(t-i)*this._viscosity},_onPreDragLimit:function(){if(this._viscosity&&this._offsetLimit){var t=this._draggable._newPos.subtract(this._draggable._startPos),i=this._offsetLimit;t.xi.max.x&&(t.x=this._viscousLimit(t.x,i.max.x)),t.y>i.max.y&&(t.y=this._viscousLimit(t.y,i.max.y)),this._draggable._newPos=this._draggable._startPos.add(t)}},_onPreDragWrap:function(){var t=this._worldWidth,i=Math.round(t/2),e=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-i+e)%t+i-e,s=(n+i+e)%t-i-e,r=Math.abs(o+e)0?s:-s))-i;this._delta=0,this._startTime=null,r&&("center"===t.options.scrollWheelZoom?t.setZoom(i+r):t.setZoomAround(this._lastMousePos,i+r))}});ye.addInitHook("addHandler","scrollWheelZoom",Pn),ye.mergeOptions({tap:!0,tapTolerance:15});var bn=Me.extend({addHooks:function(){V(this._map._container,"touchstart",this._onDown,this)},removeHooks:function(){G(this._map._container,"touchstart",this._onDown,this)},_onDown:function(t){if(t.touches){if($(t),this._fireClick=!0,t.touches.length>1)return this._fireClick=!1,void clearTimeout(this._holdTimeout);var i=t.touches[0],n=i.target;this._startPos=this._newPos=new x(i.clientX,i.clientY),n.tagName&&"a"===n.tagName.toLowerCase()&&pt(n,"leaflet-active"),this._holdTimeout=setTimeout(e(function(){this._isTapValid()&&(this._fireClick=!1,this._onUp(),this._simulateEvent("contextmenu",i))},this),1e3),this._simulateEvent("mousedown",i),V(document,{touchmove:this._onMove,touchend:this._onUp},this)}},_onUp:function(t){if(clearTimeout(this._holdTimeout),G(document,{touchmove:this._onMove,touchend:this._onUp},this),this._fireClick&&t&&t.changedTouches){var i=t.changedTouches[0],e=i.target;e&&e.tagName&&"a"===e.tagName.toLowerCase()&&mt(e,"leaflet-active"),this._simulateEvent("mouseup",i),this._isTapValid()&&this._simulateEvent("click",i)}},_isTapValid:function(){return this._newPos.distanceTo(this._startPos)<=this._map.options.tapTolerance},_onMove:function(t){var i=t.touches[0];this._newPos=new x(i.clientX,i.clientY),this._simulateEvent("mousemove",i)},_simulateEvent:function(t,i){var e=document.createEvent("MouseEvents");e._simulated=!0,i.target._simulatedClick=!0,e.initMouseEvent(t,!0,!0,window,1,i.screenX,i.screenY,i.clientX,i.clientY,!1,!1,!1,!1,0,null),i.target.dispatchEvent(e)}});Hi&&!Wi&&ye.addInitHook("addHandler","tap",bn),ye.mergeOptions({touchZoom:Hi&&!Ti,bounceAtZoomLimits:!0});var Tn=Me.extend({addHooks:function(){pt(this._map._container,"leaflet-touch-zoom"),V(this._map._container,"touchstart",this._onTouchStart,this)},removeHooks:function(){mt(this._map._container,"leaflet-touch-zoom"),G(this._map._container,"touchstart",this._onTouchStart,this)},_onTouchStart:function(t){var i=this._map;if(t.touches&&2===t.touches.length&&!i._animatingZoom&&!this._zooming){var e=i.mouseEventToContainerPoint(t.touches[0]),n=i.mouseEventToContainerPoint(t.touches[1]);this._centerPoint=i.getSize()._divideBy(2),this._startLatLng=i.containerPointToLatLng(this._centerPoint),"center"!==i.options.touchZoom&&(this._pinchStartLatLng=i.containerPointToLatLng(e.add(n)._divideBy(2))),this._startDist=e.distanceTo(n),this._startZoom=i.getZoom(),this._moved=!1,this._zooming=!0,i._stop(),V(document,"touchmove",this._onTouchMove,this),V(document,"touchend",this._onTouchEnd,this),$(t)}},_onTouchMove:function(t){if(t.touches&&2===t.touches.length&&this._zooming){var i=this._map,n=i.mouseEventToContainerPoint(t.touches[0]),o=i.mouseEventToContainerPoint(t.touches[1]),s=n.distanceTo(o)/this._startDist;if(this._zoom=i.getScaleZoom(s,this._startZoom),!i.options.bounceAtZoomLimits&&(this._zoomi.getMaxZoom()&&s>1)&&(this._zoom=i._limitZoom(this._zoom)),"center"===i.options.touchZoom){if(this._center=this._startLatLng,1===s)return}else{var r=n._add(o)._divideBy(2)._subtract(this._centerPoint);if(1===s&&0===r.x&&0===r.y)return;this._center=i.unproject(i.project(this._pinchStartLatLng,this._zoom).subtract(r),this._zoom)}this._moved||(i._moveStart(!0),this._moved=!0),g(this._animRequest);var a=e(i._move,i,this._center,this._zoom,{pinch:!0,round:!1});this._animRequest=f(a,this,!0),$(t)}},_onTouchEnd:function(){this._moved&&this._zooming?(this._zooming=!1,g(this._animRequest),G(document,"touchmove",this._onTouchMove),G(document,"touchend",this._onTouchEnd),this._map.options.zoomAnimation?this._map._animateZoom(this._center,this._map._limitZoom(this._zoom),!0,this._map.options.zoomSnap):this._map._resetView(this._center,this._map._limitZoom(this._zoom))):this._zooming=!1}});ye.addInitHook("addHandler","touchZoom",Tn),ye.BoxZoom=yn,ye.DoubleClickZoom=xn,ye.Drag=wn,ye.Keyboard=Ln,ye.ScrollWheelZoom=Pn,ye.Tap=bn,ye.TouchZoom=Tn;var zn=window.L;window.L=t,Object.freeze=$t,t.version="1.2.0",t.noConflict=function(){return window.L=zn,this},t.Control=xe,t.control=we,t.Browser=Yi,t.Evented=ui,t.Mixin=Ce,t.Util=ai,t.Class=v,t.Handler=Me,t.extend=i,t.bind=e,t.stamp=n,t.setOptions=l,t.DomEvent=le,t.DomUtil=ge,t.PosAnimation=ve,t.Draggable=ke,t.LineUtil=Be,t.PolyUtil=Ie,t.Point=x,t.point=w,t.Bounds=P,t.bounds=b,t.Transformation=Z,t.transformation=E,t.Projection=Re,t.LatLng=M,t.latLng=C,t.LatLngBounds=T,t.latLngBounds=z,t.CRS=li,t.GeoJSON=Qe,t.geoJSON=Kt,t.geoJson=en,t.Layer=We,t.LayerGroup=He,t.layerGroup=function(t){return new He(t)},t.FeatureGroup=Fe,t.featureGroup=function(t){return new Fe(t)},t.ImageOverlay=nn,t.imageOverlay=function(t,i,e){return new nn(t,i,e)},t.VideoOverlay=on,t.videoOverlay=function(t,i,e){return new on(t,i,e)},t.DivOverlay=sn,t.Popup=rn,t.popup=function(t,i){return new rn(t,i)},t.Tooltip=an,t.tooltip=function(t,i){return new an(t,i)},t.Icon=Ue,t.icon=function(t){return new Ue(t)},t.DivIcon=hn,t.divIcon=function(t){return new hn(t)},t.Marker=qe,t.marker=function(t,i){return new qe(t,i)},t.TileLayer=ln,t.tileLayer=Yt,t.GridLayer=un,t.gridLayer=function(t){return new un(t)},t.SVG=gn,t.svg=Jt,t.Renderer=_n,t.Canvas=dn,t.canvas=Xt,t.Path=Ke,t.CircleMarker=Ye,t.circleMarker=function(t,i){return new Ye(t,i)},t.Circle=Xe,t.circle=function(t,i,e){return new Xe(t,i,e)},t.Polyline=Je,t.polyline=function(t,i){return new Je(t,i)},t.Polygon=$e,t.polygon=function(t,i){return new $e(t,i)},t.Rectangle=vn,t.rectangle=function(t,i){return new vn(t,i)},t.Map=ye,t.map=function(t,i){return new ye(t,i)}}); \ No newline at end of file diff --git a/frappe/templates/print_formats/standard_macros.html b/frappe/templates/print_formats/standard_macros.html index a720b404f0..9d04bc1c0a 100644 --- a/frappe/templates/print_formats/standard_macros.html +++ b/frappe/templates/print_formats/standard_macros.html @@ -8,6 +8,8 @@ {%- elif df.fieldtype in ("Image", "Attach Image", "Attach", "Signature") and (guess_mimetype(doc[df.fieldname])[0] or "").startswith("image/") -%} {{ render_image(df, doc) }} + {%- elif df.fieldtype=="Geolocation" -%} + {{ render_geolocation(df, doc) }} {%- else -%} {{ render_field_with_label(df, doc) }} {%- endif -%} @@ -98,6 +100,10 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}" {{ print_value(df, doc) }} {% endmacro %} +{%- macro render_geolocation(df, doc) -%} + {{ "" }} +{%- endmacro -%} + {%- macro print_value(df, doc, parent_doc=None, visible_columns=None) -%} {% if doc.print_templates and doc.print_templates.get(df.fieldname) %} diff --git a/frappe/tests/ui/test_control_geolocation.js b/frappe/tests/ui/test_control_geolocation.js new file mode 100644 index 0000000000..0e3bedda26 --- /dev/null +++ b/frappe/tests/ui/test_control_geolocation.js @@ -0,0 +1,39 @@ +QUnit.module('controls'); + +QUnit.test("Test ControlGeolocation", function(assert) { + assert.expect(1); + + const random_name = frappe.utils.get_random(3).toLowerCase(); + + let done = assert.async(); + + // geolocation alert dialog suppressed (only secure origins or localhost allowed) + window.alert = function() { + console.log.apply(console, arguments); //eslint-disable-line + }; + + frappe.run_serially([ + () => { + return frappe.tests.make('Custom Field', [ + {dt: 'ToDo'}, + {fieldtype: 'Geolocation'}, + {label: random_name}, + ]); + }, + () => frappe.set_route('List', 'ToDo'), + () => frappe.new_doc('ToDo'), + () => { + if (frappe.quick_entry) + { + frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(); + return frappe.timeout(1); + } + }, + () => { + const control = $(`.frappe-control[data-fieldname="${random_name}"]`); + + return assert.ok(control.data('fieldtype') === 'Geolocation'); + }, + () => done() + ]); +}); diff --git a/frappe/tests/ui/tests.txt b/frappe/tests/ui/tests.txt index 3c0bb98ebb..4526159cd4 100644 --- a/frappe/tests/ui/tests.txt +++ b/frappe/tests/ui/tests.txt @@ -14,3 +14,4 @@ frappe/desk/doctype/event/test_event.js frappe/workflow/doctype/workflow/tests/test_workflow_create.js frappe/workflow/doctype/workflow/tests/test_workflow_test.js frappe/tests/ui/test_control_html.js +frappe/tests/ui/test_control_geolocation.js From 550865148522ab243c689503db9b986bf341f632 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 26 Oct 2017 17:45:53 +0530 Subject: [PATCH 13/51] Browse attached images in Image View (#4374) * Browse attached images in Image View #4141 * fix codacy --- frappe/core/doctype/file/file.py | 21 ++ frappe/public/css/list.css | 16 ++ .../js/frappe/views/image/image_view.js | 244 ++++++++++++------ .../js/frappe/views/image/photoswipe_dom.html | 92 +++---- frappe/public/less/list.less | 21 ++ 5 files changed, 264 insertions(+), 130 deletions(-) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 1976f4c862..03701e5ba0 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -423,3 +423,24 @@ def unzip_file(name): '''Unzip the given file and make file records for each of the extracted files''' file_obj = frappe.get_doc('File', name) file_obj.unzip() + +@frappe.whitelist() +def get_attached_images(doctype, names): + '''get list of image urls attached in form + returns {name: ['image.jpg', 'image.png']}''' + + if isinstance(names, string_types): + names = json.loads(names) + + img_urls = frappe.db.get_list('File', filters={ + 'attached_to_doctype': doctype, + 'attached_to_name': ('in', names), + 'is_folder': 0 + }, fields=['file_url', 'attached_to_name as docname']) + + out = frappe._dict() + for i in img_urls: + out[i.docname] = out.get(i.docname, []) + out[i.docname].append(i.file_url) + + return out \ No newline at end of file diff --git a/frappe/public/css/list.css b/frappe/public/css/list.css index abed4bc105..ece1c80a82 100644 --- a/frappe/public/css/list.css +++ b/frappe/public/css/list.css @@ -405,6 +405,22 @@ .pswp__bg { background-color: #fff !important; } +.pswp__more-items { + position: absolute; + bottom: 12px; + left: 50%; + transform: translateX(-50%); +} +.pswp__more-item { + display: inline-block; + margin: 5px; + height: 100px; + cursor: pointer; + border: 1px solid #d1d8dd; +} +.pswp__more-item img { + max-height: 100%; +} .gantt .details-container .heading { margin-bottom: 10px; font-size: 12px; diff --git a/frappe/public/js/frappe/views/image/image_view.js b/frappe/public/js/frappe/views/image/image_view.js index 6e094909f3..bf6b91c5b6 100644 --- a/frappe/public/js/frappe/views/image/image_view.js +++ b/frappe/public/js/frappe/views/image/image_view.js @@ -7,8 +7,17 @@ frappe.views.ImageView = frappe.views.ListRenderer.extend({ name: 'Image', render_view: function (values) { this.items = values; - this.render_image_view(); - this.setup_gallery(); + + this.get_attached_images() + .then(() => { + this.render_image_view(); + + if (!this.gallery) { + this.setup_gallery(); + } else { + this.gallery.prepare_pswp_items(this.items, this.images_map); + } + }); }, set_defaults: function() { this._super(); @@ -22,9 +31,14 @@ frappe.views.ImageView = frappe.views.ListRenderer.extend({ }, render_image_view: function () { var html = this.items.map(this.render_item.bind(this)).join(""); - this.container = $('
    ') - .addClass('image-view-container') - .appendTo(this.wrapper); + + this.container = this.wrapper.find('.image-view-container'); + if (this.container.length === 0) { + this.container = $('
    ') + .addClass('image-view-container') + .appendTo(this.wrapper); + } + this.container.append(html); }, render_item: function (item) { @@ -50,6 +64,14 @@ frappe.views.ImageView = frappe.views.ListRenderer.extend({ } return null; }, + get_attached_images: function () { + return frappe.call({ + method: 'frappe.core.doctype.file.file.get_attached_images', + args: { doctype: this.doctype, names: this.items.map(i => i.name) } + }).then(r => { + this.images_map = Object.assign(this.images_map || {}, r.message); + }); + }, get_header_html: function () { var main = frappe.render_template('list_item_main_head', { col: { type: "Subject" }, @@ -60,16 +82,17 @@ frappe.views.ImageView = frappe.views.ListRenderer.extend({ }, setup_gallery: function() { var me = this; - var gallery = new frappe.views.GalleryView({ + this.gallery = new frappe.views.GalleryView({ doctype: this.doctype, items: this.items, - wrapper: this.container + wrapper: this.container, + images_map: this.images_map }); this.container.on('click', '.btn.zoom-view', function(e) { e.preventDefault(); e.stopPropagation(); var name = $(this).data().name; - gallery.show(name); + me.gallery.show(name); return false; }); } @@ -80,10 +103,9 @@ frappe.views.GalleryView = Class.extend({ $.extend(this, opts); var me = this; - this.ready = false; - this.load_lib(function() { + this.lib_ready = this.load_lib(); + this.lib_ready.then(function() { me.prepare(); - me.ready = true; }); }, prepare: function() { @@ -94,101 +116,151 @@ frappe.views.GalleryView = Class.extend({ this.pswp_root = $(pswp).appendTo('body'); } }, - show: function(docname) { + prepare_pswp_items: function(_items, _images_map) { var me = this; - if(!this.ready) { - setTimeout(this.show.bind(this), 200); - return; + + if (_items) { + // passed when more button clicked + this.items = this.items.concat(_items); + this.images_map = _images_map; } - var items = this.items.map(function(i) { - var query = 'img[data-name="'+i.name+'"]'; - var el = me.wrapper.find(query).get(0); - - if(el) { - var width = el.naturalWidth; - var height = el.naturalHeight; - } - - if(!el) { - el = me.wrapper.find('.image-field[data-name="'+i.name+'"]').get(0); - width = el.getBoundingClientRect().width; - height = el.getBoundingClientRect().height; - } - - return { - src: i._image_url, - msrc: i._image_url, - name: i.name, - w: width, - h: height, - el: el - } - }); - var index; - items.map(function(item, i) { - if(item.name === docname) - index = i; + return new Promise(resolve => { + const items = this.items.map(function(i) { + const query = 'img[data-name="'+i.name+'"]'; + let el = me.wrapper.find(query).get(0); + + let width, height; + if(el) { + width = el.naturalWidth; + height = el.naturalHeight; + } + + if(!el) { + el = me.wrapper.find('.image-field[data-name="'+i.name+'"]').get(0); + width = el.getBoundingClientRect().width; + height = el.getBoundingClientRect().height; + } + + return { + src: i._image_url, + msrc: i._image_url, + name: i.name, + w: width, + h: height, + el: el + } + }); + this.pswp_items = items; + resolve(); }); + }, + show: function(docname) { + this.lib_ready + .then(() => this.prepare_pswp_items()) + .then(() => this._show(docname)); + }, + _show: function(docname) { + const me = this; + const items = this.pswp_items; + const item_index = items.findIndex(item => item.name === docname); var options = { - index: index, + index: item_index, getThumbBoundsFn: function(index) { - var thumbnail = items[index].el, // find thumbnail - pageYScroll = window.pageYOffset || document.documentElement.scrollTop, + const query = 'img[data-name="' + items[index].name + '"]'; + let thumbnail = me.wrapper.find(query).get(0); + + if (!thumbnail) { + return; + } + + var pageYScroll = window.pageYOffset || document.documentElement.scrollTop, rect = thumbnail.getBoundingClientRect(); return {x:rect.left, y:rect.top + pageYScroll, w:rect.width}; }, history: false, shareEl: false, + showHideOpacity: true } - var pswp = new PhotoSwipe( + + // init + this.pswp = new PhotoSwipe( this.pswp_root.get(0), PhotoSwipeUI_Default, items, options ); - pswp.init(); + this.browse_images(); + this.pswp.init(); }, - get_image_urls: function() { - // not implemented yet - return frappe.call({ - method: "frappe.client.get_list", - args: { - doctype: "File", - order_by: "attached_to_name", - fields: [ - "'image/*' as type", "ifnull(thumbnail_url, file_url) as thumbnail", - "concat(attached_to_name, ' - ', file_name) as title", "file_url as src", - "attached_to_name as name" - ], - filters: [ - ["File", "attached_to_doctype", "=", this.doctype], - ["File", "attached_to_name", "in", this.docnames], - ["File", "is_folder", "!=", 1] - ] - }, - freeze: true, - freeze_message: __("Fetching Images..") - }).then(function(r) { - if (!r.message) { - frappe.msgprint(__("No Images found")) - } else { - // filter image files from other - var images = r.message.filter(function(image) { - return frappe.utils.is_image_file(image.title || image.href); - }); - } + browse_images: function() { + const $more_items = this.pswp_root.find('.pswp__more-items'); + const images_map = this.images_map; + let last_hide_timeout = null; + + this.pswp.listen('afterChange', function() { + const images = images_map[this.currItem.name]; + if (!images || images.length === 1) return; + + hide_more_items_after_2s(); + const html = images.map(img_html).join(""); + $more_items.html(html); + }); + + this.pswp.listen('beforeChange', hide_more_items); + this.pswp.listen('initialZoomOut', hide_more_items); + this.pswp.listen('destroy', $(document).off('mousemove', hide_more_items_after_2s)); + + // Replace current image on click + $more_items.on('click', '.pswp__more-item', (e) => { + const img_el = e.target; + const index = this.pswp.items.findIndex(i => i.name === this.pswp.currItem.name); + + this.pswp.goTo(index); + this.pswp.items.splice(index, 1, { + src: img_el.src, + w: img_el.naturalWidth, + h: img_el.naturalHeight, + name: this.pswp.currItem.name + }); + this.pswp.invalidateCurrItems(); + this.pswp.updateSize(true); }); + + // hide more-images 2s after mousemove + $(document).on('mousemove', hide_more_items_after_2s); + + function hide_more_items_after_2s() { + clearTimeout(last_hide_timeout); + show_more_items(); + last_hide_timeout = setTimeout(hide_more_items, 2000); + } + + function show_more_items() { + $more_items.show(); + } + + function hide_more_items() { + $more_items.hide(); + } + + function img_html(src) { + return `
    + +
    `; + } }, - load_lib: function(callback) { - var asset_dir = 'assets/frappe/js/lib/photoswipe/'; - frappe.require([ - asset_dir + 'photoswipe.css', - asset_dir + 'default-skin.css', - asset_dir + 'photoswipe.js', - asset_dir + 'photoswipe-ui-default.js' - ], callback); + load_lib: function() { + return new Promise(resolve => { + var asset_dir = 'assets/frappe/js/lib/photoswipe/'; + frappe.require([ + asset_dir + 'photoswipe.css', + asset_dir + 'default-skin.css', + asset_dir + 'photoswipe.js', + asset_dir + 'photoswipe-ui-default.js' + ], resolve); + }); } -}); \ No newline at end of file +}); diff --git a/frappe/public/js/frappe/views/image/photoswipe_dom.html b/frappe/public/js/frappe/views/image/photoswipe_dom.html index ce4b06dce6..24d5e13ac0 100644 --- a/frappe/public/js/frappe/views/image/photoswipe_dom.html +++ b/frappe/public/js/frappe/views/image/photoswipe_dom.html @@ -4,66 +4,70 @@ \ No newline at end of file diff --git a/frappe/public/less/list.less b/frappe/public/less/list.less index b8f27cda1f..13da609b91 100644 --- a/frappe/public/less/list.less +++ b/frappe/public/less/list.less @@ -487,6 +487,27 @@ background-color: #fff !important; } +.pswp__more-items { + position: absolute; + bottom: 12px; + left: 50%; + transform: translateX(-50%); + + background-color: @bg +} + +.pswp__more-item { + display: inline-block; + margin: 5px; + height: 100px; + cursor: pointer; + border: 1px solid @border-color; + + img { + max-height: 100%; + } +} + // gantt .gantt { .details-container { From dd5bd8ef11e8f115e8b437234482bf47e835e3b9 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 26 Oct 2017 18:16:50 +0530 Subject: [PATCH 14/51] [minor][fix] Image view gallery (#4388) --- frappe/public/js/frappe/views/image/image_view.js | 5 ++++- frappe/public/less/list.less | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/views/image/image_view.js b/frappe/public/js/frappe/views/image/image_view.js index bf6b91c5b6..b5f3d369b3 100644 --- a/frappe/public/js/frappe/views/image/image_view.js +++ b/frappe/public/js/frappe/views/image/image_view.js @@ -202,7 +202,10 @@ frappe.views.GalleryView = Class.extend({ this.pswp.listen('afterChange', function() { const images = images_map[this.currItem.name]; - if (!images || images.length === 1) return; + if (!images || images.length === 1) { + $more_items.html(''); + return; + } hide_more_items_after_2s(); const html = images.map(img_html).join(""); diff --git a/frappe/public/less/list.less b/frappe/public/less/list.less index 13da609b91..3e6a598d71 100644 --- a/frappe/public/less/list.less +++ b/frappe/public/less/list.less @@ -492,8 +492,6 @@ bottom: 12px; left: 50%; transform: translateX(-50%); - - background-color: @bg } .pswp__more-item { From 0529b4b85b5f86756d3a793a0658cb302e087858 Mon Sep 17 00:00:00 2001 From: vishalseshagiri Date: Thu, 26 Oct 2017 18:40:56 +0530 Subject: [PATCH 15/51] Trigger map refresh if map_field is not empty, fixes incorrect map centering on load (#4387) --- frappe/public/js/frappe/form/controls/geolocation.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/js/frappe/form/controls/geolocation.js b/frappe/public/js/frappe/form/controls/geolocation.js index 515620da21..12f63a5c53 100644 --- a/frappe/public/js/frappe/form/controls/geolocation.js +++ b/frappe/public/js/frappe/form/controls/geolocation.js @@ -45,6 +45,7 @@ frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlData.extend({ // suppress error if layer has a point. } this.editableLayers.addTo(this.map); + this.map._onResize(); } else if ((value===undefined) || (value == JSON.stringify(new L.FeatureGroup().toGeoJSON()))) { this.locate_control.start(); } From 1cf3c338383b3529629c3d23329771433e67d79d Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 27 Oct 2017 14:47:10 +0530 Subject: [PATCH 16/51] [fix] catch dropbox api malformed_path exception and log file path with traceback (#4332) --- .../dropbox_settings/dropbox_settings.py | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py index a839049d7b..0b456205bf 100644 --- a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py +++ b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py @@ -13,7 +13,7 @@ from frappe.utils.background_jobs import enqueue from six.moves.urllib.parse import urlparse, parse_qs from frappe.integrations.utils import make_post_request from frappe.utils import (cint, split_emails, get_request_site_address, cstr, - get_files_path, get_backups_path, encode, get_url) + get_files_path, get_backups_path, get_url, encode) ignore_list = [".DS_Store"] @@ -152,19 +152,27 @@ def upload_file_to_dropbox(filename, folder, dropbox_client): f = open(encode(filename), 'rb') path = "{0}/{1}".format(folder, os.path.basename(filename)) - if file_size <= chunk_size: - dropbox_client.files_upload(f.read(), path, mode) - else: - upload_session_start_result = dropbox_client.files_upload_session_start(f.read(chunk_size)) - cursor = dropbox.files.UploadSessionCursor(session_id=upload_session_start_result.session_id, offset=f.tell()) - commit = dropbox.files.CommitInfo(path=path, mode=mode) - - while f.tell() < file_size: - if ((file_size - f.tell()) <= chunk_size): - dropbox_client.files_upload_session_finish(f.read(chunk_size), cursor, commit) - else: - dropbox_client.files_upload_session_append(f.read(chunk_size), cursor.session_id,cursor.offset) - cursor.offset = f.tell() + try: + if file_size <= chunk_size: + dropbox_client.files_upload(f.read(), path, mode) + else: + upload_session_start_result = dropbox_client.files_upload_session_start(f.read(chunk_size)) + cursor = dropbox.files.UploadSessionCursor(session_id=upload_session_start_result.session_id, offset=f.tell()) + commit = dropbox.files.CommitInfo(path=path, mode=mode) + + while f.tell() < file_size: + if ((file_size - f.tell()) <= chunk_size): + dropbox_client.files_upload_session_finish(f.read(chunk_size), cursor, commit) + else: + dropbox_client.files_upload_session_append(f.read(chunk_size), cursor.session_id,cursor.offset) + cursor.offset = f.tell() + except dropbox.exceptions.ApiError as e: + if isinstance(e.error, dropbox.files.UploadError): + error = "File Path: {path}\n".foramt(path=path) + error += frappe.get_traceback() + frappe.log_error(error) + else: + raise def create_folder_if_not_exists(folder, dropbox_client): try: @@ -210,7 +218,7 @@ def get_redirect_url(): if response.get("message"): return response["message"] - except Exception as e: + except Exception: frappe.log_error() frappe.throw( _("Something went wrong while generating dropbox access token. Please check error log for more details.") From 299ab6198a76eec3267f6e2b0eb1e7fd06ea46d0 Mon Sep 17 00:00:00 2001 From: joezsweet Date: Fri, 27 Oct 2017 11:18:48 +0200 Subject: [PATCH 17/51] Cron like events scheduler (#4339) * - trigger new "cron" event - check cron string syntax - added croniter to requirements * - run scheduler ever 60 sec - trigger all enabled events - enqueue if now >= next time execution since last one * Update task-runner.md * fixed tests * fix triggering with now = True * modified sobstitution to cron_map modified annually label to annual * ability to use labels defined in cron_map in cron string definition --- frappe/docs/user/en/tutorial/task-runner.md | 21 +++- frappe/tests/test_scheduler.py | 4 +- frappe/utils/scheduler.py | 118 +++++++++++--------- requirements.txt | 2 +- 4 files changed, 86 insertions(+), 59 deletions(-) diff --git a/frappe/docs/user/en/tutorial/task-runner.md b/frappe/docs/user/en/tutorial/task-runner.md index 0afdfeaa1b..a700f1cafd 100755 --- a/frappe/docs/user/en/tutorial/task-runner.md +++ b/frappe/docs/user/en/tutorial/task-runner.md @@ -1,8 +1,8 @@ # Scheduled Tasks -Finally, an application also has to send email notifications and do other kind of scheduled tasks. In Frappé, if you have setup the bench, the task / scheduler is setup via Celery using Redis Queue. +Finally, an application also has to send email notifications and do other kind of scheduled tasks. In Frappé, if you have setup the bench, the task / scheduler is setup via RQ using Redis Queue. -To add a new task handler, go to `hooks.py` and add a new handler. Default handlers are `all`, `daily`, `weekly`, `monthly`. The `all` handler is called every 3 minutes by default. +To add a new task handler, go to `hooks.py` and add a new handler. Default handlers are `all`, `daily`, `weekly`, `monthly`, `cron`. The `all` handler is called every 4 minutes by default. # Scheduled Tasks # --------------- @@ -11,6 +11,15 @@ To add a new task handler, go to `hooks.py` and add a new handler. Default handl "daily": [ "library_management.tasks.daily" ], + "cron": { + "0/10 * * * *": [ + "library_management.task.run_every_ten_mins" + ], + "15 18 * * *": [ + "library_management.task.every_day_at_18_15" + ] + } + } Here we can point to a Python function and that function will be executed every day. Let us look what this function looks like: @@ -21,6 +30,14 @@ Here we can point to a Python function and that function will be executed every from __future__ import unicode_literals import frappe from frappe.utils import datediff, nowdate, format_date, add_days + + def every_ten_minutes(): + # stuff to do every 10 minutes + pass + + def every_day_at_18_15(): + # stuff to do every day at 6:15pm + pass def daily(): loan_period = frappe.db.get_value("Library Management Settings", diff --git a/frappe/tests/test_scheduler.py b/frappe/tests/test_scheduler.py index ce508c3e71..4ca5feaa29 100644 --- a/frappe/tests/test_scheduler.py +++ b/frappe/tests/test_scheduler.py @@ -33,7 +33,7 @@ class TestScheduler(TestCase): next_event = last_event + relativedelta(minutes=30) enqueue_applicable_events(frappe.local.site, next_event, last_event) - self.assertFalse("all" in frappe.flags.ran_schedulers) + self.assertFalse("cron" in frappe.flags.ran_schedulers) # maintain last_event and next_event on the same day last_event = now_datetime().replace(hour=0, minute=0, second=0, microsecond=0) @@ -55,7 +55,7 @@ class TestScheduler(TestCase): enqueue_applicable_events(frappe.local.site, next_event, last_event) self.assertTrue("all" in frappe.flags.ran_schedulers) - self.assertTrue("hourly" in frappe.flags.ran_schedulers) + self.assertFalse("hourly" in frappe.flags.ran_schedulers) def test_restrict_scheduler_events(self): diff --git a/frappe/utils/scheduler.py b/frappe/utils/scheduler.py index 68ab6f7cc2..aa024b2419 100755 --- a/frappe/utils/scheduler.py +++ b/frappe/utils/scheduler.py @@ -25,15 +25,30 @@ from frappe.utils.data import get_datetime, now_datetime from frappe.core.doctype.user.user import STANDARD_USERS from frappe.installer import update_site_config from six import string_types +from croniter import croniter DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' +cron_map = { + "yearly": "0 0 1 1 *", + "annual": "0 0 1 1 *", + "monthly": "0 0 1 * *", + "monthly_long": "0 0 1 * *", + "weekly": "0 0 * * 0", + "weekly_long": "0 0 * * 0", + "daily": "0 0 * * *", + "daily_long": "0 0 * * *", + "midnight": "0 0 * * *", + "hourly": "0 * * * *", + "hourly_long": "0 * * * *", + "all": "0/" + str((frappe.get_conf().scheduler_interval or 240) // 60) + " * * * *", +} + def start_scheduler(): '''Run enqueue_events_for_all_sites every 2 minutes (default). Specify scheduler_interval in seconds in common_site_config.json''' - interval = frappe.get_conf().scheduler_interval or 240 - schedule.every(interval).seconds.do(enqueue_events_for_all_sites) + schedule.every(60).seconds.do(enqueue_events_for_all_sites) while True: schedule.run_pending() @@ -105,64 +120,59 @@ def enqueue_applicable_events(site, nowtime, last, queued_jobs=()): enabled_events = get_enabled_scheduler_events() - def trigger_if_enabled(site, event): - if event in enabled_events: - trigger(site, event, queued_jobs) - _log(event) + def trigger_if_enabled(site, event, last, queued_jobs): + trigger(site, event, last, queued_jobs) + _log(event) def _log(event): out.append("{time} - {event} - queued".format(time=nowtime_str, event=event)) - if nowtime.day != last.day: - # if first task of the day execute daily tasks - trigger_if_enabled(site, "daily") - trigger_if_enabled(site, "daily_long") - - if nowtime.month != last.month: - trigger_if_enabled(site, "monthly") - trigger_if_enabled(site, "monthly_long") - - if nowtime.weekday()==0: - trigger_if_enabled(site, "weekly") - trigger_if_enabled(site, "weekly_long") - - if "all" not in enabled_events: - trigger(site, "all", queued_jobs) - - if "hourly" not in enabled_events: - trigger(site, "hourly", queued_jobs) - - if nowtime.hour != last.hour: - trigger_if_enabled(site, "hourly") - trigger_if_enabled(site, "hourly_long") - - if "all" not in enabled_events: - trigger(site, "all", queued_jobs) + for event in enabled_events: + trigger_if_enabled(site, event, last, queued_jobs) - trigger_if_enabled(site, "all") + if "all" not in enabled_events: + trigger_if_enabled(site, "all", last, queued_jobs) return out -def trigger(site, event, queued_jobs=(), now=False): - """trigger method in hooks.scheduler_events""" - queue = 'long' if event.endswith('_long') else 'short' - timeout = queue_timeout[queue] - if not queued_jobs and not now: - queued_jobs = get_jobs(site=site, queue=queue) - - if frappe.flags.in_test: - frappe.flags.ran_schedulers.append(event) - - events = get_scheduler_events(event) - if not events: - return - - for handler in events: - if not now: - if handler not in queued_jobs: - enqueue(handler, queue, timeout, event) - else: - scheduler_task(site=site, event=event, handler=handler, now=True) +def trigger(site, event, last=None, queued_jobs=(), now=False): + """Trigger method in hooks.scheduler_events.""" + + queue = 'long' if event.endswith('_long') else 'short' + timeout = queue_timeout[queue] + if not queued_jobs and not now: + queued_jobs = get_jobs(site=site, queue=queue) + + if frappe.flags.in_test: + frappe.flags.ran_schedulers.append(event) + + events_from_hooks = get_scheduler_events(event) + if not events_from_hooks: + return + + events = events_from_hooks + if not now: + events = [] + if event == "cron": + for e in events_from_hooks: + e = cron_map.get(e, e) + if croniter.is_valid(e): + if croniter(e, last).get_next(datetime) <= frappe.utils.now_datetime(): + events.extend(events_from_hooks[e]) + else: + frappe.log_error("Cron string " + e + " is not valid", "Error triggering cron job") + frappe.logger(__name__).error('Exception in Trigger Events for Site {0}, Cron String {1}'.format(site, e)) + + else: + if croniter(cron_map[event], last).get_next(datetime) <= frappe.utils.now_datetime(): + events.extend(events_from_hooks) + + for handler in events: + if not now: + if handler not in queued_jobs: + enqueue(handler, queue, timeout, event) + else: + scheduler_task(site=site, event=event, handler=handler, now=True) def get_scheduler_events(event): '''Get scheduler events from hooks and integrations''' @@ -205,7 +215,7 @@ def get_enabled_scheduler_events(): return enabled_events return ["all", "hourly", "hourly_long", "daily", "daily_long", - "weekly", "weekly_long", "monthly", "monthly_long"] + "weekly", "weekly_long", "monthly", "monthly_long", "cron"] def is_scheduler_disabled(): if frappe.conf.disable_scheduler: @@ -293,7 +303,7 @@ def restrict_scheduler_events_if_dormant(): update_site_config('dormant', True) def restrict_scheduler_events(*args, **kwargs): - val = json.dumps(["hourly", "hourly_long", "daily", "daily_long", "weekly", "weekly_long", "monthly", "monthly_long"]) + val = json.dumps(["hourly", "hourly_long", "daily", "daily_long", "weekly", "weekly_long", "monthly", "monthly_long", "cron"]) frappe.db.set_global('enabled_scheduler_events', val) def is_dormant(since = 345600): diff --git a/requirements.txt b/requirements.txt index cf72c3109d..3312d232b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -50,4 +50,4 @@ pyqrcode pypng premailer psycopg2 - +croniter From b20be20dce411d20cf6960ca4547bf3043815659 Mon Sep 17 00:00:00 2001 From: Pawan Mehta Date: Fri, 27 Oct 2017 14:55:37 +0530 Subject: [PATCH 18/51] Added upload instructions to file related to bulk edit feature (ERPNext repo issue:#11207) (#4345) * [fix] #4344 * Update grid.js --- frappe/public/js/frappe/form/grid.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index b9987b38e8..c249883465 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -611,7 +611,7 @@ frappe.ui.form.Grid = Class.extend({ me.frm.clear_table(me.df.fieldname); $.each(data, function(i, row) { - if(i > 4) { + if(i > 6) { var blank_row = true; $.each(row, function(ci, value) { if(value) { @@ -659,6 +659,8 @@ frappe.ui.form.Grid = Class.extend({ data.push([]); data.push([]); data.push([]); + data.push([__("The CSV format is case sensitive")]); + data.push([__("Do not edit headers which are preset in the template")]); data.push(["------"]); $.each(frappe.get_meta(me.df.options).fields, function(i, df) { if(frappe.model.is_value_type(df.fieldtype)) { From 57dcc98ae835ca0a5ee0ea5837b8821e7f0fc246 Mon Sep 17 00:00:00 2001 From: almeidapaulopt Date: Fri, 27 Oct 2017 10:26:06 +0100 Subject: [PATCH 19/51] [hotfix]Gsuite change code tag to pre (#4354) * change code tag to pre * gelete test files --- .../integrations/doctype/gsuite_settings/gsuite_settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/integrations/doctype/gsuite_settings/gsuite_settings.json b/frappe/integrations/doctype/gsuite_settings/gsuite_settings.json index 49cf853853..65ed904c74 100644 --- a/frappe/integrations/doctype/gsuite_settings/gsuite_settings.json +++ b/frappe/integrations/doctype/gsuite_settings/gsuite_settings.json @@ -250,7 +250,7 @@ "label": "Script Code", "length": 0, "no_copy": 0, - "options": "// ERPNEXT GSuite integration\n//\n\nfunction doGet(e){\n return ContentService.createTextOutput('ok');\n}\n\nfunction doPost(e) {\n var p = JSON.parse(e.postData.contents);\n\n switch(p.exec){\n case 'new':\n var url = createDoc(p);\n result = { 'url': url };\n break;\n case 'test':\n result = { 'test':'ping' , 'version':'1.0'}\n }\n return ContentService.createTextOutput(JSON.stringify(result)).setMimeType(ContentService.MimeType.JSON);\n}\n\nfunction replaceVars(body,p){\n for (key in p) {\n if (p.hasOwnProperty(key)) {\n if (p[key] != null) {\n body.replaceText('{{'+key+'}}', p[key]);\n }\n }\n } \n}\n\nfunction createDoc(p) {\n if(p.destination){\n var folder = DriveApp.getFolderById(p.destination);\n } else {\n var folder = DriveApp.getRootFolder();\n }\n var template = DriveApp.getFileById( p.template )\n var newfile = template.makeCopy( p.filename , folder );\n\n switch(newfile.getMimeType()){\n case MimeType.GOOGLE_DOCS:\n var body = DocumentApp.openById(newfile.getId()).getBody();\n replaceVars(body,p.vars);\n break;\n case MimeType.GOOGLE_SHEETS:\n //TBD\n case MimeType.GOOGLE_SLIDES:\n //TBD\n }\n return newfile.getUrl()\n}\n\n", + "options": "
    // ERPNEXT GSuite integration\n//\n\nfunction doGet(e){\n  return ContentService.createTextOutput('ok');\n}\n\nfunction doPost(e) {\n  var p = JSON.parse(e.postData.contents);\n\n  switch(p.exec){\n    case 'new':\n      var url = createDoc(p);\n      result = { 'url': url };\n      break;\n    case 'test':\n      result = { 'test':'ping' , 'version':'1.0'}\n  }\n  return ContentService.createTextOutput(JSON.stringify(result)).setMimeType(ContentService.MimeType.JSON);\n}\n\nfunction replaceVars(body,p){\n  for (key in p) {\n    if (p.hasOwnProperty(key)) {\n      if (p[key] != null) {\n        body.replaceText('{{'+key+'}}', p[key]);\n      }\n    }\n  }    \n}\n\nfunction createDoc(p) {\n  if(p.destination){\n    var folder = DriveApp.getFolderById(p.destination);\n  } else {\n    var folder = DriveApp.getRootFolder();\n  }\n  var template = DriveApp.getFileById( p.template )\n  var newfile = template.makeCopy( p.filename , folder );\n\n  switch(newfile.getMimeType()){\n    case MimeType.GOOGLE_DOCS:\n      var body = DocumentApp.openById(newfile.getId()).getBody();\n      replaceVars(body,p.vars);\n      break;\n    case MimeType.GOOGLE_SHEETS:\n      //TBD\n    case MimeType.GOOGLE_SLIDES:\n      //TBD\n  }\n  return newfile.getUrl()\n}\n\n
    ", "permlevel": 0, "precision": "", "print_hide": 0, @@ -365,7 +365,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2017-05-19 15:28:44.663715", + "modified": "2017-10-20 16:11:47.757030", "modified_by": "Administrator", "module": "Integrations", "name": "GSuite Settings", From 183c481dd2ba2911d201c4d74d3f4170bf8aa2d5 Mon Sep 17 00:00:00 2001 From: Shreya Shah Date: Fri, 27 Oct 2017 15:06:07 +0530 Subject: [PATCH 20/51] fixed select options in dialog (#4361) --- frappe/public/js/frappe/form/quick_entry.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/frappe/public/js/frappe/form/quick_entry.js b/frappe/public/js/frappe/form/quick_entry.js index f3f5e55d71..031982392d 100644 --- a/frappe/public/js/frappe/form/quick_entry.js +++ b/frappe/public/js/frappe/form/quick_entry.js @@ -91,8 +91,6 @@ frappe.ui.form.QuickEntryForm = Class.extend({ fields: this.mandatory, }); this.dialog.doc = this.doc; - // refresh dependencies etc - this.dialog.refresh(); this.register_primary_action(); this.render_edit_in_full_page_link(); From bd8b57ee9ac5099ae455cb944756fa221800a337 Mon Sep 17 00:00:00 2001 From: Javier Wong Date: Fri, 27 Oct 2017 17:37:36 +0800 Subject: [PATCH 21/51] [fix] Do not show the Save Button if there is a Workflow without Local Changes and Print Preview is Toggled (#4362) * [fix] Do not show the Save Button if there is a Workflow without Local Changes The save button is currently shown when there is a workflow and when the print preview is shown and subsequently cleared, even though there were no local changes made. * Improved Code Readability --- frappe/public/js/frappe/form/toolbar.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/toolbar.js b/frappe/public/js/frappe/form/toolbar.js index a3954dfac0..06230be561 100644 --- a/frappe/public/js/frappe/form/toolbar.js +++ b/frappe/public/js/frappe/form/toolbar.js @@ -264,7 +264,10 @@ frappe.ui.form.Toolbar = Class.extend({ status = "Submit"; } else if (this.can_save()) { if (!this.frm.save_disabled) { - status = "Save"; + //Show the save button if there is no workflow or if there is a workflow and there are changes + if (this.has_workflow() ? this.frm.doc.__unsaved : true) { + status = "Save"; + } } } else if (this.can_update()) { status = "Update"; From 7fa97e97feedbb0cd0f603f532d4cadf11d277c1 Mon Sep 17 00:00:00 2001 From: Shreya Shah Date: Fri, 27 Oct 2017 15:18:44 +0530 Subject: [PATCH 22/51] Able to add options for customized form (#4369) * exception added to add options * Update customize_form.py * Update customize_form.py --- frappe/custom/doctype/customize_form/customize_form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 0392b46ee4..ad583b77c6 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -68,7 +68,7 @@ allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Da ('Text', 'Data'), ('Text', 'Text Editor', 'Code', 'Signature'), ('Data', 'Select'), ('Text', 'Small Text'), ('Text', 'Data', 'Barcode'), ('Code', 'Geolocation')) -allowed_fieldtype_for_options_change = ('Read Only', 'HTML', 'Select',) +allowed_fieldtype_for_options_change = ('Read Only', 'HTML', 'Select', 'Data') class CustomizeForm(Document): def on_update(self): From a279cdc4a2d6c7d48c5949ca3c9b85db8b7611b4 Mon Sep 17 00:00:00 2001 From: Manas Solanki Date: Fri, 27 Oct 2017 15:25:52 +0530 Subject: [PATCH 23/51] attach print format in the emails before sending. (#4366) --- frappe/core/doctype/communication/email.py | 9 ++++-- .../email/doctype/email_alert/email_alert.py | 3 +- frappe/email/queue.py | 31 ++++++++++++------- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index ac69cc24e2..2188a808c0 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -259,8 +259,8 @@ def prepare_to_notify(doc, print_html=None, print_format=None, attachments=None) doc.attachments = [] if print_html or print_format: - doc.attachments.append(frappe.attach_print(doc.reference_doctype, doc.reference_name, - print_format=print_format, html=print_html)) + doc.attachments.append({"print_format_attachment":1, "doctype":doc.reference_doctype, + "name":doc.reference_name, "print_format":print_format, "html":print_html}) if attachments: if isinstance(attachments, string_types): @@ -270,8 +270,11 @@ def prepare_to_notify(doc, print_html=None, print_format=None, attachments=None) if isinstance(a, string_types): # is it a filename? try: + # keep this for error handling file = get_file(a) - doc.attachments.append({"fname": file[0], "fcontent": file[1]}) + # these attachments will be attached on-demand + # and won't be stored in the message + doc.attachments.append({"fid": a}) except IOError: frappe.throw(_("Unable to find attachment {0}").format(a)) else: diff --git a/frappe/email/doctype/email_alert/email_alert.py b/frappe/email/doctype/email_alert/email_alert.py index 10a964d88f..93208b7e12 100755 --- a/frappe/email/doctype/email_alert/email_alert.py +++ b/frappe/email/doctype/email_alert/email_alert.py @@ -117,7 +117,8 @@ def get_context(context): please enable Allow Print For {0} in Print Settings""".format(status)), title=_("Error in Email Alert")) else: - return [frappe.attach_print(doc.doctype, doc.name, None, self.print_format)] + return [{"print_format_attachment":1, "doctype":doc.doctype, "name": doc.name, + "print_format":self.print_format}] context = get_context(doc) recipients = [] diff --git a/frappe/email/queue.py b/frappe/email/queue.py index 91ac4cf06a..085dfb0c18 100755 --- a/frappe/email/queue.py +++ b/frappe/email/queue.py @@ -162,11 +162,13 @@ def get_email_queue(recipients, sender, subject, **kwargs): e.priority = kwargs.get('send_priority') attachments = kwargs.get('attachments') if attachments: - # store attachments with fid, to be attached on-demand later + # store attachments with fid or print format details, to be attached on-demand later _attachments = [] for att in attachments: if att.get('fid'): _attachments.append(att) + elif att.get("print_format_attachment") == 1: + _attachments.append(att) e.attachments = json.dumps(_attachments) try: @@ -517,17 +519,22 @@ def prepare_message(email, recipient, recipients_list): for attachment in attachments: if attachment.get('fcontent'): continue - fid = attachment.get('fid') - if not fid: continue - - fname, fcontent = get_file(fid) - attachment.update({ - 'fname': fname, - 'fcontent': fcontent, - 'parent': msg_obj - }) - attachment.pop("fid", None) - add_attachment(**attachment) + fid = attachment.get("fid") + if fid: + fname, fcontent = get_file(fid) + attachment.update({ + 'fname': fname, + 'fcontent': fcontent, + 'parent': msg_obj + }) + attachment.pop("fid", None) + add_attachment(**attachment) + + elif attachment.get("print_format_attachment") == 1: + attachment.pop("print_format_attachment", None) + print_format_file = frappe.attach_print(**attachment) + print_format_file.update({"parent": msg_obj}) + add_attachment(**print_format_file) return msg_obj.as_string() From 426aec34e19ae4de380021d373c02b10e5c239a5 Mon Sep 17 00:00:00 2001 From: Zarrar Date: Fri, 27 Oct 2017 15:26:47 +0530 Subject: [PATCH 24/51] restore cancel doc in draft state (#4379) --- frappe/core/doctype/deleted_document/deleted_document.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/deleted_document/deleted_document.py b/frappe/core/doctype/deleted_document/deleted_document.py index e01e9b08bf..7dbd4d2645 100644 --- a/frappe/core/doctype/deleted_document/deleted_document.py +++ b/frappe/core/doctype/deleted_document/deleted_document.py @@ -17,7 +17,9 @@ def restore(name): try: doc.insert() except frappe.DocstatusTransitionError: - frappe.throw(_("Cannot restore Cancelled Document")) + frappe.msgprint(_("Cancelled Document restored as Draft")) + doc.docstatus = 0 + doc.insert() doc.add_comment('Edit', _('restored {0} as {1}').format(deleted.deleted_name, doc.name)) From 14be497683352f55fd658288260c2707e4b3f2f4 Mon Sep 17 00:00:00 2001 From: Zarrar Date: Fri, 27 Oct 2017 15:28:56 +0530 Subject: [PATCH 25/51] Fix tree (#4384) * treeview trigger on filter type Link * Make root visible if set_root is set * better functionality --- frappe/public/js/frappe/views/treeview.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/frappe/public/js/frappe/views/treeview.js b/frappe/public/js/frappe/views/treeview.js index 5b9e17b512..9f7bcd3efd 100644 --- a/frappe/public/js/frappe/views/treeview.js +++ b/frappe/public/js/frappe/views/treeview.js @@ -93,16 +93,20 @@ frappe.views.TreeView = Class.extend({ filter.default = frappe.route_options[filter.fieldname] } - me.page.add_field(filter).$input - .on('change', function() { - var val = $(this).val(); - if(val) { - me.args[$(this).attr("data-fieldname")] = val; - frappe.treeview_settings.filters = me.args; + filter.change = function() { + var val = this.get_value(); + if(!val && me.set_root){ + val = me.opts.root_label; + } + if(val){ + me.args[filter.fieldname] = val; + frappe.treeview_setting me.make_tree(); me.page.set_title(val); - } - }) + } + } + + me.page.add_field(filter); if (filter.default) { $("[data-fieldname='"+filter.fieldname+"']").trigger("change"); From ee33c36b55604fe26f31c19730b075c53d0e34ec Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Fri, 27 Oct 2017 15:31:50 +0530 Subject: [PATCH 26/51] UI fixes to Geolocation field (#4392) One field in section takes up 2 col width instead of 1 col --- frappe/public/js/frappe/form/controls/geolocation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/controls/geolocation.js b/frappe/public/js/frappe/form/controls/geolocation.js index 12f63a5c53..7dd0c7f64e 100644 --- a/frappe/public/js/frappe/form/controls/geolocation.js +++ b/frappe/public/js/frappe/form/controls/geolocation.js @@ -1,4 +1,4 @@ -frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlData.extend({ +frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlCode.extend({ make_wrapper() { // Create the elements for map area this._super(); From 355b1d98b6a16ba9bb332184d3c1bfb5a46c37d7 Mon Sep 17 00:00:00 2001 From: Britlog Date: Fri, 27 Oct 2017 12:02:02 +0200 Subject: [PATCH 27/51] Change mysqlclient package to avoid error in Debian (#4393) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3312d232b8..b9f1462bc5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ httplib2 jinja2 markdown2 markupsafe --e git+https://github.com/frappe/mysqlclient-python.git@1.3.12#egg=mysqlclient +mysqlclient>=1.3.12 python-geoip python-geoip-geolite2 python-dateutil From 1ca8707893f9dda7a5b8dfa7c633d4757cdcb5f2 Mon Sep 17 00:00:00 2001 From: Javier Wong Date: Fri, 27 Oct 2017 18:03:50 +0800 Subject: [PATCH 28/51] [fix] Workflow does not permit submission, duplication etc. (#4394) If the user's roles does not contain the "Allow Edit For" role, the workflow pervents the user from performing functions such as Submit, Duplicate etc. even though the user's other roles actually allow them to perform such functions. --- frappe/public/js/legacy/form.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/legacy/form.js b/frappe/public/js/legacy/form.js index b526f9b6c8..3be7434cb5 100644 --- a/frappe/public/js/legacy/form.js +++ b/frappe/public/js/legacy/form.js @@ -955,8 +955,8 @@ _f.Frm.prototype.validate_form_action = function(action, resolve) { // Allow submit, write, cancel and create permissions for read only documents that are assigned by // workflows if the user already have those permissions. This is to allow for users to // continue through the workflow states and to allow execution of functions like Duplicate. - if (!frappe.workflow.is_read_only(this.doctype, this.docname) && (perms["write"] || - perms["create"] || perms["submit"] || perms["cancel"])) { + if ((frappe.workflow.is_read_only(this.doctype, this.docname) && (perms["write"] || + perms["create"] || perms["submit"] || perms["cancel"])) || !frappe.workflow.is_read_only(this.doctype, this.docname)) { var allowed_for_workflow = true; } From 8c246365aad3ffaabd6354177dbb1bf12b6fd397 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 27 Oct 2017 15:34:04 +0530 Subject: [PATCH 29/51] Role Profile (#4395) * Added doctype Role Profile * Set role profile for user * Fetch roles from role profile * Added tests, updated user.js and role_profile.py * update_roles on add/delete moved to user controller * added py test * improved naming * validate_roles method added * [minor] style --- frappe/config/setup.py | 5 + frappe/core/doctype/role_profile/__init__.py | 0 .../core/doctype/role_profile/role_profile.js | 23 +++ .../doctype/role_profile/role_profile.json | 175 ++++++++++++++++++ .../core/doctype/role_profile/role_profile.py | 16 ++ .../doctype/role_profile/test_role_profile.js | 33 ++++ .../doctype/role_profile/test_role_profile.py | 24 +++ frappe/core/doctype/user/test_user.js | 2 +- .../user/test_user_with_role_profile.js | 35 ++++ frappe/core/doctype/user/user.js | 30 ++- frappe/core/doctype/user/user.json | 38 +++- frappe/core/doctype/user/user.py | 24 ++- frappe/public/js/frappe/roles_editor.js | 48 ++--- frappe/tests/ui/tests.txt | 2 + 14 files changed, 424 insertions(+), 31 deletions(-) create mode 100644 frappe/core/doctype/role_profile/__init__.py create mode 100644 frappe/core/doctype/role_profile/role_profile.js create mode 100644 frappe/core/doctype/role_profile/role_profile.json create mode 100644 frappe/core/doctype/role_profile/role_profile.py create mode 100644 frappe/core/doctype/role_profile/test_role_profile.js create mode 100644 frappe/core/doctype/role_profile/test_role_profile.py create mode 100644 frappe/core/doctype/user/test_user_with_role_profile.js diff --git a/frappe/config/setup.py b/frappe/config/setup.py index 55ed2dbd9e..142c8ab722 100644 --- a/frappe/config/setup.py +++ b/frappe/config/setup.py @@ -17,6 +17,11 @@ def get_data(): "type": "doctype", "name": "Role", "description": _("User Roles") + }, + { + "type": "doctype", + "name": "Role Profile", + "description": _("Role Profile") } ] }, diff --git a/frappe/core/doctype/role_profile/__init__.py b/frappe/core/doctype/role_profile/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/role_profile/role_profile.js b/frappe/core/doctype/role_profile/role_profile.js new file mode 100644 index 0000000000..09aead670a --- /dev/null +++ b/frappe/core/doctype/role_profile/role_profile.js @@ -0,0 +1,23 @@ +// Copyright (c) 2017, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Role Profile', { + setup: function(frm) { + if(has_common(frappe.user_roles, ["Administrator", "System Manager"])) { + if(!frm.roles_editor) { + var role_area = $('
    ') + .appendTo(frm.fields_dict.roles_html.wrapper); + frm.roles_editor = new frappe.RoleEditor(role_area, frm); + frm.roles_editor.show(); + } else { + frm.roles_editor.show(); + } + } + }, + + validate: function(frm) { + if(frm.roles_editor) { + frm.roles_editor.set_roles_in_table(); + } + } +}); diff --git a/frappe/core/doctype/role_profile/role_profile.json b/frappe/core/doctype/role_profile/role_profile.json new file mode 100644 index 0000000000..4b3f35aa57 --- /dev/null +++ b/frappe/core/doctype/role_profile/role_profile.json @@ -0,0 +1,175 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "role_profile", + "beta": 0, + "creation": "2017-08-31 04:16:38.764465", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "role_profile", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Role Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 1 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "roles_html", + "fieldtype": "HTML", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Roles HTML", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "roles", + "fieldtype": "Table", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Roles Assigned", + "length": 0, + "no_copy": 0, + "options": "Has Role", + "permlevel": 1, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-10-17 11:05:11.183066", + "modified_by": "Administrator", + "module": "Core", + "name": "Role Profile", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "role_profile", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/core/doctype/role_profile/role_profile.py b/frappe/core/doctype/role_profile/role_profile.py new file mode 100644 index 0000000000..4def834adb --- /dev/null +++ b/frappe/core/doctype/role_profile/role_profile.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe.model.document import Document + +class RoleProfile(Document): + def autoname(self): + """set name as Role Profile name""" + self.name = self.role_profile + + def on_update(self): + """ Changes in role_profile reflected across all its user """ + from frappe.core.doctype.user.user import update_roles + update_roles(self.name) diff --git a/frappe/core/doctype/role_profile/test_role_profile.js b/frappe/core/doctype/role_profile/test_role_profile.js new file mode 100644 index 0000000000..559a5fc0ac --- /dev/null +++ b/frappe/core/doctype/role_profile/test_role_profile.js @@ -0,0 +1,33 @@ +QUnit.module('Core'); + +QUnit.test("test: Role Profile", function (assert) { + let done = assert.async(); + + assert.expect(3); + + frappe.run_serially([ + // insert a new user + () => frappe.tests.make('Role Profile', [ + {role_profile: 'Test 2'} + ]), + + () => { + $('input.box')[0].checked = true; + $('input.box')[2].checked = true; + $('input.box')[4].checked = true; + cur_frm.save(); + }, + + () => frappe.timeout(1), + () => cur_frm.refresh(), + () => frappe.timeout(2), + () => { + assert.equal($('input.box')[0].checked, true); + assert.equal($('input.box')[2].checked, true); + assert.equal($('input.box')[4].checked, true); + }, + + () => done() + ]); + +}); \ No newline at end of file diff --git a/frappe/core/doctype/role_profile/test_role_profile.py b/frappe/core/doctype/role_profile/test_role_profile.py new file mode 100644 index 0000000000..d338bec9e2 --- /dev/null +++ b/frappe/core/doctype/role_profile/test_role_profile.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals +import frappe +import unittest + +class TestRoleProfile(unittest.TestCase): + def test_make_new_role_profile(self): + new_role_profile = frappe.get_doc(dict(doctype='Role Profile', role_profile='Test 1')).insert() + + self.assertEquals(new_role_profile.role_profile, 'Test 1') + + # add role + new_role_profile.append("roles", { + "role": '_Test Role 2' + }) + new_role_profile.save() + self.assertEquals(new_role_profile.roles[0].role, '_Test Role 2') + + # clear roles + new_role_profile.roles = [] + new_role_profile.save() + self.assertEquals(new_role_profile.roles, []) \ No newline at end of file diff --git a/frappe/core/doctype/user/test_user.js b/frappe/core/doctype/user/test_user.js index 52f9b7e42c..923a39c3a5 100644 --- a/frappe/core/doctype/user/test_user.js +++ b/frappe/core/doctype/user/test_user.js @@ -20,4 +20,4 @@ QUnit.test("test: User", function (assert) { () => done() ]); -}); +}); \ No newline at end of file diff --git a/frappe/core/doctype/user/test_user_with_role_profile.js b/frappe/core/doctype/user/test_user_with_role_profile.js new file mode 100644 index 0000000000..5fd6f72410 --- /dev/null +++ b/frappe/core/doctype/user/test_user_with_role_profile.js @@ -0,0 +1,35 @@ +QUnit.module('Core'); + +QUnit.test("test: Set role profile in user", function (assert) { + let done = assert.async(); + + assert.expect(3); + + frappe.run_serially([ + + // Insert a new user + () => frappe.tests.make('User', [ + {email: 'test@test2.com'}, + {first_name: 'Test 2'}, + {send_welcome_email: 0} + ]), + + () => frappe.timeout(2), + () => { + return frappe.tests.set_form_values(cur_frm, [ + {role_profile_name:'Test 2'} + ]); + }, + + () => cur_frm.save(), + () => frappe.timeout(2), + + () => { + assert.equal($('input.box')[0].checked, true); + assert.equal($('input.box')[2].checked, true); + assert.equal($('input.box')[4].checked, true); + }, + () => done() + ]); + +}); diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index ac443c8d69..ba78e21c08 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -17,24 +17,48 @@ frappe.ui.form.on('User', { } }, + + role_profile_name: function(frm) { + if(frm.doc.role_profile_name) { + frappe.call({ + "method": "frappe.core.doctype.user.user.get_role_profile", + args: { + role_profile: frm.doc.role_profile_name + }, + callback: function (data) { + frm.set_value("roles", []); + $.each(data.message || [], function(i, v){ + var d = frm.add_child("roles"); + d.role = v.role; + }); + frm.roles_editor.show(); + } + }); + } + }, + onload: function(frm) { + if(has_common(frappe.user_roles, ["Administrator", "System Manager"]) && !frm.doc.__islocal) { if(!frm.roles_editor) { var role_area = $('
    ') .appendTo(frm.fields_dict.roles_html.wrapper); - frm.roles_editor = new frappe.RoleEditor(role_area, frm); + frm.roles_editor = new frappe.RoleEditor(role_area, frm, true); var module_area = $('
    ') .appendTo(frm.fields_dict.modules_html.wrapper); frm.module_editor = new frappe.ModuleEditor(frm, module_area) - } else { + } + else { frm.roles_editor.show(); } } }, refresh: function(frm) { var doc = frm.doc; - + if(!frm.doc.islocal && !frm.roles_editor) { + frm.reload_doc(); + } if(doc.name===frappe.session.user && !doc.__unsaved && frappe.all_timezones && (doc.language || frappe.boot.user.language) diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 5ce4df115f..78f7163223 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -503,6 +503,37 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "role_profile_name", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Role Profile", + "length": 0, + "no_copy": 0, + "options": "Role Profile", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -1213,7 +1244,6 @@ "label": "Background Image", "length": 0, "no_copy": 0, - "options": "image", "permlevel": 0, "precision": "", "print_hide": 0, @@ -1389,7 +1419,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "description": "Enter default value fields (keys) and values. If you add multiple values for a field, the first one will be picked. These defaults are also used to set \"match\" permission rules. To see list of fields, go to \"Customize Form\".", + "description": "Enter default value fields (keys) and values. If you add multiple values for a field,the first one will be picked. These defaults are also used to set \"match\" permission rules. To see list of fields,go to \"Customize Form\".", "fieldname": "defaults", "fieldtype": "Table", "hidden": 1, @@ -1483,7 +1513,7 @@ "collapsible": 0, "columns": 0, "default": "System User", - "description": "If the user has any role checked, then the user becomes a \"System User\". \"System User\" has access to the desktop", + "description": "If the user has any role checked,then the user becomes a \"System User\". \"System User\" has access to the desktop", "fieldname": "user_type", "fieldtype": "Select", "hidden": 0, @@ -2002,7 +2032,7 @@ "istable": 0, "max_attachments": 5, "menu_index": 0, - "modified": "2017-10-09 15:33:43.818915", + "modified": "2017-10-17 11:06:05.570463", "modified_by": "Administrator", "module": "Core", "name": "User", diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 4fa1183e74..48f519b4e7 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -67,6 +67,7 @@ class User(Document): self.remove_disabled_roles() self.validate_user_email_inbox() ask_pass_update() + self.validate_roles() if self.language == "Loading...": self.language = None @@ -74,6 +75,12 @@ class User(Document): if (self.name not in ["Administrator", "Guest"]) and (not self.frappe_userid): self.frappe_userid = frappe.generate_hash(length=39) + def validate_roles(self): + if self.role_profile_name: + role_profile = frappe.get_doc('Role Profile', self.role_profile_name) + self.set('roles', []) + self.append_roles(*[role.role for role in role_profile.roles]) + def on_update(self): # clear new password self.validate_user_limit() @@ -84,6 +91,7 @@ class User(Document): if self.name not in ('Administrator', 'Guest') and not self.user_image: frappe.enqueue('frappe.core.doctype.user.user.update_gravatar', name=self.name) + def has_website_permission(self, ptype, verbose=False): """Returns true if current user is the session user""" return self.name == frappe.session.user @@ -983,4 +991,18 @@ def throttle_user_creation(): if frappe.flags.in_import: return if frappe.db.get_creation_count('User', 60) > 60: - frappe.throw(_('Throttled')) \ No newline at end of file + frappe.throw(_('Throttled')) + +@frappe.whitelist() +def get_role_profile(role_profile): + roles = frappe.get_doc('Role Profile', {'role_profile': role_profile}) + return roles.roles + +def update_roles(role_profile): + users = frappe.get_all('User', filters={'role_profile_name': role_profile}) + role_profile = frappe.get_doc('Role Profile', role_profile) + roles = [role.role for role in role_profile.roles] + for d in users: + user = frappe.get_doc('User', d) + user.set('roles', []) + user.add_roles(*roles) diff --git a/frappe/public/js/frappe/roles_editor.js b/frappe/public/js/frappe/roles_editor.js index 37904dbb74..dc69f5a3d2 100644 --- a/frappe/public/js/frappe/roles_editor.js +++ b/frappe/public/js/frappe/roles_editor.js @@ -1,8 +1,9 @@ frappe.RoleEditor = Class.extend({ - init: function(wrapper, frm) { + init: function(wrapper, frm, disable) { var me = this; this.frm = frm; this.wrapper = wrapper; + this.disable = disable; $(wrapper).html('
    ' + __("Loading") + '...
    ') return frappe.call({ method: 'frappe.core.doctype.user.user.get_all_roles', @@ -21,33 +22,35 @@ frappe.RoleEditor = Class.extend({ show_roles: function() { var me = this; $(this.wrapper).empty(); - var role_toolbar = $('

    \ -

    ').appendTo($(this.wrapper)); - - role_toolbar.find(".btn-add") - .html(__('Add all roles')) - .on("click", function () { - $(me.wrapper).find('input[type="checkbox"]').each(function (i, check) { - if (!$(check).is(":checked")) { - check.checked = true; - } + if(me.frm.doctype != 'User') { + var role_toolbar = $('

    \ +

    ').appendTo($(this.wrapper)); + + role_toolbar.find(".btn-add") + .html(__('Add all roles')) + .on("click", function () { + $(me.wrapper).find('input[type="checkbox"]').each(function (i, check) { + if (!$(check).is(":checked")) { + check.checked = true; + } + }); }); - }); - - role_toolbar.find(".btn-remove") - .html(__('Clear all roles')) - .on("click", function() { - $(me.wrapper).find('input[type="checkbox"]').each(function(i, check) { - if($(check).is(":checked")) { - check.checked = false; - } + + role_toolbar.find(".btn-remove") + .html(__('Clear all roles')) + .on("click", function() { + $(me.wrapper).find('input[type="checkbox"]').each(function(i, check) { + if($(check).is(":checked")) { + check.checked = false; + } + }); }); - }); + } $.each(this.roles, function(i, role) { $(me.wrapper).append(repl('
    \ - \ + \ %(role_display)s\
    ', {role_value: role,role_display:__(role)})); }); @@ -63,6 +66,7 @@ frappe.RoleEditor = Class.extend({ }, show: function() { var me = this; + $('.box').attr('disabled', this.disable); // uncheck all roles $(this.wrapper).find('input[type="checkbox"]') diff --git a/frappe/tests/ui/tests.txt b/frappe/tests/ui/tests.txt index 4526159cd4..076d1a515b 100644 --- a/frappe/tests/ui/tests.txt +++ b/frappe/tests/ui/tests.txt @@ -15,3 +15,5 @@ frappe/workflow/doctype/workflow/tests/test_workflow_create.js frappe/workflow/doctype/workflow/tests/test_workflow_test.js frappe/tests/ui/test_control_html.js frappe/tests/ui/test_control_geolocation.js +frappe/core/doctype/role_profile/test_role_profile.js +frappe/core/doctype/user/test_user_with_role_profile.js From 4fa7730845f5463b6af9fa9331cff1ab90505adb Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 27 Oct 2017 16:44:49 +0530 Subject: [PATCH 30/51] [fix] upload for customize form (#4397) --- frappe/custom/doctype/customize_form/customize_form.js | 1 + frappe/custom/doctype/customize_form/customize_form.py | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/frappe/custom/doctype/customize_form/customize_form.js b/frappe/custom/doctype/customize_form/customize_form.js index 8198f545da..227c6de1ee 100644 --- a/frappe/custom/doctype/customize_form/customize_form.js +++ b/frappe/custom/doctype/customize_form/customize_form.js @@ -176,6 +176,7 @@ frappe.customize_form.confirm = function(msg, frm) { frappe.msgprint(r.exc); } else { d.hide(); + frappe.show_alert({message:__('Customizations Reset'), indicator:'green'}); frappe.customize_form.clear_locals_and_refresh(frm); } } diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index ad583b77c6..f3eede9397 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -163,16 +163,13 @@ class CustomizeForm(Document): property_type=doctype_properties[property]) for df in self.get("fields"): - if df.get("__islocal"): - continue - meta_df = meta.get("fields", {"fieldname": df.fieldname}) if not meta_df or meta_df[0].get("is_custom_field"): continue for property in docfield_properties: - if property != "idx" and df.get(property) != meta_df[0].get(property): + if property != "idx" and (df.get(property) or '') != (meta_df[0].get(property) or ''): if property == "fieldtype": self.validate_fieldtype_change(df, meta_df[0].get(property), df.get(property)) @@ -329,6 +326,6 @@ class CustomizeForm(Document): return frappe.db.sql("""delete from `tabProperty Setter` where doc_type=%s - and ifnull(field_name, '')!='naming_series'""", self.doc_type) + and !(`field_name`='naming_series' and `property`='options')""", self.doc_type) frappe.clear_cache(doctype=self.doc_type) self.fetch_to_customize() From 56363859d2148cba0261a6177e7a26aa23412ab6 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 27 Oct 2017 16:49:45 +0530 Subject: [PATCH 31/51] [patch] reload doctype before setting domain --- frappe/patches.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/patches.txt b/frappe/patches.txt index e570a8d899..ef56831332 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -22,7 +22,7 @@ frappe.patches.v8_0.rename_page_role_to_has_role #2017-03-16 frappe.patches.v7_2.setup_custom_perms #2017-01-19 frappe.patches.v8_0.set_user_permission_for_page_and_report #2017-03-20 execute:frappe.reload_doc('core', 'doctype', 'role') #2017-05-23 -execute:frappe.reload_doc('core', 'doctype', 'user') +execute:frappe.reload_doc('core', 'doctype', 'user') #2017-10-27 execute:frappe.reload_doc('custom', 'doctype', 'custom_field') #2015-10-19 execute:frappe.reload_doc('core', 'doctype', 'page') #2013-13-26 execute:frappe.reload_doc('core', 'doctype', 'report') #2014-06-03 From 875b965d40ab4cb8b38fb5222830cce75f56adc9 Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Tue, 31 Oct 2017 12:53:11 +0530 Subject: [PATCH 32/51] use the new frappe charts :D (#4410) --- .eslintrc | 3 +- frappe/public/build.json | 5 +- frappe/public/css/charts.css | 284 --- frappe/public/js/frappe/form/dashboard.js | 4 +- frappe/public/js/frappe/ui/charts.js | 1556 ----------------- .../js/frappe/views/reports/grid_report.js | 2 +- .../js/frappe/views/reports/query_report.js | 2 +- frappe/public/js/lib/Chart.min.js | 39 - .../js/lib/frappe-charts/frappe-charts.min.js | 2 + frappe/public/less/charts.less | 327 ---- 10 files changed, 10 insertions(+), 2214 deletions(-) delete mode 100644 frappe/public/css/charts.css delete mode 100644 frappe/public/js/frappe/ui/charts.js delete mode 100755 frappe/public/js/lib/Chart.min.js create mode 100644 frappe/public/js/lib/frappe-charts/frappe-charts.min.js delete mode 100644 frappe/public/less/charts.less diff --git a/.eslintrc b/.eslintrc index a956ffdd47..d7cd742651 100644 --- a/.eslintrc +++ b/.eslintrc @@ -120,6 +120,7 @@ "get_url_arg": true, "QUnit": true, "JsBarcode": true, - "L": true + "L": true, + "Chart": true } } diff --git a/frappe/public/build.json b/frappe/public/build.json index 7e02a8fe16..097a6f043a 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -119,8 +119,7 @@ "public/css/desktop.css", "public/css/form.css", "public/css/mobile.css", - "public/css/kanban.css", - "public/css/charts.css" + "public/css/kanban.css" ], "css/frappe-rtl.css": [ "public/css/bootstrap-rtl.css", @@ -142,6 +141,7 @@ "public/js/frappe/translate.js", "public/js/lib/datepicker/datepicker.min.js", "public/js/lib/datepicker/locale-all.js", + "public/js/lib/frappe-charts/frappe-charts.min.js", "public/js/lib/jquery.jrumble.min.js", "public/js/lib/webcam.min.js", "public/js/lib/leaflet/leaflet.js", @@ -240,7 +240,6 @@ "public/js/frappe/desk.js", "public/js/frappe/query_string.js", - "public/js/frappe/ui/charts.js", "public/js/frappe/ui/comment.js", "public/js/frappe/misc/rating_icons.html", diff --git a/frappe/public/css/charts.css b/frappe/public/css/charts.css deleted file mode 100644 index f5d279568a..0000000000 --- a/frappe/public/css/charts.css +++ /dev/null @@ -1,284 +0,0 @@ -/* charts */ -.chart-container .graph-focus-margin { - margin: 0px 5%; -} -.chart-container .graphics { - margin-top: 10px; - padding-top: 10px; - padding-bottom: 10px; - position: relative; -} -.chart-container .graph-stats-group { - display: flex; - justify-content: space-around; - flex: 1; -} -.chart-container .graph-stats-container { - display: flex; - justify-content: space-around; - padding-top: 10px; -} -.chart-container .graph-stats-container .stats { - padding-bottom: 15px; -} -.chart-container .graph-stats-container .stats-title { - color: #8D99A6; -} -.chart-container .graph-stats-container .stats-value { - font-size: 20px; - font-weight: 300; -} -.chart-container .graph-stats-container .stats-description { - font-size: 12px; - color: #8D99A6; -} -.chart-container .graph-stats-container .graph-data .stats-value { - color: #98d85b; -} -.chart-container .axis, -.chart-container .chart-label { - font-size: 10px; - fill: #555b51; -} -.chart-container .axis line, -.chart-container .chart-label line { - stroke: rgba(27, 31, 35, 0.2); -} -.chart-container .percentage-graph .progress { - margin-bottom: 0px; -} -.chart-container .data-points circle { - stroke: #fff; - stroke-width: 2; -} -.chart-container .data-points path { - fill: none; - stroke-opacity: 1; - stroke-width: 2px; -} -.chart-container line.dashed { - stroke-dasharray: 5,3; -} -.chart-container .tick.x-axis-label { - display: block; -} -.chart-container .tick .specific-value { - text-anchor: start; -} -.chart-container .tick .y-value-text { - text-anchor: end; -} -.chart-container .tick .x-value-text { - text-anchor: middle; -} -.graph-svg-tip { - position: absolute; - z-index: 99999; - padding: 10px; - font-size: 12px; - color: #959da5; - text-align: center; - background: rgba(0, 0, 0, 0.8); - border-radius: 3px; -} -.graph-svg-tip.comparison { - padding: 0; - text-align: left; - pointer-events: none; -} -.graph-svg-tip.comparison .title { - display: block; - padding: 10px; - margin: 0; - font-weight: 600; - line-height: 1; - pointer-events: none; -} -.graph-svg-tip.comparison ul { - margin: 0; - white-space: nowrap; - list-style: none; -} -.graph-svg-tip.comparison li { - display: inline-block; - padding: 5px 10px; -} -.graph-svg-tip ul, -.graph-svg-tip ol { - padding-left: 0; - display: flex; -} -.graph-svg-tip ul.data-point-list li { - min-width: 90px; - flex: 1; -} -.graph-svg-tip strong { - color: #dfe2e5; -} -.graph-svg-tip .svg-pointer { - position: absolute; - bottom: -10px; - left: 50%; - width: 5px; - height: 5px; - margin: 0 0 0 -5px; - content: " "; - border: 5px solid transparent; - border-top-color: rgba(0, 0, 0, 0.8); -} -.stroke.grey { - stroke: #F0F4F7; -} -.stroke.blue { - stroke: #5e64ff; -} -.stroke.red { - stroke: #ff5858; -} -.stroke.light-green { - stroke: #98d85b; -} -.stroke.lightgreen { - stroke: #98d85b; -} -.stroke.green { - stroke: #28a745; -} -.stroke.orange { - stroke: #ffa00a; -} -.stroke.purple { - stroke: #743ee2; -} -.stroke.darkgrey { - stroke: #b8c2cc; -} -.stroke.black { - stroke: #36414C; -} -.stroke.yellow { - stroke: #FEEF72; -} -.stroke.light-blue { - stroke: #7CD6FD; -} -.stroke.lightblue { - stroke: #7CD6FD; -} -.fill.grey { - fill: #F0F4F7; -} -.fill.blue { - fill: #5e64ff; -} -.fill.red { - fill: #ff5858; -} -.fill.light-green { - fill: #98d85b; -} -.fill.lightgreen { - fill: #98d85b; -} -.fill.green { - fill: #28a745; -} -.fill.orange { - fill: #ffa00a; -} -.fill.purple { - fill: #743ee2; -} -.fill.darkgrey { - fill: #b8c2cc; -} -.fill.black { - fill: #36414C; -} -.fill.yellow { - fill: #FEEF72; -} -.fill.light-blue { - fill: #7CD6FD; -} -.fill.lightblue { - fill: #7CD6FD; -} -.background.grey { - background: #F0F4F7; -} -.background.blue { - background: #5e64ff; -} -.background.red { - background: #ff5858; -} -.background.light-green { - background: #98d85b; -} -.background.lightgreen { - background: #98d85b; -} -.background.green { - background: #28a745; -} -.background.orange { - background: #ffa00a; -} -.background.purple { - background: #743ee2; -} -.background.darkgrey { - background: #b8c2cc; -} -.background.black { - background: #36414C; -} -.background.yellow { - background: #FEEF72; -} -.background.light-blue { - background: #7CD6FD; -} -.background.lightblue { - background: #7CD6FD; -} -.border-top.grey { - border-top: 3px solid #F0F4F7; -} -.border-top.blue { - border-top: 3px solid #5e64ff; -} -.border-top.red { - border-top: 3px solid #ff5858; -} -.border-top.light-green { - border-top: 3px solid #98d85b; -} -.border-top.lightgreen { - border-top: 3px solid #98d85b; -} -.border-top.green { - border-top: 3px solid #28a745; -} -.border-top.orange { - border-top: 3px solid #ffa00a; -} -.border-top.purple { - border-top: 3px solid #743ee2; -} -.border-top.darkgrey { - border-top: 3px solid #b8c2cc; -} -.border-top.black { - border-top: 3px solid #36414C; -} -.border-top.yellow { - border-top: 3px solid #FEEF72; -} -.border-top.light-blue { - border-top: 3px solid #7CD6FD; -} -.border-top.lightblue { - border-top: 3px solid #7CD6FD; -} diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js index 9c84a07af0..caa3323d46 100644 --- a/frappe/public/js/frappe/form/dashboard.js +++ b/frappe/public/js/frappe/form/dashboard.js @@ -334,7 +334,7 @@ frappe.ui.form.Dashboard = Class.extend({ // heatmap render_heatmap: function() { if(!this.heatmap) { - this.heatmap = new frappe.chart.FrappeChart({ + this.heatmap = new Chart({ parent: "#heatmap-" + frappe.model.scrub(this.frm.doctype), type: 'heatmap', height: 100, @@ -412,7 +412,7 @@ frappe.ui.form.Dashboard = Class.extend({ }); this.show(); - this.chart = new frappe.chart.FrappeChart(args); + this.chart = new Chart(args); if(!this.chart) { this.hide(); } diff --git a/frappe/public/js/frappe/ui/charts.js b/frappe/public/js/frappe/ui/charts.js deleted file mode 100644 index 8927f2e21b..0000000000 --- a/frappe/public/js/frappe/ui/charts.js +++ /dev/null @@ -1,1556 +0,0 @@ -// specific_values = [ -// { -// title: "Average", -// line_type: "dashed", // "dashed" or "solid" -// value: 10 -// }, - -// summary = [ -// { -// title: "Total", -// color: 'blue', // Indicator colors: 'grey', 'blue', 'red', 'green', 'orange', -// // 'purple', 'darkgrey', 'black', 'yellow', 'lightblue' -// value: 80 -// } -// ] - -// Validate all arguments, check passed data format, set defaults - -frappe.provide("frappe.chart"); - -frappe.chart.FrappeChart = class { - constructor({ - parent = "", - height = 240, - - title = '', subtitle = '', - - data = {}, - format_lambdas = {}, - - specific_values = [], - summary = [], - - is_navigable = 0, - - type = '' - }) { - if(Object.getPrototypeOf(this) === frappe.chart.FrappeChart.prototype) { - if(type === 'line') { - return new frappe.chart.LineChart(arguments[0]); - } else if(type === 'bar') { - return new frappe.chart.BarChart(arguments[0]); - } else if(type === 'percentage') { - return new frappe.chart.PercentageChart(arguments[0]); - } else if(type === 'heatmap') { - return new frappe.chart.HeatMap(arguments[0]); - } - } - - this.parent = document.querySelector(parent); - this.title = title; - this.subtitle = subtitle; - - this.data = data; - this.format_lambdas = format_lambdas; - - this.specific_values = specific_values; - this.summary = summary; - - this.is_navigable = is_navigable; - if(this.is_navigable) { - this.current_index = 0; - } - - this.set_margins(height); - } - - set_margins(height) { - this.base_height = height; - this.height = height - 40; - this.translate_x = 60; - this.translate_y = 10; - } - - setup() { - this.bind_window_events(); - this.refresh(); - } - - bind_window_events() { - window.addEventListener('resize', () => this.refresh()); - window.addEventListener('orientationchange', () => this.refresh()); - } - - refresh() { - this.setup_base_values(); - this.set_width(); - - this.setup_container(); - this.setup_components(); - - this.setup_values(); - this.setup_utils(); - - this.make_graph_components(); - this.make_tooltip(); - - if(this.summary.length > 0) { - this.show_custom_summary(); - } else { - this.show_summary(); - } - - if(this.is_navigable) { - this.setup_navigation(); - } - } - - set_width() { - let special_values_width = 0; - this.specific_values.map(val => { - if(this.get_strwidth(val.title) > special_values_width) { - special_values_width = this.get_strwidth(val.title); - } - }); - this.base_width = this.parent.offsetWidth - special_values_width; - this.width = this.base_width - this.translate_x * 2; - } - - setup_base_values() {} - - setup_container() { - this.container = $$.create('div', { - className: 'chart-container', - innerHTML: `
    ${this.title}
    -
    ${this.subtitle}
    -
    -
    ` - }); - - // Chart needs a dedicated parent element - this.parent.innerHTML = ''; - this.parent.appendChild(this.container); - - this.chart_wrapper = this.container.querySelector('.frappe-chart'); - // this.chart_wrapper.appendChild(); - - this.make_chart_area(); - this.make_draw_area(); - - this.stats_wrapper = this.container.querySelector('.graph-stats-container'); - } - - make_chart_area() { - this.svg = $$.createSVG('svg', { - className: 'chart', - inside: this.chart_wrapper, - width: this.base_width, - height: this.base_height - }); - - return this.svg; - } - - make_draw_area() { - this.draw_area = $$.createSVG("g", { - className: this.type, - inside: this.svg, - transform: `translate(${this.translate_x}, ${this.translate_y})` - }); - } - - setup_components() { - this.svg_units_group = $$.createSVG('g', { - className: 'data-points', - inside: this.draw_area - }); - } - - make_tooltip() { - this.tip = new frappe.chart.SvgTip({ - parent: this.chart_wrapper, - }); - this.bind_tooltip(); - } - - - show_summary() {} - show_custom_summary() { - this.summary.map(d => { - let stats = $$.create('div', { - className: 'stats', - innerHTML: `${d.title}: ${d.value}` - }); - this.stats_wrapper.appendChild(stats); - }); - } - - setup_navigation() { - this.make_overlay(); - this.bind_overlay(); - document.onkeydown = (e) => { - e = e || window.event; - - if (e.keyCode == '37') { - this.on_left_arrow(); - } else if (e.keyCode == '39') { - this.on_right_arrow(); - } else if (e.keyCode == '38') { - this.on_up_arrow(); - } else if (e.keyCode == '40') { - this.on_down_arrow(); - } else if (e.keyCode == '13') { - this.on_enter_key(); - } - }; - } - - make_overlay() {} - bind_overlay() {} - - on_left_arrow() {} - on_right_arrow() {} - on_up_arrow() {} - on_down_arrow() {} - on_enter_key() {} - - get_data_point(index=this.current_index) { - // check for length - let data_point = { - index: index - }; - let y = this.y[0]; - ['svg_units', 'y_tops', 'values'].map(key => { - let data_key = key.slice(0, key.length-1); - data_point[data_key] = y[key][index]; - }); - data_point.label = this.x[index]; - return data_point; - } - - update_current_data_point(index) { - if(index < 0) index = 0; - if(index >= this.x.length) index = this.x.length - 1; - if(index === this.current_index) return; - this.current_index = index; - $$.fire(this.parent, "data-select", this.get_data_point()); - } - - // Helpers - get_strwidth(string) { - return string.length * 8; - } - - // Objects - setup_utils() { - this.draw = { - 'bar': (x, y, args, color, index) => { - let total_width = this.avg_unit_width - args.space_width; - let start_x = x - total_width/2; - - let width = total_width / args.no_of_datasets; - let current_x = start_x + width * index; - if(y == this.height) { - y = this.height * 0.98; - } - return $$.createSVG('rect', { - className: `bar mini fill ${color}`, - x: current_x, - y: y, - width: width, - height: this.height - y - }); - - }, - 'dot': (x, y, args, color) => { - return $$.createSVG('circle', { - className: `fill ${color}`, - cx: x, - cy: y, - r: args.radius - }); - } - }; - - this.animate = { - 'bar': (bar, new_y, args) => { - return [bar, {height: args.new_height, y: new_y}, 300, "easein"]; - // bar.animate({height: args.new_height, y: new_y}, 300, mina.easein); - }, - 'dot': (dot, new_y) => { - return [dot, {cy: new_y}, 300, "easein"]; - // dot.animate({cy: new_y}, 300, mina.easein); - } - }; - } -} - -frappe.chart.AxisChart = class AxisChart extends frappe.chart.FrappeChart { - constructor(args) { - super(args); - - this.x = this.data.labels; - this.y = this.data.datasets; - - this.get_x_label = this.format_lambdas.x_label; - this.get_y_label = this.format_lambdas.y_label; - this.get_x_tooltip = this.format_lambdas.x_tooltip; - this.get_y_tooltip = this.format_lambdas.y_tooltip; - - this.colors = ['lightblue', 'purple', 'blue', 'green', 'lightgreen', - 'yellow', 'orange', 'red']; - } - - setup_values() { - this.data.datasets.map(d => { - d.values = d.values.map(val => (!isNaN(val) ? val : 0)); - }); - this.setup_x(); - this.setup_y(); - } - - setup_x() { - this.set_avg_unit_width_and_x_offset(); - this.x_axis_values = this.x.map((d, i) => frappe.chart.utils.float_2(this.x_offset + i * this.avg_unit_width)); - } - - setup_y() { - this.setup_metrics(); - this.y_axis_values = this.get_y_axis_values(this.upper_limit, this.parts); - } - - setup_components() { - this.y_axis_group = $$.createSVG('g', {className: 'y axis', inside: this.draw_area}); - this.x_axis_group = $$.createSVG('g', {className: 'x axis', inside: this.draw_area}); - this.specific_y_lines = $$.createSVG('g', {className: 'specific axis', inside: this.draw_area}); - super.setup_components(); - } - - make_graph_components() { - this.make_y_axis(); - this.make_x_axis(); - this.draw_graph(); - this.make_y_specifics(); - } - - // make HORIZONTAL lines for y values - make_y_axis() { - if(this.y_axis_group.textContent) { - // animate from old to new, both elemnets - } else { - // only new - } - - this.y_axis_group.textContent = ''; - - let width, text_end_at = -9, label_class = '', start_at = 0; - if(this.y_axis_mode === 'span') { // long spanning lines - width = this.width + 6; - start_at = -6; - } else if(this.y_axis_mode === 'tick'){ // short label lines - width = -6; - label_class = 'y-axis-label'; - } - - this.y_axis_values.map((point) => { - let line = $$.createSVG('line', { - x1: start_at, - x2: width, - y1: 0, - y2: 0 - }); - let text = $$.createSVG('text', { - className: 'y-value-text', - x: text_end_at, - y: 0, - dy: '.32em', - innerHTML: point+"" - }); - - let y_level = $$.createSVG('g', { - className: `tick ${label_class}`, - transform: `translate(0, ${this.height - point * this.multiplier })` - }); - - y_level.appendChild(line); - y_level.appendChild(text); - - this.y_axis_group.appendChild(y_level); - }); - } - - // make VERTICAL lines for x values - make_x_axis() { - let start_at, height, text_start_at, label_class = ''; - if(this.x_axis_mode === 'span') { // long spanning lines - start_at = -7; - height = this.height + 15; - text_start_at = this.height + 25; - } else if(this.x_axis_mode === 'tick'){ // short label lines - start_at = this.height; - height = 6; - text_start_at = 9; - label_class = 'x-axis-label'; - } - - this.x_axis_group.setAttribute('transform', `translate(0,${start_at})`); - - this.x.map((point, i) => { - let allowed_space = this.avg_unit_width * 1.5; - if(this.get_strwidth(point) > allowed_space) { - let allowed_letters = allowed_space / 8; - point = point.slice(0, allowed_letters-3) + " ..."; - } - - let line = $$.createSVG('line', { - x1: 0, - x2: 0, - y1: 0, - y2: height - }); - let text = $$.createSVG('text', { - className: 'x-value-text', - x: 0, - y: text_start_at, - dy: '.71em', - innerHTML: point - }); - - let x_level = $$.createSVG('g', { - className: `tick ${label_class}`, - transform: `translate(${ this.x_axis_values[i] }, 0)` - }); - - x_level.appendChild(line); - x_level.appendChild(text); - - this.x_axis_group.appendChild(x_level); - }); - } - - draw_graph() { - // TODO: Don't animate on refresh - let data = []; - this.svg_units_group.textContent = ''; - this.y.map((d, i) => { - // Anim: Don't draw initial values, store them and update later - d.y_tops = new Array(d.values.length).fill(this.height); // no value - data.push({values: d.values}); - d.svg_units = []; - - this.make_new_units_for_dataset(d.y_tops, d.color || this.colors[i], i); - this.make_path && this.make_path(d, d.color || this.colors[i]); - }); - - // Data points - // this.calc_all_y_tops(); - // this.calc_min_tops(); - - setTimeout(() => { - this.update_values(data); - }, 500); - } - - setup_navigation() { - // Hack: defer nav till initial update_values - setTimeout(() => { - super.setup_navigation(); - }, 1000); - } - - make_new_units_for_dataset(y_values, color, dataset_index) { - this.y[dataset_index].svg_units = []; - - let d = this.unit_args; - y_values.map((y, i) => { - let data_unit = this.draw[d.type]( - this.x_axis_values[i], - y, - d.args, - color, - dataset_index - ); - this.svg_units_group.appendChild(data_unit); - this.y[dataset_index].svg_units.push(data_unit); - }); - } - - make_y_specifics() { - this.specific_values.map(d => { - let line = $$.createSVG('line', { - className: d.line_type === "dashed" ? "dashed": "", - x1: 0, - x2: this.width, - y1: 0, - y2: 0 - }); - - let text = $$.createSVG('text', { - className: 'specific-value', - x: this.width + 5, - y: 0, - dy: '.32em', - innerHTML: d.title.toUpperCase() - }); - - let specific_y_level = $$.createSVG('g', { - className: `tick`, - transform: `translate(0, ${this.height - d.value * this.multiplier })` - }); - - specific_y_level.appendChild(line); - specific_y_level.appendChild(text); - - this.specific_y_lines.appendChild(specific_y_level); - }); - } - - bind_tooltip() { - // should be w.r.t. this.parent, but will have to take care of - // all the elements and padding, margins on top - this.chart_wrapper.addEventListener('mousemove', (e) => { - let rect = this.chart_wrapper.getBoundingClientRect(); - let offset = { - top: rect.top + document.body.scrollTop, - left: rect.left + document.body.scrollLeft - } - let relX = e.pageX - offset.left - this.translate_x; - let relY = e.pageY - offset.top - this.translate_y; - - if(relY < this.height + this.translate_y * 2) { - this.map_tooltip_x_position_and_show(relX); - } else { - this.tip.hide_tip(); - } - }); - } - - map_tooltip_x_position_and_show(relX) { - for(var i=this.x_axis_values.length - 1; i >= 0 ; i--) { - let x_val = this.x_axis_values[i]; - // let delta = i === 0 ? this.avg_unit_width : x_val - this.x_axis_values[i-1]; - if(relX > x_val - this.avg_unit_width/2) { - let x = x_val + this.translate_x - 0.5; - let y = this.y_min_tops[i] + this.translate_y + 4; // adjustment - - let title = this.x.formatted && this.x.formatted.length>0 - ? this.x.formatted[i] : this.x[i]; - let values = this.y.map((set, j) => { - return { - title: set.title, - value: set.formatted ? set.formatted[i] : set.values[i], - color: set.color || this.colors[j], - } - }); - - this.tip.set_values(x, y, title, '', values); - this.tip.show_tip(); - break; - } - } - } - - // API - update_values(new_y) { - // Just update values prop, setup_y() will do the rest - this.y.map((d, i) => {d.values = new_y[i].values;}); - - let old_upper_limit = this.upper_limit; - this.setup_y(); - if(old_upper_limit !== this.upper_limit){ - this.make_y_axis(); - } - - let elements_to_animate = []; - elements_to_animate = this.animate_for_equilength_data(elements_to_animate); - - // create new x,y pair string and animate path - if(this.y[0].path) { - this.y.map((e, i) => { - let new_points_list = e.y_tops.map((y, i) => (this.x_axis_values[i] + ',' + y)); - let new_path_str = "M"+new_points_list.join("L"); - let args = [{unit:this.y[i].path, object: this.y[i], key:'path'}, {d:new_path_str}, 300, "easein"]; - elements_to_animate.push(args); - }); - } - - // elements_to_animate = elements_to_animate.concat(this.update_y_axis()); - let anim_svg = $$.runSVGAnimation(this.svg, elements_to_animate); - this.chart_wrapper.innerHTML = ''; - this.chart_wrapper.appendChild(anim_svg); - - // Replace the new svg (data has long been replaced) - setTimeout(() => { - this.chart_wrapper.innerHTML = ''; - this.chart_wrapper.appendChild(this.svg); - }, 250); - } - - update_y_axis() { - let elements = []; - - return elements; - } - - update_x_axis() { - // update - } - - animate_for_equilength_data(elements_to_animate) { - this.y.map((d) => { - d.y_tops = d.values.map(val => frappe.chart.utils.float_2(this.height - val * this.multiplier)); - d.svg_units.map((unit, j) => { - elements_to_animate.push(this.animate[this.unit_args.type]( - {unit:unit, array:d.svg_units, index: j}, // unit, with info to replace from data - d.y_tops[j], - {new_height: this.height - d.y_tops[j]} - )); - }); - }); - this.calc_min_tops(); - return elements_to_animate; - } - - add_data_point(data_point) { - this.x.push(data_point.label); - this.y.values.push(); - } - - // Helpers - get_upper_limit_and_parts(array) { - let max_val = parseInt(Math.max(...array)); - if((max_val+"").length <= 1) { - return [10, 5]; - } else { - let multiplier = Math.pow(10, ((max_val+"").length - 1)); - let significant = Math.ceil(max_val/multiplier); - if(significant % 2 !== 0) significant++; - let parts = (significant < 5) ? significant : significant/2; - return [significant * multiplier, parts]; - } - } - - get_y_axis_values(upper_limit, parts) { - let y_axis = []; - for(var i = 0; i <= parts; i++){ - y_axis.push(upper_limit / parts * i); - } - return y_axis; - } - - set_avg_unit_width_and_x_offset() { - this.avg_unit_width = this.width/(this.x.length - 1); - this.x_offset = 0; - } - - setup_metrics() { - // Metrics: upper limit, no. of parts, multiplier - let values = this.get_all_y_values(); - [this.upper_limit, this.parts] = this.get_upper_limit_and_parts(values); - this.multiplier = this.height / this.upper_limit; - } - - get_all_y_values() { - let all_values = []; - this.y.map(d => { - all_values = all_values.concat(d.values); - }); - return all_values.concat(this.specific_values.map(d => d.value)); - } - - calc_all_y_tops() { - this.y.map(d => { - d.y_tops = d.values.map( val => frappe.chart.utils.float_2(this.height - val * this.multiplier)); - }); - } - - calc_min_tops() { - this.y_min_tops = new Array(this.x_axis_values.length).fill(9999); - this.y.map(d => { - d.y_tops.map( (y_top, i) => { - if(y_top < this.y_min_tops[i]) { - this.y_min_tops[i] = y_top; - } - }); - }); - } -} - -frappe.chart.BarChart = class BarChart extends frappe.chart.AxisChart { - constructor() { - super(arguments[0]); - - this.type = 'bar-graph'; - this.setup(); - } - - setup_values() { - super.setup_values(); - this.x_offset = this.avg_unit_width; - this.y_axis_mode = 'span'; - this.x_axis_mode = 'tick'; - this.unit_args = { - type: 'bar', - args: { - space_width: this.avg_unit_width/2, - no_of_datasets: this.y.length - } - }; - } - - make_overlay() { - // Just make one out of the first element - let unit = this.y[0].svg_units[0]; - - this.overlay = unit.cloneNode(); - this.overlay.style.fill = '#000000'; - this.overlay.style.opacity = '0.4'; - this.draw_area.appendChild(this.overlay); - } - - bind_overlay() { - // on event, update overlay - this.parent.addEventListener('data-select', (e) => { - this.update_overlay(e.svg_unit); - }); - } - - update_overlay(unit) { - let attributes = []; - Object.keys(unit.attributes).map(index => { - attributes.push(unit.attributes[index]); - }); - - attributes.filter(attr => attr.specified).map(attr => { - this.overlay.setAttribute(attr.name, attr.nodeValue); - }); - } - - on_left_arrow() { - this.update_current_data_point(this.current_index - 1); - } - - on_right_arrow() { - this.update_current_data_point(this.current_index + 1); - } - - set_avg_unit_width_and_x_offset() { - this.avg_unit_width = this.width/(this.x.length + 1); - this.x_offset = this.avg_unit_width; - } -} - -frappe.chart.LineChart = class LineChart extends frappe.chart.AxisChart { - constructor(args) { - super(args); - if(Object.getPrototypeOf(this) !== frappe.chart.LineChart.prototype) { - return; - } - - this.type = 'line-graph'; - - this.setup(); - } - - setup_values() { - super.setup_values(); - this.y_axis_mode = 'span'; - this.x_axis_mode = 'span'; - this.unit_args = { - type: 'dot', - args: { radius: 4 } - }; - } - - make_path(d, color) { - let points_list = d.y_tops.map((y, i) => (this.x_axis_values[i] + ',' + y)); - let path_str = "M"+points_list.join("L"); - - d.path = $$.createSVG('path', { - className: `stroke ${color}`, - d: path_str - }); - - this.svg_units_group.prepend(d.path); - } -} - -frappe.chart.RegionChart = class RegionChart extends frappe.chart.LineChart { - constructor(args) { - super(args); - - this.type = 'region-graph'; - this.region_fill = 1; - this.setup(); - } -} - -frappe.chart.PercentageChart = class PercentageChart extends frappe.chart.FrappeChart { - constructor(args) { - super(args); - - this.x = this.data.labels; - this.y = this.data.datasets; - - this.get_x_label = this.format_lambdas.x_label; - this.get_y_label = this.format_lambdas.y_label; - this.get_x_tooltip = this.format_lambdas.x_tooltip; - this.get_y_tooltip = this.format_lambdas.y_tooltip; - - this.setup(); - } - - make_chart_area() { - this.chart_wrapper.className += ' ' + 'graph-focus-margin'; - this.chart_wrapper.style.marginTop = '45px'; - - this.stats_wrapper.className += ' ' + 'graph-focus-margin'; - this.stats_wrapper.style.marginBottom = '30px'; - this.stats_wrapper.style.paddingTop = '0px'; - - this.chart_div = $$.create('div', { - className: 'div', - inside: this.chart_wrapper, - width: this.base_width, - height: this.base_height - }); - - this.chart = $$.create('div', { - className: 'progress-chart', - inside: this.chart_div - }); - } - - setup_values() { - this.x.totals = this.x.map((d, i) => { - let total = 0; - this.y.map(e => { - total += e.values[i]; - }); - return total; - }); - - if(!this.x.colors) { - this.x.colors = ['green', 'blue', 'purple', 'red', 'orange', - 'yellow', 'lightblue', 'lightgreen']; - } - } - - setup_utils() { } - setup_components() { - this.percentage_bar = $$.create('div', { - className: 'progress', - inside: this.chart - }); - } - - make_graph_components() { - this.grand_total = this.x.totals.reduce((a, b) => a + b, 0); - this.x.units = []; - this.x.totals.map((total, i) => { - let part = $$.create('div', { - className: `progress-bar background ${this.x.colors[i]}`, - style: `width: ${total*100/this.grand_total}%`, - inside: this.percentage_bar - }); - this.x.units.push(part); - }); - } - - bind_tooltip() { - this.x.units.map((part, i) => { - part.addEventListener('mouseenter', () => { - let g_off = this.chart_wrapper.offset(), p_off = part.offset(); - - let x = p_off.left - g_off.left + part.offsetWidth/2; - let y = p_off.top - g_off.top - 6; - let title = (this.x.formatted && this.x.formatted.length>0 - ? this.x.formatted[i] : this.x[i]) + ': '; - let percent = (this.x.totals[i]*100/this.grand_total).toFixed(1); - - this.tip.set_values(x, y, title, percent); - this.tip.show_tip(); - }); - }); - } - - show_summary() { - let x_values = this.x.formatted && this.x.formatted.length > 0 - ? this.x.formatted : this.x; - this.x.totals.map((d, i) => { - if(d) { - let stats = $$.create('div', { - className: 'stats', - inside: this.stats_wrapper - }); - stats.innerHTML = ` - ${x_values[i]}: - ${d} - `; - } - }); - } -} - -frappe.chart.HeatMap = class HeatMap extends frappe.chart.FrappeChart { - constructor({ - start = new Date(moment().subtract(1, 'year').toDate()), - domain = '', - subdomain = '', - data = {}, - discrete_domains = 0, - count_label = '' - }) { - super(arguments[0]); - - this.type = 'heatmap'; - - this.domain = domain; - this.subdomain = subdomain; - this.start = start; - this.data = data; - this.discrete_domains = discrete_domains; - this.count_label = count_label; - - this.legend_colors = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']; - - this.translate_x = 0; - this.setup(); - } - - setup_base_values() { - this.today = new Date(); - - if(!this.start) { - this.start = new Date(); - this.start.setFullYear( this.start.getFullYear() - 1 ); - } - this.first_week_start = new Date(this.start.toDateString()); - this.last_week_start = new Date(this.today.toDateString()); - if(this.first_week_start.getDay() !== 7) { - this.add_days(this.first_week_start, (-1) * this.first_week_start.getDay()); - } - if(this.last_week_start.getDay() !== 7) { - this.add_days(this.last_week_start, (-1) * this.last_week_start.getDay()); - } - this.no_of_cols = this.get_weeks_between(this.first_week_start + '', this.last_week_start + '') + 1; - } - - set_width() { - this.base_width = (this.no_of_cols) * 12; - } - - setup_components() { - this.domain_label_group = $$.createSVG("g", { - className: "domain-label-group chart-label", - inside: this.draw_area - }); - this.data_groups = $$.createSVG("g", { - className: "data-groups", - inside: this.draw_area, - transform: `translate(0, 20)` - }); - } - - setup_values() { - this.domain_label_group.textContent = ''; - this.data_groups.textContent = ''; - this.distribution = this.get_distribution(this.data, this.legend_colors); - this.month_names = ["January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December" - ]; - - this.render_all_weeks_and_store_x_values(this.no_of_cols); - } - - render_all_weeks_and_store_x_values(no_of_weeks) { - let current_week_sunday = new Date(this.first_week_start); - this.week_col = 0; - this.current_month = current_week_sunday.getMonth(); - - this.months = [this.current_month + '']; - this.month_weeks = {}, this.month_start_points = []; - this.month_weeks[this.current_month] = 0; - this.month_start_points.push(13); - - for(var i = 0; i < no_of_weeks; i++) { - let data_group, month_change = 0; - let day = new Date(current_week_sunday); - - [data_group, month_change] = this.get_week_squares_group(day, this.week_col); - this.data_groups.appendChild(data_group); - this.week_col += 1 + parseInt(this.discrete_domains && month_change); - this.month_weeks[this.current_month]++; - if(month_change) { - this.current_month = (this.current_month + 1) % 12; - this.months.push(this.current_month + ''); - this.month_weeks[this.current_month] = 1; - } - this.add_days(current_week_sunday, 7); - } - this.render_month_labels(); - } - - get_week_squares_group(current_date, index) { - const no_of_weekdays = 7; - const square_side = 10; - const cell_padding = 2; - const step = 1; - - let month_change = 0; - let week_col_change = 0; - - let data_group = $$.createSVG("g", { - className: "data-group", - inside: this.data_groups - }); - - for(var y = 0, i = 0; i < no_of_weekdays; i += step, y += (square_side + cell_padding)) { - let data_value = 0; - let color_index = 0; - - // TODO: More foolproof for any data - let timestamp = Math.floor(current_date.getTime()/1000).toFixed(1); - - if(this.data[timestamp]) { - data_value = this.data[timestamp]; - color_index = this.get_max_checkpoint(data_value, this.distribution); - } - - if(this.data[Math.round(timestamp)]) { - data_value = this.data[Math.round(timestamp)]; - color_index = this.get_max_checkpoint(data_value, this.distribution); - } - - let x = 13 + (index + week_col_change) * 12; - - $$.createSVG("rect", { - className: 'day', - inside: data_group, - x: x, - y: y, - width: square_side, - height: square_side, - fill: this.legend_colors[color_index], - 'data-date': this.get_dd_mm_yyyy(current_date), - 'data-value': data_value, - 'data-day': current_date.getDay() - }); - - let next_date = new Date(current_date); - this.add_days(next_date, 1); - if(next_date.getMonth() - current_date.getMonth()) { - month_change = 1; - if(this.discrete_domains) { - week_col_change = 1; - } - - this.month_start_points.push(13 + (index + week_col_change) * 12); - } - current_date = next_date; - } - - return [data_group, month_change]; - } - - render_month_labels() { - // this.first_month_label = 1; - // if (this.first_week_start.getDate() > 8) { - // this.first_month_label = 0; - // } - // this.last_month_label = 1; - - // let first_month = this.months.shift(); - // let first_month_start = this.month_start_points.shift(); - // render first month if - - // let last_month = this.months.pop(); - // let last_month_start = this.month_start_points.pop(); - // render last month if - - this.months.shift(); - this.month_start_points.shift(); - this.months.pop(); - this.month_start_points.pop(); - - this.month_start_points.map((start, i) => { - let month_name = this.month_names[this.months[i]].substring(0, 3); - - $$.createSVG('text', { - className: 'y-value-text', - inside: this.domain_label_group, - x: start + 12, - y: 10, - dy: '.32em', - innerHTML: month_name - }); - - }); - } - - make_graph_components() { - Array.prototype.slice.call( - this.container.querySelectorAll('.graph-stats-container, .sub-title, .title') - ).map(d => { - d.style.display = 'None'; - }); - this.chart_wrapper.style.marginTop = '0px'; - this.chart_wrapper.style.paddingTop = '0px'; - } - - bind_tooltip() { - Array.prototype.slice.call( - document.querySelectorAll(".data-group .day") - ).map(el => { - el.addEventListener('mouseenter', (e) => { - let count = e.target.getAttribute('data-value'); - let date_parts = e.target.getAttribute('data-date').split('-'); - - let month = this.month_names[parseInt(date_parts[1])-1].substring(0, 3); - - let g_off = this.chart_wrapper.getBoundingClientRect(), p_off = e.target.getBoundingClientRect(); - - let width = parseInt(e.target.getAttribute('width')); - let x = p_off.left - g_off.left + (width+2)/2; - let y = p_off.top - g_off.top - (width+2)/2; - let value = count + ' ' + this.count_label; - let name = ' on ' + month + ' ' + date_parts[0] + ', ' + date_parts[2]; - - this.tip.set_values(x, y, name, value, [], 1); - this.tip.show_tip(); - }); - }); - } - - update(data) { - this.data = data; - this.setup_values(); - this.bind_tooltip(); - } - - get_distribution(data={}, mapper_array) { - let data_values = Object.keys(data).map(key => data[key]); - let data_max_value = Math.max(...data_values); - - let distribution_step = 1 / (mapper_array.length - 1); - let distribution = []; - - mapper_array.map((color, i) => { - let checkpoint = data_max_value * (distribution_step * i); - distribution.push(checkpoint); - }); - - return distribution; - } - - get_max_checkpoint(value, distribution) { - return distribution.filter((d, i) => { - if(i === 1) { - return distribution[0] < value; - } - return d <= value; - }).length - 1; - } - - // TODO: date utils, move these out - - // https://stackoverflow.com/a/11252167/6495043 - treat_as_utc(date_str) { - let result = new Date(date_str); - result.setMinutes(result.getMinutes() - result.getTimezoneOffset()); - return result; - } - - get_dd_mm_yyyy(date) { - let dd = date.getDate(); - let mm = date.getMonth() + 1; // getMonth() is zero-based - return [ - (dd>9 ? '' : '0') + dd, - (mm>9 ? '' : '0') + mm, - date.getFullYear() - ].join('-'); - } - - get_weeks_between(start_date_str, end_date_str) { - return Math.ceil(this.get_days_between(start_date_str, end_date_str) / 7); - } - - get_days_between(start_date_str, end_date_str) { - let milliseconds_per_day = 24 * 60 * 60 * 1000; - return (this.treat_as_utc(end_date_str) - this.treat_as_utc(start_date_str)) / milliseconds_per_day; - } - - // mutates - add_days(date, number_of_days) { - date.setDate(date.getDate() + number_of_days); - } - - get_month_name() {} -} - -frappe.chart.SvgTip = class { - constructor({ - parent = null - }) { - this.parent = parent; - this.title_name = ''; - this.title_value = ''; - this.list_values = []; - this.title_value_first = 0; - - this.x = 0; - this.y = 0; - - this.top = 0; - this.left = 0; - - this.setup(); - } - - setup() { - this.make_tooltip(); - } - - refresh() { - this.fill(); - this.calc_position(); - // this.show_tip(); - } - - make_tooltip() { - this.container = $$.create('div', { - className: 'graph-svg-tip comparison', - innerHTML: ` -
      -
      ` - }); - - this.parent.appendChild(this.container); - this.hide_tip(); - - this.title = this.container.querySelector('.title'); - this.data_point_list = this.container.querySelector('.data-point-list'); - - this.parent.addEventListener('mouseleave', () => { - this.hide_tip(); - }); - } - - fill() { - let title; - if(this.title_value_first) { - title = `${this.title_value}${this.title_name}`; - } else { - title = `${this.title_name}${this.title_value}`; - } - this.title.innerHTML = title; - this.data_point_list.innerHTML = ''; - - this.list_values.map((set) => { - let li = $$.create('li', { - className: `border-top ${set.color || 'black'}`, - innerHTML: `${set.value ? set.value : '' } - ${set.title ? set.title : '' }` - }); - - this.data_point_list.appendChild(li); - }); - } - - calc_position() { - this.top = this.y - this.container.offsetHeight; - this.left = this.x - this.container.offsetWidth/2; - let max_left = this.parent.offsetWidth - this.container.offsetWidth; - - let pointer = this.container.querySelector('.svg-pointer'); - - if(this.left < 0) { - pointer.style.left = `calc(50% - ${-1 * this.left}px)`; - this.left = 0; - } else if(this.left > max_left) { - let delta = this.left - max_left; - pointer.style.left = `calc(50% + ${delta}px)`; - this.left = max_left; - } else { - pointer.style.left = `50%`; - } - } - - set_values(x, y, title_name = '', title_value = '', list_values = [], title_value_first = 0) { - this.title_name = title_name; - this.title_value = title_value; - this.list_values = list_values; - this.x = x; - this.y = y; - this.title_value_first = title_value_first; - this.refresh(); - } - - hide_tip() { - this.container.style.top = '0px'; - this.container.style.left = '0px'; - this.container.style.opacity = '0'; - } - - show_tip() { - this.container.style.top = this.top + 'px'; - this.container.style.left = this.left + 'px'; - this.container.style.opacity = '1'; - } -} - -frappe.chart.map_c3 = (chart) => { - if (chart.data) { - let data = chart.data; - let type = chart.chart_type || 'line'; - if(type === 'pie') { - type = 'percentage'; - } - - let x = {}, y = []; - - if(data.columns) { - let columns = data.columns; - - x = columns.filter(col => { - return col[0] === data.x; - })[0]; - - if(x && x.length) { - let dataset_length = x.length; - let dirty = false; - columns.map(col => { - if(col[0] !== data.x) { - if(col.length === dataset_length) { - let title = col[0]; - col.splice(0, 1); - y.push({ - title: title, - values: col, - }); - } else { - dirty = true; - } - } - }); - - if(dirty) { - return; - } - - x.splice(0, 1); - - return { - type: type, - y: y, - x: x - } - - } - } else if(data.rows) { - let rows = data.rows; - x = rows[0]; - - rows.map((row, i) => { - if(i === 0) { - x = row; - } else { - y.push({ - title: 'data' + i, - values: row, - }); - } - }); - - return { - type: type, - y: y, - x: x - } - } - } -} - -// Helpers -frappe.chart.utils = {}; -frappe.chart.utils.float_2 = d => parseFloat(d.toFixed(2)); -function $$(expr, con) { - return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; -} - -// $$.findNodeIndex = (node) => -// { -// var i = 0; -// while (node = node.previousSibling) { -// if (node.nodeType === 1) { ++i; } -// } -// return i; -// } - -$$.create = function(tag, o) { - var element = document.createElement(tag); - - for (var i in o) { - var val = o[i]; - - if (i === "inside") { - $$(val).appendChild(element); - } - else if (i === "around") { - var ref = $$(val); - ref.parentNode.insertBefore(element, ref); - element.appendChild(ref); - } - else if (i in element) { - element[i] = val; - } - else { - element.setAttribute(i, val); - } - } - - return element; -}; - -$$.createSVG = function(tag, o) { - var element = document.createElementNS("http://www.w3.org/2000/svg", tag); - - for (var i in o) { - var val = o[i]; - - if (i === "inside") { - $$(val).appendChild(element); - } - else if (i === "around") { - var ref = $$(val); - ref.parentNode.insertBefore(element, ref); - element.appendChild(ref); - } - else { - if(i === "className") { i = "class"; } - if(i === "innerHTML") { - element['textContent'] = val; - } else { - element.setAttribute(i, val); - } - } - } - - return element; -}; - -$$.runSVGAnimation = (svg_container, elements) => { - let parent = elements[0][0]['unit'].parentNode; - - let new_elements = []; - let anim_elements = []; - - elements.map(element => { - let obj = element[0]; - // let index = $$.findNodeIndex(obj.unit); - - let anim_element, new_element; - - element[0] = obj.unit; - [anim_element, new_element] = $$.animateSVG(...element); - - new_elements.push(new_element); - anim_elements.push(anim_element); - - parent.replaceChild(anim_element, obj.unit); - - if(obj.array) { - obj.array[obj.index] = new_element; - } else { - obj.object[obj.key] = new_element; - } - }); - - let anim_svg = svg_container.cloneNode(true); - - anim_elements.map((anim_element, i) => { - parent.replaceChild(new_elements[i], anim_element); - elements[i][0] = new_elements[i]; - }); - - return anim_svg; -} - -$$.animateSVG = (element, props, dur, easing_type="linear") => { - let easing = { - ease: "0.25 0.1 0.25 1", - linear: "0 0 1 1", - // easein: "0.42 0 1 1", - easein: "0.1 0.8 0.2 1", - easeout: "0 0 0.58 1", - easeinout: "0.42 0 0.58 1" - } - - let anim_element = element.cloneNode(false); - let new_element = element.cloneNode(false); - - for(var attributeName in props) { - let animate_element = document.createElementNS("http://www.w3.org/2000/svg", "animate"); - - let current_value = element.getAttribute(attributeName); - let value = props[attributeName]; - - let anim_attr = { - attributeName: attributeName, - from: current_value, - to: value, - begin: "0s", - dur: dur/1000 + "s", - values: current_value + ";" + value, - keySplines: easing[easing_type], - keyTimes: "0;1", - calcMode: "spline" - } - - for (var i in anim_attr) { - animate_element.setAttribute(i, anim_attr[i]); - } - - anim_element.appendChild(animate_element); - new_element.setAttribute(attributeName, value); - } - - return [anim_element, new_element]; -} - -$$.bind = function(element, o) { - if (element) { - for (var event in o) { - var callback = o[event]; - - event.split(/\s+/).forEach(function (event) { - element.addEventListener(event, callback); - }); - } - } -}; - -$$.unbind = function(element, o) { - if (element) { - for (var event in o) { - var callback = o[event]; - - event.split(/\s+/).forEach(function(event) { - element.removeEventListener(event, callback); - }); - } - } -}; - -$$.fire = function(target, type, properties) { - var evt = document.createEvent("HTMLEvents"); - - evt.initEvent(type, true, true ); - - for (var j in properties) { - evt[j] = properties[j]; - } - - return target.dispatchEvent(evt); -}; diff --git a/frappe/public/js/frappe/views/reports/grid_report.js b/frappe/public/js/frappe/views/reports/grid_report.js index 504708a12c..054a3b2b09 100644 --- a/frappe/public/js/frappe/views/reports/grid_report.js +++ b/frappe/public/js/frappe/views/reports/grid_report.js @@ -672,7 +672,7 @@ frappe.views.GridReportWithPlot = frappe.views.GridReport.extend({ } var chart_data = this.get_chart_data ? this.get_chart_data() : null; - this.chart = new frappe.chart.FrappeChart({ + this.chart = new Chart({ parent: ".chart", height: 200, data: chart_data, diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 54dda994f5..88ada6c62f 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -945,7 +945,7 @@ frappe.views.QueryReport = Class.extend({ if(opts.data && opts.data.labels && opts.data.labels.length) { this.chart_area.toggle(true); - this.chart = new frappe.chart.FrappeChart(opts); + this.chart = new Chart(opts); } }, diff --git a/frappe/public/js/lib/Chart.min.js b/frappe/public/js/lib/Chart.min.js deleted file mode 100755 index ab63588108..0000000000 --- a/frappe/public/js/lib/Chart.min.js +++ /dev/null @@ -1,39 +0,0 @@ -var Chart=function(s){function v(a,c,b){a=A((a-c.graphMin)/(c.steps*c.stepValue),1,0);return b*c.steps*a}function x(a,c,b,e){function h(){g+=f;var k=a.animation?A(d(g),null,0):1;e.clearRect(0,0,q,u);a.scaleOverlay?(b(k),c()):(c(),b(k));if(1>=g)D(h);else if("function"==typeof a.onAnimationComplete)a.onAnimationComplete()}var f=a.animation?1/A(a.animationSteps,Number.MAX_VALUE,1):1,d=B[a.animationEasing],g=a.animation?0:1;"function"!==typeof c&&(c=function(){});D(h)}function C(a,c,b,e,h,f){var d;a= -Math.floor(Math.log(e-h)/Math.LN10);h=Math.floor(h/(1*Math.pow(10,a)))*Math.pow(10,a);e=Math.ceil(e/(1*Math.pow(10,a)))*Math.pow(10,a)-h;a=Math.pow(10,a);for(d=Math.round(e/a);dc;)a=dc?c:!isNaN(parseFloat(b))&& -isFinite(b)&&a)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return c? -b(c):b}var r=this,B={linear:function(a){return a},easeInQuad:function(a){return a*a},easeOutQuad:function(a){return-1*a*(a-2)},easeInOutQuad:function(a){return 1>(a/=0.5)?0.5*a*a:-0.5*(--a*(a-2)-1)},easeInCubic:function(a){return a*a*a},easeOutCubic:function(a){return 1*((a=a/1-1)*a*a+1)},easeInOutCubic:function(a){return 1>(a/=0.5)?0.5*a*a*a:0.5*((a-=2)*a*a+2)},easeInQuart:function(a){return a*a*a*a},easeOutQuart:function(a){return-1*((a=a/1-1)*a*a*a-1)},easeInOutQuart:function(a){return 1>(a/=0.5)? -0.5*a*a*a*a:-0.5*((a-=2)*a*a*a-2)},easeInQuint:function(a){return 1*(a/=1)*a*a*a*a},easeOutQuint:function(a){return 1*((a=a/1-1)*a*a*a*a+1)},easeInOutQuint:function(a){return 1>(a/=0.5)?0.5*a*a*a*a*a:0.5*((a-=2)*a*a*a*a+2)},easeInSine:function(a){return-1*Math.cos(a/1*(Math.PI/2))+1},easeOutSine:function(a){return 1*Math.sin(a/1*(Math.PI/2))},easeInOutSine:function(a){return-0.5*(Math.cos(Math.PI*a/1)-1)},easeInExpo:function(a){return 0==a?1:1*Math.pow(2,10*(a/1-1))},easeOutExpo:function(a){return 1== -a?1:1*(-Math.pow(2,-10*a/1)+1)},easeInOutExpo:function(a){return 0==a?0:1==a?1:1>(a/=0.5)?0.5*Math.pow(2,10*(a-1)):0.5*(-Math.pow(2,-10*--a)+2)},easeInCirc:function(a){return 1<=a?a:-1*(Math.sqrt(1-(a/=1)*a)-1)},easeOutCirc:function(a){return 1*Math.sqrt(1-(a=a/1-1)*a)},easeInOutCirc:function(a){return 1>(a/=0.5)?-0.5*(Math.sqrt(1-a*a)-1):0.5*(Math.sqrt(1-(a-=2)*a)+1)},easeInElastic:function(a){var c=1.70158,b=0,e=1;if(0==a)return 0;if(1==(a/=1))return 1;b||(b=0.3);ea?-0.5*e*Math.pow(2,10* -(a-=1))*Math.sin((1*a-c)*2*Math.PI/b):0.5*e*Math.pow(2,-10*(a-=1))*Math.sin((1*a-c)*2*Math.PI/b)+1},easeInBack:function(a){return 1*(a/=1)*a*(2.70158*a-1.70158)},easeOutBack:function(a){return 1*((a=a/1-1)*a*(2.70158*a+1.70158)+1)},easeInOutBack:function(a){var c=1.70158;return 1>(a/=0.5)?0.5*a*a*(((c*=1.525)+1)*a-c):0.5*((a-=2)*a*(((c*=1.525)+1)*a+c)+2)},easeInBounce:function(a){return 1-B.easeOutBounce(1-a)},easeOutBounce:function(a){return(a/=1)<1/2.75?1*7.5625*a*a:a<2/2.75?1*(7.5625*(a-=1.5/2.75)* -a+0.75):a<2.5/2.75?1*(7.5625*(a-=2.25/2.75)*a+0.9375):1*(7.5625*(a-=2.625/2.75)*a+0.984375)},easeInOutBounce:function(a){return 0.5>a?0.5*B.easeInBounce(2*a):0.5*B.easeOutBounce(2*a-1)+0.5}},q=s.canvas.width,u=s.canvas.height;window.devicePixelRatio&&(s.canvas.style.width=q+"px",s.canvas.style.height=u+"px",s.canvas.height=u*window.devicePixelRatio,s.canvas.width=q*window.devicePixelRatio,s.scale(window.devicePixelRatio,window.devicePixelRatio));this.PolarArea=function(a,c){r.PolarArea.defaults={scaleOverlay:!0, -scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleShowLine:!0,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animation:!0,animationSteps:100,animationEasing:"easeOutBounce", -animateRotate:!0,animateScale:!1,onAnimationComplete:null};var b=c?y(r.PolarArea.defaults,c):r.PolarArea.defaults;return new G(a,b,s)};this.Radar=function(a,c){r.Radar.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleShowLine:!0,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!1,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)", -scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,angleShowLineOut:!0,angleLineColor:"rgba(0,0,0,.1)",angleLineWidth:1,pointLabelFontFamily:"'Arial'",pointLabelFontStyle:"normal",pointLabelFontSize:12,pointLabelFontColor:"#666",pointDot:!0,pointDotRadius:3,pointDotStrokeWidth:1,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null};var b=c?y(r.Radar.defaults,c):r.Radar.defaults;return new H(a,b,s)};this.Pie=function(a, -c){r.Pie.defaults={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,onAnimationComplete:null};var b=c?y(r.Pie.defaults,c):r.Pie.defaults;return new I(a,b,s)};this.Doughnut=function(a,c){r.Doughnut.defaults={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,percentageInnerCutout:50,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1, -onAnimationComplete:null};var b=c?y(r.Doughnut.defaults,c):r.Doughnut.defaults;return new J(a,b,s)};this.Line=function(a,c){r.Line.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,bezierCurve:!0, -pointDot:!0,pointDotRadius:4,pointDotStrokeWidth:2,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null};var b=c?y(r.Line.defaults,c):r.Line.defaults;return new K(a,b,s)};this.Bar=function(a,c){r.Bar.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'", -scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,barShowStroke:!0,barStrokeWidth:2,barValueSpacing:5,barDatasetSpacing:1,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null};var b=c?y(r.Bar.defaults,c):r.Bar.defaults;return new L(a,b,s)};var G=function(a,c,b){var e,h,f,d,g,k,j,l,m;g=Math.min.apply(Math,[q,u])/2;g-=Math.max.apply(Math,[0.5*c.scaleFontSize,0.5*c.scaleLineWidth]); -d=2*c.scaleFontSize;c.scaleShowLabelBackdrop&&(d+=2*c.scaleBackdropPaddingY,g-=1.5*c.scaleBackdropPaddingY);l=g;d=d?d:5;e=Number.MIN_VALUE;h=Number.MAX_VALUE;for(f=0;fe&&(e=a[f].value),a[f].valuel&&(l=h);g-=Math.max.apply(Math,[l,1.5*(c.pointLabelFontSize/2)]);g-=c.pointLabelFontSize;l=g=A(g,null,0);d=d?d:5;e=Number.MIN_VALUE; -h=Number.MAX_VALUE;for(f=0;fe&&(e=a.datasets[f].data[m]),a.datasets[f].data[m]Math.PI?"right":"left";b.textBaseline="middle";b.fillText(a.labels[d],f,-h)}b.restore()},function(d){var e=2*Math.PI/a.datasets[0].data.length;b.save();b.translate(q/2,u/2);for(var g=0;gt?e:t;q/a.labels.lengthe&&(e=a.datasets[f].data[l]),a.datasets[f].data[l]d?h:d;d+=10}r=q-d-t;m=Math.floor(r/(a.labels.length-1));n=q-t/2-r;p=g+c.scaleFontSize/2;x(c,function(){b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath();b.moveTo(q-t/2+5,p);b.lineTo(q-t/2-r-5,p);b.stroke();0t?e:t;q/a.labels.lengthe&&(e=a.datasets[f].data[l]),a.datasets[f].data[l]< -h&&(h=a.datasets[f].data[l]);f=Math.floor(g/(0.66*d));d=Math.floor(0.5*(g/d));l=c.scaleShowLabels?c.scaleLabel:"";c.scaleOverride?(j={steps:c.scaleSteps,stepValue:c.scaleStepWidth,graphMin:c.scaleStartValue,labels:[]},z(l,j.labels,j.steps,c.scaleStartValue,c.scaleStepWidth)):j=C(g,f,d,e,h,l);k=Math.floor(g/j.steps);d=1;if(c.scaleShowLabels){b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily;for(e=0;ed?h:d;d+=10}r=q-d-t;m= -Math.floor(r/a.labels.length);s=(m-2*c.scaleGridLineWidth-2*c.barValueSpacing-(c.barDatasetSpacing*a.datasets.length-1)-(c.barStrokeWidth/2*a.datasets.length-1))/a.datasets.length;n=q-t/2-r;p=g+c.scaleFontSize/2;x(c,function(){b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath();b.moveTo(q-t/2+5,p);b.lineTo(q-t/2-r-5,p);b.stroke();0.title{margin-top:25px;margin-left:25px;text-align:left;font-weight:400;font-size:12px;color:#6c7680}.chart-container .graphics{margin-top:10px;padding-top:10px;padding-bottom:10px;position:relative}.chart-container .graph-stats-group{-ms-flex-pack:distribute;-webkit-box-flex:1;-ms-flex:1;flex:1}.chart-container .graph-stats-container,.chart-container .graph-stats-group{display:-webkit-box;display:-ms-flexbox;display:flex;justify-content:space-around}.chart-container .graph-stats-container{-ms-flex-pack:distribute;padding-top:10px}.chart-container .graph-stats-container .stats{padding-bottom:15px}.chart-container .graph-stats-container .stats-title{color:#8d99a6}.chart-container .graph-stats-container .stats-value{font-size:20px;font-weight:300}.chart-container .graph-stats-container .stats-description{font-size:12px;color:#8d99a6}.chart-container .graph-stats-container .graph-data .stats-value{color:#98d85b}.chart-container .axis,.chart-container .chart-label{font-size:11px;fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .percentage-graph .progress{margin-bottom:0}.chart-container .data-points circle{stroke:#fff;stroke-width:2}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .tick.x-axis-label{display:block}.chart-container .tick .specific-value{text-anchor:start}.chart-container .tick .y-value-text{text-anchor:end}.chart-container .tick .x-value-text{text-anchor:middle}.chart-container .progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.chart-container .progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#36414c;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;transition:width .6s ease}.graph-svg-tip{position:absolute;z-index:1;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.graph-svg-tip ol,.graph-svg-tip ul{padding-left:0;display:-webkit-box;display:-ms-flexbox;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.graph-svg-tip .svg-pointer{position:absolute;bottom:-10px;left:50%;width:5px;height:5px;margin:0 0 0 -5px;content:" ";border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}.indicator,.indicator-right{background:none;font-size:12px;vertical-align:middle;font-weight:700;color:#6c7680}.indicator-right:after,.indicator:before{content:"";display:inline-block;height:8px;width:8px;border-radius:8px}.indicator:before{margin:0 4px 0 0}.indicator-right:after{margin:0 0 0 4px}.background.grey,.indicator-right.grey:after,.indicator.grey:before{background:#bdd3e6}.background.light-grey,.indicator-right.light-grey:after,.indicator.light-grey:before{background:#f0f4f7}.background.blue,.indicator-right.blue:after,.indicator.blue:before{background:#5e64ff}.background.red,.indicator-right.red:after,.indicator.red:before{background:#ff5858}.background.green,.indicator-right.green:after,.indicator.green:before{background:#28a745}.background.light-green,.indicator-right.light-green:after,.indicator.light-green:before{background:#98d85b}.background.orange,.indicator-right.orange:after,.indicator.orange:before{background:#ffa00a}.background.violet,.indicator-right.violet:after,.indicator.violet:before{background:#743ee2}.background.dark-grey,.indicator-right.dark-grey:after,.indicator.dark-grey:before{background:#b8c2cc}.background.black,.indicator-right.black:after,.indicator.black:before{background:#36414c}.background.yellow,.indicator-right.yellow:after,.indicator.yellow:before{background:#feef72}.background.light-blue,.indicator-right.light-blue:after,.indicator.light-blue:before{background:#7cd6fd}.background.purple,.indicator-right.purple:after,.indicator.purple:before{background:#b554ff}.background.magenta,.indicator-right.magenta:after,.indicator.magenta:before{background:#ffa3ef}.stroke.grey{stroke:#bdd3e6}.stroke.light-grey{stroke:#f0f4f7}.stroke.blue{stroke:#5e64ff}.stroke.red{stroke:#ff5858}.stroke.light-green{stroke:#98d85b}.stroke.green{stroke:#28a745}.stroke.orange{stroke:#ffa00a}.stroke.violet{stroke:#743ee2}.stroke.dark-grey{stroke:#b8c2cc}.stroke.black{stroke:#36414c}.stroke.yellow{stroke:#feef72}.stroke.light-blue{stroke:#7cd6fd}.stroke.purple{stroke:#b554ff}.stroke.magenta{stroke:#ffa3ef}.fill.grey{fill:#bdd3e6}.fill.light-grey{fill:#f0f4f7}.fill.blue{fill:#5e64ff}.fill.red{fill:#ff5858}.fill.light-green{fill:#98d85b}.fill.green{fill:#28a745}.fill.orange{fill:#ffa00a}.fill.violet{fill:#743ee2}.fill.dark-grey{fill:#b8c2cc}.fill.black{fill:#36414c}.fill.yellow{fill:#feef72}.fill.light-blue{fill:#7cd6fd}.fill.purple{fill:#b554ff}.fill.magenta{fill:#ffa3ef}.border-top.grey{border-top:3px solid #bdd3e6}.border-top.light-grey{border-top:3px solid #f0f4f7}.border-top.blue{border-top:3px solid #5e64ff}.border-top.red{border-top:3px solid #ff5858}.border-top.light-green{border-top:3px solid #98d85b}.border-top.green{border-top:3px solid #28a745}.border-top.orange{border-top:3px solid #ffa00a}.border-top.violet{border-top:3px solid #743ee2}.border-top.dark-grey{border-top:3px solid #b8c2cc}.border-top.black{border-top:3px solid #36414c}.border-top.yellow{border-top:3px solid #feef72}.border-top.light-blue{border-top:3px solid #7cd6fd}.border-top.purple{border-top:3px solid #b554ff}.border-top.magenta{border-top:3px solid #ffa3ef}.stop-color.grey{stop-color:#bdd3e6}.stop-color.light-grey{stop-color:#f0f4f7}.stop-color.blue{stop-color:#5e64ff}.stop-color.red{stop-color:#ff5858}.stop-color.light-green{stop-color:#98d85b}.stop-color.green{stop-color:#28a745}.stop-color.orange{stop-color:#ffa00a}.stop-color.violet{stop-color:#743ee2}.stop-color.dark-grey{stop-color:#b8c2cc}.stop-color.black{stop-color:#36414c}.stop-color.yellow{stop-color:#feef72}.stop-color.light-blue{stop-color:#7cd6fd}.stop-color.purple{stop-color:#b554ff}.stop-color.magenta{stop-color:#ffa3ef}',void 0);!function(){function t(t){this.value=t}function e(e){function i(s,n){try{var r=e[s](n),o=r.value;o instanceof t?Promise.resolve(o.value).then(function(t){i("next",t)},function(t){i("throw",t)}):a(r.done?"return":"normal",r.value)}catch(t){a("throw",t)}}function a(t,e){switch(t){case"return":s.resolve({value:e,done:!0});break;case"throw":s.reject(e);break;default:s.resolve({value:e,done:!1})}(s=s.next)?i(s.key,s.arg):n=null}var s,n;this._invoke=function(t,e){return new Promise(function(a,r){var o={key:t,arg:e,resolve:a,reject:r,next:null};n?n=n.next=o:(s=n=o,i(t,e))})},"function"!=typeof e.return&&(this.return=void 0)}"function"==typeof Symbol&&Symbol.asyncIterator&&(e.prototype[Symbol.asyncIterator]=function(){return this}),e.prototype.next=function(t){return this._invoke("next",t)},e.prototype.throw=function(t){return this._invoke("throw",t)},e.prototype.return=function(t){return this._invoke("return",t)}}();var a=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},s=function(){function t(t,e){for(var i=0;i3&&void 0!==arguments[3]?arguments[3]:"linear",s=arguments.length>4&&void 0!==arguments[4]?arguments[4]:void 0,n=arguments.length>5&&void 0!==arguments[5]?arguments[5]:{},r={ease:"0.25 0.1 0.25 1",linear:"0 0 1 1",easein:"0.1 0.8 0.2 1",easeout:"0 0 0.58 1",easeinout:"0.42 0 0.58 1"},o=t.cloneNode(!0),l=t.cloneNode(!0);for(var h in e){var _=void 0;_="transform"===h?document.createElementNS("http://www.w3.org/2000/svg","animateTransform"):document.createElementNS("http://www.w3.org/2000/svg","animate");var u=n[h]||t.getAttribute(h),c=e[h],p={attributeName:h,from:u,to:c,begin:"0s",dur:i/1e3+"s",values:u+";"+c,keySplines:r[a],keyTimes:"0;1",calcMode:"spline",fill:"freeze"};s&&(p.type=s);for(var d in p)_.setAttribute(d,p[d]);o.appendChild(_),s?l.setAttribute(h,"translate("+c+")"):l.setAttribute(h,c)}return[o,l]},t.offset=function(t){var e=t.getBoundingClientRect();return{top:e.top+(document.documentElement.scrollTop||document.body.scrollTop),left:e.left+(document.documentElement.scrollLeft||document.body.scrollLeft)}},t.isElementInViewport=function(t){var e=t.getBoundingClientRect();return e.top>=0&&e.left>=0&&e.bottom<=(window.innerHeight||document.documentElement.clientHeight)&&e.right<=(window.innerWidth||document.documentElement.clientWidth)},t.bind=function(t,e){if(t)for(var i in e){var a=e[i];i.split(/\s+/).forEach(function(e){t.addEventListener(e,a)})}},t.unbind=function(t,e){if(t)for(var i in e){var a=e[i];i.split(/\s+/).forEach(function(e){t.removeEventListener(e,a)})}},t.fire=function(t,e,i){var a=document.createEvent("HTMLEvents");a.initEvent(e,!0,!0);for(var s in i)a[s]=i[s];return t.dispatchEvent(a)};var _=function(){function e(t){var i=t.parent,s=void 0===i?null:i;a(this,e),this.parent=s,this.title_name="",this.title_value="",this.list_values=[],this.title_value_first=0,this.x=0,this.y=0,this.top=0,this.left=0,this.setup()}return s(e,[{key:"setup",value:function(){this.make_tooltip()}},{key:"refresh",value:function(){this.fill(),this.calc_position()}},{key:"make_tooltip",value:function(){var e=this;this.container=t.create("div",{inside:this.parent,className:"graph-svg-tip comparison",innerHTML:'\n\t\t\t\t
        \n\t\t\t\t
        '}),this.hide_tip(),this.title=this.container.querySelector(".title"),this.data_point_list=this.container.querySelector(".data-point-list"),this.parent.addEventListener("mouseleave",function(){e.hide_tip()})}},{key:"fill",value:function(){var e=this,i=void 0;i=this.title_value_first?""+this.title_value+""+this.title_name:this.title_name+""+this.title_value+"",this.title.innerHTML=i,this.data_point_list.innerHTML="",this.list_values.map(function(i){var a=t.create("li",{className:"border-top "+(i.color||"black"),innerHTML:''+(i.value?i.value:"")+"\n\t\t\t\t\t"+(i.title?i.title:"")});e.data_point_list.appendChild(a)})}},{key:"calc_position",value:function(){this.top=this.y-this.container.offsetHeight,this.left=this.x-this.container.offsetWidth/2;var t=this.parent.offsetWidth-this.container.offsetWidth,e=this.container.querySelector(".svg-pointer");if(this.left<0)e.style.left="calc(50% - "+-1*this.left+"px)",this.left=0;else if(this.left>t){var i=this.left-t;e.style.left="calc(50% + "+i+"px)",this.left=t}else e.style.left="50%"}},{key:"set_values",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"",a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"",s=arguments.length>4&&void 0!==arguments[4]?arguments[4]:[],n=arguments.length>5&&void 0!==arguments[5]?arguments[5]:0;this.title_name=i,this.title_value=a,this.list_values=s,this.x=t,this.y=e,this.title_value_first=n,this.refresh()}},{key:"hide_tip",value:function(){this.container.style.top="0px",this.container.style.left="0px",this.container.style.opacity="0"}},{key:"show_tip",value:function(){this.container.style.top=this.top+"px",this.container.style.left=this.left+"px",this.container.style.opacity="1"}}]),e}(),u=function(){function e(t){var i=t.parent,s=void 0===i?"":i,n=t.height,r=void 0===n?240:n,o=t.title,l=void 0===o?"":o,h=t.subtitle,_=void 0===h?"":h,u=t.data,c=void 0===u?{}:u,p=t.format_lambdas,d=void 0===p?{}:p,f=t.summary,g=void 0===f?[]:f,v=t.is_navigable,y=void 0===v?0:v,m=t.has_legend,x=void 0===m?0:m;a(this,e),this.raw_chart_args=arguments[0],this.parent=document.querySelector(s),this.title=l,this.subtitle=_,this.data=c,this.format_lambdas=d,this.specific_values=c.specific_values||[],this.summary=g,this.is_navigable=y,this.is_navigable&&(this.current_index=0),this.has_legend=x,this.chart_types=["line","scatter","bar","percentage","heatmap"],this.set_margins(r)}return s(e,[{key:"get_different_chart",value:function(t){if(this.chart_types.includes(t)||console.error("'"+t+"' is not a valid chart type."),t!==this.type){return{bar:["line","scatter","percentage"],line:["scatter","bar","percentage"],scatter:["line","bar","percentage"],percentage:["bar","line","scatter"],heatmap:[]}[this.type].includes(t)||console.error("'"+this.type+"' chart cannot be converted to a '"+t+"' chart."),new y({parent:this.raw_chart_args.parent,title:this.title,data:this.raw_chart_args.data,type:t,height:this.raw_chart_args.height})}}},{key:"set_margins",value:function(t){this.base_height=t,this.height=t-40,this.translate_x=60,this.translate_y=10}},{key:"setup",value:function(){this.bind_window_events(),this.refresh(!0)}},{key:"bind_window_events",value:function(){var t=this;window.addEventListener("resize",function(){return t.refresh()}),window.addEventListener("orientationchange",function(){return t.refresh()})}},{key:"refresh",value:function(){var t=arguments.length>0&&void 0!==arguments[0]&&arguments[0];this.setup_base_values(),this.set_width(),this.setup_container(),this.setup_components(),this.setup_values(),this.setup_utils(),this.make_graph_components(t),this.make_tooltip(),this.summary.length>0?this.show_custom_summary():this.show_summary(),this.is_navigable&&this.setup_navigation(t)}},{key:"set_width",value:function(){var t=this,e=0;this.specific_values.map(function(i){t.get_strwidth(i.title)>e&&(e=t.get_strwidth(i.title)-40)}),this.base_width=this.parent.offsetWidth-e,this.width=this.base_width-2*this.translate_x}},{key:"setup_base_values",value:function(){}},{key:"setup_container",value:function(){this.container=t.create("div",{className:"chart-container",innerHTML:'
        '+this.title+'
        \n\t\t\t\t
        '+this.subtitle+'
        \n\t\t\t\t
        \n\t\t\t\t
        '}),this.parent.innerHTML="",this.parent.appendChild(this.container),this.chart_wrapper=this.container.querySelector(".frappe-chart"),this.stats_wrapper=this.container.querySelector(".graph-stats-container"),this.make_chart_area(),this.make_draw_area()}},{key:"make_chart_area",value:function(){return this.svg=t.createSVG("svg",{className:"chart",inside:this.chart_wrapper,width:this.base_width,height:this.base_height}),this.svg_defs=t.createSVG("defs",{inside:this.svg}),this.svg}},{key:"make_draw_area",value:function(){this.draw_area=t.createSVG("g",{className:this.type+"-chart",inside:this.svg,transform:"translate("+this.translate_x+", "+this.translate_y+")"})}},{key:"setup_components",value:function(){}},{key:"make_tooltip",value:function(){this.tip=new _({parent:this.chart_wrapper}),this.bind_tooltip()}},{key:"show_summary",value:function(){}},{key:"show_custom_summary",value:function(){var e=this;this.summary.map(function(i){var a=t.create("div",{className:"stats",innerHTML:''+i.title+": "+i.value+""});e.stats_wrapper.appendChild(a)})}},{key:"setup_navigation",value:function(){var e=this,i=arguments.length>0&&void 0!==arguments[0]&&arguments[0];this.make_overlay(),i&&(this.bind_overlay(),document.addEventListener("keydown",function(i){t.isElementInViewport(e.chart_wrapper)&&("37"==(i=i||window.event).keyCode?e.on_left_arrow():"39"==i.keyCode?e.on_right_arrow():"38"==i.keyCode?e.on_up_arrow():"40"==i.keyCode?e.on_down_arrow():"13"==i.keyCode&&e.on_enter_key())}))}},{key:"make_overlay",value:function(){}},{key:"bind_overlay",value:function(){}},{key:"bind_units",value:function(){}},{key:"on_left_arrow",value:function(){}},{key:"on_right_arrow",value:function(){}},{key:"on_up_arrow",value:function(){}},{key:"on_down_arrow",value:function(){}},{key:"on_enter_key",value:function(){}},{key:"get_data_point",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.current_index,e={index:t},i=this.y[0];return["svg_units","y_tops","values"].map(function(a){var s=a.slice(0,a.length-1);e[s]=i[a][t]}),e.label=this.x[t],e}},{key:"update_current_data_point",value:function(e){(e=parseInt(e))<0&&(e=0),e>=this.x.length&&(e=this.x.length-1),e!==this.current_index&&(this.current_index=e,t.fire(this.parent,"data-select",this.get_data_point()))}},{key:"get_strwidth",value:function(t){return 8*(t+"").length}},{key:"setup_utils",value:function(){}}]),e}(),c=function(_){function c(t){a(this,c);var e=o(this,(c.__proto__||Object.getPrototypeOf(c)).call(this,t));return e.x=e.data.labels,e.y=e.data.datasets,e.is_series=t.is_series,e.get_y_label=e.format_lambdas.y_label,e.get_y_tooltip=e.format_lambdas.y_tooltip,e.get_x_tooltip=e.format_lambdas.x_tooltip,e.colors=["green","blue","violet","red","orange","yellow","light-blue","light-green","purple","magenta"],e.zero_line=e.height,e}return r(c,u),s(c,[{key:"setup_values",value:function(){this.data.datasets.map(function(t){t.values=t.values.map(function(t){return isNaN(t)?0:t})}),this.setup_x(),this.setup_y()}},{key:"setup_x",value:function(){var t=this;this.set_avg_unit_width_and_x_offset(),this.x_axis_positions&&(this.x_old_axis_positions=this.x_axis_positions.slice()),this.x_axis_positions=this.x.map(function(i,a){return e(t.x_offset+a*t.avg_unit_width)}),this.x_old_axis_positions||(this.x_old_axis_positions=this.x_axis_positions.slice())}},{key:"setup_y",value:function(){this.y_axis_values&&(this.y_old_axis_values=this.y_axis_values.slice());var t=this.get_all_y_values();this.y_sums&&this.y_sums.length>0&&(t=t.concat(this.y_sums)),this.y_axis_values=this.get_y_axis_points(t),this.y_old_axis_values||(this.y_old_axis_values=this.y_axis_values.slice());var e=this.y_axis_values,i=e[e.length-1]-e[0];this.multiplier&&(this.old_multiplier=this.multiplier),this.multiplier=this.height/i,this.old_multiplier||(this.old_multiplier=this.multiplier);var a=e.indexOf(0),s=(e[1]-e[0])*this.multiplier;this.zero_line&&(this.old_zero_line=this.zero_line),this.zero_line=this.height-a*s,this.old_zero_line||(this.old_zero_line=this.zero_line)}},{key:"setup_components",value:function(){n(c.prototype.__proto__||Object.getPrototypeOf(c.prototype),"setup_components",this).call(this),this.setup_marker_components(),this.setup_aggregation_components(),this.setup_graph_components()}},{key:"setup_marker_components",value:function(){this.y_axis_group=t.createSVG("g",{className:"y axis",inside:this.draw_area}),this.x_axis_group=t.createSVG("g",{className:"x axis",inside:this.draw_area}),this.specific_y_group=t.createSVG("g",{className:"specific axis",inside:this.draw_area})}},{key:"setup_aggregation_components",value:function(){this.sum_group=t.createSVG("g",{className:"data-points",inside:this.draw_area}),this.average_group=t.createSVG("g",{className:"chart-area",inside:this.draw_area})}},{key:"setup_graph_components",value:function(){var e=this;this.svg_units_groups=[],this.y.map(function(i,a){e.svg_units_groups[a]=t.createSVG("g",{className:"data-points data-points-"+a,inside:e.draw_area})})}},{key:"make_graph_components",value:function(){var t=arguments.length>0&&void 0!==arguments[0]&&arguments[0];this.make_y_axis(),this.make_x_axis(),this.draw_graph(t),this.make_y_specifics()}},{key:"make_x_axis",value:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],i=void 0,a=void 0,s=void 0,n="";if("span"===this.x_axis_mode?(i=-7,a=this.height+15,s=this.height+25):"tick"===this.x_axis_mode&&(i=this.height,a=6,s=9,n="x-axis-label"),this.x_axis_group.setAttribute("transform","translate(0,"+i+")"),e)this.make_anim_x_axis(a,s,n);else{var r=1.5*this.avg_unit_width,o=r/8;this.x_axis_group.textContent="",this.x.map(function(e,i){var l=t.get_strwidth(e);if(l>r)if(t.is_series){for(var h=1;l/h*2>r;)h++;if(i%h!=0)return}else e=e.slice(0,o-3)+" ...";t.x_axis_group.appendChild(t.make_x_line(a,s,e,"x-value-text",n,t.x_axis_positions[i]))})}}},{key:"make_y_axis",value:function(){var t=this;if(arguments.length>0&&void 0!==arguments[0]&&arguments[0])return this.make_anim_y_axis(),void this.make_anim_y_specifics();var e=this.get_y_axis_line_props(),i=l(e,4),a=i[0],s=i[1],n=i[2],r=i[3];this.y_axis_group.textContent="",this.y_axis_values.map(function(e,i){t.y_axis_group.appendChild(t.make_y_line(r,a,s,e,"y-value-text",n,t.zero_line-e*t.multiplier,0===e&&0!==i))})}},{key:"get_y_axis_line_props",value:function(){if(arguments.length>0&&void 0!==arguments[0]&&arguments[0])return[this.width,this.width+5,"specific-value",0];var t=void 0,e="",i=0;return"span"===this.y_axis_mode?(t=this.width+6,i=-6):"tick"===this.y_axis_mode&&(t=-6,e="y-axis-label"),[t,-9,e,i]}},{key:"draw_graph",value:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];!this.raw_chart_args.hasOwnProperty("init")||this.raw_chart_args.init?e?this.draw_new_graph_and_animate():this.y.map(function(e,i){e.svg_units=[],t.make_path&&t.make_path(e,i,t.x_axis_positions,e.y_tops,e.color||t.colors[i]),t.make_new_units(e,i)}):this.y.map(function(e,i){e.svg_units=[],t.make_path&&t.make_path(e,i,t.x_axis_positions,e.y_tops,e.color||t.colors[i]),t.make_new_units(e,i),t.calc_y_dependencies()})}},{key:"draw_new_graph_and_animate",value:function(){var t=this,e=[];this.y.map(function(i,a){i.y_tops=new Array(i.values.length).fill(t.zero_line),e.push({values:i.values}),i.svg_units=[],t.make_path&&t.make_path(i,a,t.x_axis_positions,i.y_tops,i.color||t.colors[a]),t.make_new_units(i,a)}),setTimeout(function(){t.update_values(e)},350)}},{key:"setup_navigation",value:function(t){var e=this;t?setTimeout(function(){n(c.prototype.__proto__||Object.getPrototypeOf(c.prototype),"setup_navigation",e).call(e,t)},500):n(c.prototype.__proto__||Object.getPrototypeOf(c.prototype),"setup_navigation",this).call(this,t)}},{key:"make_new_units",value:function(t,e){this.make_new_units_for_dataset(this.x_axis_positions,t.y_tops,t.color||this.colors[e],e,this.y.length)}},{key:"make_new_units_for_dataset",value:function(t,e,i,a,s,n,r,o){var l=this;n||(n=this.svg_units_groups[a]),r||(r=this.y[a].svg_units),o||(o=this.unit_args),n.textContent="",r.length=0,e.map(function(e,h){var _=l.draw[o.type](t[h],e,o.args,i,h,a,s);n.appendChild(_),r.push(_)}),this.is_navigable&&this.bind_units(r)}},{key:"make_y_specifics",value:function(){var t=this;this.specific_y_group.textContent="",this.specific_values.map(function(e){t.specific_y_group.appendChild(t.make_y_line(0,t.width,t.width+5,e.title.toUpperCase(),"specific-value","specific-value",t.zero_line-e.value*t.multiplier,!1,e.line_type))})}},{key:"bind_tooltip",value:function(){var e=this;this.chart_wrapper.addEventListener("mousemove",function(i){var a=t.offset(e.chart_wrapper),s=i.pageX-a.left-e.translate_x;i.pageY-a.top-e.translate_y=0;i--){var a=this.x_axis_positions[i];if(t>a-this.avg_unit_width/2){var s=a+this.translate_x,n=this.y_min_tops[i]+this.translate_y,r=this.x.formatted&&this.x.formatted.length>0?this.x.formatted[i]:this.x[i],o=this.y.map(function(t,a){return{title:t.title,value:t.formatted?t.formatted[i]:t.values[i],color:t.color||e.colors[a]}});this.tip.set_values(s,n,r,"",o),this.tip.show_tip();break}}}},{key:"show_sums",value:function(){var t=this;this.updating=!0,this.y_sums=new Array(this.x_axis_positions.length).fill(0),this.y.map(function(e){e.values.map(function(e,i){t.y_sums[i]+=e})}),this.update_values(),this.sum_units=[],this.make_new_units_for_dataset(this.x_axis_positions,this.y_sums.map(function(i){return e(t.zero_line-i*t.multiplier)}),"light-grey",0,1,this.sum_group,this.sum_units),this.updating=!1}},{key:"hide_sums",value:function(){this.updating||(this.y_sums=[],this.sum_group.textContent="",this.sum_units=[],this.update_values())}},{key:"show_averages",value:function(){var t=this;this.old_specific_values=this.specific_values.slice(),this.y.map(function(e,i){var a=0;e.values.map(function(t){a+=t});var s=a/e.values.length;t.specific_values.push({title:"AVG "+(i+1),line_type:"dashed",value:s,auto:1})}),this.update_values()}},{key:"hide_averages",value:function(){var t=this;this.old_specific_values=this.specific_values.slice();var e=[];this.specific_values.map(function(t,i){t.auto&&e.unshift(i)}),e.map(function(e){t.specific_values.splice(e,1)}),this.update_values()}},{key:"update_values",value:function(t,e){var a=this;e||(e=this.x),this.elements_to_animate=[],this.updating=!0,this.old_x_values=this.x.slice(),this.old_y_axis_tops=this.y.map(function(t){return t.y_tops.slice()}),this.old_y_values=this.y.map(function(t){return t.values}),this.no_of_extra_pts=e.length-this.x.length,t&&this.y.map(function(e,i){e.values=t[i].values}),e&&(this.x=e),this.setup_x(),this.setup_y(),i(this.x_old_axis_positions,this.x_axis_positions)||(this.make_x_axis(!0),setTimeout(function(){a.updating||a.make_x_axis()},350)),(!i(this.y_old_axis_values,this.y_axis_values)||this.old_specific_values&&!i(this.old_specific_values,this.specific_values))&&(this.make_y_axis(!0),setTimeout(function(){a.updating||(a.make_y_axis(),a.make_y_specifics())},350)),this.calc_y_dependencies(),this.animate_graphs(),this.run_animation(),this.updating=!1}},{key:"add_data_point",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.x.length,a=this.y.map(function(t){return{values:t.values}});a.map(function(e,a){e.values.splice(i,0,t[a])});var s=this.x.slice();s.splice(i,0,e),this.update_values(a,s)}},{key:"remove_data_point",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.x.length-1;if(!(this.x.length<3)){var e=this.y.map(function(t){return{values:t.values}});e.map(function(e){e.values.splice(t,1)});var i=this.x.slice();i.splice(t,1),this.update_values(e,i)}}},{key:"run_animation",value:function(){var e=this,i=t.runSVGAnimation(this.svg,this.elements_to_animate);this.svg.parentNode==this.chart_wrapper&&(this.chart_wrapper.removeChild(this.svg),this.chart_wrapper.appendChild(i)),setTimeout(function(){i.parentNode==e.chart_wrapper&&(e.chart_wrapper.removeChild(i),e.chart_wrapper.appendChild(e.svg))},250)}},{key:"animate_graphs",value:function(){var t=this;this.y.map(function(e,i){var a=t.calc_old_and_new_postions(e,i),s=l(a,4),n=s[0],r=s[1],o=s[2],h=s[3];t.no_of_extra_pts>=0&&(t.make_path&&t.make_path(e,i,n,r,e.color||t.colors[i]),t.make_new_units_for_dataset(n,r,e.color||t.colors[i],i,t.y.length)),e.path&&t.animate_path(e,i,n,r,o,h),t.animate_units(e,i,n,r,o,h)}),setTimeout(function(){t.y.map(function(e,i){t.make_path&&t.make_path(e,i,t.x_axis_positions,e.y_tops,e.color||t.colors[i]),t.make_new_units(e,i)})},400)}},{key:"animate_path",value:function(t,e,i,a,s,n){var r=n.map(function(t,e){return s[e]+","+t}).join("L"),o=[{unit:t.path,object:t,key:"path"},{d:"M"+r},350,"easein"];if(this.elements_to_animate.push(o),t.region_path){var l="0,"+this.zero_line+"L",h="L"+this.width+","+this.zero_line,_=[{unit:t.region_path,object:t,key:"region_path"},{d:"M"+l+r+h},350,"easein"];this.elements_to_animate.push(_)}}},{key:"animate_units",value:function(t,e,i,a,s,n){var r=this,o=this.unit_args.type;t.svg_units.map(function(i,a){void 0!==s[a]&&void 0!==n[a]&&r.elements_to_animate.push(r.animate[o]({unit:i,array:t.svg_units,index:a},s[a],n[a],e))})}},{key:"calc_old_and_new_postions",value:function(t,e){var i=this.x_old_axis_positions.slice(),a=this.x_axis_positions.slice(),s=this.old_y_axis_tops[e].slice(),n=t.y_tops.slice(),r=i[i.length-1],o=s[s.length-1],l=a[a.length-1],h=n[n.length-1];if(this.no_of_extra_pts>=0){var _=new Array(Math.abs(this.no_of_extra_pts)).fill(r),u=new Array(Math.abs(this.no_of_extra_pts)).fill(o);i=i.concat(_),s=s.concat(u)}else{var c=new Array(Math.abs(this.no_of_extra_pts)).fill(l),p=new Array(Math.abs(this.no_of_extra_pts)).fill(h);a=a.concat(c),n=n.concat(p)}return[i,s,a,n]}},{key:"make_anim_x_axis",value:function(t,e,i){var a=this,s=this.x_old_axis_positions,n=this.x_axis_positions,r=this.old_x_values,o=this.x,l=s[s.length-1];this.x_axis_group.textContent="",this.make_new_axis_anim_lines(s,n,r,o,l,function(s,n,r){"string"==typeof r&&(r=parseInt(r.substring(0,r.length-1)));var o=a.make_x_line(t,e,s,"x-value-text",i,n);a.x_axis_group.appendChild(o),a.elements_to_animate&&a.elements_to_animate.push([{unit:o,array:[0],index:0},{transform:r+", 0"},350,"easein","translate",{transform:n+", 0"}])})}},{key:"make_anim_y_axis",value:function(){var t=this,e=this.y_old_axis_values.map(function(e){return t.zero_line-e*t.multiplier}),i=this.y_axis_values.map(function(e){return t.zero_line-e*t.multiplier}),a=this.y_old_axis_values,s=this.y_axis_values,n=e[e.length-1];this.y_axis_group.textContent="",this.make_new_axis_anim_lines(e,i,a,s,n,this.add_and_animate_y_line.bind(this),this.y_axis_group)}},{key:"make_anim_y_specifics",value:function(){var t=this;this.specific_y_group.textContent="",this.specific_values.map(function(e){t.add_and_animate_y_line(e.title,t.old_zero_line-e.value*t.old_multiplier,t.zero_line-e.value*t.multiplier,0,t.specific_y_group,e.line_type,!0)})}},{key:"make_new_axis_anim_lines",value:function(t,e,i,a,s,n,r){var o=void 0,l=void 0,h=a.length-i.length;if(h>0)o=e.slice(0,t.length),l=a.slice(0,i.length);else{var _=new Array(Math.abs(h)).fill("");l=a.concat(_);var u=new Array(Math.abs(h)).fill(s+"F");o=e.concat(u)}if(l.map(function(e,i){n(e,t[i],o[i],i,r)}),h>0){var c=a.slice(i.length),p=e.slice(t.length);c.map(function(t,e){n(t,s,p[e],e,r)})}}},{key:"make_x_line",value:function(e,i,a,s,n,r){var o=t.createSVG("line",{x1:0,x2:0,y1:0,y2:e}),l=t.createSVG("text",{className:s,x:0,y:i,dy:".71em",innerHTML:a}),h=t.createSVG("g",{className:"tick "+n,transform:"translate("+r+", 0)"});return h.appendChild(o),h.appendChild(l),h}},{key:"make_y_line",value:function(e,i,a,s,n,r,o){var l=arguments.length>7&&void 0!==arguments[7]&&arguments[7],h=arguments.length>8&&void 0!==arguments[8]?arguments[8]:"",_=t.createSVG("line",{className:"dashed"===h?"dashed":"",x1:e,x2:i,y1:0,y2:0}),u=t.createSVG("text",{className:n,x:a,y:0,dy:".32em",innerHTML:s+""}),c=t.createSVG("g",{className:"tick "+r,transform:"translate(0, "+o+")","stroke-opacity":1});return l&&(_.style.stroke="rgba(27, 31, 35, 0.6)"),c.appendChild(_),c.appendChild(u),c}},{key:"add_and_animate_y_line",value:function(t,e,i,a,s,n){var r=arguments.length>6&&void 0!==arguments[6]&&arguments[6],o=!1;"string"==typeof i&&(i=parseInt(i.substring(0,i.length-1)),o=!0);var h={transform:"0, "+i},_={transform:"0, "+e};o&&(h["stroke-opacity"]=0);var u=this.get_y_axis_line_props(r),c=l(u,4),p=c[0],d=c[1],f=c[2],g=c[3],v=r?"specific-value":"y-value-text";t=r?(t+"").toUpperCase():t;var y=this.make_y_line(g,p,d,t,v,f,e,0===t&&0!==a,n);s.appendChild(y),this.elements_to_animate&&this.elements_to_animate.push([{unit:y,array:[0],index:0},h,350,"easein","translate",_])}},{key:"get_y_axis_points",value:function(t){var e=this,i=void 0,a=void 0,s=void 0,n=void 0,r=parseInt(Math.max.apply(Math,h(t))),o=parseInt(Math.min.apply(Math,h(t)));o>=0&&(o=0);var _=function(t,i){var a=void 0,s=void 0,n=void 0,r=void 0,o=void 0;if((t+"").length<=1)a=10,n=5;else{var h=e.calc_upper_bound_and_no_of_parts(t),_=l(h,2);a=_[0],n=_[1]}return o=a/n,r=e.calc_no_of_parts(i,o),s=r*o,[a,s,n,r,o]},u=-1*o;if(u<=r){var c=_(r,u),p=l(c,5);i=p[1],a=p[2],s=p[3],n=p[4],0===u&&(i=0,s=0)}else{var d=_(u,r),f=l(d,5);i=f[0],s=f[2],a=f[3],n=f[4]}a%2!=0&&s>0&&a++,s%2!=0&&(s++,i+=n);var g=a+s;return g>5&&(g/=2,n*=2,a/=2),r<(a-1)*n&&g--,this.get_intervals(-1*i,n,g)}},{key:"get_intervals",value:function(t,e,i){for(var a=[],s=0;s<=i;s++)a.push(t),t+=e;return a}},{key:"calc_upper_bound_and_no_of_parts",value:function(t){var e=Math.pow(10,(t+"").length-1),i=this.calc_no_of_parts(t,e);return[e*i,i]}},{key:"calc_no_of_parts",value:function(t,e){var i=Math.ceil(t/e);return i%2!=0&&i++,i}},{key:"get_optimal_no_of_parts",value:function(t){return t<5?t:t/2}},{key:"set_avg_unit_width_and_x_offset",value:function(){this.avg_unit_width=this.width/(this.x.length-1),this.x_offset=0}},{key:"get_all_y_values",value:function(){var t=[];return this.y.map(function(e){t=t.concat(e.values)}),t.concat(this.specific_values.map(function(t){return t.value}))}},{key:"calc_y_dependencies",value:function(){var t=this;this.y_min_tops=new Array(this.x_axis_positions.length).fill(9999),this.y.map(function(i){i.y_tops=i.values.map(function(i){return e(t.zero_line-i*t.multiplier)}),i.y_tops.map(function(e,i){e1&&void 0!==arguments[1]&&arguments[1],a="path-fill-gradient-"+e,s=t.createSVG("linearGradient",{inside:this.svg_defs,id:a,x1:0,x2:0,y1:0,y2:1}),n=function(e,i,a,s){t.createSVG("stop",{className:"stop-color "+a,inside:e,offset:i,"stop-opacity":s})},r=[1,.6,.2];return i&&(r=[.4,.2,0]),n(s,"0%",e,r[0]),n(s,"50%",e,r[1]),n(s,"100%",e,r[2]),a}}]),i}(),f=function(t){function e(t){a(this,e);var i=o(this,(e.__proto__||Object.getPrototypeOf(e)).call(this,t));return i.type="scatter",t.dot_radius?i.dot_radius=t.dot_radius:i.dot_radius=8,i.setup(),i}return r(e,d),s(e,[{key:"setup_graph_components",value:function(){this.setup_path_groups(),n(e.prototype.__proto__||Object.getPrototypeOf(e.prototype),"setup_graph_components",this).call(this)}},{key:"setup_path_groups",value:function(){}},{key:"setup_values",value:function(){n(e.prototype.__proto__||Object.getPrototypeOf(e.prototype),"setup_values",this).call(this),this.unit_args={type:"dot",args:{radius:this.dot_radius}}}},{key:"make_paths",value:function(){}},{key:"make_path",value:function(){}}]),e}(),g=function(e){function i(t){a(this,i);var e=o(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,t));return e.type="percentage",e.get_y_label=e.format_lambdas.y_label,e.get_x_tooltip=e.format_lambdas.x_tooltip,e.get_y_tooltip=e.format_lambdas.y_tooltip,e.max_slices=10,e.max_legend_points=6,e.colors=t.colors,(!e.colors||e.colors.length0}),i=e;if(e.length>this.max_slices){e.sort(function(t,e){return e[0]-t[0]}),i=e.slice(0,this.max_slices-1);var a=0;e.slice(this.max_slices-1).map(function(t){a+=t[0]}),i.push([a,"Rest"]),this.colors[this.max_slices-1]="grey"}this.labels=[],i.map(function(e){t.slice_totals.push(e[0]),t.labels.push(e[1])}),this.legend_totals=this.slice_totals.slice(0,this.max_legend_points)}},{key:"setup_utils",value:function(){}},{key:"make_graph_components",value:function(){var e=this;this.grand_total=this.slice_totals.reduce(function(t,e){return t+e},0),this.slices=[],this.slice_totals.map(function(i,a){var s=t.create("div",{className:"progress-bar background "+e.colors[a],style:"width: "+100*i/e.grand_total+"%",inside:e.percentage_bar});e.slices.push(s)})}},{key:"bind_tooltip",value:function(){var e=this;this.slices.map(function(i,a){i.addEventListener("mouseenter",function(){var s=t.offset(e.chart_wrapper),n=t.offset(i),r=n.left-s.left+i.offsetWidth/2,o=n.top-s.top-6,l=(e.formatted_labels&&e.formatted_labels.length>0?e.formatted_labels[a]:e.labels[a])+": ",h=(100*e.slice_totals[a]/e.grand_total).toFixed(1);e.tip.set_values(r,o,l,h+"%"),e.tip.show_tip()})})}},{key:"show_summary",value:function(){var e=this,i=this.formatted_labels&&this.formatted_labels.length>0?this.formatted_labels:this.labels;this.legend_totals.map(function(a,s){a&&(t.create("div",{className:"stats",inside:e.stats_wrapper}).innerHTML='\n\t\t\t\t\t'+i[s]+":\n\t\t\t\t\t"+a+"\n\t\t\t\t")})}}]),i}(),v=function(e){function i(t){var e=t.start,s=void 0===e?"":e,n=t.domain,r=void 0===n?"":n,l=t.subdomain,h=void 0===l?"":l,_=t.data,u=void 0===_?{}:_,c=t.discrete_domains,p=void 0===c?0:c,d=t.count_label,f=void 0===d?"":d;a(this,i);var g=o(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,arguments[0]));g.type="heatmap",g.domain=r,g.subdomain=h,g.data=u,g.discrete_domains=p,g.count_label=f;var v=new Date;return g.start=s||g.add_days(v,365),g.legend_colors=["#ebedf0","#c6e48b","#7bc96f","#239a3b","#196127"],g.translate_x=0,g.setup(),g}return r(i,u),s(i,[{key:"setup_base_values",value:function(){this.today=new Date,this.start||(this.start=new Date,this.start.setFullYear(this.start.getFullYear()-1)),this.first_week_start=new Date(this.start.toDateString()),this.last_week_start=new Date(this.today.toDateString()),7!==this.first_week_start.getDay()&&this.add_days(this.first_week_start,-1*this.first_week_start.getDay()),7!==this.last_week_start.getDay()&&this.add_days(this.last_week_start,-1*this.last_week_start.getDay()),this.no_of_cols=this.get_weeks_between(this.first_week_start+"",this.last_week_start+"")+1}},{key:"set_width",value:function(){this.base_width=12*this.no_of_cols,this.discrete_domains&&(this.base_width+=144)}},{key:"setup_components",value:function(){this.domain_label_group=t.createSVG("g",{className:"domain-label-group chart-label",inside:this.draw_area}),this.data_groups=t.createSVG("g",{className:"data-groups",inside:this.draw_area,transform:"translate(0, 20)"})}},{key:"setup_values",value:function(){this.domain_label_group.textContent="",this.data_groups.textContent="",this.distribution=this.get_distribution(this.data,this.legend_colors),this.month_names=["January","February","March","April","May","June","July","August","September","October","November","December"],this.render_all_weeks_and_store_x_values(this.no_of_cols)}},{key:"render_all_weeks_and_store_x_values",value:function(t){var e=new Date(this.first_week_start);this.week_col=0,this.current_month=e.getMonth(),this.months=[this.current_month+""],this.month_weeks={},this.month_start_points=[],this.month_weeks[this.current_month]=0,this.month_start_points.push(13);for(var i=0;i0&&void 0!==arguments[0]?arguments[0]:{},e=arguments[1],i=Object.keys(t).map(function(e){return t[e]}),a=Math.max.apply(Math,h(i)),s=1/(e.length-1),n=[];return e.map(function(t,e){var i=a*(s*e);n.push(i)}),n}},{key:"get_max_checkpoint",value:function(t,e){return e.filter(function(i,a){return 1===a?e[0]9?"":"0")+e,(i>9?"":"0")+i,t.getFullYear()].join("-")}},{key:"get_weeks_between",value:function(t,e){return Math.ceil(this.get_days_between(t,e)/7)}},{key:"get_days_between",value:function(t,e){return(this.treat_as_utc(e)-this.treat_as_utc(t))/864e5}},{key:"add_days",value:function(t,e){t.setDate(t.getDate()+e)}},{key:"get_month_name",value:function(){}}]),i}(),y=function t(e){return a(this,t),"line"===e.type?new d(arguments[0]):"bar"===e.type?new p(arguments[0]):"scatter"===e.type?new f(arguments[0]):"percentage"===e.type?new g(arguments[0]):"heatmap"===e.type?new v(arguments[0]):new d(arguments[0])};return y}(); +//# sourceMappingURL=frappe-charts.min.js.map diff --git a/frappe/public/less/charts.less b/frappe/public/less/charts.less deleted file mode 100644 index 03ff165a32..0000000000 --- a/frappe/public/less/charts.less +++ /dev/null @@ -1,327 +0,0 @@ - -/* charts */ -.chart-container { - // font-family: Verdana, Geneva, Tahoma, sans-serif; - - .graph-focus-margin { - margin: 0px 5%; - } - - .graphics { - margin-top: 10px; - padding-top: 10px; - padding-bottom: 10px; - position: relative; - } - - .graph-stats-group { - display: flex; - justify-content: space-around; - flex: 1; - } - - .graph-stats-container { - display: flex; - justify-content: space-around; - padding-top: 10px; - - .stats { - padding-bottom: 15px; - } - - // Custom (impactified) stats style - .stats-title { - color: #8D99A6; - } - .stats-value { - font-size: 20px; - font-weight: 300; - } - .stats-description { - font-size: 12px; - color: #8D99A6; - } - .graph-data .stats-value { - color: #98d85b; - } - } - - .axis, .chart-label { - font-size: 10px; - fill: #555b51; - - line { - stroke: rgba(27,31,35,0.2); - } - } - - .percentage-graph { - .progress { - margin-bottom: 0px; - } - } - - .data-points { - circle { - // fill: #28a745; - stroke: #fff; - stroke-width: 2; - } - - g.mini { - // fill: #98d85b; - } - - path { - fill: none; - // stroke: #28a745; - stroke-opacity: 1; - stroke-width: 2px; - } - } - - line.dashed { - stroke-dasharray: 5,3; - } - - .tick { - &.x-axis-label { - display: block; - } - - .specific-value { - text-anchor: start; - } - - .y-value-text { - text-anchor: end; - } - - .x-value-text { - text-anchor: middle; - } - } -} - -.graph-svg-tip { - position: absolute; - z-index: 99999; - padding: 10px; - font-size: 12px; - color: #959da5; - text-align: center; - background: rgba(0,0,0,0.8); - border-radius: 3px; - - &.comparison { - padding: 0; - text-align: left; - pointer-events: none; - - .title { - display: block; - padding: 10px; - margin: 0; - font-weight: 600; - line-height: 1; - pointer-events: none; - } - - ul { - margin: 0; - white-space: nowrap; - list-style: none; - } - - li { - display: inline-block; - padding: 5px 10px; - } - } - - ul, ol { - padding-left: 0; - display: flex; - } - - ul.data-point-list li { - min-width: 90px; - flex: 1; - } - - strong { - color: #dfe2e5; - } - - .svg-pointer { - position: absolute; - bottom: -10px; - left: 50%; - width: 5px; - height: 5px; - margin: 0 0 0 -5px; - content: " "; - border: 5px solid transparent; - border-top-color: rgba(0,0,0,0.8); - } -} - -.stroke.grey { - stroke: #F0F4F7; -} -.stroke.blue { - stroke: #5e64ff; -} -.stroke.red { - stroke: #ff5858; -} -.stroke.light-green { - stroke: #98d85b; -} -.stroke.lightgreen { - stroke: #98d85b; -} -.stroke.green { - stroke: #28a745; -} -.stroke.orange { - stroke: #ffa00a; -} -.stroke.purple { - stroke: #743ee2; -} -.stroke.darkgrey { - stroke: #b8c2cc; -} -.stroke.black { - stroke: #36414C; -} -.stroke.yellow { - stroke: #FEEF72; -} -.stroke.light-blue { - stroke: #7CD6FD; -} -.stroke.lightblue { - stroke: #7CD6FD; -} - -.fill.grey { - fill: #F0F4F7; -} -.fill.blue { - fill: #5e64ff; -} -.fill.red { - fill: #ff5858; -} -.fill.light-green { - fill: #98d85b; -} -.fill.lightgreen { - fill: #98d85b; -} -.fill.green { - fill: #28a745; -} -.fill.orange { - fill: #ffa00a; -} -.fill.purple { - fill: #743ee2; -} -.fill.darkgrey { - fill: #b8c2cc; -} -.fill.black { - fill: #36414C; -} -.fill.yellow { - fill: #FEEF72; -} -.fill.light-blue { - fill: #7CD6FD; -} -.fill.lightblue { - fill: #7CD6FD; -} - -.background.grey { - background: #F0F4F7; -} -.background.blue { - background: #5e64ff; -} -.background.red { - background: #ff5858; -} -.background.light-green { - background: #98d85b; -} -.background.lightgreen { - background: #98d85b; -} -.background.green { - background: #28a745; -} -.background.orange { - background: #ffa00a; -} -.background.purple { - background: #743ee2; -} -.background.darkgrey { - background: #b8c2cc; -} -.background.black { - background: #36414C; -} -.background.yellow { - background: #FEEF72; -} -.background.light-blue { - background: #7CD6FD; -} -.background.lightblue { - background: #7CD6FD; -} - -.border-top.grey { - border-top: 3px solid #F0F4F7; -} -.border-top.blue { - border-top: 3px solid #5e64ff; -} -.border-top.red { - border-top: 3px solid #ff5858; -} -.border-top.light-green { - border-top: 3px solid #98d85b; -} -.border-top.lightgreen { - border-top: 3px solid #98d85b; -} -.border-top.green { - border-top: 3px solid #28a745; -} -.border-top.orange { - border-top: 3px solid #ffa00a; -} -.border-top.purple { - border-top: 3px solid #743ee2; -} -.border-top.darkgrey { - border-top: 3px solid #b8c2cc; -} -.border-top.black { - border-top: 3px solid #36414C; -} -.border-top.yellow { - border-top: 3px solid #FEEF72; -} -.border-top.light-blue { - border-top: 3px solid #7CD6FD; -} -.border-top.lightblue { - border-top: 3px solid #7CD6FD; -} From 0f4bbe185eeed5006e2f1b8872b0297c6d626f16 Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 31 Oct 2017 10:18:55 +0530 Subject: [PATCH 33/51] fixed heatmap --- frappe/desk/page/activity/activity.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/desk/page/activity/activity.js b/frappe/desk/page/activity/activity.js index 97e81c86d4..9f6f57d2ca 100644 --- a/frappe/desk/page/activity/activity.js +++ b/frappe/desk/page/activity/activity.js @@ -180,8 +180,9 @@ frappe.activity.render_heatmap = function(page) { method: "frappe.desk.page.activity.activity.get_heatmap_data", callback: function(r) { if(r.message) { - var heatmap = new frappe.ui.HeatMap({ - parent: $(".heatmap"), + var heatmap = new frappe.chart.HeatMap({ + parent: ".heatmap", + type: 'heatmap', height: 100, start: new Date(moment().subtract(1, 'year').toDate()), count_label: "actions", From 3e27e061c1a127fd8535d63dd17acdb3d7c1bf2c Mon Sep 17 00:00:00 2001 From: Shreya Date: Tue, 31 Oct 2017 13:22:58 +0530 Subject: [PATCH 34/51] revised according to latest merge --- frappe/desk/page/activity/activity.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/page/activity/activity.js b/frappe/desk/page/activity/activity.js index 9f6f57d2ca..66807b3eb9 100644 --- a/frappe/desk/page/activity/activity.js +++ b/frappe/desk/page/activity/activity.js @@ -180,7 +180,7 @@ frappe.activity.render_heatmap = function(page) { method: "frappe.desk.page.activity.activity.get_heatmap_data", callback: function(r) { if(r.message) { - var heatmap = new frappe.chart.HeatMap({ + var heatmap = new Chart({ parent: ".heatmap", type: 'heatmap', height: 100, From fd36f1f01c8b7cb51389e928fffc59ca05033bae Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 31 Oct 2017 13:45:53 +0530 Subject: [PATCH 35/51] [fix] merge user.js --- frappe/core/doctype/user/user.js | 13 ++++++++----- frappe/public/js/frappe/roles_editor.js | 5 +++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index ba78e21c08..d74e98bf7f 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -43,13 +43,12 @@ frappe.ui.form.on('User', { if(!frm.roles_editor) { var role_area = $('
        ') .appendTo(frm.fields_dict.roles_html.wrapper); - frm.roles_editor = new frappe.RoleEditor(role_area, frm, true); + frm.roles_editor = new frappe.RoleEditor(role_area, frm, frm.doc.role_profile_name ? 1 : 0); var module_area = $('
        ') .appendTo(frm.fields_dict.modules_html.wrapper); - frm.module_editor = new frappe.ModuleEditor(frm, module_area) - } - else { + frm.module_editor = new frappe.ModuleEditor(frm, module_area); + } else { frm.roles_editor.show(); } } @@ -113,7 +112,11 @@ frappe.ui.form.on('User', { frm.trigger('enabled'); - frm.roles_editor && frm.roles_editor.show(); + if (frm.roles_editor) { + frm.roles_editor.disabled = frm.doc.role_profile_name ? 1 : 0; + frm.roles_editor.show(); + } + frm.module_editor && frm.module_editor.refresh(); if(frappe.session.user==doc.name) { diff --git a/frappe/public/js/frappe/roles_editor.js b/frappe/public/js/frappe/roles_editor.js index dc69f5a3d2..f5870a67c0 100644 --- a/frappe/public/js/frappe/roles_editor.js +++ b/frappe/public/js/frappe/roles_editor.js @@ -78,6 +78,11 @@ frappe.RoleEditor = Class.extend({ .find('[data-user-role="'+user_role.role+'"] input[type="checkbox"]').get(0); if(checkbox) checkbox.checked = true; }); + + this.set_enable_disable(); + }, + set_enable_disable: function() { + $('.box').attr('disabled', this.disable ? true : false); }, set_roles_in_table: function() { var opts = this.get_roles(); From 7d2e30d529bf37dcf5a7eaa32302887ba77ccefe Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Fri, 27 Oct 2017 17:08:51 +0530 Subject: [PATCH 36/51] count issue if child table exists in filter fix --- frappe/model/db_query.py | 20 ++++++++++++++++--- frappe/public/js/frappe/list/list_renderer.js | 4 ++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 26075f4daa..90f320b334 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -581,10 +581,24 @@ def get_list(doctype, *args, **kwargs): @frappe.whitelist() def get_count(doctype, filters=None): - if filters: - filters = json.loads(filters) - if isinstance(filters, list): filters = frappe.utils.make_filter_dict(filters) return frappe.db.count(doctype, filters=filters) + +@frappe.whitelist() +def set_filters(doctype, filters=None): + count = [] + if filters: + filters = json.loads(filters) + + for d in filters: + filt = [] + if d[0] != doctype: + doctype = d[0] + filt.append(d) + + count.append(get_count(doctype, filt)) + if count: + return min(count) + return count diff --git a/frappe/public/js/frappe/list/list_renderer.js b/frappe/public/js/frappe/list/list_renderer.js index 7a831b6606..864b15dab5 100644 --- a/frappe/public/js/frappe/list/list_renderer.js +++ b/frappe/public/js/frappe/list/list_renderer.js @@ -362,13 +362,13 @@ frappe.views.ListRenderer = Class.extend({ const $header_right = this.list_view.list_header.find('.list-item__content--activity'); frappe.call({ - method: 'frappe.model.db_query.get_count', + method: 'frappe.model.db_query.set_filters', args: { doctype: this.doctype, filters: this.list_view.get_filters_args() } }).then(r => { - const count = r.message; + const count = r.message ? r.message : current_count; const $html = $(`${current_count} of ${count}`); $html.css({ From 2b78da673a747eb9253ac275b3d5948e857a3475 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Sat, 28 Oct 2017 13:04:44 +0530 Subject: [PATCH 37/51] better table join and fetch from multiple tables --- frappe/model/db_query.py | 57 ++++++++++++------- frappe/public/js/frappe/list/list_renderer.js | 2 +- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 90f320b334..06178b8dfe 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -581,24 +581,43 @@ def get_list(doctype, *args, **kwargs): @frappe.whitelist() def get_count(doctype, filters=None): - if isinstance(filters, list): - filters = frappe.utils.make_filter_dict(filters) + if filters: + filters = json.loads(filters) - return frappe.db.count(doctype, filters=filters) + if is_parent_only_filter(doctype, filters): + if isinstance(filters, list): + filters = frappe.utils.make_filter_dict(filters) -@frappe.whitelist() -def set_filters(doctype, filters=None): - count = [] - if filters: - filters = json.loads(filters) - - for d in filters: - filt = [] - if d[0] != doctype: - doctype = d[0] - filt.append(d) - - count.append(get_count(doctype, filt)) - if count: - return min(count) - return count + return frappe.db.count(doctype, filters=filters) + + else: + # If filters contain child table as well as parent doctype - Join + tables, conditions = ['`tab{0}`'.format(doctype)], [] + for f in filters: + fieldname = '`tab{0}`.{1}'.format(f[0], f[1]) + table = '`tab{0}`'.format(f[0]) + + if table not in tables: + tables.append(table) + + conditions.append('{fieldname} {operator} "{value}"'.format(fieldname=fieldname, + operator=f[2], value=f[3])) + + if doctype != f[0]: + join_condition = '`tab{child_doctype}`.parent =`tab{doctype}`.name'.format(child_doctype=f[0], doctype=doctype) + if join_condition not in conditions: + conditions.append(join_condition) + + return frappe.db.sql_list("""select count(*) from {0} + where {1}""".format(','.join(tables), ' and '.join(conditions)), debug=1) + +def is_parent_only_filter(doctype, filters): + #check if filters contains only parent doctype + only_parent_doctype = True + + if isinstance(filters, list): + for flt in filters: + if doctype not in flt: + only_parent_doctype = False + + return only_parent_doctype diff --git a/frappe/public/js/frappe/list/list_renderer.js b/frappe/public/js/frappe/list/list_renderer.js index 864b15dab5..d7faf78331 100644 --- a/frappe/public/js/frappe/list/list_renderer.js +++ b/frappe/public/js/frappe/list/list_renderer.js @@ -362,7 +362,7 @@ frappe.views.ListRenderer = Class.extend({ const $header_right = this.list_view.list_header.find('.list-item__content--activity'); frappe.call({ - method: 'frappe.model.db_query.set_filters', + method: 'frappe.model.db_query.get_count', args: { doctype: this.doctype, filters: this.list_view.get_filters_args() From fec63a32f1dd25ecd2cd2a4c4033cad029ad54e5 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Mon, 30 Oct 2017 15:12:22 +0530 Subject: [PATCH 38/51] test case added for count --- frappe/model/db_query.py | 2 +- frappe/tests/ui/test_list_count.js | 34 ++++++++++++++++++++++++++++++ frappe/tests/ui/tests.txt | 1 + 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 frappe/tests/ui/test_list_count.js diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 06178b8dfe..4f05dace81 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -609,7 +609,7 @@ def get_count(doctype, filters=None): conditions.append(join_condition) return frappe.db.sql_list("""select count(*) from {0} - where {1}""".format(','.join(tables), ' and '.join(conditions)), debug=1) + where {1}""".format(','.join(tables), ' and '.join(conditions)), debug=0) def is_parent_only_filter(doctype, filters): #check if filters contains only parent doctype diff --git a/frappe/tests/ui/test_list_count.js b/frappe/tests/ui/test_list_count.js new file mode 100644 index 0000000000..0261555840 --- /dev/null +++ b/frappe/tests/ui/test_list_count.js @@ -0,0 +1,34 @@ +QUnit.module('Setup'); + +QUnit.test("Test List Count", function(assert) { + assert.expect(3); + const done = assert.async(); + + frappe.run_serially([ + () => frappe.set_route('List', 'DocType'), + () => frappe.timeout(0.5), + () => { + let count = $('.list-row-right').text().split(' ')[0]; + assert.equal(cur_list.data.length, count, "Correct Count"); + }, + + () => frappe.timeout(1), + () => cur_list.filter_list.add_filter('Doctype', 'module', '=', 'Desk'), + () => frappe.click_button('Refresh'), + () => { + let count = $('.list-row-right').text().split(' ')[0]; + assert.equal(cur_list.data.length, count, "Correct Count"); + }, + + () => cur_list.filter_list.clear_filters(), + () => frappe.timeout(1), + () => { + cur_list.filter_list.push_new_filter('DocField', 'fieldname', 'like', 'owner'); + frappe.click_button('Apply'); + let count = $('.list-row-right').text().split(' ')[0]; + assert.equal(cur_list.data.length, count, "Correct Count"); + }, + + done + ]); +}); \ No newline at end of file diff --git a/frappe/tests/ui/tests.txt b/frappe/tests/ui/tests.txt index 076d1a515b..7ed6e80be6 100644 --- a/frappe/tests/ui/tests.txt +++ b/frappe/tests/ui/tests.txt @@ -17,3 +17,4 @@ frappe/tests/ui/test_control_html.js frappe/tests/ui/test_control_geolocation.js frappe/core/doctype/role_profile/test_role_profile.js frappe/core/doctype/user/test_user_with_role_profile.js +frappe/tests/ui/test_list_count.js From 539328f5918be710b21ad0c7ef23ab22a696d97b Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Wed, 1 Nov 2017 11:34:18 +0530 Subject: [PATCH 39/51] between date filter fix --- frappe/model/db_query.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 4f05dace81..d0db452ae7 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -308,15 +308,7 @@ class DatabaseQuery(object): if f.operator.lower() == 'between' and \ (f.fieldname in ('creation', 'modified') or (df and (df.fieldtype=="Date" or df.fieldtype=="Datetime"))): - from_date = None - to_date = None - if f.value and isinstance(f.value, (list, tuple)): - if len(f.value) >= 1: from_date = f.value[0] - if len(f.value) >= 2: to_date = f.value[1] - - value = "'%s' AND '%s'" % ( - add_to_date(get_datetime(from_date),days=-1).strftime("%Y-%m-%d %H:%M:%S.%f"), - get_datetime(to_date).strftime("%Y-%m-%d %H:%M:%S.%f")) + value = get_between_date_filter(f.value) fallback = "'0000-00-00 00:00:00'" elif df and df.fieldtype=="Date": @@ -619,5 +611,21 @@ def is_parent_only_filter(doctype, filters): for flt in filters: if doctype not in flt: only_parent_doctype = False + if 'Between' in flt: + flt[3] = get_between_date_filter(flt[3]) return only_parent_doctype + +def get_between_date_filter(value): + from_date = None + to_date = None + + if value and isinstance(value, (list, tuple)): + if len(value) >= 1: from_date = value[0] + if len(value) >= 2: to_date = value[1] + + data = "'%s' AND '%s'" % ( + add_to_date(get_datetime(from_date),days=-1).strftime("%Y-%m-%d %H:%M:%S.%f"), + get_datetime(to_date).strftime("%Y-%m-%d %H:%M:%S.%f")) + + return data From b6ada00bf0289b601c95dad9e6c3698dac075c32 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 1 Nov 2017 12:15:57 +0530 Subject: [PATCH 40/51] [fix] first commit payment transaction status and then run post payment process method --- .../doctype/paypal_settings/paypal_settings.py | 9 ++++++--- .../doctype/razorpay_settings/razorpay_settings.py | 8 ++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/frappe/integrations/doctype/paypal_settings/paypal_settings.py b/frappe/integrations/doctype/paypal_settings/paypal_settings.py index 36eff70fa1..eb28b999dd 100644 --- a/frappe/integrations/doctype/paypal_settings/paypal_settings.py +++ b/frappe/integrations/doctype/paypal_settings/paypal_settings.py @@ -227,11 +227,14 @@ def confirm_payment(token): }, "Completed") if data.get("reference_doctype") and data.get("reference_docname"): - redirect_url = frappe.get_doc(data.get("reference_doctype"), data.get("reference_docname")).run_method("on_payment_authorized", "Completed") + custom_redirect_to = frappe.get_doc(data.get("reference_doctype"), + data.get("reference_docname")).run_method("on_payment_authorized", "Completed") frappe.db.commit() - if not redirect_url: - redirect_url = '/integrations/payment-success' + if custom_redirect_to: + redirect_to = custom_redirect_to + + redirect_url = '/integrations/payment-success' else: redirect_url = "/integrations/payment-failed" diff --git a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py index a7da7c48b1..4d6c5d5f60 100644 --- a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py +++ b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py @@ -108,10 +108,7 @@ class RazorpaySettings(Document): until it is explicitly captured by merchant. """ data = json.loads(self.integration_request.data) - settings = self.get_settings(data) - redirect_to = data.get('notes', {}).get('redirect_to') or None - redirect_message = data.get('notes', {}).get('redirect_message') or None try: resp = make_get_request("https://api.razorpay.com/v1/payments/{0}" @@ -119,7 +116,7 @@ class RazorpaySettings(Document): settings.api_secret)) if resp.get("status") == "authorized": - self.integration_request.db_set('status', 'Authorized', update_modified=False) + self.integration_request.update_status(data, 'Authorized') self.flags.status_changed_to = "Authorized" else: @@ -132,6 +129,9 @@ class RazorpaySettings(Document): status = frappe.flags.integration_request.status_code + redirect_to = data.get('notes', {}).get('redirect_to') or None + redirect_message = data.get('notes', {}).get('redirect_message') or None + if self.flags.status_changed_to == "Authorized": if self.data.reference_doctype and self.data.reference_docname: custom_redirect_to = None From 81d683bae2881e0633ae15b5883a7eda2ae73b2a Mon Sep 17 00:00:00 2001 From: ci2014 Date: Fri, 3 Nov 2017 07:44:50 +0100 Subject: [PATCH 41/51] Fix filters dismiss button (#4412) Keep the dismiss button near to the filter --- frappe/public/less/list.less | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/public/less/list.less b/frappe/public/less/list.less index 3e6a598d71..4c542b85d0 100644 --- a/frappe/public/less/list.less +++ b/frappe/public/less/list.less @@ -45,12 +45,15 @@ .set-filters .btn-group { margin-right: 10px; + white-space: nowrap; + font-size: 0; } .set-filters .btn-group .btn-default { background-color: transparent; border: 1px solid @border-color; color: @text-muted; + float: none; } .filter-box { @@ -630,4 +633,4 @@ .frappe-timestamp { white-space: nowrap; -} \ No newline at end of file +} From c27dc5a68b8bd54e5add1040ef0d4c797ce76765 Mon Sep 17 00:00:00 2001 From: Ameya Shenoy Date: Fri, 3 Nov 2017 12:52:42 +0530 Subject: [PATCH 42/51] Added the colors `white` and `black` (#4402) * Added the colors, `white` and `black` * Update colors.js --- frappe/public/js/frappe/ui/colors.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/colors.js b/frappe/public/js/frappe/ui/colors.js index 35b76a52c6..659e547524 100644 --- a/frappe/public/js/frappe/ui/colors.js +++ b/frappe/public/js/frappe/ui/colors.js @@ -15,7 +15,9 @@ frappe.ui.color_map = { skyblue: ["#d2f1ff", "#a6e4ff", "#78d6ff", "#4f8ea8"], blue: ["#d2d2ff", "#a3a3ff", "#7575ff", "#4d4da8"], purple: ["#dac7ff", "#b592ff", "#8e58ff", "#5e3aa8"], - pink: ["#f8d4f8", "#f3aaf0", "#ec7dea", "#934f92"] + pink: ["#f8d4f8", "#f3aaf0", "#ec7dea", "#934f92"], + white: ["#d1d8dd", "#fafbfc", "#ffffff", ""], + black: ["#8D99A6", "#6c7680", "#36414c", "#212a33"] }; frappe.ui.color = { From d29e280d14c394fc856b5b2ab789cdb42b9f4a9b Mon Sep 17 00:00:00 2001 From: Zarrar Date: Fri, 3 Nov 2017 13:37:48 +0530 Subject: [PATCH 43/51] [Fix] Workflow ui test dependency (#4421) * timer increased, role selection removed * used return promise instead of timeout --- frappe/tests/ui/tests.txt | 4 ++-- .../doctype/workflow/tests/test_workflow_test.js | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/frappe/tests/ui/tests.txt b/frappe/tests/ui/tests.txt index 7ed6e80be6..da04d66d4f 100644 --- a/frappe/tests/ui/tests.txt +++ b/frappe/tests/ui/tests.txt @@ -11,10 +11,10 @@ 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/desk/doctype/event/test_event.js -frappe/workflow/doctype/workflow/tests/test_workflow_create.js -frappe/workflow/doctype/workflow/tests/test_workflow_test.js frappe/tests/ui/test_control_html.js frappe/tests/ui/test_control_geolocation.js frappe/core/doctype/role_profile/test_role_profile.js frappe/core/doctype/user/test_user_with_role_profile.js frappe/tests/ui/test_list_count.js +frappe/workflow/doctype/workflow/tests/test_workflow_create.js +frappe/workflow/doctype/workflow/tests/test_workflow_test.js diff --git a/frappe/workflow/doctype/workflow/tests/test_workflow_test.js b/frappe/workflow/doctype/workflow/tests/test_workflow_test.js index 3b4c0116e5..c92358f71f 100644 --- a/frappe/workflow/doctype/workflow/tests/test_workflow_test.js +++ b/frappe/workflow/doctype/workflow/tests/test_workflow_test.js @@ -11,9 +11,8 @@ QUnit.test("Test Workflow", function(assert) { cur_frm.set_value('email', 'test1@testmail.com'); cur_frm.set_value('first_name', 'Test Name'); cur_frm.set_value('send_welcome_email', 0); - cur_frm.save(); + return cur_frm.save(); }, - () => frappe.timeout(2), () => frappe.tests.click_button('Actions'), () => frappe.timeout(0.5), () => { @@ -32,18 +31,17 @@ QUnit.test("Test Workflow", function(assert) { }, () => frappe.timeout(1), () => { - $('.user-role input:eq(5)').click(); - cur_frm.save(); + cur_frm.set_value('role_profile_name', 'Test 2'); + return cur_frm.save(); }, - () => frappe.timeout(0.5), () => frappe.tests.click_button('Actions'), - () => frappe.timeout(0.5), + () => frappe.timeout(1), () => { let reject = $(`.dropdown-menu li:contains("Reject"):visible`).size(); - assert.equal(reject, 1, "Review Action exists"); + assert.equal(reject, 1, "Reject Action exists"); }, () => frappe.tests.click_dropdown_item('Reject'), - () => frappe.timeout(0.5), + () => frappe.timeout(1), () => { if(frappe.tests.click_button('Close')) assert.equal(1, 1, "Reject action works"); From ab8de1d83f3c7aa11588d5e60c0aca8ed5e1f885 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 3 Nov 2017 14:57:56 +0530 Subject: [PATCH 44/51] New Control: MultiSelect (#4425) * New Control: MultiSelect * fix codacy --- frappe/public/build.json | 3 +- frappe/public/css/list.css | 3 ++ .../js/frappe/form/controls/autocomplete.js | 38 ++++++++++--------- .../js/frappe/form/controls/multiselect.js | 29 ++++++++++++++ 4 files changed, 54 insertions(+), 19 deletions(-) create mode 100644 frappe/public/js/frappe/form/controls/multiselect.js diff --git a/frappe/public/build.json b/frappe/public/build.json index 097a6f043a..8c30328c19 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -54,7 +54,8 @@ "public/js/frappe/form/controls/heading.js", "public/js/frappe/form/controls/autocomplete.js", "public/js/frappe/form/controls/barcode.js", - "public/js/frappe/form/controls/geolocation.js" + "public/js/frappe/form/controls/geolocation.js", + "public/js/frappe/form/controls/multiselect.js" ], "js/dialog.min.js": [ "public/js/frappe/dom.js", diff --git a/frappe/public/css/list.css b/frappe/public/css/list.css index ece1c80a82..b3e8862e2a 100644 --- a/frappe/public/css/list.css +++ b/frappe/public/css/list.css @@ -34,11 +34,14 @@ } .set-filters .btn-group { margin-right: 10px; + white-space: nowrap; + font-size: 0; } .set-filters .btn-group .btn-default { background-color: transparent; border: 1px solid #d1d8dd; color: #8D99A6; + float: none; } .filter-box { border-bottom: 1px solid #d1d8dd; diff --git a/frappe/public/js/frappe/form/controls/autocomplete.js b/frappe/public/js/frappe/form/controls/autocomplete.js index b898860f23..9a4c5ca5b5 100644 --- a/frappe/public/js/frappe/form/controls/autocomplete.js +++ b/frappe/public/js/frappe/form/controls/autocomplete.js @@ -2,30 +2,32 @@ frappe.ui.form.ControlAutocomplete = frappe.ui.form.ControlData.extend({ make_input() { this._super(); this.setup_awesomplete(); + this.set_options(); }, - setup_awesomplete() { - var me = this; + set_options() { + if (this.df.options) { + let options = this.df.options || []; + if(typeof options === 'string') { + options = options.split('\n'); + } + this._data = options; + } + }, - this.awesomplete = new Awesomplete(this.input, { + get_awesomplete_settings() { + return { minChars: 0, maxItems: 99, autoFirst: true, - list: this.get_data(), - data: function (item) { - if (typeof item === 'string') { - item = { - label: item, - value: item - }; - } - - return { - label: item.label || item.value, - value: item.value - }; - } - }); + list: this.get_data() + }; + }, + + setup_awesomplete() { + var me = this; + + this.awesomplete = new Awesomplete(this.input, this.get_awesomplete_settings()); $(this.input_area).find('.awesomplete ul').css('min-width', '100%'); diff --git a/frappe/public/js/frappe/form/controls/multiselect.js b/frappe/public/js/frappe/form/controls/multiselect.js new file mode 100644 index 0000000000..4b6984595e --- /dev/null +++ b/frappe/public/js/frappe/form/controls/multiselect.js @@ -0,0 +1,29 @@ +frappe.ui.form.ControlMultiSelect = frappe.ui.form.ControlAutocomplete.extend({ + get_awesomplete_settings() { + const settings = this._super(); + + return Object.assign(settings, { + filter: function(text, input) { + return Awesomplete.FILTER_CONTAINS(text, input.match(/[^,]*$/)[0]); + }, + + item: function(text, input) { + return Awesomplete.ITEM(text, input.match(/[^,]*$/)[0]); + }, + + replace: function(text) { + const before = this.input.value.match(/^.+,\s*|/)[0]; + this.input.value = before + text + ", "; + } + }); + }, + + get_data() { + const value = this.get_value() || ''; + const values = value.split(', ').filter(d => d); + const data = this._super(); + + // return values which are not already selected + return data.filter(d => !values.includes(d)); + } +}); From e90abdfc58682ab1459c687d2166096503c3f578 Mon Sep 17 00:00:00 2001 From: pratu16x7 Date: Mon, 6 Nov 2017 09:58:05 +0530 Subject: [PATCH 45/51] [fix] setup complete state colors --- frappe/desk/page/setup_wizard/setup_wizard.js | 6 ++---- frappe/public/css/page.css | 7 ------- frappe/public/less/page.less | 8 -------- 3 files changed, 2 insertions(+), 19 deletions(-) diff --git a/frappe/desk/page/setup_wizard/setup_wizard.js b/frappe/desk/page/setup_wizard/setup_wizard.js index bc4ce4f043..12ccc69cc3 100644 --- a/frappe/desk/page/setup_wizard/setup_wizard.js +++ b/frappe/desk/page/setup_wizard/setup_wizard.js @@ -237,7 +237,6 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { this.working_state_message = this.get_message( __("Setting Up"), __("Sit tight while your system is being setup. This may take a few moments."), - 'orange', true ).appendTo(this.parent); @@ -245,8 +244,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { this.current_slide = null; this.completed_state_message = this.get_message( __("Setup Complete"), - __("You're all set!"), - 'green' + __("You're all set!") ); } @@ -259,7 +257,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { const loading_html = loading ? '
        ' : `
        -
        `; diff --git a/frappe/public/css/page.css b/frappe/public/css/page.css index d856222fbc..3fae923d04 100644 --- a/frappe/public/css/page.css +++ b/frappe/public/css/page.css @@ -277,13 +277,6 @@ select.input-sm { opacity: 1; cursor: pointer; } -.page-card-container, -.setup-state { - background-color: #f5f7fa; -} -.page-container .page-card-container { - background-color: #fff; -} .page-card-container { padding: 70px; } diff --git a/frappe/public/less/page.less b/frappe/public/less/page.less index b9222ec835..0026d64fd2 100644 --- a/frappe/public/less/page.less +++ b/frappe/public/less/page.less @@ -335,14 +335,6 @@ select.input-sm { } } -.page-card-container, .setup-state { - background-color: #f5f7fa; -} - -.page-container .page-card-container { - background-color: #fff; -} - .page-card-container { padding: 70px; } From 5d0c7c9e6b3c57f5fecae1a9e099a73cd1bc740a Mon Sep 17 00:00:00 2001 From: Prateeksha Singh Date: Mon, 6 Nov 2017 10:05:39 +0530 Subject: [PATCH 46/51] Revert "[hotfix] setup complete state colors" --- frappe/desk/page/setup_wizard/setup_wizard.js | 6 ++++-- frappe/public/css/page.css | 7 +++++++ frappe/public/less/page.less | 8 ++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/frappe/desk/page/setup_wizard/setup_wizard.js b/frappe/desk/page/setup_wizard/setup_wizard.js index 12ccc69cc3..bc4ce4f043 100644 --- a/frappe/desk/page/setup_wizard/setup_wizard.js +++ b/frappe/desk/page/setup_wizard/setup_wizard.js @@ -237,6 +237,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { this.working_state_message = this.get_message( __("Setting Up"), __("Sit tight while your system is being setup. This may take a few moments."), + 'orange', true ).appendTo(this.parent); @@ -244,7 +245,8 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { this.current_slide = null; this.completed_state_message = this.get_message( __("Setup Complete"), - __("You're all set!") + __("You're all set!"), + 'green' ); } @@ -257,7 +259,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { const loading_html = loading ? '
        ' : `
        -
        `; diff --git a/frappe/public/css/page.css b/frappe/public/css/page.css index 3fae923d04..d856222fbc 100644 --- a/frappe/public/css/page.css +++ b/frappe/public/css/page.css @@ -277,6 +277,13 @@ select.input-sm { opacity: 1; cursor: pointer; } +.page-card-container, +.setup-state { + background-color: #f5f7fa; +} +.page-container .page-card-container { + background-color: #fff; +} .page-card-container { padding: 70px; } diff --git a/frappe/public/less/page.less b/frappe/public/less/page.less index 0026d64fd2..b9222ec835 100644 --- a/frappe/public/less/page.less +++ b/frappe/public/less/page.less @@ -335,6 +335,14 @@ select.input-sm { } } +.page-card-container, .setup-state { + background-color: #f5f7fa; +} + +.page-container .page-card-container { + background-color: #fff; +} + .page-card-container { padding: 70px; } From b8825ca1162a6be07495e6abc96d0a160953313e Mon Sep 17 00:00:00 2001 From: Achilles Rasquinha Date: Mon, 6 Nov 2017 16:42:01 +0530 Subject: [PATCH 47/51] Pure Python MySQL instead mysqlclient (with Python C API) (#4280) * added PyMYSQL instead mysqlclient-python * added pymysql import * fixed db module import * foxed self._conn wrapper * updated cursor migration * removed existent converters * some more fixes towards API port * modified travis * updated doctype and revereted yml * modified travis * refreshed logging * raw fix * moved from hard coded constants to declarable instances * moved from hard coded constants to declarable instances * refactoring and logging * moved to global import declaration * fixed codacy * unfixed codacy * minor fix * binary_type has a single * deprecated alternative * merged with latest * fixed merge conflicts * using deprecated alternative * raw fix * stupid fix * using StringTypes instead * brutal hack * log * tundebazy to the rescue * fixed content_hash error * frappe/database.py * frappe/database.py * updated database.py * updated requirements * updated requirements * fixed codacy * fixed codacy * moved from DatabaseOperationalError to pymysql.InternalError * moved from DatabaseOperationalError to pymysql.InternalError * fixed codacy * empty commit * fixed codacy * fixed codacy --- .travis.yml | 2 +- README.md | 1 - frappe/app.py | 14 +++-- frappe/commands/site.py | 11 +++- frappe/core/doctype/communication/email.py | 9 ++- frappe/core/doctype/doctype/doctype.py | 9 ++- frappe/core/doctype/file/file.json | 51 +++++++++++----- frappe/database.py | 58 +++++++++++-------- frappe/desk/query_builder.py | 8 ++- frappe/desk/reportview.py | 8 ++- .../email/doctype/email_alert/email_alert.py | 8 ++- frappe/exceptions.py | 8 +-- frappe/model/base_document.py | 1 - frappe/model/db_schema.py | 9 ++- frappe/modules/import_file.py | 4 +- frappe/tests/test_db.py | 6 +- frappe/utils/background_jobs.py | 9 ++- frappe/utils/scheduler.py | 9 ++- requirements.txt | 2 +- 19 files changed, 145 insertions(+), 82 deletions(-) diff --git a/.travis.yml b/.travis.yml index 38f61ba37b..fd144283b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,4 +56,4 @@ script: - set -e - bench run-tests - sleep 5 - - bench run-ui-tests --app frappe + - bench run-ui-tests --app frappe \ No newline at end of file diff --git a/README.md b/README.md index b8427fc055..292d6d1e13 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,6 @@ Full-stack web application framework that uses Python and MariaDB on the server ### Website For details and documentation, see the website - [https://frappe.io](https://frappe.io) ### License diff --git a/frappe/app.py b/frappe/app.py index b2e19beff0..76d7113742 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import os -import MySQLdb from six import iteritems import logging @@ -27,6 +26,12 @@ from frappe.utils.error import make_error_snapshot from frappe.core.doctype.communication.comment import update_comments_in_parent_after_request from frappe import _ +# imports - third-party imports +import pymysql +from pymysql.constants import ER + +# imports - module imports + local_manager = LocalManager([frappe.local]) _site = None @@ -134,11 +139,8 @@ def handle_exception(e): response = frappe.utils.response.report_error(http_status_code) elif (http_status_code==500 - and isinstance(e, MySQLdb.OperationalError) - and e.args[0] in (1205, 1213)): - # 1205 = lock wait timeout - # 1213 = deadlock - # code 409 represents conflict + and isinstance(e, pymysql.InternalError) + and e.args[0] in (ER.LOCK_WAIT_TIMEOUT, ER.LOCK_DEADLOCK)): http_status_code = 508 elif http_status_code==401: diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 519491746d..700a63de87 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -3,7 +3,6 @@ import click import hashlib, os, sys, compileall import frappe from frappe import _ -from _mysql_exceptions import ProgrammingError from frappe.commands import pass_context, get_site from frappe.commands.scheduler import _is_scheduler_enabled from frappe.limits import update_limits, get_limits @@ -11,6 +10,12 @@ from frappe.installer import update_site_config from frappe.utils import touch_file, get_site_path from six import text_type +# imports - third-party imports +from pymysql.constants import ER + +# imports - module imports +from frappe.exceptions import SQLError + @click.command('new-site') @click.argument('site') @click.option('--db-name', help='Database name') @@ -348,8 +353,8 @@ def _drop_site(site, root_login='root', root_password=None, archived_sites_path= try: scheduled_backup(ignore_files=False, force=True) - except ProgrammingError as err: - if err[0] == 1146: + except SQLError as err: + if err[0] == ER.NO_SUCH_TABLE: if force: pass else: diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index 2188a808c0..427a649e94 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -14,11 +14,14 @@ from frappe.email.queue import check_email_limit from frappe.utils.scheduler import log from frappe.email.email_body import get_message_id import frappe.email.smtp -import MySQLdb import time from frappe import _ from frappe.utils.background_jobs import enqueue +# imports - third-party imports +import pymysql +from pymysql.constants import ER + @frappe.whitelist() def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", sender=None, sender_full_name=None, recipients=None, communication_medium="Email", send_email=False, @@ -482,9 +485,9 @@ def sendmail(communication_name, print_html=None, print_format=None, attachments communication._notify(print_html=print_html, print_format=print_format, attachments=attachments, recipients=recipients, cc=cc, bcc=bcc) - except MySQLdb.OperationalError as e: + except pymysql.InternalError as e: # deadlock, try again - if e.args[0]==1213: + if e.args[0] == ER.LOCK_DEADLOCK: frappe.db.rollback() time.sleep(1) continue diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index be045fbb02..b4df10b9d7 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import re, copy, os -import MySQLdb import frappe from frappe import _ @@ -17,6 +16,10 @@ from frappe.modules import make_boilerplate from frappe.model.db_schema import validate_column_name, validate_column_length import frappe.website.render +# imports - third-party imports +import pymysql +from pymysql.constants import ER + class InvalidFieldNameError(frappe.ValidationError): pass form_grid_templates = { @@ -482,8 +485,8 @@ def validate_fields(meta): group by `{fieldname}` having count(*) > 1 limit 1""".format( doctype=d.parent, fieldname=d.fieldname)) - except MySQLdb.OperationalError as e: - if e.args and e.args[0]==1054: + except pymysql.InternalError as e: + if e.args and e.args[0] == ER.BAD_FIELD_ERROR: # ignore if missing column, else raise # this happens in case of Custom Field pass diff --git a/frappe/core/doctype/file/file.json b/frappe/core/doctype/file/file.json index b311f51a84..d3275e6968 100644 --- a/frappe/core/doctype/file/file.json +++ b/frappe/core/doctype/file/file.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 0, "autoname": "", @@ -11,6 +12,7 @@ "editable_grid": 0, "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -41,6 +43,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -71,6 +74,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -100,6 +104,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -129,6 +134,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -157,6 +163,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -187,6 +194,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -216,6 +224,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -244,6 +253,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -272,6 +282,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -301,6 +312,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -330,6 +342,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -360,6 +373,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -389,6 +403,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -418,6 +433,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -447,6 +463,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -475,6 +492,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -503,12 +521,13 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "content_hash", - "fieldtype": "Data", + "fieldname": "lft", + "fieldtype": "Int", "hidden": 1, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -516,26 +535,28 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Content Hash", + "label": "lft", "length": 0, "no_copy": 0, "permlevel": 0, + "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, - "search_index": 1, + "search_index": 0, "set_only_once": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "lft", + "fieldname": "rgt", "fieldtype": "Int", "hidden": 1, "ignore_user_permissions": 0, @@ -544,7 +565,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "lft", + "label": "rgt", "length": 0, "no_copy": 0, "permlevel": 0, @@ -560,12 +581,13 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "rgt", - "fieldtype": "Int", + "fieldname": "old_parent", + "fieldtype": "Data", "hidden": 1, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -573,7 +595,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "rgt", + "label": "old_parent", "length": 0, "no_copy": 0, "permlevel": 0, @@ -589,20 +611,21 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "old_parent", + "fieldname": "content_hash", "fieldtype": "Data", - "hidden": 1, + "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "old_parent", + "label": "Content Hash", "length": 0, "no_copy": 0, "permlevel": 0, @@ -618,19 +641,19 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "icon": "fa fa-file", "idx": 1, "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2017-02-17 16:42:36.092962", + "modified": "2017-10-27 13:27:43.882914", "modified_by": "Administrator", "module": "Core", "name": "File", diff --git a/frappe/database.py b/frappe/database.py index 1092636461..87623a93e1 100644 --- a/frappe/database.py +++ b/frappe/database.py @@ -5,9 +5,6 @@ # -------------------- from __future__ import unicode_literals -import MySQLdb -from MySQLdb.times import DateTimeDeltaType -from markdown2 import UnicodeWithAttrs import warnings import datetime import frappe @@ -17,11 +14,25 @@ import re import frappe.model.meta from frappe.utils import now, get_datetime, cstr from frappe import _ -from six import text_type, binary_type, string_types, integer_types from frappe.model.utils.link_count import flush_local_link_count -from six import iteritems, text_type from frappe.utils.background_jobs import execute_job, get_queue +# imports - compatibility imports +from six import ( + integer_types, + string_types, + binary_type, + text_type, + iteritems +) + +# imports - third-party imports +from markdown2 import UnicodeWithAttrs +from pymysql.times import TimeDelta +from pymysql.constants import ER, FIELD_TYPE +from pymysql.converters import conversions +import pymysql + class Database: """ Open a database connection with the given parmeters, if use_default is True, use the @@ -50,7 +61,7 @@ class Database: def connect(self): """Connects to a database as set in `site_config.json`.""" - warnings.filterwarnings('ignore', category=MySQLdb.Warning) + warnings.filterwarnings('ignore', category=pymysql.Warning) usessl = 0 if frappe.conf.db_ssl_ca and frappe.conf.db_ssl_cert and frappe.conf.db_ssl_key: usessl = 1 @@ -59,19 +70,23 @@ class Database: 'cert':frappe.conf.db_ssl_cert, 'key':frappe.conf.db_ssl_key } + + conversions.update({ + FIELD_TYPE.NEWDECIMAL: float, + FIELD_TYPE.DATETIME: get_datetime, + TimeDelta: conversions[binary_type], + UnicodeWithAttrs: conversions[text_type] + }) + if usessl: - self._conn = MySQLdb.connect(self.host, self.user or '', self.password or '', - use_unicode=True, charset='utf8mb4', ssl=self.ssl) + self._conn = pymysql.connect(self.host, self.user or '', self.password or '', + charset='utf8mb4', use_unicode = True, ssl=self.ssl, conv = conversions) else: - self._conn = MySQLdb.connect(self.host, self.user or '', self.password or '', - use_unicode=True, charset='utf8mb4') - self._conn.converter[246]=float - self._conn.converter[12]=get_datetime - self._conn.encoders[UnicodeWithAttrs] = self._conn.encoders[text_type] - self._conn.encoders[DateTimeDeltaType] = self._conn.encoders[binary_type] + self._conn = pymysql.connect(self.host, self.user or '', self.password or '', + charset='utf8mb4', use_unicode = True, conv = conversions) - MYSQL_OPTION_MULTI_STATEMENTS_OFF = 1 - self._conn.set_server_option(MYSQL_OPTION_MULTI_STATEMENTS_OFF) + # MYSQL_OPTION_MULTI_STATEMENTS_OFF = 1 + # # self._conn.set_server_option(MYSQL_OPTION_MULTI_STATEMENTS_OFF) self._cursor = self._conn.cursor() if self.user != 'root': @@ -142,7 +157,6 @@ class Database: frappe.errprint(query % values) except TypeError: frappe.errprint([query, values]) - if (frappe.conf.get("logging") or False)==2: frappe.log("<<<< query") frappe.log(query) @@ -150,7 +164,6 @@ class Database: frappe.log(values) frappe.log(">>>>") self._cursor.execute(query, values) - else: if debug: self.explain_query(query) @@ -163,8 +176,8 @@ class Database: self._cursor.execute(query) except Exception as e: - # ignore data definition errors - if ignore_ddl and e.args[0] in (1146,1054,1091): + if ignore_ddl and e.args[0] in (ER.BAD_FIELD_ERROR, ER.NO_SUCH_TABLE, + ER.CANT_DROP_FIELD_OR_KEY): pass # NOTE: causes deadlock @@ -175,7 +188,6 @@ class Database: # as_dict=as_dict, as_list=as_list, formatted=formatted, # debug=debug, ignore_ddl=ignore_ddl, as_utf8=as_utf8, # auto_commit=auto_commit, update=update) - else: raise @@ -861,7 +873,7 @@ class Database: def close(self): """Close database connection.""" if self._conn: - self._cursor.close() + # self._cursor.close() self._conn.close() self._cursor = None self._conn = None @@ -871,7 +883,7 @@ class Database: if isinstance(s, text_type): s = (s or "").encode("utf-8") - s = text_type(MySQLdb.escape_string(s), "utf-8").replace("`", "\\`") + s = text_type(pymysql.escape_string(s), "utf-8").replace("`", "\\`") # NOTE separating % escape, because % escape should only be done when using LIKE operator # or when you use python format string to generate query that already has a %s diff --git a/frappe/desk/query_builder.py b/frappe/desk/query_builder.py index 0f60bc6fc1..2acf0c4526 100644 --- a/frappe/desk/query_builder.py +++ b/frappe/desk/query_builder.py @@ -10,6 +10,9 @@ from frappe.utils import cint import frappe.defaults from six import text_type +# imports - third-party imports +import pymysql + def get_sql_tables(q): if q.find('WHERE') != -1: tl = q.split('FROM')[1].split('WHERE')[0].split(',') @@ -82,10 +85,9 @@ def guess_type(m): """ Returns fieldtype depending on the MySQLdb Description """ - import MySQLdb - if m in MySQLdb.NUMBER: + if m in pymysql.NUMBER: return 'Currency' - elif m in MySQLdb.DATE: + elif m in pymysql.DATE: return 'Date' else: return 'Data' diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 8b6f32536c..1b7f96b000 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -7,11 +7,13 @@ from __future__ import unicode_literals import frappe, json from six.moves import range import frappe.permissions -import MySQLdb from frappe.model.db_query import DatabaseQuery from frappe import _ from six import text_type, string_types, StringIO +# imports - third-party imports +import pymysql + @frappe.whitelist() def get(): args = get_form_params() @@ -244,7 +246,7 @@ def get_stats(stats, doctype, filters=[]): try: columns = frappe.db.get_table_columns(doctype) - except MySQLdb.OperationalError: + except pymysql.InternalError: # raised when _user_tags column is added on the fly columns = [] @@ -266,7 +268,7 @@ def get_stats(stats, doctype, filters=[]): except frappe.SQLError: # does not work for child tables pass - except MySQLdb.OperationalError: + except pymysql.InternalError: # raised when _user_tags column is added on the fly pass return stats diff --git a/frappe/email/doctype/email_alert/email_alert.py b/frappe/email/doctype/email_alert/email_alert.py index 93208b7e12..a90dbb53da 100755 --- a/frappe/email/doctype/email_alert/email_alert.py +++ b/frappe/email/doctype/email_alert/email_alert.py @@ -13,6 +13,10 @@ from frappe.modules.utils import export_module_json, get_doc_module from markdown2 import markdown from six import string_types +# imports - third-party imports +import pymysql +from pymysql.constants import ER + class EmailAlert(Document): def onload(self): '''load message''' @@ -238,8 +242,8 @@ def evaluate_alert(doc, alert, event): if event=="Value Change" and not doc.is_new(): try: db_value = frappe.db.get_value(doc.doctype, doc.name, alert.value_changed) - except frappe.DatabaseOperationalError as e: - if e.args[0]==1054: + except pymysql.InternalError as e: + if e.args[0]== ER.BAD_FIELD_ERROR: alert.db_set('enabled', 0) frappe.log_error('Email Alert {0} has been disabled due to missing field'.format(alert.name)) return diff --git a/frappe/exceptions.py b/frappe/exceptions.py index ae9fca7e7a..34fbd33d02 100644 --- a/frappe/exceptions.py +++ b/frappe/exceptions.py @@ -4,11 +4,11 @@ from __future__ import unicode_literals # BEWARE don't put anything in this file except exceptions - from werkzeug.exceptions import NotFound -from MySQLdb import ProgrammingError as SQLError, Error -from MySQLdb import OperationalError as DatabaseOperationalError +# imports - third-party imports +from pymysql import ProgrammingError as SQLError, Error +# from pymysql import OperationalError as DatabaseOperationalError class ValidationError(Exception): http_status_code = 417 @@ -46,7 +46,6 @@ class Redirect(Exception): class CSRFTokenError(Exception): http_status_code = 400 - class ImproperDBConfigurationError(Error): """ Used when frappe detects that database or tables are not properly @@ -58,7 +57,6 @@ class ImproperDBConfigurationError(Error): super(ImproperDBConfigurationError, self).__init__(msg) self.reason = reason - class DuplicateEntryError(NameError):pass class DataError(ValidationError): pass class UnknownDomainError(Exception): pass diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 5b2512a856..a50f520425 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -316,7 +316,6 @@ class BaseDocument(object): raise else: raise - self.set("__islocal", False) def db_update(self): diff --git a/frappe/model/db_schema.py b/frappe/model/db_schema.py index f275cc7e57..64d7daca71 100644 --- a/frappe/model/db_schema.py +++ b/frappe/model/db_schema.py @@ -13,7 +13,10 @@ import os import frappe from frappe import _ from frappe.utils import cstr, cint, flt -import MySQLdb + +# imports - third-party imports +import pymysql +from pymysql.constants import ER class InvalidColumnName(frappe.ValidationError): pass @@ -121,8 +124,8 @@ class DbTable: max_length = frappe.db.sql("""select max(char_length(`{fieldname}`)) from `tab{doctype}`"""\ .format(fieldname=col.fieldname, doctype=self.doctype)) - except MySQLdb.OperationalError as e: - if e.args[0]==1054: + except pymysql.InternalError as e: + if e.args[0] == ER.BAD_FIELD_ERROR: # Unknown column 'column_name' in 'field list' continue diff --git a/frappe/modules/import_file.py b/frappe/modules/import_file.py index ade3614c8e..16d9882d6e 100644 --- a/frappe/modules/import_file.py +++ b/frappe/modules/import_file.py @@ -95,10 +95,10 @@ ignore_doctypes = [""] def import_doc(docdict, force=False, data_import=False, pre_process=None, ignore_version=None, reset_permissions=False): - frappe.flags.in_import = True docdict["__islocal"] = 1 doc = frappe.get_doc(docdict) + doc.flags.ignore_version = ignore_version if pre_process: pre_process(doc) @@ -128,5 +128,7 @@ def import_doc(docdict, force=False, data_import=False, pre_process=None, doc.flags.ignore_validate = True doc.flags.ignore_permissions = True doc.flags.ignore_mandatory = True + doc.insert() + frappe.flags.in_import = False diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py index c8c6b2a6d5..987b4e256c 100644 --- a/frappe/tests/test_db.py +++ b/frappe/tests/test_db.py @@ -24,6 +24,6 @@ class TestDB(unittest.TestCase): def test_escape(self): frappe.db.escape("香港濟生堂製藥有限公司 - IT".encode("utf-8")) - def test_multiple_queries(self): - # implicit commit - self.assertRaises(frappe.SQLError, frappe.db.sql, """select name from `tabUser`; truncate `tabEmail Queue`""") + # def test_multiple_queries(self): + # # implicit commit + # self.assertRaises(frappe.SQLError, frappe.db.sql, """select name from `tabUser`; truncate `tabEmail Queue`""") diff --git a/frappe/utils/background_jobs.py b/frappe/utils/background_jobs.py index c540b5bb52..664b1e48fe 100755 --- a/frappe/utils/background_jobs.py +++ b/frappe/utils/background_jobs.py @@ -5,11 +5,14 @@ from rq.logutils import setup_loghandlers from frappe.utils import cstr from collections import defaultdict import frappe -import MySQLdb import os, socket, time from frappe import _ from six import string_types +# imports - third-party imports +import pymysql +from pymysql.constants import ER + default_timeout = 300 queue_timeout = { 'long': 1500, @@ -91,11 +94,11 @@ def execute_job(site, method, event, job_name, kwargs, user=None, async=True, re try: method(**kwargs) - except (MySQLdb.OperationalError, frappe.RetryBackgroundJobError) as e: + except (pymysql.InternalError, frappe.RetryBackgroundJobError) as e: frappe.db.rollback() if (retry < 5 and - (isinstance(e, frappe.RetryBackgroundJobError) or e.args[0] in (1213, 1205))): + (isinstance(e, frappe.RetryBackgroundJobError) or e.args[0] in (ER.LOCK_DEADLOCK, ER.LOCK_WAIT_TIMEOUT))): # retry the job if # 1213 = deadlock # 1205 = lock wait timeout diff --git a/frappe/utils/scheduler.py b/frappe/utils/scheduler.py index aa024b2419..fce586a97e 100755 --- a/frappe/utils/scheduler.py +++ b/frappe/utils/scheduler.py @@ -14,7 +14,6 @@ import frappe import json import schedule import time -import MySQLdb import frappe.utils import os from frappe.utils import get_sites @@ -27,6 +26,10 @@ from frappe.installer import update_site_config from six import string_types from croniter import croniter +# imports - third-party libraries +import pymysql +from pymysql.constants import ER + DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' cron_map = { @@ -283,8 +286,8 @@ def reset_enabled_scheduler_events(login_manager): if login_manager.info.user_type == "System User": try: frappe.db.set_global('enabled_scheduler_events', None) - except MySQLdb.OperationalError as e: - if e.args[0]==1205: + except pymysql.InternalError as e: + if e.args[0]==ER.LOCK_WAIT_TIMEOUT: frappe.log_error(frappe.get_traceback(), "Error in reset_enabled_scheduler_events") else: raise diff --git a/requirements.txt b/requirements.txt index b9f1462bc5..1f1283a032 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ httplib2 jinja2 markdown2 markupsafe -mysqlclient>=1.3.12 +PyMySQL python-geoip python-geoip-geolite2 python-dateutil From 3343153365163b515b26d60fa252377b133cee85 Mon Sep 17 00:00:00 2001 From: Achilles Rasquinha Date: Mon, 6 Nov 2017 18:38:05 +0530 Subject: [PATCH 48/51] Remove psutil (#4437) --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1f1283a032..f3c2985cce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,7 +39,6 @@ pyopenssl ndg-httpsclient pyasn1 zxcvbn-python -psutil unittest-xml-reporting oauthlib PyJWT From 61277ad734b7cf79c2e0eb16f3feef297390cc97 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 8 Nov 2017 11:37:37 +0530 Subject: [PATCH 49/51] [fix] Render count in list, translate string (#4445) --- frappe/public/js/frappe/list/list_renderer.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/list/list_renderer.js b/frappe/public/js/frappe/list/list_renderer.js index d7faf78331..fd622a5154 100644 --- a/frappe/public/js/frappe/list/list_renderer.js +++ b/frappe/public/js/frappe/list/list_renderer.js @@ -355,11 +355,12 @@ frappe.views.ListRenderer = Class.extend({ this.render_tags($item_container, value); }); - this.render_count(values.length); + this.render_count(); }, - render_count: function(current_count) { + render_count: function() { const $header_right = this.list_view.list_header.find('.list-item__content--activity'); + const current_count = this.list_view.data.length; frappe.call({ method: 'frappe.model.db_query.get_count', @@ -368,8 +369,9 @@ frappe.views.ListRenderer = Class.extend({ filters: this.list_view.get_filters_args() } }).then(r => { - const count = r.message ? r.message : current_count; - const $html = $(`${current_count} of ${count}`); + const count = r.message || current_count; + const str = __('{0} of {1}', [current_count, count]); + const $html = $(`${str}`); $html.css({ marginRight: '10px' From 20dd8de0c1b74c8c4b8eaad6435ec1f704cbff0f Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 9 Nov 2017 17:44:26 +0530 Subject: [PATCH 50/51] [fix] Unlimited refresh in user form (#4457) --- frappe/core/doctype/user/user.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index d74e98bf7f..c29b03a954 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -38,8 +38,9 @@ frappe.ui.form.on('User', { }, onload: function(frm) { + frm.can_edit_roles = has_common(frappe.user_roles, ["Administrator", "System Manager"]); - if(has_common(frappe.user_roles, ["Administrator", "System Manager"]) && !frm.doc.__islocal) { + if(frm.can_edit_roles && !frm.is_new()) { if(!frm.roles_editor) { var role_area = $('
        ') .appendTo(frm.fields_dict.roles_html.wrapper); @@ -55,8 +56,9 @@ frappe.ui.form.on('User', { }, refresh: function(frm) { var doc = frm.doc; - if(!frm.doc.islocal && !frm.roles_editor) { + if(!frm.is_new() && !frm.roles_editor && frm.can_edit_roles) { frm.reload_doc(); + return; } if(doc.name===frappe.session.user && !doc.__unsaved && frappe.all_timezones @@ -68,7 +70,7 @@ frappe.ui.form.on('User', { frm.toggle_display(['sb1', 'sb3', 'modules_access'], false); - if(!doc.__islocal){ + if(!frm.is_new()) { frm.add_custom_button(__("Set Desktop Icons"), function() { frappe.route_options = { "user": doc.name @@ -112,8 +114,8 @@ frappe.ui.form.on('User', { frm.trigger('enabled'); - if (frm.roles_editor) { - frm.roles_editor.disabled = frm.doc.role_profile_name ? 1 : 0; + if (frm.roles_editor && frm.can_edit_roles) { + frm.roles_editor.disable = frm.doc.role_profile_name ? 1 : 0; frm.roles_editor.show(); } @@ -156,13 +158,13 @@ frappe.ui.form.on('User', { }, enabled: function(frm) { var doc = frm.doc; - if(!doc.__islocal && has_common(frappe.user_roles, ["Administrator", "System Manager"])) { + if(!frm.is_new() && has_common(frappe.user_roles, ["Administrator", "System Manager"])) { frm.toggle_display(['sb1', 'sb3', 'modules_access'], doc.enabled); frm.set_df_property('enabled', 'read_only', 0); } if(frappe.session.user!=="Administrator") { - frm.toggle_enable('email', doc.__islocal); + frm.toggle_enable('email', frm.is_new()); } }, create_user_email:function(frm) { From e7beaacc9ec5dd808f07ad5b5d6ce89f2cabe4d4 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 10 Nov 2017 11:11:59 +0530 Subject: [PATCH 51/51] Nestedset model fixes for changing parent and renaming (#4436) * Nestedset model fixes for changing parent * Rename function of File --- frappe/utils/nestedset.py | 81 +++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/frappe/utils/nestedset.py b/frappe/utils/nestedset.py index 78a6b11810..34bba95101 100644 --- a/frappe/utils/nestedset.py +++ b/frappe/utils/nestedset.py @@ -15,6 +15,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document +from frappe.utils import now class NestedSetRecursionError(frappe.ValidationError): pass class NestedSetMultipleRootsError(frappe.ValidationError): pass @@ -50,7 +51,7 @@ def update_add_node(doc, parent, parent_field): """ insert a new node """ - from frappe.utils import now + n = now() doctype = doc.doctype @@ -58,8 +59,8 @@ def update_add_node(doc, parent, parent_field): # get the last sibling of the parent if parent: - left, right = frappe.db.sql("select lft, rgt from `tab%s` where name=%s" \ - % (doctype, "%s"), parent)[0] + left, right = frappe.db.sql("select lft, rgt from `tab{0}` where name=%s" + .format(doctype), parent)[0] validate_loop(doc.doctype, doc.name, left, right) else: # root right = frappe.db.sql("select ifnull(max(rgt),0)+1 from `tab%s` \ @@ -67,43 +68,43 @@ def update_add_node(doc, parent, parent_field): right = right or 1 # update all on the right - frappe.db.sql("update `tab%s` set rgt = rgt+2, modified=%s where rgt >= %s" % - (doctype, '%s', '%s'), (n, right)) - frappe.db.sql("update `tab%s` set lft = lft+2, modified=%s where lft >= %s" % - (doctype, '%s', '%s'), (n, right)) + frappe.db.sql("update `tab{0}` set rgt = rgt+2, modified=%s where rgt >= %s" + .format(doctype), (n, right)) + frappe.db.sql("update `tab{0}` set lft = lft+2, modified=%s where lft >= %s" + .format(doctype), (n, right)) # update index of new node - if frappe.db.sql("select * from `tab%s` where lft=%s or rgt=%s"% (doctype, right, right+1)): + if frappe.db.sql("select * from `tab{0}` where lft=%s or rgt=%s".format(doctype), (right, right+1)): frappe.msgprint(_("Nested set error. Please contact the Administrator.")) raise Exception frappe.db.sql("update `tab{0}` set lft=%s, rgt=%s, modified=%s where name=%s".format(doctype), - (right,right+1,n,name)) + (right,right+1, n, name)) return right def update_move_node(doc, parent_field): + n = now() parent = doc.get(parent_field) if parent: - new_parent = frappe.db.sql("""select lft, rgt from `tab%s` - where name = %s""" % (doc.doctype, '%s'), parent, as_dict=1)[0] + new_parent = frappe.db.sql("""select lft, rgt from `tab{0}` + where name = %s""".format(doc.doctype), parent, as_dict=1)[0] validate_loop(doc.doctype, doc.name, new_parent.lft, new_parent.rgt) # move to dark side - frappe.db.sql("""update `tab%s` set lft = -lft, rgt = -rgt - where lft >= %s and rgt <= %s"""% (doc.doctype, '%s', '%s'), (doc.lft, doc.rgt)) + frappe.db.sql("""update `tab{0}` set lft = -lft, rgt = -rgt, modified=%s + where lft >= %s and rgt <= %s""".format(doc.doctype), (n, doc.lft, doc.rgt)) # shift left diff = doc.rgt - doc.lft + 1 - frappe.db.sql("""update `tab%s` set lft = lft -%s, rgt = rgt - %s - where lft > %s"""% (doc.doctype, '%s', '%s', '%s'), (diff, diff, doc.rgt)) + frappe.db.sql("""update `tab{0}` set lft = lft -%s, rgt = rgt - %s, modified=%s + where lft > %s""".format(doc.doctype), (diff, diff, n, doc.rgt)) # shift left rgts of ancestors whose only rgts must shift - frappe.db.sql("""update `tab%s` set rgt = rgt - %s - where lft < %s and rgt > %s"""% (doc.doctype, '%s', '%s', '%s'), - (diff, doc.lft, doc.rgt)) + frappe.db.sql("""update `tab{0}` set rgt = rgt - %s, modified=%s + where lft < %s and rgt > %s""".format(doc.doctype), (diff, n, doc.lft, doc.rgt)) if parent: new_parent = frappe.db.sql("""select lft, rgt from `tab%s` @@ -111,29 +112,28 @@ def update_move_node(doc, parent_field): # set parent lft, rgt - frappe.db.sql("""update `tab%s` set rgt = rgt + %s - where name = %s"""% (doc.doctype, '%s', '%s'), (diff, parent)) + frappe.db.sql("""update `tab{0}` set rgt = rgt + %s, modified=%s + where name = %s""".format(doc.doctype), (diff, n, parent)) # shift right at new parent - frappe.db.sql("""update `tab%s` set lft = lft + %s, rgt = rgt + %s - where lft > %s""" % (doc.doctype, '%s', '%s', '%s'), - (diff, diff, new_parent.rgt)) + frappe.db.sql("""update `tab{0}` set lft = lft + %s, rgt = rgt + %s, modified=%s + where lft > %s""".format(doc.doctype), (diff, diff, n, new_parent.rgt)) # shift right rgts of ancestors whose only rgts must shift - frappe.db.sql("""update `tab%s` set rgt = rgt + %s - where lft < %s and rgt > %s""" % (doc.doctype, '%s', '%s', '%s'), - (diff, new_parent.lft, new_parent.rgt)) + frappe.db.sql("""update `tab{0}` set rgt = rgt + %s, modified=%s + where lft < %s and rgt > %s""".format(doc.doctype), + (diff, n, new_parent.lft, new_parent.rgt)) new_diff = new_parent.rgt - doc.lft else: # new root - max_rgt = frappe.db.sql("""select max(rgt) from `tab%s`""" % doc.doctype)[0][0] + max_rgt = frappe.db.sql("""select max(rgt) from `tab{0}`""".format(doc.doctype))[0][0] new_diff = max_rgt + 1 - doc.lft # bring back from dark side - frappe.db.sql("""update `tab%s` set lft = -lft + %s, rgt = -rgt + %s - where lft < 0"""% (doc.doctype, '%s', '%s'), (new_diff, new_diff)) + frappe.db.sql("""update `tab{0}` set lft = -lft + %s, rgt = -rgt + %s, modified=%s + where lft < 0""".format(doc.doctype), (new_diff, new_diff, n)) def rebuild_tree(doctype, parent_field): """ @@ -160,8 +160,8 @@ def rebuild_node(doctype, parent, left, parent_field): right = left+1 # get all children of this node - result = frappe.db.sql("SELECT name FROM `tab%s` WHERE `%s`=%s" % - (doctype, parent_field, '%s'), (parent)) + result = frappe.db.sql("SELECT name FROM `tab{0}` WHERE `{1}`=%s" + .format(doctype, parent_field), (parent)) for r in result: right = rebuild_node(doctype, r[0], right, parent_field) @@ -176,8 +176,8 @@ def rebuild_node(doctype, parent, left, parent_field): def validate_loop(doctype, name, lft, rgt): """check if item not an ancestor (loop)""" - if name in frappe.db.sql_list("""select name from `tab%s` where lft <= %s and rgt >= %s""" % (doctype, - "%s", "%s"), (lft, rgt)): + if name in frappe.db.sql_list("""select name from `tab{0}` where lft <= %s and rgt >= %s""" + .format(doctype), (lft, rgt)): frappe.throw(_("Item cannot be added to its own descendents"), NestedSetRecursionError) class NestedSet(Document): @@ -218,8 +218,13 @@ class NestedSet(Document): frappe.throw(_("Merging is only possible between Group-to-Group or Leaf Node-to-Leaf Node"), NestedSetInvalidMergeError) def after_rename(self, olddn, newdn, merge=False): + parent_field = "parent_" + self.doctype.replace(" ", "_").lower() + + # set old_parent for children + frappe.db.sql("update `tab{0}` set old_parent=%s where {1}=%s" + .format(self.doctype, parent_field), (newdn, newdn)) + if merge: - parent_field = "parent_" + self.doctype.replace(" ", "_").lower() rebuild_tree(self.doctype, parent_field) def validate_one_root(self): @@ -230,8 +235,8 @@ class NestedSet(Document): def validate_ledger(self, group_identifier="is_group"): if self.get(group_identifier) == "No": - if frappe.db.sql("""select name from `tab%s` where %s=%s and docstatus!=2""" % - (self.doctype, self.nsm_parent_field, '%s'), (self.name)): + if frappe.db.sql("""select name from `tab{0}` where {1}=%s and docstatus!=2""" + .format(self.doctype, self.nsm_parent_field), (self.name)): frappe.throw(_("{0} {1} cannot be a leaf node as it has children").format(_(self.doctype), self.name)) def get_ancestors(self): @@ -247,6 +252,6 @@ def get_root_of(doctype): def get_ancestors_of(doctype, name): """Get ancestor elements of a DocType with a tree structure""" lft, rgt = frappe.db.get_value(doctype, name, ["lft", "rgt"]) - result = frappe.db.sql_list("""select name from `tab%s` - where lft<%s and rgt>%s order by lft desc""" % (doctype, "%s", "%s"), (lft, rgt)) + result = frappe.db.sql_list("""select name from `tab{0}` + where lft<%s and rgt>%s order by lft desc""".format(doctype), (lft, rgt)) return result or []