adds komoot integration
This commit is contained in:
@@ -72,7 +72,7 @@
|
||||
</div>
|
||||
{#if activity.photos.length}
|
||||
<div
|
||||
class="grid {activity.photos.length > 1
|
||||
class="grid gap-[1px] {activity.photos.length > 1
|
||||
? 'grid-cols-[8fr_5fr]'
|
||||
: 'grid-cols-1'}"
|
||||
>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
import Toggle from "$lib/components/base/toggle.svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
interface Props {
|
||||
onclick: () => void;
|
||||
ontoggle: (value: boolean) => void;
|
||||
active: boolean;
|
||||
disabled: boolean;
|
||||
img: string;
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
let {
|
||||
onclick,
|
||||
ontoggle,
|
||||
active = $bindable(),
|
||||
disabled,
|
||||
img,
|
||||
title,
|
||||
description,
|
||||
}: Props = $props();
|
||||
</script>
|
||||
<div class="border border-input-border rounded-lg p-4 space-y-4">
|
||||
<img class="h-20" src={img} alt="integration logo"/>
|
||||
<div>
|
||||
<h5 class="text-xl font-semibold">{title}</h5>
|
||||
<p class="text-sm text-gray-500">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<button class="btn-secondary" {onclick}
|
||||
><i class="fa fa-cogs mr-2"></i>{$_("settings")}</button
|
||||
>
|
||||
<Toggle bind:value={active} onchange={ontoggle} {disabled}></Toggle>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,82 @@
|
||||
<script lang="ts">
|
||||
import Modal from "$lib/components/base/modal.svelte";
|
||||
import TextField from "$lib/components/base/text_field.svelte";
|
||||
import { KomootSchema } from "$lib/models/api/integration_schema";
|
||||
import type {
|
||||
Integration,
|
||||
KomootIntegration,
|
||||
} from "$lib/models/integration";
|
||||
import { validator } from "@felte/validator-zod";
|
||||
import { createForm } from "felte";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
interface Props {
|
||||
integration?: Integration;
|
||||
onsave?: (komootIntegration: KomootIntegration) => void;
|
||||
}
|
||||
|
||||
let { integration, onsave }: Props = $props();
|
||||
|
||||
let modal: Modal;
|
||||
|
||||
export function openModal() {
|
||||
errors.set({})
|
||||
modal.openModal();
|
||||
}
|
||||
|
||||
const {
|
||||
form,
|
||||
errors,
|
||||
data: d,
|
||||
} = createForm({
|
||||
initialValues: {
|
||||
email: integration?.komoot?.email ?? "",
|
||||
password: integration?.komoot?.password ?? "",
|
||||
active: integration?.komoot?.active ?? false,
|
||||
},
|
||||
extend: validator({
|
||||
schema: KomootSchema,
|
||||
}),
|
||||
onSubmit: async (form) => {
|
||||
onsave?.(form);
|
||||
modal.closeModal();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
id="komoot-settings-modal"
|
||||
size="max-w-lg"
|
||||
title={"komoot " + $_("settings")}
|
||||
bind:this={modal}
|
||||
>
|
||||
{#snippet content()}
|
||||
<form id="komoot-settings-form" class="space-y-2" use:form>
|
||||
<TextField
|
||||
label={$_("email")}
|
||||
placeholder="user@example.com"
|
||||
name="email"
|
||||
error={$errors.email}
|
||||
></TextField>
|
||||
<TextField
|
||||
label={$_("password")}
|
||||
name="password"
|
||||
type="password"
|
||||
error={$errors.password}
|
||||
></TextField>
|
||||
</form>
|
||||
{/snippet}
|
||||
{#snippet footer()}
|
||||
<div class="flex items-center gap-4">
|
||||
<button class="btn-secondary" onclick={() => modal.closeModal()}
|
||||
>{$_("cancel")}</button
|
||||
>
|
||||
<button
|
||||
class="btn-primary"
|
||||
form="komoot-settings-form"
|
||||
type="submit"
|
||||
name="save">{$_("save")}</button
|
||||
>
|
||||
</div>
|
||||
{/snippet}</Modal
|
||||
>
|
||||
@@ -0,0 +1,93 @@
|
||||
<script lang="ts">
|
||||
import Modal from "$lib/components/base/modal.svelte";
|
||||
import TextField from "$lib/components/base/text_field.svelte";
|
||||
import Toggle from "$lib/components/base/toggle.svelte";
|
||||
import { StravaSchema } from "$lib/models/api/integration_schema";
|
||||
import type {
|
||||
Integration,
|
||||
StravaIntegration,
|
||||
} from "$lib/models/integration";
|
||||
import { validator } from "@felte/validator-zod";
|
||||
import { createForm } from "felte";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
interface Props {
|
||||
integration?: Integration;
|
||||
onsave?: (stravaIntegration: StravaIntegration) => void;
|
||||
}
|
||||
|
||||
let { integration, onsave }: Props = $props();
|
||||
|
||||
let modal: Modal;
|
||||
|
||||
export function openModal() {
|
||||
errors.set({})
|
||||
modal.openModal();
|
||||
}
|
||||
|
||||
const {
|
||||
form,
|
||||
errors,
|
||||
} = createForm({
|
||||
initialValues: {
|
||||
clientId: integration?.strava?.clientId ?? "",
|
||||
clientSecret: integration?.strava?.clientSecret ?? "",
|
||||
routes: integration?.strava?.routes ?? true,
|
||||
activities: integration?.strava?.activities ?? true,
|
||||
active: integration?.strava?.active ?? false,
|
||||
},
|
||||
extend: validator({
|
||||
schema: StravaSchema,
|
||||
}),
|
||||
onSubmit: async (form) => {
|
||||
onsave?.(form);
|
||||
modal.closeModal();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
id="strava-settings-modal"
|
||||
size="max-w-lg"
|
||||
title={"strava " + $_("settings")}
|
||||
bind:this={modal}
|
||||
>
|
||||
{#snippet content()}
|
||||
<form id="strava-settings-form" class="space-y-2" use:form>
|
||||
<TextField
|
||||
label="Client-ID"
|
||||
placeholder="000000"
|
||||
name="clientId"
|
||||
error={$errors.clientId}
|
||||
></TextField>
|
||||
<TextField
|
||||
label="Client Secret"
|
||||
placeholder="de8b3789bd7116d..."
|
||||
name="clientSecret"
|
||||
type="password"
|
||||
error={$errors.clientSecret}
|
||||
></TextField>
|
||||
<div class="flex gap-x-4">
|
||||
<Toggle name="routes" label={$_("route", { values: { n: 2 } })}
|
||||
></Toggle>
|
||||
<Toggle
|
||||
name="activities"
|
||||
label={$_("activity", { values: { n: 2 } })}
|
||||
></Toggle>
|
||||
</div>
|
||||
</form>
|
||||
{/snippet}
|
||||
{#snippet footer()}
|
||||
<div class="flex items-center gap-4">
|
||||
<button class="btn-secondary" onclick={() => modal.closeModal()}
|
||||
>{$_("cancel")}</button
|
||||
>
|
||||
<button
|
||||
class="btn-primary"
|
||||
form="strava-settings-form"
|
||||
type="submit"
|
||||
name="save">{$_("save")}</button
|
||||
>
|
||||
</div>
|
||||
{/snippet}</Modal
|
||||
>
|
||||
@@ -55,7 +55,7 @@
|
||||
<div
|
||||
class="relative w-full min-h-40 max-h-48 overflow-hidden rounded-t-2xl"
|
||||
>
|
||||
<img width="100%" id="header-img" src={thumbnail} alt="" />
|
||||
<img class="min-h-40" id="header-img" src={thumbnail} alt="" />
|
||||
</div>
|
||||
{#if (trail.public || trailIsShared) && pb.authStore.model}
|
||||
<div
|
||||
|
||||
@@ -44,13 +44,20 @@
|
||||
import TrailTimeline from "./trail_timeline.svelte";
|
||||
|
||||
interface Props {
|
||||
trail: Trail;
|
||||
initTrail: Trail;
|
||||
mode?: "overview" | "map" | "list";
|
||||
markers?: M.Marker[];
|
||||
activeTab?: number;
|
||||
}
|
||||
|
||||
let { trail, mode = "map", markers = [], activeTab = 0 }: Props = $props();
|
||||
let {
|
||||
initTrail,
|
||||
mode = "map",
|
||||
markers = [],
|
||||
activeTab = 0,
|
||||
}: Props = $props();
|
||||
|
||||
let trail = $state(initTrail);
|
||||
|
||||
const tabs = [
|
||||
$_("summit-book"),
|
||||
@@ -362,7 +369,9 @@
|
||||
{:else}
|
||||
<EmptyStateDescription></EmptyStateDescription>
|
||||
{/if}
|
||||
<h4 class="text-2xl font-semibold mb-6 mt-12">{$_("route", { values: { n: 2 } })}</h4>
|
||||
<h4 class="text-2xl font-semibold mb-6 mt-12">
|
||||
{$_("route", { values: { n: 2 } })}
|
||||
</h4>
|
||||
{#if mode === "overview"}
|
||||
<div
|
||||
class="relative border border-input-border rounded-xl p-2 mb-6 text-xs"
|
||||
|
||||
@@ -42,23 +42,26 @@
|
||||
|
||||
<div class="border border-input-border rounded-xl overflow-hidden">
|
||||
{#if wp.photos.length}
|
||||
<PhotoGallery photos={wp.photos.map((p) => getFileURL(wp, p))} bind:this={gallery[i]}
|
||||
<PhotoGallery
|
||||
photos={wp.photos.map((p) => getFileURL(wp, p))}
|
||||
bind:this={gallery[i]}
|
||||
></PhotoGallery>
|
||||
<div
|
||||
class="grid {wp.photos.length > 1
|
||||
class="grid gap-[1px] {wp.photos.length > 1
|
||||
? 'grid-cols-[8fr_5fr]'
|
||||
: 'grid-cols-1'} cursor-pointer"
|
||||
>
|
||||
{#each wp.photos as photo, j}
|
||||
<button onclick={() => gallery[i].openGallery(j)}>
|
||||
<img
|
||||
class="object-cover h-full max-h-80 w-full"
|
||||
class:row-span-2={i == 0 &&
|
||||
wp.photos.length > 2}
|
||||
src={getFileURL(wp, photo)}
|
||||
alt=""
|
||||
/>
|
||||
</button>
|
||||
<img
|
||||
onclick={() => gallery[i].openGallery(j)}
|
||||
role="presentation"
|
||||
class="w-full object-cover {j == 0 &&
|
||||
wp.photos.length > 2
|
||||
? 'row-span-2 h-80'
|
||||
: 'h-[159.5px]'}"
|
||||
src={getFileURL(wp, photo)}
|
||||
alt=""
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex gap-4 p-4 outline outline-1 outline-input-border rounded-md my-2 hover:outline-2"
|
||||
class="flex gap-4 p-4 outline outline-1 outline-input-border rounded-md my-2 hover:outline-2 items-start"
|
||||
>
|
||||
{#if imgSrc.length}
|
||||
{#if mode == "show"}
|
||||
@@ -61,7 +61,7 @@
|
||||
>
|
||||
{#each imgSrc as img, i}
|
||||
<img
|
||||
class="absolute h-full rounded-xl object-cover"
|
||||
class="absolute h-full rounded-xl object-cover aspect-square"
|
||||
style="top: {6 * i}px; right: {6 *
|
||||
i}px; transform: rotate(-{i * 5}deg)"
|
||||
src={img}
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
"error-during-login": "Fehler beim Login",
|
||||
"error-during-password-reset": "Email zum Zurücksetzen des Passworts konnte nicht versandt werden",
|
||||
"error-exporting-trail": "Fehler beim Exportieren des Trails",
|
||||
"error-logging-in-to-komoot": "",
|
||||
"error-posting-comment": "Fehler beim Posten des Kommentars",
|
||||
"error-printing-map": "Fehler beim Drucken der Karte",
|
||||
"error-reading-file": "Fehler beim Lesen der Datei",
|
||||
@@ -107,6 +108,7 @@
|
||||
"error-saving-trail": "Fehler beim Speichern der Route",
|
||||
"error-setting-up-strava-integration": "",
|
||||
"error-updating-password": "Fehler beim Aktualisieren des Passworts",
|
||||
"error-updating-strava-integration": "",
|
||||
"est-duration": "Gesch. Dauer",
|
||||
"explore": "Erkunden",
|
||||
"explore-some-trails": "Erkunde einige Routen",
|
||||
@@ -143,6 +145,8 @@
|
||||
"import": "Importieren",
|
||||
"import-hint": "GPX, FIT, KML oder TCX Dateien auswählen oder hierher ziehen...",
|
||||
"include-description": "Beschreibung übernehmen",
|
||||
"integration-description-komoot": "",
|
||||
"integration-description-strava": "",
|
||||
"integrations": "",
|
||||
"invalid-date": "Ungültiges Datum",
|
||||
"invalid-username": "Ungültiger Nutzername",
|
||||
@@ -257,6 +261,7 @@
|
||||
"settings-privacy-lists-public": "Deine Listen sind standardmäßig öffentlich. \nJeder kann sie sehen. \nDu kannst diese Einstellung jederzeit für einzelne Listen ändern.",
|
||||
"settings-privacy-trails-private": "Deine Routen sind standardmäßig privat. \nNiemand außer Ihnen kann sie sehen. \nDu kannst diese Einstellung jederzeit für einzelne Routen ändern.",
|
||||
"settings-privacy-trails-public": "Deine Trails sind standardmäßig öffentlich. \nJeder kann sie sehen. \nDu kannst diese Einstellung jederzeit für einzelne Routen ändern.",
|
||||
"settings-saved": "",
|
||||
"share": "Teilen",
|
||||
"share-profile": "Profil teilen",
|
||||
"share-this-list": "Diese Liste teilen",
|
||||
@@ -295,4 +300,4 @@
|
||||
"welcome_to": "Willkommen bei",
|
||||
"wrong-username-or-password": "Falscher Nutzername oder falsches Passwort",
|
||||
"you-can": "Du kannst"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
"error-during-login": "Error during login",
|
||||
"error-during-password-reset": "Unable to send password reset email",
|
||||
"error-exporting-trail": "Error exporting trail",
|
||||
"error-logging-in-to-komoot": "Error logging in to komoot",
|
||||
"error-posting-comment": "Error posting comment",
|
||||
"error-printing-map": "Error printing map",
|
||||
"error-reading-file": "Error reading file",
|
||||
@@ -107,6 +108,7 @@
|
||||
"error-saving-trail": "Error saving trail",
|
||||
"error-setting-up-strava-integration": "Error setting up strava integration",
|
||||
"error-updating-password": "Error updating password",
|
||||
"error-updating-strava-integration": "Error updating komoot integration",
|
||||
"est-duration": "Est. duration",
|
||||
"explore": "Explore",
|
||||
"explore-some-trails": "Explore some trails",
|
||||
@@ -143,6 +145,8 @@
|
||||
"import": "Import",
|
||||
"import-hint": "Select or drag GPX, FIT, KML or TCX files here...",
|
||||
"include-description": "Include description",
|
||||
"integration-description-komoot": "Syncs your komoot tours with wanderer in regular intervals.",
|
||||
"integration-description-strava": "Syncs your strava routes & activities with wanderer in regular intervals.",
|
||||
"integrations": "Integrations",
|
||||
"invalid-date": "Invalid Date",
|
||||
"invalid-username": "Invalid username",
|
||||
@@ -257,6 +261,7 @@
|
||||
"settings-privacy-lists-public": "Your lists are public by default. Everyone will be able to see them. You can change this setting at any point for individual lists.",
|
||||
"settings-privacy-trails-private": "Your trails are private by default. No one except you will be able to see them. You can change this setting at any point for individual trails.",
|
||||
"settings-privacy-trails-public": "Your trails are public by default. Everyone will be able to see them. You can change this setting at any point for individual trails.",
|
||||
"settings-saved": "Settings saved",
|
||||
"share": "Share",
|
||||
"share-profile": "Share profile",
|
||||
"share-this-list": "Share this list",
|
||||
@@ -295,4 +300,4 @@
|
||||
"welcome_to": "Welcome to",
|
||||
"wrong-username-or-password": "Wrong username or password",
|
||||
"you-can": "You can"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
"error-during-login": "Error durante el acceso",
|
||||
"error-during-password-reset": "Imposible enviar la contraseña de restablecimiento al correo electrónico",
|
||||
"error-exporting-trail": "Error exportando la ruta",
|
||||
"error-logging-in-to-komoot": "",
|
||||
"error-posting-comment": "Error publicando el comentario",
|
||||
"error-printing-map": "Error durante la impresión del mapa",
|
||||
"error-reading-file": "Error leyendo el archivo",
|
||||
@@ -107,6 +108,7 @@
|
||||
"error-saving-trail": "Error guardando la ruta",
|
||||
"error-setting-up-strava-integration": "",
|
||||
"error-updating-password": "Error actualizando la contraseña",
|
||||
"error-updating-strava-integration": "",
|
||||
"est-duration": "Duración estimada",
|
||||
"explore": "Explora",
|
||||
"explore-some-trails": "Explora alguna ruta",
|
||||
@@ -143,6 +145,8 @@
|
||||
"import": "Importar",
|
||||
"import-hint": "Selecciona o arrastra aquí archivos GPX, FIT, KML o TCX...",
|
||||
"include-description": "Incluir descripción",
|
||||
"integration-description-komoot": "",
|
||||
"integration-description-strava": "",
|
||||
"integrations": "",
|
||||
"invalid-date": "Fecha no válida",
|
||||
"invalid-username": "Usuario no válido",
|
||||
@@ -257,6 +261,7 @@
|
||||
"settings-privacy-lists-public": "Tus listas son públicas por defecto. Todos podrán verlas. Puedes cambiar esta configuración en cualquier momento para listas específicas.",
|
||||
"settings-privacy-trails-private": "Tus rutas son privadas por defecto. Nadie excepto tú podrás verlas. Puedes cambiar esta configuración en cualquier momento para rutas específicas.",
|
||||
"settings-privacy-trails-public": "Tus rutas son públicas por defecto. Todos podrán verlas. Puedes cambiar esta configuración en cualquier momento para rutas específicas.",
|
||||
"settings-saved": "",
|
||||
"share": "Compartir",
|
||||
"share-profile": "Compartir perfil",
|
||||
"share-this-list": "Compartir esta lista",
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
"error-during-login": "Erreur durant la connexion",
|
||||
"error-during-password-reset": "Unable to send password reset email",
|
||||
"error-exporting-trail": "Erreur lors de l'export de l'itinéraire",
|
||||
"error-logging-in-to-komoot": "",
|
||||
"error-posting-comment": "Error posting comment",
|
||||
"error-printing-map": "Erreur d'impression de la carte",
|
||||
"error-reading-file": "Erreur de lecture du fichier",
|
||||
@@ -107,6 +108,7 @@
|
||||
"error-saving-trail": "Erreur lors de l'enregistrement de l'itinéraire",
|
||||
"error-setting-up-strava-integration": "",
|
||||
"error-updating-password": "Erreur lors de la mise à jour du mot de passe",
|
||||
"error-updating-strava-integration": "",
|
||||
"est-duration": "Temps estimé",
|
||||
"explore": "Explorer",
|
||||
"explore-some-trails": "Explorer les itinéraires",
|
||||
@@ -143,6 +145,8 @@
|
||||
"import": "Importer",
|
||||
"import-hint": "Sélectionnez ou glissez des fichiers GPX, FIT, KML ou TCX ici...",
|
||||
"include-description": "Inclure la description",
|
||||
"integration-description-komoot": "",
|
||||
"integration-description-strava": "",
|
||||
"integrations": "",
|
||||
"invalid-date": "Date invalide",
|
||||
"invalid-username": "Nom d'utilisateur invalide",
|
||||
@@ -257,6 +261,7 @@
|
||||
"settings-privacy-lists-public": "Your lists are public by default. Everyone will be able to see them. You can change this setting at any point for individual lists.",
|
||||
"settings-privacy-trails-private": "Your trails are private by default. No one except you will be able to see them. You can change this setting at any point for individual trails.",
|
||||
"settings-privacy-trails-public": "Your trails are public by default. Everyone will be able to see them. You can change this setting at any point for individual trails.",
|
||||
"settings-saved": "",
|
||||
"share": "Partager",
|
||||
"share-profile": "Share profile",
|
||||
"share-this-list": "Partager cette liste",
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
"error-during-login": "Hiba bejelentkezés közben",
|
||||
"error-during-password-reset": "Unable to send password reset email",
|
||||
"error-exporting-trail": "Error exporting trail",
|
||||
"error-logging-in-to-komoot": "",
|
||||
"error-posting-comment": "Error posting comment",
|
||||
"error-printing-map": "Error printing map",
|
||||
"error-reading-file": "Hiba a fájl olvasása közben",
|
||||
@@ -107,6 +108,7 @@
|
||||
"error-saving-trail": "Error saving trail",
|
||||
"error-setting-up-strava-integration": "",
|
||||
"error-updating-password": "Error updating password",
|
||||
"error-updating-strava-integration": "",
|
||||
"est-duration": "Becsült időtartam",
|
||||
"explore": "Felfedezés",
|
||||
"explore-some-trails": "Fedezzen fel néhány ösvényt",
|
||||
@@ -143,6 +145,8 @@
|
||||
"import": "Import",
|
||||
"import-hint": "Select or drag GPX, FIT, KML or TCX files here...",
|
||||
"include-description": "Include description",
|
||||
"integration-description-komoot": "",
|
||||
"integration-description-strava": "",
|
||||
"integrations": "",
|
||||
"invalid-date": "Érvénytelen dátum",
|
||||
"invalid-username": "Érvénytelen felhasználó",
|
||||
@@ -257,6 +261,7 @@
|
||||
"settings-privacy-lists-public": "Your lists are public by default. Everyone will be able to see them. You can change this setting at any point for individual lists.",
|
||||
"settings-privacy-trails-private": "Your trails are private by default. No one except you will be able to see them. You can change this setting at any point for individual trails.",
|
||||
"settings-privacy-trails-public": "Your trails are public by default. Everyone will be able to see them. You can change this setting at any point for individual trails.",
|
||||
"settings-saved": "",
|
||||
"share": "Share",
|
||||
"share-profile": "Share profile",
|
||||
"share-this-list": "Share this list",
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
"error-during-login": "Errore durante il login",
|
||||
"error-during-password-reset": "Impossibile inviare email per ripristinare la password",
|
||||
"error-exporting-trail": "Errore durante l'esportazione del percorso",
|
||||
"error-logging-in-to-komoot": "",
|
||||
"error-posting-comment": "Errore pubblicando il commento",
|
||||
"error-printing-map": "Errore durante la stampa della mappa",
|
||||
"error-reading-file": "Errore durante la lettura del file",
|
||||
@@ -107,6 +108,7 @@
|
||||
"error-saving-trail": "Errore nel salvataggio del percorso",
|
||||
"error-setting-up-strava-integration": "",
|
||||
"error-updating-password": "Errore nell'aggiornamento della password",
|
||||
"error-updating-strava-integration": "",
|
||||
"est-duration": "Durata stimata",
|
||||
"explore": "Esplora",
|
||||
"explore-some-trails": "Esplora alcuni percorsi",
|
||||
@@ -143,6 +145,8 @@
|
||||
"import": "Importa",
|
||||
"import-hint": "Seleziona o trascina qui i file GPX, FIT, KML o TCX...",
|
||||
"include-description": "Adotta descrizione",
|
||||
"integration-description-komoot": "",
|
||||
"integration-description-strava": "",
|
||||
"integrations": "",
|
||||
"invalid-date": "Data non valida",
|
||||
"invalid-username": "Nome utente non valido",
|
||||
@@ -257,6 +261,7 @@
|
||||
"settings-privacy-lists-public": "Le tue liste sono pubbliche per impostazione predefinita. Tutto potrànno vederle. Puoi cambiare queste impostazione in qualsiasi momento per liste specifiche.",
|
||||
"settings-privacy-trails-private": "Le tuoi percorsi sono privati per impostazione predefinita. Nessuno tranne tu potrà vederli. Puoi cambiare queste impostazione in qualsiasi momento per percorsi specifici.",
|
||||
"settings-privacy-trails-public": "Le tuoi percorsi sono pubblici per impostazione predefinita. Tutti potranno vederli. Puoi cambiare queste impostazione in qualsiasi momento per percorsi specifici.",
|
||||
"settings-saved": "",
|
||||
"share": "Condividi",
|
||||
"share-profile": "Condividere profilo",
|
||||
"share-this-list": "Condividi questa lista",
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
"error-during-login": "Het inloggen is mislukt",
|
||||
"error-during-password-reset": "Unable to send password reset email",
|
||||
"error-exporting-trail": "Error exporting trail",
|
||||
"error-logging-in-to-komoot": "",
|
||||
"error-posting-comment": "Error posting comment",
|
||||
"error-printing-map": "Fout bij afdrukken van kaart",
|
||||
"error-reading-file": "Het bestand kan niet worden ingelezen",
|
||||
@@ -107,6 +108,7 @@
|
||||
"error-saving-trail": "Error saving trail",
|
||||
"error-setting-up-strava-integration": "",
|
||||
"error-updating-password": "Error updating password",
|
||||
"error-updating-strava-integration": "",
|
||||
"est-duration": "Geschatte duur",
|
||||
"explore": "Verkennen",
|
||||
"explore-some-trails": "Verken enkele wandelroutes",
|
||||
@@ -143,6 +145,8 @@
|
||||
"import": "Import",
|
||||
"import-hint": "Select or drag GPX, FIT, KML or TCX files here...",
|
||||
"include-description": "Inclusief beschrijving",
|
||||
"integration-description-komoot": "",
|
||||
"integration-description-strava": "",
|
||||
"integrations": "",
|
||||
"invalid-date": "Ongeldige datum",
|
||||
"invalid-username": "Ongeldige gebruikersnaam",
|
||||
@@ -257,6 +261,7 @@
|
||||
"settings-privacy-lists-public": "Your lists are public by default. Everyone will be able to see them. You can change this setting at any point for individual lists.",
|
||||
"settings-privacy-trails-private": "Your trails are private by default. No one except you will be able to see them. You can change this setting at any point for individual trails.",
|
||||
"settings-privacy-trails-public": "Your trails are public by default. Everyone will be able to see them. You can change this setting at any point for individual trails.",
|
||||
"settings-saved": "",
|
||||
"share": "Share",
|
||||
"share-profile": "Share profile",
|
||||
"share-this-list": "Share this list",
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
"error-during-login": "Błąd podczas logowania",
|
||||
"error-during-password-reset": "Nie udało się wysłać e-maila z resetowaniem hasła",
|
||||
"error-exporting-trail": "Błąd podczas eksportowania szlaku",
|
||||
"error-logging-in-to-komoot": "",
|
||||
"error-posting-comment": "Error posting comment",
|
||||
"error-printing-map": "Błąd podczas drukowania mapy",
|
||||
"error-reading-file": "Błąd wczytywania pliku",
|
||||
@@ -107,6 +108,7 @@
|
||||
"error-saving-trail": "Błąd podczas zapisywania szlaku",
|
||||
"error-setting-up-strava-integration": "",
|
||||
"error-updating-password": "Błąd podczas aktualizacji hasła",
|
||||
"error-updating-strava-integration": "",
|
||||
"est-duration": "Szacowany czas",
|
||||
"explore": "Eksploruj",
|
||||
"explore-some-trails": "Eksploruj różne szlaki",
|
||||
@@ -143,6 +145,8 @@
|
||||
"import": "Importuj",
|
||||
"import-hint": "Wybierz lub przeciągnij tutaj plik GPX, FIT, KML lub TCX...",
|
||||
"include-description": "Dołącz opis",
|
||||
"integration-description-komoot": "",
|
||||
"integration-description-strava": "",
|
||||
"integrations": "",
|
||||
"invalid-date": "Nieprawidłowa data",
|
||||
"invalid-username": "Błędna nazwa użytkownika",
|
||||
@@ -257,6 +261,7 @@
|
||||
"settings-privacy-lists-public": "Domyślnie, twoje listy są publiczne. Każdy będzie mógł je zobaczyć. Zawsze możesz zmieniać to ustawienie, osobno dla każdej listy.",
|
||||
"settings-privacy-trails-private": "Domyślnie, twoje szlaki są prywatne. Nikt poza tobą nie będzie mógł ich zobaczyć. Zawsze możesz zmieniać to ustawienie, osobno dla każdego szlaku.",
|
||||
"settings-privacy-trails-public": "Domyślnie, twoje szlaki są publiczne. Każdy będzie mógł je zobaczyć. Zawsze możesz zmieniać to ustawienie, osobno dla każdego szlaku.",
|
||||
"settings-saved": "",
|
||||
"share": "Udostępnij",
|
||||
"share-profile": "Udostępnij profil",
|
||||
"share-this-list": "Udostępnij tę listę",
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
"error-during-login": "Erro durante o ‘login’",
|
||||
"error-during-password-reset": "Unable to send password reset email",
|
||||
"error-exporting-trail": "Erro na exportação do percurso",
|
||||
"error-logging-in-to-komoot": "",
|
||||
"error-posting-comment": "Error posting comment",
|
||||
"error-printing-map": "Erro na impressão do mapa",
|
||||
"error-reading-file": "Erro ao ler o arquivo",
|
||||
@@ -107,6 +108,7 @@
|
||||
"error-saving-trail": "Erro ao gravar percurso",
|
||||
"error-setting-up-strava-integration": "",
|
||||
"error-updating-password": "Erro ao atualizar password",
|
||||
"error-updating-strava-integration": "",
|
||||
"est-duration": "Duração prevista",
|
||||
"explore": "Explorar",
|
||||
"explore-some-trails": "Explore algumas trilhas",
|
||||
@@ -143,6 +145,8 @@
|
||||
"import": "Importar",
|
||||
"import-hint": "Selecionar ou arrastar ficheiros GPX, FIT, KML ou TCX para aqui...",
|
||||
"include-description": "Incluir descrição",
|
||||
"integration-description-komoot": "",
|
||||
"integration-description-strava": "",
|
||||
"integrations": "",
|
||||
"invalid-date": "Data inválida",
|
||||
"invalid-username": "Nome de usuário inválido",
|
||||
@@ -257,6 +261,7 @@
|
||||
"settings-privacy-lists-public": "Your lists are public by default. Everyone will be able to see them. You can change this setting at any point for individual lists.",
|
||||
"settings-privacy-trails-private": "Your trails are private by default. No one except you will be able to see them. You can change this setting at any point for individual trails.",
|
||||
"settings-privacy-trails-public": "Your trails are public by default. Everyone will be able to see them. You can change this setting at any point for individual trails.",
|
||||
"settings-saved": "",
|
||||
"share": "Partilhar",
|
||||
"share-profile": "Share profile",
|
||||
"share-this-list": "Partilhar esta lista",
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
"error-during-login": "登录错误",
|
||||
"error-during-password-reset": "Unable to send password reset email",
|
||||
"error-exporting-trail": "导出路线失败",
|
||||
"error-logging-in-to-komoot": "",
|
||||
"error-posting-comment": "Error posting comment",
|
||||
"error-printing-map": "打印地图失败",
|
||||
"error-reading-file": "读取文件错误",
|
||||
@@ -107,6 +108,7 @@
|
||||
"error-saving-trail": "保存路线失败",
|
||||
"error-setting-up-strava-integration": "",
|
||||
"error-updating-password": "更新密码失败",
|
||||
"error-updating-strava-integration": "",
|
||||
"est-duration": "预计时长",
|
||||
"explore": "探索",
|
||||
"explore-some-trails": "探索行程",
|
||||
@@ -143,6 +145,8 @@
|
||||
"import": "导入",
|
||||
"import-hint": "在此选择或拖拽GPX、FIT、KML或TCX文件...",
|
||||
"include-description": "包含描述",
|
||||
"integration-description-komoot": "",
|
||||
"integration-description-strava": "",
|
||||
"integrations": "",
|
||||
"invalid-date": "无效日期",
|
||||
"invalid-username": "无效用户名",
|
||||
@@ -257,6 +261,7 @@
|
||||
"settings-privacy-lists-public": "Your lists are public by default. Everyone will be able to see them. You can change this setting at any point for individual lists.",
|
||||
"settings-privacy-trails-private": "Your trails are private by default. No one except you will be able to see them. You can change this setting at any point for individual trails.",
|
||||
"settings-privacy-trails-public": "Your trails are public by default. Everyone will be able to see them. You can change this setting at any point for individual trails.",
|
||||
"settings-saved": "",
|
||||
"share": "分享",
|
||||
"share-profile": "Share profile",
|
||||
"share-this-list": "分享此列表",
|
||||
|
||||
@@ -12,14 +12,22 @@ const StravaSchema = z.object({
|
||||
active: z.boolean()
|
||||
})
|
||||
|
||||
const KomootSchema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string(),
|
||||
active: z.boolean()
|
||||
})
|
||||
|
||||
const IntegrationCreateSchema = z.object({
|
||||
user: z.string().length(15),
|
||||
strava: StravaSchema,
|
||||
strava: StravaSchema.optional(),
|
||||
komoot: KomootSchema.optional()
|
||||
|
||||
}) satisfies ZodType<Integration>
|
||||
|
||||
const IntegrationUpdateSchema = z.object({
|
||||
strava: StravaSchema.optional(),
|
||||
strava: StravaSchema.optional().nullable(),
|
||||
komoot: KomootSchema.optional().nullable()
|
||||
}) satisfies ZodType<Partial<Integration>>
|
||||
|
||||
export { StravaSchema, IntegrationCreateSchema, IntegrationUpdateSchema }
|
||||
export { StravaSchema, IntegrationCreateSchema, IntegrationUpdateSchema, KomootSchema };
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
|
||||
interface BaseIntegration {
|
||||
export interface BaseIntegration {
|
||||
active: boolean
|
||||
}
|
||||
|
||||
interface StravaIntegration extends BaseIntegration {
|
||||
export interface StravaIntegration extends BaseIntegration {
|
||||
clientId: string | number;
|
||||
clientSecret: string;
|
||||
routes: boolean;
|
||||
@@ -13,13 +13,21 @@ interface StravaIntegration extends BaseIntegration {
|
||||
expiresAt?: number;
|
||||
}
|
||||
|
||||
export interface KomootIntegration extends BaseIntegration {
|
||||
email: string,
|
||||
password: string,
|
||||
}
|
||||
|
||||
|
||||
export class Integration {
|
||||
id?: string;
|
||||
user: string;
|
||||
strava?: StravaIntegration;
|
||||
strava?: StravaIntegration | null;
|
||||
komoot?: KomootIntegration | null
|
||||
|
||||
constructor(user: string, strava?: StravaIntegration) {
|
||||
constructor(user: string, strava?: StravaIntegration, komoot?: KomootIntegration) {
|
||||
this.user = user;
|
||||
this.strava = strava;
|
||||
this.komoot = komoot;
|
||||
}
|
||||
}
|
||||
@@ -361,7 +361,7 @@
|
||||
onclick={(data) => selectTrail(data.trail)}
|
||||
></ListPanel>
|
||||
{:else if selectedList && selectedTrail}
|
||||
<TrailInfoPanel trail={selectedTrail} mode="list" {markers}
|
||||
<TrailInfoPanel initTrail={selectedTrail} mode="list" {markers}
|
||||
></TrailInfoPanel>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</svelte:head>
|
||||
<main class="grid grid-cols-1 md:grid-cols-[458px_1fr] gap-x-1 gap-y-4">
|
||||
<div id="panel" class="hidden md:block">
|
||||
<TrailInfoPanel trail={$trail} {markers}></TrailInfoPanel>
|
||||
<TrailInfoPanel initTrail={$trail} {markers}></TrailInfoPanel>
|
||||
</div>
|
||||
<div id="trail-details">
|
||||
<MapWithElevationMaplibre
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/state";
|
||||
import Button from "$lib/components/base/button.svelte";
|
||||
import Modal from "$lib/components/base/modal.svelte";
|
||||
import TextField from "$lib/components/base/text_field.svelte";
|
||||
import Toggle from "$lib/components/base/toggle.svelte";
|
||||
import { StravaSchema } from "$lib/models/api/integration_schema.js";
|
||||
import { Integration } from "$lib/models/integration.js";
|
||||
import IntegrationCard from "$lib/components/settings/integrations/integration_card.svelte";
|
||||
import KomootSettingsModal from "$lib/components/settings/integrations/komoot_settings_modal.svelte";
|
||||
import StravaSettingsModal from "$lib/components/settings/integrations/strava_settings_modal.svelte";
|
||||
import {
|
||||
Integration,
|
||||
type BaseIntegration,
|
||||
type KomootIntegration,
|
||||
type StravaIntegration,
|
||||
} from "$lib/models/integration.js";
|
||||
import {
|
||||
integrations_create,
|
||||
integrations_update,
|
||||
} from "$lib/stores/integration_store.js";
|
||||
import { show_toast } from "$lib/stores/toast_store.js";
|
||||
import { validator } from "@felte/validator-zod";
|
||||
import { createForm } from "felte";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
let { data } = $props();
|
||||
@@ -22,54 +23,44 @@
|
||||
const scope = "read_all,activity:read_all";
|
||||
const redirectUri = page.url.href + "/callback/strava";
|
||||
|
||||
let stravaSettingsModal: Modal;
|
||||
let stravaSettingsModal: StravaSettingsModal;
|
||||
let stravaToggleValue: boolean = $derived(
|
||||
integration?.strava?.active || false,
|
||||
);
|
||||
|
||||
const { form, errors, data: d } = createForm({
|
||||
initialValues: {
|
||||
clientId: data.integration?.strava?.clientId ?? "",
|
||||
clientSecret: data.integration?.strava?.clientSecret ?? "",
|
||||
routes: data.integration?.strava?.routes ?? true,
|
||||
activities: data.integration?.strava?.activities ?? true,
|
||||
active: data.integration?.strava?.active ?? false,
|
||||
},
|
||||
extend: validator({
|
||||
schema: StravaSchema,
|
||||
}),
|
||||
onSubmit: async (form) => {
|
||||
stravaSettingsModal.closeModal();
|
||||
let komootSettingsModal: KomootSettingsModal;
|
||||
let komootToggleValue: boolean = $state(data.integration?.komoot?.active ?? false);
|
||||
|
||||
try {
|
||||
if (integration) {
|
||||
integration.strava = {
|
||||
clientId: form.clientId,
|
||||
clientSecret: form.clientSecret,
|
||||
routes: form.routes,
|
||||
activities: form.activities,
|
||||
active: integration.strava?.active ?? false,
|
||||
};
|
||||
integration = await integrations_update(integration);
|
||||
} else {
|
||||
const newIntegration = new Integration("", {
|
||||
routes: form.routes,
|
||||
activities: form.activities,
|
||||
clientId: form.clientId,
|
||||
clientSecret: form.clientSecret,
|
||||
active: false,
|
||||
});
|
||||
integration = await integrations_create(newIntegration);
|
||||
}
|
||||
} catch (e) {
|
||||
show_toast({
|
||||
text: $_("error-setting-up-strava-integration"),
|
||||
icon: "close",
|
||||
type: "error",
|
||||
});
|
||||
async function onSettingsSave(
|
||||
form: StravaIntegration | KomootIntegration,
|
||||
key: "strava" | "komoot",
|
||||
) {
|
||||
try {
|
||||
if (integration) {
|
||||
integration[key] = form as any;
|
||||
integration = await integrations_update(integration);
|
||||
} else {
|
||||
const newIntegration: Integration = {
|
||||
user: "",
|
||||
[key]: form,
|
||||
};
|
||||
integration = await integrations_create(newIntegration);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
show_toast({
|
||||
text: $_("settings-saved"),
|
||||
icon: "check",
|
||||
type: "success",
|
||||
});
|
||||
} catch (e) {
|
||||
show_toast({
|
||||
text: $_("error-setting-up-strava-integration"),
|
||||
icon: "close",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function onStravaToggle(value: boolean) {
|
||||
if (!integration?.strava) {
|
||||
return;
|
||||
@@ -80,21 +71,13 @@
|
||||
} else {
|
||||
const deauthUrl = `https://www.strava.com/oauth/deauthorize`;
|
||||
|
||||
const r = await fetch(deauthUrl, {
|
||||
await fetch(deauthUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${integration.strava.accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!r.ok) {
|
||||
show_toast({
|
||||
text: $_("error-disabling-strava-integration"),
|
||||
icon: "close",
|
||||
type: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
integration.strava = {
|
||||
clientId: integration.strava.clientId,
|
||||
clientSecret: integration.strava.clientSecret,
|
||||
@@ -116,6 +99,41 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function onKomootToggle(value: boolean) {
|
||||
if (!integration?.komoot) {
|
||||
return;
|
||||
}
|
||||
if (value) {
|
||||
const authUrl = `https://api.komoot.de/v006/account/email/${integration.komoot.email}/`;
|
||||
const r = await fetch(authUrl, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Basic ${btoa(integration.komoot.email + ":" + integration.komoot.password)}`,
|
||||
},
|
||||
});
|
||||
if (!r.ok) {
|
||||
komootToggleValue = false;
|
||||
show_toast({
|
||||
text: $_("error-logging-in-to-komoot"),
|
||||
icon: "close",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
integration.komoot.active = true;
|
||||
} else {
|
||||
integration.komoot.active = false;
|
||||
}
|
||||
try {
|
||||
integration = await integrations_update(integration);
|
||||
} catch (e) {
|
||||
show_toast({
|
||||
text: $_("error-updating-strava-integration"),
|
||||
icon: "close",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -125,77 +143,35 @@
|
||||
<h3 class="text-2xl font-semibold">{$_("integrations")}</h3>
|
||||
<hr class="mt-4 mb-6 border-input-border" />
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
|
||||
<div class="border border-input-border rounded-lg p-4 space-y-4">
|
||||
<img
|
||||
src="https://upload.wikimedia.org/wikipedia/commons/c/cb/Strava_Logo.svg"
|
||||
alt="strava logo"
|
||||
/>
|
||||
<div>
|
||||
<h5 class="text-xl font-semibold">strava</h5>
|
||||
<p class="text-sm text-gray-500">
|
||||
Syncs your strava routes with wanderer in regular intervals.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<button
|
||||
class="btn-secondary"
|
||||
onclick={() => stravaSettingsModal.openModal()}
|
||||
><i class="fa fa-cogs mr-2"></i>{$_("settings")}</button
|
||||
>
|
||||
<Toggle
|
||||
value={stravaToggleValue}
|
||||
onchange={onStravaToggle}
|
||||
disabled={!integration?.strava}
|
||||
></Toggle>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<IntegrationCard
|
||||
img="https://upload.wikimedia.org/wikipedia/commons/c/cb/Strava_Logo.svg"
|
||||
title="strava"
|
||||
description={$_("integration-description-strava")}
|
||||
disabled={!integration?.strava}
|
||||
active={stravaToggleValue}
|
||||
onclick={() => stravaSettingsModal.openModal()}
|
||||
ontoggle={onStravaToggle}
|
||||
></IntegrationCard>
|
||||
<IntegrationCard
|
||||
img="https://upload.wikimedia.org/wikipedia/commons/8/82/Komoot-logo-type.svg"
|
||||
title="komoot"
|
||||
description={$_("integration-description-komoot")}
|
||||
disabled={!integration?.komoot}
|
||||
bind:active={komootToggleValue}
|
||||
onclick={() => komootSettingsModal.openModal()}
|
||||
ontoggle={onKomootToggle}
|
||||
></IntegrationCard>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
id="strava-settings-modal"
|
||||
size="max-w-lg"
|
||||
title={"strava " + $_("settings")}
|
||||
<StravaSettingsModal
|
||||
bind:this={stravaSettingsModal}
|
||||
>
|
||||
{#snippet content()}
|
||||
<form id="strava-settings-form" class="space-y-2" use:form>
|
||||
<TextField
|
||||
label="Client-ID"
|
||||
placeholder="000000"
|
||||
name="clientId"
|
||||
error={$errors.clientId}
|
||||
></TextField>
|
||||
<TextField
|
||||
label="Client Secret"
|
||||
placeholder="de8b3789bd7116d..."
|
||||
name="clientSecret"
|
||||
type="password"
|
||||
error={$errors.clientSecret}
|
||||
></TextField>
|
||||
<div class="flex gap-x-4">
|
||||
<Toggle name="routes" label={$_("route", { values: { n: 2 } })}
|
||||
></Toggle>
|
||||
<Toggle
|
||||
name="activities"
|
||||
label={$_("activity", { values: { n: 2 } })}
|
||||
></Toggle>
|
||||
</div>
|
||||
</form>
|
||||
{/snippet}
|
||||
{#snippet footer()}
|
||||
<div class="flex items-center gap-4">
|
||||
<button
|
||||
class="btn-secondary"
|
||||
onclick={() => stravaSettingsModal.closeModal()}
|
||||
>{$_("cancel")}</button
|
||||
>
|
||||
<button
|
||||
class="btn-primary"
|
||||
form="strava-settings-form"
|
||||
type="submit"
|
||||
name="save">{$_("save")}</button
|
||||
>
|
||||
</div>
|
||||
{/snippet}</Modal
|
||||
>
|
||||
{integration}
|
||||
onsave={(form) => onSettingsSave(form, "strava")}
|
||||
></StravaSettingsModal>
|
||||
|
||||
<KomootSettingsModal
|
||||
bind:this={komootSettingsModal}
|
||||
{integration}
|
||||
onsave={(form) => onSettingsSave(form, "komoot")}
|
||||
></KomootSettingsModal>
|
||||
|
||||
@@ -4,6 +4,10 @@ import { error, redirect, type RequestEvent, type ServerLoad } from "@sveltejs/k
|
||||
export const load: ServerLoad = async ({ url, fetch }) => {
|
||||
const oauthError = url.searchParams.get('error');
|
||||
if (oauthError) {
|
||||
// user cancelled
|
||||
if(oauthError == "access_denied") {
|
||||
return redirect(302, '/settings/integrations')
|
||||
}
|
||||
return error(400, {
|
||||
message: oauthError
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{#if data.trail}
|
||||
<TrailInfoPanel
|
||||
activeTab={parseInt(page.url.searchParams.get("t") ?? "0")}
|
||||
trail={data.trail}
|
||||
initTrail={data.trail}
|
||||
mode="overview"
|
||||
></TrailInfoPanel>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user