Merge branch 'main' into i10n_main
This commit is contained in:
@@ -48,6 +48,7 @@ function isFormContentType(request: Request) {
|
||||
);
|
||||
}
|
||||
|
||||
let publicMeilisearchKey: string | undefined = undefined;
|
||||
|
||||
const auth: Handle = async ({ event, resolve }) => {
|
||||
const pb = new PocketBase(envPub.PUBLIC_POCKETBASE_URL)
|
||||
@@ -82,10 +83,14 @@ const auth: Handle = async ({ event, resolve }) => {
|
||||
if (pb.authStore.record) {
|
||||
meiliApiKey = pb.authStore.record.token
|
||||
settings = await pb.collection('settings').getFirstListItem<Settings>(`user="${pb.authStore.record.id}"`, { requestKey: null })
|
||||
actor = await pb.collection("activitypub_actors").getFirstListItem(`user='${pb.authStore.record.id}'`)
|
||||
actor = await pb.collection("activitypub_actors").getFirstListItem(`isLocal=1&&user='${pb.authStore.record.id}'`)
|
||||
} else {
|
||||
const response = await pb.send("/public/search/token", { method: "GET", fetch: event.fetch });
|
||||
meiliApiKey = response.token;
|
||||
if (!publicMeilisearchKey) {
|
||||
const response = await pb.send("/public/search/token", { method: "GET", fetch: event.fetch });
|
||||
publicMeilisearchKey = response.token;
|
||||
}
|
||||
|
||||
meiliApiKey = publicMeilisearchKey!;
|
||||
}
|
||||
const ms = new MeiliSearch({ host: env.MEILI_URL, apiKey: meiliApiKey });
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import Datepicker from "$lib/components/base/datepicker.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";
|
||||
@@ -25,23 +26,32 @@
|
||||
modal.openModal();
|
||||
}
|
||||
|
||||
const { form, errors } = createForm({
|
||||
const {
|
||||
form,
|
||||
errors,
|
||||
data: formData,
|
||||
} = 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,
|
||||
after: integration?.strava?.after,
|
||||
},
|
||||
extend: validator({
|
||||
schema: StravaSchema,
|
||||
}),
|
||||
onSubmit: async (form) => {
|
||||
form.active = integration?.strava?.active ?? form.active
|
||||
form.active = integration?.strava?.active ?? form.active;
|
||||
onsave?.(form);
|
||||
modal.closeModal();
|
||||
},
|
||||
});
|
||||
|
||||
function clearAfterDate() {
|
||||
($formData as any).after = undefined;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
@@ -60,7 +70,9 @@
|
||||
></TextField>
|
||||
<TextField
|
||||
label="Client Secret"
|
||||
placeholder={integration?.strava ? `(${$_("unchanged")})` : "de8b3789bd7116d..."}
|
||||
placeholder={integration?.strava
|
||||
? `(${$_("unchanged")})`
|
||||
: "de8b3789bd7116d..."}
|
||||
name="clientSecret"
|
||||
type="password"
|
||||
error={$errors.clientSecret}
|
||||
@@ -73,6 +85,25 @@
|
||||
label={$_("activity", { values: { n: 2 } })}
|
||||
></Toggle>
|
||||
</div>
|
||||
<p
|
||||
class="text-xs text-gray-500 max-w-lg pt-4 pb-1 border-t border-input-border"
|
||||
>
|
||||
{$_("strava-integration-after-date-hint")}
|
||||
</p>
|
||||
<div class="flex items-end relative gap-x-2">
|
||||
<Datepicker
|
||||
error={$errors.after}
|
||||
label={$_("after")}
|
||||
bind:value={$formData.after}
|
||||
></Datepicker>
|
||||
<button
|
||||
class="btn-icon mb-[10px]"
|
||||
type="button"
|
||||
onclick={clearAfterDate}
|
||||
aria-label="Clear 'after' date"
|
||||
><i class="fa fa-close"></i></button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
{/snippet}
|
||||
{#snippet footer()}
|
||||
|
||||
@@ -51,9 +51,9 @@
|
||||
];
|
||||
|
||||
const difficultyItems: SelectItem[] = [
|
||||
{ text: $_("easy"), value: "easy" },
|
||||
{ text: $_("moderate"), value: "moderate" },
|
||||
{ text: $_("difficult"), value: "difficult" },
|
||||
{ text: $_("easy"), value: 0 },
|
||||
{ text: $_("moderate"), value: 1 },
|
||||
{ text: $_("difficult"), value: 2 },
|
||||
];
|
||||
|
||||
let searchDropdownItems: SearchItem[] = $state([]);
|
||||
|
||||
@@ -658,7 +658,7 @@
|
||||
<MapWithElevationMaplibre
|
||||
trails={[trail]}
|
||||
activeTrail={0}
|
||||
waypoints={trail.expand?.waypoints}
|
||||
waypoints={trail.expand?.waypoints_via_trail}
|
||||
showElevation={true}
|
||||
elevationProfileContainer={"epc-container"}
|
||||
showStyleSwitcher={false}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<div class="">
|
||||
<p class="font-semibold">{$_("start")}</p>
|
||||
</div>
|
||||
{#each trail.expand?.waypoints ?? [] as wp, i}
|
||||
{#each trail.expand?.waypoints_via_trail ?? [] as wp, i}
|
||||
<div
|
||||
class="bg-background cursor-pointer"
|
||||
role="presentation"
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"bakery": "Bäckerei",
|
||||
"barrier": "Barriere",
|
||||
"basic-info": "Basisinformation",
|
||||
"basque": "Baskisch",
|
||||
"before": "Vor",
|
||||
"bicycle-parking": "Fahrrad Parkplatz",
|
||||
"bicycle-rental": "Fahrradverleih",
|
||||
@@ -56,7 +57,6 @@
|
||||
"car": "Auto",
|
||||
"car-motorcycle": "Auto/Motorrad",
|
||||
"card": "{n, plural, =1 {Karte} other {Karten}}",
|
||||
"basque": "Baskisch",
|
||||
"categories": "Kategorien",
|
||||
"category": "Kategorie",
|
||||
"change": "Ändern",
|
||||
@@ -390,6 +390,7 @@
|
||||
"statistics": "Statistiken",
|
||||
"stop-drawing": "Zeichnen beenden",
|
||||
"stop-editing": "Bearbeiten beenden",
|
||||
"strava-integration-after-date-hint": "",
|
||||
"subway-stop": "U-Bahn Eingang",
|
||||
"summit": "Gipfel",
|
||||
"summit-book": "Gipfelbuch",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"bakery": "Bakery",
|
||||
"barrier": "Barrier",
|
||||
"basic-info": "Basic Info",
|
||||
"basque": "Basque",
|
||||
"before": "Before",
|
||||
"bicycle-parking": "Bicycle Parking",
|
||||
"bicycle-rental": "Bicycle Rental",
|
||||
@@ -56,7 +57,6 @@
|
||||
"car": "Car",
|
||||
"car-motorcycle": "Car/Motorcycle",
|
||||
"card": "{n, plural, =1 {Card} other {Cards}}",
|
||||
"basque": "Basque",
|
||||
"categories": "Categories",
|
||||
"category": "Category",
|
||||
"change": "Change",
|
||||
@@ -390,6 +390,7 @@
|
||||
"statistics": "Statistics",
|
||||
"stop-drawing": "Stop drawing",
|
||||
"stop-editing": "Stop editing",
|
||||
"strava-integration-after-date-hint": "If your account has a large amount of acitivities you may run into Strava's API rate limit preventing you from syncing all activities at once. To mitigate this issue you can set an \"After\" date below so that only activities that were recorded after this date are synced.",
|
||||
"subway-stop": "Subway entrance",
|
||||
"summit": "Summit",
|
||||
"summit-book": "Summit Book",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"bakery": "Bakery",
|
||||
"barrier": "Barrier",
|
||||
"basic-info": "Información básica",
|
||||
"basque": "Basque",
|
||||
"before": "Antes",
|
||||
"bicycle-parking": "Bicycle Parking",
|
||||
"bicycle-rental": "Bicycle Rental",
|
||||
@@ -56,7 +57,6 @@
|
||||
"car": "Coche",
|
||||
"car-motorcycle": "Car/Motorcycle",
|
||||
"card": "{n, plural, one {}=1 {Ficha} other {Fichas}}",
|
||||
"basque": "Basque",
|
||||
"categories": "Categorías",
|
||||
"category": "Categoría",
|
||||
"change": "Modificar",
|
||||
@@ -390,6 +390,7 @@
|
||||
"statistics": "Estadísticas",
|
||||
"stop-drawing": "Parar de diseñar",
|
||||
"stop-editing": "Parar de editar",
|
||||
"strava-integration-after-date-hint": "",
|
||||
"subway-stop": "Subway entrance",
|
||||
"summit": "Summit",
|
||||
"summit-book": "Libro de ascensos",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"bakery": "Okindegia",
|
||||
"barrier": "Oztopoa",
|
||||
"basic-info": "Oinarrizko informazioa",
|
||||
"basque": "Basque",
|
||||
"before": "Aurretik",
|
||||
"bicycle-parking": "Bizikleta-parkina",
|
||||
"bicycle-rental": "Bizikleta-alokairua",
|
||||
@@ -56,7 +57,6 @@
|
||||
"car": "Kotxea",
|
||||
"car-motorcycle": "Kotxea/Motozikleta",
|
||||
"card": "{n, plural, one {}=1 {Txartea} other {Txartelak}}",
|
||||
"basque": "Basque",
|
||||
"categories": "Kategoriak",
|
||||
"category": "Kategoria",
|
||||
"change": "Aldatu",
|
||||
@@ -390,6 +390,7 @@
|
||||
"statistics": "Estatistikak",
|
||||
"stop-drawing": "Utzi marrazteari",
|
||||
"stop-editing": "Utzi editatzeari",
|
||||
"strava-integration-after-date-hint": "",
|
||||
"subway-stop": "Metro sarbidea",
|
||||
"summit": "Gailurra",
|
||||
"summit-book": "Igoeren liburua",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"bakery": "Bakery",
|
||||
"barrier": "Barrier",
|
||||
"basic-info": "Informations de base",
|
||||
"basque": "Basque",
|
||||
"before": "Avant le",
|
||||
"bicycle-parking": "Bicycle Parking",
|
||||
"bicycle-rental": "Bicycle Rental",
|
||||
@@ -56,7 +57,6 @@
|
||||
"car": "Voiture",
|
||||
"car-motorcycle": "Car/Motorcycle",
|
||||
"card": "{n, plural, =1 {Tuile} other {Tuiles}}",
|
||||
"basque": "Basque",
|
||||
"categories": "Catégories",
|
||||
"category": "Catégorie",
|
||||
"change": "Changement",
|
||||
@@ -390,6 +390,7 @@
|
||||
"statistics": "Statistiques",
|
||||
"stop-drawing": "Arrêter de tracer",
|
||||
"stop-editing": "Arrêter la modification",
|
||||
"strava-integration-after-date-hint": "",
|
||||
"subway-stop": "Subway entrance",
|
||||
"summit": "Summit",
|
||||
"summit-book": "Liste des ascensions",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"bakery": "Bakery",
|
||||
"barrier": "Barrier",
|
||||
"basic-info": "Alap információk",
|
||||
"basque": "Basque",
|
||||
"before": "Before",
|
||||
"bicycle-parking": "Bicycle Parking",
|
||||
"bicycle-rental": "Bicycle Rental",
|
||||
@@ -56,7 +57,6 @@
|
||||
"car": "Car",
|
||||
"car-motorcycle": "Car/Motorcycle",
|
||||
"card": "{n, plural, =1 {Kártya} other {Kártyák}}",
|
||||
"basque": "Basque",
|
||||
"categories": "Kategóriák",
|
||||
"category": "Kategória",
|
||||
"change": "Változás",
|
||||
@@ -390,6 +390,7 @@
|
||||
"statistics": "Statistics",
|
||||
"stop-drawing": "Stop drawing",
|
||||
"stop-editing": "Stop editing",
|
||||
"strava-integration-after-date-hint": "",
|
||||
"subway-stop": "Subway entrance",
|
||||
"summit": "Summit",
|
||||
"summit-book": "Csúcspont könyv",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"bakery": "Bakery",
|
||||
"barrier": "Barrier",
|
||||
"basic-info": "Informazioni di base",
|
||||
"basque": "Basque",
|
||||
"before": "Prima",
|
||||
"bicycle-parking": "Bicycle Parking",
|
||||
"bicycle-rental": "Bicycle Rental",
|
||||
@@ -56,7 +57,6 @@
|
||||
"car": "Car",
|
||||
"car-motorcycle": "Car/Motorcycle",
|
||||
"card": "{n, plural, =1 {Carta} other {Carte}}",
|
||||
"basque": "Basque",
|
||||
"categories": "Categorie",
|
||||
"category": "Categoria",
|
||||
"change": "Modifica",
|
||||
@@ -390,6 +390,7 @@
|
||||
"statistics": "Statistiche",
|
||||
"stop-drawing": "Smettere di disegnare",
|
||||
"stop-editing": "Stop editing",
|
||||
"strava-integration-after-date-hint": "",
|
||||
"subway-stop": "Subway entrance",
|
||||
"summit": "Summit",
|
||||
"summit-book": "Libro di vetta",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"bakery": "Bakery",
|
||||
"barrier": "Barrier",
|
||||
"basic-info": "Algemene informatie",
|
||||
"basque": "Basque",
|
||||
"before": "Voor",
|
||||
"bicycle-parking": "Bicycle Parking",
|
||||
"bicycle-rental": "Bicycle Rental",
|
||||
@@ -56,7 +57,6 @@
|
||||
"car": "Wagen",
|
||||
"car-motorcycle": "Car/Motorcycle",
|
||||
"card": "{n, plural, =1 {Kaart} other {Kaarten}}",
|
||||
"basque": "Basque",
|
||||
"categories": "Categorieën",
|
||||
"category": "Categorie",
|
||||
"change": "Wijzigen",
|
||||
@@ -390,6 +390,7 @@
|
||||
"statistics": "Statistieken",
|
||||
"stop-drawing": "Stop met tekenen",
|
||||
"stop-editing": "Stop met bewerken",
|
||||
"strava-integration-after-date-hint": "",
|
||||
"subway-stop": "Subway entrance",
|
||||
"summit": "Summit",
|
||||
"summit-book": "Bergtopboek",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"bakery": "Bakery",
|
||||
"barrier": "Barrier",
|
||||
"basic-info": "Podstawowe informacje",
|
||||
"basque": "Basque",
|
||||
"before": "Przed",
|
||||
"bicycle-parking": "Bicycle Parking",
|
||||
"bicycle-rental": "Bicycle Rental",
|
||||
@@ -56,7 +57,6 @@
|
||||
"car": "Samochód",
|
||||
"car-motorcycle": "Car/Motorcycle",
|
||||
"card": "{n, plural, =1 {Karta} other {Karty}}",
|
||||
"basque": "Basque",
|
||||
"categories": "Kategorie",
|
||||
"category": "Kategoria",
|
||||
"change": "Zmień",
|
||||
@@ -390,6 +390,7 @@
|
||||
"statistics": "Statystyki",
|
||||
"stop-drawing": "Przestań rysować",
|
||||
"stop-editing": "Zakończ edycję",
|
||||
"strava-integration-after-date-hint": "",
|
||||
"subway-stop": "Subway entrance",
|
||||
"summit": "Summit",
|
||||
"summit-book": "Logbook",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"bakery": "Bakery",
|
||||
"barrier": "Barrier",
|
||||
"basic-info": "Informações básicas",
|
||||
"basque": "Basque",
|
||||
"before": "Antes",
|
||||
"bicycle-parking": "Bicycle Parking",
|
||||
"bicycle-rental": "Bicycle Rental",
|
||||
@@ -56,7 +57,6 @@
|
||||
"car": "Car",
|
||||
"car-motorcycle": "Car/Motorcycle",
|
||||
"card": "{n, plural, =1 {Cartão} other {Cartões}}",
|
||||
"basque": "Basque",
|
||||
"categories": "Categorias",
|
||||
"category": "Categoria",
|
||||
"change": "Alterar",
|
||||
@@ -390,6 +390,7 @@
|
||||
"statistics": "Statistics",
|
||||
"stop-drawing": "Parar desenho",
|
||||
"stop-editing": "Stop editing",
|
||||
"strava-integration-after-date-hint": "",
|
||||
"subway-stop": "Subway entrance",
|
||||
"summit": "Summit",
|
||||
"summit-book": "Livro da cimeira",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"bakery": "Bakery",
|
||||
"barrier": "Barrier",
|
||||
"basic-info": "Основная информация",
|
||||
"basque": "Basque",
|
||||
"before": "До",
|
||||
"bicycle-parking": "Bicycle Parking",
|
||||
"bicycle-rental": "Bicycle Rental",
|
||||
@@ -56,7 +57,6 @@
|
||||
"car": "Автомобиль:",
|
||||
"car-motorcycle": "Car/Motorcycle",
|
||||
"card": "{n, plural, =1 {Карточка} other {Карточки}}",
|
||||
"basque": "Basque",
|
||||
"categories": "Категории",
|
||||
"category": "Категория",
|
||||
"change": "Изменить",
|
||||
@@ -390,6 +390,7 @@
|
||||
"statistics": "Статистика",
|
||||
"stop-drawing": "Закончить рисование",
|
||||
"stop-editing": "Закончить редактирование",
|
||||
"strava-integration-after-date-hint": "",
|
||||
"subway-stop": "Subway entrance",
|
||||
"summit": "Summit",
|
||||
"summit-book": "История поездок",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"bakery": "面包店",
|
||||
"barrier": "障碍",
|
||||
"basic-info": "基本信息",
|
||||
"basque": "Basque",
|
||||
"before": "之前",
|
||||
"bicycle-parking": "自行车停车场",
|
||||
"bicycle-rental": "自行车租车",
|
||||
@@ -56,7 +57,6 @@
|
||||
"car": "汽车",
|
||||
"car-motorcycle": "汽车/摩托车",
|
||||
"card": "{n, plural, =1 {卡片} other {卡片}}",
|
||||
"basque": "Basque",
|
||||
"categories": "分类",
|
||||
"category": "分类",
|
||||
"change": "更换",
|
||||
@@ -390,6 +390,7 @@
|
||||
"statistics": "统计",
|
||||
"stop-drawing": "停止绘制",
|
||||
"stop-editing": "停止编辑",
|
||||
"strava-integration-after-date-hint": "",
|
||||
"subway-stop": "地铁入口",
|
||||
"summit": "山峰",
|
||||
"summit-book": "详细日程",
|
||||
|
||||
@@ -6,7 +6,8 @@ const StravaSchema = z.object({
|
||||
clientSecret: z.string().length(40).optional().or(z.literal('')),
|
||||
routes: z.boolean(),
|
||||
activities: z.boolean(),
|
||||
active: z.boolean()
|
||||
active: z.boolean(),
|
||||
after: z.string().date().optional(),
|
||||
})
|
||||
|
||||
const KomootSchema = z.object({
|
||||
|
||||
@@ -18,7 +18,6 @@ const TrailCreateSchema = z.object({
|
||||
duration: z.number({ coerce: true }).nonnegative().optional(),
|
||||
photos: z.array(z.string()).default([]),
|
||||
thumbnail: z.number().int().nonnegative().optional(),
|
||||
waypoints: z.array(z.string()).default([]),
|
||||
like_count: z.number().int().min(0).optional().default(0),
|
||||
category: z.string().length(15).optional().or(z.literal('')),
|
||||
tags: z.array(z.string()).default([]),
|
||||
@@ -44,7 +43,6 @@ const TrailUpdateSchema = z.object({
|
||||
"photos-": z.string().optional(),
|
||||
"photos+": z.string().optional(),
|
||||
thumbnail: z.number().int().nonnegative().optional(),
|
||||
waypoints: z.array(z.string()).optional(),
|
||||
like_count: z.number().int().min(0).optional().default(0),
|
||||
category: z.string().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
|
||||
@@ -6,20 +6,21 @@ const WaypointCreateSchema = z.object({
|
||||
id: z.string().length(15).optional(),
|
||||
name: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
lat: z.number({coerce: true}).min(-90).max(90),
|
||||
lon: z.number({coerce: true}).min(-180).max(180),
|
||||
distance_from_start: z.number({coerce: true}).min(0).optional(),
|
||||
lat: z.number({ coerce: true }).min(-90).max(90),
|
||||
lon: z.number({ coerce: true }).min(-180).max(180),
|
||||
distance_from_start: z.number({ coerce: true }).min(0).optional(),
|
||||
icon: z.enum(icons).optional(),
|
||||
author: z.string().length(15),
|
||||
photos: z.array(z.string()).default([]),
|
||||
trail: z.string().length(15).optional()
|
||||
}) satisfies ZodType<Partial<Waypoint>>
|
||||
|
||||
const WaypointUpdateSchema = z.object({
|
||||
name: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
lat: z.number({coerce: true}).min(-90).max(90).optional(),
|
||||
lon: z.number({coerce: true}).min(-180).max(180).optional(),
|
||||
distance_from_start: z.number({coerce: true}).min(0).optional(),
|
||||
lat: z.number({ coerce: true }).min(-90).max(90).optional(),
|
||||
lon: z.number({ coerce: true }).min(-180).max(180).optional(),
|
||||
distance_from_start: z.number({ coerce: true }).min(0).optional(),
|
||||
icon: z.enum(icons).default("circle").optional(),
|
||||
photos: z.array(z.string()).optional(),
|
||||
"photos-": z.string().optional(),
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface StravaIntegration extends BaseIntegration {
|
||||
accessToken?: string;
|
||||
refreshToken?: string;
|
||||
expiresAt?: number;
|
||||
after?: string
|
||||
}
|
||||
|
||||
export interface KomootIntegration extends BaseIntegration {
|
||||
|
||||
@@ -28,7 +28,6 @@ class Trail {
|
||||
updated?: string;
|
||||
category?: string;
|
||||
tags: string[];
|
||||
waypoints: string[];
|
||||
polyline?: string;
|
||||
domain?: string;
|
||||
iri?: string;
|
||||
@@ -36,7 +35,7 @@ class Trail {
|
||||
expand?: {
|
||||
tags?: Tag[]
|
||||
category?: Category;
|
||||
waypoints?: Waypoint[]
|
||||
waypoints_via_trail?: Waypoint[]
|
||||
summit_logs_via_trail?: SummitLog[]
|
||||
author?: Actor
|
||||
comments_via_trail?: Comment[]
|
||||
@@ -90,13 +89,12 @@ class Trail {
|
||||
this.lon = params?.lon;
|
||||
this.thumbnail = params?.thumbnail ?? 0;
|
||||
this.photos = params?.photos ?? [];
|
||||
this.waypoints = [];
|
||||
this.tags = []
|
||||
this.gpx = params?.gpx;
|
||||
this.like_count = 0
|
||||
this.expand = {
|
||||
category: params?.category,
|
||||
waypoints: params?.waypoints ?? [],
|
||||
waypoints_via_trail: params?.waypoints ?? [],
|
||||
summit_logs_via_trail: params?.summit_logs ?? [],
|
||||
comments_via_trail: params?.comments ?? [],
|
||||
trail_share_via_trail: params?.shares ?? []
|
||||
@@ -111,7 +109,7 @@ interface TrailFilter {
|
||||
q: string,
|
||||
category: string[],
|
||||
tags: string[],
|
||||
difficulty: ("easy" | "moderate" | "difficult")[]
|
||||
difficulty: (0 | 1 | 2)[]
|
||||
author?: string;
|
||||
public?: boolean;
|
||||
shared?: boolean;
|
||||
@@ -169,7 +167,7 @@ interface TrailSearchResult {
|
||||
elevation_gain: number;
|
||||
elevation_loss: number;
|
||||
duration: number;
|
||||
difficulty: "easy" | "moderate" | "difficult";
|
||||
difficulty: 0 | 1 | 2;
|
||||
category: string;
|
||||
completed: boolean;
|
||||
date: number;
|
||||
|
||||
@@ -13,10 +13,12 @@ class Waypoint {
|
||||
photos: string[];
|
||||
_photos?: File[];
|
||||
author: string;
|
||||
trail?: string;
|
||||
|
||||
constructor(lat: number, lon: number, params?: {
|
||||
id?: string, name?: string, description?: string, icon?: typeof icons[number], marker?: M.Marker, photos?: string[];
|
||||
id?: string, name?: string, description?: string, icon?: typeof icons[number], marker?: M.Marker, photos?: string[], trail?: string
|
||||
}) {
|
||||
this.trail = params?.trail;
|
||||
this.id = params?.id;
|
||||
this.name = params?.name ?? "";
|
||||
this.description = params?.description ?? "";
|
||||
|
||||
@@ -22,7 +22,7 @@ export const editTrail: Writable<Trail> = writable(new Trail(""));
|
||||
export async function trails_index(perPage: number = 21, random: boolean = false, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||
const r = await f('/api/v1/trail?' + new URLSearchParams({
|
||||
"perPage": perPage.toString(),
|
||||
expand: "category,waypoints,summit_logs_via_trail,tags",
|
||||
expand: "category,waypoints_via_trail,summit_logs_via_trail,tags",
|
||||
sort: random ? "@random" : "",
|
||||
}), {
|
||||
method: 'GET',
|
||||
@@ -136,7 +136,7 @@ export async function trails_search_bounding_box(northEast: M.LngLat, southWest:
|
||||
export async function trails_show(id: string, handle?: string, share?: string, loadGPX?: boolean, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||
|
||||
const r = await f(`/api/v1/trail/${id}?` + new URLSearchParams({
|
||||
expand: "category,waypoints,summit_logs_via_trail,summit_logs_via_trail.author,trail_share_via_trail.actor,trail_like_via_trail,tags,author",
|
||||
expand: "category,waypoints_via_trail,summit_logs_via_trail,summit_logs_via_trail.author,trail_share_via_trail.actor,trail_like_via_trail,tags,author",
|
||||
...(handle ? { handle } : {}),
|
||||
...(share ? { share } : {})
|
||||
}), {
|
||||
@@ -148,7 +148,7 @@ export async function trails_show(id: string, handle?: string, share?: string, l
|
||||
throw new APIError(r.status, response.message, response.detail)
|
||||
}
|
||||
|
||||
const response = await r.json()
|
||||
const response: Trail = await r.json()
|
||||
|
||||
if (loadGPX) {
|
||||
if (!response.expand) {
|
||||
@@ -171,8 +171,8 @@ export async function trails_show(id: string, handle?: string, share?: string, l
|
||||
}
|
||||
}
|
||||
|
||||
response.expand.waypoints = response.expand.waypoints || [];
|
||||
response.expand.summit_logs = response.expand.summit_logs?.sort((a: SummitLog, b: SummitLog) => Date.parse(a.date) - Date.parse(b.date)) || [];
|
||||
response.expand!.waypoints_via_trail = response.expand!.waypoints_via_trail || [];
|
||||
response.expand!.summit_logs_via_trail = response.expand!.summit_logs_via_trail?.sort((a: SummitLog, b: SummitLog) => Date.parse(a.date) - Date.parse(b.date)) || [];
|
||||
|
||||
trail.set(response);
|
||||
|
||||
@@ -185,13 +185,6 @@ export async function trails_create(trail: Trail, photos: File[], gpx: File | Bl
|
||||
throw Error("Unauthenticated")
|
||||
}
|
||||
|
||||
for (const waypoint of trail.expand?.waypoints ?? []) {
|
||||
const model = await waypoints_create({
|
||||
...waypoint,
|
||||
marker: undefined,
|
||||
}, f, user);
|
||||
trail.waypoints.push(model.id!);
|
||||
}
|
||||
for (const tag of trail.expand?.tags ?? []) {
|
||||
if (!tag.id) {
|
||||
const model = await tags_create(tag)
|
||||
@@ -214,7 +207,7 @@ export async function trails_create(trail: Trail, photos: File[], gpx: File | Bl
|
||||
}
|
||||
|
||||
let r = await f(`/api/v1/trail/form?` + new URLSearchParams({
|
||||
expand: "category,waypoints,summit_logs_via_trail,trail_share_via_trail,tags",
|
||||
expand: "category,waypoints_via_trail,summit_logs_via_trail,trail_share_via_trail,tags",
|
||||
}), {
|
||||
method: 'PUT',
|
||||
body: formData,
|
||||
@@ -232,6 +225,14 @@ export async function trails_create(trail: Trail, photos: File[], gpx: File | Bl
|
||||
await summit_logs_create(summitLog, f);
|
||||
}
|
||||
|
||||
for (const wp of trail.expand?.waypoints_via_trail ?? []) {
|
||||
wp.trail = model.id!;
|
||||
await waypoints_create({
|
||||
...wp,
|
||||
marker: undefined,
|
||||
}, f, user);
|
||||
}
|
||||
|
||||
return model;
|
||||
|
||||
}
|
||||
@@ -239,18 +240,17 @@ export async function trails_create(trail: Trail, photos: File[], gpx: File | Bl
|
||||
export async function trails_update(oldTrail: Trail, newTrail: Trail, photos?: File[], gpx?: File | Blob | null) {
|
||||
newTrail.author = oldTrail.author
|
||||
|
||||
const waypointUpdates = compareObjectArrays<Waypoint>(oldTrail.expand?.waypoints ?? [], newTrail.expand?.waypoints ?? []);
|
||||
const waypointUpdates = compareObjectArrays<Waypoint>(oldTrail.expand?.waypoints_via_trail ?? [], newTrail.expand?.waypoints_via_trail ?? []);
|
||||
|
||||
for (const addedWaypoint of waypointUpdates.added) {
|
||||
const model = await waypoints_create({
|
||||
...addedWaypoint,
|
||||
marker: undefined,
|
||||
},);
|
||||
newTrail.waypoints.push(model.id!);
|
||||
}
|
||||
|
||||
for (const updatedWaypoint of waypointUpdates.updated) {
|
||||
const oldWaypoint = oldTrail.expand?.waypoints?.find(w => w.id == updatedWaypoint.id);
|
||||
const oldWaypoint = oldTrail.expand?.waypoints_via_trail?.find(w => w.id == updatedWaypoint.id);
|
||||
const model = await waypoints_update(oldWaypoint!, {
|
||||
...updatedWaypoint,
|
||||
marker: undefined,
|
||||
@@ -313,7 +313,7 @@ export async function trails_update(oldTrail: Trail, newTrail: Trail, photos?: F
|
||||
|
||||
|
||||
let r = await fetch(`/api/v1/trail/form/${newTrail.id}?` + new URLSearchParams({
|
||||
expand: "category,waypoints,summit_logs_via_trail,trail_share_via_trail,tags",
|
||||
expand: "category,waypoints_via_trail,summit_logs_via_trail,trail_share_via_trail,tags",
|
||||
}), {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
@@ -340,17 +340,6 @@ export async function trails_update(oldTrail: Trail, newTrail: Trail, photos?: F
|
||||
|
||||
|
||||
export async function trails_delete(trail: Trail) {
|
||||
if (trail.expand?.waypoints) {
|
||||
for (const waypoint of trail.expand.waypoints) {
|
||||
await waypoints_delete(waypoint);
|
||||
}
|
||||
}
|
||||
if (trail.expand?.summit_logs_via_trail) {
|
||||
for (const summit_log of trail.expand.summit_logs_via_trail) {
|
||||
await summit_logs_delete(summit_log);
|
||||
}
|
||||
}
|
||||
|
||||
const r = await fetch('/api/v1/trail/' + trail.id, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
@@ -457,7 +446,7 @@ export async function searchResultToTrailList(hits: Hits<TrailSearchResult>): Pr
|
||||
created: new Date(h.created * 1000).toISOString(),
|
||||
date: new Date(h.date * 1000).toISOString(),
|
||||
description: h.description,
|
||||
difficulty: h.difficulty,
|
||||
difficulty: h.difficulty == 0 ? "easy" : h.difficulty == 1 ? "moderate" : "difficult",
|
||||
distance: h.distance,
|
||||
duration: h.duration,
|
||||
elevation_gain: h.elevation_gain,
|
||||
|
||||
@@ -45,7 +45,7 @@ export async function gpx2trail(gpxString: string, fallbackName?: string, correc
|
||||
wp.id = cryptoRandomString({ length: 15 });
|
||||
wp.name = wpt.name ?? ""
|
||||
wp.description = wpt.desc;
|
||||
trail.expand!.waypoints?.push(wp);
|
||||
trail.expand!.waypoints_via_trail?.push(wp);
|
||||
}
|
||||
|
||||
const totals = gpx.features
|
||||
@@ -108,7 +108,7 @@ export async function trail2gpx(trail: Trail, user?: AuthRecord) {
|
||||
gpx.wpt = [];
|
||||
}
|
||||
|
||||
for (const wp of gpxTrail.expand!.waypoints ?? []) {
|
||||
for (const wp of gpxTrail.expand!.waypoints_via_trail ?? []) {
|
||||
const gpxWpt = gpx.wpt.find((w) => w.$.lat == wp.lat && w.$.lon == wp.lon)
|
||||
if (!gpxWpt) {
|
||||
gpx.wpt.push(new GPXWaypoint({
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function GET(event: RequestEvent) {
|
||||
|
||||
const [username, domain] = splitUsername(fullUsername, env.ORIGIN)
|
||||
|
||||
const actor: Actor = await event.locals.pb.collection("activitypub_actors").getFirstListItem(`preferred_username:lower='${username?.toLowerCase()}'&&isLocal=true`)
|
||||
const actor: Actor = await event.locals.pb.collection("activitypub_actors").getFirstListItem(`preferred_username:lower='${username?.toLowerCase()}'&&isLocal=1`)
|
||||
const user: UserAnonymous = await event.locals.pb.collection("users_anonymous").getOne(actor.user!)
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export async function GET(event: RequestEvent) {
|
||||
t.expand = {} as any
|
||||
}
|
||||
|
||||
t.expand?.waypoints?.sort((a, b) => (a.distance_from_start ?? 0) - (b.distance_from_start ?? 0))
|
||||
t.expand?.waypoints_via_trail?.sort((a, b) => (a.distance_from_start ?? 0) - (b.distance_from_start ?? 0))
|
||||
}
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
|
||||
@@ -84,7 +84,7 @@ export async function GET(event: RequestEvent) {
|
||||
l.expand.author.isLocal = false
|
||||
}
|
||||
})
|
||||
t.expand?.waypoints?.forEach(w => {
|
||||
t.expand?.waypoints_via_trail?.forEach(w => {
|
||||
|
||||
w.photos = w.photos.map(p =>
|
||||
`${origin}/api/v1/files/waypoints/${w.id}/${p}`
|
||||
@@ -131,7 +131,7 @@ export async function GET(event: RequestEvent) {
|
||||
await enrichRecord(event.locals.pb, t);
|
||||
|
||||
// sort waypoints by distance
|
||||
t.expand?.waypoints?.sort((a, b) => (a.distance_from_start ?? 0) - (b.distance_from_start ?? 0))
|
||||
t.expand?.waypoints_via_trail?.sort((a, b) => (a.distance_from_start ?? 0) - (b.distance_from_start ?? 0))
|
||||
return json(t)
|
||||
} catch (e: any) {
|
||||
return handleError(e)
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
let selectedTrailIndex = $derived(selectedTrail ? 0 : null);
|
||||
|
||||
let selectedTrailWaypoints = $derived(
|
||||
(selectedTrail as Trail | null)?.expand?.waypoints,
|
||||
(selectedTrail as Trail | null)?.expand?.waypoints_via_trail,
|
||||
);
|
||||
|
||||
onMount(() => {
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
const trailItems = r[0].hits.map((t: TrailSearchResult) => ({
|
||||
text: t.name,
|
||||
description: `Trail ${t.location.length ? ", " + t.location : ""}`,
|
||||
value: `@${t.author}${t.domain ? `@${t.domain}` : ""}/${t.id}`,
|
||||
value: `@${t.author_name}${t.domain ? `@${t.domain}` : ""}/${t.id}`,
|
||||
icon: "route",
|
||||
}));
|
||||
const listItems = r[1].hits.map((t: ListSearchResult) => ({
|
||||
|
||||
@@ -11,7 +11,7 @@ export const load: ServerLoad = async ({ params, locals, fetch }) => {
|
||||
q: "",
|
||||
category: [],
|
||||
tags: [],
|
||||
difficulty: ["easy", "moderate", "difficult"],
|
||||
difficulty: [0, 1, 2],
|
||||
author: "",
|
||||
public: true,
|
||||
shared: true,
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<div id="trail-details">
|
||||
<MapWithElevationMaplibre
|
||||
trails={[trail]}
|
||||
waypoints={trail.expand?.waypoints}
|
||||
waypoints={trail.expand?.waypoints_via_trail}
|
||||
activeTrail={0}
|
||||
bind:markers
|
||||
showTerrain={true}
|
||||
|
||||
@@ -390,7 +390,7 @@
|
||||
currentHeight += getTextHeight($trail.description, doc, width - 32) + 8;
|
||||
}
|
||||
|
||||
if (includeWaypoints && $trail.expand?.waypoints) {
|
||||
if (includeWaypoints && $trail.expand?.waypoints_via_trail) {
|
||||
const header = $_("waypoints", { values: { n: 2 } })
|
||||
let textHeight = getTextHeight(header, doc, width - 32)
|
||||
if (currentHeight + textHeight + 8 > height) {
|
||||
@@ -401,7 +401,7 @@
|
||||
currentHeight += textHeight + 8;
|
||||
doc.setFont("IBMPlexSans-Regular", "normal");
|
||||
|
||||
($trail.expand.waypoints || []).forEach(waypoint => {
|
||||
($trail.expand.waypoints_via_trail || []).forEach(waypoint => {
|
||||
let description = waypoint.description || "";
|
||||
let name = waypoint.name || "";
|
||||
|
||||
@@ -616,7 +616,7 @@
|
||||
<div class="basis-full">
|
||||
<MapWithElevationMaplibre
|
||||
trails={[$trail]}
|
||||
waypoints={$trail.expand?.waypoints}
|
||||
waypoints={$trail.expand?.waypoints_via_trail}
|
||||
activeTrail={0}
|
||||
onzoom={updateScale}
|
||||
bind:map
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-xl font-semibold">Timeline</h4>
|
||||
{#if !feed.items?.length && data.isOwnProfile}
|
||||
<a class="btn-primary inline-block" href="/trails/edit/new"
|
||||
<a class="btn-primary inline-block" href="/trail/edit/new"
|
||||
>+ {$_("new-trail")}</a
|
||||
>
|
||||
{:else if !feed.items?.length}
|
||||
|
||||
@@ -12,7 +12,7 @@ export const load: Load = async ({ params, fetch, parent }) => {
|
||||
q: "",
|
||||
category: [],
|
||||
tags: [],
|
||||
difficulty: ["easy", "moderate", "difficult"],
|
||||
difficulty: [0, 1, 2],
|
||||
author: actor.id,
|
||||
public: true,
|
||||
shared: true,
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
summit_logs_via_trail: z
|
||||
.array(SummitLogCreateSchema)
|
||||
.optional(),
|
||||
waypoints: z
|
||||
waypoints_via_trail: z
|
||||
.array(
|
||||
WaypointCreateSchema.extend({
|
||||
marker: z.any().optional(),
|
||||
@@ -384,11 +384,10 @@
|
||||
}
|
||||
|
||||
function clearWaypoints() {
|
||||
for (const waypoint of $formData.expand!.waypoints ?? []) {
|
||||
for (const waypoint of $formData.expand!.waypoints_via_trail ?? []) {
|
||||
waypoint.marker?.remove();
|
||||
}
|
||||
$formData.expand!.waypoints = [];
|
||||
$formData.waypoints = [];
|
||||
$formData.expand!.waypoints_via_trail = [];
|
||||
}
|
||||
|
||||
function initRouteAnchors(gpx: GPX, addToMap: boolean = false) {
|
||||
@@ -445,29 +444,28 @@
|
||||
}
|
||||
|
||||
function deleteWaypoint(index: number) {
|
||||
const wp = $formData.expand!.waypoints?.splice(index, 1);
|
||||
$formData.waypoints.splice(index, 1);
|
||||
const wp = $formData.expand!.waypoints_via_trail?.splice(index, 1);
|
||||
|
||||
if (!$formData.expand!.waypoints?.length) {
|
||||
$formData.expand!.waypoints = [];
|
||||
if (!$formData.expand!.waypoints_via_trail?.length) {
|
||||
$formData.expand!.waypoints_via_trail = [];
|
||||
}
|
||||
$formData.expand!.waypoints = $formData.expand!.waypoints;
|
||||
$formData.expand!.waypoints_via_trail = $formData.expand!.waypoints_via_trail;
|
||||
|
||||
// updateTrailOnMap();
|
||||
}
|
||||
|
||||
function saveWaypoint(savedWaypoint: Waypoint) {
|
||||
let editedWaypointIndex =
|
||||
$formData.expand!.waypoints?.findIndex(
|
||||
$formData.expand!.waypoints_via_trail?.findIndex(
|
||||
(s) => s.id == savedWaypoint.id,
|
||||
) ?? -1;
|
||||
|
||||
if (editedWaypointIndex >= 0) {
|
||||
$formData.expand!.waypoints![editedWaypointIndex] = savedWaypoint;
|
||||
$formData.expand!.waypoints_via_trail![editedWaypointIndex] = savedWaypoint;
|
||||
} else {
|
||||
savedWaypoint.id = cryptoRandomString({ length: 15 });
|
||||
$formData.expand!.waypoints = [
|
||||
...($formData.expand!.waypoints ?? []),
|
||||
$formData.expand!.waypoints_via_trail = [
|
||||
...($formData.expand!.waypoints_via_trail ?? []),
|
||||
savedWaypoint,
|
||||
];
|
||||
|
||||
@@ -478,15 +476,15 @@
|
||||
function moveMarker(marker: M.Marker, wpId?: string) {
|
||||
const position = marker.getLngLat();
|
||||
const editableWaypointIndex =
|
||||
$formData.expand!.waypoints?.findIndex((w) => w.id == wpId) ?? -1;
|
||||
$formData.expand!.waypoints_via_trail?.findIndex((w) => w.id == wpId) ?? -1;
|
||||
const editableWaypoint =
|
||||
$formData.expand!.waypoints![editableWaypointIndex];
|
||||
$formData.expand!.waypoints_via_trail![editableWaypointIndex];
|
||||
if (!editableWaypoint) {
|
||||
return;
|
||||
}
|
||||
editableWaypoint.lat = position.lat;
|
||||
editableWaypoint.lon = position.lng;
|
||||
$formData.expand!.waypoints = [...($formData.expand!.waypoints ?? [])];
|
||||
$formData.expand!.waypoints_via_trail = [...($formData.expand!.waypoints_via_trail ?? [])];
|
||||
// updateTrailOnMap();
|
||||
}
|
||||
|
||||
@@ -1369,7 +1367,7 @@
|
||||
{$_("waypoints", { values: { n: 2 } })}
|
||||
</h3>
|
||||
<ul>
|
||||
{#each $formData.expand?.waypoints ?? [] as waypoint, i}
|
||||
{#each $formData.expand?.waypoints_via_trail ?? [] as waypoint, i}
|
||||
<li
|
||||
onmouseenter={() => openMarkerPopup(waypoint)}
|
||||
onmouseleave={() => openMarkerPopup(waypoint)}
|
||||
@@ -1501,7 +1499,7 @@
|
||||
<div id="trail-map">
|
||||
<MapWithElevationMaplibre
|
||||
trails={mapTrail}
|
||||
waypoints={$formData.expand?.waypoints}
|
||||
waypoints={$formData.expand?.waypoints_via_trail}
|
||||
drawing={drawingActive}
|
||||
showTerrain={true}
|
||||
onmarkerdragend={moveMarker}
|
||||
|
||||
@@ -25,7 +25,24 @@
|
||||
export const snapshot: Snapshot<TrailFilter> = {
|
||||
capture: () => filter,
|
||||
restore: (value) => {
|
||||
filter = value;
|
||||
const difficultyMap: Record<string, 0 | 1 | 2> = {
|
||||
easy: 0,
|
||||
moderate: 1,
|
||||
difficult: 2,
|
||||
};
|
||||
// defensive copy
|
||||
const migrated = { ...value };
|
||||
|
||||
if (Array.isArray(migrated.difficulty)) {
|
||||
migrated.difficulty = migrated.difficulty.map((d: any) => {
|
||||
if (typeof d === "string" && d in difficultyMap) {
|
||||
return difficultyMap[d];
|
||||
}
|
||||
return d;
|
||||
});
|
||||
}
|
||||
|
||||
filter = migrated;
|
||||
handleFilterUpdate();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ export const load: ServerLoad = async ({ params, locals, url, fetch }) => {
|
||||
q: "",
|
||||
category: [],
|
||||
tags: [],
|
||||
difficulty: ["easy", "moderate", "difficult"],
|
||||
difficulty: [0, 1, 2],
|
||||
author: "",
|
||||
public: true,
|
||||
shared: true,
|
||||
|
||||
Reference in New Issue
Block a user