Browse Source

Geolocation control (#4327)

* added static map field

* leaflet draw plugin added to map control

* Editable layer working no icons

* Save and load data in form field

* images and icons working

* Locate plugin added, and loads on geolocation

* organized map control code

* loads layer as per form

* new form clears editableLayers

* update: leaflet 1.2.0 and leaflet-draw 0.4.2

* changed to ERPNext colors

* Multiple map fields and fixes

* fixes codacy suggestions

* loaded geojson data editable

* Map layers editable

* add only one layer on draw:create

* code organized with helper functions

* Replaced Leaflet Draw with Leaflet Editable

* read values from database

* layers saved in db

* Using Patched Leaflet Draw (mobile-friendly)

* Working Map field with multiple forms open

* Leaflet Draw css image path fix

* Leaflet Draw

Fixes Leaflet draw css image paths
Fixes Circle marker and Circle

* locate to geolocation or set featurecollection as center

* [fix] leaflet fitBounds padding 50,50

* [Fix] Leaflet (auto) locate

* Map field basic test

* added refresh button on map

* Mute map geojson in print format

* renamed Map field to Geolocation

* Suggested changes and fixes

* eslint disable

* csslint allow important
version-14
Revant Nandgaonkar 7 years ago
committed by Faris Ansari
parent
commit
ebf3554754
32 changed files with 3823 additions and 20 deletions
  1. +2
    -1
      .eslintrc
  2. +2
    -2
      frappe/core/doctype/docfield/docfield.json
  3. +2
    -2
      frappe/custom/doctype/custom_field/custom_field.json
  4. +23
    -0
      frappe/custom/doctype/custom_field/test_custom_field.js
  5. +1
    -1
      frappe/custom/doctype/customize_form/customize_form.py
  6. +2
    -2
      frappe/custom/doctype/customize_form_field/customize_form_field.json
  7. +10
    -9
      frappe/model/db_schema.py
  8. +13
    -3
      frappe/public/build.json
  9. BIN
      frappe/public/images/leaflet/layers-2x.png
  10. BIN
      frappe/public/images/leaflet/layers.png
  11. BIN
      frappe/public/images/leaflet/leafletmarker-icon.png
  12. BIN
      frappe/public/images/leaflet/leafletmarker-shadow.png
  13. BIN
      frappe/public/images/leaflet/lego.png
  14. BIN
      frappe/public/images/leaflet/marker-icon-2x.png
  15. BIN
      frappe/public/images/leaflet/marker-icon.png
  16. BIN
      frappe/public/images/leaflet/marker-shadow.png
  17. BIN
      frappe/public/images/leaflet/spritesheet-2x.png
  18. BIN
      frappe/public/images/leaflet/spritesheet.png
  19. +156
    -0
      frappe/public/images/leaflet/spritesheet.svg
  20. +5
    -0
      frappe/public/js/frappe/dom.js
  21. +183
    -0
      frappe/public/js/frappe/form/controls/geolocation.js
  22. +12
    -0
      frappe/public/js/lib/leaflet/L.Control.Locate.css
  23. +591
    -0
      frappe/public/js/lib/leaflet/L.Control.Locate.js
  24. +56
    -0
      frappe/public/js/lib/leaflet/easy-button.css
  25. +370
    -0
      frappe/public/js/lib/leaflet/easy-button.js
  26. +632
    -0
      frappe/public/js/lib/leaflet/leaflet.css
  27. +10
    -0
      frappe/public/js/lib/leaflet/leaflet.draw.css
  28. +1702
    -0
      frappe/public/js/lib/leaflet/leaflet.draw.js
  29. +5
    -0
      frappe/public/js/lib/leaflet/leaflet.js
  30. +6
    -0
      frappe/templates/print_formats/standard_macros.html
  31. +39
    -0
      frappe/tests/ui/test_control_geolocation.js
  32. +1
    -0
      frappe/tests/ui/tests.txt

+ 2
- 1
.eslintrc View File

@@ -119,6 +119,7 @@
"getCookies": true, "getCookies": true,
"get_url_arg": true, "get_url_arg": true,
"QUnit": true, "QUnit": true,
"JsBarcode": true
"JsBarcode": true,
"L": true
} }
} }

+ 2
- 2
frappe/core/doctype/docfield/docfield.json View File

