diff --git a/backend/.gitignore b/backend/.gitignore
index 84cc0d7..ef81b1e 100644
--- a/backend/.gitignore
+++ b/backend/.gitignore
@@ -1,3 +1 @@
/.venv/
-
-**/__pycache__/
diff --git a/backend/trip/__init__.py b/backend/trip/__init__.py
index e7f9341..b3f408d 100644
--- a/backend/trip/__init__.py
+++ b/backend/trip/__init__.py
@@ -1 +1 @@
-__version__ = "1.27.1"
+__version__ = "1.28.0"
diff --git a/backend/trip/__pycache__/__init__.cpython-312.pyc b/backend/trip/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..ed90f2f
Binary files /dev/null and b/backend/trip/__pycache__/__init__.cpython-312.pyc differ
diff --git a/backend/trip/__pycache__/config.cpython-312.pyc b/backend/trip/__pycache__/config.cpython-312.pyc
new file mode 100644
index 0000000..b455401
Binary files /dev/null and b/backend/trip/__pycache__/config.cpython-312.pyc differ
diff --git a/backend/trip/__pycache__/deps.cpython-312.pyc b/backend/trip/__pycache__/deps.cpython-312.pyc
new file mode 100644
index 0000000..e25a1dd
Binary files /dev/null and b/backend/trip/__pycache__/deps.cpython-312.pyc differ
diff --git a/backend/trip/__pycache__/main.cpython-312.pyc b/backend/trip/__pycache__/main.cpython-312.pyc
new file mode 100644
index 0000000..ec30794
Binary files /dev/null and b/backend/trip/__pycache__/main.cpython-312.pyc differ
diff --git a/backend/trip/__pycache__/security.cpython-312.pyc b/backend/trip/__pycache__/security.cpython-312.pyc
new file mode 100644
index 0000000..0b2e81b
Binary files /dev/null and b/backend/trip/__pycache__/security.cpython-312.pyc differ
diff --git a/backend/trip/alembic/__pycache__/env.cpython-312.pyc b/backend/trip/alembic/__pycache__/env.cpython-312.pyc
new file mode 100644
index 0000000..9be068b
Binary files /dev/null and b/backend/trip/alembic/__pycache__/env.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/032b5b7de8bd_trip_attachment.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/032b5b7de8bd_trip_attachment.cpython-312.pyc
new file mode 100644
index 0000000..41f2335
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/032b5b7de8bd_trip_attachment.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/1181ac441ce5_trip_packing_list.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/1181ac441ce5_trip_packing_list.cpython-312.pyc
new file mode 100644
index 0000000..a59cbaa
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/1181ac441ce5_trip_packing_list.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/11c969913a7e_update_indexes.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/11c969913a7e_update_indexes.cpython-312.pyc
new file mode 100644
index 0000000..70fae95
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/11c969913a7e_update_indexes.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/16bffb744f33_trip_notes.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/16bffb744f33_trip_notes.cpython-312.pyc
new file mode 100644
index 0000000..47a26fe
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/16bffb744f33_trip_notes.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/1cc13f8893ad_category_color.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/1cc13f8893ad_category_color.cpython-312.pyc
new file mode 100644
index 0000000..ec343c3
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/1cc13f8893ad_category_color.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/23320f01d8ce_tripitem_paid_by.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/23320f01d8ce_tripitem_paid_by.cpython-312.pyc
new file mode 100644
index 0000000..146adaa
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/23320f01d8ce_tripitem_paid_by.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/26c89b7466f2_trip_multi_users.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/26c89b7466f2_trip_multi_users.cpython-312.pyc
new file mode 100644
index 0000000..96fbc5c
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/26c89b7466f2_trip_multi_users.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/4f837664b686_trip_archival_review.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/4f837664b686_trip_archival_review.cpython-312.pyc
new file mode 100644
index 0000000..4fb27e2
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/4f837664b686_trip_archival_review.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/4fadf12fa98c_user_totp.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/4fadf12fa98c_user_totp.cpython-312.pyc
new file mode 100644
index 0000000..ff031c4
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/4fadf12fa98c_user_totp.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/54aec61bc15d_tripday_date.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/54aec61bc15d_tripday_date.cpython-312.pyc
new file mode 100644
index 0000000..f5528c1
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/54aec61bc15d_tripday_date.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/60a9bb641d8a_trip_checklist.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/60a9bb641d8a_trip_checklist.cpython-312.pyc
new file mode 100644
index 0000000..f3a2b75
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/60a9bb641d8a_trip_checklist.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/77027ac49c26_trip_share.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/77027ac49c26_trip_share.cpython-312.pyc
new file mode 100644
index 0000000..747ada8
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/77027ac49c26_trip_share.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/7c0ec2b61abb_user_custom_tile_layer.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/7c0ec2b61abb_user_custom_tile_layer.cpython-312.pyc
new file mode 100644
index 0000000..e032b91
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/7c0ec2b61abb_user_custom_tile_layer.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/7e331b851cb7_trip_currency.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/7e331b851cb7_trip_currency.cpython-312.pyc
new file mode 100644
index 0000000..4656afa
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/7e331b851cb7_trip_currency.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/8775a65d510f_tripitem_image.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/8775a65d510f_tripitem_image.cpython-312.pyc
new file mode 100644
index 0000000..d13e833
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/8775a65d510f_tripitem_image.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/8e12410a0b8e_tripitem_gpx.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/8e12410a0b8e_tripitem_gpx.cpython-312.pyc
new file mode 100644
index 0000000..21cccfa
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/8e12410a0b8e_tripitem_gpx.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/b2ed4bf9c1b2_init.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/b2ed4bf9c1b2_init.cpython-312.pyc
new file mode 100644
index 0000000..4acb988
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/b2ed4bf9c1b2_init.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/b6d707f9c35a_backup_task_feature.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/b6d707f9c35a_backup_task_feature.cpython-312.pyc
new file mode 100644
index 0000000..6413d0f
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/b6d707f9c35a_backup_task_feature.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/c6bf10b10d0a_tripitem_attachments.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/c6bf10b10d0a_tripitem_attachments.cpython-312.pyc
new file mode 100644
index 0000000..91cf83b
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/c6bf10b10d0a_tripitem_attachments.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/d5fee6ec85c2_user_settings_snake_case.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/d5fee6ec85c2_user_settings_snake_case.cpython-312.pyc
new file mode 100644
index 0000000..0feb2f0
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/d5fee6ec85c2_user_settings_snake_case.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/dd7a55d2ae42_user_settings_modes_low_network_dark_.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/dd7a55d2ae42_user_settings_modes_low_network_dark_.cpython-312.pyc
new file mode 100644
index 0000000..15f81bf
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/dd7a55d2ae42_user_settings_modes_low_network_dark_.cpython-312.pyc differ
diff --git a/backend/trip/alembic/versions/__pycache__/e75fca7d8759_user_google_api_key.cpython-312.pyc b/backend/trip/alembic/versions/__pycache__/e75fca7d8759_user_google_api_key.cpython-312.pyc
new file mode 100644
index 0000000..8d3ed70
Binary files /dev/null and b/backend/trip/alembic/versions/__pycache__/e75fca7d8759_user_google_api_key.cpython-312.pyc differ
diff --git a/backend/trip/db/__pycache__/__init__.cpython-312.pyc b/backend/trip/db/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..4fff26c
Binary files /dev/null and b/backend/trip/db/__pycache__/__init__.cpython-312.pyc differ
diff --git a/backend/trip/db/__pycache__/core.cpython-312.pyc b/backend/trip/db/__pycache__/core.cpython-312.pyc
new file mode 100644
index 0000000..e8a8978
Binary files /dev/null and b/backend/trip/db/__pycache__/core.cpython-312.pyc differ
diff --git a/backend/trip/models/__pycache__/__init__.cpython-312.pyc b/backend/trip/models/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..a79359d
Binary files /dev/null and b/backend/trip/models/__pycache__/__init__.cpython-312.pyc differ
diff --git a/backend/trip/models/__pycache__/models.cpython-312.pyc b/backend/trip/models/__pycache__/models.cpython-312.pyc
new file mode 100644
index 0000000..7dd276d
Binary files /dev/null and b/backend/trip/models/__pycache__/models.cpython-312.pyc differ
diff --git a/backend/trip/routers/__pycache__/__init__.cpython-312.pyc b/backend/trip/routers/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..ec84c98
Binary files /dev/null and b/backend/trip/routers/__pycache__/__init__.cpython-312.pyc differ
diff --git a/backend/trip/routers/__pycache__/auth.cpython-312.pyc b/backend/trip/routers/__pycache__/auth.cpython-312.pyc
new file mode 100644
index 0000000..f172c7e
Binary files /dev/null and b/backend/trip/routers/__pycache__/auth.cpython-312.pyc differ
diff --git a/backend/trip/routers/__pycache__/categories.cpython-312.pyc b/backend/trip/routers/__pycache__/categories.cpython-312.pyc
new file mode 100644
index 0000000..6969289
Binary files /dev/null and b/backend/trip/routers/__pycache__/categories.cpython-312.pyc differ
diff --git a/backend/trip/routers/__pycache__/places.cpython-312.pyc b/backend/trip/routers/__pycache__/places.cpython-312.pyc
new file mode 100644
index 0000000..44d70c5
Binary files /dev/null and b/backend/trip/routers/__pycache__/places.cpython-312.pyc differ
diff --git a/backend/trip/routers/__pycache__/settings.cpython-312.pyc b/backend/trip/routers/__pycache__/settings.cpython-312.pyc
new file mode 100644
index 0000000..931ff8f
Binary files /dev/null and b/backend/trip/routers/__pycache__/settings.cpython-312.pyc differ
diff --git a/backend/trip/routers/__pycache__/trips.cpython-312.pyc b/backend/trip/routers/__pycache__/trips.cpython-312.pyc
new file mode 100644
index 0000000..97a77d5
Binary files /dev/null and b/backend/trip/routers/__pycache__/trips.cpython-312.pyc differ
diff --git a/backend/trip/utils/__pycache__/__init__.cpython-312.pyc b/backend/trip/utils/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..e70e7bd
Binary files /dev/null and b/backend/trip/utils/__pycache__/__init__.cpython-312.pyc differ
diff --git a/backend/trip/utils/__pycache__/date.cpython-312.pyc b/backend/trip/utils/__pycache__/date.cpython-312.pyc
new file mode 100644
index 0000000..f6dd1e7
Binary files /dev/null and b/backend/trip/utils/__pycache__/date.cpython-312.pyc differ
diff --git a/backend/trip/utils/__pycache__/gmaps.cpython-312.pyc b/backend/trip/utils/__pycache__/gmaps.cpython-312.pyc
new file mode 100644
index 0000000..89906ba
Binary files /dev/null and b/backend/trip/utils/__pycache__/gmaps.cpython-312.pyc differ
diff --git a/backend/trip/utils/__pycache__/utils.cpython-312.pyc b/backend/trip/utils/__pycache__/utils.cpython-312.pyc
new file mode 100644
index 0000000..4d9ae70
Binary files /dev/null and b/backend/trip/utils/__pycache__/utils.cpython-312.pyc differ
diff --git a/backend/trip/utils/__pycache__/zip.cpython-312.pyc b/backend/trip/utils/__pycache__/zip.cpython-312.pyc
new file mode 100644
index 0000000..1af1f04
Binary files /dev/null and b/backend/trip/utils/__pycache__/zip.cpython-312.pyc differ
diff --git a/backend/trip/utils/zip.py b/backend/trip/utils/zip.py
index 68f3bf3..eeeefc5 100644
--- a/backend/trip/utils/zip.py
+++ b/backend/trip/utils/zip.py
@@ -19,7 +19,7 @@ from ..models.models import (Backup, BackupStatus, Category, CategoryRead,
TripRead, User, UserRead)
from .date import dt_utc, iso_to_dt
from .utils import (assets_folder_path, attachments_trip_folder_path,
- b64img_decode, save_image_to_file)
+ b64img_decode, remove_image, save_image_to_file)
def process_backup_export(session: SessionDep, backup_id: int):
@@ -147,79 +147,47 @@ async def process_backup_import(
except Exception:
raise HTTPException(status_code=400, detail="Invalid file")
- try:
- with ZipFile(io.BytesIO(zip_content), "r") as zipf:
- zip_filenames = zipf.namelist()
- if "data.json" not in zip_filenames:
- raise HTTPException(status_code=400, detail="Invalid file")
+ with ZipFile(io.BytesIO(zip_content), "r") as zipf:
+ zip_filenames = zipf.namelist()
+ if "data.json" not in zip_filenames:
+ raise HTTPException(status_code=400, detail="Invalid file")
- try:
- data = json.loads(zipf.read("data.json"))
- except Exception:
- raise HTTPException(status_code=400, detail="Invalid file")
+ try:
+ data = json.loads(zipf.read("data.json"))
+ except Exception:
+ raise HTTPException(status_code=400, detail="Invalid file")
- image_files = {
- path.split("/")[-1]: path
- for path in zip_filenames
- if path.startswith("images/") and not path.endswith("/")
+ image_files = {
+ path.split("/")[-1]: path
+ for path in zip_filenames
+ if path.startswith("images/") and not path.endswith("/")
+ }
+
+ attachment_files = {
+ path.split("/")[-1]: path
+ for path in zip_filenames
+ if path.startswith("attachments/") and not path.endswith("/")
+ }
+
+ error_details = "Bad request"
+ created_image_filenames = []
+ created_attachment_trips = []
+ try:
+ existing_categories = {
+ category.name: category
+ for category in session.exec(select(Category).where(Category.user == current_user)).all()
}
- attachment_files = {
- path.split("/")[-1]: path
- for path in zip_filenames
- if path.startswith("attachments/") and not path.endswith("/")
- }
+ categories_to_add = []
+ for category in data.get("categories", []):
+ category_name = category.get("name")
+ category_exists = existing_categories.get(category_name)
- try:
- existing_categories = {
- category.name: category
- for category in session.exec(select(Category).where(Category.user == current_user)).all()
- }
+ if category_exists:
+ if category.get("color"):
+ category_exists.color = category["color"]
- categories_to_add = []
- for category in data.get("categories", []):
- category_name = category.get("name")
- category_exists = existing_categories.get(category_name)
-
- if category_exists:
- if category.get("color"):
- category_exists.color = category["color"]
-
- if category.get("image_id") and category.get("image_id") != category_exists.image_id:
- category_filename = category.get("image").split("/")[-1]
- if category_filename and category_filename in image_files:
- try:
- image_bytes = zipf.read(image_files[category_filename])
- filename = save_image_to_file(image_bytes, settings.PLACE_IMAGE_SIZE)
- if filename:
- image = Image(filename=filename, user=current_user)
- session.add(image)
- session.flush()
- session.refresh(image)
-
- if category_exists.image_id:
- old_image = session.get(Image, category_exists.image_id)
- if old_image:
- session.delete(old_image)
- category_exists.image_id = None
- session.flush()
-
- category_exists.image_id = image.id
- except Exception:
- pass
-
- session.add(category_exists)
- existing_categories[category_name] = category_exists
- continue
-
- new_category = {
- key: category[key]
- for key in category.keys()
- if key not in {"id", "image", "image_id"}
- }
- new_category["user"] = current_user
-
- if category.get("image_id"):
+ if category.get("image_id") and category.get("image_id") != category_exists.image_id:
category_filename = category.get("image").split("/")[-1]
if category_filename and category_filename in image_files:
try:
@@ -230,266 +198,317 @@ async def process_backup_import(
session.add(image)
session.flush()
session.refresh(image)
- new_category["image_id"] = image.id
+ created_image_filenames.append(filename)
+
+ if category_exists.image_id:
+ old_image = session.get(Image, category_exists.image_id)
+ if old_image:
+ session.delete(old_image)
+ category_exists.image_id = None
+ session.flush()
+
+ category_exists.image_id = image.id
except Exception:
pass
- new_category = Category(**new_category)
- categories_to_add.append(new_category)
- session.add(new_category)
+ session.add(category_exists)
+ existing_categories[category_name] = category_exists
+ continue
- if categories_to_add:
- session.flush()
- for category in categories_to_add:
- existing_categories[category.name] = category
+ new_category = {
+ key: category[key] for key in category.keys() if key not in {"id", "image", "image_id"}
+ }
+ new_category["user"] = current_user
- places = []
- places_to_add = []
- for place in data.get("places", []):
- category_name = place.get("category", {}).get("name")
- category = existing_categories.get(category_name)
- if not category:
+ if category.get("image_id"):
+ category_filename = category.get("image").split("/")[-1]
+ if category_filename and category_filename in image_files:
+ try:
+ image_bytes = zipf.read(image_files[category_filename])
+ filename = save_image_to_file(image_bytes, settings.PLACE_IMAGE_SIZE)
+ if filename:
+ image = Image(filename=filename, user=current_user)
+ session.add(image)
+ session.flush()
+ session.refresh(image)
+ created_image_filenames.append(filename)
+ new_category["image_id"] = image.id
+ except Exception:
+ pass
+
+ new_category = Category(**new_category)
+ categories_to_add.append(new_category)
+ session.add(new_category)
+
+ if categories_to_add:
+ session.flush()
+ for category in categories_to_add:
+ existing_categories[category.name] = category
+
+ places = []
+ places_to_add = []
+ for place in data.get("places", []):
+ category_name = place.get("category", {}).get("name")
+ category = existing_categories.get(category_name)
+ if not category:
+ continue
+
+ new_place = {
+ key: place[key]
+ for key in place.keys()
+ if key not in {"id", "image", "image_id", "category", "category_id"}
+ }
+ new_place["user"] = current_user
+ new_place["category_id"] = category.id
+
+ if place.get("image_id"):
+ place_filename = place.get("image").split("/")[-1]
+ if place_filename and place_filename in image_files:
+ try:
+ image_bytes = zipf.read(image_files[place_filename])
+ filename = save_image_to_file(image_bytes, settings.PLACE_IMAGE_SIZE)
+ if filename:
+ image = Image(filename=filename, user=current_user)
+ session.add(image)
+ session.flush()
+ session.refresh(image)
+ created_image_filenames.append(filename)
+ new_place["image_id"] = image.id
+ except Exception:
+ pass
+
+ new_place = Place(**new_place)
+ places_to_add.append(new_place)
+ places.append(new_place)
+
+ if places_to_add:
+ session.add_all(places_to_add)
+ session.flush()
+
+ db_user = session.get(User, current_user)
+ if data.get("settings") and db_user:
+ settings_data = data["settings"]
+ setting_fields = [
+ "map_lat",
+ "map_lng",
+ "currency",
+ "tile_layer",
+ "mode_low_network",
+ "mode_dark",
+ "mode_gpx_in_place",
+ ]
+
+ for field in setting_fields:
+ if field in settings_data:
+ setattr(db_user, field, settings_data[field])
+
+ if "do_not_display" in settings_data:
+ db_user.do_not_display = ",".join(settings_data["do_not_display"])
+
+ session.add(db_user)
+ session.flush()
+
+ trip_place_id_map = {p["id"]: new_p.id for p, new_p in zip(data.get("places", []), places)}
+ items_to_add = []
+ packing_to_add = []
+ checklist_to_add = []
+ attachment_links_to_add = []
+ for trip in data.get("trips", []):
+ new_trip = {
+ key: trip[key]
+ for key in trip.keys()
+ if key
+ not in {
+ "id",
+ "image",
+ "image_id",
+ "places",
+ "days",
+ "shared",
+ "collaborators",
+ "attachments",
+ "packing_items",
+ "checklist_items",
+ }
+ }
+ new_trip["user"] = current_user
+
+ if trip.get("image_id"):
+ trip_filename = trip.get("image").split("/")[-1]
+ if trip_filename and trip_filename in image_files:
+ try:
+ image_bytes = zipf.read(image_files[trip_filename])
+ filename = save_image_to_file(image_bytes, settings.TRIP_IMAGE_SIZE)
+ if filename:
+ image = Image(filename=filename, user=current_user)
+ session.add(image)
+ session.flush()
+ session.refresh(image)
+ created_image_filenames.append(filename)
+ new_trip["image_id"] = image.id
+ except Exception:
+ pass
+
+ new_trip = Trip(**new_trip)
+ session.add(new_trip)
+ session.flush()
+ session.refresh(new_trip)
+
+ for place in trip.get("places", []):
+ old_id = place.get("id")
+ new_place_id = trip_place_id_map.get(old_id)
+ if new_place_id:
+ db_place = session.get(Place, new_place_id)
+ if db_place:
+ new_trip.places.append(db_place)
+
+ trip_attachment_mapping = {}
+ for attachment in trip.get("attachments", []):
+ stored_filename = attachment.get("stored_filename")
+ old_attachment_id = attachment.get("id")
+
+ if not stored_filename or not old_attachment_id:
continue
- new_place = {
- key: place[key]
- for key in place.keys()
- if key not in {"id", "image", "image_id", "category", "category_id"}
- }
- new_place["user"] = current_user
- new_place["category_id"] = category.id
+ if stored_filename in attachment_files:
+ try:
+ attachment_bytes = zipf.read(attachment_files[stored_filename])
+ new_attachment = {
+ key: attachment[key]
+ for key in attachment
+ if key not in {"id", "trip_id", "trip", "uploaded_by"}
+ }
+ new_attachment["trip_id"] = new_trip.id
+ new_attachment["uploaded_by"] = current_user
+ new_attachment_obj = TripAttachment(**new_attachment)
- if place.get("image_id"):
- place_filename = place.get("image").split("/")[-1]
- if place_filename and place_filename in image_files:
- try:
- image_bytes = zipf.read(image_files[place_filename])
- filename = save_image_to_file(image_bytes, settings.PLACE_IMAGE_SIZE)
- if filename:
- image = Image(filename=filename, user=current_user)
- session.add(image)
- session.flush()
- session.refresh(image)
- new_place["image_id"] = image.id
- except Exception:
- pass
+ attachment_path = attachments_trip_folder_path(new_trip.id) / stored_filename
+ created_attachment_trips.append(new_trip.id)
+ attachment_path.write_bytes(attachment_bytes)
+ session.add(new_attachment_obj)
+ session.flush()
+ session.refresh(new_attachment_obj)
+ trip_attachment_mapping[old_attachment_id] = new_attachment_obj.id
- new_place = Place(**new_place)
- places_to_add.append(new_place)
- places.append(new_place)
-
- if places_to_add:
- session.add_all(places_to_add)
- session.flush()
-
- db_user = session.get(User, current_user)
- if data.get("settings") and db_user:
- settings_data = data["settings"]
- setting_fields = [
- "map_lat",
- "map_lng",
- "currency",
- "tile_layer",
- "mode_low_network",
- "mode_dark",
- "mode_gpx_in_place",
- ]
-
- for field in setting_fields:
- if field in settings_data:
- setattr(db_user, field, settings_data[field])
-
- if "do_not_display" in settings_data:
- db_user.do_not_display = ",".join(settings_data["do_not_display"])
-
- session.add(db_user)
- session.flush()
-
- trip_place_id_map = {p["id"]: new_p.id for p, new_p in zip(data.get("places", []), places)}
- items_to_add = []
- packing_to_add = []
- checklist_to_add = []
- attachment_links_to_add = []
- for trip in data.get("trips", []):
- new_trip = {
- key: trip[key]
- for key in trip.keys()
- if key
- not in {
- "id",
- "image",
- "image_id",
- "places",
- "days",
- "shared",
- "collaborators",
- "attachments",
- "packing_items",
- "checklist_items",
- }
- }
- new_trip["user"] = current_user
-
- if trip.get("image_id"):
- trip_filename = trip.get("image").split("/")[-1]
- if trip_filename and trip_filename in image_files:
- try:
- image_bytes = zipf.read(image_files[trip_filename])
- filename = save_image_to_file(image_bytes, settings.TRIP_IMAGE_SIZE)
- if filename:
- image = Image(filename=filename, user=current_user)
- session.add(image)
- session.flush()
- session.refresh(image)
- new_trip["image_id"] = image.id
- except Exception:
- pass
-
- new_trip = Trip(**new_trip)
- session.add(new_trip)
- session.flush()
- session.refresh(new_trip)
-
- for place in trip.get("places", []):
- old_id = place.get("id")
- new_place_id = trip_place_id_map.get(old_id)
- if new_place_id:
- db_place = session.get(Place, new_place_id)
- if db_place:
- new_trip.places.append(db_place)
-
- trip_attachment_mapping = {}
- for attachment in trip.get("attachments", []):
- stored_filename = attachment.get("stored_filename")
- old_attachment_id = attachment.get("id")
-
- if not stored_filename or not old_attachment_id:
+ except Exception:
continue
- if stored_filename in attachment_files:
- try:
- attachment_bytes = zipf.read(attachment_files[stored_filename])
- new_attachment = {
- key: attachment[key]
- for key in attachment
- if key not in {"id", "trip_id", "trip"}
- }
- new_attachment["trip_id"] = new_trip.id
- new_attachment["user"] = current_user
- new_attachment_obj = TripAttachment(**new_attachment)
+ for day in trip.get("days", []):
+ day_data = {key: day[key] for key in day if key not in {"id", "items"}}
+ if "dt" in day_data and isinstance(day_data["dt"], str):
+ day_data["dt"] = iso_to_dt(day_data["dt"])
+ new_day = TripDay(**day_data, trip_id=new_trip.id)
+ session.add(new_day)
+ session.flush()
- attachment_path = attachments_trip_folder_path(new_trip.id) / stored_filename
- attachment_path.write_bytes(attachment_bytes)
- session.add(new_attachment_obj)
- session.flush()
- session.refresh(new_attachment_obj)
- trip_attachment_mapping[old_attachment_id] = new_attachment_obj.id
+ for item in day.get("items", []):
+ if item.get("paid_by"):
+ u = item.get("paid_by")
+ db_user = session.get(User, u)
+ if not db_user:
+ error_details = f"User <{u}> does not exist and is specified in Paid By"
+ raise
- except Exception:
- continue
+ item_data = {
+ key: item[key]
+ for key in item
+ if key not in {"id", "place", "place_id", "image", "image_id", "attachments"}
+ }
+ item_data["day_id"] = new_day.id
- for day in trip.get("days", []):
- day_data = {key: day[key] for key in day if key not in {"id", "items"}}
- if "dt" in day_data and isinstance(day_data["dt"], str):
- day_data["dt"] = iso_to_dt(day_data["dt"])
- new_day = TripDay(**day_data, trip_id=new_trip.id)
- session.add(new_day)
+ place = item.get("place")
+ if place and (place_id := place.get("id")):
+ new_place_id = trip_place_id_map.get(place_id)
+ item_data["place_id"] = new_place_id
+
+ if item_data.get("image_id"):
+ place_filename = place.get("image").split("/")[-1]
+ if place_filename and place_filename in image_files:
+ try:
+ image_bytes = zipf.read(image_files[place_filename])
+ filename = save_image_to_file(image_bytes, settings.PLACE_IMAGE_SIZE)
+ if filename:
+ image = Image(filename=filename, user=current_user)
+ session.add(image)
+ session.flush()
+ session.refresh(image)
+ created_image_filenames.append(filename)
+ item_data["image_id"] = image.id
+ except Exception:
+ pass
+
+ trip_item = TripItem(**item_data)
+ session.add(trip_item)
session.flush()
+ session.refresh(trip_item)
+ items_to_add.append(trip_item)
- for item in day.get("items", []):
- item_data = {
- key: item[key]
- for key in item
- if key not in {"id", "place", "place_id", "image", "image_id", "attachments"}
- }
- item_data["day_id"] = new_day.id
- item_data["user"] = current_user
+ for attachment in item.get("attachments", []):
+ attachment_id = attachment.get("id")
+ if attachment_id and attachment_id in trip_attachment_mapping:
+ new_attachment_id = trip_attachment_mapping[attachment_id]
+ link = TripItemAttachmentLink(
+ item_id=trip_item.id, attachment_id=new_attachment_id
+ )
+ attachment_links_to_add.append(link)
- place = item.get("place")
- if place and (place_id := place.get("id")):
- new_place_id = trip_place_id_map.get(place_id)
- item_data["place_id"] = new_place_id
+ for item in trip.get("packing_items", []):
+ new_packing = {
+ key: item[key] for key in item.keys() if key not in {"id", "trip_id", "trip"}
+ }
+ new_packing["trip_id"] = new_trip.id
+ packing_to_add.append(TripPackingListItem(**new_packing))
- if item_data.get("image_id"):
- place_filename = place.get("image").split("/")[-1]
- if place_filename and place_filename in image_files:
- try:
- image_bytes = zipf.read(image_files[place_filename])
- filename = save_image_to_file(image_bytes, settings.PLACE_IMAGE_SIZE)
- if filename:
- image = Image(filename=filename, user=current_user)
- session.add(image)
- session.flush()
- session.refresh(image)
- item_data["image_id"] = image.id
- except Exception:
- pass
+ for item in trip.get("checklist_items", []):
+ new_checklist = {
+ key: item[key] for key in item.keys() if key not in {"id", "trip_id", "trip"}
+ }
+ new_checklist["trip_id"] = new_trip.id
+ checklist_to_add.append(TripChecklistItem(**new_checklist))
- trip_item = TripItem(**item_data)
- session.add(trip_item)
- session.flush()
- session.refresh(trip_item)
- items_to_add.append(trip_item)
+ if attachment_links_to_add:
+ session.add_all(attachment_links_to_add)
- for attachment in item.get("attachments", []):
- attachment_id = attachment.get("id")
- if attachment_id and attachment_id in trip_attachment_mapping:
- new_attachment_id = trip_attachment_mapping[attachment_id]
- link = TripItemAttachmentLink(
- item_id=trip_item.id, attachment_id=new_attachment_id
- )
- attachment_links_to_add.append(link)
+ if items_to_add:
+ session.add_all(items_to_add)
- for item in trip.get("packing_items", []):
- new_packing = {
- key: item[key] for key in item.keys() if key not in {"id", "trip_id", "trip"}
- }
- new_packing["trip_id"] = new_trip.id
- packing_to_add.append(TripPackingListItem(**new_packing))
+ if packing_to_add:
+ session.add_all(packing_to_add)
- for item in trip.get("checklist_items", []):
- new_checklist = {
- key: item[key] for key in item.keys() if key not in {"id", "trip_id", "trip"}
- }
- new_checklist["trip_id"] = new_trip.id
- checklist_to_add.append(TripChecklistItem(**new_checklist))
+ if checklist_to_add:
+ session.add_all(checklist_to_add)
- if attachment_links_to_add:
- session.add_all(attachment_links_to_add)
+ # BOOM!
+ session.commit()
- if items_to_add:
- session.add_all(items_to_add)
+ return {
+ "places": [PlaceRead.serialize(p) for p in places],
+ "categories": [
+ CategoryRead.serialize(c)
+ for c in session.exec(
+ select(Category)
+ .options(selectinload(Category.image))
+ .where(Category.user == current_user)
+ ).all()
+ ],
+ "settings": UserRead.serialize(session.get(User, current_user)),
+ }
- if packing_to_add:
- session.add_all(packing_to_add)
-
- if checklist_to_add:
- session.add_all(checklist_to_add)
-
- # BOOM!
- session.commit()
-
- return {
- "places": [PlaceRead.serialize(p) for p in places],
- "categories": [
- CategoryRead.serialize(c)
- for c in session.exec(
- select(Category)
- .options(selectinload(Category.image))
- .where(Category.user == current_user)
- ).all()
- ],
- "settings": UserRead.serialize(session.get(User, current_user)),
- }
-
- except Exception as exc:
- session.rollback()
- print(exc)
- raise HTTPException(status_code=400, detail="Bad request")
-
- except Exception as exc:
- print(exc)
- raise HTTPException(status_code=400, detail="Bad request")
+ except Exception:
+ session.rollback()
+ for filename in created_image_filenames:
+ remove_image(filename)
+ for trip_id in created_attachment_trips:
+ try:
+ folder = attachments_trip_folder_path(trip_id)
+ if not folder.exists():
+ return
+ for file in folder.iterdir():
+ file.unlink()
+ folder.rmdir()
+ except Exception:
+ pass
+ raise HTTPException(status_code=400, detail=error_details)
async def process_legacy_import(
diff --git a/docs/404.html b/docs/404.html
index 03de039..3a85504 100644
--- a/docs/404.html
+++ b/docs/404.html
@@ -4,7 +4,7 @@
Page Not Found | TRIP
-
+
diff --git a/docs/assets/js/6e0ab1b4.319f6a48.js b/docs/assets/js/6e0ab1b4.319f6a48.js
new file mode 100644
index 0000000..f18a35f
--- /dev/null
+++ b/docs/assets/js/6e0ab1b4.319f6a48.js
@@ -0,0 +1 @@
+"use strict";(globalThis.webpackChunktripdocs=globalThis.webpackChunktripdocs||[]).push([[972],{2541:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>c,default:()=>d,frontMatter:()=>a,metadata:()=>r,toc:()=>o});const r=JSON.parse('{"id":"trips-planner/trip-places","title":"Trip - Places","description":"A trip can reference places","source":"@site/docs/trips-planner/trip-places.md","sourceDirName":"trips-planner","slug":"/trips-planner/trip-places","permalink":"/trip/docs/trips-planner/trip-places","draft":false,"unlisted":false,"tags":[],"version":"current","sidebarPosition":5,"frontMatter":{"sidebar_position":5,"description":"A trip can reference places"},"sidebar":"docSidebar","previous":{"title":"Trip - Concepts","permalink":"/trip/docs/trips-planner/trip-concepts"},"next":{"title":"Trip - Map","permalink":"/trip/docs/trips-planner/trip-map"}}');var i=n(4848),s=n(8453);const a={sidebar_position:5,description:"A trip can reference places"},c="Trip - Places",p={},o=[];function l(e){const t={admonition:"admonition",em:"em",h1:"h1",header:"header",p:"p",...(0,s.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(t.header,{children:(0,i.jsx)(t.h1,{id:"trip---places",children:"Trip - Places"})}),"\n",(0,i.jsx)(t.admonition,{title:"TL;DR",type:"note",children:(0,i.jsx)(t.p,{children:"A trip can reference places. Manage the associated places and create new ones from the trip interface"})}),"\n",(0,i.jsx)(t.p,{children:"A trip can reference\xa0your places\xa0or those of other members, to associate them to the plans."}),"\n",(0,i.jsx)(t.admonition,{title:"important",type:"info",children:(0,i.jsx)(t.p,{children:"Creating a place from a trip is a shortcut, the place itself is not owned by the trip."})}),"\n",(0,i.jsx)(t.admonition,{type:"tip",children:(0,i.jsx)(t.p,{children:"The \u2705 icon next to the place's category indicates that the place is used in the plans."})}),"\n",(0,i.jsxs)(t.p,{children:["The list of references places is in the dedicated ",(0,i.jsx)(t.em,{children:"Places"})," panel."]}),"\n",(0,i.jsx)("img",{src:"/trip/img/trip_places.png",alt:"Trip - Places"}),"\n",(0,i.jsx)("div",{style:{textAlign:"center"},children:(0,i.jsx)("sup",{children:"Trip - Places"})}),"\n",(0,i.jsx)(t.p,{children:"To reference places to your trip, you can either manage the associated places or directly create new ones from the trip interface."}),"\n",(0,i.jsx)("img",{src:"/trip/img/trip_places_manage.png",alt:"Trip - Manage or Create Places"}),"\n",(0,i.jsx)("div",{style:{textAlign:"center"},children:(0,i.jsx)("sup",{children:"Trip - Manage or Create Places"})}),"\n",(0,i.jsx)(t.p,{children:"Hovering over a place highlights it on the map."}),"\n",(0,i.jsx)("img",{src:"/trip/img/trip_place_highlight.png",alt:"Trip - Highlight place on hover"}),"\n",(0,i.jsx)("div",{style:{textAlign:"center"},children:(0,i.jsx)("sup",{children:"Trip - Highlight place on hover"})})]})}function d(e={}){const{wrapper:t}={...(0,s.R)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(l,{...e})}):l(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>a,x:()=>c});var r=n(6540);const i={},s=r.createContext(i);function a(e){const t=r.useContext(s);return r.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function c(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),r.createElement(s.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/docs/assets/js/6e0ab1b4.af89496f.js b/docs/assets/js/6e0ab1b4.af89496f.js
deleted file mode 100644
index 14d24e4..0000000
--- a/docs/assets/js/6e0ab1b4.af89496f.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(globalThis.webpackChunktripdocs=globalThis.webpackChunktripdocs||[]).push([[972],{2541:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>a,default:()=>d,frontMatter:()=>p,metadata:()=>r,toc:()=>o});const r=JSON.parse('{"id":"trips-planner/trip-places","title":"Trip - Places","description":"A trip can reference places","source":"@site/docs/trips-planner/trip-places.md","sourceDirName":"trips-planner","slug":"/trips-planner/trip-places","permalink":"/trip/docs/trips-planner/trip-places","draft":false,"unlisted":false,"tags":[],"version":"current","sidebarPosition":5,"frontMatter":{"sidebar_position":5,"description":"A trip can reference places"},"sidebar":"docSidebar","previous":{"title":"Trip - Concepts","permalink":"/trip/docs/trips-planner/trip-concepts"},"next":{"title":"Trip - Map","permalink":"/trip/docs/trips-planner/trip-map"}}');var i=n(4848),s=n(8453);const p={sidebar_position:5,description:"A trip can reference places"},a="Trip - Places",c={},o=[];function l(e){const t={admonition:"admonition",h1:"h1",header:"header",p:"p",...(0,s.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(t.header,{children:(0,i.jsx)(t.h1,{id:"trip---places",children:"Trip - Places"})}),"\n",(0,i.jsx)(t.admonition,{title:"TL;DR",type:"note",children:(0,i.jsx)(t.p,{children:"A trip can reference places. Manage the associated places and create new ones from the trip interface"})}),"\n",(0,i.jsx)(t.p,{children:"A trip can reference\xa0your places\xa0or those of other members, to associate them to the plans."}),"\n",(0,i.jsx)(t.p,{children:"To add places to your trip, you can either manage the associated places or directly create new ones from the trip interface."}),"\n",(0,i.jsx)(t.admonition,{title:"important",type:"info",children:(0,i.jsx)(t.p,{children:"Creating a place from a trip is a shortcut, the place itself is not owned by the trip."})}),"\n",(0,i.jsx)("img",{src:"/trip/img/trip_places.png",alt:"Trip - Places"}),"\n",(0,i.jsx)("div",{style:{textAlign:"center"},children:(0,i.jsx)("sup",{children:"Trip - Places"})}),"\n",(0,i.jsx)(t.admonition,{type:"tip",children:(0,i.jsx)(t.p,{children:"The \u2705 icon next to the place's category indicates that the place is used in the plans."})}),"\n",(0,i.jsx)(t.p,{children:"Hovering over a place highlights it on the map."}),"\n",(0,i.jsx)("img",{src:"/trip/img/trip_place_highlight.png",alt:"Trip - Highlight place on hover"}),"\n",(0,i.jsx)("div",{style:{textAlign:"center"},children:(0,i.jsx)("sup",{children:"Trip - Highlight place on hover"})})]})}function d(e={}){const{wrapper:t}={...(0,s.R)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(l,{...e})}):l(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>p,x:()=>a});var r=n(6540);const i={},s=r.createContext(i);function p(e){const t=r.useContext(s);return r.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:p(e.components),r.createElement(s.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/docs/assets/js/7e910379.c3dd3989.js b/docs/assets/js/7e910379.c3dd3989.js
new file mode 100644
index 0000000..313aca0
--- /dev/null
+++ b/docs/assets/js/7e910379.c3dd3989.js
@@ -0,0 +1 @@
+"use strict";(globalThis.webpackChunktripdocs=globalThis.webpackChunktripdocs||[]).push([[906],{5923:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>c,contentTitle:()=>r,default:()=>p,frontMatter:()=>s,metadata:()=>a,toc:()=>o});const a=JSON.parse('{"id":"map-tracker/places-creation","title":"Places - Creation","description":"Creating a Place using the Place creation modal","source":"@site/docs/map-tracker/places-creation.md","sourceDirName":"map-tracker","slug":"/map-tracker/places-creation","permalink":"/trip/docs/map-tracker/places-creation","draft":false,"unlisted":false,"tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"sidebar_position":3,"description":"Creating a Place using the Place creation modal"},"sidebar":"docSidebar","previous":{"title":"Places","permalink":"/trip/docs/map-tracker/places"},"next":{"title":"Map - Panel","permalink":"/trip/docs/map-tracker/map-panel"}}');var t=i(4848),l=i(8453);const s={sidebar_position:3,description:"Creating a Place using the Place creation modal"},r="Places - Creation",c={},o=[{value:"GMaps API autocompletion",id:"gmaps-api-autocompletion",level:3},{value:"GMaps Place Parser",id:"gmaps-place-parser",level:3},{value:"Latitude, Longitude Parser",id:"latitude-longitude-parser",level:3},{value:"Batch Creation",id:"batch-creation",level:2}];function d(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",ul:"ul",...(0,l.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"places---creation",children:"Places - Creation"})}),"\n",(0,t.jsx)(n.admonition,{title:"TL;DR",type:"note",children:(0,t.jsxs)(n.p,{children:["Modal supports ",(0,t.jsx)(n.a,{href:"places-creation#latitude-longitude-parser",children:"flexible coordinate formats"}),", ",(0,t.jsx)(n.a,{href:"places-creation#gmaps-place-parser",children:"Google Maps Place links"}),", and ",(0,t.jsx)(n.a,{href:"places-creation#gmaps-api-autocompletion",children:"Google Maps API"})," autocomplete (API key required)."]})}),"\n",(0,t.jsx)(n.p,{children:"Creating a place is done through the place creation modal."}),"\n",(0,t.jsx)("img",{src:"/trip/img/place_creation_modal.png",alt:"Place creation modal"}),"\n",(0,t.jsx)("div",{style:{textAlign:"center"},children:(0,t.jsx)("sup",{children:"Place creation modal"})}),"\n",(0,t.jsx)(n.p,{children:"A place contains the following informations:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"name"}),": the place's name"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"latitude"}),": latitude coordinates (",(0,t.jsx)(n.a,{href:"places-creation",children:"supports multiple formats"}),")"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"longitude"}),": longitude coordinates"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"place"}),": address or identifier (",(0,t.jsx)(n.a,{href:"places-creation#gmaps-place-parser",children:"supports GMaps Place link"}),")"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"category"}),": a label to categorize the places"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"image"}),": optional image to display in the map"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"duration"}),": optional duration in minutes (e.g., ",(0,t.jsx)(n.code,{children:"90"}),")"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"price"}),": optional price"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"description"}),": optional description (links will be auto-clickable)"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"allow dog"}),": optional boolean indicating if dogs are allowed"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"visited"}),": status indicating if the place has been visited (controls default visibility)"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"gpx"}),": optional GPX file to display trace on the map"]}),"\n"]}),"\n",(0,t.jsx)(n.h3,{id:"gmaps-api-autocompletion",children:"GMaps API autocompletion"}),"\n",(0,t.jsx)(n.admonition,{type:"warning",children:(0,t.jsxs)(n.p,{children:["You must add your ",(0,t.jsx)(n.em,{children:"Google API Key"})," in your ",(0,t.jsx)(n.a,{href:"settings",children:"settings"}),"."]})}),"\n",(0,t.jsx)(n.admonition,{type:"tip",children:(0,t.jsxs)(n.p,{children:["You can use the shortcuts ",(0,t.jsx)("kbd",{children:"Shift"}),"+",(0,t.jsx)("kbd",{children:"Enter"})," to run the completion and ",(0,t.jsx)("kbd",{children:"Ctrl"}),"+",(0,t.jsx)("kbd",{children:"Enter"})," to confirm the modal (if there is no missing field)"]})}),"\n",(0,t.jsx)(n.p,{children:"After entering a name, you can autocomplete the other fields by clicking the button inside the input area."}),"\n",(0,t.jsx)("img",{src:"/trip/img/place_gmaps_api.png",alt:"Autocomplete using GMaps API"}),"\n",(0,t.jsx)("div",{style:{textAlign:"center"},children:(0,t.jsx)("sup",{children:"Autocomplete using GMaps API"})}),"\n",(0,t.jsx)(n.h3,{id:"gmaps-place-parser",children:"GMaps Place Parser"}),"\n",(0,t.jsxs)(n.p,{children:["You can paste a Google Maps place link (",(0,t.jsx)(n.em,{children:(0,t.jsx)(n.a,{href:"https://www.google.com/maps/place/XXX",children:"https://www.google.com/maps/place/XXX"})}),") into the ",(0,t.jsx)(n.code,{children:"place"})," input to automatically populate the ",(0,t.jsx)(n.code,{children:"name"}),", ",(0,t.jsx)(n.code,{children:"place"}),", ",(0,t.jsx)(n.code,{children:"latitude"})," and ",(0,t.jsx)(n.code,{children:"longitude"})," fields from the link content."]}),"\n",(0,t.jsx)("img",{src:"/trip/img/place_gmaps_parse.png",alt:"Parse Google Maps place link"}),"\n",(0,t.jsx)("div",{style:{textAlign:"center"},children:(0,t.jsx)("sup",{children:"Parse Google Maps place link"})}),"\n",(0,t.jsx)(n.h3,{id:"latitude-longitude-parser",children:"Latitude, Longitude Parser"}),"\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.code,{children:"latitude"})," field is flexible and supports multiple LatLng coordinate formats, like:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.code,{children:"37.7749, -122.4194"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.code,{children:"37.7749\xb0 N, 122.4194\xb0 W"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.code,{children:"37\xb046'29.64\" N, 122\xb025'9.84\" W"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.code,{children:"37\xb046.494' N, 122\xb025.164' W"})}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["It also supports full* ",(0,t.jsx)(n.a,{href:"https://maps.google.com/pluscodes/",children:"Plus Code"})," (e.g., ",(0,t.jsx)(n.code,{children:"849VCWC8+R9"}),")."]}),"\n",(0,t.jsx)(n.admonition,{type:"warning",children:(0,t.jsxs)(n.p,{children:["Only full Plus Codes are currently handled. The ",(0,t.jsx)(n.code,{children:"+"})," sign is added after eight characters for full codes\xa0(e.g., ",(0,t.jsx)(n.code,{children:"849VCWC8+R9"}),") and after the four characters for short codes (e.g., not full: ",(0,t.jsx)(n.code,{children:"V75V+9Q"}),")."]})}),"\n",(0,t.jsx)(n.h2,{id:"batch-creation",children:"Batch Creation"}),"\n",(0,t.jsx)(n.p,{children:"Places can\xa0be created using\xa0the batch creation\xa0dialog, which\xa0accepts a JSON\xa0array Example:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-json",children:'[\n { "category": "Culture", "name": "Car Museum", "lat": 12.12, "lng": 50.89, "place": "Auto History Museum" },\n {\n "category": "Nature & Outdoor",\n "name": "An amazing park",\n "lat": 50.12,\n "lng": 12.89,\n "place": "The Park",\n "image": "https://upload.wikimedia.org/wikipedia/commons/b/be/Random_pyramids.jpg"\n }\n]\n'})}),"\n",(0,t.jsx)(n.admonition,{title:"mandatory properties",type:"warning",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:'"category": "Category name" (case-sensitive)\n"name": "The name"\n"lat": 0.00\n"lng": 0.00\n"place": "Your string"\n'})})}),"\n",(0,t.jsxs)(n.admonition,{title:"optional properties",type:"note",children:[(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:'"image": "https://example.com/image.jpg"\n"allowdog": true/false\n"description": "A description for the place"\n"price": 0.00\n"duration": 0\n"favorite": true/false\n"visited": true/false\n"gpx": "gpx file content"\n'})}),(0,t.jsx)(n.p,{children:"Image URLs must include the file extension. URLs without it will create the place but won't attach the image."})]})]})}function p(e={}){const{wrapper:n}={...(0,l.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>s,x:()=>r});var a=i(6540);const t={},l=a.createContext(t);function s(e){const n=a.useContext(l);return a.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:s(e.components),a.createElement(l.Provider,{value:n},e.children)}}}]);
\ No newline at end of file
diff --git a/docs/assets/js/7e910379.e69e8fd8.js b/docs/assets/js/7e910379.e69e8fd8.js
deleted file mode 100644
index e8a20d1..0000000
--- a/docs/assets/js/7e910379.e69e8fd8.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(globalThis.webpackChunktripdocs=globalThis.webpackChunktripdocs||[]).push([[906],{5923:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>c,contentTitle:()=>r,default:()=>p,frontMatter:()=>s,metadata:()=>a,toc:()=>o});const a=JSON.parse('{"id":"map-tracker/places-creation","title":"Places - Creation","description":"Creating a Place using the Place creation modal","source":"@site/docs/map-tracker/places-creation.md","sourceDirName":"map-tracker","slug":"/map-tracker/places-creation","permalink":"/trip/docs/map-tracker/places-creation","draft":false,"unlisted":false,"tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"sidebar_position":3,"description":"Creating a Place using the Place creation modal"},"sidebar":"docSidebar","previous":{"title":"Places","permalink":"/trip/docs/map-tracker/places"},"next":{"title":"Map - Panel","permalink":"/trip/docs/map-tracker/map-panel"}}');var t=i(4848),l=i(8453);const s={sidebar_position:3,description:"Creating a Place using the Place creation modal"},r="Places - Creation",c={},o=[{value:"GMaps API autocompletion",id:"gmaps-api-autocompletion",level:3},{value:"GMaps Place Parser",id:"gmaps-place-parser",level:3},{value:"Latitude, Longitude Parser",id:"latitude-longitude-parser",level:3},{value:"Batch Creation",id:"batch-creation",level:2}];function d(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",ul:"ul",...(0,l.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"places---creation",children:"Places - Creation"})}),"\n",(0,t.jsx)(n.admonition,{title:"TL;DR",type:"note",children:(0,t.jsxs)(n.p,{children:["Modal supports ",(0,t.jsx)(n.a,{href:"places-creation#latitude-longitude-parser",children:"flexible coordinate formats"}),", ",(0,t.jsx)(n.a,{href:"places-creation#gmaps-place-parser",children:"Google Maps Place links"}),", and ",(0,t.jsx)(n.a,{href:"places-creation#gmaps-api-autocompletion",children:"Google Maps API"})," autocomplete (API key required)."]})}),"\n",(0,t.jsx)(n.p,{children:"Creating a place is done through the place creation modal."}),"\n",(0,t.jsx)("img",{src:"/trip/img/place_creation_modal.png",alt:"Place creation modal"}),"\n",(0,t.jsx)("div",{style:{textAlign:"center"},children:(0,t.jsx)("sup",{children:"Place creation modal"})}),"\n",(0,t.jsx)(n.p,{children:"A place contains the following informations:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"name"}),": the place's name"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"latitude"}),": latitude coordinates (",(0,t.jsx)(n.a,{href:"places-creation",children:"supports multiple formats"}),")"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"longitude"}),": longitude coordinates"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"place"}),": address or identifier (",(0,t.jsx)(n.a,{href:"places-creation#gmaps-place-parser",children:"supports GMaps Place link"}),")"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"category"}),": a label to categorize the places"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"image"}),": optional image to display in the map"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"duration"}),": optional duration in minutes (e.g., ",(0,t.jsx)(n.code,{children:"90"}),")"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"price"}),": optional price"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"description"}),": optional description (links will be auto-clickable)"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"allow dog"}),": optional boolean indicating if dogs are allowed"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"visited"}),": status indicating if the place has been visited (controls default visibility)"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"gpx"}),": optional GPX file to display trace on the map"]}),"\n"]}),"\n",(0,t.jsx)(n.h3,{id:"gmaps-api-autocompletion",children:"GMaps API autocompletion"}),"\n",(0,t.jsx)(n.admonition,{type:"warning",children:(0,t.jsxs)(n.p,{children:["You must add your ",(0,t.jsx)(n.em,{children:"Google API Key"})," in your ",(0,t.jsx)(n.a,{href:"settings",children:"settings"}),"."]})}),"\n",(0,t.jsx)(n.p,{children:"After entering a name, you can autocomplete the other fields by clicking the button inside the input area."}),"\n",(0,t.jsx)("img",{src:"/trip/img/place_gmaps_api.png",alt:"Autocomplete using GMaps API"}),"\n",(0,t.jsx)("div",{style:{textAlign:"center"},children:(0,t.jsx)("sup",{children:"Autocomplete using GMaps API"})}),"\n",(0,t.jsx)(n.h3,{id:"gmaps-place-parser",children:"GMaps Place Parser"}),"\n",(0,t.jsxs)(n.p,{children:["You can paste a Google Maps place link (",(0,t.jsx)(n.em,{children:(0,t.jsx)(n.a,{href:"https://www.google.com/maps/place/XXX",children:"https://www.google.com/maps/place/XXX"})}),") into the ",(0,t.jsx)(n.code,{children:"place"})," input to automatically populate the ",(0,t.jsx)(n.code,{children:"name"}),", ",(0,t.jsx)(n.code,{children:"place"}),", ",(0,t.jsx)(n.code,{children:"latitude"})," and ",(0,t.jsx)(n.code,{children:"longitude"})," fields from the link content."]}),"\n",(0,t.jsx)("img",{src:"/trip/img/place_gmaps_parse.png",alt:"Parse Google Maps place link"}),"\n",(0,t.jsx)("div",{style:{textAlign:"center"},children:(0,t.jsx)("sup",{children:"Parse Google Maps place link"})}),"\n",(0,t.jsx)(n.h3,{id:"latitude-longitude-parser",children:"Latitude, Longitude Parser"}),"\n",(0,t.jsxs)(n.p,{children:["The ",(0,t.jsx)(n.code,{children:"latitude"})," field is flexible and supports multiple LatLng coordinate formats, like:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.code,{children:"37.7749, -122.4194"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.code,{children:"37.7749\xb0 N, 122.4194\xb0 W"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.code,{children:"37\xb046'29.64\" N, 122\xb025'9.84\" W"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.code,{children:"37\xb046.494' N, 122\xb025.164' W"})}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["It also supports full* ",(0,t.jsx)(n.a,{href:"https://maps.google.com/pluscodes/",children:"Plus Code"})," (e.g., ",(0,t.jsx)(n.code,{children:"849VCWC8+R9"}),")."]}),"\n",(0,t.jsx)(n.admonition,{type:"warning",children:(0,t.jsxs)(n.p,{children:["Only full Plus Codes are currently handled. The ",(0,t.jsx)(n.code,{children:"+"})," sign is added after eight characters for full codes\xa0(e.g., ",(0,t.jsx)(n.code,{children:"849VCWC8+R9"}),") and after the four characters for short codes (e.g., not full: ",(0,t.jsx)(n.code,{children:"V75V+9Q"}),")."]})}),"\n",(0,t.jsx)(n.h2,{id:"batch-creation",children:"Batch Creation"}),"\n",(0,t.jsx)(n.p,{children:"Places can\xa0be created using\xa0the batch creation\xa0dialog, which\xa0accepts a JSON\xa0array Example:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-json",children:'[\n { "category": "Culture", "name": "Car Museum", "lat": 12.12, "lng": 50.89, "place": "Auto History Museum" },\n {\n "category": "Nature & Outdoor",\n "name": "An amazing park",\n "lat": 50.12,\n "lng": 12.89,\n "place": "The Park",\n "image": "https://upload.wikimedia.org/wikipedia/commons/b/be/Random_pyramids.jpg"\n }\n]\n'})}),"\n",(0,t.jsx)(n.admonition,{title:"mandatory properties",type:"warning",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:'"category": "Category name" (case-sensitive)\n"name": "The name"\n"lat": 0.00\n"lng": 0.00\n"place": "Your string"\n'})})}),"\n",(0,t.jsxs)(n.admonition,{title:"optional properties",type:"note",children:[(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:'"image": "https://example.com/image.jpg"\n"allowdog": true/false\n"description": "A description for the place"\n"price": 0.00\n"duration": 0\n"favorite": true/false\n"visited": true/false\n"gpx": "gpx file content"\n'})}),(0,t.jsx)(n.p,{children:"Image URLs must include the file extension. URLs without it will create the place but won't attach the image."})]})]})}function p(e={}){const{wrapper:n}={...(0,l.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>s,x:()=>r});var a=i(6540);const t={},l=a.createContext(t);function s(e){const n=a.useContext(l);return a.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:s(e.components),a.createElement(l.Provider,{value:n},e.children)}}}]);
\ No newline at end of file
diff --git a/docs/assets/js/runtime~main.7981b117.js b/docs/assets/js/runtime~main.7981b117.js
deleted file mode 100644
index 720009e..0000000
--- a/docs/assets/js/runtime~main.7981b117.js
+++ /dev/null
@@ -1 +0,0 @@
-(()=>{"use strict";var e,a,t,r,f,o={},c={};function d(e){var a=c[e];if(void 0!==a)return a.exports;var t=c[e]={exports:{}};return o[e].call(t.exports,t,t.exports,d),t.exports}d.m=o,e=[],d.O=(a,t,r,f)=>{if(!t){var o=1/0;for(i=0;i=f)&&Object.keys(d.O).every(e=>d.O[e](t[n]))?t.splice(n--,1):(c=!1,f0&&e[i-1][2]>f;i--)e[i]=e[i-1];e[i]=[t,r,f]},d.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return d.d(a,{a:a}),a},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,d.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var f=Object.create(null);d.r(f);var o={};a=a||[null,t({}),t([]),t(t)];for(var c=2&r&&e;("object"==typeof c||"function"==typeof c)&&!~a.indexOf(c);c=t(c))Object.getOwnPropertyNames(c).forEach(a=>o[a]=()=>e[a]);return o.default=()=>e,d.d(f,o),f},d.d=(e,a)=>{for(var t in a)d.o(a,t)&&!d.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:a[t]})},d.f={},d.e=e=>Promise.all(Object.keys(d.f).reduce((a,t)=>(d.f[t](e,a),a),[])),d.u=e=>"assets/js/"+({20:"6fd453cb",26:"bc3400a6",48:"a94703ab",89:"7fd16edc",98:"a7bd4aaa",138:"3b6bb3a0",165:"0628ec19",185:"e1ff7207",192:"b62cd033",235:"a7456010",293:"3a165c72",350:"c3ff9a3c",394:"ceb477ef",401:"17896441",409:"a199cff3",459:"4d54d076",470:"82b76652",546:"c6b70549",582:"2d727d9f",583:"1df93b7f",588:"39a9af6f",647:"5e95c892",653:"7a8dce06",674:"70366980",687:"26922847",691:"7454facd",715:"009f1e98",733:"1d246e39",742:"aba21aa0",760:"34f354b2",886:"66a53f71",903:"ea11414a",906:"7e910379",922:"1aa3da28",969:"14eb3368",970:"7bbb8eb5",972:"6e0ab1b4",976:"0e384e19"}[e]||e)+"."+{20:"ebf0bc06",26:"8f3aea16",48:"2971bd44",89:"641e9b8d",98:"9fc1ddc3",138:"c4941bbf",165:"530602df",185:"b5c0a403",192:"86a39958",235:"e71bf6db",237:"3c68e903",293:"bda419fe",350:"f85214e1",394:"bba7ec46",401:"8045314b",409:"87b3df57",459:"d0509837",470:"d448a0c0",546:"384fc1ff",582:"5d876f1a",583:"84d45fd9",588:"9737b4a9",647:"497bdc2d",653:"f33608e8",674:"ef3b0a37",687:"324d16b6",691:"36967535",715:"6f61e663",733:"0362cab9",742:"615d9be6",760:"6bc66896",886:"cfc1c01e",903:"aa3d0bf4",906:"e69e8fd8",922:"638ef7f1",969:"b2793437",970:"409019c5",972:"af89496f",976:"33e8d20a"}[e]+".js",d.miniCssF=e=>{},d.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),d.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),r={},f="tripdocs:",d.l=(e,a,t,o)=>{if(r[e])r[e].push(a);else{var c,n;if(void 0!==t)for(var b=document.getElementsByTagName("script"),i=0;i{c.onerror=c.onload=null,clearTimeout(s);var f=r[e];if(delete r[e],c.parentNode&&c.parentNode.removeChild(c),f&&f.forEach(e=>e(t)),a)return a(t)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:c}),12e4);c.onerror=l.bind(null,c.onerror),c.onload=l.bind(null,c.onload),n&&document.head.appendChild(c)}},d.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.p="/trip/",d.gca=function(e){return e={17896441:"401",26922847:"687",70366980:"674","6fd453cb":"20",bc3400a6:"26",a94703ab:"48","7fd16edc":"89",a7bd4aaa:"98","3b6bb3a0":"138","0628ec19":"165",e1ff7207:"185",b62cd033:"192",a7456010:"235","3a165c72":"293",c3ff9a3c:"350",ceb477ef:"394",a199cff3:"409","4d54d076":"459","82b76652":"470",c6b70549:"546","2d727d9f":"582","1df93b7f":"583","39a9af6f":"588","5e95c892":"647","7a8dce06":"653","7454facd":"691","009f1e98":"715","1d246e39":"733",aba21aa0:"742","34f354b2":"760","66a53f71":"886",ea11414a:"903","7e910379":"906","1aa3da28":"922","14eb3368":"969","7bbb8eb5":"970","6e0ab1b4":"972","0e384e19":"976"}[e]||e,d.p+d.u(e)},(()=>{var e={354:0,869:0};d.f.j=(a,t)=>{var r=d.o(e,a)?e[a]:void 0;if(0!==r)if(r)t.push(r[2]);else if(/^(354|869)$/.test(a))e[a]=0;else{var f=new Promise((t,f)=>r=e[a]=[t,f]);t.push(r[2]=f);var o=d.p+d.u(a),c=new Error;d.l(o,t=>{if(d.o(e,a)&&(0!==(r=e[a])&&(e[a]=void 0),r)){var f=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;c.message="Loading chunk "+a+" failed.\n("+f+": "+o+")",c.name="ChunkLoadError",c.type=f,c.request=o,r[1](c)}},"chunk-"+a,a)}},d.O.j=a=>0===e[a];var a=(a,t)=>{var r,f,[o,c,n]=t,b=0;if(o.some(a=>0!==e[a])){for(r in c)d.o(c,r)&&(d.m[r]=c[r]);if(n)var i=n(d)}for(a&&a(t);b{"use strict";var e,a,t,r,f,o={},d={};function c(e){var a=d[e];if(void 0!==a)return a.exports;var t=d[e]={exports:{}};return o[e].call(t.exports,t,t.exports,c),t.exports}c.m=o,e=[],c.O=(a,t,r,f)=>{if(!t){var o=1/0;for(i=0;i=f)&&Object.keys(c.O).every(e=>c.O[e](t[n]))?t.splice(n--,1):(d=!1,f0&&e[i-1][2]>f;i--)e[i]=e[i-1];e[i]=[t,r,f]},c.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return c.d(a,{a:a}),a},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,c.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var f=Object.create(null);c.r(f);var o={};a=a||[null,t({}),t([]),t(t)];for(var d=2&r&&e;("object"==typeof d||"function"==typeof d)&&!~a.indexOf(d);d=t(d))Object.getOwnPropertyNames(d).forEach(a=>o[a]=()=>e[a]);return o.default=()=>e,c.d(f,o),f},c.d=(e,a)=>{for(var t in a)c.o(a,t)&&!c.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:a[t]})},c.f={},c.e=e=>Promise.all(Object.keys(c.f).reduce((a,t)=>(c.f[t](e,a),a),[])),c.u=e=>"assets/js/"+({20:"6fd453cb",26:"bc3400a6",48:"a94703ab",89:"7fd16edc",98:"a7bd4aaa",138:"3b6bb3a0",165:"0628ec19",185:"e1ff7207",192:"b62cd033",235:"a7456010",293:"3a165c72",350:"c3ff9a3c",394:"ceb477ef",401:"17896441",409:"a199cff3",459:"4d54d076",470:"82b76652",546:"c6b70549",582:"2d727d9f",583:"1df93b7f",588:"39a9af6f",647:"5e95c892",653:"7a8dce06",674:"70366980",687:"26922847",691:"7454facd",715:"009f1e98",733:"1d246e39",742:"aba21aa0",760:"34f354b2",886:"66a53f71",903:"ea11414a",906:"7e910379",922:"1aa3da28",969:"14eb3368",970:"7bbb8eb5",972:"6e0ab1b4",976:"0e384e19"}[e]||e)+"."+{20:"ebf0bc06",26:"8f3aea16",48:"2971bd44",89:"641e9b8d",98:"9fc1ddc3",138:"c4941bbf",165:"530602df",185:"b5c0a403",192:"86a39958",235:"e71bf6db",237:"3c68e903",293:"bda419fe",350:"f85214e1",394:"bba7ec46",401:"8045314b",409:"87b3df57",459:"d0509837",470:"d448a0c0",546:"384fc1ff",582:"5d876f1a",583:"84d45fd9",588:"9737b4a9",647:"497bdc2d",653:"f33608e8",674:"ef3b0a37",687:"324d16b6",691:"36967535",715:"6f61e663",733:"0362cab9",742:"615d9be6",760:"6bc66896",886:"cfc1c01e",903:"aa3d0bf4",906:"c3dd3989",922:"638ef7f1",969:"b2793437",970:"409019c5",972:"319f6a48",976:"33e8d20a"}[e]+".js",c.miniCssF=e=>{},c.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),c.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),r={},f="tripdocs:",c.l=(e,a,t,o)=>{if(r[e])r[e].push(a);else{var d,n;if(void 0!==t)for(var b=document.getElementsByTagName("script"),i=0;i{d.onerror=d.onload=null,clearTimeout(s);var f=r[e];if(delete r[e],d.parentNode&&d.parentNode.removeChild(d),f&&f.forEach(e=>e(t)),a)return a(t)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=l.bind(null,d.onerror),d.onload=l.bind(null,d.onload),n&&document.head.appendChild(d)}},c.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},c.p="/trip/",c.gca=function(e){return e={17896441:"401",26922847:"687",70366980:"674","6fd453cb":"20",bc3400a6:"26",a94703ab:"48","7fd16edc":"89",a7bd4aaa:"98","3b6bb3a0":"138","0628ec19":"165",e1ff7207:"185",b62cd033:"192",a7456010:"235","3a165c72":"293",c3ff9a3c:"350",ceb477ef:"394",a199cff3:"409","4d54d076":"459","82b76652":"470",c6b70549:"546","2d727d9f":"582","1df93b7f":"583","39a9af6f":"588","5e95c892":"647","7a8dce06":"653","7454facd":"691","009f1e98":"715","1d246e39":"733",aba21aa0:"742","34f354b2":"760","66a53f71":"886",ea11414a:"903","7e910379":"906","1aa3da28":"922","14eb3368":"969","7bbb8eb5":"970","6e0ab1b4":"972","0e384e19":"976"}[e]||e,c.p+c.u(e)},(()=>{var e={354:0,869:0};c.f.j=(a,t)=>{var r=c.o(e,a)?e[a]:void 0;if(0!==r)if(r)t.push(r[2]);else if(/^(354|869)$/.test(a))e[a]=0;else{var f=new Promise((t,f)=>r=e[a]=[t,f]);t.push(r[2]=f);var o=c.p+c.u(a),d=new Error;c.l(o,t=>{if(c.o(e,a)&&(0!==(r=e[a])&&(e[a]=void 0),r)){var f=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;d.message="Loading chunk "+a+" failed.\n("+f+": "+o+")",d.name="ChunkLoadError",d.type=f,d.request=o,r[1](d)}},"chunk-"+a,a)}},c.O.j=a=>0===e[a];var a=(a,t)=>{var r,f,[o,d,n]=t,b=0;if(o.some(a=>0!==e[a])){for(r in d)c.o(d,r)&&(c.m[r]=d[r]);if(n)var i=n(c)}for(a&&a(t);bGetting Started | TRIP
-
+
diff --git a/docs/docs/category/map-tracker/index.html b/docs/docs/category/map-tracker/index.html
index 54e5aa1..b382771 100644
--- a/docs/docs/category/map-tracker/index.html
+++ b/docs/docs/category/map-tracker/index.html
@@ -4,7 +4,7 @@
Map Tracker | TRIP
-
+
diff --git a/docs/docs/category/miscelaneous/index.html b/docs/docs/category/miscelaneous/index.html
index 9f75caf..c1a1d17 100644
--- a/docs/docs/category/miscelaneous/index.html
+++ b/docs/docs/category/miscelaneous/index.html
@@ -4,7 +4,7 @@
Miscelaneous | TRIP
-
+
diff --git a/docs/docs/category/trips-planner/index.html b/docs/docs/category/trips-planner/index.html
index 26c389a..907e072 100644
--- a/docs/docs/category/trips-planner/index.html
+++ b/docs/docs/category/trips-planner/index.html
@@ -4,7 +4,7 @@
Trips Planner | TRIP
-
+
diff --git a/docs/docs/contributing/index.html b/docs/docs/contributing/index.html
index e5ef253..970b98d 100644
--- a/docs/docs/contributing/index.html
+++ b/docs/docs/contributing/index.html
@@ -4,7 +4,7 @@
Contributing | TRIP
-
+
diff --git a/docs/docs/getting-started/configuration/index.html b/docs/docs/getting-started/configuration/index.html
index 193665e..87e3556 100644
--- a/docs/docs/getting-started/configuration/index.html
+++ b/docs/docs/getting-started/configuration/index.html
@@ -4,7 +4,7 @@
Configuration | TRIP
-
+
diff --git a/docs/docs/getting-started/deploy/index.html b/docs/docs/getting-started/deploy/index.html
index c2aee7f..a87376f 100644
--- a/docs/docs/getting-started/deploy/index.html
+++ b/docs/docs/getting-started/deploy/index.html
@@ -4,7 +4,7 @@
Deployment | TRIP
-
+
diff --git a/docs/docs/intro/index.html b/docs/docs/intro/index.html
index d9e2eb0..8810b53 100644
--- a/docs/docs/intro/index.html
+++ b/docs/docs/intro/index.html
@@ -4,7 +4,7 @@
Introduction | TRIP
-
+
diff --git a/docs/docs/map-tracker/introduction/index.html b/docs/docs/map-tracker/introduction/index.html
index f6163cc..bd46e2f 100644
--- a/docs/docs/map-tracker/introduction/index.html
+++ b/docs/docs/map-tracker/introduction/index.html
@@ -4,7 +4,7 @@
Introduction | TRIP
-
+
diff --git a/docs/docs/map-tracker/map-filtering/index.html b/docs/docs/map-tracker/map-filtering/index.html
index 538746c..b391d6e 100644
--- a/docs/docs/map-tracker/map-filtering/index.html
+++ b/docs/docs/map-tracker/map-filtering/index.html
@@ -4,7 +4,7 @@
Map - Filtering | TRIP
-
+
diff --git a/docs/docs/map-tracker/map-panel/index.html b/docs/docs/map-tracker/map-panel/index.html
index e6403d5..e04f098 100644
--- a/docs/docs/map-tracker/map-panel/index.html
+++ b/docs/docs/map-tracker/map-panel/index.html
@@ -4,7 +4,7 @@
Map - Panel | TRIP
-
+
diff --git a/docs/docs/map-tracker/places-creation/index.html b/docs/docs/map-tracker/places-creation/index.html
index 38d15bd..e539924 100644
--- a/docs/docs/map-tracker/places-creation/index.html
+++ b/docs/docs/map-tracker/places-creation/index.html
@@ -4,7 +4,7 @@
Places - Creation | TRIP
-
+
@@ -33,6 +33,7 @@