adds docs
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
export let openModal: (() => void) | undefined = undefined;
|
||||
export let closeModal: (() => void) | undefined = undefined;
|
||||
|
||||
export let title: string = "Confirm Deletion";
|
||||
export let title: string = $_('confirm-deletion');
|
||||
export let text: string;
|
||||
export let action: string = "delete";
|
||||
|
||||
|
||||
@@ -6,43 +6,28 @@
|
||||
formatElevation,
|
||||
formatTimeHHMM,
|
||||
} from "$lib/util/format_util";
|
||||
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
import ShareInfo from "../share_info.svelte";
|
||||
import { currentUser } from "$lib/stores/user_store";
|
||||
export let list: List;
|
||||
export let active: boolean = false;
|
||||
|
||||
$: cumulativeDistance = list.expand?.trails.reduce(
|
||||
$: cumulativeDistance = list.expand?.trails?.reduce(
|
||||
(s, b) => s + b.distance!,
|
||||
0,
|
||||
);
|
||||
|
||||
$: cumulativeElevationGain = list.expand?.trails.reduce(
|
||||
$: cumulativeElevationGain = list.expand?.trails?.reduce(
|
||||
(s, b) => s + b.elevation_gain!,
|
||||
0,
|
||||
);
|
||||
|
||||
$: cumulativeDuration = list.expand?.trails.reduce(
|
||||
$: cumulativeDuration = list.expand?.trails?.reduce(
|
||||
(s, b) => s + b.duration!,
|
||||
0,
|
||||
);
|
||||
|
||||
$: listIsShared = (list.expand?.list_share_via_list?.length ?? 0) > 0;
|
||||
|
||||
$: allowEdit =
|
||||
list.author == $currentUser?.id ||
|
||||
list.expand?.list_share_via_list?.some((s) => s.permission == "edit");
|
||||
|
||||
$: dropdownItems = [
|
||||
...(list.author == $currentUser?.id
|
||||
? [{ text: $_("share"), value: "share" }]
|
||||
: []),
|
||||
...(allowEdit ? [{ text: $_("edit"), value: "edit" }] : []),
|
||||
...(list.author == $currentUser?.id
|
||||
? [{ text: $_("delete"), value: "delete" }]
|
||||
: []),
|
||||
];
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -72,9 +57,6 @@
|
||||
{#if listIsShared}
|
||||
<ShareInfo type="list" subject={list}></ShareInfo>
|
||||
{/if}
|
||||
{#if dropdownItems.length}
|
||||
<Dropdown items={dropdownItems} on:change></Dropdown>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex mt-1 gap-x-4 text-sm text-gray-500 whitespace-nowrap flex-wrap">
|
||||
<span
|
||||
@@ -94,9 +76,9 @@
|
||||
>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 mb-2">
|
||||
{list.expand?.trails.length ?? 0}
|
||||
{list.expand?.trails?.length ?? 0}
|
||||
{$_("trail", {
|
||||
values: { n: list.expand?.trails.length ?? 0 },
|
||||
values: { n: list.expand?.trails?.length ?? 0 },
|
||||
})}
|
||||
</p>
|
||||
<p
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import { browser } from "$app/environment";
|
||||
import { listSchema, type List } from "$lib/models/list";
|
||||
import { list } from "$lib/stores/list_store";
|
||||
import { createForm } from "$lib/vendor/svelte-form-lib/index";
|
||||
import { util } from "$lib/vendor/svelte-form-lib/util";
|
||||
import { _ } from "svelte-i18n";
|
||||
import Modal from "../base/modal.svelte";
|
||||
import TextField from "../base/text_field.svelte";
|
||||
import Textarea from "../base/textarea.svelte";
|
||||
import { getFileURL } from "$lib/util/file_util";
|
||||
export let openModal: (() => void) | undefined = undefined;
|
||||
export let closeModal: (() => void) | undefined = undefined;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let previewURL = "";
|
||||
|
||||
const { form, errors, handleChange, handleSubmit } = createForm<List>({
|
||||
initialValues: $list,
|
||||
validationSchema: listSchema,
|
||||
onSubmit: async (submittedList) => {
|
||||
dispatch("save", { list: submittedList, avatar: (document.getElementById("avatar") as HTMLInputElement).files![0] });
|
||||
(document.getElementById("avatar") as HTMLInputElement).value = "";
|
||||
closeModal!();
|
||||
},
|
||||
});
|
||||
|
||||
function openAvatarBrowser() {
|
||||
document.getElementById("avatar")!.click();
|
||||
}
|
||||
|
||||
function handleAvatarSelection() {
|
||||
const files = (document.getElementById("avatar") as HTMLInputElement)
|
||||
.files;
|
||||
|
||||
if (!files) {
|
||||
return;
|
||||
}
|
||||
|
||||
previewURL = URL.createObjectURL(files[0]);
|
||||
}
|
||||
$: if (browser) {
|
||||
form.set(util.cloneDeep($list));
|
||||
previewURL = getFileURL($list, $list.avatar) ?? "";
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
id="list-modal"
|
||||
title={$form.id ? $_("edit-list") : $_("new-list")}
|
||||
let:openModal
|
||||
bind:openModal
|
||||
bind:closeModal
|
||||
>
|
||||
<slot {openModal} />
|
||||
<form
|
||||
id="list-form"
|
||||
slot="content"
|
||||
class="modal-content space-y-4"
|
||||
on:submit={handleSubmit}
|
||||
>
|
||||
<label for="avatar" class="text-sm font-medium block"> {$_('avatar')} </label>
|
||||
<input
|
||||
name="avatar"
|
||||
type="file"
|
||||
id="avatar"
|
||||
accept="image/*"
|
||||
style="display: none;"
|
||||
on:change={handleAvatarSelection}
|
||||
/>
|
||||
<div class="flex items-center gap-4">
|
||||
{#if previewURL.length > 0}
|
||||
<img
|
||||
class="w-32 aspect-square rounded-full object-cover"
|
||||
alt="avatar"
|
||||
src={previewURL}
|
||||
/>
|
||||
{/if}
|
||||
<button
|
||||
class="btn-secondary"
|
||||
type="button"
|
||||
on:click={openAvatarBrowser}>{$_('change')}...</button
|
||||
>
|
||||
</div>
|
||||
|
||||
<TextField
|
||||
name="name"
|
||||
label={$_("name")}
|
||||
bind:value={$form.name}
|
||||
error={$errors.name}
|
||||
on:change={handleChange}
|
||||
></TextField>
|
||||
|
||||
<Textarea
|
||||
name="description"
|
||||
label={$_("description")}
|
||||
bind:value={$form.description}
|
||||
error={$errors.description}
|
||||
on:change={handleChange}
|
||||
></Textarea>
|
||||
</form>
|
||||
<div slot="footer" class="flex items-center gap-4">
|
||||
<button class="btn-secondary" on:click={closeModal}
|
||||
>{$_("cancel")}</button
|
||||
>
|
||||
<button class="btn-primary" type="submit" form="list-form" name="save"
|
||||
>{$_("save")}</button
|
||||
>
|
||||
</div>
|
||||
</Modal>
|
||||
156
web/src/lib/components/list/list_panel.svelte
Normal file
156
web/src/lib/components/list/list_panel.svelte
Normal file
@@ -0,0 +1,156 @@
|
||||
<script lang="ts">
|
||||
import type { List } from "$lib/models/list";
|
||||
import { getFileURL } from "$lib/util/file_util";
|
||||
import {
|
||||
formatDistance,
|
||||
formatElevation,
|
||||
formatTimeHHMM,
|
||||
} from "$lib/util/format_util";
|
||||
import { _ } from "svelte-i18n";
|
||||
import TrailListItem from "../trail/trail_list_item.svelte";
|
||||
import type { Trail } from "$lib/models/trail";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { currentUser } from "$lib/stores/user_store";
|
||||
import Dropdown from "../base/dropdown.svelte";
|
||||
import ShareInfo from "../share_info.svelte";
|
||||
|
||||
export let list: List;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
$: cumulativeDistance = list.expand?.trails?.reduce(
|
||||
(s, b) => s + b.distance!,
|
||||
0,
|
||||
);
|
||||
|
||||
$: cumulativeElevationGain = list.expand?.trails?.reduce(
|
||||
(s, b) => s + b.elevation_gain!,
|
||||
0,
|
||||
);
|
||||
|
||||
$: cumulativeDuration = list.expand?.trails?.reduce(
|
||||
(s, b) => s + b.duration!,
|
||||
0,
|
||||
);
|
||||
|
||||
$: allowEdit =
|
||||
list.author == $currentUser?.id ||
|
||||
list.expand?.list_share_via_list?.some((s) => s.permission == "edit");
|
||||
|
||||
$: dropdownItems = [
|
||||
...(list.author == $currentUser?.id
|
||||
? [{ text: $_("share"), value: "share", icon: "share" }]
|
||||
: []),
|
||||
...(allowEdit
|
||||
? [{ text: $_("edit"), value: "edit", icon: "pen" }]
|
||||
: []),
|
||||
...(list.author == $currentUser?.id
|
||||
? [{ text: $_("delete"), value: "delete", icon: "trash" }]
|
||||
: []),
|
||||
];
|
||||
|
||||
$: listIsShared = (list.expand?.list_share_via_list?.length ?? 0) > 0;
|
||||
|
||||
let fullDescription: boolean = false;
|
||||
|
||||
function handleTrailSelect(trail: Trail, index: number) {
|
||||
dispatch("click", { trail, index });
|
||||
}
|
||||
|
||||
function handleTrailMouseEnter(trail: Trail, index: number) {
|
||||
dispatch("mouseenter", { trail, index });
|
||||
}
|
||||
|
||||
function handleTrailMouseLeave(trail: Trail, index: number) {
|
||||
dispatch("mouseleave", { trail, index });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="relative">
|
||||
{#if listIsShared}
|
||||
<div class="absolute top-8 right-8 bg-white rounded-full w-8 py-1 text-center">
|
||||
<ShareInfo type="list" subject={list}></ShareInfo>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if dropdownItems.length}
|
||||
<div class="absolute bottom-8 right-8">
|
||||
<Dropdown
|
||||
items={dropdownItems}
|
||||
on:change
|
||||
let:toggleMenu={openDropdown}
|
||||
><button
|
||||
class="rounded-full bg-white text-black hover:bg-gray-200 focus:ring-4 ring-gray-100/50 transition-colors h-12 w-12"
|
||||
on:click={openDropdown}
|
||||
>
|
||||
<i class="fa fa-ellipsis-vertical"></i>
|
||||
</button></Dropdown
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
{#if list.avatar}
|
||||
<img
|
||||
class="w-full object-cover"
|
||||
src={getFileURL(list, list.avatar)}
|
||||
alt="avatar"
|
||||
/>
|
||||
{:else}
|
||||
<div class="flex w-full shrink-0 items-center justify-center min-h-72">
|
||||
<i class="fa fa-table-list text-5xl"></i>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="p-4 md:p-6">
|
||||
<h4 class="text-2xl font-semibold mb-4">{list.name}</h4>
|
||||
|
||||
<hr />
|
||||
<div class="flex my-4 gap-x-4 font-semibold whitespace-nowrap flex-wrap justify-around">
|
||||
<span
|
||||
><i class="fa fa-left-right mr-2"></i>{formatDistance(
|
||||
cumulativeDistance,
|
||||
)}</span
|
||||
>
|
||||
<span
|
||||
><i class="fa fa-up-down mr-2"></i>{formatElevation(
|
||||
cumulativeElevationGain,
|
||||
)}</span
|
||||
>
|
||||
<span
|
||||
><i class="fa fa-clock mr-2"></i>{formatTimeHHMM(
|
||||
cumulativeDuration,
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
<hr class="mb-4" />
|
||||
<p
|
||||
class="text-gray-500 whitespace-pre-wrap {fullDescription
|
||||
? ''
|
||||
: 'max-h-24 overflow-hidden text-ellipsis'}"
|
||||
>
|
||||
{!fullDescription
|
||||
? list.description?.substring(0, 100)
|
||||
: list.description}
|
||||
{#if (list.description?.length ?? 0) > 100 && !fullDescription}
|
||||
<button on:click={() => (fullDescription = true)}>
|
||||
... <span class="underline">{$_("read-more")}</span></button
|
||||
>
|
||||
{/if}
|
||||
</p>
|
||||
<h5 class="text-xl font-semibold my-4">
|
||||
{list.trails?.length ?? 0}
|
||||
{$_("trail", { values: { n: list.trails?.length ?? 0 } })}
|
||||
</h5>
|
||||
<div class="space-y-2">
|
||||
{#each list.expand?.trails ?? [] as trail, i}
|
||||
<div
|
||||
role="presentation"
|
||||
on:click={() => handleTrailSelect(trail, i)}
|
||||
on:mouseenter={() => handleTrailMouseEnter(trail, i)}
|
||||
on:mouseleave={() => handleTrailMouseLeave(trail, i)}
|
||||
>
|
||||
<TrailListItem {trail} showDescription={false}></TrailListItem>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
@@ -20,6 +20,7 @@
|
||||
export let map: any | null = null;
|
||||
export let options: any = {};
|
||||
export let activeTrailIndex: number | null = 0;
|
||||
export let markers: any[] = [];
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -28,7 +29,9 @@
|
||||
|
||||
let selectedMetric: "altitude" | "slope" | "speed" | false = "altitude";
|
||||
|
||||
$: gpxData = trails.map((t) => t.expand.gpx_data);
|
||||
$: gpxData = trails.map((t) => {
|
||||
return { id: t.id, gpx: t.expand.gpx_data };
|
||||
});
|
||||
$: if (
|
||||
gpxData &&
|
||||
gpxGroup &&
|
||||
@@ -216,15 +219,20 @@
|
||||
|
||||
gpxGroup.on("selection_changed", ({ polyline }: { polyline: any }) => {
|
||||
markerLayerGroup.clearLayers();
|
||||
|
||||
activeTrailIndex = null;
|
||||
markers = [];
|
||||
|
||||
if (polyline._selected) {
|
||||
activeTrailIndex = polyline.options.index;
|
||||
activeTrailIndex = trails.findIndex(
|
||||
(t) => t.id ==
|
||||
polyline.options.id,
|
||||
);
|
||||
|
||||
for (const waypoint of trails.at(activeTrailIndex!)?.expand
|
||||
.waypoints ?? []) {
|
||||
const marker = createMarkerFromWaypoint(L, waypoint);
|
||||
marker.addTo(markerLayerGroup!);
|
||||
markers.push(marker);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -237,8 +245,8 @@
|
||||
gpxGroup.on("route_loaded", ({ route }: { route: any }) => {
|
||||
const trail = trails.find(
|
||||
(t) =>
|
||||
gpxGroup?._hashCode(t.expand.gpx_data) ==
|
||||
route.options.hash,
|
||||
t.id ==
|
||||
route.options.id,
|
||||
);
|
||||
|
||||
if (!trail) {
|
||||
@@ -275,16 +283,28 @@
|
||||
gpxGroup.addTo(map);
|
||||
});
|
||||
|
||||
export function selectTrail(index: number) {
|
||||
gpxGroup.select(index);
|
||||
export function highlightTrail(id: string) {
|
||||
gpxGroup?.highlightTrack(id);
|
||||
}
|
||||
|
||||
export function openPopup(index: number) {
|
||||
gpxGroup.openPopup(index);
|
||||
export function unHighlightTrail(id: string) {
|
||||
gpxGroup?.unHighlightTrack(id);
|
||||
}
|
||||
|
||||
export function closePopup(index: number) {
|
||||
gpxGroup.closePopup(index);
|
||||
export function selectTrail(id: string) {
|
||||
gpxGroup?.select(id);
|
||||
}
|
||||
|
||||
export function resetSelection() {
|
||||
gpxGroup?.resetSelection();
|
||||
}
|
||||
|
||||
export function openPopup(id: string) {
|
||||
gpxGroup?.openPopup(id);
|
||||
}
|
||||
|
||||
export function closePopup(id: string) {
|
||||
gpxGroup?.closePopup(id);
|
||||
}
|
||||
|
||||
function switchHotline(metric: "altitude" | "slope" | "speed" | false) {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
import { pb } from "$lib/pocketbase";
|
||||
|
||||
export let trail: Trail;
|
||||
export let mode: "overview" | "map";
|
||||
export let mode: "overview" | "map" | "list";
|
||||
|
||||
let openConfirmModal: () => void;
|
||||
let openListSelectModal: () => void;
|
||||
|
||||
@@ -21,7 +21,11 @@
|
||||
formatElevation,
|
||||
formatTimeHHMM,
|
||||
} from "$lib/util/format_util";
|
||||
import { createMarkerFromWaypoint, endIcon, startIcon } from "$lib/util/leaflet_util";
|
||||
import {
|
||||
createMarkerFromWaypoint,
|
||||
endIcon,
|
||||
startIcon,
|
||||
} from "$lib/util/leaflet_util";
|
||||
import "$lib/vendor/leaflet-elevation/src/index.css";
|
||||
import type { Icon, Map, Marker } from "leaflet";
|
||||
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
|
||||
@@ -36,7 +40,7 @@
|
||||
import ShareInfo from "../share_info.svelte";
|
||||
|
||||
export let trail: Trail;
|
||||
export let mode: "overview" | "map" = "map";
|
||||
export let mode: "overview" | "map" | "list" = "map";
|
||||
export let markers: Marker[] = [];
|
||||
|
||||
const tabs = [
|
||||
@@ -116,6 +120,10 @@
|
||||
markers[i].openPopup();
|
||||
}
|
||||
|
||||
function closeMarkerPopup(i: number) {
|
||||
markers[i].closePopup();
|
||||
}
|
||||
|
||||
async function toggleMapFullScreen() {
|
||||
goto(`/map/trail/${trail.id!}`);
|
||||
}
|
||||
@@ -160,12 +168,19 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="trail-info-panel mx-auto border border-input-border rounded-3xl h-full"
|
||||
class="trail-info-panel mx-auto {mode == 'list'
|
||||
? ''
|
||||
: 'border border-input-border rounded-3xl'} h-full"
|
||||
style="max-width: min(100%, 64rem);"
|
||||
>
|
||||
<div class="trail-info-panel-header">
|
||||
<section class="relative h-80">
|
||||
<img class="w-full h-80 rounded-t-3xl" src={thumbnail} alt="" />
|
||||
<img
|
||||
class="w-full h-80 "
|
||||
class:rounded-t-3xl={mode !== "list"}
|
||||
src={thumbnail}
|
||||
alt=""
|
||||
/>
|
||||
<div
|
||||
class="absolute bottom-0 w-full h-1/2 bg-gradient-to-b from-transparent to-black opacity-50"
|
||||
></div>
|
||||
@@ -186,7 +201,8 @@
|
||||
</span>
|
||||
{/if}
|
||||
{#if trailIsShared}
|
||||
<ShareInfo type="trail" subject={trail} large={true}></ShareInfo>
|
||||
<ShareInfo type="trail" subject={trail} large={true}
|
||||
></ShareInfo>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -286,7 +302,7 @@
|
||||
{#if activeTab == 1}
|
||||
<ul>
|
||||
{#each trail.expand.waypoints ?? [] as waypoint, i}
|
||||
<li on:mouseenter={() => openMarkerPopup(i)}>
|
||||
<li on:mouseenter={() => openMarkerPopup(i)} on:mouseleave={() => closeMarkerPopup(i)}>
|
||||
<WaypointCard {waypoint}></WaypointCard>
|
||||
</li>
|
||||
{/each}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
export let trail: Trail;
|
||||
|
||||
export let showDescription: boolean = true;
|
||||
|
||||
$: thumbnail = trail.photos.length
|
||||
? getFileURL(trail, trail.photos[trail.thumbnail])
|
||||
: "/imgs/default_thumbnail.webp";
|
||||
@@ -51,9 +53,7 @@
|
||||
<h5><i class="fa fa-location-dot mr-3"></i>{trail.location}</h5>
|
||||
{/if}
|
||||
<h5>
|
||||
<i class="fa fa-gauge mr-3"></i>{$_(
|
||||
trail.difficulty ?? "?",
|
||||
)}
|
||||
<i class="fa fa-gauge mr-3"></i>{$_(trail.difficulty ?? "?")}
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
@@ -74,10 +74,12 @@
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
<p
|
||||
class="mt-3 text-sm whitespace-nowrap min-w-0 max-w-full overflow-hidden text-ellipsis"
|
||||
>
|
||||
{trail.description}
|
||||
</p>
|
||||
{#if showDescription}
|
||||
<p
|
||||
class="mt-3 text-sm whitespace-nowrap min-w-0 max-w-full overflow-hidden text-ellipsis"
|
||||
>
|
||||
{trail.description}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"completed": "Abgeschlossen",
|
||||
"completion-status": "Abschlussstatus",
|
||||
"confirm": "",
|
||||
"confirm-deletion": "",
|
||||
"confirm-share": "",
|
||||
"contribute": "Mitwirken",
|
||||
"copy-link": "Link kopieren",
|
||||
@@ -155,6 +156,7 @@
|
||||
"profile": "Profil",
|
||||
"public": "Öffentlich",
|
||||
"radius": "Radius",
|
||||
"read-more": "",
|
||||
"register": "Registrieren",
|
||||
"removed-trail-from": "Route entfernt aus",
|
||||
"required": "Pflichtfeld",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"completed": "Completed",
|
||||
"completion-status": "Completion Status",
|
||||
"confirm": "Confirm",
|
||||
"confirm-deletion": "Confirm Deletion",
|
||||
"confirm-share": "Confirm share",
|
||||
"contribute": "Contribute",
|
||||
"copy-link": "Copy Link",
|
||||
@@ -155,6 +156,7 @@
|
||||
"profile": "Profile",
|
||||
"public": "Public",
|
||||
"radius": "Radius",
|
||||
"read-more": "Read more",
|
||||
"register": "Register",
|
||||
"removed-trail-from": "Removed trail from",
|
||||
"required": "Required",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"completed": "Compléter",
|
||||
"completion-status": "L'état d'achèvement",
|
||||
"confirm": "",
|
||||
"confirm-deletion": "",
|
||||
"confirm-share": "",
|
||||
"contribute": "Contribuer",
|
||||
"copy-link": "",
|
||||
@@ -155,6 +156,7 @@
|
||||
"profile": "Profile",
|
||||
"public": "Publique",
|
||||
"radius": "Rayon",
|
||||
"read-more": "",
|
||||
"register": "S'enregistrer",
|
||||
"removed-trail-from": "Enlever l'itinéraire de",
|
||||
"required": "Requis",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"completed": "Teljesítve",
|
||||
"completion-status": "Befejezés állapota",
|
||||
"confirm": "",
|
||||
"confirm-deletion": "",
|
||||
"confirm-share": "",
|
||||
"contribute": "Hozzájárulás",
|
||||
"copy-link": "",
|
||||
@@ -155,6 +156,7 @@
|
||||
"profile": "Profil",
|
||||
"public": "Publikus",
|
||||
"radius": "Átmérő",
|
||||
"read-more": "",
|
||||
"register": "Regisztráció",
|
||||
"removed-trail-from": "Eltávolított nyomvonal a",
|
||||
"required": "Kötelező",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"completed": "Completato",
|
||||
"completion-status": "Stato di completamento",
|
||||
"confirm": "",
|
||||
"confirm-deletion": "",
|
||||
"confirm-share": "",
|
||||
"contribute": "Contribuisci",
|
||||
"copy-link": "Copia link",
|
||||
@@ -155,6 +156,7 @@
|
||||
"profile": "Profilo",
|
||||
"public": "Pubblico",
|
||||
"radius": "Raggio",
|
||||
"read-more": "",
|
||||
"register": "Registrati",
|
||||
"removed-trail-from": "Percorso rimosso da",
|
||||
"required": "Obbligatorio",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"completed": "Voltooid",
|
||||
"completion-status": "Voltooiingsstatus",
|
||||
"confirm": "",
|
||||
"confirm-deletion": "",
|
||||
"confirm-share": "",
|
||||
"contribute": "Bijdragen",
|
||||
"copy-link": "",
|
||||
@@ -155,6 +156,7 @@
|
||||
"profile": "Profiel",
|
||||
"public": "Openbaar",
|
||||
"radius": "Straal",
|
||||
"read-more": "",
|
||||
"register": "Registreren",
|
||||
"removed-trail-from": "De wandelroute is verwijderd van",
|
||||
"required": "Verplicht",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"completed": "Zakończono",
|
||||
"completion-status": "Stan ukończenia",
|
||||
"confirm": "",
|
||||
"confirm-deletion": "",
|
||||
"confirm-share": "",
|
||||
"contribute": "Kontrybuuj",
|
||||
"copy-link": "",
|
||||
@@ -155,6 +156,7 @@
|
||||
"profile": "Profil",
|
||||
"public": "Publiczny",
|
||||
"radius": "Promień",
|
||||
"read-more": "",
|
||||
"register": "Zarejestruj",
|
||||
"removed-trail-from": "Usunięto ścieżkę z",
|
||||
"required": "Wymagane",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"completed": "Completada",
|
||||
"completion-status": "Status de conclusão",
|
||||
"confirm": "",
|
||||
"confirm-deletion": "",
|
||||
"confirm-share": "",
|
||||
"contribute": "Contribuir",
|
||||
"copy-link": "",
|
||||
@@ -155,6 +156,7 @@
|
||||
"profile": "Perfil",
|
||||
"public": "Público",
|
||||
"radius": "Raio",
|
||||
"read-more": "",
|
||||
"register": "Registo",
|
||||
"removed-trail-from": "Trilha removida de",
|
||||
"required": "Obrigatório",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"completed": "已完成",
|
||||
"completion-status": "完成状态",
|
||||
"confirm": "",
|
||||
"confirm-deletion": "",
|
||||
"confirm-share": "",
|
||||
"contribute": "贡献",
|
||||
"copy-link": "",
|
||||
@@ -155,6 +156,7 @@
|
||||
"profile": "个人资料",
|
||||
"public": "公开",
|
||||
"radius": "半径",
|
||||
"read-more": "",
|
||||
"register": "注册",
|
||||
"removed-trail-from": "路线已删除自",
|
||||
"required": "必填",
|
||||
|
||||
@@ -9,7 +9,7 @@ export class List {
|
||||
avatar?: string;
|
||||
trails?: string[];
|
||||
expand?: {
|
||||
trails: Trail[]
|
||||
trails?: Trail[]
|
||||
list_share_via_list?: ListShare[]
|
||||
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import { writable, type Writable } from "svelte/store";
|
||||
import { fetchGPX } from "./trail_store";
|
||||
|
||||
export const lists: Writable<List[]> = writable([])
|
||||
export const list: Writable<List> = writable(new List("", []))
|
||||
|
||||
export const list: Writable<List | null> = writable(null)
|
||||
export const listTrail: Writable<Trail | null> = writable(null);
|
||||
|
||||
export async function lists_index(filter?: ListFilter, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||
const r = await f('/api/v1/list?' + new URLSearchParams({
|
||||
@@ -26,7 +26,7 @@ export async function lists_index(filter?: ListFilter, f: (url: RequestInfo | UR
|
||||
lists.set(fetchedLists);
|
||||
|
||||
if (fetchedLists.length > 0) {
|
||||
list.set(fetchedLists[0])
|
||||
// list.set(fetchedLists[0])
|
||||
}
|
||||
|
||||
return fetchedLists;
|
||||
@@ -39,7 +39,7 @@ export async function lists_show(id: string, f: (url: RequestInfo | URL, config?
|
||||
})
|
||||
const response = await r.json()
|
||||
|
||||
for (const trail of response.expand.trails) {
|
||||
for (const trail of response.expand?.trails ?? []) {
|
||||
const gpxData: string = await fetchGPX(trail);
|
||||
trail.expand.gpx_data = gpxData;
|
||||
}
|
||||
|
||||
@@ -140,40 +140,62 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
|
||||
|
||||
},
|
||||
|
||||
select(index) {
|
||||
if (index > this._routes.length - 1) {
|
||||
return
|
||||
highlightTrack(id) {
|
||||
const route = this._routes.find(r => r.options.id == id)
|
||||
if (!route) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setSelection(this._routes[index])
|
||||
route.eachLayer((l) => {
|
||||
this.highlight(route, l)
|
||||
})
|
||||
},
|
||||
|
||||
openPopup(index) {
|
||||
if (index > this._routes.length - 1) {
|
||||
return
|
||||
unHighlightTrack(id) {
|
||||
const route = this._routes.find(r => r.options.id == id)
|
||||
if (!route) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._routes[index].openPopup();
|
||||
route.eachLayer((l) => {
|
||||
this.unhighlight(route, l)
|
||||
})
|
||||
},
|
||||
|
||||
closePopup(index) {
|
||||
if (index > this._routes.length - 1) {
|
||||
return
|
||||
select(id) {
|
||||
const route = this._routes.find(r => r.options.id == id)
|
||||
if (!route) {
|
||||
return;
|
||||
}
|
||||
this.setSelection(route)
|
||||
},
|
||||
|
||||
this._routes[index].closePopup();
|
||||
resetSelection() {
|
||||
this.setSelection(this._selected)
|
||||
if (this.options.flyToBounds) {
|
||||
this._map.flyToBounds(this.getBounds(), { duration: 0.25, easeLinearity: 0.25, noMoveStart: true });
|
||||
}
|
||||
},
|
||||
|
||||
openPopup(id) {
|
||||
const route = this._routes.find(r => r.options.id == id)
|
||||
if (!route) {
|
||||
return;
|
||||
}
|
||||
route.openPopup();
|
||||
},
|
||||
|
||||
closePopup(id) {
|
||||
const route = this._routes.find(r => r.options.id == id)
|
||||
if (!route) {
|
||||
return;
|
||||
}
|
||||
route.closePopup();
|
||||
},
|
||||
|
||||
_addTrack: function (track) {
|
||||
if (track instanceof Object) {
|
||||
this._loadGeoJSON(track);
|
||||
} else if (track !== undefined) {
|
||||
this._elevation._parseFromString(track)
|
||||
.then(geojson => this._loadGeoJSON(geojson, this._hashCode(track), track.split('/').pop().split('#')[0].split('?')[0]))
|
||||
}
|
||||
},
|
||||
this._elevation._parseFromString(track.gpx)
|
||||
.then(geojson => this._loadGeoJSON(geojson, track.id, track.gpx.split('/').pop().split('#')[0].split('?')[0]))
|
||||
|
||||
_hashCode: (s) => s.split('').reduce((a, b) => (((a << 5) - a) + b.charCodeAt(0)) | 0, 0),
|
||||
},
|
||||
|
||||
clear: function () {
|
||||
this._elevation.clear()
|
||||
@@ -197,9 +219,9 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
|
||||
}
|
||||
},
|
||||
|
||||
_loadGeoJSON: function (geojson, hash, fallbackName) {
|
||||
_loadGeoJSON: function (geojson, id, fallbackName) {
|
||||
if (geojson) {
|
||||
geojson.hash = hash
|
||||
geojson.id = id
|
||||
geojson.name = geojson.name || (geojson[0] && geojson[0].properties.name) || fallbackName;
|
||||
this._loadRoute(geojson);
|
||||
}
|
||||
@@ -208,7 +230,7 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
|
||||
_loadRoute: function (data) {
|
||||
if (!data) return;
|
||||
var line_style = {
|
||||
color: this._hashToColor(data.hash),
|
||||
color: this._stringToColor(data.id),
|
||||
opacity: 0.75,
|
||||
weight: 5,
|
||||
distanceMarkers: this.options.distanceMarkers_options,
|
||||
@@ -220,7 +242,7 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
|
||||
distanceMarkers: line_style.distanceMarkers,
|
||||
originalStyle: line_style,
|
||||
isGroupLayer: true,
|
||||
hash: data.hash,
|
||||
id: data.id,
|
||||
filter: feature => feature.geometry.type != "Point",
|
||||
});
|
||||
|
||||
@@ -351,23 +373,20 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
|
||||
}
|
||||
},
|
||||
|
||||
_hashToColor(hash) {
|
||||
// Ensure hash is a positive integer
|
||||
hash = Math.abs(hash);
|
||||
|
||||
_stringToColor(input) {
|
||||
// Define the maximum brightness to ensure the color is not too light
|
||||
const maxBrightness = 250;
|
||||
const maxBrightness = 200;
|
||||
|
||||
// Convert the hash to a color in a reduced RGB range to prevent light and green colors
|
||||
const r = (hash >> 16) & 0xFF; // Extract the red component
|
||||
const g = (hash >> 8) & 0xFF; // Extract the green component
|
||||
const b = hash & 0xFF; // Extract the blue component
|
||||
// Divide the string into 3 parts (5 characters each)
|
||||
const redPart = input.slice(0, 5);
|
||||
const greenPart = input.slice(5, 10);
|
||||
const bluePart = input.slice(10, 15);
|
||||
|
||||
// Adjust the green value to avoid greenish colors
|
||||
// Reducing the green component significantly to avoid strong green shades
|
||||
const green = Math.floor((g / 255) * (maxBrightness * 0.5)); // Reduce green component range
|
||||
const red = Math.floor((r / 255) * maxBrightness);
|
||||
const blue = Math.floor((b / 255) * maxBrightness);
|
||||
// Calculate the red, green, and blue values by summing the char codes of the parts
|
||||
const red = Math.floor((redPart.split('').reduce((sum, char) => sum + char.charCodeAt(0), 0) % 256) * (maxBrightness / 255));
|
||||
const greenRaw = greenPart.split('').reduce((sum, char) => sum + char.charCodeAt(0), 0) % 256;
|
||||
const green = Math.floor((greenRaw * 0.5) * (maxBrightness / 255)); // Reduce green to avoid greenish colors
|
||||
const blue = Math.floor((bluePart.split('').reduce((sum, char) => sum + char.charCodeAt(0), 0) % 256) * (maxBrightness / 255));
|
||||
|
||||
// Format as HEX color
|
||||
return `#${(1 << 24 | red << 16 | green << 8 | blue).toString(16).slice(1).toUpperCase()}`;
|
||||
|
||||
Reference in New Issue
Block a user