Trail Table: Allow multi select (#264)
* trail table: multi select * trail cards: multi select * multiselect for list view, several multiselect fixes, code beautify * remove unnecessary imports, move code to simplify diff of trail_dropdown * translations * fix horizontal scrollbar in table view * direct export from list, multiselect export * fix refreshing share icon after sharing from trail list * fix adding multiple trails to a trail-list * fix retrieving mail notification template * migrates to v0.17.0 --------- Co-authored-by: Christian Beutel <>
This commit is contained in:
@@ -59,7 +59,7 @@ func GenerateHTML(appUrl string, recipientName string, authorName string, notifi
|
|||||||
}
|
}
|
||||||
|
|
||||||
html, err := registry.LoadFiles(
|
html, err := registry.LoadFiles(
|
||||||
"templates/mail/notification.html",
|
"db/templates/mail/notification.html",
|
||||||
).Render(content)
|
).Render(content)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
try {
|
try {
|
||||||
const actors: Actor[] = await searchActors(q, includeSelf);
|
const actors: Actor[] = await searchActors(q, includeSelf);
|
||||||
searchItems = actors.map((a) => ({
|
searchItems = actors.map((a) => ({
|
||||||
text: "a.username!",
|
text: a.username,
|
||||||
description: `@${a.preferred_username}${a.isLocal ? "" : "@" + a.domain}`,
|
description: `@${a.preferred_username}${a.isLocal ? "" : "@" + a.domain}`,
|
||||||
value: a,
|
value: a,
|
||||||
icon: "user",
|
icon: "user",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { type Snippet } from "svelte";
|
import { type Snippet } from "svelte";
|
||||||
|
|
||||||
import type { List } from "$lib/models/list";
|
import type { List } from "$lib/models/list";
|
||||||
|
import type { Trail } from "$lib/models/trail";
|
||||||
import { trail } from "$lib/stores/trail_store";
|
import { trail } from "$lib/stores/trail_store";
|
||||||
import { getFileURL } from "$lib/util/file_util";
|
import { getFileURL } from "$lib/util/file_util";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
@@ -12,11 +13,12 @@
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
lists: List[];
|
lists: List[];
|
||||||
|
trails?: Set<Trail> | undefined;
|
||||||
children?: Snippet<[any]>;
|
children?: Snippet<[any]>;
|
||||||
onchange?: (list: List) => void
|
onchange?: (list: List) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
let { lists, children, onchange }: Props = $props();
|
let { lists, trails, children, onchange }: Props = $props();
|
||||||
|
|
||||||
let modal: Modal;
|
let modal: Modal;
|
||||||
|
|
||||||
@@ -29,6 +31,20 @@
|
|||||||
modal.closeModal!();
|
modal.closeModal!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function listContainsAllTrails(list: List) : boolean {
|
||||||
|
if (trails === undefined) {
|
||||||
|
return listContainsCurrentTrail(list) ?? false;
|
||||||
|
} else if (list.trails !== undefined) {
|
||||||
|
for (const lTrail of trails) {
|
||||||
|
if (!list.trails!.includes(lTrail.id!)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function listContainsCurrentTrail(list: List) {
|
function listContainsCurrentTrail(list: List) {
|
||||||
return list.trails?.includes($trail.id!);
|
return list.trails?.includes($trail.id!);
|
||||||
}
|
}
|
||||||
@@ -66,7 +82,7 @@
|
|||||||
<h5 class="text-md font-semibold">{list.name}</h5>
|
<h5 class="text-md font-semibold">{list.name}</h5>
|
||||||
|
|
||||||
<i
|
<i
|
||||||
class="fa fa-{listContainsCurrentTrail(list)
|
class="fa fa-{listContainsAllTrails(list)
|
||||||
? 'minus'
|
? 'minus'
|
||||||
: 'plus'} rounded-full border border-input-border p-2"
|
: 'plus'} rounded-full border border-input-border p-2"
|
||||||
></i>
|
></i>
|
||||||
|
|||||||
@@ -18,15 +18,21 @@
|
|||||||
interface Props {
|
interface Props {
|
||||||
trail: Trail;
|
trail: Trail;
|
||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
|
selected: boolean;
|
||||||
|
hovered: boolean;
|
||||||
onmouseenter?: MouseEventHandler<HTMLDivElement>;
|
onmouseenter?: MouseEventHandler<HTMLDivElement>;
|
||||||
onmouseleave?: MouseEventHandler<HTMLDivElement>;
|
onmouseleave?: MouseEventHandler<HTMLDivElement>;
|
||||||
|
onTrailSelect?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
trail,
|
trail,
|
||||||
fullWidth = false,
|
fullWidth = false,
|
||||||
|
selected = false,
|
||||||
|
hovered = false,
|
||||||
onmouseenter,
|
onmouseenter,
|
||||||
onmouseleave,
|
onmouseleave,
|
||||||
|
onTrailSelect,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
let thumbnail = $derived(
|
let thumbnail = $derived(
|
||||||
@@ -45,6 +51,11 @@
|
|||||||
(trail.expand?.trail_share_via_trail?.length ?? 0) > 0,
|
(trail.expand?.trail_share_via_trail?.length ?? 0) > 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function handleInputClick(e: Event) {
|
||||||
|
e.stopPropagation();
|
||||||
|
onTrailSelect?.();
|
||||||
|
hovered = true;
|
||||||
|
}
|
||||||
// expand and collapse the tags
|
// expand and collapse the tags
|
||||||
let expandedTags = $state(false);
|
let expandedTags = $state(false);
|
||||||
|
|
||||||
@@ -85,18 +96,17 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if $currentUser && trail.like_count > 0}
|
{#if hovered || selected}
|
||||||
<div
|
<div
|
||||||
class="flex absolute items-center justify-center top-4 left-4 bg-background w-8 h-8 rounded-full"
|
class="flex absolute top-4 left-4 w-8 h-8 rounded-full items-center justify-center bg-background text-content"
|
||||||
>
|
>
|
||||||
<span class="tooltip" data-title={$_("likes")}>
|
<input
|
||||||
<i class="fa fa-heart"></i>
|
id="trail-selected"
|
||||||
</span>
|
type="checkbox"
|
||||||
<div
|
class="w-4 h-4 bg-input-background accent-primary border-input-border focus:ring-input-ring focus:ring-2"
|
||||||
class="absolute pointer-events-none left-5 -top-1 text-xs rounded-full bg-menu-background px-1 text-center"
|
bind:checked={selected}
|
||||||
>
|
onclick={(e) => handleInputClick(e)}
|
||||||
{trail.like_count}
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if (trail.public || trailIsShared) && $currentUser}
|
{#if (trail.public || trailIsShared) && $currentUser}
|
||||||
@@ -121,6 +131,20 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $currentUser && trail.like_count > 0}
|
||||||
|
<div
|
||||||
|
class="flex absolute items-center justify-center {trailIsShared || trail.public ? 'top-14': 'top-4'} right-4 bg-background w-8 h-8 rounded-full"
|
||||||
|
>
|
||||||
|
<span class="tooltip" data-title={$_("likes")}>
|
||||||
|
<i class="fa fa-heart"></i>
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="absolute pointer-events-none left-5 -top-1 text-xs rounded-full bg-menu-background px-1 text-center"
|
||||||
|
>
|
||||||
|
{trail.like_count}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div>
|
<div>
|
||||||
<h4 class="font-semibold text-lg line-clamp-2">{trail.name}</h4>
|
<h4 class="font-semibold text-lg line-clamp-2">{trail.name}</h4>
|
||||||
@@ -162,9 +186,9 @@
|
|||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{#if expandedTags}
|
{#if expandedTags}
|
||||||
{$_('show-less')}
|
{$_("show-less")}
|
||||||
{:else}
|
{:else}
|
||||||
+{trail.tags.length - 2} {$_('more')}
|
+{trail.tags.length - 2} {$_("more")}
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -20,14 +20,15 @@
|
|||||||
import ListSelectModal from "../list/list_select_modal.svelte";
|
import ListSelectModal from "../list/list_select_modal.svelte";
|
||||||
import TrailExportModal from "./trail_export_modal.svelte";
|
import TrailExportModal from "./trail_export_modal.svelte";
|
||||||
import TrailShareModal from "./trail_share_modal.svelte";
|
import TrailShareModal from "./trail_share_modal.svelte";
|
||||||
|
import { handleFromRecordWithIRI } from "$lib/util/activitypub_util";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
trail: Trail;
|
trails?: Set<Trail> | undefined;
|
||||||
handle: string;
|
mode: "overview" | "map" | "list" | "multi-select";
|
||||||
mode: "overview" | "map" | "list";
|
onconfirm?: (resetSelection?: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { trail, handle, mode }: Props = $props();
|
let { trails, mode, onconfirm }: Props = $props();
|
||||||
|
|
||||||
let confirmModal: ConfirmModal;
|
let confirmModal: ConfirmModal;
|
||||||
let listSelectModal: ListSelectModal;
|
let listSelectModal: ListSelectModal;
|
||||||
@@ -36,25 +37,38 @@
|
|||||||
|
|
||||||
let lists: List[] = $state([]);
|
let lists: List[] = $state([]);
|
||||||
|
|
||||||
const isOwned: boolean = trail.author == $currentUser?.actor;
|
function allowEdit(): boolean {
|
||||||
|
return (
|
||||||
const allowEdit =
|
hasTrail() &&
|
||||||
isOwned ||
|
!isMultiselectMode() &&
|
||||||
trail.expand?.trail_share_via_trail?.some(
|
(trail()!.expand?.author?.id === $currentUser?.actor ||
|
||||||
|
trail()!.expand?.trail_share_via_trail?.some(
|
||||||
(s) => s.permission == "edit",
|
(s) => s.permission == "edit",
|
||||||
|
))!
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const dropdownItems: DropdownItem[] = [
|
function dropdownItems(): DropdownItem[] {
|
||||||
mode == "overview"
|
return [
|
||||||
? { text: $_("show-on-map"), value: "show", icon: "map" }
|
...(!isMultiselectMode()
|
||||||
|
? [
|
||||||
|
mode == "overview" || mode == "multi-select"
|
||||||
|
? {
|
||||||
|
text: $_("show-on-map"),
|
||||||
|
value: "show",
|
||||||
|
icon: "map",
|
||||||
|
}
|
||||||
: {
|
: {
|
||||||
text: $_("show-in-overview"),
|
text: $_("show-in-overview"),
|
||||||
value: "show",
|
value: "show",
|
||||||
icon: "table-columns",
|
icon: "table-columns",
|
||||||
},
|
},
|
||||||
|
]
|
||||||
{ text: $_("directions"), value: "direction", icon: "car" },
|
: []),
|
||||||
...(trail.gpx
|
...(!isMultiselectMode()
|
||||||
|
? [{ text: $_("directions"), value: "direction", icon: "car" }]
|
||||||
|
: []),
|
||||||
|
...(canExport()
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
text: $_("export"),
|
text: $_("export"),
|
||||||
@@ -63,28 +77,105 @@
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
{ text: $_("print"), value: "print", icon: "print" },
|
...(!isMultiselectMode()
|
||||||
...(isOwned
|
? [{ text: $_("print"), value: "print", icon: "print" }]
|
||||||
? [{ text: $_("add-to-list"), value: "list", icon: "bookmark" }]
|
|
||||||
: []),
|
: []),
|
||||||
...(isOwned
|
...(!isFromCurrentUser()
|
||||||
? [{ text: $_("share"), value: "share", icon: "share" }]
|
? []
|
||||||
: []),
|
: [
|
||||||
...(allowEdit
|
{
|
||||||
|
text: $_("add-to-list"),
|
||||||
|
value: "list",
|
||||||
|
icon: "bookmark",
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
...(isMultiselectMode() || !isFromCurrentUser()
|
||||||
|
? []
|
||||||
|
: [{ text: $_("share"), value: "share", icon: "share" }]),
|
||||||
|
...(allowEdit()
|
||||||
? [{ text: $_("edit"), value: "edit", icon: "pen" }]
|
? [{ text: $_("edit"), value: "edit", icon: "pen" }]
|
||||||
: []),
|
: []),
|
||||||
...(isOwned
|
...(allowDelete()
|
||||||
? [{ text: $_("delete"), value: "delete", icon: "trash" }]
|
? [{ text: $_("delete"), value: "delete", icon: "trash" }]
|
||||||
: []),
|
: []),
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMultiselectMode(): boolean {
|
||||||
|
return trails !== undefined && trails.size > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasTrail(): boolean {
|
||||||
|
return (
|
||||||
|
trails !== undefined &&
|
||||||
|
trails.size > 0 &&
|
||||||
|
[...trails][0] !== undefined
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasGpx(): boolean {
|
||||||
|
if (!hasTrail()) return false;
|
||||||
|
|
||||||
|
for (const gTrail of trails!) {
|
||||||
|
if (gTrail.gpx) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function canExport(): boolean {
|
||||||
|
return hasGpx();
|
||||||
|
}
|
||||||
|
|
||||||
|
function trailId(): string | undefined {
|
||||||
|
return trail()?.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTrails(): Set<Trail> | undefined {
|
||||||
|
return trails;
|
||||||
|
}
|
||||||
|
|
||||||
|
function trail(): Trail | undefined {
|
||||||
|
return hasTrail() ? [...trails!][0] : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFromCurrentUser(uTrail?: Trail): boolean {
|
||||||
|
if (uTrail !== undefined) {
|
||||||
|
return uTrail.expand?.author?.id === $currentUser?.actor;
|
||||||
|
} else if (trails !== undefined && trails.size > 0) {
|
||||||
|
for (const sTrail of trails) {
|
||||||
|
if (sTrail.expand?.author?.id === $currentUser?.actor) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function allowDelete(): boolean {
|
||||||
|
return isFromCurrentUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
function allowDeleteTrail(dTrail?: Trail): boolean {
|
||||||
|
return isFromCurrentUser(dTrail);
|
||||||
|
}
|
||||||
|
|
||||||
async function handleDropdownClick(item: { text: string; value: any }) {
|
async function handleDropdownClick(item: { text: string; value: any }) {
|
||||||
|
if (!trail()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handle = handleFromRecordWithIRI(trail());
|
||||||
|
|
||||||
if (item.value == "show") {
|
if (item.value == "show") {
|
||||||
|
if (hasTrail()) {
|
||||||
goto(
|
goto(
|
||||||
mode == "overview"
|
mode == "overview" || mode == "multi-select"
|
||||||
? `/map/trail/${handle}/${trail.id!}`
|
? `/map/trail/${handle}/${trailId()}`
|
||||||
: `/trail/view/${handle}/${trail.id!}`,
|
: `/trail/view/${handle}/${trailId()}`,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else if (item.value == "list") {
|
} else if (item.value == "list") {
|
||||||
lists = (
|
lists = (
|
||||||
await lists_index(
|
await lists_index(
|
||||||
@@ -95,32 +186,54 @@
|
|||||||
).items;
|
).items;
|
||||||
listSelectModal.openModal();
|
listSelectModal.openModal();
|
||||||
} else if (item.value == "direction") {
|
} else if (item.value == "direction") {
|
||||||
|
if (hasTrail()) {
|
||||||
window
|
window
|
||||||
.open(
|
.open(
|
||||||
`https://www.google.com/maps/dir/Current+Location/${trail.lat},${trail.lon}`,
|
`https://www.google.com/maps/dir/Current+Location/${trail()!.lat},${trail()!.lon}`,
|
||||||
"_blank",
|
"_blank",
|
||||||
)
|
)
|
||||||
?.focus();
|
?.focus();
|
||||||
|
}
|
||||||
} else if (item.value == "print") {
|
} else if (item.value == "print") {
|
||||||
goto(`/map/trail/${handle}/${trail.id}/print`);
|
if (hasTrail()) {
|
||||||
|
goto(`/map/trail/${handle}/${trailId()}/print`);
|
||||||
|
}
|
||||||
} else if (item.value == "share") {
|
} else if (item.value == "share") {
|
||||||
trailShareModal.openModal();
|
trailShareModal.openModal();
|
||||||
} else if (item.value == "download") {
|
} else if (item.value == "download") {
|
||||||
trailExportModal.openModal();
|
trailExportModal.openModal();
|
||||||
} else if (item.value == "edit") {
|
} else if (item.value == "edit") {
|
||||||
goto(`/trail/edit/${trail.id}`);
|
if (hasTrail()) {
|
||||||
|
goto(`/trail/edit/${trailId()}`);
|
||||||
|
}
|
||||||
} else if (item.value == "delete") {
|
} else if (item.value == "delete") {
|
||||||
confirmModal.openModal();
|
confirmModal.openModal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function exportTrail(exportSettings: {
|
async function exportTrails(exportSettings: {
|
||||||
fileFormat: "gpx" | "json";
|
fileFormat: "gpx" | "json";
|
||||||
photos: boolean;
|
photos: boolean;
|
||||||
summitLog: boolean;
|
summitLog: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
if (trails !== undefined && trails.size > 0) {
|
||||||
|
for (const cTrail of trails) {
|
||||||
|
await doExportTrail(exportSettings, cTrail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doExportTrail(
|
||||||
|
exportSettings: {
|
||||||
|
fileFormat: "gpx" | "json";
|
||||||
|
photos: boolean;
|
||||||
|
summitLog: boolean;
|
||||||
|
},
|
||||||
|
eTrail: Trail,
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
let fileData: string = await trail2gpx(trail, $currentUser);
|
if (eTrail !== undefined) {
|
||||||
|
let fileData: string = await trail2gpx(eTrail, $currentUser);
|
||||||
if (exportSettings.fileFormat == "json") {
|
if (exportSettings.fileFormat == "json") {
|
||||||
fileData = JSON.stringify(
|
fileData = JSON.stringify(
|
||||||
gpx(
|
gpx(
|
||||||
@@ -138,36 +251,40 @@
|
|||||||
? "application/json"
|
? "application/json"
|
||||||
: "application/gpx+xml",
|
: "application/gpx+xml",
|
||||||
});
|
});
|
||||||
saveAs(blob, `${trail.name}.${exportSettings.fileFormat}`);
|
saveAs(blob, `${eTrail.name}.${exportSettings.fileFormat}`);
|
||||||
} else {
|
} else {
|
||||||
const zip = new JSZip();
|
const zip = new JSZip();
|
||||||
zip.file(
|
zip.file(
|
||||||
`${trail.name}.${exportSettings.fileFormat}`,
|
`${eTrail.name}.${exportSettings.fileFormat}`,
|
||||||
fileData,
|
fileData,
|
||||||
);
|
);
|
||||||
if (exportSettings.photos) {
|
if (exportSettings.photos) {
|
||||||
const photoFolder = zip.folder($_("photos"));
|
const photoFolder = zip.folder($_("photos"));
|
||||||
for (const photo of trail.photos) {
|
for (const photo of eTrail.photos) {
|
||||||
const photoURL = getFileURL(trail, photo);
|
const photoURL = getFileURL(eTrail, photo);
|
||||||
const photoBlob = await fetch(photoURL).then(
|
const photoBlob = await fetch(photoURL).then(
|
||||||
(response) => response.blob(),
|
(response) => response.blob(),
|
||||||
);
|
);
|
||||||
const photoData = new File([photoBlob], photo);
|
const photoData = new File([photoBlob], photo);
|
||||||
photoFolder?.file(photo, photoData, { base64: true });
|
photoFolder?.file(photo, photoData, {
|
||||||
|
base64: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (exportSettings.summitLog) {
|
if (exportSettings.summitLog) {
|
||||||
let summitLogString = "";
|
let summitLogString = "";
|
||||||
for (const summitLog of trail.expand?.summit_logs_via_trail ?? []) {
|
for (const summitLog of eTrail.expand
|
||||||
|
?.summit_logs_via_trail ?? []) {
|
||||||
summitLogString += `${summitLog.date},${summitLog.text}\n`;
|
summitLogString += `${summitLog.date},${summitLog.text}\n`;
|
||||||
}
|
}
|
||||||
zip.file(
|
zip.file(
|
||||||
`${trail.name} - ${$_("summit-book")}.csv`,
|
`${eTrail.name} - ${$_("summit-book")}.csv`,
|
||||||
summitLogString,
|
summitLogString,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const blob = await zip.generateAsync({ type: "blob" });
|
const blob = await zip.generateAsync({ type: "blob" });
|
||||||
saveAs(blob, `${trail.name}.zip`);
|
saveAs(blob, `${eTrail.name}.zip`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -179,28 +296,57 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteTrail() {
|
async function deleteTrails() {
|
||||||
await trails_delete(trail);
|
if (hasTrail()) {
|
||||||
setTimeout(() => {
|
for (const dTrail of trails!) {
|
||||||
goto("/trails");
|
await doDeleteTrail(dTrail);
|
||||||
}, 500);
|
}
|
||||||
|
|
||||||
|
onconfirm?.(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doDeleteTrail(dTrail: Trail) {
|
||||||
|
if (dTrail === undefined) return;
|
||||||
|
|
||||||
|
if (!allowDeleteTrail(dTrail)) return;
|
||||||
|
|
||||||
|
await trails_delete(dTrail);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleShareUpdate() {
|
||||||
|
onconfirm?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleListSelection(list: List) {
|
async function handleListSelection(list: List) {
|
||||||
try {
|
try {
|
||||||
if (list.trails?.includes(trail.id!)) {
|
let deleted = false;
|
||||||
await lists_remove_trail(list, trail);
|
let multiple = false;
|
||||||
|
|
||||||
|
if (hasTrail()) {
|
||||||
|
multiple = true;
|
||||||
|
for (const lTrail of trails!) {
|
||||||
|
if (await doHandleListSelection(list, lTrail)) {
|
||||||
|
deleted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleted) {
|
||||||
show_toast({
|
show_toast({
|
||||||
type: "success",
|
type: "success",
|
||||||
icon: "check",
|
icon: "check",
|
||||||
text: `${$_("removed-trail-from")} "${list.name}"`,
|
text: multiple
|
||||||
|
? `${$_("removed-trails-from")} "${list.name}"`
|
||||||
|
: `${$_("removed-trail-from")} "${list.name}"`,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await lists_add_trail(list, trail);
|
|
||||||
show_toast({
|
show_toast({
|
||||||
type: "success",
|
type: "success",
|
||||||
icon: "check",
|
icon: "check",
|
||||||
text: `${$_("added-trail-to")} "${list.name}"`,
|
text: multiple
|
||||||
|
? `${$_("added-trails-to")} "${list.name}"`
|
||||||
|
: `${$_("added-trail-to")} "${list.name}"`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -213,10 +359,51 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function doHandleListSelection(
|
||||||
|
list: List,
|
||||||
|
lTrail: Trail,
|
||||||
|
): Promise<boolean> {
|
||||||
|
if (list.trails?.includes(lTrail.id!)) {
|
||||||
|
if (listContainsAllTrails(list)) {
|
||||||
|
await lists_remove_trail(list, lTrail);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await lists_add_trail(list, lTrail);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function listContainsAllTrails(list: List): boolean {
|
||||||
|
if (trails === undefined) {
|
||||||
|
return false;
|
||||||
|
} else if (list.trails !== undefined) {
|
||||||
|
for (const lTrail of trails) {
|
||||||
|
if (!list.trails!.includes(lTrail.id!)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Dropdown items={dropdownItems} onchange={(item) => handleDropdownClick(item)}
|
<Dropdown items={dropdownItems()} onchange={(item) => handleDropdownClick(item)}
|
||||||
>{#snippet children({ toggleMenu: openDropdown })}
|
>{#snippet children({ toggleMenu: openDropdown })}
|
||||||
|
{#if mode == "multi-select"}
|
||||||
|
<button
|
||||||
|
aria-label="Open dropdown"
|
||||||
|
class="btn-primary flex-shrink-0 !font-medium"
|
||||||
|
onclick={openDropdown}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
>{trails?.size}
|
||||||
|
{$_("selected")} <i class="fa fa-caret-down ml-1"></i></span
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
{:else}
|
||||||
<button
|
<button
|
||||||
aria-label="Open dropdown"
|
aria-label="Open dropdown"
|
||||||
class=" btn-primary !rounded-full h-12 w-12"
|
class=" btn-primary !rounded-full h-12 w-12"
|
||||||
@@ -224,21 +411,27 @@
|
|||||||
>
|
>
|
||||||
<i class="fa fa-ellipsis-vertical"></i>
|
<i class="fa fa-ellipsis-vertical"></i>
|
||||||
</button>
|
</button>
|
||||||
|
{/if}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
text={$_("delete-trail-confirm")}
|
text={$_("delete-trail-confirm")}
|
||||||
bind:this={confirmModal}
|
bind:this={confirmModal}
|
||||||
onconfirm={deleteTrail}
|
onconfirm={deleteTrails}
|
||||||
></ConfirmModal>
|
></ConfirmModal>
|
||||||
<ListSelectModal
|
<ListSelectModal
|
||||||
{lists}
|
{lists}
|
||||||
|
trails={getTrails()}
|
||||||
bind:this={listSelectModal}
|
bind:this={listSelectModal}
|
||||||
onchange={(list) => handleListSelection(list)}
|
onchange={(list) => handleListSelection(list)}
|
||||||
></ListSelectModal>
|
></ListSelectModal>
|
||||||
<TrailExportModal
|
<TrailExportModal
|
||||||
bind:this={trailExportModal}
|
bind:this={trailExportModal}
|
||||||
onexport={(settings) => exportTrail(settings)}
|
onexport={(settings) => exportTrails(settings)}
|
||||||
></TrailExportModal>
|
></TrailExportModal>
|
||||||
<TrailShareModal {trail} bind:this={trailShareModal}></TrailShareModal>
|
<TrailShareModal
|
||||||
|
trail={trail()}
|
||||||
|
onsave={handleShareUpdate}
|
||||||
|
bind:this={trailShareModal}
|
||||||
|
></TrailShareModal>
|
||||||
|
|||||||
@@ -401,7 +401,10 @@
|
|||||||
{#if ($currentUser && $currentUser.actor == trail.author) || trail.expand?.trail_share_via_trail?.length || trail.public}
|
{#if ($currentUser && $currentUser.actor == trail.author) || trail.expand?.trail_share_via_trail?.length || trail.public}
|
||||||
<div class="flex flex-col items-center gap-y-2">
|
<div class="flex flex-col items-center gap-y-2">
|
||||||
<LikeButton {trail}></LikeButton>
|
<LikeButton {trail}></LikeButton>
|
||||||
<TrailDropdown {trail} {mode} {handle}></TrailDropdown>
|
<TrailDropdown
|
||||||
|
trails={new Set<Trail>([trail])}
|
||||||
|
{mode}
|
||||||
|
></TrailDropdown>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -658,7 +661,7 @@
|
|||||||
elevationProfileContainer={"epc-container"}
|
elevationProfileContainer={"epc-container"}
|
||||||
showStyleSwitcher={false}
|
showStyleSwitcher={false}
|
||||||
showFullscreen={true}
|
showFullscreen={true}
|
||||||
mapOptions={{ attributionControl: {compact: true} }}
|
mapOptions={{ attributionControl: { compact: true } }}
|
||||||
onfullscreen={toggleMapFullScreen}
|
onfullscreen={toggleMapFullScreen}
|
||||||
bind:markers
|
bind:markers
|
||||||
></MapWithElevationMaplibre>
|
></MapWithElevationMaplibre>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
import SkeletonCard from "../base/skeleton_card.svelte";
|
import SkeletonCard from "../base/skeleton_card.svelte";
|
||||||
import SkeletonListItem from "../base/skeleton_list_item.svelte";
|
import SkeletonListItem from "../base/skeleton_list_item.svelte";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
import TrailDropdown from "$lib/components/trail/trail_dropdown.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
filter?: TrailFilter | null;
|
filter?: TrailFilter | null;
|
||||||
@@ -17,7 +18,10 @@
|
|||||||
pagination?: { page: number; totalPages: number };
|
pagination?: { page: number; totalPages: number };
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
fullWidthCards?: boolean;
|
fullWidthCards?: boolean;
|
||||||
onupdate?: (filter: TrailFilter | null) => void;
|
onupdate?: (
|
||||||
|
filter: TrailFilter | null,
|
||||||
|
selection: Set<Trail> | undefined,
|
||||||
|
) => void;
|
||||||
onpagination?: (page: number) => void;
|
onpagination?: (page: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +46,9 @@
|
|||||||
|
|
||||||
let selectedDisplayOption = $state(displayOptions[0].value);
|
let selectedDisplayOption = $state(displayOptions[0].value);
|
||||||
|
|
||||||
|
let selection: Set<Trail> | undefined = $state();
|
||||||
|
let hoveredTrail: Trail | undefined = $state();
|
||||||
|
|
||||||
const sortOptions: SelectItem[] = [
|
const sortOptions: SelectItem[] = [
|
||||||
{ text: $_("name"), value: "name" },
|
{ text: $_("name"), value: "name" },
|
||||||
{ text: $_("distance"), value: "distance" },
|
{ text: $_("distance"), value: "distance" },
|
||||||
@@ -71,7 +78,7 @@
|
|||||||
(storedSortOrder as typeof filter.sortOrder | null) ??
|
(storedSortOrder as typeof filter.sortOrder | null) ??
|
||||||
filter.sortOrder;
|
filter.sortOrder;
|
||||||
}
|
}
|
||||||
onupdate?.(filter);
|
onupdate?.(filter, selection);
|
||||||
});
|
});
|
||||||
|
|
||||||
function setDisplayOption() {
|
function setDisplayOption() {
|
||||||
@@ -83,7 +90,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
localStorage.setItem("sort", filter.sort);
|
localStorage.setItem("sort", filter.sort);
|
||||||
onupdate?.(filter);
|
onupdate?.(filter, selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSortOrder() {
|
function setSortOrder() {
|
||||||
@@ -96,7 +103,7 @@
|
|||||||
filter.sortOrder = "+";
|
filter.sortOrder = "+";
|
||||||
}
|
}
|
||||||
localStorage.setItem("sort_order", filter.sortOrder);
|
localStorage.setItem("sort_order", filter.sortOrder);
|
||||||
onupdate?.(filter);
|
onupdate?.(filter, selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSortUpdate(sort: any) {
|
function handleSortUpdate(sort: any) {
|
||||||
@@ -111,6 +118,96 @@
|
|||||||
setSort();
|
setSort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isHovered(trail: Trail): boolean {
|
||||||
|
if (trail === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hoveredTrail === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hoveredTrail.id === trail.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSelected(trail: Trail): boolean {
|
||||||
|
if (trail === undefined) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
if (selection === undefined) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
for (const sTrail of selection) {
|
||||||
|
if (sTrail !== undefined && sTrail.id === trail.id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelectionUpdate(trail: Trail) {
|
||||||
|
let newSelection = new Set<Trail>();
|
||||||
|
|
||||||
|
if (trail !== undefined) {
|
||||||
|
let isSelected = false;
|
||||||
|
|
||||||
|
if (selection !== undefined && selection.size > 0) {
|
||||||
|
for (const sTrail of selection) {
|
||||||
|
if (sTrail !== undefined && sTrail.id === trail.id) {
|
||||||
|
isSelected = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
newSelection.add(sTrail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSelected) {
|
||||||
|
newSelection.add(trail);
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
selection === undefined ||
|
||||||
|
selection.size === 0 ||
|
||||||
|
(trails !== undefined && selection.size !== trails.length)
|
||||||
|
) {
|
||||||
|
for (const eTrail of trails) {
|
||||||
|
newSelection.add(eTrail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selection = newSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleHoverUpdate(hTrail: Trail) {
|
||||||
|
if (hTrail === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hoveredTrail === undefined) hoveredTrail = hTrail;
|
||||||
|
else hoveredTrail = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTrailsEditDone(resetSelection: boolean = false) {
|
||||||
|
if (resetSelection) {
|
||||||
|
selection?.clear();
|
||||||
|
hoveredTrail = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
onupdate?.(filter, selection);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseEnter(trail: Trail) {
|
||||||
|
handleHoverUpdate(trail);
|
||||||
|
}
|
||||||
|
function handleMouseLeave(trail: Trail) {
|
||||||
|
handleHoverUpdate(trail);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
@@ -122,6 +219,15 @@
|
|||||||
{onpagination}
|
{onpagination}
|
||||||
></Pagination>
|
></Pagination>
|
||||||
</div>
|
</div>
|
||||||
|
{#if selection !== undefined && selection.size > 0}
|
||||||
|
<div class="flex relative flex-shrink-0">
|
||||||
|
<TrailDropdown
|
||||||
|
trails={selection}
|
||||||
|
mode={"multi-select"}
|
||||||
|
onconfirm={handleTrailsEditDone}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{#if filter}
|
{#if filter}
|
||||||
<div class="shrink-0">
|
<div class="shrink-0">
|
||||||
{#if selectedDisplayOption !== "table"}
|
{#if selectedDisplayOption !== "table"}
|
||||||
@@ -158,7 +264,10 @@
|
|||||||
<div id="trails" class="flex items-start flex-wrap gap-8 py-8 max-w-full">
|
<div id="trails" class="flex items-start flex-wrap gap-8 py-8 max-w-full">
|
||||||
{#if loading}
|
{#if loading}
|
||||||
{#if selectedDisplayOption === "table"}
|
{#if selectedDisplayOption === "table"}
|
||||||
<TrailTable trails={null} tableHeader={sortOptions}
|
<TrailTable
|
||||||
|
trails={null}
|
||||||
|
selection={new Set<Trail>()}
|
||||||
|
tableHeader={sortOptions}
|
||||||
></TrailTable>
|
></TrailTable>
|
||||||
{:else}
|
{:else}
|
||||||
{#each { length: 12 } as _, index}
|
{#each { length: 12 } as _, index}
|
||||||
@@ -180,11 +289,13 @@
|
|||||||
{#if selectedDisplayOption === "table"}
|
{#if selectedDisplayOption === "table"}
|
||||||
<TrailTable
|
<TrailTable
|
||||||
{trails}
|
{trails}
|
||||||
|
{selection}
|
||||||
tableHeader={sortOptions.filter(
|
tableHeader={sortOptions.filter(
|
||||||
(option) => option.value !== "elevation_loss",
|
(option) => option.value !== "elevation_loss",
|
||||||
)}
|
)}
|
||||||
{filter}
|
{filter}
|
||||||
onsort={handleSortUpdate}
|
onsort={handleSortUpdate}
|
||||||
|
onTrailSelect={(t) => handleSelectionUpdate(t)}
|
||||||
></TrailTable>
|
></TrailTable>
|
||||||
{:else}
|
{:else}
|
||||||
{#each trails as trail}
|
{#each trails as trail}
|
||||||
@@ -194,12 +305,26 @@
|
|||||||
href="/trail/view/@{trail.author}{trail.domain
|
href="/trail/view/@{trail.author}{trail.domain
|
||||||
? `@${trail.domain}`
|
? `@${trail.domain}`
|
||||||
: ''}/{trail.id}"
|
: ''}/{trail.id}"
|
||||||
|
onmouseenter={(e) => handleMouseEnter(trail)}
|
||||||
|
onmouseleave={(e) => handleMouseLeave(trail)}
|
||||||
>
|
>
|
||||||
{#if selectedDisplayOption === "cards"}
|
{#if selectedDisplayOption === "cards"}
|
||||||
<TrailCard fullWidth={fullWidthCards} {trail}
|
<TrailCard
|
||||||
|
fullWidth={fullWidthCards}
|
||||||
|
{trail}
|
||||||
|
selected={isSelected(trail)}
|
||||||
|
hovered={isHovered(trail)}
|
||||||
|
onTrailSelect={() =>
|
||||||
|
handleSelectionUpdate(trail)}
|
||||||
></TrailCard>
|
></TrailCard>
|
||||||
{:else}
|
{:else}
|
||||||
<TrailListItem {trail}></TrailListItem>
|
<TrailListItem
|
||||||
|
{trail}
|
||||||
|
selected={isSelected(trail)}
|
||||||
|
hovered={isHovered(trail)}
|
||||||
|
onTrailSelect={() =>
|
||||||
|
handleSelectionUpdate(trail)}
|
||||||
|
></TrailListItem>
|
||||||
{/if}
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -19,9 +19,18 @@
|
|||||||
interface Props {
|
interface Props {
|
||||||
trail: Trail;
|
trail: Trail;
|
||||||
showDescription?: boolean;
|
showDescription?: boolean;
|
||||||
|
selected: boolean;
|
||||||
|
hovered: boolean;
|
||||||
|
onTrailSelect?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { trail, showDescription = true }: Props = $props();
|
let {
|
||||||
|
trail,
|
||||||
|
showDescription = true,
|
||||||
|
selected = false,
|
||||||
|
hovered = false,
|
||||||
|
onTrailSelect,
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
let thumbnail = $derived(
|
let thumbnail = $derived(
|
||||||
trail.photos.length
|
trail.photos.length
|
||||||
@@ -35,6 +44,12 @@
|
|||||||
: emptyStateTrailDark,
|
: emptyStateTrailDark,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function handleInputClick(e: Event) {
|
||||||
|
e.stopPropagation();
|
||||||
|
onTrailSelect?.();
|
||||||
|
hovered = true;
|
||||||
|
}
|
||||||
|
|
||||||
let expandedTags = $state(false);
|
let expandedTags = $state(false);
|
||||||
|
|
||||||
function toggleExpandTags(e: MouseEvent) {
|
function toggleExpandTags(e: MouseEvent) {
|
||||||
@@ -64,7 +79,7 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="min-w-0 basis-full">
|
<div class="min-w-0 basis-full relative">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h4 class="font-semibold text-lg">
|
<h4 class="font-semibold text-lg">
|
||||||
{trail.name}
|
{trail.name}
|
||||||
@@ -139,9 +154,6 @@
|
|||||||
{#if trail.location}
|
{#if trail.location}
|
||||||
<h5><i class="fa fa-location-dot mr-3"></i>{trail.location}</h5>
|
<h5><i class="fa fa-location-dot mr-3"></i>{trail.location}</h5>
|
||||||
{/if}
|
{/if}
|
||||||
<h5>
|
|
||||||
<i class="fa fa-gauge mr-3"></i>{$_(trail.difficulty ?? "?")}
|
|
||||||
</h5>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap mt-1 gap-x-4 gap-y-2 text-sm text-gray-500">
|
<div class="flex flex-wrap mt-1 gap-x-4 gap-y-2 text-sm text-gray-500">
|
||||||
@@ -168,10 +180,23 @@
|
|||||||
</div>
|
</div>
|
||||||
{#if showDescription}
|
{#if showDescription}
|
||||||
<p
|
<p
|
||||||
class="mt-3 text-sm whitespace-nowrap min-w-0 max-w-full overflow-hidden text-ellipsis"
|
class="mt-3 text-sm whitespace-nowrap min-w-0 max-w-full overflow-hidden text-ellipsis basis-full"
|
||||||
>
|
>
|
||||||
{formatHTMLAsText(trail.description ?? "")}
|
{formatHTMLAsText(trail.description ?? "")}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if hovered || selected}
|
||||||
|
<div
|
||||||
|
class="flex absolute bottom-0 right-0 w-8 h-8 rounded-full items-center justify-center bg-background text-content"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="trail-selected"
|
||||||
|
type="checkbox"
|
||||||
|
class="w-4 h-4 bg-input-background accent-primary border-input-border focus:ring-input-ring focus:ring-2"
|
||||||
|
bind:checked={selected}
|
||||||
|
onclick={(e) => handleInputClick(e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
import Select from "../base/select.svelte";
|
import Select from "../base/select.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
trail: Trail;
|
trail?: Trail;
|
||||||
onsave?: () => void;
|
onsave?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +56,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function shareTrail(item: SelectItem) {
|
async function shareTrail(item: SelectItem) {
|
||||||
|
if (trail === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!item.value.isLocal && !trail.public) {
|
if (!item.value.isLocal && !trail.public) {
|
||||||
displayShareError = true;
|
displayShareError = true;
|
||||||
return;
|
return;
|
||||||
@@ -80,6 +84,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fetchShares() {
|
async function fetchShares() {
|
||||||
|
if (trail === undefined) return;
|
||||||
|
|
||||||
sharesLoading = true;
|
sharesLoading = true;
|
||||||
await trail_share_index({ trail: trail.id! });
|
await trail_share_index({ trail: trail.id! });
|
||||||
sharesLoading = false;
|
sharesLoading = false;
|
||||||
|
|||||||
@@ -14,14 +14,18 @@
|
|||||||
interface Props {
|
interface Props {
|
||||||
tableHeader: SelectItem[];
|
tableHeader: SelectItem[];
|
||||||
trails?: Trail[] | null;
|
trails?: Trail[] | null;
|
||||||
|
selection: Set<Trail> | undefined;
|
||||||
filter?: TrailFilter | null;
|
filter?: TrailFilter | null;
|
||||||
onsort?: (value: any) => void;
|
onsort?: (value: any) => void
|
||||||
|
onTrailSelect?: (value: any) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
let { tableHeader, trails = null, filter = null, onsort }: Props = $props();
|
let { tableHeader, trails = null, selection, filter = null, onsort, onTrailSelect: onselect }: Props = $props();
|
||||||
|
|
||||||
function getColumnWidth(columnValue: string): string {
|
function getColumnWidth(columnValue: string): string {
|
||||||
switch (columnValue) {
|
switch (columnValue) {
|
||||||
|
case "select":
|
||||||
|
return "w-[2%]";
|
||||||
case "name":
|
case "name":
|
||||||
return "w-[25%]";
|
return "w-[25%]";
|
||||||
case "distance":
|
case "distance":
|
||||||
@@ -37,14 +41,68 @@
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setSelectedTrail(e: Event, trail: Trail) {
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
if (trail !== undefined) {
|
||||||
|
if (onselect !== undefined) {
|
||||||
|
onselect(trail)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error("undefined event handler")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSelectedAllTrails(e: Event) {
|
||||||
|
onselect?.(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSelected(trail: Trail): boolean {
|
||||||
|
if (selection === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trail !== undefined) {
|
||||||
|
for (const strail of selection) {
|
||||||
|
if (strail !== undefined && strail.id === trail.id)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function allSelected(): boolean {
|
||||||
|
if (selection === undefined || trails === undefined || trails === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return selection.size === trails.length;
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="table-container w-full border border-input-border rounded-xl overflow-x-scroll overflow-y-clip"
|
class="table-container w-full border border-input-border rounded-xl overflow-x-auto overflow-y-clip"
|
||||||
>
|
>
|
||||||
<table class="w-full">
|
<table class="w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-secondary-hover">
|
<tr class="bg-secondary-hover">
|
||||||
|
<th
|
||||||
|
class="p-4 text-left text-sm font-medium {getColumnWidth("select")}"
|
||||||
|
>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input
|
||||||
|
id="trail-selected"
|
||||||
|
type="checkbox"
|
||||||
|
class="w-4 h-4 bg-input-background accent-primary border-input-border focus:ring-input-ring focus:ring-2"
|
||||||
|
onclick={setSelectedAllTrails}
|
||||||
|
checked={allSelected()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
{#each tableHeader as column}
|
{#each tableHeader as column}
|
||||||
<th
|
<th
|
||||||
class="p-4 text-left text-sm font-medium {getColumnWidth(
|
class="p-4 text-left text-sm font-medium {getColumnWidth(
|
||||||
@@ -78,6 +136,16 @@
|
|||||||
}/${trail.id}`,
|
}/${trail.id}`,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
<td class="p-4 text-sm">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="w-4 h-4 bg-input-background accent-primary border-input-border focus:ring-input-ring focus:ring-2"
|
||||||
|
checked={isSelected(trail)}
|
||||||
|
onclick={(e: Event) => setSelectedTrail(e, trail)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td
|
<td
|
||||||
class="flex justify-between items-center text-sm relative"
|
class="flex justify-between items-center text-sm relative"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"add-to-list": "Zu Liste hinzufügen",
|
"add-to-list": "Zu Liste hinzufügen",
|
||||||
"add-waypoint": "Wegpunkt hinzufügen",
|
"add-waypoint": "Wegpunkt hinzufügen",
|
||||||
"added-trail-to": "Route hinzugefügt zu",
|
"added-trail-to": "Route hinzugefügt zu",
|
||||||
|
"added-trails-to": "Routen hinzugefügt zu",
|
||||||
"after": "Nach",
|
"after": "Nach",
|
||||||
"all-activities": "Alle Aktivitäten",
|
"all-activities": "Alle Aktivitäten",
|
||||||
"alphabetical": "Alphabetisch",
|
"alphabetical": "Alphabetisch",
|
||||||
@@ -275,6 +276,7 @@
|
|||||||
"register": "Registrieren",
|
"register": "Registrieren",
|
||||||
"remote-users-cannot-edit": "Nutzer anderer Instanzen können nicht bearbeiten",
|
"remote-users-cannot-edit": "Nutzer anderer Instanzen können nicht bearbeiten",
|
||||||
"removed-trail-from": "Route entfernt aus",
|
"removed-trail-from": "Route entfernt aus",
|
||||||
|
"removed-trails-from": "Routen entfernt aus",
|
||||||
"required": "Pflichtfeld",
|
"required": "Pflichtfeld",
|
||||||
"reset-password": "Passwort zurücksetzen",
|
"reset-password": "Passwort zurücksetzen",
|
||||||
"road": "Straße",
|
"road": "Straße",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"search-places": "Orte suchen",
|
"search-places": "Orte suchen",
|
||||||
"search-trails": "Route suchen",
|
"search-trails": "Route suchen",
|
||||||
"select-list": "Liste auswählen",
|
"select-list": "Liste auswählen",
|
||||||
|
"selected": "selected",
|
||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
"settings-notification-comment-mention": "Jemand hat dich in einem Kommentar erwähnt",
|
"settings-notification-comment-mention": "Jemand hat dich in einem Kommentar erwähnt",
|
||||||
"settings-notification-list-create": "Ein Benutzer, dem Du folgst, hat eine Liste erstellt",
|
"settings-notification-list-create": "Ein Benutzer, dem Du folgst, hat eine Liste erstellt",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"add-to-list": "Add to list",
|
"add-to-list": "Add to list",
|
||||||
"add-waypoint": "Add Waypoint",
|
"add-waypoint": "Add Waypoint",
|
||||||
"added-trail-to": "Added trail to",
|
"added-trail-to": "Added trail to",
|
||||||
|
"added-trails-to": "Added trails to",
|
||||||
"after": "After",
|
"after": "After",
|
||||||
"all-activities": "All activities",
|
"all-activities": "All activities",
|
||||||
"alphabetical": "Alphabetical",
|
"alphabetical": "Alphabetical",
|
||||||
@@ -275,6 +276,7 @@
|
|||||||
"register": "Register",
|
"register": "Register",
|
||||||
"remote-users-cannot-edit": "",
|
"remote-users-cannot-edit": "",
|
||||||
"removed-trail-from": "Removed trail from",
|
"removed-trail-from": "Removed trail from",
|
||||||
|
"removed-trails-from": "Removed trails from",
|
||||||
"required": "Required",
|
"required": "Required",
|
||||||
"reset-password": "Reset Password",
|
"reset-password": "Reset Password",
|
||||||
"road": "Road",
|
"road": "Road",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"search-places": "Search places",
|
"search-places": "Search places",
|
||||||
"search-trails": "Search trails",
|
"search-trails": "Search trails",
|
||||||
"select-list": "Select List",
|
"select-list": "Select List",
|
||||||
|
"selected": "",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"settings-notification-comment-mention": "Someone mentioned you in a comment",
|
"settings-notification-comment-mention": "Someone mentioned you in a comment",
|
||||||
"settings-notification-list-create": "A user who you follow has created a list",
|
"settings-notification-list-create": "A user who you follow has created a list",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"add-to-list": "Añadir a la lista",
|
"add-to-list": "Añadir a la lista",
|
||||||
"add-waypoint": "Añadir Punto de Interés",
|
"add-waypoint": "Añadir Punto de Interés",
|
||||||
"added-trail-to": "Ruta añadida a",
|
"added-trail-to": "Ruta añadida a",
|
||||||
|
"added-trails-to": "Rutas añadida a",
|
||||||
"after": "Después",
|
"after": "Después",
|
||||||
"all-activities": "Todas las actividades",
|
"all-activities": "Todas las actividades",
|
||||||
"alphabetical": "Alfabético",
|
"alphabetical": "Alfabético",
|
||||||
@@ -275,6 +276,7 @@
|
|||||||
"register": "Registrar",
|
"register": "Registrar",
|
||||||
"remote-users-cannot-edit": "",
|
"remote-users-cannot-edit": "",
|
||||||
"removed-trail-from": "Ruta borrada de",
|
"removed-trail-from": "Ruta borrada de",
|
||||||
|
"removed-trails-from": "Rutas borrada de",
|
||||||
"required": "Obligatorio",
|
"required": "Obligatorio",
|
||||||
"reset-password": "Restablecer Contraseña",
|
"reset-password": "Restablecer Contraseña",
|
||||||
"road": "Carretera",
|
"road": "Carretera",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"search-places": "Buscar lugares",
|
"search-places": "Buscar lugares",
|
||||||
"search-trails": "Buscar ruta",
|
"search-trails": "Buscar ruta",
|
||||||
"select-list": "Seleccionar Lista",
|
"select-list": "Seleccionar Lista",
|
||||||
|
"selected": "",
|
||||||
"settings": "Configuración",
|
"settings": "Configuración",
|
||||||
"settings-notification-comment-mention": "Someone mentioned you in a comment",
|
"settings-notification-comment-mention": "Someone mentioned you in a comment",
|
||||||
"settings-notification-list-create": "Un usuario al que sigues ha creado una nueva lista",
|
"settings-notification-list-create": "Un usuario al que sigues ha creado una nueva lista",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"add-to-list": "Ajouter à une liste",
|
"add-to-list": "Ajouter à une liste",
|
||||||
"add-waypoint": "Ajouter un point de passage",
|
"add-waypoint": "Ajouter un point de passage",
|
||||||
"added-trail-to": "Ajouter un itinéraire à",
|
"added-trail-to": "Ajouter un itinéraire à",
|
||||||
|
"added-trails-to": "Ajouter les itinéraires à",
|
||||||
"after": "Après",
|
"after": "Après",
|
||||||
"all-activities": "Toutes les activités",
|
"all-activities": "Toutes les activités",
|
||||||
"alphabetical": "Alphabétique",
|
"alphabetical": "Alphabétique",
|
||||||
@@ -275,6 +276,7 @@
|
|||||||
"register": "Créer un compte",
|
"register": "Créer un compte",
|
||||||
"remote-users-cannot-edit": "",
|
"remote-users-cannot-edit": "",
|
||||||
"removed-trail-from": "Enlever l'itinéraire de",
|
"removed-trail-from": "Enlever l'itinéraire de",
|
||||||
|
"removed-trails-from": "Enlever les itinéraires de",
|
||||||
"required": "Requis",
|
"required": "Requis",
|
||||||
"reset-password": "Réinitialiser le mot de passe",
|
"reset-password": "Réinitialiser le mot de passe",
|
||||||
"road": "Road",
|
"road": "Road",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"search-places": "Chercher des lieux",
|
"search-places": "Chercher des lieux",
|
||||||
"search-trails": "Chercher un itinéraire",
|
"search-trails": "Chercher un itinéraire",
|
||||||
"select-list": "Liste de choix",
|
"select-list": "Liste de choix",
|
||||||
|
"selected": "",
|
||||||
"settings": "Paramètres",
|
"settings": "Paramètres",
|
||||||
"settings-notification-comment-mention": "Someone mentioned you in a comment",
|
"settings-notification-comment-mention": "Someone mentioned you in a comment",
|
||||||
"settings-notification-list-create": "Un utilisateur que vous suivez à créé une nouvelle liste",
|
"settings-notification-list-create": "Un utilisateur que vous suivez à créé une nouvelle liste",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"add-to-list": "Hozzáadás a listához",
|
"add-to-list": "Hozzáadás a listához",
|
||||||
"add-waypoint": "Útvonalpont hozzáadása",
|
"add-waypoint": "Útvonalpont hozzáadása",
|
||||||
"added-trail-to": "Hozzáadott nyomvonal a",
|
"added-trail-to": "Hozzáadott nyomvonal a",
|
||||||
|
"added-trails-to": "Hozzáadott nyomvonalak a",
|
||||||
"after": "After",
|
"after": "After",
|
||||||
"all-activities": "All activities",
|
"all-activities": "All activities",
|
||||||
"alphabetical": "Betűrendben",
|
"alphabetical": "Betűrendben",
|
||||||
@@ -275,6 +276,7 @@
|
|||||||
"register": "Regisztráció",
|
"register": "Regisztráció",
|
||||||
"remote-users-cannot-edit": "",
|
"remote-users-cannot-edit": "",
|
||||||
"removed-trail-from": "Eltávolított nyomvonal a",
|
"removed-trail-from": "Eltávolított nyomvonal a",
|
||||||
|
"removed-trails-from": "Eltávolított nyomvonalak a",
|
||||||
"required": "Kötelező",
|
"required": "Kötelező",
|
||||||
"reset-password": "Reset Password",
|
"reset-password": "Reset Password",
|
||||||
"road": "Road",
|
"road": "Road",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"search-places": "Search places",
|
"search-places": "Search places",
|
||||||
"search-trails": "Nyomvonalak keresése",
|
"search-trails": "Nyomvonalak keresése",
|
||||||
"select-list": "Lista kiválasztása",
|
"select-list": "Lista kiválasztása",
|
||||||
|
"selected": "",
|
||||||
"settings": "Beállítások",
|
"settings": "Beállítások",
|
||||||
"settings-notification-comment-mention": "Someone mentioned you in a comment",
|
"settings-notification-comment-mention": "Someone mentioned you in a comment",
|
||||||
"settings-notification-list-create": "A user who you follow has created a list",
|
"settings-notification-list-create": "A user who you follow has created a list",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"add-to-list": "Aggiungi alla lista",
|
"add-to-list": "Aggiungi alla lista",
|
||||||
"add-waypoint": "Aggiungi un punto di passaggio",
|
"add-waypoint": "Aggiungi un punto di passaggio",
|
||||||
"added-trail-to": "Percorso aggiunto a",
|
"added-trail-to": "Percorso aggiunto a",
|
||||||
|
"added-trails-to": "Percorsi aggiunto a",
|
||||||
"after": "Dopo",
|
"after": "Dopo",
|
||||||
"all-activities": "Tutte le Attività",
|
"all-activities": "Tutte le Attività",
|
||||||
"alphabetical": "Alfabetico",
|
"alphabetical": "Alfabetico",
|
||||||
@@ -275,6 +276,7 @@
|
|||||||
"register": "Registrati",
|
"register": "Registrati",
|
||||||
"remote-users-cannot-edit": "",
|
"remote-users-cannot-edit": "",
|
||||||
"removed-trail-from": "Percorso rimosso da",
|
"removed-trail-from": "Percorso rimosso da",
|
||||||
|
"removed-trails-from": "Percorsi rimosso da",
|
||||||
"required": "Obbligatorio",
|
"required": "Obbligatorio",
|
||||||
"reset-password": "Ripristinare Password",
|
"reset-password": "Ripristinare Password",
|
||||||
"road": "Road",
|
"road": "Road",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"search-places": "Search places",
|
"search-places": "Search places",
|
||||||
"search-trails": "Cerca percorsi",
|
"search-trails": "Cerca percorsi",
|
||||||
"select-list": "Seleziona lista",
|
"select-list": "Seleziona lista",
|
||||||
|
"selected": "",
|
||||||
"settings": "Impostazioni",
|
"settings": "Impostazioni",
|
||||||
"settings-notification-comment-mention": "Someone mentioned you in a comment",
|
"settings-notification-comment-mention": "Someone mentioned you in a comment",
|
||||||
"settings-notification-list-create": "Un utente che segui ha creato una lista",
|
"settings-notification-list-create": "Un utente che segui ha creato una lista",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"add-to-list": "Toevoegen aan lijst",
|
"add-to-list": "Toevoegen aan lijst",
|
||||||
"add-waypoint": "Routepunt toevoegen",
|
"add-waypoint": "Routepunt toevoegen",
|
||||||
"added-trail-to": "Route toegevoegd aan",
|
"added-trail-to": "Route toegevoegd aan",
|
||||||
|
"added-trails-to": "Route toegevoegd aan",
|
||||||
"after": "Na",
|
"after": "Na",
|
||||||
"all-activities": "Alle activiteiten",
|
"all-activities": "Alle activiteiten",
|
||||||
"alphabetical": "Alfabetisch",
|
"alphabetical": "Alfabetisch",
|
||||||
@@ -275,6 +276,7 @@
|
|||||||
"register": "Registreren",
|
"register": "Registreren",
|
||||||
"remote-users-cannot-edit": "",
|
"remote-users-cannot-edit": "",
|
||||||
"removed-trail-from": "Route verwijderd van",
|
"removed-trail-from": "Route verwijderd van",
|
||||||
|
"removed-trails-from": "Routes verwijderd van",
|
||||||
"required": "Verplicht",
|
"required": "Verplicht",
|
||||||
"reset-password": "Wachtwoord opnieuw instellen",
|
"reset-password": "Wachtwoord opnieuw instellen",
|
||||||
"road": "Weg",
|
"road": "Weg",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"search-places": "Zoek plaatsen",
|
"search-places": "Zoek plaatsen",
|
||||||
"search-trails": "Zoek routes",
|
"search-trails": "Zoek routes",
|
||||||
"select-list": "Kies een lijst",
|
"select-list": "Kies een lijst",
|
||||||
|
"selected": "",
|
||||||
"settings": "Instellingen",
|
"settings": "Instellingen",
|
||||||
"settings-notification-comment-mention": "Someone mentioned you in a comment",
|
"settings-notification-comment-mention": "Someone mentioned you in a comment",
|
||||||
"settings-notification-list-create": "Een gebruiker die je volgt, heeft een lijst gecreëerd",
|
"settings-notification-list-create": "Een gebruiker die je volgt, heeft een lijst gecreëerd",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"add-to-list": "Dodaj do listy",
|
"add-to-list": "Dodaj do listy",
|
||||||
"add-waypoint": "Dodaj Punkt",
|
"add-waypoint": "Dodaj Punkt",
|
||||||
"added-trail-to": "Dodaj szlak do",
|
"added-trail-to": "Dodaj szlak do",
|
||||||
|
"added-trails-to": "Dodaj szlaki do",
|
||||||
"after": "Po",
|
"after": "Po",
|
||||||
"all-activities": "Wszystkie aktywności",
|
"all-activities": "Wszystkie aktywności",
|
||||||
"alphabetical": "Alfabetyczne",
|
"alphabetical": "Alfabetyczne",
|
||||||
@@ -275,6 +276,7 @@
|
|||||||
"register": "Zarejestruj",
|
"register": "Zarejestruj",
|
||||||
"remote-users-cannot-edit": "",
|
"remote-users-cannot-edit": "",
|
||||||
"removed-trail-from": "Usunięto szlak z",
|
"removed-trail-from": "Usunięto szlak z",
|
||||||
|
"removed-trails-from": "Usunięto szlaki z",
|
||||||
"required": "Wymagane",
|
"required": "Wymagane",
|
||||||
"reset-password": "Resetuj hasło",
|
"reset-password": "Resetuj hasło",
|
||||||
"road": "Road",
|
"road": "Road",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"search-places": "Szukaj miejsc",
|
"search-places": "Szukaj miejsc",
|
||||||
"search-trails": "Szukaj szlaków",
|
"search-trails": "Szukaj szlaków",
|
||||||
"select-list": "Wybierz Listę",
|
"select-list": "Wybierz Listę",
|
||||||
|
"selected": "",
|
||||||
"settings": "Ustawienia",
|
"settings": "Ustawienia",
|
||||||
"settings-notification-comment-mention": "Someone mentioned you in a comment",
|
"settings-notification-comment-mention": "Someone mentioned you in a comment",
|
||||||
"settings-notification-list-create": "Użytkownik, którego obserwujesz, utworzył listę",
|
"settings-notification-list-create": "Użytkownik, którego obserwujesz, utworzył listę",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"add-to-list": "Adicionar à lista",
|
"add-to-list": "Adicionar à lista",
|
||||||
"add-waypoint": "Adicionar ponto de vista",
|
"add-waypoint": "Adicionar ponto de vista",
|
||||||
"added-trail-to": "Trilha adicionada para",
|
"added-trail-to": "Trilha adicionada para",
|
||||||
|
"added-trails-to": "trilhas adicionada para",
|
||||||
"after": "Depois",
|
"after": "Depois",
|
||||||
"all-activities": "Todas as atividades",
|
"all-activities": "Todas as atividades",
|
||||||
"alphabetical": "Alfabético",
|
"alphabetical": "Alfabético",
|
||||||
@@ -275,6 +276,7 @@
|
|||||||
"register": "Registo",
|
"register": "Registo",
|
||||||
"remote-users-cannot-edit": "",
|
"remote-users-cannot-edit": "",
|
||||||
"removed-trail-from": "Trilha removida de",
|
"removed-trail-from": "Trilha removida de",
|
||||||
|
"removed-trails-from": "Trilhos removidos de",
|
||||||
"required": "Obrigatório",
|
"required": "Obrigatório",
|
||||||
"reset-password": "Reset Password",
|
"reset-password": "Reset Password",
|
||||||
"road": "Road",
|
"road": "Road",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"search-places": "Search places",
|
"search-places": "Search places",
|
||||||
"search-trails": "Procurar trilhos",
|
"search-trails": "Procurar trilhos",
|
||||||
"select-list": "Selecionar lista",
|
"select-list": "Selecionar lista",
|
||||||
|
"selected": "",
|
||||||
"settings": "Definições",
|
"settings": "Definições",
|
||||||
"settings-notification-comment-mention": "Someone mentioned you in a comment",
|
"settings-notification-comment-mention": "Someone mentioned you in a comment",
|
||||||
"settings-notification-list-create": "A user who you follow has created a list",
|
"settings-notification-list-create": "A user who you follow has created a list",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"add-to-list": "添加到列表",
|
"add-to-list": "添加到列表",
|
||||||
"add-waypoint": "添加坐标",
|
"add-waypoint": "添加坐标",
|
||||||
"added-trail-to": "添加路线到",
|
"added-trail-to": "添加路线到",
|
||||||
|
"added-trails-to": "添加路线到",
|
||||||
"after": "之后",
|
"after": "之后",
|
||||||
"all-activities": "所有活动",
|
"all-activities": "所有活动",
|
||||||
"alphabetical": "字母",
|
"alphabetical": "字母",
|
||||||
@@ -275,6 +276,7 @@
|
|||||||
"register": "注册",
|
"register": "注册",
|
||||||
"remote-users-cannot-edit": "",
|
"remote-users-cannot-edit": "",
|
||||||
"removed-trail-from": "路线已删除自",
|
"removed-trail-from": "路线已删除自",
|
||||||
|
"removed-trails-from": "路线已删除自",
|
||||||
"required": "必填",
|
"required": "必填",
|
||||||
"reset-password": "重置密码",
|
"reset-password": "重置密码",
|
||||||
"road": "Road",
|
"road": "Road",
|
||||||
@@ -289,6 +291,7 @@
|
|||||||
"search-places": "Search places",
|
"search-places": "Search places",
|
||||||
"search-trails": "搜索路线",
|
"search-trails": "搜索路线",
|
||||||
"select-list": "选择列表",
|
"select-list": "选择列表",
|
||||||
|
"selected": "",
|
||||||
"settings": "设置",
|
"settings": "设置",
|
||||||
"settings-notification-comment-mention": "Someone mentioned you in a comment",
|
"settings-notification-comment-mention": "Someone mentioned you in a comment",
|
||||||
"settings-notification-list-create": "A user who you follow has created a list",
|
"settings-notification-list-create": "A user who you follow has created a list",
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import JSZip from "jszip";
|
|||||||
import type { AuthRecord } from "pocketbase";
|
import type { AuthRecord } from "pocketbase";
|
||||||
import * as xmldom from 'xmldom';
|
import * as xmldom from 'xmldom';
|
||||||
import { bbox, splitMultiLineStringToLineStrings } from "./geojson_util";
|
import { bbox, splitMultiLineStringToLineStrings } from "./geojson_util";
|
||||||
|
import { trails_show } from "$lib/stores/trail_store";
|
||||||
|
import { handleFromRecordWithIRI } from "./activitypub_util";
|
||||||
|
|
||||||
|
|
||||||
export async function gpx2trail(gpxString: string, fallbackName?: string, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
export async function gpx2trail(gpxString: string, fallbackName?: string, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||||
@@ -71,10 +73,21 @@ export async function gpx2trail(gpxString: string, fallbackName?: string, f: (ur
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function trail2gpx(trail: Trail, user?: AuthRecord) {
|
export async function trail2gpx(trail: Trail, user?: AuthRecord) {
|
||||||
|
let gpxTrail = trail;
|
||||||
|
|
||||||
if (!trail.expand?.gpx_data) {
|
if (!trail.expand?.gpx_data) {
|
||||||
|
// no gpx_data -> empty trail?
|
||||||
|
// or just not expanded? -> expand now
|
||||||
|
const response = await trails_show(trail.id!, handleFromRecordWithIRI(trail), true);
|
||||||
|
|
||||||
|
if (!response.expand?.gpx_data) {
|
||||||
throw Error("Trail has no GPX data")
|
throw Error("Trail has no GPX data")
|
||||||
|
} else {
|
||||||
|
gpxTrail = response;
|
||||||
}
|
}
|
||||||
const gpx = await GPX.parse(trail.expand.gpx_data) as GPX;
|
}
|
||||||
|
|
||||||
|
const gpx = await GPX.parse(gpxTrail.expand!.gpx_data!) as GPX;
|
||||||
|
|
||||||
if (gpx instanceof Error) {
|
if (gpx instanceof Error) {
|
||||||
throw gpx;
|
throw gpx;
|
||||||
@@ -92,7 +105,7 @@ export async function trail2gpx(trail: Trail, user?: AuthRecord) {
|
|||||||
gpx.wpt = [];
|
gpx.wpt = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const wp of trail.expand.waypoints ?? []) {
|
for (const wp of gpxTrail.expand!.waypoints ?? []) {
|
||||||
const gpxWpt = gpx.wpt.find((w) => w.$.lat == wp.lat && w.$.lon == wp.lon)
|
const gpxWpt = gpx.wpt.find((w) => w.$.lat == wp.lat && w.$.lon == wp.lon)
|
||||||
if (!gpxWpt) {
|
if (!gpxWpt) {
|
||||||
gpx.wpt.push({
|
gpx.wpt.push({
|
||||||
|
|||||||
Reference in New Issue
Block a user