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}
|
||||
|
||||
Reference in New Issue
Block a user