@@ -96,7 +96,7 @@
"no_copy": 0, "no_copy": 0,
"oldfieldname": "fieldtype", "oldfieldname": "fieldtype",
"oldfieldtype": "Select", "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, "permlevel": 0,
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
@@ -1364,7 +1364,7 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-10-07 19:20:15.888708",
"modified": "2017-10-24 11:39:56.795852",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Core", "module": "Core",
"name": "DocField", "name": "DocField",


+ 2
- 2
frappe/custom/doctype/custom_field/custom_field.json View File

@@ -220,7 +220,7 @@
"no_copy": 0, "no_copy": 0,
"oldfieldname": "fieldtype", "oldfieldname": "fieldtype",
"oldfieldtype": "Select", "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, "permlevel": 0,
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
@@ -1161,7 +1161,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-07-06 17:23:43.835189",
"modified": "2017-10-24 11:40:37.986457",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Custom", "module": "Custom",
"name": "Custom Field", "name": "Custom Field",


+ 23
- 0
frappe/custom/doctype/custom_field/test_custom_field.js View File

@@ -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()
]);

});

+ 1
- 1
frappe/custom/doctype/customize_form/customize_form.py View File

@@ -66,7 +66,7 @@ docfield_properties = {


allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'), allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'),
('Text', 'Data'), ('Text', 'Text Editor', 'Code', 'Signature'), ('Data', 'Select'), ('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',)




+ 2
- 2
frappe/custom/doctype/customize_form_field/customize_form_field.json View File

@@ -94,7 +94,7 @@
"no_copy": 0, "no_copy": 0,
"oldfieldname": "fieldtype", "oldfieldname": "fieldtype",
"oldfieldtype": "Select", "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, "permlevel": 0,
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
@@ -1202,7 +1202,7 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-10-11 06:45:20.172291",
"modified": "2017-10-24 11:41:31.075929",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Custom", "module": "Custom",
"name": "Customize Form Field", "name": "Customize Form Field",


+ 10
- 9
frappe/model/db_schema.py View File

@@ -26,25 +26,26 @@ type_map = {
,'Float': ('decimal', '18,6') ,'Float': ('decimal', '18,6')
,'Percent': ('decimal', '18,6') ,'Percent': ('decimal', '18,6')
,'Check': ('int', '1') ,'Check': ('int', '1')
,'Small Text': ('text', '')
,'Long Text': ('longtext', '')
,'Small Text': ('text', '')
,'Long Text': ('longtext', '')
,'Code': ('longtext', '') ,'Code': ('longtext', '')
,'Text Editor': ('longtext', '')
,'Text Editor': ('longtext', '')
,'Date': ('date', '') ,'Date': ('date', '')
,'Datetime': ('datetime', '6')
,'Datetime': ('datetime', '6')
,'Time': ('time', '6') ,'Time': ('time', '6')
,'Text': ('text', '') ,'Text': ('text', '')
,'Data': ('varchar', varchar_len) ,'Data': ('varchar', varchar_len)
,'Link': ('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) ,'Select': ('varchar', varchar_len)
,'Read Only': ('varchar', varchar_len)
,'Read Only': ('varchar', varchar_len)
,'Attach': ('text', '') ,'Attach': ('text', '')
,'Attach Image':('text', '')
,'Signature': ('longtext', '')
,'Attach Image': ('text', '')
,'Signature': ('longtext', '')
,'Color': ('varchar', varchar_len) ,'Color': ('varchar', varchar_len)
,'Barcode': ('longtext', '') ,'Barcode': ('longtext', '')
,'Geolocation': ('longtext', '')
} }


