├── .python-version ├── MANIFEST.in ├── .gitignore ├── mpd ├── templates │ ├── create.html │ ├── base.html │ ├── list.html │ ├── piece.html │ ├── party.html │ └── edit.html ├── db.py ├── __init__.py ├── party.py └── static │ └── Leaflet.Editable.js ├── README.md ├── pyproject.toml ├── LICENSE └── uv.lock /.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft mpd/templates 2 | graft mpd/static 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | instance/ 2 | __pycache__/ 3 | .venv/ 4 | build/ 5 | *.egg-info/ 6 | -------------------------------------------------------------------------------- /mpd/templates/create.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |

Creating a new mapping party

4 |
5 | 6 | 7 |
8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mapping Pie Dispencer 2 | 3 | It's a website for uploading or editing 4 | [pie pieces](https://wiki.openstreetmap.org/wiki/Cake_diagram) for mapping parties. 5 | After that it presents a web interface to view those pieces, 6 | and download them to various apps, namely Every Door. 7 | 8 | ## Author and License 9 | 10 | Written by Ilya Zverev, published under ISC License. 11 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "mapping-pie-dispenser" 3 | version = "0.1.7" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = [ 8 | "authlib>=1.5.2", 9 | "click>=8.1.8", 10 | "flask>=3.1.0", 11 | "gunicorn>=23.0.0", 12 | "qrcode>=8.2", 13 | "requests>=2.32.3", 14 | ] 15 | 16 | [build-system] 17 | requires = ["setuptools >= 77.0.3"] 18 | build-backend = "setuptools.build_meta" 19 | 20 | [tool.setuptools] 21 | packages = ["mpd"] 22 | -------------------------------------------------------------------------------- /mpd/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% if party %}{% if piece %}Piece {{piece.properties.id}} — {% endif %}{{ party.title }}{% else %}Mapping Parties{% endif %} 5 | 6 | 7 | {% block header %}{% endblock %} 8 | 9 | 10 | 11 | {% with messages = get_flashed_messages() %} 12 | {% if messages %} 13 | 18 | {% endif %} 19 | {% endwith %} 20 | {% block content %}{% endblock %} 21 | 22 | 23 | -------------------------------------------------------------------------------- /mpd/templates/list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 | {% if not logged_in %} 4 |

Sign in to create and edit parties.

5 | {% else %} 6 |

Create a new mapping party 7 | or log out.

8 | {% endif %} 9 | 10 |

Mapping Parties

11 | 21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright 2025 Ilya Zverev 4 | 5 | Permission to use, copy, modify, and/or distribute this software 6 | for any purpose with or without fee is hereby granted, provided 7 | that the above copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, 12 | DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER 13 | RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 14 | NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH 15 | THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /mpd/db.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import click 3 | from flask import current_app, g 4 | 5 | 6 | def get_db(): 7 | if 'db' not in g: 8 | g.db = sqlite3.connect(current_app.config['DATABASE']) 9 | g.db.row_factory = sqlite3.Row 10 | return g.db 11 | 12 | 13 | def close_db(e=None): 14 | db = g.pop('db', None) 15 | if db is not None: 16 | db.close() 17 | 18 | 19 | def init_db(): 20 | sql = ''' 21 | drop table if exists parties; 22 | create table parties ( 23 | party_id integer primary key autoincrement, 24 | title text not null, 25 | owner_id integer not null, 26 | created integer not null, 27 | scheduled text, 28 | pie text, 29 | location text, 30 | piece_count integer 31 | ); 32 | ''' 33 | db = get_db() 34 | db.executescript(sql) 35 | 36 | 37 | @click.command('initdb') 38 | def init_db_command(): 39 | init_db() 40 | click.echo('OK') 41 | 42 | 43 | def init_app(app): 44 | app.teardown_appcontext(close_db) 45 | app.cli.add_command(init_db_command) 46 | -------------------------------------------------------------------------------- /mpd/templates/piece.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block header %} 3 | 4 | 5 | 6 | {% endblock %} 7 | {% block content %} 8 |

Piece {{ piece.properties.id }} — {{ party.title }}

9 | 10 |
11 | 12 |

Install Every Door Plugin or use this QR code:

13 | 14 | {{ qr | safe }} 15 | 16 |

Back to the mapping party

17 | 18 | 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /mpd/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from flask import Flask 3 | from authlib.integrations.flask_client import OAuth 4 | from werkzeug.middleware.proxy_fix import ProxyFix 5 | 6 | 7 | oauth = OAuth() 8 | 9 | 10 | def create_app(): 11 | app = Flask(__name__, instance_relative_config=True) 12 | app.config.from_mapping( 13 | SECRET_KEY='sdfsdfsdf', 14 | DATABASE=os.path.join(app.instance_path, 'mpd.sqlite'), 15 | OAUTH_ID='', 16 | OAUTH_SECRET='', 17 | PROXY=False, 18 | ) 19 | app.config.from_pyfile('config.py', silent=True) 20 | os.makedirs(app.instance_path, exist_ok=True) 21 | 22 | from . import db 23 | db.init_app(app) 24 | from . import party 25 | app.register_blueprint(party.papp) 26 | 27 | oauth.register( 28 | 'openstreetmap', 29 | api_base_url='https://api.openstreetmap.org/api/0.6/', 30 | access_token_url='https://www.openstreetmap.org/oauth2/token', 31 | authorize_url='https://www.openstreetmap.org/oauth2/authorize', 32 | client_id=app.config['OAUTH_ID'], 33 | client_secret=app.config['OAUTH_SECRET'], 34 | client_kwargs={'scope': 'read_prefs'}, 35 | ) 36 | oauth.init_app(app) 37 | 38 | if app.config['PROXY']: 39 | app.wsgi_app = ProxyFix( 40 | app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1) 41 | 42 | return app 43 | -------------------------------------------------------------------------------- /mpd/templates/party.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block header %} 3 | 4 | 5 | 6 | {% endblock %} 7 | {% block content %} 8 |

{{ party.title }}

9 | 10 | {% if party.scheduled %} 11 |

Coming up {{ party.scheduled }}!

12 | {% endif %} 13 | 14 | {% if pie %} 15 |

Pieces

16 |
17 | {% else %} 18 |

No pie pieces yet.

19 | {% endif %} 20 | 21 | {% if is_owner %} 22 |

Edit party

23 |

Edit Pie

24 |
25 | (party will be hidden if not set)
26 |
27 |
28 | 29 |
30 | {% endif %} 31 | 32 |

Back to the list

33 | {% if is_owner %} 34 |

Delete

35 | {% endif %} 36 | 37 | 49 | {% endblock %} 50 | -------------------------------------------------------------------------------- /mpd/templates/edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block header %} 3 | 4 | 5 | 6 | 10 | {% endblock %} 11 | {% block content %} 12 |

{{ party.title }}

13 | 14 |

Sorry! This feature is still pending!

15 | 16 |
17 | 18 |
19 | 20 |
21 | 22 |

Back to the party

