diff --git a/.eslintrc b/.eslintrc index 84cdc6bb85..d7cd742651 100644 --- a/.eslintrc +++ b/.eslintrc @@ -119,6 +119,8 @@ "getCookies": true, "get_url_arg": true, "QUnit": true, - "JsBarcode": true + "JsBarcode": true, + "L": true, + "Chart": true } } 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/__init__.py b/frappe/__init__.py index 8e2dceb3af..ce04094934 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/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/build.js b/frappe/build.js index de12e31ca0..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 @@ -60,11 +61,12 @@ 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(); }); @@ -77,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) { @@ -88,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['"];/, ''); } @@ -122,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 @@ -262,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; } 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/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/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..427a649e94 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -14,15 +14,18 @@ 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, - 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 +61,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 +106,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 +120,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 +135,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 +156,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 +199,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 +210,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 +228,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 @@ -247,8 +262,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): @@ -258,8 +273,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: @@ -345,6 +363,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 +406,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 +428,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 +467,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,11 +483,11 @@ 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: + 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 @@ -453,6 +504,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/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)) 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/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/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/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 dbe9be60c1..c29b03a954 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -17,8 +17,30 @@ 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) { + frm.can_edit_roles = has_common(frappe.user_roles, ["Administrator", "System Manager"]); + + if(frm.can_edit_roles && !frm.is_new()) { if(!frm.roles_editor) { var role_area = $('
') .appendTo(frm.fields_dict.roles_html.wrapper); @@ -34,7 +56,10 @@ frappe.ui.form.on('User', { }, refresh: function(frm) { var doc = frm.doc; - + 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 && (doc.language || frappe.boot.user.language) @@ -45,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 @@ -89,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(); } @@ -133,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) { 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 857019964d..4f09b75ed9 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/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.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 70251711f3..f3eede9397 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -66,9 +66,9 @@ 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',) +allowed_fieldtype_for_options_change = ('Read Only', 'HTML', 'Select', 'Data') class CustomizeForm(Document): def on_update(self): @@ -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() 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/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/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..a58a9555db --- /dev/null +++ b/frappe/desk/doctype/calendar_view/calendar_view.js @@ -0,0 +1,35 @@ +// 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'); + }, + 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; + + 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..ae8ab1eb46 --- /dev/null +++ b/frappe/desk/doctype/calendar_view/calendar_view.py @@ -0,0 +1,9 @@ +# -*- 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 CalendarView(Document): + pass diff --git a/frappe/desk/page/activity/activity.js b/frappe/desk/page/activity/activity.js index 97e81c86d4..66807b3eb9 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 Chart({ + parent: ".heatmap", + type: 'heatmap', height: 100, start: new Date(moment().subtract(1, 'year').toDate()), count_label: "actions", 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/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/email/doctype/email_alert/email_alert.py b/frappe/email/doctype/email_alert/email_alert.py index 10a964d88f..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''' @@ -117,7 +121,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 = [] @@ -237,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/email/email_body.py b/frappe/email/email_body.py index c12f2a857e..8ab7ae5c85 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..085dfb0c18 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, @@ -158,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: @@ -174,6 +180,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 +201,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 +211,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 @@ -511,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() 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/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.") 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", 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 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_query.py b/frappe/model/db_query.py index 0cd71684c9..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": @@ -571,3 +563,69 @@ 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) + +@frappe.whitelist() +def get_count(doctype, filters=None): + if filters: + filters = json.loads(filters) + + if is_parent_only_filter(doctype, filters): + if isinstance(filters, list): + filters = frappe.utils.make_filter_dict(filters) + + 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=0) + +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 + 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 diff --git a/frappe/model/db_schema.py b/frappe/model/db_schema.py index fb3de07332..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 @@ -26,25 +29,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', @@ -120,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/patches.txt b/frappe/patches.txt index 6b5bcd8c7b..e55931b031 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -23,7 +23,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 diff --git a/frappe/public/build.json b/frappe/public/build.json index 1a418248dd..8c30328c19 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -53,7 +53,9 @@ "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", + "public/js/frappe/form/controls/multiselect.js" ], "js/dialog.min.js": [ "public/js/frappe/dom.js", @@ -94,12 +96,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", @@ -113,8 +120,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", @@ -136,8 +142,13 @@ "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/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", @@ -230,7 +241,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/css/list.css b/frappe/public/css/list.css index abed4bc105..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; @@ -405,6 +408,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/images/leaflet/layers-2x.png b/frappe/public/images/leaflet/layers-2x.png new file mode 100644 index 0000000000..200c333dca Binary files /dev/null and b/frappe/public/images/leaflet/layers-2x.png differ diff --git a/frappe/public/images/leaflet/layers.png b/frappe/public/images/leaflet/layers.png new file mode 100644 index 0000000000..1a72e5784b Binary files /dev/null and b/frappe/public/images/leaflet/layers.png differ diff --git a/frappe/public/images/leaflet/leafletmarker-icon.png b/frappe/public/images/leaflet/leafletmarker-icon.png new file mode 100644 index 0000000000..950edf2467 Binary files /dev/null and b/frappe/public/images/leaflet/leafletmarker-icon.png differ diff --git a/frappe/public/images/leaflet/leafletmarker-shadow.png b/frappe/public/images/leaflet/leafletmarker-shadow.png new file mode 100644 index 0000000000..9fd2979532 Binary files /dev/null and b/frappe/public/images/leaflet/leafletmarker-shadow.png differ diff --git a/frappe/public/images/leaflet/lego.png b/frappe/public/images/leaflet/lego.png new file mode 100644 index 0000000000..f173107335 Binary files /dev/null and b/frappe/public/images/leaflet/lego.png differ diff --git a/frappe/public/images/leaflet/marker-icon-2x.png b/frappe/public/images/leaflet/marker-icon-2x.png new file mode 100644 index 0000000000..e4abba3b51 Binary files /dev/null and b/frappe/public/images/leaflet/marker-icon-2x.png differ diff --git a/frappe/public/images/leaflet/marker-icon.png b/frappe/public/images/leaflet/marker-icon.png new file mode 100644 index 0000000000..950edf2467 Binary files /dev/null and b/frappe/public/images/leaflet/marker-icon.png differ diff --git a/frappe/public/images/leaflet/marker-shadow.png b/frappe/public/images/leaflet/marker-shadow.png new file mode 100644 index 0000000000..9fd2979532 Binary files /dev/null and b/frappe/public/images/leaflet/marker-shadow.png differ diff --git a/frappe/public/images/leaflet/spritesheet-2x.png b/frappe/public/images/leaflet/spritesheet-2x.png new file mode 100644 index 0000000000..c45231aff8 Binary files /dev/null and b/frappe/public/images/leaflet/spritesheet-2x.png differ diff --git a/frappe/public/images/leaflet/spritesheet.png b/frappe/public/images/leaflet/spritesheet.png new file mode 100644 index 0000000000..97d71c6805 Binary files /dev/null and b/frappe/public/images/leaflet/spritesheet.png differ diff --git a/frappe/public/images/leaflet/spritesheet.svg b/frappe/public/images/leaflet/spritesheet.svg new file mode 100644 index 0000000000..3c00f30314 --- /dev/null +++ b/frappe/public/images/leaflet/spritesheet.svg @@ -0,0 +1,156 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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) => { 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/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/geolocation.js b/frappe/public/js/frappe/form/controls/geolocation.js new file mode 100644 index 0000000000..7dd0c7f64e --- /dev/null +++ b/frappe/public/js/frappe/form/controls/geolocation.js @@ -0,0 +1,184 @@ +frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlCode.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); + this.map._onResize(); + } 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/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)); + } +}); 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/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)) { 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(); 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"; diff --git a/frappe/public/js/frappe/list/list_renderer.js b/frappe/public/js/frappe/list/list_renderer.js index cd844f6238..fd622a5154 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'); }, @@ -348,6 +355,30 @@ frappe.views.ListRenderer = Class.extend({ this.render_tags($item_container, value); }); + this.render_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', + args: { + doctype: this.doctype, + filters: this.list_view.get_filters_args() + } + }).then(r => { + const count = r.message || current_count; + const str = __('{0} of {1}', [current_count, count]); + const $html = $(`${str}`); + + $html.css({ + marginRight: '10px' + }) + $header_right.addClass('text-muted'); + $header_right.html($html); + }) }, // returns html for a data item, diff --git a/frappe/public/js/frappe/list/list_sidebar.js b/frappe/public/js/frappe/list/list_sidebar.js index ed9d767dd1..efc970f405 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.removeClass('hide'); + $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/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/roles_editor.js b/frappe/public/js/frappe/roles_editor.js index 4a941a412c..f5870a67c0 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/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/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 = { diff --git a/frappe/public/js/frappe/views/calendar/calendar.js b/frappe/public/js/frappe/views/calendar/calendar.js index f43e721cfb..45c15990bf 100644 --- a/frappe/public/js/frappe/views/calendar/calendar.js +++ b/frappe/public/js/frappe/views/calendar/calendar.js @@ -8,14 +8,25 @@ 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 + this.get_calendar_options() + .then(options => { + 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; + } } - $.extend(options, frappe.views.calendar[this.doctype]); - this.calendar = new frappe.views.Calendar(options); + + return false; }, set_defaults: function() { this._super(); @@ -27,6 +38,62 @@ frappe.views.CalendarView = frappe.views.ListRenderer.extend({ get_header_html: function() { return null; }, + 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(); + + 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; + } + 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', { + 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, () => { + 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', @@ -202,7 +269,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; }, @@ -232,6 +300,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; @@ -241,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 { 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); }); diff --git a/frappe/public/js/frappe/views/image/image_view.js b/frappe/public/js/frappe/views/image/image_view.js index 6e094909f3..b5f3d369b3 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,154 @@ 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; - } + 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); - if(!el) { - el = me.wrapper.find('.image-field[data-name="'+i.name+'"]').get(0); - width = el.getBoundingClientRect().width; - height = el.getBoundingClientRect().height; - } + let width, height; + if(el) { + width = el.naturalWidth; + height = el.naturalHeight; + } - return { - src: i._image_url, - msrc: i._image_url, - name: i.name, - w: width, - h: height, - el: el - } - }); + if(!el) { + el = me.wrapper.find('.image-field[data-name="'+i.name+'"]').get(0); + width = el.getBoundingClientRect().width; + height = el.getBoundingClientRect().height; + } - var index; - items.map(function(item, i) { - if(item.name === docname) - index = i; + 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) { + $more_items.html(''); + 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/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/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"); 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; } 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/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/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; -} diff --git a/frappe/public/less/list.less b/frappe/public/less/list.less index b8f27cda1f..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 { @@ -487,6 +490,25 @@ 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 @border-color; + + img { + max-height: 100%; + } +} + // gantt .gantt { .details-container { @@ -611,4 +633,4 @@ .frappe-timestamp { white-space: nowrap; -} \ 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/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/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/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/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 3c0bb98ebb..da04d66d4f 100644 --- a/frappe/tests/ui/tests.txt +++ b/frappe/tests/ui/tests.txt @@ -11,6 +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/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 -frappe/tests/ui/test_control_html.js 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/data.py b/frappe/utils/data.py index 168e0430c0..9c8a81ad36 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -782,6 +782,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 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 [] diff --git a/frappe/utils/scheduler.py b/frappe/utils/scheduler.py index 68ab6f7cc2..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 @@ -25,15 +24,34 @@ 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 + +# imports - third-party libraries +import pymysql +from pymysql.constants import ER 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 +123,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") + for event in enabled_events: + trigger_if_enabled(site, event, last, queued_jobs) - if "all" not in enabled_events: - trigger(site, "all", 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 +218,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: @@ -273,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 @@ -293,7 +306,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/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"); diff --git a/requirements.txt b/requirements.txt index bd767c849e..f3c2985cce 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 +PyMySQL python-geoip python-geoip-geolite2 python-dateutil @@ -39,7 +39,6 @@ pyopenssl ndg-httpsclient pyasn1 zxcvbn-python -psutil unittest-xml-reporting oauthlib PyJWT @@ -50,3 +49,4 @@ pyqrcode pypng premailer psycopg2 +croniter