default_columns = ['name', 'creation', 'modified', 'modified_by', 'owner', default_columns = ['name', 'creation', 'modified', 'modified_by', 'owner',


+ 13
- 3
frappe/public/build.json View File

@@ -53,7 +53,8 @@
"public/js/frappe/form/controls/html.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/autocomplete.js", "public/js/frappe/form/controls/autocomplete.js",
"public/js/frappe/form/controls/barcode.js"
"public/js/frappe/form/controls/barcode.js",
"public/js/frappe/form/controls/geolocation.js"
], ],
"js/dialog.min.js": [ "js/dialog.min.js": [
"public/js/frappe/dom.js", "public/js/frappe/dom.js",
@@ -94,12 +95,17 @@
"public/js/frappe/form/controls/read_only.js", "public/js/frappe/form/controls/read_only.js",
"public/js/frappe/form/controls/button.js", "public/js/frappe/form/controls/button.js",
"public/js/frappe/form/controls/html.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": [ "css/desk.min.css": [
"public/js/lib/datepicker/datepicker.min.css", "public/js/lib/datepicker/datepicker.min.css",
"public/js/lib/awesomplete/awesomplete.css", "public/js/lib/awesomplete/awesomplete.css",
"public/js/lib/summernote/summernote.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/bootstrap.css",
"public/css/font-awesome.css", "public/css/font-awesome.css",
"public/css/octicons/octicons.css", "public/css/octicons/octicons.css",
@@ -137,7 +143,11 @@
"public/js/lib/datepicker/datepicker.min.js", "public/js/lib/datepicker/datepicker.min.js",
"public/js/lib/datepicker/locale-all.js", "public/js/lib/datepicker/locale-all.js",
"public/js/lib/jquery.jrumble.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": [ "js/desk.min.js": [
"public/js/frappe/class.js", "public/js/frappe/class.js",


BIN
frappe/public/images/leaflet/layers-2x.png View File

Before After
Width: 52  |  Height: 52  |  Size: 1.2 KiB

BIN
frappe/public/images/leaflet/layers.png View File

Before After
Width: 26  |  Height: 26  |  Size: 696 B

BIN
frappe/public/images/leaflet/leafletmarker-icon.png View File

Before After
Width: 25  |  Height: 41  |  Size: 1.4 KiB

BIN
frappe/public/images/leaflet/leafletmarker-shadow.png View File

Before After
Width: 41  |  Height: 41  |  Size: 618 B

BIN
frappe/public/images/leaflet/lego.png View File

Before After
Width: 477  |  Height: 472  |  Size: 221 KiB

BIN
frappe/public/images/leaflet/marker-icon-2x.png View File

Before After
Width: 50  |  Height: 82  |  Size: 2.5 KiB

BIN
frappe/public/images/leaflet/marker-icon.png View File

Before After
Width: 25  |  Height: 41  |  Size: 1.4 KiB

BIN
frappe/public/images/leaflet/marker-shadow.png View File

Before After
Width: 41  |  Height: 41  |  Size: 618 B

BIN
frappe/public/images/leaflet/spritesheet-2x.png View File

Before After
Width: 600  |  Height: 60  |  Size: 3.5 KiB

BIN
frappe/public/images/leaflet/spritesheet.png View File

Before After
Width: 300  |  Height: 30  |  Size: 1.9 KiB

+ 156
- 0
frappe/public/images/leaflet/spritesheet.svg View File

@@ -0,0 +1,156 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 600 60"
height="60"
width="600"
id="svg4225"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="spritesheet.svg"
inkscape:export-filename="/home/fpuga/development/upstream/icarto.Leaflet.draw/src/images/spritesheet-2x.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<metadata
id="metadata4258">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4256" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1056"
id="namedview4254"
showgrid="false"
inkscape:zoom="1.3101852"
inkscape:cx="237.56928"
inkscape:cy="7.2419621"
inkscape:window-x="1920"
inkscape:window-y="24"
inkscape:window-maximized="1"
inkscape:current-layer="svg4225" />
<g
id="enabled"
style="fill:#464646;fill-opacity:1">
<g
id="polyline"
style="fill:#464646;fill-opacity:1">
<path
d="m 18,36 0,6 6,0 0,-6 -6,0 z m 4,4 -2,0 0,-2 2,0 0,2 z"
id="path4229"
inkscape:connector-curvature="0"
style="fill:#464646;fill-opacity:1" />
<path
d="m 36,18 0,6 6,0 0,-6 -6,0 z m 4,4 -2,0 0,-2 2,0 0,2 z"
id="path4231"
inkscape:connector-curvature="0"
style="fill:#464646;fill-opacity:1" />
<path
d="m 23.142,39.145 -2.285,-2.29 16,-15.998 2.285,2.285 z"
id="path4233"
inkscape:connector-curvature="0"
style="fill:#464646;fill-opacity:1" />
</g>
<path
id="polygon"
d="M 100,24.565 97.904,39.395 83.07,42 76,28.773 86.463,18 Z"
inkscape:connector-curvature="0"
style="fill:#464646;fill-opacity:1" />
<path
id="rectangle"
d="m 140,20 20,0 0,20 -20,0 z"
inkscape:connector-curvature="0"
style="fill:#464646;fill-opacity:1" />
<path
id="circle"
d="m 221,30 c 0,6.078 -4.926,11 -11,11 -6.074,0 -11,-4.922 -11,-11 0,-6.074 4.926,-11 11,-11 6.074,0 11,4.926 11,11 z"
inkscape:connector-curvature="0"
style="fill:#464646;fill-opacity:1" />
<path
id="marker"
d="m 270,19 c -4.971,0 -9,4.029 -9,9 0,4.971 5.001,12 9,14 4.001,-2 9,-9.029 9,-14 0,-4.971 -4.029,-9 -9,-9 z m 0,12.5 c -2.484,0 -4.5,-2.014 -4.5,-4.5 0,-2.484 2.016,-4.5 4.5,-4.5 2.485,0 4.5,2.016 4.5,4.5 0,2.486 -2.015,4.5 -4.5,4.5 z"
inkscape:connector-curvature="0"
style="fill:#464646;fill-opacity:1" />
<g
id="edit"
style="fill:#464646;fill-opacity:1">
<path
d="m 337,30.156 0,0.407 0,5.604 c 0,1.658 -1.344,3 -3,3 l -10,0 c -1.655,0 -3,-1.342 -3,-3 l 0,-10 c 0,-1.657 1.345,-3 3,-3 l 6.345,0 3.19,-3.17 -9.535,0 c -3.313,0 -6,2.687 -6,6 l 0,10 c 0,3.313 2.687,6 6,6 l 10,0 c 3.314,0 6,-2.687 6,-6 l 0,-8.809 -3,2.968"
id="path4240"
inkscape:connector-curvature="0"
style="fill:#464646;fill-opacity:1" />
<path
d="m 338.72,24.637 -8.892,8.892 -2.828,0 0,-2.829 8.89,-8.89 z"
id="path4242"
inkscape:connector-curvature="0"
style="fill:#464646;fill-opacity:1" />
<path
d="m 338.697,17.826 4,0 0,4 -4,0 z"
transform="matrix(-0.70698336,-0.70723018,0.70723018,-0.70698336,567.55917,274.78273)"
id="path4244"
inkscape:connector-curvature="0"
style="fill:#464646;fill-opacity:1" />
</g>
<g
id="remove"
style="fill:#464646;fill-opacity:1">
<path
d="m 381,42 18,0 0,-18 -18,0 0,18 z m 14,-16 2,0 0,14 -2,0 0,-14 z m -4,0 2,0 0,14 -2,0 0,-14 z m -4,0 2,0 0,14 -2,0 0,-14 z m -4,0 2,0 0,14 -2,0 0,-14 z"
id="path4247"
inkscape:connector-curvature="0"
style="fill:#464646;fill-opacity:1" />
<path
d="m 395,20 0,-4 -10,0 0,4 -6,0 0,2 22,0 0,-2 -6,0 z m -2,0 -6,0 0,-2 6,0 0,2 z"
id="path4249"
inkscape:connector-curvature="0"
style="fill:#464646;fill-opacity:1" />
</g>
</g>
<g
id="disabled"
transform="translate(120,0)"
style="fill:#bbbbbb">
<use
xlink:href="#edit"
id="edit-disabled"
x="0"
y="0"
width="100%"
height="100%" />
<use
xlink:href="#remove"
id="remove-disabled"
x="0"
y="0"
width="100%"
height="100%" />
</g>
<path
style="fill:none;stroke:#464646;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="circle-3"
d="m 581.65725,30 c 0,6.078 -4.926,11 -11,11 -6.074,0 -11,-4.922 -11,-11 0,-6.074 4.926,-11 11,-11 6.074,0 11,4.926 11,11 z"
inkscape:connector-curvature="0" />
</svg>

+ 5
- 0
frappe/public/js/frappe/dom.js View File

@@ -10,6 +10,11 @@ frappe.dom = {
by_id: function(id) { by_id: function(id) {
return document.getElementById(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) { set_unique_id: function(ele) {
var $ele = $(ele); var $ele = $(ele);
if($ele.attr('id')) { if($ele.attr('id')) {


+ 183
- 0
frappe/public/js/frappe/form/controls/geolocation.js View File

@@ -0,0 +1,183 @@
frappe.ui.form.ControlGeolocation = frappe.ui.form.ControlData.extend({
make_wrapper() {
// Create the elements for map area
this._super();

let $input_wrapper = this.$wrapper.find('.control-input-wrapper');
this.map_id = frappe.dom.get_unique_id();
this.map_area = $(
`<div class="map-wrapper border">
<div id="` + this.map_id + `" style="min-height: 400px; z-index: 1; max-width:100%"></div>
</div>`
);
this.map_area.prependTo($input_wrapper);
this.$wrapper.find('.control-input').addClass("hidden");
this.bind_leaflet_map();
this.bind_leaflet_draw_control();
this.bind_leaflet_locate_control();
this.bind_leaflet_refresh_button();
},

format_for_input(value) {
// render raw value from db into map
this.clear_editable_layers();
if(value) {
var data_layers = new L.FeatureGroup()
.addLayer(L.geoJson(JSON.parse(value),{
pointToLayer: function(geoJsonPoint, latlng) {
if (geoJsonPoint.properties.point_type == "circle"){
return L.circle(latlng, {radius: geoJsonPoint.properties.radius});
} else if (geoJsonPoint.properties.point_type == "circlemarker") {
return L.circleMarker(latlng, {radius: geoJsonPoint.properties.radius});
}
else {
return L.marker(latlng);
}
}
}));
this.add_non_group_layers(data_layers, this.editableLayers);
try {
this.map.flyToBounds(this.editableLayers.getBounds(), {
padding: [50,50]
});
}
catch(err) {
// suppress error if layer has a point.
}
this.editableLayers.addTo(this.map);
} else if ((value===undefined) || (value == JSON.stringify(new L.FeatureGroup().toGeoJSON()))) {
this.locate_control.start();
}
},

bind_leaflet_map() {

var circleToGeoJSON = L.Circle.prototype.toGeoJSON;
L.Circle.include({
toGeoJSON: function() {
var feature = circleToGeoJSON.call(this);
feature.properties = {
point_type: 'circle',
radius: this.getRadius()
};
return feature;
}
});

L.CircleMarker.include({
toGeoJSON: function() {
var feature = circleToGeoJSON.call(this);
feature.properties = {
point_type: 'circlemarker',
radius: this.getRadius()
};
return feature;
}
});

L.Icon.Default.imagePath = '/assets/frappe/images/leaflet/';
this.map = L.map(this.map_id).setView([19.0800, 72.8961], 13);

L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> 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: '<strong>Oh snap!<strong> 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);
});
}
});

+ 12
- 0
frappe/public/js/lib/leaflet/L.Control.Locate.css View File

@@ -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;
}

+ 591
- 0
frappe/public/js/lib/leaflet/L.Control.Locate.js View File

@@ -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));

+ 56
- 0
frappe/public/js/lib/leaflet/easy-button.css View File

@@ -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;
}

+ 370
- 0
frappe/public/js/lib/leaflet/easy-button.js View File

@@ -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 <a>
// }

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<this._states.length;i++){
this.button.appendChild(this._states[i].icon);
}
}
},


_currentState: {
// placeholder content
stateName: 'unnamed',
icon: (function(){ return document.createElement('span'); })()
},



_states: null, // populated on init



state: function(newState){

// activate by name
if(typeof newState == 'string'){

this._activateStateNamed(newState);

// activate by index
} else if (typeof newState == 'number'){

this._activateState(this._states[newState]);
}

return this;
},


_activateStateNamed: function(stateName){
for(var i = 0; i < this._states.length; i++){
if( this._states[i].stateName == stateName ){
this._activateState( this._states[i] );
}
}
},

_activateState: function(newState){

if( newState === this._currentState ){

// don't touch the dom if it'll just be the same after
return;

} else {

// swap out elements... if you're into that kind of thing
if( this.options.type == 'replace' ){
this.button.appendChild(newState.icon);
this.button.removeChild(this._currentState.icon);
}

if( newState.title ){
this.button.title = newState.title;
} else {
this.button.removeAttribute('title');
}

// update classes for animations
for(var i=0;i<this._states.length;i++){
L.DomUtil.removeClass(this._states[i].icon, this._currentState.stateName + '-active');
L.DomUtil.addClass(this._states[i].icon, newState.stateName + '-active');
}

// update classes for animations
L.DomUtil.removeClass(this.button, this._currentState.stateName + '-active');
L.DomUtil.addClass(this.button, newState.stateName + '-active');

// update the record
this._currentState = newState;

}
},

enable: function(){
L.DomUtil.addClass(this.button, 'enabled');
L.DomUtil.removeClass(this.button, 'disabled');
this.button.setAttribute('aria-hidden', 'false');
return this;
},

disable: function(){
L.DomUtil.addClass(this.button, 'disabled');
L.DomUtil.removeClass(this.button, 'enabled');
this.button.setAttribute('aria-hidden', 'true');
return this;
},

onAdd: function(map){
var bar = L.easyBar([this], {
position: this.options.position,
leafletClasses: this.options.leafletClasses
});
this._anonymousBar = bar;
this._container = bar.container;
return this._anonymousBar.container;
},

removeFrom: function (map) {
if (this._map === map)
this.remove();
return this;
},

});

L.easyButton = function(/* args will pass automatically */){
var args = Array.prototype.concat.apply([L.Control.EasyButton],arguments);
return new (Function.prototype.bind.apply(L.Control.EasyButton, args));
};

/*************************
*
* util functions
*
*************************/

// constructor for states so only curated
// states end up getting called
function State(template, easyButton){

this.title = template.title;
this.stateName = template.stateName ? template.stateName : 'unnamed-state';

// build the wrapper
this.icon = L.DomUtil.create('span', '');

L.DomUtil.addClass(this.icon, 'button-state state-' + this.stateName.replace(/(^\s*|\s*$)/g,''));
this.icon.innerHTML = buildIcon(template.icon);
this.onClick = L.Util.bind(template.onClick?template.onClick:function(){}, easyButton);
}

function buildIcon(ambiguousIconString) {

var tmpIcon;

// does this look like html? (i.e. not a class)
if( ambiguousIconString.match(/[&;=<>"']/) ){

// 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;
}

})();

+ 632
- 0
frappe/public/js/lib/leaflet/leaflet.css View File

@@ -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;
}

+ 10
- 0
frappe/public/js/lib/leaflet/leaflet.draw.css View File

@@ -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}

+ 1702
- 0
frappe/public/js/lib/leaflet/leaflet.draw.js
File diff suppressed because it is too large
View File


+ 5
- 0
frappe/public/js/lib/leaflet/leaflet.js
File diff suppressed because it is too large
View File


+ 6
- 0
frappe/templates/print_formats/standard_macros.html View File

@@ -8,6 +8,8 @@
{%- elif df.fieldtype in ("Image", "Attach Image", "Attach", "Signature") {%- elif df.fieldtype in ("Image", "Attach Image", "Attach", "Signature")
and (guess_mimetype(doc[df.fieldname])[0] or "").startswith("image/") -%} and (guess_mimetype(doc[df.fieldname])[0] or "").startswith("image/") -%}
{{ render_image(df, doc) }} {{ render_image(df, doc) }}
{%- elif df.fieldtype=="Geolocation" -%}
{{ render_geolocation(df, doc) }}
{%- else -%} {%- else -%}
{{ render_field_with_label(df, doc) }} {{ render_field_with_label(df, doc) }}
{%- endif -%} {%- endif -%}
@@ -98,6 +100,10 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}"
{{ print_value(df, doc) }} {{ print_value(df, doc) }}
{% endmacro %} {% endmacro %}


{%- macro render_geolocation(df, doc) -%}
{{ "" }}
{%- endmacro -%}

{%- macro print_value(df, doc, parent_doc=None, visible_columns=None) -%} {%- macro print_value(df, doc, parent_doc=None, visible_columns=None) -%}
{% if doc.print_templates and {% if doc.print_templates and
doc.print_templates.get(df.fieldname) %} doc.print_templates.get(df.fieldname) %}


+ 39
- 0
frappe/tests/ui/test_control_geolocation.js View File

@@ -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()
]);
});

+ 1
- 0
frappe/tests/ui/tests.txt View File

@@ -14,3 +14,4 @@ frappe/desk/doctype/event/test_event.js
frappe/workflow/doctype/workflow/tests/test_workflow_create.js frappe/workflow/doctype/workflow/tests/test_workflow_create.js
frappe/workflow/doctype/workflow/tests/test_workflow_test.js frappe/workflow/doctype/workflow/tests/test_workflow_test.js
frappe/tests/ui/test_control_html.js frappe/tests/ui/test_control_html.js
frappe/tests/ui/test_control_geolocation.js

Loading…
Cancel
Save