23 | 24 | 75 | {% endblock %} 76 | -------------------------------------------------------------------------------- /mpd/party.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | import re 4 | import requests 5 | import qrcode 6 | import qrcode.image.svg 7 | import urllib.parse 8 | import zipfile 9 | import io 10 | from . import oauth 11 | from .db import get_db 12 | from flask import ( 13 | Blueprint, url_for, redirect, render_template, 14 | session, flash, request, make_response, 15 | ) 16 | 17 | 18 | papp = Blueprint('party', __name__) 19 | 20 | 21 | class JsonValidator: 22 | def __init__(self, content: str): 23 | self.error: str | None = 'Validator todo' 24 | self.data: dict | None = None # unpacked content 25 | self.location: str | None = None # from Nominatim 26 | self.validate(content) 27 | 28 | @property 29 | def pie(self): 30 | return json.dumps(self.data, ensure_ascii=False) 31 | 32 | @property 33 | def piece_count(self): 34 | return len(self.data['features']) 35 | 36 | def validate(self, content): 37 | if not content: 38 | self.error = 'Empty content' 39 | return 40 | try: 41 | data = json.loads(content) 42 | except: # noqa 43 | self.error = 'Error decoding json' 44 | return 45 | if not data.get('features'): 46 | self.error = 'No features found' 47 | return 48 | 49 | features = [] 50 | next_piece = 1 51 | pieces = set[str]() 52 | for f in data['features']: 53 | if 'geometry' not in f: 54 | continue 55 | if f['geometry']['type'] != 'Polygon': 56 | continue 57 | if len(f['geometry']['coordinates']) != 1: 58 | continue 59 | 60 | p = f.get('properties', {}) 61 | piece_id = p.get('id') or p.get('ref') or p.get('piece') 62 | if piece_id is not None: 63 | piece_id = str(piece_id).strip() 64 | if piece_id in pieces: 65 | self.error = f'Duplicate piece id "{piece_id}"' 66 | return 67 | else: 68 | while str(next_piece) in pieces: 69 | next_piece += 1 70 | piece_id = str(next_piece) 71 | next_piece += 1 72 | f['properties'] = {'id': piece_id} 73 | pieces.add(piece_id) 74 | features.append(f) 75 | 76 | if not features: 77 | self.error = 'No Polygon features found' 78 | return 79 | self.error = None 80 | self.data = {'type': 'FeatureCollection', 'features': features} 81 | self.location = self.detect_location() 82 | 83 | def detect_location(self) -> str | None: 84 | if not self.data: 85 | return None 86 | count = 0 87 | lat = 0.0 88 | lon = 0.0 89 | for f in self.data['features']: 90 | if f['geometry']['type'] != 'Polygon': 91 | continue 92 | plat = 0.0 93 | plon = 0.0 94 | for coord in f['geometry']['coordinates'][0]: 95 | plon += coord[0] 96 | plat += coord[1] 97 | pcount = len(f['geometry']['coordinates'][0]) 98 | lon += plon / pcount 99 | lat += plat / pcount 100 | count += 1 101 | 102 | lat /= count 103 | lon /= count 104 | if abs(lat) < 0.1 and abs(lon) < 0.1: 105 | return None 106 | 107 | # Now do the Nominatim request 108 | resp = requests.get('https://nominatim.openstreetmap.org/reverse', { 109 | 'format': 'jsonv2', 110 | 'lat': lat, 111 | 'lon': lon, 112 | }) 113 | if resp.status_code != 200: 114 | return None 115 | data = resp.json().get('address') 116 | if not data: 117 | return None 118 | city = data.get('city') or data.get('town') or data.get('village') or data.get('hamlet') or data.get('place') 119 | state = data.get('state') 120 | country = data.get('country') 121 | # TODO: skip empty parts 122 | return f'{city}, {state}, {country}' 123 | 124 | 125 | def plugin_id(party_id, piece_id): 126 | return f'mpd_{party_id}_{piece_id}' 127 | 128 | 129 | @papp.route('/', endpoint='list') 130 | def list_projects(): 131 | user_id = session.get('user_id') 132 | db = get_db() 133 | cur = db.execute( 134 | 'select * from parties ' 135 | 'order by scheduled desc, created desc ' 136 | 'limit 100') 137 | parties = cur.fetchall() 138 | today = datetime.date.today().isoformat() 139 | return render_template( 140 | 'list.html', logged_in=user_id is not None, user_id=user_id, 141 | parties=parties, today=today) 142 | 143 | 144 | @papp.route('/create', methods=['GET', 'POST']) 145 | def create(): 146 | if 'user_id' not in session: 147 | return redirect(url_for('party.list')) 148 | if request.method == 'POST': 149 | title = request.form['title'].strip() 150 | if not title: 151 | flash('Empty title lol') 152 | else: 153 | db = get_db() 154 | db.execute( 155 | 'insert into parties (title, created, owner_id) ' 156 | 'values (?, ?, ?)', 157 | (title, round(datetime.datetime.now().timestamp()), 158 | session['user_id']) 159 | ) 160 | db.commit() 161 | return redirect(url_for('party.list')) 162 | return render_template('create.html') 163 | 164 | 165 | @papp.route('/', methods=['GET', 'POST']) 166 | def party(party_id): 167 | db = get_db() 168 | party = db.execute( 169 | 'select * from parties where party_id = ?', (party_id,)).fetchone() 170 | if not party: 171 | flash('Party not found') 172 | return redirect(url_for('party.list')) 173 | is_owner = 'user_id' in session and session['user_id'] == party['owner_id'] 174 | 175 | if is_owner and request.method == 'POST': 176 | title = request.form['title'].strip() 177 | if not title: 178 | flash('Empty title lol') 179 | scheduled = request.form['scheduled'] 180 | if not re.match(r'', scheduled): 181 | scheduled = None 182 | if 'json' in request.files and request.files['json'].filename: 183 | # Uploading a new JSON 184 | data = JsonValidator(request.files['json'].read()) 185 | if data.error: 186 | flash(f'Error reading GeoJSON: {data.error}') 187 | return redirect(url_for('party.party', party_id=party_id)) 188 | db.execute( 189 | 'update parties set title = ?, scheduled = ?, pie = ?, ' 190 | 'location = ?, piece_count = ? where party_id = ?', 191 | (title, scheduled, data.pie, data.location, data.piece_count, 192 | party_id)) 193 | else: 194 | db.execute( 195 | 'update parties set title = ?, scheduled = ? ' 196 | 'where party_id = ?', 197 | (title, scheduled, party_id)) 198 | db.commit() 199 | return redirect(url_for('party.party', party_id=party_id)) 200 | 201 | pie = None if not party['pie'] else json.loads(party['pie']) 202 | return render_template( 203 | 'party.html', is_owner=is_owner, party=party, pie=pie) 204 | 205 | 206 | @papp.route('/delete/') 207 | def delete_party(party_id): 208 | db = get_db() 209 | party = db.execute( 210 | 'select * from parties where party_id = ?', (party_id,)).fetchone() 211 | if not party: 212 | flash('Party not found') 213 | return redirect(url_for('party.party', party_id=party_id)) 214 | is_owner = 'user_id' in session and session['user_id'] == party['owner_id'] 215 | if not is_owner: 216 | flash('No permissions for that') 217 | return redirect(url_for('party.party', party_id=party_id)) 218 | db.execute('delete from parties where party_id = ?', (party_id,)) 219 | db.commit() 220 | flash('Party deleted') 221 | return redirect(url_for('party.list')) 222 | 223 | 224 | @papp.route('/edit/', methods=['GET', 'POST']) 225 | def edit_pie(party_id): 226 | db = get_db() 227 | party = db.execute( 228 | 'select * from parties where party_id = ?', (party_id,)).fetchone() 229 | if not party: 230 | flash('Party not found') 231 | return redirect(url_for('party.list')) 232 | is_owner = 'user_id' in session and session['user_id'] == party['owner_id'] 233 | if not is_owner: 234 | flash('No permissions for that') 235 | return redirect(url_for('party.party', party_id=party_id)) 236 | 237 | if request.method == 'POST': 238 | data = JsonValidator(request.form['pie']) 239 | if data.error: 240 | flash(f'Error reading GeoJSON: {data.error}') 241 | return redirect(url_for('party.party', party_id=party_id)) 242 | db.execute( 243 | 'update parties set pie = ?, ' 244 | 'location = ?, piece_count = ? where party_id = ?', 245 | (data.pie, data.location, data.piece_count, party_id)) 246 | return redirect(url_for('party.party', party_id=party_id)) 247 | 248 | pie = None if not party['pie'] else json.loads(party['pie']) 249 | return render_template('edit.html', party=party, pie=pie) 250 | 251 | 252 | @papp.route('//') 253 | def piece(party_id, piece_id): 254 | db = get_db() 255 | party = db.execute( 256 | 'select * from parties where party_id = ?', (party_id,)).fetchone() 257 | if not party: 258 | flash('Party not found') 259 | return redirect(url_for('party.list')) 260 | if not party['pie']: 261 | flash('No pie in the party') 262 | return redirect(url_for('party.list')) 263 | data = json.loads(party['pie']) 264 | piece = None 265 | for f in data['features']: 266 | if f['properties']['id'] == piece_id: 267 | piece = f 268 | if not piece: 269 | flash(f'Could not find piece "{piece_id}"') 270 | return redirect(url_for('party.list')) 271 | 272 | everydoor_url = url_for( 273 | 'party.piece_ed_plugin', party_id=party_id, 274 | piece_id=piece_id, _external=True) 275 | everydoor_url = (f'https://plugins.every-door.app/i/my?' 276 | f'url={urllib.parse.quote(everydoor_url)}&update=true') 277 | 278 | qr = qrcode.make( 279 | everydoor_url, image_factory=qrcode.image.svg.SvgPathImage, 280 | border=1, box_size=20) 281 | 282 | return render_template( 283 | 'piece.html', party=party, piece=piece, everydoor=everydoor_url, 284 | qr=qr.to_string().decode()) 285 | 286 | 287 | @papp.route('/mpd__.edp') 288 | def piece_ed_plugin(party_id, piece_id): 289 | db = get_db() 290 | party = db.execute( 291 | 'select * from parties where party_id = ?', (party_id,)).fetchone() 292 | if not party: 293 | flash('Party not found') 294 | return redirect(url_for('party.list')) 295 | if not party['pie']: 296 | flash('No pie in the party') 297 | return redirect(url_for('party.list')) 298 | data = json.loads(party['pie']) 299 | piece = None 300 | for f in data['features']: 301 | if f['properties']['id'] == piece_id: 302 | piece = f 303 | if not piece: 304 | flash(f'Could not find piece "{piece_id}"') 305 | return redirect(url_for('party.list')) 306 | 307 | # Now prepare the zip file 308 | piece['properties']['fill-opacity'] = 0.0 309 | piece['properties']['stroke-width'] = 3.0 310 | piece['properties']['stroke'] = '#3388ff' 311 | pluginyaml = f'''--- 312 | id: mpd_{party_id}_{piece_id} 313 | name: Piece {piece_id} of {party['title']} 314 | version: 1 315 | overlays: 316 | - url: piece.geojson 317 | ''' 318 | buffer = io.BytesIO() 319 | with zipfile.ZipFile(buffer, 'w') as z: 320 | z.writestr('plugin.yaml', pluginyaml) 321 | z.writestr('piece.geojson', json.dumps(piece)) 322 | 323 | resp = make_response(buffer.getvalue()) 324 | resp.headers['Content-Type'] = 'application/zip' 325 | return resp 326 | 327 | 328 | @papp.route('/login') 329 | def login(): 330 | url = url_for('party.authorize', _external=True) 331 | return oauth.openstreetmap.authorize_redirect(url) 332 | 333 | 334 | @papp.route('/authorize') 335 | def authorize(): 336 | oauth.openstreetmap.authorize_access_token() 337 | resp = oauth.openstreetmap.get('user/details.json') 338 | resp.raise_for_status() 339 | profile = resp.json() 340 | user_id = profile['user']['id'] 341 | # there is "display_name" if we need it 342 | session['user_id'] = user_id 343 | return redirect(url_for('party.list')) 344 | 345 | 346 | @papp.route('/logout') 347 | def logout(): 348 | if 'user_id' in session: 349 | del session['user_id'] 350 | return redirect(url_for('party.list')) 351 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 2 3 | requires-python = ">=3.12" 4 | 5 | [[package]] 6 | name = "authlib" 7 | version = "1.5.2" 8 | source = { registry = "https://pypi.org/simple" } 9 | dependencies = [ 10 | { name = "cryptography" }, 11 | ] 12 | sdist = { url = "https://files.pythonhosted.org/packages/2a/b3/5f5bc73c6558a21f951ffd267f41c6340d15f5fe0ff4b6bf37694f3558b8/authlib-1.5.2.tar.gz", hash = "sha256:fe85ec7e50c5f86f1e2603518bb3b4f632985eb4a355e52256530790e326c512", size = 153000, upload_time = "2025-04-02T10:31:36.488Z" } 13 | wheels = [ 14 | { url = "https://files.pythonhosted.org/packages/e3/71/8dcec996ea8cc882cec9cace91ae1b630a226b88b0f04ab2ffa778f565ad/authlib-1.5.2-py2.py3-none-any.whl", hash = "sha256:8804dd4402ac5e4a0435ac49e0b6e19e395357cfa632a3f624dcb4f6df13b4b1", size = 232055, upload_time = "2025-04-02T10:31:34.59Z" }, 15 | ] 16 | 17 | [[package]] 18 | name = "blinker" 19 | version = "1.9.0" 20 | source = { registry = "https://pypi.org/simple" } 21 | sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload_time = "2024-11-08T17:25:47.436Z" } 22 | wheels = [ 23 | { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload_time = "2024-11-08T17:25:46.184Z" }, 24 | ] 25 | 26 | [[package]] 27 | name = "certifi" 28 | version = "2025.4.26" 29 | source = { registry = "https://pypi.org/simple" } 30 | sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload_time = "2025-04-26T02:12:29.51Z" } 31 | wheels = [ 32 | { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload_time = "2025-04-26T02:12:27.662Z" }, 33 | ] 34 | 35 | [[package]] 36 | name = "cffi" 37 | version = "1.17.1" 38 | source = { registry = "https://pypi.org/simple" } 39 | dependencies = [ 40 | { name = "pycparser" }, 41 | ] 42 | sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload_time = "2024-09-04T20:45:21.852Z" } 43 | wheels = [ 44 | { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload_time = "2024-09-04T20:44:12.232Z" }, 45 | { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload_time = "2024-09-04T20:44:13.739Z" }, 46 | { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload_time = "2024-09-04T20:44:15.231Z" }, 47 | { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload_time = "2024-09-04T20:44:17.188Z" }, 48 | { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload_time = "2024-09-04T20:44:18.688Z" }, 49 | { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload_time = "2024-09-04T20:44:20.248Z" }, 50 | { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload_time = "2024-09-04T20:44:21.673Z" }, 51 | { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload_time = "2024-09-04T20:44:23.245Z" }, 52 | { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload_time = "2024-09-04T20:44:24.757Z" }, 53 | { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload_time = "2024-09-04T20:44:26.208Z" }, 54 | { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload_time = "2024-09-04T20:44:27.578Z" }, 55 | { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload_time = "2024-09-04T20:44:28.956Z" }, 56 | { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload_time = "2024-09-04T20:44:30.289Z" }, 57 | { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload_time = "2024-09-04T20:44:32.01Z" }, 58 | { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload_time = "2024-09-04T20:44:33.606Z" }, 59 | { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload_time = "2024-09-04T20:44:35.191Z" }, 60 | { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload_time = "2024-09-04T20:44:36.743Z" }, 61 | { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload_time = "2024-09-04T20:44:38.492Z" }, 62 | { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload_time = "2024-09-04T20:44:40.046Z" }, 63 | { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload_time = "2024-09-04T20:44:41.616Z" }, 64 | { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload_time = "2024-09-04T20:44:43.733Z" }, 65 | { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload_time = "2024-09-04T20:44:45.309Z" }, 66 | ] 67 | 68 | [[package]] 69 | name = "charset-normalizer" 70 | version = "3.4.2" 71 | source = { registry = "https://pypi.org/simple" } 72 | sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload_time = "2025-05-02T08:34:42.01Z" } 73 | wheels = [ 74 | { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload_time = "2025-05-02T08:32:33.712Z" }, 75 | { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload_time = "2025-05-02T08:32:35.768Z" }, 76 | { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload_time = "2025-05-02T08:32:37.284Z" }, 77 | { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload_time = "2025-05-02T08:32:38.803Z" }, 78 | { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload_time = "2025-05-02T08:32:40.251Z" }, 79 | { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload_time = "2025-05-02T08:32:41.705Z" }, 80 | { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload_time = "2025-05-02T08:32:43.709Z" }, 81 | { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload_time = "2025-05-02T08:32:46.197Z" }, 82 | { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload_time = "2025-05-02T08:32:48.105Z" }, 83 | { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload_time = "2025-05-02T08:32:49.719Z" }, 84 | { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload_time = "2025-05-02T08:32:51.404Z" }, 85 | { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload_time = "2025-05-02T08:32:53.079Z" }, 86 | { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload_time = "2025-05-02T08:32:54.573Z" }, 87 | { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload_time = "2025-05-02T08:32:56.363Z" }, 88 | { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload_time = "2025-05-02T08:32:58.551Z" }, 89 | { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload_time = "2025-05-02T08:33:00.342Z" }, 90 | { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload_time = "2025-05-02T08:33:02.081Z" }, 91 | { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload_time = "2025-05-02T08:33:04.063Z" }, 92 | { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload_time = "2025-05-02T08:33:06.418Z" }, 93 | { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload_time = "2025-05-02T08:33:08.183Z" }, 94 | { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload_time = "2025-05-02T08:33:09.986Z" }, 95 | { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload_time = "2025-05-02T08:33:11.814Z" }, 96 | { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload_time = "2025-05-02T08:33:13.707Z" }, 97 | { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload_time = "2025-05-02T08:33:15.458Z" }, 98 | { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload_time = "2025-05-02T08:33:17.06Z" }, 99 | { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload_time = "2025-05-02T08:33:18.753Z" }, 100 | { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload_time = "2025-05-02T08:34:40.053Z" }, 101 | ] 102 | 103 | [[package]] 104 | name = "click" 105 | version = "8.1.8" 106 | source = { registry = "https://pypi.org/simple" } 107 | dependencies = [ 108 | { name = "colorama", marker = "sys_platform == 'win32'" }, 109 | ] 110 | sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload_time = "2024-12-21T18:38:44.339Z" } 111 | wheels = [ 112 | { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload_time = "2024-12-21T18:38:41.666Z" }, 113 | ] 114 | 115 | [[package]] 116 | name = "colorama" 117 | version = "0.4.6" 118 | source = { registry = "https://pypi.org/simple" } 119 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload_time = "2022-10-25T02:36:22.414Z" } 120 | wheels = [ 121 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload_time = "2022-10-25T02:36:20.889Z" }, 122 | ] 123 | 124 | [[package]] 125 | name = "cryptography" 126 | version = "44.0.3" 127 | source = { registry = "https://pypi.org/simple" } 128 | dependencies = [ 129 | { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, 130 | ] 131 | sdist = { url = "https://files.pythonhosted.org/packages/53/d6/1411ab4d6108ab167d06254c5be517681f1e331f90edf1379895bcb87020/cryptography-44.0.3.tar.gz", hash = "sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053", size = 711096, upload_time = "2025-05-02T19:36:04.667Z" } 132 | wheels = [ 133 | { url = "https://files.pythonhosted.org/packages/08/53/c776d80e9d26441bb3868457909b4e74dd9ccabd182e10b2b0ae7a07e265/cryptography-44.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88", size = 6670281, upload_time = "2025-05-02T19:34:50.665Z" }, 134 | { url = "https://files.pythonhosted.org/packages/6a/06/af2cf8d56ef87c77319e9086601bef621bedf40f6f59069e1b6d1ec498c5/cryptography-44.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137", size = 3959305, upload_time = "2025-05-02T19:34:53.042Z" }, 135 | { url = "https://files.pythonhosted.org/packages/ae/01/80de3bec64627207d030f47bf3536889efee8913cd363e78ca9a09b13c8e/cryptography-44.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c", size = 4171040, upload_time = "2025-05-02T19:34:54.675Z" }, 136 | { url = "https://files.pythonhosted.org/packages/bd/48/bb16b7541d207a19d9ae8b541c70037a05e473ddc72ccb1386524d4f023c/cryptography-44.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76", size = 3963411, upload_time = "2025-05-02T19:34:56.61Z" }, 137 | { url = "https://files.pythonhosted.org/packages/42/b2/7d31f2af5591d217d71d37d044ef5412945a8a8e98d5a2a8ae4fd9cd4489/cryptography-44.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359", size = 3689263, upload_time = "2025-05-02T19:34:58.591Z" }, 138 | { url = "https://files.pythonhosted.org/packages/25/50/c0dfb9d87ae88ccc01aad8eb93e23cfbcea6a6a106a9b63a7b14c1f93c75/cryptography-44.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43", size = 4196198, upload_time = "2025-05-02T19:35:00.988Z" }, 139 | { url = "https://files.pythonhosted.org/packages/66/c9/55c6b8794a74da652690c898cb43906310a3e4e4f6ee0b5f8b3b3e70c441/cryptography-44.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01", size = 3966502, upload_time = "2025-05-02T19:35:03.091Z" }, 140 | { url = "https://files.pythonhosted.org/packages/b6/f7/7cb5488c682ca59a02a32ec5f975074084db4c983f849d47b7b67cc8697a/cryptography-44.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d", size = 4196173, upload_time = "2025-05-02T19:35:05.018Z" }, 141 | { url = "https://files.pythonhosted.org/packages/d2/0b/2f789a8403ae089b0b121f8f54f4a3e5228df756e2146efdf4a09a3d5083/cryptography-44.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904", size = 4087713, upload_time = "2025-05-02T19:35:07.187Z" }, 142 | { url = "https://files.pythonhosted.org/packages/1d/aa/330c13655f1af398fc154089295cf259252f0ba5df93b4bc9d9c7d7f843e/cryptography-44.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44", size = 4299064, upload_time = "2025-05-02T19:35:08.879Z" }, 143 | { url = "https://files.pythonhosted.org/packages/10/a8/8c540a421b44fd267a7d58a1fd5f072a552d72204a3f08194f98889de76d/cryptography-44.0.3-cp37-abi3-win32.whl", hash = "sha256:3be3f649d91cb182c3a6bd336de8b61a0a71965bd13d1a04a0e15b39c3d5809d", size = 2773887, upload_time = "2025-05-02T19:35:10.41Z" }, 144 | { url = "https://files.pythonhosted.org/packages/b9/0d/c4b1657c39ead18d76bbd122da86bd95bdc4095413460d09544000a17d56/cryptography-44.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:3883076d5c4cc56dbef0b898a74eb6992fdac29a7b9013870b34efe4ddb39a0d", size = 3209737, upload_time = "2025-05-02T19:35:12.12Z" }, 145 | { url = "https://files.pythonhosted.org/packages/34/a3/ad08e0bcc34ad436013458d7528e83ac29910943cea42ad7dd4141a27bbb/cryptography-44.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:5639c2b16764c6f76eedf722dbad9a0914960d3489c0cc38694ddf9464f1bb2f", size = 6673501, upload_time = "2025-05-02T19:35:13.775Z" }, 146 | { url = "https://files.pythonhosted.org/packages/b1/f0/7491d44bba8d28b464a5bc8cc709f25a51e3eac54c0a4444cf2473a57c37/cryptography-44.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759", size = 3960307, upload_time = "2025-05-02T19:35:15.917Z" }, 147 | { url = "https://files.pythonhosted.org/packages/f7/c8/e5c5d0e1364d3346a5747cdcd7ecbb23ca87e6dea4f942a44e88be349f06/cryptography-44.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645", size = 4170876, upload_time = "2025-05-02T19:35:18.138Z" }, 148 | { url = "https://files.pythonhosted.org/packages/73/96/025cb26fc351d8c7d3a1c44e20cf9a01e9f7cf740353c9c7a17072e4b264/cryptography-44.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2", size = 3964127, upload_time = "2025-05-02T19:35:19.864Z" }, 149 | { url = "https://files.pythonhosted.org/packages/01/44/eb6522db7d9f84e8833ba3bf63313f8e257729cf3a8917379473fcfd6601/cryptography-44.0.3-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54", size = 3689164, upload_time = "2025-05-02T19:35:21.449Z" }, 150 | { url = "https://files.pythonhosted.org/packages/68/fb/d61a4defd0d6cee20b1b8a1ea8f5e25007e26aeb413ca53835f0cae2bcd1/cryptography-44.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93", size = 4198081, upload_time = "2025-05-02T19:35:23.187Z" }, 151 | { url = "https://files.pythonhosted.org/packages/1b/50/457f6911d36432a8811c3ab8bd5a6090e8d18ce655c22820994913dd06ea/cryptography-44.0.3-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c", size = 3967716, upload_time = "2025-05-02T19:35:25.426Z" }, 152 | { url = "https://files.pythonhosted.org/packages/35/6e/dca39d553075980ccb631955c47b93d87d27f3596da8d48b1ae81463d915/cryptography-44.0.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f", size = 4197398, upload_time = "2025-05-02T19:35:27.678Z" }, 153 | { url = "https://files.pythonhosted.org/packages/9b/9d/d1f2fe681eabc682067c66a74addd46c887ebacf39038ba01f8860338d3d/cryptography-44.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5", size = 4087900, upload_time = "2025-05-02T19:35:29.312Z" }, 154 | { url = "https://files.pythonhosted.org/packages/c4/f5/3599e48c5464580b73b236aafb20973b953cd2e7b44c7c2533de1d888446/cryptography-44.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b", size = 4301067, upload_time = "2025-05-02T19:35:31.547Z" }, 155 | { url = "https://files.pythonhosted.org/packages/a7/6c/d2c48c8137eb39d0c193274db5c04a75dab20d2f7c3f81a7dcc3a8897701/cryptography-44.0.3-cp39-abi3-win32.whl", hash = "sha256:c138abae3a12a94c75c10499f1cbae81294a6f983b3af066390adee73f433028", size = 2775467, upload_time = "2025-05-02T19:35:33.805Z" }, 156 | { url = "https://files.pythonhosted.org/packages/c9/ad/51f212198681ea7b0deaaf8846ee10af99fba4e894f67b353524eab2bbe5/cryptography-44.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:5d186f32e52e66994dce4f766884bcb9c68b8da62d61d9d215bfe5fb56d21334", size = 3210375, upload_time = "2025-05-02T19:35:35.369Z" }, 157 | ] 158 | 159 | [[package]] 160 | name = "flask" 161 | version = "3.1.0" 162 | source = { registry = "https://pypi.org/simple" } 163 | dependencies = [ 164 | { name = "blinker" }, 165 | { name = "click" }, 166 | { name = "itsdangerous" }, 167 | { name = "jinja2" }, 168 | { name = "werkzeug" }, 169 | ] 170 | sdist = { url = "https://files.pythonhosted.org/packages/89/50/dff6380f1c7f84135484e176e0cac8690af72fa90e932ad2a0a60e28c69b/flask-3.1.0.tar.gz", hash = "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac", size = 680824, upload_time = "2024-11-13T18:24:38.127Z" } 171 | wheels = [ 172 | { url = "https://files.pythonhosted.org/packages/af/47/93213ee66ef8fae3b93b3e29206f6b251e65c97bd91d8e1c5596ef15af0a/flask-3.1.0-py3-none-any.whl", hash = "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136", size = 102979, upload_time = "2024-11-13T18:24:36.135Z" }, 173 | ] 174 | 175 | [[package]] 176 | name = "gunicorn" 177 | version = "23.0.0" 178 | source = { registry = "https://pypi.org/simple" } 179 | dependencies = [ 180 | { name = "packaging" }, 181 | ] 182 | sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload_time = "2024-08-10T20:25:27.378Z" } 183 | wheels = [ 184 | { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload_time = "2024-08-10T20:25:24.996Z" }, 185 | ] 186 | 187 | [[package]] 188 | name = "idna" 189 | version = "3.10" 190 | source = { registry = "https://pypi.org/simple" } 191 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload_time = "2024-09-15T18:07:39.745Z" } 192 | wheels = [ 193 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload_time = "2024-09-15T18:07:37.964Z" }, 194 | ] 195 | 196 | [[package]] 197 | name = "itsdangerous" 198 | version = "2.2.0" 199 | source = { registry = "https://pypi.org/simple" } 200 | sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload_time = "2024-04-16T21:28:15.614Z" } 201 | wheels = [ 202 | { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload_time = "2024-04-16T21:28:14.499Z" }, 203 | ] 204 | 205 | [[package]] 206 | name = "jinja2" 207 | version = "3.1.6" 208 | source = { registry = "https://pypi.org/simple" } 209 | dependencies = [ 210 | { name = "markupsafe" }, 211 | ] 212 | sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload_time = "2025-03-05T20:05:02.478Z" } 213 | wheels = [ 214 | { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload_time = "2025-03-05T20:05:00.369Z" }, 215 | ] 216 | 217 | [[package]] 218 | name = "mapping-pie-dispenser" 219 | version = "0.1.0" 220 | source = { editable = "." } 221 | dependencies = [ 222 | { name = "authlib" }, 223 | { name = "click" }, 224 | { name = "flask" }, 225 | { name = "gunicorn" }, 226 | { name = "qrcode" }, 227 | { name = "requests" }, 228 | ] 229 | 230 | [package.metadata] 231 | requires-dist = [ 232 | { name = "authlib", specifier = ">=1.5.2" }, 233 | { name = "click", specifier = ">=8.1.8" }, 234 | { name = "flask", specifier = ">=3.1.0" }, 235 | { name = "gunicorn", specifier = ">=23.0.0" }, 236 | { name = "qrcode", specifier = ">=8.2" }, 237 | { name = "requests", specifier = ">=2.32.3" }, 238 | ] 239 | 240 | [[package]] 241 | name = "markupsafe" 242 | version = "3.0.2" 243 | source = { registry = "https://pypi.org/simple" } 244 | sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload_time = "2024-10-18T15:21:54.129Z" } 245 | wheels = [ 246 | { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload_time = "2024-10-18T15:21:13.777Z" }, 247 | { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload_time = "2024-10-18T15:21:14.822Z" }, 248 | { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload_time = "2024-10-18T15:21:15.642Z" }, 249 | { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload_time = "2024-10-18T15:21:17.133Z" }, 250 | { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload_time = "2024-10-18T15:21:18.064Z" }, 251 | { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload_time = "2024-10-18T15:21:18.859Z" }, 252 | { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload_time = "2024-10-18T15:21:19.671Z" }, 253 | { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload_time = "2024-10-18T15:21:20.971Z" }, 254 | { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload_time = "2024-10-18T15:21:22.646Z" }, 255 | { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload_time = "2024-10-18T15:21:23.499Z" }, 256 | { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload_time = "2024-10-18T15:21:24.577Z" }, 257 | { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload_time = "2024-10-18T15:21:25.382Z" }, 258 | { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload_time = "2024-10-18T15:21:26.199Z" }, 259 | { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload_time = "2024-10-18T15:21:27.029Z" }, 260 | { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload_time = "2024-10-18T15:21:27.846Z" }, 261 | { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload_time = "2024-10-18T15:21:28.744Z" }, 262 | { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload_time = "2024-10-18T15:21:29.545Z" }, 263 | { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload_time = "2024-10-18T15:21:30.366Z" }, 264 | { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload_time = "2024-10-18T15:21:31.207Z" }, 265 | { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload_time = "2024-10-18T15:21:32.032Z" }, 266 | { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload_time = "2024-10-18T15:21:33.625Z" }, 267 | { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload_time = "2024-10-18T15:21:34.611Z" }, 268 | { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload_time = "2024-10-18T15:21:35.398Z" }, 269 | { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload_time = "2024-10-18T15:21:36.231Z" }, 270 | { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload_time = "2024-10-18T15:21:37.073Z" }, 271 | { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload_time = "2024-10-18T15:21:37.932Z" }, 272 | { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload_time = "2024-10-18T15:21:39.799Z" }, 273 | { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload_time = "2024-10-18T15:21:40.813Z" }, 274 | { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload_time = "2024-10-18T15:21:41.814Z" }, 275 | { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload_time = "2024-10-18T15:21:42.784Z" }, 276 | ] 277 | 278 | [[package]] 279 | name = "packaging" 280 | version = "25.0" 281 | source = { registry = "https://pypi.org/simple" } 282 | sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload_time = "2025-04-19T11:48:59.673Z" } 283 | wheels = [ 284 | { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload_time = "2025-04-19T11:48:57.875Z" }, 285 | ] 286 | 287 | [[package]] 288 | name = "pycparser" 289 | version = "2.22" 290 | source = { registry = "https://pypi.org/simple" } 291 | sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload_time = "2024-03-30T13:22:22.564Z" } 292 | wheels = [ 293 | { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload_time = "2024-03-30T13:22:20.476Z" }, 294 | ] 295 | 296 | [[package]] 297 | name = "qrcode" 298 | version = "8.2" 299 | source = { registry = "https://pypi.org/simple" } 300 | dependencies = [ 301 | { name = "colorama", marker = "sys_platform == 'win32'" }, 302 | ] 303 | sdist = { url = "https://files.pythonhosted.org/packages/8f/b2/7fc2931bfae0af02d5f53b174e9cf701adbb35f39d69c2af63d4a39f81a9/qrcode-8.2.tar.gz", hash = "sha256:35c3f2a4172b33136ab9f6b3ef1c00260dd2f66f858f24d88418a015f446506c", size = 43317, upload_time = "2025-05-01T15:44:24.726Z" } 304 | wheels = [ 305 | { url = "https://files.pythonhosted.org/packages/dd/b8/d2d6d731733f51684bbf76bf34dab3b70a9148e8f2cef2bb544fccec681a/qrcode-8.2-py3-none-any.whl", hash = "sha256:16e64e0716c14960108e85d853062c9e8bba5ca8252c0b4d0231b9df4060ff4f", size = 45986, upload_time = "2025-05-01T15:44:22.781Z" }, 306 | ] 307 | 308 | [[package]] 309 | name = "requests" 310 | version = "2.32.3" 311 | source = { registry = "https://pypi.org/simple" } 312 | dependencies = [ 313 | { name = "certifi" }, 314 | { name = "charset-normalizer" }, 315 | { name = "idna" }, 316 | { name = "urllib3" }, 317 | ] 318 | sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload_time = "2024-05-29T15:37:49.536Z" } 319 | wheels = [ 320 | { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload_time = "2024-05-29T15:37:47.027Z" }, 321 | ] 322 | 323 | [[package]] 324 | name = "urllib3" 325 | version = "2.4.0" 326 | source = { registry = "https://pypi.org/simple" } 327 | sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload_time = "2025-04-10T15:23:39.232Z" } 328 | wheels = [ 329 | { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload_time = "2025-04-10T15:23:37.377Z" }, 330 | ] 331 | 332 | [[package]] 333 | name = "werkzeug" 334 | version = "3.1.3" 335 | source = { registry = "https://pypi.org/simple" } 336 | dependencies = [ 337 | { name = "markupsafe" }, 338 | ] 339 | sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload_time = "2024-11-08T15:52:18.093Z" } 340 | wheels = [ 341 | { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload_time = "2024-11-08T15:52:16.132Z" }, 342 | ] 343 | -------------------------------------------------------------------------------- /mpd/static/Leaflet.Editable.js: -------------------------------------------------------------------------------- 1 | ;((factory, window) => { 2 | /*globals define, module, require*/ 3 | 4 | // define an AMD module that relies on 'leaflet' 5 | if (typeof define === 'function' && define.amd) { 6 | define(['leaflet'], factory) 7 | 8 | // define a Common JS module that relies on 'leaflet' 9 | } else if (typeof exports === 'object') { 10 | module.exports = factory(require('leaflet')) 11 | } 12 | 13 | // attach your plugin to the global 'L' variable 14 | if (typeof window !== 'undefined' && window.L) { 15 | factory(window.L) 16 | } 17 | })((L) => { 18 | // 🍂miniclass CancelableEvent (Event objects) 19 | // 🍂method cancel() 20 | // Cancel any subsequent action. 21 | 22 | // 🍂miniclass VertexEvent (Event objects) 23 | // 🍂property vertex: VertexMarker 24 | // The vertex that fires the event. 25 | 26 | // 🍂miniclass ShapeEvent (Event objects) 27 | // 🍂property shape: Array 28 | // The shape (LatLngs array) subject of the action. 29 | 30 | // 🍂miniclass CancelableVertexEvent (Event objects) 31 | // 🍂inherits VertexEvent 32 | // 🍂inherits CancelableEvent 33 | 34 | // 🍂miniclass CancelableShapeEvent (Event objects) 35 | // 🍂inherits ShapeEvent 36 | // 🍂inherits CancelableEvent 37 | 38 | // 🍂miniclass LayerEvent (Event objects) 39 | // 🍂property layer: object 40 | // The Layer (Marker, Polyline…) subject of the action. 41 | 42 | // 🍂namespace Editable; 🍂class Editable; 🍂aka L.Editable 43 | // Main edition handler. By default, it is attached to the map 44 | // as `map.editTools` property. 45 | // Leaflet.Editable is made to be fully extendable. You have three ways to customize 46 | // the behaviour: using options, listening to events, or extending. 47 | L.Editable = L.Evented.extend({ 48 | statics: { 49 | FORWARD: 1, 50 | BACKWARD: -1, 51 | }, 52 | 53 | options: { 54 | // You can pass them when creating a map using the `editOptions` key. 55 | // 🍂option zIndex: int = 1000 56 | // The default zIndex of the editing tools. 57 | zIndex: 1000, 58 | 59 | // 🍂option polygonClass: class = L.Polygon 60 | // Class to be used when creating a new Polygon. 61 | polygonClass: L.Polygon, 62 | 63 | // 🍂option polylineClass: class = L.Polyline 64 | // Class to be used when creating a new Polyline. 65 | polylineClass: L.Polyline, 66 | 67 | // 🍂option markerClass: class = L.Marker 68 | // Class to be used when creating a new Marker. 69 | markerClass: L.Marker, 70 | 71 | // 🍂option circleMarkerClass: class = L.CircleMarker 72 | // Class to be used when creating a new CircleMarker. 73 | circleMarkerClass: L.CircleMarker, 74 | 75 | // 🍂option rectangleClass: class = L.Rectangle 76 | // Class to be used when creating a new Rectangle. 77 | rectangleClass: L.Rectangle, 78 | 79 | // 🍂option circleClass: class = L.Circle 80 | // Class to be used when creating a new Circle. 81 | circleClass: L.Circle, 82 | 83 | // 🍂option drawingCSSClass: string = 'leaflet-editable-drawing' 84 | // CSS class to be added to the map container while drawing. 85 | drawingCSSClass: 'leaflet-editable-drawing', 86 | 87 | // 🍂option drawingCursor: const = 'crosshair' 88 | // Cursor mode set to the map while drawing. 89 | drawingCursor: 'crosshair', 90 | 91 | // 🍂option editLayer: Layer = new L.LayerGroup() 92 | // Layer used to store edit tools (vertex, line guide…). 93 | editLayer: undefined, 94 | 95 | // 🍂option featuresLayer: Layer = new L.LayerGroup() 96 | // Default layer used to store drawn features (Marker, Polyline…). 97 | featuresLayer: undefined, 98 | 99 | // 🍂option polylineEditorClass: class = PolylineEditor 100 | // Class to be used as Polyline editor. 101 | polylineEditorClass: undefined, 102 | 103 | // 🍂option polygonEditorClass: class = PolygonEditor 104 | // Class to be used as Polygon editor. 105 | polygonEditorClass: undefined, 106 | 107 | // 🍂option markerEditorClass: class = MarkerEditor 108 | // Class to be used as Marker editor. 109 | markerEditorClass: undefined, 110 | 111 | // 🍂option circleMarkerEditorClass: class = CircleMarkerEditor 112 | // Class to be used as CircleMarker editor. 113 | circleMarkerEditorClass: undefined, 114 | 115 | // 🍂option rectangleEditorClass: class = RectangleEditor 116 | // Class to be used as Rectangle editor. 117 | rectangleEditorClass: undefined, 118 | 119 | // 🍂option circleEditorClass: class = CircleEditor 120 | // Class to be used as Circle editor. 121 | circleEditorClass: undefined, 122 | 123 | // 🍂option lineGuideOptions: hash = {} 124 | // Options to be passed to the line guides. 125 | lineGuideOptions: {}, 126 | 127 | // 🍂option skipMiddleMarkers: boolean = false 128 | // Set this to true if you don't want middle markers. 129 | skipMiddleMarkers: false, 130 | }, 131 | 132 | initialize: function (map, options) { 133 | L.setOptions(this, options) 134 | this._lastZIndex = this.options.zIndex 135 | this.map = map 136 | this.editLayer = this.createEditLayer() 137 | this.featuresLayer = this.createFeaturesLayer() 138 | this.forwardLineGuide = this.createLineGuide() 139 | this.backwardLineGuide = this.createLineGuide() 140 | }, 141 | 142 | fireAndForward: function (type, e) { 143 | e = e || {} 144 | e.editTools = this 145 | this.fire(type, e) 146 | this.map.fire(type, e) 147 | }, 148 | 149 | createLineGuide: function () { 150 | const options = L.extend( 151 | { dashArray: '5,10', weight: 1, interactive: false }, 152 | this.options.lineGuideOptions 153 | ) 154 | return L.polyline([], options) 155 | }, 156 | 157 | createVertexIcon: (options) => 158 | L.Browser.mobile && L.Browser.touch 159 | ? new L.Editable.TouchVertexIcon(options) 160 | : new L.Editable.VertexIcon(options), 161 | 162 | createEditLayer: function () { 163 | return this.options.editLayer || new L.LayerGroup().addTo(this.map) 164 | }, 165 | 166 | createFeaturesLayer: function () { 167 | return this.options.featuresLayer || new L.LayerGroup().addTo(this.map) 168 | }, 169 | 170 | moveForwardLineGuide: function (latlng) { 171 | if (this.forwardLineGuide._latlngs.length) { 172 | this.forwardLineGuide._latlngs[1] = latlng 173 | this.forwardLineGuide._bounds.extend(latlng) 174 | this.forwardLineGuide.redraw() 175 | } 176 | }, 177 | 178 | moveBackwardLineGuide: function (latlng) { 179 | if (this.backwardLineGuide._latlngs.length) { 180 | this.backwardLineGuide._latlngs[1] = latlng 181 | this.backwardLineGuide._bounds.extend(latlng) 182 | this.backwardLineGuide.redraw() 183 | } 184 | }, 185 | 186 | anchorForwardLineGuide: function (latlng) { 187 | this.forwardLineGuide._latlngs[0] = latlng 188 | this.forwardLineGuide._bounds.extend(latlng) 189 | this.forwardLineGuide.redraw() 190 | }, 191 | 192 | anchorBackwardLineGuide: function (latlng) { 193 | this.backwardLineGuide._latlngs[0] = latlng 194 | this.backwardLineGuide._bounds.extend(latlng) 195 | this.backwardLineGuide.redraw() 196 | }, 197 | 198 | attachForwardLineGuide: function () { 199 | this.editLayer.addLayer(this.forwardLineGuide) 200 | }, 201 | 202 | attachBackwardLineGuide: function () { 203 | this.editLayer.addLayer(this.backwardLineGuide) 204 | }, 205 | 206 | detachForwardLineGuide: function () { 207 | this.forwardLineGuide.setLatLngs([]) 208 | this.editLayer.removeLayer(this.forwardLineGuide) 209 | }, 210 | 211 | detachBackwardLineGuide: function () { 212 | this.backwardLineGuide.setLatLngs([]) 213 | this.editLayer.removeLayer(this.backwardLineGuide) 214 | }, 215 | 216 | blockEvents: function () { 217 | // Hack: force map not to listen to other layers events while drawing. 218 | if (!this._oldTargets) { 219 | this._oldTargets = this.map._targets 220 | this.map._targets = {} 221 | } 222 | }, 223 | 224 | unblockEvents: function () { 225 | if (this._oldTargets) { 226 | // Reset, but keep targets created while drawing. 227 | this.map._targets = L.extend(this.map._targets, this._oldTargets) 228 | delete this._oldTargets 229 | } 230 | }, 231 | 232 | registerForDrawing: function (editor) { 233 | if (this._drawingEditor) this.unregisterForDrawing(this._drawingEditor) 234 | this.blockEvents() 235 | editor.reset() // Make sure editor tools still receive events. 236 | this._drawingEditor = editor 237 | this.map.on('mousemove touchmove', editor.onDrawingMouseMove, editor) 238 | this.map.on('mousedown', this.onMousedown, this) 239 | this.map.on('mouseup', this.onMouseup, this) 240 | L.DomUtil.addClass(this.map._container, this.options.drawingCSSClass) 241 | this.defaultMapCursor = this.map._container.style.cursor 242 | this.map._container.style.cursor = this.options.drawingCursor 243 | }, 244 | 245 | unregisterForDrawing: function (editor) { 246 | this.unblockEvents() 247 | L.DomUtil.removeClass(this.map._container, this.options.drawingCSSClass) 248 | this.map._container.style.cursor = this.defaultMapCursor 249 | editor = editor || this._drawingEditor 250 | if (!editor) return 251 | this.map.off('mousemove touchmove', editor.onDrawingMouseMove, editor) 252 | this.map.off('mousedown', this.onMousedown, this) 253 | this.map.off('mouseup', this.onMouseup, this) 254 | if (editor !== this._drawingEditor) return 255 | delete this._drawingEditor 256 | if (editor._drawing) editor.cancelDrawing() 257 | }, 258 | 259 | onMousedown: function (e) { 260 | if (e.originalEvent.which != 1) return 261 | this._mouseDown = e 262 | this._drawingEditor.onDrawingMouseDown(e) 263 | }, 264 | 265 | onMouseup: function (e) { 266 | if (this._mouseDown) { 267 | const editor = this._drawingEditor 268 | const mouseDown = this._mouseDown 269 | this._mouseDown = null 270 | editor.onDrawingMouseUp(e) 271 | if (this._drawingEditor !== editor) return // onDrawingMouseUp may call unregisterFromDrawing. 272 | const origin = L.point( 273 | mouseDown.originalEvent.clientX, 274 | mouseDown.originalEvent.clientY 275 | ) 276 | const distance = L.point( 277 | e.originalEvent.clientX, 278 | e.originalEvent.clientY 279 | ).distanceTo(origin) 280 | if (Math.abs(distance) < 9 * (window.devicePixelRatio || 1)) 281 | this._drawingEditor.onDrawingClick(e) 282 | } 283 | }, 284 | 285 | // 🍂section Public methods 286 | // You will generally access them by the `map.editTools` 287 | // instance: 288 | // 289 | // `map.editTools.startPolyline();` 290 | 291 | // 🍂method drawing(): boolean 292 | // Return true if any drawing action is ongoing. 293 | drawing: function () { 294 | return this._drawingEditor?.drawing() 295 | }, 296 | 297 | // 🍂method stopDrawing() 298 | // When you need to stop any ongoing drawing, without needing to know which editor is active. 299 | stopDrawing: function () { 300 | this.unregisterForDrawing() 301 | }, 302 | 303 | // 🍂method commitDrawing() 304 | // When you need to commit any ongoing drawing, without needing to know which editor is active. 305 | commitDrawing: function (e) { 306 | if (!this._drawingEditor) return 307 | this._drawingEditor.commitDrawing(e) 308 | }, 309 | 310 | connectCreatedToMap: function (layer) { 311 | return this.featuresLayer.addLayer(layer) 312 | }, 313 | 314 | // 🍂method startPolyline(latlng: L.LatLng, options: hash): L.Polyline 315 | // Start drawing a Polyline. If `latlng` is given, a first point will be added. In any case, continuing on user click. 316 | // If `options` is given, it will be passed to the Polyline class constructor. 317 | startPolyline: function (latlng, options) { 318 | const line = this.createPolyline([], options) 319 | line.enableEdit(this.map).newShape(latlng) 320 | return line 321 | }, 322 | 323 | // 🍂method startPolygon(latlng: L.LatLng, options: hash): L.Polygon 324 | // Start drawing a Polygon. If `latlng` is given, a first point will be added. In any case, continuing on user click. 325 | // If `options` is given, it will be passed to the Polygon class constructor. 326 | startPolygon: function (latlng, options) { 327 | const polygon = this.createPolygon([], options) 328 | polygon.enableEdit(this.map).newShape(latlng) 329 | return polygon 330 | }, 331 | 332 | // 🍂method startMarker(latlng: L.LatLng, options: hash): L.Marker 333 | // Start adding a Marker. If `latlng` is given, the Marker will be shown first at this point. 334 | // In any case, it will follow the user mouse, and will have a final `latlng` on next click (or touch). 335 | // If `options` is given, it will be passed to the Marker class constructor. 336 | startMarker: function (latlng, options) { 337 | latlng = latlng || this.map.getCenter().clone() 338 | const marker = this.createMarker(latlng, options) 339 | marker.enableEdit(this.map).startDrawing() 340 | return marker 341 | }, 342 | 343 | // 🍂method startCircleMarker(latlng: L.LatLng, options: hash): L.CircleMarker 344 | // Start adding a CircleMarker. If `latlng` is given, the CircleMarker will be shown first at this point. 345 | // In any case, it will follow the user mouse, and will have a final `latlng` on next click (or touch). 346 | // If `options` is given, it will be passed to the CircleMarker class constructor. 347 | startCircleMarker: function (latlng, options) { 348 | latlng = latlng || this.map.getCenter().clone() 349 | const marker = this.createCircleMarker(latlng, options) 350 | marker.enableEdit(this.map).startDrawing() 351 | return marker 352 | }, 353 | 354 | // 🍂method startRectangle(latlng: L.LatLng, options: hash): L.Rectangle 355 | // Start drawing a Rectangle. If `latlng` is given, the Rectangle anchor will be added. In any case, continuing on user drag. 356 | // If `options` is given, it will be passed to the Rectangle class constructor. 357 | startRectangle: function (latlng, options) { 358 | const corner = latlng || L.latLng([0, 0]) 359 | const bounds = new L.LatLngBounds(corner, corner) 360 | const rectangle = this.createRectangle(bounds, options) 361 | rectangle.enableEdit(this.map).startDrawing() 362 | return rectangle 363 | }, 364 | 365 | // 🍂method startCircle(latlng: L.LatLng, options: hash): L.Circle 366 | // Start drawing a Circle. If `latlng` is given, the Circle anchor will be added. In any case, continuing on user drag. 367 | // If `options` is given, it will be passed to the Circle class constructor. 368 | startCircle: function (latlng, options) { 369 | latlng = latlng || this.map.getCenter().clone() 370 | const circle = this.createCircle(latlng, options) 371 | circle.enableEdit(this.map).startDrawing() 372 | return circle 373 | }, 374 | 375 | startHole: (editor, latlng) => { 376 | editor.newHole(latlng) 377 | }, 378 | 379 | createLayer: function (klass, latlngs, options) { 380 | options = L.Util.extend({ editOptions: { editTools: this } }, options) 381 | const layer = new klass(latlngs, options) 382 | // 🍂namespace Editable 383 | // 🍂event editable:created: LayerEvent 384 | // Fired when a new feature (Marker, Polyline…) is created. 385 | this.fireAndForward('editable:created', { layer: layer }) 386 | return layer 387 | }, 388 | 389 | createPolyline: function (latlngs, options) { 390 | return this.createLayer( 391 | options?.polylineClass || this.options.polylineClass, 392 | latlngs, 393 | options 394 | ) 395 | }, 396 | 397 | createPolygon: function (latlngs, options) { 398 | return this.createLayer( 399 | options?.polygonClass || this.options.polygonClass, 400 | latlngs, 401 | options 402 | ) 403 | }, 404 | 405 | createMarker: function (latlng, options) { 406 | return this.createLayer( 407 | options?.markerClass || this.options.markerClass, 408 | latlng, 409 | options 410 | ) 411 | }, 412 | 413 | createCircleMarker: function (latlng, options) { 414 | return this.createLayer( 415 | options?.circleMarkerClass || this.options.circleMarkerClass, 416 | latlng, 417 | options 418 | ) 419 | }, 420 | 421 | createRectangle: function (bounds, options) { 422 | return this.createLayer( 423 | options?.rectangleClass || this.options.rectangleClass, 424 | bounds, 425 | options 426 | ) 427 | }, 428 | 429 | createCircle: function (latlng, options) { 430 | return this.createLayer( 431 | options?.circleClass || this.options.circleClass, 432 | latlng, 433 | options 434 | ) 435 | }, 436 | }) 437 | 438 | L.extend(L.Editable, { 439 | makeCancellable: (e) => { 440 | e.cancel = () => { 441 | e._cancelled = true 442 | } 443 | }, 444 | }) 445 | 446 | // 🍂namespace Map; 🍂class Map 447 | // Leaflet.Editable add options and events to the `L.Map` object. 448 | // See `Editable` events for the list of events fired on the Map. 449 | // 🍂example 450 | // 451 | // ```js 452 | // var map = L.map('map', { 453 | // editable: true, 454 | // editOptions: { 455 | // … 456 | // } 457 | // }); 458 | // ``` 459 | // 🍂section Editable Map Options 460 | L.Map.mergeOptions({ 461 | // 🍂namespace Map 462 | // 🍂section Map Options 463 | // 🍂option editToolsClass: class = L.Editable 464 | // Class to be used as vertex, for path editing. 465 | editToolsClass: L.Editable, 466 | 467 | // 🍂option editable: boolean = false 468 | // Whether to create a L.Editable instance at map init. 469 | editable: false, 470 | 471 | // 🍂option editOptions: hash = {} 472 | // Options to pass to L.Editable when instantiating. 473 | editOptions: {}, 474 | }) 475 | 476 | L.Map.addInitHook(function () { 477 | this.whenReady(function () { 478 | if (this.options.editable) { 479 | this.editTools = new this.options.editToolsClass(this, this.options.editOptions) 480 | } 481 | }) 482 | }) 483 | 484 | L.Editable.VertexIcon = L.DivIcon.extend({ 485 | options: { 486 | iconSize: new L.Point(8, 8), 487 | }, 488 | }) 489 | 490 | L.Editable.TouchVertexIcon = L.Editable.VertexIcon.extend({ 491 | options: { 492 | iconSize: new L.Point(20, 20), 493 | }, 494 | }) 495 | 496 | // 🍂namespace Editable; 🍂class VertexMarker; Handler for dragging path vertices. 497 | L.Editable.VertexMarker = L.Marker.extend({ 498 | options: { 499 | draggable: true, 500 | className: 'leaflet-div-icon leaflet-vertex-icon', 501 | }, 502 | 503 | // 🍂section Public methods 504 | // The marker used to handle path vertex. You will usually interact with a `VertexMarker` 505 | // instance when listening for events like `editable:vertex:ctrlclick`. 506 | 507 | initialize: function (latlng, latlngs, editor, options) { 508 | // We don't use this._latlng, because on drag Leaflet replace it while 509 | // we want to keep reference. 510 | this.latlng = latlng 511 | this.latlngs = latlngs 512 | this.editor = editor 513 | L.Marker.prototype.initialize.call(this, latlng, options) 514 | this.options.icon = this.editor.tools.createVertexIcon({ 515 | className: this.options.className, 516 | }) 517 | this.latlng.__vertex = this 518 | this.connect() 519 | this.setZIndexOffset(editor.tools._lastZIndex + 1) 520 | }, 521 | 522 | connect: function () { 523 | this.editor.editLayer.addLayer(this) 524 | }, 525 | 526 | onAdd: function (map) { 527 | L.Marker.prototype.onAdd.call(this, map) 528 | this.on('drag', this.onDrag) 529 | this.on('dragstart', this.onDragStart) 530 | this.on('dragend', this.onDragEnd) 531 | this.on('mouseup', this.onMouseup) 532 | this.on('click', this.onClick) 533 | this.on('contextmenu', this.onContextMenu) 534 | this.on('mousedown touchstart', this.onMouseDown) 535 | this.on('mouseover', this.onMouseOver) 536 | this.on('mouseout', this.onMouseOut) 537 | this.addMiddleMarkers() 538 | }, 539 | 540 | onRemove: function (map) { 541 | if (this.middleMarker) this.middleMarker.delete() 542 | delete this.latlng.__vertex 543 | this.off('drag', this.onDrag) 544 | this.off('dragstart', this.onDragStart) 545 | this.off('dragend', this.onDragEnd) 546 | this.off('mouseup', this.onMouseup) 547 | this.off('click', this.onClick) 548 | this.off('contextmenu', this.onContextMenu) 549 | this.off('mousedown touchstart', this.onMouseDown) 550 | this.off('mouseover', this.onMouseOver) 551 | this.off('mouseout', this.onMouseOut) 552 | L.Marker.prototype.onRemove.call(this, map) 553 | }, 554 | 555 | onDrag: function (e) { 556 | e.vertex = this 557 | this.editor.onVertexMarkerDrag(e) 558 | const iconPos = L.DomUtil.getPosition(this._icon) 559 | const latlng = this._map.layerPointToLatLng(iconPos) 560 | this.latlng.update(latlng) 561 | this._latlng = this.latlng // Push back to Leaflet our reference. 562 | this.editor.refresh() 563 | if (this.middleMarker) this.middleMarker.updateLatLng() 564 | const next = this.getNext() 565 | if (next?.middleMarker) next.middleMarker.updateLatLng() 566 | }, 567 | 568 | onDragStart: function (e) { 569 | e.vertex = this 570 | this.editor.onVertexMarkerDragStart(e) 571 | }, 572 | 573 | onDragEnd: function (e) { 574 | e.vertex = this 575 | this.editor.onVertexMarkerDragEnd(e) 576 | }, 577 | 578 | onClick: function (e) { 579 | e.vertex = this 580 | this.editor.onVertexMarkerClick(e) 581 | }, 582 | 583 | onMouseup: function (e) { 584 | L.DomEvent.stop(e) 585 | e.vertex = this 586 | this.editor.map.fire('mouseup', e) 587 | }, 588 | 589 | onContextMenu: function (e) { 590 | e.vertex = this 591 | this.editor.onVertexMarkerContextMenu(e) 592 | }, 593 | 594 | onMouseDown: function (e) { 595 | e.vertex = this 596 | this.editor.onVertexMarkerMouseDown(e) 597 | }, 598 | 599 | onMouseOver: function (e) { 600 | e.vertex = this 601 | this.editor.onVertexMarkerMouseOver(e) 602 | }, 603 | 604 | onMouseOut: function (e) { 605 | e.vertex = this 606 | this.editor.onVertexMarkerMouseOut(e) 607 | }, 608 | 609 | // 🍂method delete() 610 | // Delete a vertex and the related LatLng. 611 | delete: function () { 612 | const next = this.getNext() // Compute before changing latlng 613 | this.latlngs.splice(this.getIndex(), 1) 614 | this.editor.editLayer.removeLayer(this) 615 | this.editor.onVertexDeleted({ latlng: this.latlng, vertex: this }) 616 | if (!this.latlngs.length) this.editor.deleteShape(this.latlngs) 617 | if (next) next.resetMiddleMarker() 618 | this.editor.refresh() 619 | }, 620 | 621 | // 🍂method getIndex(): int 622 | // Get the index of the current vertex among others of the same LatLngs group. 623 | getIndex: function () { 624 | return this.latlngs.indexOf(this.latlng) 625 | }, 626 | 627 | // 🍂method getLastIndex(): int 628 | // Get last vertex index of the LatLngs group of the current vertex. 629 | getLastIndex: function () { 630 | return this.latlngs.length - 1 631 | }, 632 | 633 | // 🍂method getPrevious(): VertexMarker 634 | // Get the previous VertexMarker in the same LatLngs group. 635 | getPrevious: function () { 636 | if (this.latlngs.length < 2) return 637 | const index = this.getIndex() 638 | let previousIndex = index - 1 639 | if (index === 0 && this.editor.CLOSED) previousIndex = this.getLastIndex() 640 | const previous = this.latlngs[previousIndex] 641 | if (previous) return previous.__vertex 642 | }, 643 | 644 | // 🍂method getNext(): VertexMarker 645 | // Get the next VertexMarker in the same LatLngs group. 646 | getNext: function () { 647 | if (this.latlngs.length < 2) return 648 | const index = this.getIndex() 649 | let nextIndex = index + 1 650 | if (index === this.getLastIndex() && this.editor.CLOSED) nextIndex = 0 651 | const next = this.latlngs[nextIndex] 652 | if (next) return next.__vertex 653 | }, 654 | 655 | addMiddleMarker: function (previous) { 656 | if (!this.editor.hasMiddleMarkers()) return 657 | previous = previous || this.getPrevious() 658 | if (previous && !this.middleMarker) 659 | this.middleMarker = this.editor.addMiddleMarker( 660 | previous, 661 | this, 662 | this.latlngs, 663 | this.editor 664 | ) 665 | }, 666 | 667 | addMiddleMarkers: function () { 668 | if (!this.editor.hasMiddleMarkers()) return 669 | const previous = this.getPrevious() 670 | if (previous) this.addMiddleMarker(previous) 671 | const next = this.getNext() 672 | if (next) next.resetMiddleMarker() 673 | }, 674 | 675 | resetMiddleMarker: function () { 676 | if (this.middleMarker) this.middleMarker.delete() 677 | this.addMiddleMarker() 678 | }, 679 | 680 | // 🍂method split() 681 | // Split the vertex LatLngs group at its index, if possible. 682 | split: function () { 683 | if (!this.editor.splitShape) return // Only for PolylineEditor 684 | this.editor.splitShape(this.latlngs, this.getIndex()) 685 | }, 686 | 687 | // 🍂method continue() 688 | // Continue the vertex LatLngs from this vertex. Only active for first and last vertices of a Polyline. 689 | continue: function () { 690 | if (!this.editor.continueBackward) return // Only for PolylineEditor 691 | const index = this.getIndex() 692 | if (index === 0) this.editor.continueBackward(this.latlngs) 693 | else if (index === this.getLastIndex()) this.editor.continueForward(this.latlngs) 694 | }, 695 | }) 696 | 697 | L.Editable.mergeOptions({ 698 | // 🍂namespace Editable 699 | // 🍂option vertexMarkerClass: class = VertexMarker 700 | // Class to be used as vertex, for path editing. 701 | vertexMarkerClass: L.Editable.VertexMarker, 702 | }) 703 | 704 | L.Editable.MiddleMarker = L.Marker.extend({ 705 | options: { 706 | opacity: 0.5, 707 | className: 'leaflet-div-icon leaflet-middle-icon', 708 | draggable: true, 709 | }, 710 | 711 | initialize: function (left, right, latlngs, editor, options) { 712 | this.left = left 713 | this.right = right 714 | this.editor = editor 715 | this.latlngs = latlngs 716 | L.Marker.prototype.initialize.call(this, this.computeLatLng(), options) 717 | this._opacity = this.options.opacity 718 | this.options.icon = this.editor.tools.createVertexIcon({ 719 | className: this.options.className, 720 | }) 721 | this.editor.editLayer.addLayer(this) 722 | this.setVisibility() 723 | }, 724 | 725 | setVisibility: function () { 726 | const leftPoint = this._map.latLngToContainerPoint(this.left.latlng) 727 | const rightPoint = this._map.latLngToContainerPoint(this.right.latlng) 728 | const size = L.point(this.options.icon.options.iconSize) 729 | if (leftPoint.distanceTo(rightPoint) < size.x * 3) this.hide() 730 | else this.show() 731 | }, 732 | 733 | show: function () { 734 | this.setOpacity(this._opacity) 735 | }, 736 | 737 | hide: function () { 738 | this.setOpacity(0) 739 | }, 740 | 741 | updateLatLng: function () { 742 | this.setLatLng(this.computeLatLng()) 743 | this.setVisibility() 744 | }, 745 | 746 | computeLatLng: function () { 747 | const leftPoint = this.editor.map.latLngToContainerPoint(this.left.latlng) 748 | const rightPoint = this.editor.map.latLngToContainerPoint(this.right.latlng) 749 | const y = (leftPoint.y + rightPoint.y) / 2 750 | const x = (leftPoint.x + rightPoint.x) / 2 751 | return this.editor.map.containerPointToLatLng([x, y]) 752 | }, 753 | 754 | onAdd: function (map) { 755 | L.Marker.prototype.onAdd.call(this, map) 756 | L.DomEvent.on(this._icon, 'mousedown touchstart', this.onMouseDown, this) 757 | map.on('zoomend', this.setVisibility, this) 758 | }, 759 | 760 | onRemove: function (map) { 761 | delete this.right.middleMarker 762 | L.DomEvent.off(this._icon, 'mousedown touchstart', this.onMouseDown, this) 763 | map.off('zoomend', this.setVisibility, this) 764 | L.Marker.prototype.onRemove.call(this, map) 765 | }, 766 | 767 | onMouseDown: function (e) { 768 | const iconPos = L.DomUtil.getPosition(this._icon) 769 | const latlng = this.editor.map.layerPointToLatLng(iconPos) 770 | e = { 771 | originalEvent: e, 772 | latlng: latlng, 773 | } 774 | if (this.options.opacity === 0) return 775 | L.Editable.makeCancellable(e) 776 | this.editor.onMiddleMarkerMouseDown(e) 777 | if (e._cancelled) return 778 | this.latlngs.splice(this.index(), 0, e.latlng) 779 | this.editor.refresh() 780 | const icon = this._icon 781 | const marker = this.editor.addVertexMarker(e.latlng, this.latlngs) 782 | this.editor.onNewVertex(marker) 783 | /* Hack to workaround browser not firing touchend when element is no more on DOM */ 784 | const parent = marker._icon.parentNode 785 | parent.removeChild(marker._icon) 786 | marker._icon = icon 787 | parent.appendChild(marker._icon) 788 | marker._initIcon() 789 | marker._initInteraction() 790 | marker.setOpacity(1) 791 | /* End hack */ 792 | // Transfer ongoing dragging to real marker 793 | L.Draggable._dragging = false 794 | marker.dragging._draggable._onDown(e.originalEvent) 795 | this.delete() 796 | }, 797 | 798 | delete: function () { 799 | this.editor.editLayer.removeLayer(this) 800 | }, 801 | 802 | index: function () { 803 | return this.latlngs.indexOf(this.right.latlng) 804 | }, 805 | }) 806 | 807 | L.Editable.mergeOptions({ 808 | // 🍂namespace Editable 809 | // 🍂option middleMarkerClass: class = VertexMarker 810 | // Class to be used as middle vertex, pulled by the user to create a new point in the middle of a path. 811 | middleMarkerClass: L.Editable.MiddleMarker, 812 | }) 813 | 814 | // 🍂namespace Editable; 🍂class BaseEditor; 🍂aka L.Editable.BaseEditor 815 | // When editing a feature (Marker, Polyline…), an editor is attached to it. This 816 | // editor basically knows how to handle the edition. 817 | L.Editable.BaseEditor = L.Handler.extend({ 818 | initialize: function (map, feature, options) { 819 | L.setOptions(this, options) 820 | this.map = map 821 | this.feature = feature 822 | this.feature.editor = this 823 | this.editLayer = new L.LayerGroup() 824 | this.tools = this.options.editTools || map.editTools 825 | }, 826 | 827 | // 🍂method enable(): this 828 | // Set up the drawing tools for the feature to be editable. 829 | addHooks: function () { 830 | if (this.isConnected()) this.onFeatureAdd() 831 | else this.feature.once('add', this.onFeatureAdd, this) 832 | this.onEnable() 833 | this.feature.on(this._getEvents(), this) 834 | }, 835 | 836 | // 🍂method disable(): this 837 | // Remove the drawing tools for the feature. 838 | removeHooks: function () { 839 | this.feature.off(this._getEvents(), this) 840 | if (this.feature.dragging) this.feature.dragging.disable() 841 | this.editLayer.clearLayers() 842 | this.tools.editLayer.removeLayer(this.editLayer) 843 | this.onDisable() 844 | if (this._drawing) this.cancelDrawing() 845 | }, 846 | 847 | // 🍂method drawing(): boolean 848 | // Return true if any drawing action is ongoing with this editor. 849 | drawing: function () { 850 | return !!this._drawing 851 | }, 852 | 853 | reset: () => {}, 854 | 855 | onFeatureAdd: function () { 856 | this.tools.editLayer.addLayer(this.editLayer) 857 | if (this.feature.dragging) this.feature.dragging.enable() 858 | }, 859 | 860 | hasMiddleMarkers: function () { 861 | return !this.options.skipMiddleMarkers && !this.tools.options.skipMiddleMarkers 862 | }, 863 | 864 | fireAndForward: function (type, e) { 865 | e = e || {} 866 | e.layer = this.feature 867 | this.feature.fire(type, e) 868 | this.tools.fireAndForward(type, e) 869 | }, 870 | 871 | onEnable: function () { 872 | // 🍂namespace Editable 873 | // 🍂event editable:enable: Event 874 | // Fired when an existing feature is ready to be edited. 875 | this.fireAndForward('editable:enable') 876 | }, 877 | 878 | onDisable: function () { 879 | // 🍂namespace Editable 880 | // 🍂event editable:disable: Event 881 | // Fired when an existing feature is not ready anymore to be edited. 882 | this.fireAndForward('editable:disable') 883 | }, 884 | 885 | onEditing: function () { 886 | // 🍂namespace Editable 887 | // 🍂event editable:editing: Event 888 | // Fired as soon as any change is made to the feature geometry. 889 | this.fireAndForward('editable:editing') 890 | }, 891 | 892 | onEdited: function () { 893 | // 🍂namespace Editable 894 | // 🍂event editable:edited: Event 895 | // Fired after any change is made to the feature geometry. 896 | this.fireAndForward('editable:edited') 897 | }, 898 | 899 | onStartDrawing: function () { 900 | // 🍂namespace Editable 901 | // 🍂section Drawing events 902 | // 🍂event editable:drawing:start: Event 903 | // Fired when a feature is to be drawn. 904 | this.fireAndForward('editable:drawing:start') 905 | }, 906 | 907 | onEndDrawing: function () { 908 | // 🍂namespace Editable 909 | // 🍂section Drawing events 910 | // 🍂event editable:drawing:end: Event 911 | // Fired when a feature is not drawn anymore. 912 | this.fireAndForward('editable:drawing:end') 913 | }, 914 | 915 | onCancelDrawing: function () { 916 | // 🍂namespace Editable 917 | // 🍂section Drawing events 918 | // 🍂event editable:drawing:cancel: Event 919 | // Fired when user cancel drawing while a feature is being drawn. 920 | this.fireAndForward('editable:drawing:cancel') 921 | }, 922 | 923 | onCommitDrawing: function (e) { 924 | // 🍂namespace Editable 925 | // 🍂section Drawing events 926 | // 🍂event editable:drawing:commit: Event 927 | // Fired when user finish drawing a feature. 928 | this.fireAndForward('editable:drawing:commit', e) 929 | this.onEdited() 930 | }, 931 | 932 | onDrawingMouseDown: function (e) { 933 | // 🍂namespace Editable 934 | // 🍂section Drawing events 935 | // 🍂event editable:drawing:mousedown: Event 936 | // Fired when user `mousedown` while drawing. 937 | this.fireAndForward('editable:drawing:mousedown', e) 938 | }, 939 | 940 | onDrawingMouseUp: function (e) { 941 | // 🍂namespace Editable 942 | // 🍂section Drawing events 943 | // 🍂event editable:drawing:mouseup: Event 944 | // Fired when user `mouseup` while drawing. 945 | this.fireAndForward('editable:drawing:mouseup', e) 946 | }, 947 | 948 | startDrawing: function () { 949 | if (!this._drawing) this._drawing = L.Editable.FORWARD 950 | this.tools.registerForDrawing(this) 951 | this.onStartDrawing() 952 | }, 953 | 954 | commitDrawing: function (e) { 955 | this.onCommitDrawing(e) 956 | this.endDrawing() 957 | }, 958 | 959 | cancelDrawing: function () { 960 | // If called during a vertex drag, the vertex will be removed before 961 | // the mouseup fires on it. This is a workaround. Maybe better fix is 962 | // To have L.Draggable reset it's status on disable (Leaflet side). 963 | L.Draggable._dragging = false 964 | this.onCancelDrawing() 965 | this.endDrawing() 966 | }, 967 | 968 | endDrawing: function () { 969 | this._drawing = false 970 | this.tools.unregisterForDrawing(this) 971 | this.onEndDrawing() 972 | }, 973 | 974 | onDrawingClick: function (e) { 975 | if (!this.drawing()) return 976 | L.Editable.makeCancellable(e) 977 | // 🍂namespace Editable 978 | // 🍂section Drawing events 979 | // 🍂event editable:drawing:click: CancelableEvent 980 | // Fired when user `click` while drawing, before any internal action is being processed. 981 | this.fireAndForward('editable:drawing:click', e) 982 | if (e._cancelled) return 983 | if (!this.isConnected()) this.connect(e) 984 | this.processDrawingClick(e) 985 | }, 986 | 987 | isConnected: function () { 988 | return this.map.hasLayer(this.feature) 989 | }, 990 | 991 | connect: function () { 992 | this.tools.connectCreatedToMap(this.feature) 993 | this.tools.editLayer.addLayer(this.editLayer) 994 | }, 995 | 996 | onMove: function (e) { 997 | // 🍂namespace Editable 998 | // 🍂section Drawing events 999 | // 🍂event editable:drawing:move: Event 1000 | // Fired when `move` mouse while drawing, while dragging a marker, and while dragging a vertex. 1001 | this.fireAndForward('editable:drawing:move', e) 1002 | }, 1003 | 1004 | onDrawingMouseMove: function (e) { 1005 | this.onMove(e) 1006 | }, 1007 | 1008 | _getEvents: function () { 1009 | return { 1010 | dragstart: this.onDragStart, 1011 | drag: this.onDrag, 1012 | dragend: this.onDragEnd, 1013 | remove: this.disable, 1014 | } 1015 | }, 1016 | 1017 | onDragStart: function (e) { 1018 | this.onEditing() 1019 | // 🍂namespace Editable 1020 | // 🍂event editable:dragstart: Event 1021 | // Fired before a path feature is dragged. 1022 | this.fireAndForward('editable:dragstart', e) 1023 | }, 1024 | 1025 | onDrag: function (e) { 1026 | this.onMove(e) 1027 | // 🍂namespace Editable 1028 | // 🍂event editable:drag: Event 1029 | // Fired when a path feature is being dragged. 1030 | this.fireAndForward('editable:drag', e) 1031 | }, 1032 | 1033 | onDragEnd: function (e) { 1034 | // 🍂namespace Editable 1035 | // 🍂event editable:dragend: Event 1036 | // Fired after a path feature has been dragged. 1037 | this.fireAndForward('editable:dragend', e) 1038 | this.onEdited() 1039 | }, 1040 | }) 1041 | 1042 | // 🍂namespace Editable; 🍂class MarkerEditor; 🍂aka L.Editable.MarkerEditor 1043 | // 🍂inherits BaseEditor 1044 | // Editor for Marker. 1045 | L.Editable.MarkerEditor = L.Editable.BaseEditor.extend({ 1046 | onDrawingMouseMove: function (e) { 1047 | L.Editable.BaseEditor.prototype.onDrawingMouseMove.call(this, e) 1048 | if (this._drawing) this.feature.setLatLng(e.latlng) 1049 | }, 1050 | 1051 | processDrawingClick: function (e) { 1052 | // 🍂namespace Editable 1053 | // 🍂section Drawing events 1054 | // 🍂event editable:drawing:clicked: Event 1055 | // Fired when user `click` while drawing, after all internal actions. 1056 | this.fireAndForward('editable:drawing:clicked', e) 1057 | this.commitDrawing(e) 1058 | }, 1059 | 1060 | connect: function (e) { 1061 | // On touch, the latlng has not been updated because there is 1062 | // no mousemove. 1063 | if (e) this.feature._latlng = e.latlng 1064 | L.Editable.BaseEditor.prototype.connect.call(this, e) 1065 | }, 1066 | }) 1067 | 1068 | // 🍂namespace Editable; 🍂class CircleMarkerEditor; 🍂aka L.Editable.CircleMarkerEditor 1069 | // 🍂inherits BaseEditor 1070 | // Editor for CircleMarker. 1071 | L.Editable.CircleMarkerEditor = L.Editable.BaseEditor.extend({ 1072 | onDrawingMouseMove: function (e) { 1073 | L.Editable.BaseEditor.prototype.onDrawingMouseMove.call(this, e) 1074 | if (this._drawing) this.feature.setLatLng(e.latlng) 1075 | }, 1076 | 1077 | processDrawingClick: function (e) { 1078 | // 🍂namespace Editable 1079 | // 🍂section Drawing events 1080 | // 🍂event editable:drawing:clicked: Event 1081 | // Fired when user `click` while drawing, after all internal actions. 1082 | this.fireAndForward('editable:drawing:clicked', e) 1083 | this.commitDrawing(e) 1084 | }, 1085 | 1086 | connect: function (e) { 1087 | // On touch, the latlng has not been updated because there is 1088 | // no mousemove. 1089 | if (e) this.feature._latlng = e.latlng 1090 | L.Editable.BaseEditor.prototype.connect.call(this, e) 1091 | }, 1092 | }) 1093 | 1094 | // 🍂namespace Editable; 🍂class PathEditor; 🍂aka L.Editable.PathEditor 1095 | // 🍂inherits BaseEditor 1096 | // Base class for all path editors. 1097 | L.Editable.PathEditor = L.Editable.BaseEditor.extend({ 1098 | CLOSED: false, 1099 | MIN_VERTEX: 2, 1100 | 1101 | addHooks: function () { 1102 | L.Editable.BaseEditor.prototype.addHooks.call(this) 1103 | if (this.feature) { 1104 | this.initVertexMarkers() 1105 | this.map.on('moveend', this.onMoveEnd, this) 1106 | } 1107 | return this 1108 | }, 1109 | 1110 | removeHooks: function () { 1111 | L.Editable.BaseEditor.prototype.removeHooks.call(this) 1112 | if (this.feature) { 1113 | this.map.off('moveend', this.onMoveEnd, this) 1114 | } 1115 | }, 1116 | 1117 | onMoveEnd: function () { 1118 | this.initVertexMarkers() 1119 | }, 1120 | 1121 | initVertexMarkers: function (latlngs) { 1122 | if (!this.enabled()) return 1123 | latlngs = latlngs || this.getLatLngs() 1124 | if (isFlat(latlngs)) { 1125 | this.addVertexMarkers(latlngs) 1126 | } else { 1127 | for (const member of latlngs) { 1128 | this.initVertexMarkers(member) 1129 | } 1130 | } 1131 | }, 1132 | 1133 | getLatLngs: function () { 1134 | return this.feature.getLatLngs() 1135 | }, 1136 | 1137 | // 🍂method reset() 1138 | // Rebuild edit elements (Vertex, MiddleMarker, etc.). 1139 | reset: function () { 1140 | this.editLayer.clearLayers() 1141 | this.initVertexMarkers() 1142 | }, 1143 | 1144 | addVertexMarker: function (latlng, latlngs) { 1145 | if (latlng.__vertex) { 1146 | latlng.__vertex.connect() 1147 | return latlng.__vertex 1148 | } 1149 | return new this.tools.options.vertexMarkerClass(latlng, latlngs, this) 1150 | }, 1151 | 1152 | onNewVertex: function (vertex) { 1153 | // 🍂namespace Editable 1154 | // 🍂section Vertex events 1155 | // 🍂event editable:vertex:new: VertexEvent 1156 | // Fired when a new vertex is created. 1157 | this.fireAndForward('editable:vertex:new', { 1158 | latlng: vertex.latlng, 1159 | vertex: vertex, 1160 | }) 1161 | }, 1162 | 1163 | addVertexMarkers: function (latlngs) { 1164 | const bounds = this.map.getBounds() 1165 | for (const latlng of latlngs) { 1166 | if (!bounds.contains(latlng)) continue 1167 | this.addVertexMarker(latlng, latlngs) 1168 | } 1169 | }, 1170 | 1171 | refreshVertexMarkers: function (latlngs) { 1172 | latlngs = latlngs || this.getDefaultLatLngs() 1173 | for (const latlng of latlngs) { 1174 | latlng.__vertex.update() 1175 | } 1176 | }, 1177 | 1178 | addMiddleMarker: function (left, right, latlngs) { 1179 | return new this.tools.options.middleMarkerClass(left, right, latlngs, this) 1180 | }, 1181 | 1182 | onVertexMarkerClick: function (e) { 1183 | L.Editable.makeCancellable(e) 1184 | // 🍂namespace Editable 1185 | // 🍂section Vertex events 1186 | // 🍂event editable:vertex:click: CancelableVertexEvent 1187 | // Fired when a `click` is issued on a vertex, before any internal action is being processed. 1188 | this.fireAndForward('editable:vertex:click', e) 1189 | if (e._cancelled) return 1190 | if (this.tools.drawing() && this.tools._drawingEditor !== this) return 1191 | const index = e.vertex.getIndex() 1192 | let commit 1193 | if (e.originalEvent.ctrlKey) { 1194 | this.onVertexMarkerCtrlClick(e) 1195 | } else if (e.originalEvent.altKey) { 1196 | this.onVertexMarkerAltClick(e) 1197 | } else if (e.originalEvent.shiftKey) { 1198 | this.onVertexMarkerShiftClick(e) 1199 | } else if (e.originalEvent.metaKey) { 1200 | this.onVertexMarkerMetaKeyClick(e) 1201 | } else if ( 1202 | index === e.vertex.getLastIndex() && 1203 | this._drawing === L.Editable.FORWARD 1204 | ) { 1205 | if (index >= this.MIN_VERTEX - 1) commit = true 1206 | } else if ( 1207 | index === 0 && 1208 | this._drawing === L.Editable.BACKWARD && 1209 | this._drawnLatLngs.length >= this.MIN_VERTEX 1210 | ) { 1211 | commit = true 1212 | } else if ( 1213 | index === 0 && 1214 | this._drawing === L.Editable.FORWARD && 1215 | this._drawnLatLngs.length >= this.MIN_VERTEX && 1216 | this.CLOSED 1217 | ) { 1218 | commit = true // Allow to close on first point also for polygons 1219 | } else { 1220 | this.onVertexRawMarkerClick(e) 1221 | } 1222 | // 🍂namespace Editable 1223 | // 🍂section Vertex events 1224 | // 🍂event editable:vertex:clicked: VertexEvent 1225 | // Fired when a `click` is issued on a vertex, after all internal actions. 1226 | this.fireAndForward('editable:vertex:clicked', e) 1227 | if (commit) this.commitDrawing(e) 1228 | }, 1229 | 1230 | onVertexRawMarkerClick: function (e) { 1231 | // 🍂namespace Editable 1232 | // 🍂section Vertex events 1233 | // 🍂event editable:vertex:rawclick: CancelableVertexEvent 1234 | // Fired when a `click` is issued on a vertex without any special key and without being in drawing mode. 1235 | this.fireAndForward('editable:vertex:rawclick', e) 1236 | if (e._cancelled) return 1237 | if (!this.vertexCanBeDeleted(e.vertex)) return 1238 | e.vertex.delete() 1239 | }, 1240 | 1241 | vertexCanBeDeleted: function (vertex) { 1242 | return vertex.latlngs.length > this.MIN_VERTEX 1243 | }, 1244 | 1245 | onVertexDeleted: function (e) { 1246 | // 🍂namespace Editable 1247 | // 🍂section Vertex events 1248 | // 🍂event editable:vertex:deleted: VertexEvent 1249 | // Fired after a vertex has been deleted by user. 1250 | this.fireAndForward('editable:vertex:deleted', e) 1251 | this.onEdited() 1252 | }, 1253 | 1254 | onVertexMarkerCtrlClick: function (e) { 1255 | // 🍂namespace Editable 1256 | // 🍂section Vertex events 1257 | // 🍂event editable:vertex:ctrlclick: VertexEvent 1258 | // Fired when a `click` with `ctrlKey` is issued on a vertex. 1259 | this.fireAndForward('editable:vertex:ctrlclick', e) 1260 | }, 1261 | 1262 | onVertexMarkerShiftClick: function (e) { 1263 | // 🍂namespace Editable 1264 | // 🍂section Vertex events 1265 | // 🍂event editable:vertex:shiftclick: VertexEvent 1266 | // Fired when a `click` with `shiftKey` is issued on a vertex. 1267 | this.fireAndForward('editable:vertex:shiftclick', e) 1268 | }, 1269 | 1270 | onVertexMarkerMetaKeyClick: function (e) { 1271 | // 🍂namespace Editable 1272 | // 🍂section Vertex events 1273 | // 🍂event editable:vertex:metakeyclick: VertexEvent 1274 | // Fired when a `click` with `metaKey` is issued on a vertex. 1275 | this.fireAndForward('editable:vertex:metakeyclick', e) 1276 | }, 1277 | 1278 | onVertexMarkerAltClick: function (e) { 1279 | // 🍂namespace Editable 1280 | // 🍂section Vertex events 1281 | // 🍂event editable:vertex:altclick: VertexEvent 1282 | // Fired when a `click` with `altKey` is issued on a vertex. 1283 | this.fireAndForward('editable:vertex:altclick', e) 1284 | }, 1285 | 1286 | onVertexMarkerContextMenu: function (e) { 1287 | // 🍂namespace Editable 1288 | // 🍂section Vertex events 1289 | // 🍂event editable:vertex:contextmenu: VertexEvent 1290 | // Fired when a `contextmenu` is issued on a vertex. 1291 | this.fireAndForward('editable:vertex:contextmenu', e) 1292 | }, 1293 | 1294 | onVertexMarkerMouseDown: function (e) { 1295 | // 🍂namespace Editable 1296 | // 🍂section Vertex events 1297 | // 🍂event editable:vertex:mousedown: VertexEvent 1298 | // Fired when user `mousedown` a vertex. 1299 | this.fireAndForward('editable:vertex:mousedown', e) 1300 | }, 1301 | 1302 | onVertexMarkerMouseOver: function (e) { 1303 | // 🍂namespace Editable 1304 | // 🍂section Vertex events 1305 | // 🍂event editable:vertex:mouseover: VertexEvent 1306 | // Fired when a user's mouse enters the vertex 1307 | this.fireAndForward('editable:vertex:mouseover', e) 1308 | }, 1309 | 1310 | onVertexMarkerMouseOut: function (e) { 1311 | // 🍂namespace Editable 1312 | // 🍂section Vertex events 1313 | // 🍂event editable:vertex:mouseout: VertexEvent 1314 | // Fired when a user's mouse leaves the vertex 1315 | this.fireAndForward('editable:vertex:mouseout', e) 1316 | }, 1317 | 1318 | onMiddleMarkerMouseDown: function (e) { 1319 | // 🍂namespace Editable 1320 | // 🍂section MiddleMarker events 1321 | // 🍂event editable:middlemarker:mousedown: VertexEvent 1322 | // Fired when user `mousedown` a middle marker. 1323 | this.fireAndForward('editable:middlemarker:mousedown', e) 1324 | }, 1325 | 1326 | onVertexMarkerDrag: function (e) { 1327 | this.onMove(e) 1328 | if (this.feature._bounds) this.extendBounds(e) 1329 | // 🍂namespace Editable 1330 | // 🍂section Vertex events 1331 | // 🍂event editable:vertex:drag: VertexEvent 1332 | // Fired when a vertex is dragged by user. 1333 | this.fireAndForward('editable:vertex:drag', e) 1334 | }, 1335 | 1336 | onVertexMarkerDragStart: function (e) { 1337 | // 🍂namespace Editable 1338 | // 🍂section Vertex events 1339 | // 🍂event editable:vertex:dragstart: VertexEvent 1340 | // Fired before a vertex is dragged by user. 1341 | this.fireAndForward('editable:vertex:dragstart', e) 1342 | }, 1343 | 1344 | onVertexMarkerDragEnd: function (e) { 1345 | // 🍂namespace Editable 1346 | // 🍂section Vertex events 1347 | // 🍂event editable:vertex:dragend: VertexEvent 1348 | // Fired after a vertex is dragged by user. 1349 | this.fireAndForward('editable:vertex:dragend', e) 1350 | this.onEdited() 1351 | }, 1352 | 1353 | setDrawnLatLngs: function (latlngs) { 1354 | this._drawnLatLngs = latlngs || this.getDefaultLatLngs() 1355 | }, 1356 | 1357 | startDrawing: function () { 1358 | if (!this._drawnLatLngs) this.setDrawnLatLngs() 1359 | L.Editable.BaseEditor.prototype.startDrawing.call(this) 1360 | }, 1361 | 1362 | startDrawingForward: function () { 1363 | this.startDrawing() 1364 | }, 1365 | 1366 | endDrawing: function () { 1367 | this.tools.detachForwardLineGuide() 1368 | this.tools.detachBackwardLineGuide() 1369 | if (this._drawnLatLngs && this._drawnLatLngs.length < this.MIN_VERTEX) 1370 | this.deleteShape(this._drawnLatLngs) 1371 | L.Editable.BaseEditor.prototype.endDrawing.call(this) 1372 | delete this._drawnLatLngs 1373 | }, 1374 | 1375 | addLatLng: function (latlng) { 1376 | if (this._drawing === L.Editable.FORWARD) this._drawnLatLngs.push(latlng) 1377 | else this._drawnLatLngs.unshift(latlng) 1378 | this.feature._bounds.extend(latlng) 1379 | const vertex = this.addVertexMarker(latlng, this._drawnLatLngs) 1380 | this.onNewVertex(vertex) 1381 | this.refresh() 1382 | }, 1383 | 1384 | newPointForward: function (latlng) { 1385 | this.addLatLng(latlng) 1386 | this.tools.attachForwardLineGuide() 1387 | this.tools.anchorForwardLineGuide(latlng) 1388 | }, 1389 | 1390 | newPointBackward: function (latlng) { 1391 | this.addLatLng(latlng) 1392 | this.tools.anchorBackwardLineGuide(latlng) 1393 | }, 1394 | 1395 | // 🍂namespace PathEditor 1396 | // 🍂method push() 1397 | // Programmatically add a point while drawing. 1398 | push: function (latlng) { 1399 | if (!latlng) 1400 | return console.error( 1401 | 'L.Editable.PathEditor.push expect a valid latlng as parameter' 1402 | ) 1403 | if (this._drawing === L.Editable.FORWARD) this.newPointForward(latlng) 1404 | else this.newPointBackward(latlng) 1405 | }, 1406 | 1407 | removeLatLng: function (latlng) { 1408 | latlng.__vertex.delete() 1409 | this.refresh() 1410 | }, 1411 | 1412 | // 🍂method pop(): L.LatLng or null 1413 | // Programmatically remove last point (if any) while drawing. 1414 | pop: function () { 1415 | if (this._drawnLatLngs.length <= 1) return 1416 | let latlng 1417 | if (this._drawing === L.Editable.FORWARD) { 1418 | latlng = this._drawnLatLngs[this._drawnLatLngs.length - 1] 1419 | } else { 1420 | latlng = this._drawnLatLngs[0] 1421 | } 1422 | this.removeLatLng(latlng) 1423 | if (this._drawing === L.Editable.FORWARD) { 1424 | this.tools.anchorForwardLineGuide( 1425 | this._drawnLatLngs[this._drawnLatLngs.length - 1] 1426 | ) 1427 | } else { 1428 | this.tools.anchorForwardLineGuide(this._drawnLatLngs[0]) 1429 | } 1430 | return latlng 1431 | }, 1432 | 1433 | processDrawingClick: function (e) { 1434 | if (e.vertex && e.vertex.editor === this) return 1435 | if (this._drawing === L.Editable.FORWARD) this.newPointForward(e.latlng) 1436 | else this.newPointBackward(e.latlng) 1437 | this.fireAndForward('editable:drawing:clicked', e) 1438 | }, 1439 | 1440 | onDrawingMouseMove: function (e) { 1441 | L.Editable.BaseEditor.prototype.onDrawingMouseMove.call(this, e) 1442 | if (this._drawing) { 1443 | this.tools.moveForwardLineGuide(e.latlng) 1444 | this.tools.moveBackwardLineGuide(e.latlng) 1445 | } 1446 | }, 1447 | 1448 | refresh: function () { 1449 | this.feature.redraw() 1450 | this.onEditing() 1451 | }, 1452 | 1453 | // 🍂namespace PathEditor 1454 | // 🍂method newShape(latlng?: L.LatLng) 1455 | // Add a new shape (Polyline, Polygon) in a multi, and setup up drawing tools to draw it; 1456 | // if optional `latlng` is given, start a path at this point. 1457 | newShape: function (latlng) { 1458 | const shape = this.addNewEmptyShape() 1459 | if (!shape) return 1460 | this.setDrawnLatLngs(shape[0] || shape) // Polygon or polyline 1461 | this.startDrawingForward() 1462 | // 🍂namespace Editable 1463 | // 🍂section Shape events 1464 | // 🍂event editable:shape:new: ShapeEvent 1465 | // Fired when a new shape is created in a multi (Polygon or Polyline). 1466 | this.fireAndForward('editable:shape:new', { shape: shape }) 1467 | if (latlng) this.newPointForward(latlng) 1468 | }, 1469 | 1470 | deleteShape: function (shape, latlngs) { 1471 | const e = { shape: shape } 1472 | L.Editable.makeCancellable(e) 1473 | // 🍂namespace Editable 1474 | // 🍂section Shape events 1475 | // 🍂event editable:shape:delete: CancelableShapeEvent 1476 | // Fired before a new shape is deleted in a multi (Polygon or Polyline). 1477 | this.fireAndForward('editable:shape:delete', e) 1478 | if (e._cancelled) return 1479 | shape = this._deleteShape(shape, latlngs) 1480 | if (this.ensureNotFlat) this.ensureNotFlat() // Polygon. 1481 | this.feature.setLatLngs(this.getLatLngs()) // Force bounds reset. 1482 | this.refresh() 1483 | this.reset() 1484 | // 🍂namespace Editable 1485 | // 🍂section Shape events 1486 | // 🍂event editable:shape:deleted: ShapeEvent 1487 | // Fired after a new shape is deleted in a multi (Polygon or Polyline). 1488 | this.fireAndForward('editable:shape:deleted', { shape: shape }) 1489 | this.onEdited() 1490 | return shape 1491 | }, 1492 | 1493 | _deleteShape: function (shape, latlngs) { 1494 | latlngs = latlngs || this.getLatLngs() 1495 | if (!latlngs.length) return 1496 | const inplaceDelete = (latlngs, shape) => { 1497 | // Called when deleting a flat latlngs 1498 | return latlngs.splice(0, Number.MAX_VALUE) 1499 | } 1500 | const spliceDelete = (latlngs, shape) => { 1501 | // Called when removing a latlngs inside an array 1502 | latlngs.splice(latlngs.indexOf(shape), 1) 1503 | if (!latlngs.length) this._deleteShape(latlngs) 1504 | return shape 1505 | } 1506 | if (latlngs === shape) return inplaceDelete(latlngs, shape) 1507 | for (const member of latlngs) { 1508 | if (member === shape) return spliceDelete(latlngs, shape) 1509 | if (member.indexOf(shape) !== -1) return spliceDelete(member, shape) 1510 | } 1511 | }, 1512 | 1513 | // 🍂namespace PathEditor 1514 | // 🍂method deleteShapeAt(latlng: L.LatLng): Array 1515 | // Remove a path shape at the given `latlng`. 1516 | deleteShapeAt: function (latlng) { 1517 | const shape = this.feature.shapeAt(latlng) 1518 | if (shape) return this.deleteShape(shape) 1519 | }, 1520 | 1521 | // 🍂method appendShape(shape: Array) 1522 | // Append a new shape to the Polygon or Polyline. 1523 | appendShape: function (shape) { 1524 | this.insertShape(shape) 1525 | }, 1526 | 1527 | // 🍂method prependShape(shape: Array) 1528 | // Prepend a new shape to the Polygon or Polyline. 1529 | prependShape: function (shape) { 1530 | this.insertShape(shape, 0) 1531 | }, 1532 | 1533 | // 🍂method insertShape(shape: Array, index: int) 1534 | // Insert a new shape to the Polygon or Polyline at given index (default is to append). 1535 | insertShape: function (shape, index) { 1536 | this.ensureMulti() 1537 | shape = this.formatShape(shape) 1538 | if (index === undefined) index = this.feature._latlngs.length 1539 | this.feature._latlngs.splice(index, 0, shape) 1540 | this.feature.redraw() 1541 | if (this._enabled) this.reset() 1542 | }, 1543 | 1544 | extendBounds: function (e) { 1545 | this.feature._bounds.extend(e.vertex.latlng) 1546 | }, 1547 | 1548 | onDragStart: function (e) { 1549 | this.editLayer.clearLayers() 1550 | L.Editable.BaseEditor.prototype.onDragStart.call(this, e) 1551 | }, 1552 | 1553 | onDragEnd: function (e) { 1554 | this.initVertexMarkers() 1555 | L.Editable.BaseEditor.prototype.onDragEnd.call(this, e) 1556 | }, 1557 | }) 1558 | 1559 | // 🍂namespace Editable; 🍂class PolylineEditor; 🍂aka L.Editable.PolylineEditor 1560 | // 🍂inherits PathEditor 1561 | L.Editable.PolylineEditor = L.Editable.PathEditor.extend({ 1562 | startDrawingBackward: function () { 1563 | this._drawing = L.Editable.BACKWARD 1564 | this.startDrawing() 1565 | }, 1566 | 1567 | // 🍂method continueBackward(latlngs?: Array) 1568 | // Set up drawing tools to continue the line backward. 1569 | continueBackward: function (latlngs) { 1570 | if (this.drawing()) return 1571 | latlngs = latlngs || this.getDefaultLatLngs() 1572 | this.setDrawnLatLngs(latlngs) 1573 | if (latlngs.length > 0) { 1574 | this.tools.attachBackwardLineGuide() 1575 | this.tools.anchorBackwardLineGuide(latlngs[0]) 1576 | } 1577 | this.startDrawingBackward() 1578 | }, 1579 | 1580 | // 🍂method continueForward(latlngs?: Array) 1581 | // Set up drawing tools to continue the line forward. 1582 | continueForward: function (latlngs) { 1583 | if (this.drawing()) return 1584 | latlngs = latlngs || this.getDefaultLatLngs() 1585 | this.setDrawnLatLngs(latlngs) 1586 | if (latlngs.length > 0) { 1587 | this.tools.attachForwardLineGuide() 1588 | this.tools.anchorForwardLineGuide(latlngs[latlngs.length - 1]) 1589 | } 1590 | this.startDrawingForward() 1591 | }, 1592 | 1593 | getDefaultLatLngs: function (latlngs) { 1594 | latlngs = latlngs || this.feature._latlngs 1595 | if (!latlngs.length || latlngs[0] instanceof L.LatLng) return latlngs 1596 | return this.getDefaultLatLngs(latlngs[0]) 1597 | }, 1598 | 1599 | ensureMulti: function () { 1600 | if (this.feature._latlngs.length && isFlat(this.feature._latlngs)) { 1601 | this.feature._latlngs = [this.feature._latlngs] 1602 | } 1603 | }, 1604 | 1605 | addNewEmptyShape: function () { 1606 | if (this.feature._latlngs.length) { 1607 | const shape = [] 1608 | this.appendShape(shape) 1609 | return shape 1610 | } 1611 | return this.feature._latlngs 1612 | }, 1613 | 1614 | formatShape: function (shape) { 1615 | if (isFlat(shape)) return shape 1616 | if (shape[0]) return this.formatShape(shape[0]) 1617 | }, 1618 | 1619 | // 🍂method splitShape(latlngs?: Array, index: int) 1620 | // Split the given `latlngs` shape at index `index` and integrate new shape in instance `latlngs`. 1621 | splitShape: function (shape, index) { 1622 | if (!index || index >= shape.length - 1) return 1623 | this.ensureMulti() 1624 | const shapeIndex = this.feature._latlngs.indexOf(shape) 1625 | if (shapeIndex === -1) return 1626 | const first = shape.slice(0, index + 1) 1627 | const second = shape.slice(index) 1628 | // We deal with reference, we don't want twice the same latlng around. 1629 | second[0] = L.latLng(second[0].lat, second[0].lng, second[0].alt) 1630 | this.feature._latlngs.splice(shapeIndex, 1, first, second) 1631 | this.refresh() 1632 | this.reset() 1633 | this.onEdited() 1634 | }, 1635 | }) 1636 | 1637 | // 🍂namespace Editable; 🍂class PolygonEditor; 🍂aka L.Editable.PolygonEditor 1638 | // 🍂inherits PathEditor 1639 | L.Editable.PolygonEditor = L.Editable.PathEditor.extend({ 1640 | CLOSED: true, 1641 | MIN_VERTEX: 3, 1642 | 1643 | newPointForward: function (latlng) { 1644 | L.Editable.PathEditor.prototype.newPointForward.call(this, latlng) 1645 | if (!this.tools.backwardLineGuide._latlngs.length) 1646 | this.tools.anchorBackwardLineGuide(latlng) 1647 | if (this._drawnLatLngs.length === 2) this.tools.attachBackwardLineGuide() 1648 | }, 1649 | 1650 | addNewEmptyHole: function (latlng) { 1651 | this.ensureNotFlat() 1652 | const latlngs = this.feature.shapeAt(latlng) 1653 | if (!latlngs) return 1654 | const holes = [] 1655 | latlngs.push(holes) 1656 | return holes 1657 | }, 1658 | 1659 | // 🍂method newHole(latlng?: L.LatLng, index: int) 1660 | // Set up drawing tools for creating a new hole on the Polygon. If the `latlng` param is given, a first point is created. 1661 | newHole: function (latlng) { 1662 | const holes = this.addNewEmptyHole(latlng) 1663 | if (!holes) return 1664 | this.setDrawnLatLngs(holes) 1665 | this.startDrawingForward() 1666 | if (latlng) this.newPointForward(latlng) 1667 | }, 1668 | 1669 | addNewEmptyShape: function () { 1670 | if (this.feature._latlngs.length && this.feature._latlngs[0].length) { 1671 | const shape = [] 1672 | this.appendShape(shape) 1673 | return shape 1674 | } 1675 | return this.feature._latlngs 1676 | }, 1677 | 1678 | ensureMulti: function () { 1679 | if (this.feature._latlngs.length && isFlat(this.feature._latlngs[0])) { 1680 | this.feature._latlngs = [this.feature._latlngs] 1681 | } 1682 | }, 1683 | 1684 | ensureNotFlat: function () { 1685 | if (!this.feature._latlngs.length || isFlat(this.feature._latlngs)) 1686 | this.feature._latlngs = [this.feature._latlngs] 1687 | }, 1688 | 1689 | vertexCanBeDeleted: function (vertex) { 1690 | const parent = this.feature.parentShape(vertex.latlngs) 1691 | const idx = L.Util.indexOf(parent, vertex.latlngs) 1692 | if (idx > 0) return true // Holes can be totally deleted without removing the layer itself. 1693 | return L.Editable.PathEditor.prototype.vertexCanBeDeleted.call(this, vertex) 1694 | }, 1695 | 1696 | getDefaultLatLngs: function () { 1697 | if (!this.feature._latlngs.length) this.feature._latlngs.push([]) 1698 | return this.feature._latlngs[0] 1699 | }, 1700 | 1701 | formatShape: (shape) => { 1702 | // [[1, 2], [3, 4]] => must be nested 1703 | // [] => must be nested 1704 | // [[]] => is already nested 1705 | if (isFlat(shape) && (!shape[0] || shape[0].length !== 0)) return [shape] 1706 | return shape 1707 | }, 1708 | }) 1709 | 1710 | // 🍂namespace Editable; 🍂class RectangleEditor; 🍂aka L.Editable.RectangleEditor 1711 | // 🍂inherits PathEditor 1712 | L.Editable.RectangleEditor = L.Editable.PathEditor.extend({ 1713 | CLOSED: true, 1714 | MIN_VERTEX: 4, 1715 | 1716 | options: { 1717 | skipMiddleMarkers: true, 1718 | }, 1719 | 1720 | extendBounds: function (e) { 1721 | const index = e.vertex.getIndex() 1722 | const next = e.vertex.getNext() 1723 | const previous = e.vertex.getPrevious() 1724 | const oppositeIndex = (index + 2) % 4 1725 | const opposite = e.vertex.latlngs[oppositeIndex] 1726 | const bounds = new L.LatLngBounds(e.latlng, opposite) 1727 | // Update latlngs by hand to preserve order. 1728 | previous.latlng.update([e.latlng.lat, opposite.lng]) 1729 | next.latlng.update([opposite.lat, e.latlng.lng]) 1730 | this.updateBounds(bounds) 1731 | this.refreshVertexMarkers() 1732 | }, 1733 | 1734 | onDrawingMouseDown: function (e) { 1735 | L.Editable.PathEditor.prototype.onDrawingMouseDown.call(this, e) 1736 | this.connect() 1737 | const latlngs = this.getDefaultLatLngs() 1738 | // L.Polygon._convertLatLngs removes last latlng if it equals first point, 1739 | // which is the case here as all latlngs are [0, 0] 1740 | if (latlngs.length === 3) latlngs.push(e.latlng) 1741 | const bounds = new L.LatLngBounds(e.latlng, e.latlng) 1742 | this.updateBounds(bounds) 1743 | this.updateLatLngs(bounds) 1744 | this.refresh() 1745 | this.reset() 1746 | // Stop dragging map. 1747 | // L.Draggable has two workflows: 1748 | // - mousedown => mousemove => mouseup 1749 | // - touchstart => touchmove => touchend 1750 | // Problem: L.Map.Tap does not allow us to listen to touchstart, so we only 1751 | // can deal with mousedown, but then when in a touch device, we are dealing with 1752 | // simulated events (actually simulated by L.Map.Tap), which are no more taken 1753 | // into account by L.Draggable. 1754 | // Ref.: https://github.com/Leaflet/Leaflet.Editable/issues/103 1755 | e.originalEvent._simulated = false 1756 | this.map.dragging._draggable._onUp(e.originalEvent) 1757 | // Now transfer ongoing drag action to the bottom right corner. 1758 | // Should we refine which corner will handle the drag according to 1759 | // drag direction? 1760 | latlngs[3].__vertex.dragging._draggable._onDown(e.originalEvent) 1761 | }, 1762 | 1763 | onDrawingMouseUp: function (e) { 1764 | this.commitDrawing(e) 1765 | e.originalEvent._simulated = false 1766 | L.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e) 1767 | }, 1768 | 1769 | onDrawingMouseMove: function (e) { 1770 | e.originalEvent._simulated = false 1771 | L.Editable.PathEditor.prototype.onDrawingMouseMove.call(this, e) 1772 | }, 1773 | 1774 | getDefaultLatLngs: function (latlngs) { 1775 | return latlngs || this.feature._latlngs[0] 1776 | }, 1777 | 1778 | updateBounds: function (bounds) { 1779 | this.feature._bounds = bounds 1780 | }, 1781 | 1782 | updateLatLngs: function (bounds) { 1783 | const latlngs = this.getDefaultLatLngs() 1784 | const newLatlngs = this.feature._boundsToLatLngs(bounds) 1785 | // Keep references. 1786 | for (let i = 0; i < latlngs.length; i++) { 1787 | latlngs[i].update(newLatlngs[i]) 1788 | } 1789 | }, 1790 | }) 1791 | 1792 | // 🍂namespace Editable; 🍂class CircleEditor; 🍂aka L.Editable.CircleEditor 1793 | // 🍂inherits PathEditor 1794 | L.Editable.CircleEditor = L.Editable.PathEditor.extend({ 1795 | MIN_VERTEX: 2, 1796 | 1797 | options: { 1798 | skipMiddleMarkers: true, 1799 | }, 1800 | 1801 | initialize: function (map, feature, options) { 1802 | L.Editable.PathEditor.prototype.initialize.call(this, map, feature, options) 1803 | this._resizeLatLng = this.computeResizeLatLng() 1804 | }, 1805 | 1806 | computeResizeLatLng: function () { 1807 | // While circle is not added to the map, _radius is not set. 1808 | const delta = 1809 | (this.feature._radius || this.feature._mRadius) * Math.cos(Math.PI / 4) 1810 | const point = this.map.project(this.feature._latlng) 1811 | return this.map.unproject([point.x + delta, point.y - delta]) 1812 | }, 1813 | 1814 | updateResizeLatLng: function () { 1815 | this._resizeLatLng.update(this.computeResizeLatLng()) 1816 | this._resizeLatLng.__vertex.update() 1817 | }, 1818 | 1819 | getLatLngs: function () { 1820 | return [this.feature._latlng, this._resizeLatLng] 1821 | }, 1822 | 1823 | getDefaultLatLngs: function () { 1824 | return this.getLatLngs() 1825 | }, 1826 | 1827 | onVertexMarkerDrag: function (e) { 1828 | if (e.vertex.getIndex() === 1) this.resize(e) 1829 | else this.updateResizeLatLng(e) 1830 | L.Editable.PathEditor.prototype.onVertexMarkerDrag.call(this, e) 1831 | }, 1832 | 1833 | resize: function (e) { 1834 | let radius 1835 | if (this.map.options.crs) { 1836 | radius = this.map.options.crs.distance(this.feature._latlng, e.latlng) 1837 | } else { 1838 | radius = this.feature._latlng.distanceTo(e.latlng) 1839 | } 1840 | this.feature.setRadius(radius) 1841 | }, 1842 | 1843 | onDrawingMouseDown: function (e) { 1844 | L.Editable.PathEditor.prototype.onDrawingMouseDown.call(this, e) 1845 | this._resizeLatLng.update(e.latlng) 1846 | this.feature._latlng.update(e.latlng) 1847 | this.connect() 1848 | // Stop dragging map. 1849 | e.originalEvent._simulated = false 1850 | this.map.dragging._draggable._onUp(e.originalEvent) 1851 | // Now transfer ongoing drag action to the radius handler. 1852 | this._resizeLatLng.__vertex.dragging._draggable._onDown(e.originalEvent) 1853 | }, 1854 | 1855 | onDrawingMouseUp: function (e) { 1856 | this.commitDrawing(e) 1857 | e.originalEvent._simulated = false 1858 | L.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e) 1859 | }, 1860 | 1861 | onDrawingMouseMove: function (e) { 1862 | e.originalEvent._simulated = false 1863 | L.Editable.PathEditor.prototype.onDrawingMouseMove.call(this, e) 1864 | }, 1865 | 1866 | onDrag: function (e) { 1867 | L.Editable.PathEditor.prototype.onDrag.call(this, e) 1868 | this.feature.dragging.updateLatLng(this._resizeLatLng) 1869 | }, 1870 | }) 1871 | 1872 | // 🍂namespace Editable; 🍂class EditableMixin 1873 | // `EditableMixin` is included to `L.Polyline`, `L.Polygon`, `L.Rectangle`, `L.Circle` 1874 | // and `L.Marker`. It adds some methods to them. 1875 | // *When editing is enabled, the editor is accessible on the instance with the 1876 | // `editor` property.* 1877 | const EditableMixin = { 1878 | createEditor: function (map) { 1879 | map = map || this._map 1880 | const tools = this.options.editOptions?.editTools || map.editTools 1881 | if (!tools) throw Error('Unable to detect Editable instance.') 1882 | const Klass = this.options.editorClass || this.getEditorClass(tools) 1883 | return new Klass(map, this, this.options.editOptions) 1884 | }, 1885 | 1886 | // 🍂method enableEdit(map?: L.Map): this.editor 1887 | // Enable editing, by creating an editor if not existing, and then calling `enable` on it. 1888 | enableEdit: function (map) { 1889 | if (!this.editor) this.createEditor(map) 1890 | this.editor.enable() 1891 | return this.editor 1892 | }, 1893 | 1894 | // 🍂method editEnabled(): boolean 1895 | // Return true if current instance has an editor attached, and this editor is enabled. 1896 | editEnabled: function () { 1897 | return this.editor?.enabled() 1898 | }, 1899 | 1900 | // 🍂method disableEdit() 1901 | // Disable editing, also remove the editor property reference. 1902 | disableEdit: function () { 1903 | if (this.editor) { 1904 | this.editor.disable() 1905 | delete this.editor 1906 | } 1907 | }, 1908 | 1909 | // 🍂method toggleEdit() 1910 | // Enable or disable editing, according to current status. 1911 | toggleEdit: function () { 1912 | if (this.editEnabled()) this.disableEdit() 1913 | else this.enableEdit() 1914 | }, 1915 | 1916 | _onEditableAdd: function () { 1917 | if (this.editor) this.enableEdit() 1918 | }, 1919 | } 1920 | 1921 | const PolylineMixin = { 1922 | getEditorClass: (tools) => { 1923 | return tools?.options?.polylineEditorClass || L.Editable.PolylineEditor 1924 | }, 1925 | 1926 | shapeAt: function (latlng, latlngs) { 1927 | // We can have those cases: 1928 | // - latlngs are just a flat array of latlngs, use this 1929 | // - latlngs is an array of arrays of latlngs, loop over 1930 | let shape = null 1931 | latlngs = latlngs || this._latlngs 1932 | if (!latlngs.length) return shape 1933 | if (isFlat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs 1934 | else { 1935 | for (const member of latlngs) { 1936 | if (this.isInLatLngs(latlng, member)) return member 1937 | } 1938 | } 1939 | return shape 1940 | }, 1941 | 1942 | isInLatLngs: function (l, latlngs) { 1943 | if (!latlngs) return false 1944 | let i 1945 | let k 1946 | let len 1947 | let part = [] 1948 | let p 1949 | const w = this._clickTolerance() 1950 | this._projectLatlngs(latlngs, part, this._pxBounds) 1951 | part = part[0] 1952 | p = this._map.latLngToLayerPoint(l) 1953 | 1954 | if (!this._pxBounds.contains(p)) { 1955 | return false 1956 | } 1957 | for (i = 1, len = part.length, k = 0; i < len; k = i++) { 1958 | if (L.LineUtil.pointToSegmentDistance(p, part[k], part[i]) <= w) { 1959 | return true 1960 | } 1961 | } 1962 | return false 1963 | }, 1964 | } 1965 | 1966 | const PolygonMixin = { 1967 | getEditorClass: (tools) => { 1968 | return tools?.options?.polygonEditorClass || L.Editable.PolygonEditor 1969 | }, 1970 | 1971 | shapeAt: function (latlng, latlngs) { 1972 | // We can have those cases: 1973 | // - latlngs are just a flat array of latlngs, use this 1974 | // - latlngs is an array of arrays of latlngs, this is a simple polygon (maybe with holes), use the first 1975 | // - latlngs is an array of arrays of arrays, this is a multi, loop over 1976 | let shape = null 1977 | latlngs = latlngs || this._latlngs 1978 | if (!latlngs.length) return shape 1979 | if (isFlat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs 1980 | if (isFlat(latlngs[0]) && this.isInLatLngs(latlng, latlngs[0])) { 1981 | shape = latlngs 1982 | } else { 1983 | for (const member of latlngs) { 1984 | if (this.isInLatLngs(latlng, member[0])) return member 1985 | } 1986 | } 1987 | return shape 1988 | }, 1989 | 1990 | isInLatLngs: (l, latlngs) => { 1991 | let inside = false 1992 | let l1 1993 | let l2 1994 | let j 1995 | let k 1996 | let len2 1997 | 1998 | for (j = 0, len2 = latlngs.length, k = len2 - 1; j < len2; k = j++) { 1999 | l1 = latlngs[j] 2000 | l2 = latlngs[k] 2001 | 2002 | if ( 2003 | l1.lat > l.lat !== l2.lat > l.lat && 2004 | l.lng < ((l2.lng - l1.lng) * (l.lat - l1.lat)) / (l2.lat - l1.lat) + l1.lng 2005 | ) { 2006 | inside = !inside 2007 | } 2008 | } 2009 | 2010 | return inside 2011 | }, 2012 | 2013 | parentShape: function (shape, latlngs) { 2014 | latlngs = latlngs || this._latlngs 2015 | if (!latlngs) return 2016 | let idx = L.Util.indexOf(latlngs, shape) 2017 | if (idx !== -1) return latlngs 2018 | for (const member of latlngs) { 2019 | idx = L.Util.indexOf(member, shape) 2020 | if (idx !== -1) return member 2021 | } 2022 | }, 2023 | } 2024 | 2025 | const MarkerMixin = { 2026 | getEditorClass: (tools) => { 2027 | return tools?.options?.markerEditorClass || L.Editable.MarkerEditor 2028 | }, 2029 | } 2030 | 2031 | const CircleMarkerMixin = { 2032 | getEditorClass: (tools) => { 2033 | return tools?.options?.circleMarkerEditorClass || L.Editable.CircleMarkerEditor 2034 | }, 2035 | } 2036 | 2037 | const RectangleMixin = { 2038 | getEditorClass: (tools) => { 2039 | return tools?.options?.rectangleEditorClass || L.Editable.RectangleEditor 2040 | }, 2041 | } 2042 | 2043 | const CircleMixin = { 2044 | getEditorClass: (tools) => { 2045 | return tools?.options?.circleEditorClass || L.Editable.CircleEditor 2046 | }, 2047 | } 2048 | 2049 | const keepEditable = function () { 2050 | // Make sure you can remove/readd an editable layer. 2051 | this.on('add', this._onEditableAdd) 2052 | } 2053 | 2054 | const isFlat = L.LineUtil.isFlat || L.LineUtil._flat || L.Polyline._flat // <=> 1.1 compat. 2055 | 2056 | if (L.Polyline) { 2057 | L.Polyline.include(EditableMixin) 2058 | L.Polyline.include(PolylineMixin) 2059 | L.Polyline.addInitHook(keepEditable) 2060 | } 2061 | if (L.Polygon) { 2062 | L.Polygon.include(EditableMixin) 2063 | L.Polygon.include(PolygonMixin) 2064 | } 2065 | if (L.Marker) { 2066 | L.Marker.include(EditableMixin) 2067 | L.Marker.include(MarkerMixin) 2068 | L.Marker.addInitHook(keepEditable) 2069 | } 2070 | if (L.CircleMarker) { 2071 | L.CircleMarker.include(EditableMixin) 2072 | L.CircleMarker.include(CircleMarkerMixin) 2073 | L.CircleMarker.addInitHook(keepEditable) 2074 | } 2075 | if (L.Rectangle) { 2076 | L.Rectangle.include(EditableMixin) 2077 | L.Rectangle.include(RectangleMixin) 2078 | } 2079 | if (L.Circle) { 2080 | L.Circle.include(EditableMixin) 2081 | L.Circle.include(CircleMixin) 2082 | } 2083 | 2084 | L.LatLng.prototype.update = function (latlng) { 2085 | latlng = L.latLng(latlng) 2086 | this.lat = latlng.lat 2087 | this.lng = latlng.lng 2088 | } 2089 | }, window) 2090 | --------------------------------------------------------------------------------