adds komoot integration

This commit is contained in:
Christian Beutel
2025-02-04 21:23:54 +01:00
parent a0b6480854
commit 4d1d07922a
32 changed files with 1373 additions and 189 deletions

View File

@@ -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'}"
>

View File

@@ -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>

View File

@@ -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
>

View File

@@ -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
>

View File

@@ -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

View File

@@ -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"

View File

@@ -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}

View File

@@ -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}