├── .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 |
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 |
14 | {% for message in messages %}
15 | - {{ message }}
16 | {% endfor %}
17 |
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 |
12 | {% for p in parties %}
13 | {% if (p.scheduled and p.scheduled >= today) or p.owner_id == user_id %}
14 | -
15 | {{ p.title }}
16 | {% if p.scheduled %}({{ p.scheduled }}){% endif %}
17 |
18 | {% endif %}
19 | {% endfor %}
20 |
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 |
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 |
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 |
--------------------------------------------------------------------------------