finishes profile page
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
import type { SummitLog } from "$lib/models/summit_log";
|
import type { SummitLog } from "$lib/models/summit_log";
|
||||||
import { createEventDispatcher } from "svelte";
|
import { createEventDispatcher } from "svelte";
|
||||||
import { isSameDay, isToday } from "../../util/date_util";
|
import { isSameDay, isToday } from "../../util/date_util";
|
||||||
|
import { _ } from "svelte-i18n";
|
||||||
export let logs: SummitLog[] = [];
|
export let logs: SummitLog[] = [];
|
||||||
export let colorMap: Record<string, string> = {};
|
export let colorMap: Record<string, string> = {};
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
start: Date;
|
start: Date;
|
||||||
end: Date;
|
end: Date;
|
||||||
};
|
};
|
||||||
|
click: Date;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const weekdays = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
|
const weekdays = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
|
||||||
@@ -74,9 +75,11 @@
|
|||||||
i + 1 - firstDay,
|
i + 1 - firstDay,
|
||||||
);
|
);
|
||||||
const today = isToday(date);
|
const today = isToday(date);
|
||||||
|
|
||||||
const logAtDate = logs.find((l) =>
|
const logAtDate = logs.find((l) =>
|
||||||
isSameDay(date, new Date(l.date)),
|
isSameDay(date, new Date(l.date)),
|
||||||
);
|
);
|
||||||
|
|
||||||
a.push({ date: date, today: today, log: logAtDate });
|
a.push({ date: date, today: today, log: logAtDate });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,6 +111,21 @@
|
|||||||
end: new Date(currentYear, currentMonth + 1, 0),
|
end: new Date(currentYear, currentMonth + 1, 0),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function colorKey(i: number) {
|
||||||
|
return $_(
|
||||||
|
currentMonthArray[i]?.log?.expand.trails_via_summit_logs?.at(0)
|
||||||
|
?.expand?.category?.name ?? "",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDateClick(date?: Date) {
|
||||||
|
if (!date) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch("click", date);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="calendar-header w-full flex items-center justify-between mb-6">
|
<div class="calendar-header w-full flex items-center justify-between mb-6">
|
||||||
@@ -134,18 +152,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-7 grid-rows-6" style="aspect-ratio: 1.17/1">
|
<div class="grid grid-cols-7 grid-rows-6" style="aspect-ratio: 1.17/1">
|
||||||
{#each { length: 42 } as _, i}
|
{#each { length: 42 } as _, i}
|
||||||
<div
|
<button
|
||||||
class="calendar-day flex items-center justify-center rounded-xl cursor-pointer"
|
class="calendar-day flex items-center justify-center rounded-xl"
|
||||||
|
on:click={() => handleDateClick(currentMonthArray[i]?.date)}
|
||||||
class:today={currentMonthArray[i]?.today}
|
class:today={currentMonthArray[i]?.today}
|
||||||
style="background-color: {colorMap[
|
style="background-color: {colorMap[colorKey(i)] ?? ''}"
|
||||||
currentMonthArray[
|
|
||||||
i
|
|
||||||
]?.log?.expand.trails_via_summit_logs?.at(0)?.expand
|
|
||||||
?.category?.name ?? ''
|
|
||||||
] ?? ''}"
|
|
||||||
>
|
>
|
||||||
{currentMonthArray[i]?.date?.getDate() ?? ""}
|
{currentMonthArray[i]?.date?.getDate() ?? ""}
|
||||||
</div>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
83
web/src/lib/components/base/multi_select.svelte
Normal file
83
web/src/lib/components/base/multi_select.svelte
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { writable } from "svelte/store";
|
||||||
|
import type { SelectItem } from "./select.svelte";
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
|
||||||
|
export let items: SelectItem[] = [];
|
||||||
|
export let value: SelectItem[] = [];
|
||||||
|
export let label: string = "";
|
||||||
|
export let name: string = "";
|
||||||
|
export let placeholder: string = "";
|
||||||
|
|
||||||
|
let showDropdown = false;
|
||||||
|
let dropdownRef;
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
function toggleItem(item: SelectItem) {
|
||||||
|
if (value.includes(item)) {
|
||||||
|
value = value.filter((i) => i !== item);
|
||||||
|
} else {
|
||||||
|
value = [...value, item];
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch("change", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeItem(item: SelectItem) {
|
||||||
|
value = value.filter((i) => i !== item);
|
||||||
|
dispatch("change", value);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<div class="relative max-w-full">
|
||||||
|
{#if label.length}
|
||||||
|
<label for={name} class="text-sm font-medium pb-1 block">
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
{/if}
|
||||||
|
<button
|
||||||
|
class="min-w-44 flex flex-wrap items-center gap-2 border bg-input-background min-h-[50px] p-3 rounded-md transition-colors focus:border-input-border-focus focus:outline-none focus:ring-0"
|
||||||
|
on:click={() => (showDropdown = !showDropdown)}
|
||||||
|
>
|
||||||
|
{#if value.length === 0}
|
||||||
|
<span class="text-gray-400">{placeholder}</span>
|
||||||
|
{/if}
|
||||||
|
{#each value as item}
|
||||||
|
<div
|
||||||
|
class="bg-primary text-white px-2 py-1 rounded-full flex items-center gap-1"
|
||||||
|
>
|
||||||
|
<span class="text-sm">{item.text}</span>
|
||||||
|
<button
|
||||||
|
on:click|stopPropagation={() => removeItem(item)}
|
||||||
|
class="text-white hover:bg-primary-hover rounded-full w-4 h-4 flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<i class="fa fa-close"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Dropdown menu -->
|
||||||
|
{#if showDropdown}
|
||||||
|
<div
|
||||||
|
bind:this={dropdownRef}
|
||||||
|
class="absolute z-10 mt-1 w-full bg-white border border-gray-300 rounded shadow-lg max-h-40 overflow-y-auto"
|
||||||
|
>
|
||||||
|
{#each items as item}
|
||||||
|
<button
|
||||||
|
on:click={() => toggleItem(item)}
|
||||||
|
class="px-3 py-2 hover:bg-blue-100 cursor-pointer flex justify-between items-center w-full"
|
||||||
|
>
|
||||||
|
<span>{item.text}</span>
|
||||||
|
{#if value.includes(item)}
|
||||||
|
<div class="ml-auto">
|
||||||
|
<i class="fa fa-check"></i>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
@@ -5,24 +5,28 @@
|
|||||||
import Modal from "../base/modal.svelte";
|
import Modal from "../base/modal.svelte";
|
||||||
import SummitLogTableRow from "./summit_log_table_row.svelte";
|
import SummitLogTableRow from "./summit_log_table_row.svelte";
|
||||||
|
|
||||||
import type { Map } from "leaflet";
|
|
||||||
import { gpx } from "$lib/vendor/toGeoJSON/toGeoJSON";
|
|
||||||
import { endIcon, startIcon } from "$lib/util/leaflet_util";
|
|
||||||
import { _ } from "svelte-i18n";
|
|
||||||
import MapWithElevation from "../trail/map_with_elevation.svelte";
|
|
||||||
import type { Trail } from "$lib/models/trail";
|
import type { Trail } from "$lib/models/trail";
|
||||||
import { gpx2trail } from "$lib/util/gpx_util";
|
import { gpx2trail } from "$lib/util/gpx_util";
|
||||||
|
import type { Map } from "leaflet";
|
||||||
|
import { _ } from "svelte-i18n";
|
||||||
|
import MapWithElevation from "../trail/map_with_elevation.svelte";
|
||||||
|
|
||||||
export let summitLogs: SummitLog[];
|
export let summitLogs: SummitLog[];
|
||||||
export let showCategory: boolean = false;
|
export let showCategory: boolean = false;
|
||||||
|
export let showTrail: boolean = false;
|
||||||
|
|
||||||
let openModal: () => void;
|
let openMapModal: () => void;
|
||||||
let closeModal: () => void;
|
let closeMapModal: () => void;
|
||||||
|
|
||||||
|
let openTextModal: () => void;
|
||||||
|
let closeTextModal: () => void;
|
||||||
|
|
||||||
let map: Map;
|
let map: Map;
|
||||||
|
|
||||||
let trail: Trail | null = null;
|
let trail: Trail | null = null;
|
||||||
|
|
||||||
|
let currentText: string = "";
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
// L = (await import("leaflet")).default;
|
// L = (await import("leaflet")).default;
|
||||||
// map = L.map("summit-log-table-map");
|
// map = L.map("summit-log-table-map");
|
||||||
@@ -42,12 +46,17 @@
|
|||||||
trail = (await gpx2trail(log.expand.gpx_data)).trail;
|
trail = (await gpx2trail(log.expand.gpx_data)).trail;
|
||||||
trail.expand.gpx_data = log.expand.gpx_data;
|
trail.expand.gpx_data = log.expand.gpx_data;
|
||||||
|
|
||||||
openModal();
|
openMapModal();
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
map.invalidateSize();
|
map.invalidateSize();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openText(log: SummitLog) {
|
||||||
|
currentText = log.text ?? "";
|
||||||
|
openTextModal();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<table class="w-full">
|
<table class="w-full">
|
||||||
@@ -64,7 +73,12 @@
|
|||||||
{$_("category")}
|
{$_("category")}
|
||||||
</th>
|
</th>
|
||||||
{/if}
|
{/if}
|
||||||
<th></th>
|
{#if showTrail}
|
||||||
|
<th>
|
||||||
|
{$_("trail", { values: { n: 1 } })}
|
||||||
|
</th>
|
||||||
|
{/if}
|
||||||
|
<th>{$_("description")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -72,24 +86,37 @@
|
|||||||
<SummitLogTableRow
|
<SummitLogTableRow
|
||||||
index={i}
|
index={i}
|
||||||
{log}
|
{log}
|
||||||
on:open={() => openMap(log)}
|
on:open={(e) => openMap(e.detail)}
|
||||||
|
on:text={(e) => openText(e.detail)}
|
||||||
{showCategory}
|
{showCategory}
|
||||||
|
{showTrail}
|
||||||
></SummitLogTableRow>
|
></SummitLogTableRow>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{#if !summitLogs.length}
|
||||||
|
<p class="text-center w-full my-8 text-gray-500 text-sm">{$_("no-data")}</p>
|
||||||
|
{/if}
|
||||||
<Modal
|
<Modal
|
||||||
id="summit-log-table-modal"
|
id="summit-log-table-map-modal"
|
||||||
size="max-w-4xl"
|
size="max-w-4xl"
|
||||||
title=""
|
title=""
|
||||||
bind:openModal
|
bind:openModal={openMapModal}
|
||||||
bind:closeModal
|
bind:closeModal={closeMapModal}
|
||||||
>
|
>
|
||||||
<div slot="content" id="summit-log-table-map" class="h-[32rem]">
|
<div slot="content" id="summit-log-table-map" class="h-[32rem]">
|
||||||
<MapWithElevation {trail} bind:map></MapWithElevation>
|
<MapWithElevation {trail} bind:map></MapWithElevation>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
id="summit-log-table-text-modal"
|
||||||
|
size="max-w-xl"
|
||||||
|
title={$_("description")}
|
||||||
|
bind:openModal={openTextModal}
|
||||||
|
bind:closeModal={closeTextModal}
|
||||||
|
>
|
||||||
|
<p slot="content" class="whitespace-pre-wrap">{currentText}</p>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
th {
|
th {
|
||||||
|
|||||||
@@ -15,11 +15,10 @@
|
|||||||
export let index: number;
|
export let index: number;
|
||||||
export let log: SummitLog;
|
export let log: SummitLog;
|
||||||
export let showCategory: boolean = false;
|
export let showCategory: boolean = false;
|
||||||
|
export let showTrail: boolean = false;
|
||||||
|
|
||||||
let map: Map;
|
let map: Map;
|
||||||
|
|
||||||
let showText: boolean = false;
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
@@ -59,6 +58,20 @@
|
|||||||
function openMap() {
|
function openMap() {
|
||||||
dispatch("open", log);
|
dispatch("open", log);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openText() {
|
||||||
|
dispatch("text", log);
|
||||||
|
}
|
||||||
|
|
||||||
|
function colCount() {
|
||||||
|
if (showCategory && showTrail) {
|
||||||
|
return 9;
|
||||||
|
} else if (showCategory || showTrail) {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<tr class="text-center">
|
<tr class="text-center">
|
||||||
@@ -100,23 +113,24 @@
|
|||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if showTrail}
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
class="btn-icon aspect-square"
|
||||||
|
href="/trail/view/{log.expand.trails_via_summit_logs?.at(0)
|
||||||
|
?.id ?? ''}"
|
||||||
|
><i class="fa fa-arrow-up-right-from-square px-[3px]"></i></a
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
{/if}
|
||||||
<td>
|
<td>
|
||||||
{#if log.text}
|
{#if log.text}
|
||||||
<button on:click={() => (showText = !showText)} class="btn-icon"
|
<button on:click={openText} class="btn-icon"
|
||||||
><i class="fa{showText ? '' : '-regular'} fa-message text-gray-500"
|
><i class="fa-regular fa-message"></i></button
|
||||||
></i></button
|
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{#if showText}
|
|
||||||
<tr
|
|
||||||
><td
|
|
||||||
class="text-left text-sm whitespace-pre-wrap pb-4"
|
|
||||||
colspan={showCategory ? 8 : 7}>{log.text}</td
|
|
||||||
></tr
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan={showCategory ? 8 : 7}> <hr class="border-input-border" /> </td>
|
<td colspan={colCount()}> <hr class="border-input-border" /> </td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -243,13 +243,13 @@
|
|||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Datepicker
|
<Datepicker
|
||||||
name="startDate"
|
name="startDate"
|
||||||
label="After"
|
label={$_('after')}
|
||||||
bind:value={filter.startDate}
|
bind:value={filter.startDate}
|
||||||
on:change={update}
|
on:change={update}
|
||||||
></Datepicker>
|
></Datepicker>
|
||||||
<Datepicker
|
<Datepicker
|
||||||
name="endDate"
|
name="endDate"
|
||||||
label="Before"
|
label={$_('before')}
|
||||||
bind:value={filter.endDate}
|
bind:value={filter.endDate}
|
||||||
on:change={update}
|
on:change={update}
|
||||||
></Datepicker>
|
></Datepicker>
|
||||||
|
|||||||
@@ -7,18 +7,21 @@
|
|||||||
"Walking": "Laufen",
|
"Walking": "Laufen",
|
||||||
"about": "Über",
|
"about": "Über",
|
||||||
"account-delete-confirm": "Du bist dabei, dein Konto zu löschen. Alle deine Routen werden ebenfalls gelöscht. Möchtest du fortfahren?",
|
"account-delete-confirm": "Du bist dabei, dein Konto zu löschen. Alle deine Routen werden ebenfalls gelöscht. Möchtest du fortfahren?",
|
||||||
"activity": "",
|
"activity": "{n, plural, =1 {Aktivität} other {Aktivitäten}}",
|
||||||
"add-entry": "Eintrag hinzufügen",
|
"add-entry": "Eintrag hinzufügen",
|
||||||
"add-to-list": "Zu Liste hinzufügen",
|
"add-to-list": "Zu Liste hinzufügen",
|
||||||
"add-waypoint": "Wegpunkt hinzufügen",
|
"add-waypoint": "Wegpunkt hinzufügen",
|
||||||
"added-trail-to": "Route hinzugefügt zu",
|
"added-trail-to": "Route hinzugefügt zu",
|
||||||
"all-activities": "",
|
"after": "Nach",
|
||||||
|
"all-activities": "Alle Aktivitäten",
|
||||||
"alphabetical": "Alphabetisch",
|
"alphabetical": "Alphabetisch",
|
||||||
"already-account": "Du hast bereits ein Konto?",
|
"already-account": "Du hast bereits ein Konto?",
|
||||||
"altitude": "Höhe",
|
"altitude": "Höhe",
|
||||||
"api-documentation": "API Dokumentation",
|
"api-documentation": "API Dokumentation",
|
||||||
"avatar": "Avatar",
|
"avatar": "Avatar",
|
||||||
|
"average-speed": "Durschn. Geschwindigkeit",
|
||||||
"basic-info": "Basisinformation",
|
"basic-info": "Basisinformation",
|
||||||
|
"before": "Vor",
|
||||||
"can": "kann",
|
"can": "kann",
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
"card": "{n, plural, =1 {Karte} other {Karten}}",
|
"card": "{n, plural, =1 {Karte} other {Karten}}",
|
||||||
@@ -33,9 +36,9 @@
|
|||||||
"comment": "{n, plural, =1 {Kommentar} other {Kommentare}}",
|
"comment": "{n, plural, =1 {Kommentar} other {Kommentare}}",
|
||||||
"completed": "Abgeschlossen",
|
"completed": "Abgeschlossen",
|
||||||
"completion-status": "Abschlussstatus",
|
"completion-status": "Abschlussstatus",
|
||||||
"confirm": "",
|
"confirm": "Bestätigen",
|
||||||
"confirm-deletion": "",
|
"confirm-deletion": "Löschen bestätigen",
|
||||||
"confirm-share": "",
|
"confirm-share": "Teilen bestätigen",
|
||||||
"contribute": "Mitwirken",
|
"contribute": "Mitwirken",
|
||||||
"copy-link": "Link kopieren",
|
"copy-link": "Link kopieren",
|
||||||
"create-new-list": "Neue Liste erstellen",
|
"create-new-list": "Neue Liste erstellen",
|
||||||
@@ -61,15 +64,15 @@
|
|||||||
"documentation": "Dokumentation",
|
"documentation": "Dokumentation",
|
||||||
"draw-a-route": "Route zeichnen",
|
"draw-a-route": "Route zeichnen",
|
||||||
"driving": "Auto",
|
"driving": "Auto",
|
||||||
"duration": "",
|
"duration": "Dauer",
|
||||||
"dutch": "Niederländisch",
|
"dutch": "Niederländisch",
|
||||||
"easy": "Einfach",
|
"easy": "Einfach",
|
||||||
"edit": "Bearbeiten",
|
"edit": "Bearbeiten",
|
||||||
"edit-entry": "Eintrag bearbeiten",
|
"edit-entry": "Eintrag bearbeiten",
|
||||||
"edit-list": "Liste bearbeiten",
|
"edit-list": "Liste bearbeiten",
|
||||||
"edit-waypoint": "Wegpunkt bearbeiten",
|
"edit-waypoint": "Wegpunkt bearbeiten",
|
||||||
"elevation-gain": "Höhenunterschied",
|
"elevation-gain": "Höhenunterschied (aufw.)",
|
||||||
"elevation-loss": "",
|
"elevation-loss": "Höhenunterschied (abw.)",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"english": "Englisch",
|
"english": "Englisch",
|
||||||
"entry": "Eintrag",
|
"entry": "Eintrag",
|
||||||
@@ -78,8 +81,8 @@
|
|||||||
"error-exporting-trail": "Fehler beim Exportieren des Trails",
|
"error-exporting-trail": "Fehler beim Exportieren des Trails",
|
||||||
"error-printing-map": "Fehler beim Drucken der Karte",
|
"error-printing-map": "Fehler beim Drucken der Karte",
|
||||||
"error-reading-file": "Fehler beim Lesen der Datei",
|
"error-reading-file": "Fehler beim Lesen der Datei",
|
||||||
"error-saving-list": "",
|
"error-saving-list": "Fehler beim Speichern der Liste",
|
||||||
"error-saving-trail": "Fehler bei Speichern der Route",
|
"error-saving-trail": "Fehler beim Speichern der Route",
|
||||||
"error-updating-password": "Fehler beim Aktualisieren des Passworts",
|
"error-updating-password": "Fehler beim Aktualisieren des Passworts",
|
||||||
"est-duration": "Gesch. Dauer",
|
"est-duration": "Gesch. Dauer",
|
||||||
"explore": "Erkunden",
|
"explore": "Erkunden",
|
||||||
@@ -88,6 +91,7 @@
|
|||||||
"export-all-trails": "Alle Routen exportieren",
|
"export-all-trails": "Alle Routen exportieren",
|
||||||
"features": "Features",
|
"features": "Features",
|
||||||
"file-format": "Dateiformat",
|
"file-format": "Dateiformat",
|
||||||
|
"filter-categories": "Kategorien filtern",
|
||||||
"focus-map-on": "Karte fokussieren auf",
|
"focus-map-on": "Karte fokussieren auf",
|
||||||
"french": "Französisch",
|
"french": "Französisch",
|
||||||
"german": "Deutsch",
|
"german": "Deutsch",
|
||||||
@@ -114,10 +118,10 @@
|
|||||||
"license": "Lizenz",
|
"license": "Lizenz",
|
||||||
"link-copied": "Link kopiert",
|
"link-copied": "Link kopiert",
|
||||||
"list": "{n, plural, =1 {Liste} other {Listen}}",
|
"list": "{n, plural, =1 {Liste} other {Listen}}",
|
||||||
"list-not-shared": "",
|
"list-not-shared": "Mit niemandem geteilt",
|
||||||
"list-saved-successfully": "",
|
"list-saved-successfully": "Liste gespeichert",
|
||||||
"list-share-warning": "",
|
"list-share-warning": "Durch das Teilen einer Liste werden automatisch alle darin enthaltenen Routen freigegeben.",
|
||||||
"list-share-warning-update": "",
|
"list-share-warning-update": "Hinzugefügte Routen werden mit allen geteilt, die Zugriff auf diese Liste haben.",
|
||||||
"location": "Standort",
|
"location": "Standort",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"login-details": "Login Details",
|
"login-details": "Login Details",
|
||||||
@@ -142,6 +146,7 @@
|
|||||||
"new-password": "Neues Passwort",
|
"new-password": "Neues Passwort",
|
||||||
"new-trail": "Neue Route",
|
"new-trail": "Neue Route",
|
||||||
"no-account": "Du hast noch kein Konto?",
|
"no-account": "Du hast noch kein Konto?",
|
||||||
|
"no-data": "Keine Daten",
|
||||||
"no-preference": "Keine Präferenz",
|
"no-preference": "Keine Präferenz",
|
||||||
"no-results": "Keine Ergebnisse gefunden",
|
"no-results": "Keine Ergebnisse gefunden",
|
||||||
"not-a-valid-email-address": "Keine gültige Email-Adresse",
|
"not-a-valid-email-address": "Keine gültige Email-Adresse",
|
||||||
@@ -160,12 +165,12 @@
|
|||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"public": "Öffentlich",
|
"public": "Öffentlich",
|
||||||
"radius": "Radius",
|
"radius": "Radius",
|
||||||
"read-more": "",
|
"read-more": "Mehr Info",
|
||||||
"register": "Registrieren",
|
"register": "Registrieren",
|
||||||
"removed-trail-from": "Route entfernt aus",
|
"removed-trail-from": "Route entfernt aus",
|
||||||
"required": "Pflichtfeld",
|
"required": "Pflichtfeld",
|
||||||
"save": "Speichern",
|
"save": "Speichern",
|
||||||
"save-list": "",
|
"save-list": "Liste speichern",
|
||||||
"save-trail": "Route speichern",
|
"save-trail": "Route speichern",
|
||||||
"save-your-trail-first": "Route zuerst speichern",
|
"save-your-trail-first": "Route zuerst speichern",
|
||||||
"search-cities": "Städte suchen",
|
"search-cities": "Städte suchen",
|
||||||
@@ -174,7 +179,7 @@
|
|||||||
"select-list": "Liste auswählen",
|
"select-list": "Liste auswählen",
|
||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
"share": "Teilen",
|
"share": "Teilen",
|
||||||
"share-this-list": "",
|
"share-this-list": "Diese Liste teilen",
|
||||||
"share-this-trail": "Diese Route teilen",
|
"share-this-trail": "Diese Route teilen",
|
||||||
"shared-by": "Geteilt von",
|
"shared-by": "Geteilt von",
|
||||||
"shared-with": "Geteilt mit",
|
"shared-with": "Geteilt mit",
|
||||||
|
|||||||
@@ -12,13 +12,16 @@
|
|||||||
"add-to-list": "Add to list",
|
"add-to-list": "Add to list",
|
||||||
"add-waypoint": "Add Waypoint",
|
"add-waypoint": "Add Waypoint",
|
||||||
"added-trail-to": "Added trail to",
|
"added-trail-to": "Added trail to",
|
||||||
|
"after": "After",
|
||||||
"all-activities": "All activities",
|
"all-activities": "All activities",
|
||||||
"alphabetical": "Alphabetical",
|
"alphabetical": "Alphabetical",
|
||||||
"already-account": "Already have an account?",
|
"already-account": "Already have an account?",
|
||||||
"altitude": "Altitude",
|
"altitude": "Altitude",
|
||||||
"api-documentation": "API Documentation",
|
"api-documentation": "API Documentation",
|
||||||
"avatar": "Avatar",
|
"avatar": "Avatar",
|
||||||
|
"average-speed": "Avg. Speed",
|
||||||
"basic-info": "Basic Info",
|
"basic-info": "Basic Info",
|
||||||
|
"before": "Before",
|
||||||
"can": "can",
|
"can": "can",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"card": "{n, plural, =1 {Card} other {Cards}}",
|
"card": "{n, plural, =1 {Card} other {Cards}}",
|
||||||
@@ -88,6 +91,7 @@
|
|||||||
"export-all-trails": "Export all trails",
|
"export-all-trails": "Export all trails",
|
||||||
"features": "Features",
|
"features": "Features",
|
||||||
"file-format": "File format",
|
"file-format": "File format",
|
||||||
|
"filter-categories": "Filter categories",
|
||||||
"focus-map-on": "Focus map on",
|
"focus-map-on": "Focus map on",
|
||||||
"french": "French",
|
"french": "French",
|
||||||
"german": "German",
|
"german": "German",
|
||||||
@@ -142,6 +146,7 @@
|
|||||||
"new-password": "New password",
|
"new-password": "New password",
|
||||||
"new-trail": "New Trail",
|
"new-trail": "New Trail",
|
||||||
"no-account": "Don't have an account?",
|
"no-account": "Don't have an account?",
|
||||||
|
"no-data": "No data",
|
||||||
"no-preference": "No preference",
|
"no-preference": "No preference",
|
||||||
"no-results": "No results found",
|
"no-results": "No results found",
|
||||||
"not-a-valid-email-address": "Not a valid email address",
|
"not-a-valid-email-address": "Not a valid email address",
|
||||||
|
|||||||
@@ -12,13 +12,16 @@
|
|||||||
"add-to-list": "Ajouter à une liste",
|
"add-to-list": "Ajouter à une liste",
|
||||||
"add-waypoint": "Ajouter un point de repère",
|
"add-waypoint": "Ajouter un point de repère",
|
||||||
"added-trail-to": "Ajouter un itinéraire à",
|
"added-trail-to": "Ajouter un itinéraire à",
|
||||||
|
"after": "",
|
||||||
"all-activities": "",
|
"all-activities": "",
|
||||||
"alphabetical": "Alphabétique",
|
"alphabetical": "Alphabétique",
|
||||||
"already-account": "Déjà un compte ?",
|
"already-account": "Déjà un compte ?",
|
||||||
"altitude": "Altitude",
|
"altitude": "Altitude",
|
||||||
"api-documentation": "Documentation API",
|
"api-documentation": "Documentation API",
|
||||||
"avatar": "Avatar",
|
"avatar": "Avatar",
|
||||||
|
"average-speed": "",
|
||||||
"basic-info": "Informations de base",
|
"basic-info": "Informations de base",
|
||||||
|
"before": "",
|
||||||
"can": "",
|
"can": "",
|
||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
"card": "{n, plural, =1 {Carte} other {Cartes}}",
|
"card": "{n, plural, =1 {Carte} other {Cartes}}",
|
||||||
@@ -88,6 +91,7 @@
|
|||||||
"export-all-trails": "",
|
"export-all-trails": "",
|
||||||
"features": "Fonctionnalités",
|
"features": "Fonctionnalités",
|
||||||
"file-format": "",
|
"file-format": "",
|
||||||
|
"filter-categories": "",
|
||||||
"focus-map-on": "",
|
"focus-map-on": "",
|
||||||
"french": "Français",
|
"french": "Français",
|
||||||
"german": "Allemand",
|
"german": "Allemand",
|
||||||
@@ -142,6 +146,7 @@
|
|||||||
"new-password": "",
|
"new-password": "",
|
||||||
"new-trail": "Nouvel itinéraire",
|
"new-trail": "Nouvel itinéraire",
|
||||||
"no-account": "Pas encore de compte ?",
|
"no-account": "Pas encore de compte ?",
|
||||||
|
"no-data": "",
|
||||||
"no-preference": "Pas de préférence",
|
"no-preference": "Pas de préférence",
|
||||||
"no-results": "Pas de résultat",
|
"no-results": "Pas de résultat",
|
||||||
"not-a-valid-email-address": "Adresse email invalide",
|
"not-a-valid-email-address": "Adresse email invalide",
|
||||||
|
|||||||
@@ -12,13 +12,16 @@
|
|||||||
"add-to-list": "Hozzáadás a listához",
|
"add-to-list": "Hozzáadás a listához",
|
||||||
"add-waypoint": "Útvonalpont hozzáadása",
|
"add-waypoint": "Útvonalpont hozzáadása",
|
||||||
"added-trail-to": "Hozzáadott nyomvonal a",
|
"added-trail-to": "Hozzáadott nyomvonal a",
|
||||||
|
"after": "",
|
||||||
"all-activities": "",
|
"all-activities": "",
|
||||||
"alphabetical": "Betűrendben",
|
"alphabetical": "Betűrendben",
|
||||||
"already-account": "Már rendelkezik fiókkal?",
|
"already-account": "Már rendelkezik fiókkal?",
|
||||||
"altitude": "Magasság",
|
"altitude": "Magasság",
|
||||||
"api-documentation": "API Dokumentáció",
|
"api-documentation": "API Dokumentáció",
|
||||||
"avatar": "Avatar",
|
"avatar": "Avatar",
|
||||||
|
"average-speed": "",
|
||||||
"basic-info": "Alap információk",
|
"basic-info": "Alap információk",
|
||||||
|
"before": "",
|
||||||
"can": "",
|
"can": "",
|
||||||
"cancel": "Mégsem",
|
"cancel": "Mégsem",
|
||||||
"card": "{n, plural, =1 {Kártya} other {Kártyák}}",
|
"card": "{n, plural, =1 {Kártya} other {Kártyák}}",
|
||||||
@@ -88,6 +91,7 @@
|
|||||||
"export-all-trails": "",
|
"export-all-trails": "",
|
||||||
"features": "Jellemzők",
|
"features": "Jellemzők",
|
||||||
"file-format": "",
|
"file-format": "",
|
||||||
|
"filter-categories": "",
|
||||||
"focus-map-on": "",
|
"focus-map-on": "",
|
||||||
"french": "Francia",
|
"french": "Francia",
|
||||||
"german": "Német",
|
"german": "Német",
|
||||||
@@ -142,6 +146,7 @@
|
|||||||
"new-password": "",
|
"new-password": "",
|
||||||
"new-trail": "Új útvonal",
|
"new-trail": "Új útvonal",
|
||||||
"no-account": "Nincs még fiókja?",
|
"no-account": "Nincs még fiókja?",
|
||||||
|
"no-data": "",
|
||||||
"no-preference": "Nincs preferált",
|
"no-preference": "Nincs preferált",
|
||||||
"no-results": "Nincs eredmény",
|
"no-results": "Nincs eredmény",
|
||||||
"not-a-valid-email-address": "Érvénytelen e-mail cím",
|
"not-a-valid-email-address": "Érvénytelen e-mail cím",
|
||||||
|
|||||||
@@ -12,13 +12,16 @@
|
|||||||
"add-to-list": "Aggiungi alla lista",
|
"add-to-list": "Aggiungi alla lista",
|
||||||
"add-waypoint": "Aggiungere un waypoint",
|
"add-waypoint": "Aggiungere un waypoint",
|
||||||
"added-trail-to": "Percorso aggiunto a",
|
"added-trail-to": "Percorso aggiunto a",
|
||||||
|
"after": "",
|
||||||
"all-activities": "",
|
"all-activities": "",
|
||||||
"alphabetical": "Alfabetico",
|
"alphabetical": "Alfabetico",
|
||||||
"already-account": "Hai già un account?",
|
"already-account": "Hai già un account?",
|
||||||
"altitude": "Altitudine",
|
"altitude": "Altitudine",
|
||||||
"api-documentation": "Documentazione API",
|
"api-documentation": "Documentazione API",
|
||||||
"avatar": "Avatar",
|
"avatar": "Avatar",
|
||||||
|
"average-speed": "",
|
||||||
"basic-info": "Informazioni di base",
|
"basic-info": "Informazioni di base",
|
||||||
|
"before": "",
|
||||||
"can": "può",
|
"can": "può",
|
||||||
"cancel": "Annulla",
|
"cancel": "Annulla",
|
||||||
"card": "{n, plural, =1 {Carta} other {Carte}}",
|
"card": "{n, plural, =1 {Carta} other {Carte}}",
|
||||||
@@ -88,6 +91,7 @@
|
|||||||
"export-all-trails": "",
|
"export-all-trails": "",
|
||||||
"features": "Caratteristiche",
|
"features": "Caratteristiche",
|
||||||
"file-format": "Formato del file",
|
"file-format": "Formato del file",
|
||||||
|
"filter-categories": "",
|
||||||
"focus-map-on": "",
|
"focus-map-on": "",
|
||||||
"french": "Francese",
|
"french": "Francese",
|
||||||
"german": "Tedesco",
|
"german": "Tedesco",
|
||||||
@@ -142,6 +146,7 @@
|
|||||||
"new-password": "",
|
"new-password": "",
|
||||||
"new-trail": "Nuovo percorso",
|
"new-trail": "Nuovo percorso",
|
||||||
"no-account": "Non hai ancora un account?",
|
"no-account": "Non hai ancora un account?",
|
||||||
|
"no-data": "",
|
||||||
"no-preference": "Nessuna preferenza",
|
"no-preference": "Nessuna preferenza",
|
||||||
"no-results": "Nessun risultato trovato",
|
"no-results": "Nessun risultato trovato",
|
||||||
"not-a-valid-email-address": "Indirizzo email non valido",
|
"not-a-valid-email-address": "Indirizzo email non valido",
|
||||||
|
|||||||
@@ -12,13 +12,16 @@
|
|||||||
"add-to-list": "Toevoegen aan lijst",
|
"add-to-list": "Toevoegen aan lijst",
|
||||||
"add-waypoint": "Routepunt toevoegen",
|
"add-waypoint": "Routepunt toevoegen",
|
||||||
"added-trail-to": "Wandelroute toegevoegd aan",
|
"added-trail-to": "Wandelroute toegevoegd aan",
|
||||||
|
"after": "",
|
||||||
"all-activities": "",
|
"all-activities": "",
|
||||||
"alphabetical": "Alfabetisch",
|
"alphabetical": "Alfabetisch",
|
||||||
"already-account": "Heb je al een account?",
|
"already-account": "Heb je al een account?",
|
||||||
"altitude": "Hoogte",
|
"altitude": "Hoogte",
|
||||||
"api-documentation": "API-documentatie",
|
"api-documentation": "API-documentatie",
|
||||||
"avatar": "Profielfoto",
|
"avatar": "Profielfoto",
|
||||||
|
"average-speed": "",
|
||||||
"basic-info": "Algemene informatie",
|
"basic-info": "Algemene informatie",
|
||||||
|
"before": "",
|
||||||
"can": "",
|
"can": "",
|
||||||
"cancel": "Annuleren",
|
"cancel": "Annuleren",
|
||||||
"card": "{n, plural, =1 {kaart} other {kaarten}}",
|
"card": "{n, plural, =1 {kaart} other {kaarten}}",
|
||||||
@@ -88,6 +91,7 @@
|
|||||||
"export-all-trails": "",
|
"export-all-trails": "",
|
||||||
"features": "Kenmerken",
|
"features": "Kenmerken",
|
||||||
"file-format": "",
|
"file-format": "",
|
||||||
|
"filter-categories": "",
|
||||||
"focus-map-on": "",
|
"focus-map-on": "",
|
||||||
"french": "Frans",
|
"french": "Frans",
|
||||||
"german": "Duits",
|
"german": "Duits",
|
||||||
@@ -142,6 +146,7 @@
|
|||||||
"new-password": "",
|
"new-password": "",
|
||||||
"new-trail": "Nieuwe wandelroute",
|
"new-trail": "Nieuwe wandelroute",
|
||||||
"no-account": "Heb je nog geen account?",
|
"no-account": "Heb je nog geen account?",
|
||||||
|
"no-data": "",
|
||||||
"no-preference": "Geen voorkeur",
|
"no-preference": "Geen voorkeur",
|
||||||
"no-results": "Er zijn geen zoekresultaten",
|
"no-results": "Er zijn geen zoekresultaten",
|
||||||
"not-a-valid-email-address": "Het e-mailadres is ongeldig",
|
"not-a-valid-email-address": "Het e-mailadres is ongeldig",
|
||||||
|
|||||||
@@ -12,13 +12,16 @@
|
|||||||
"add-to-list": "Dodaj do listy",
|
"add-to-list": "Dodaj do listy",
|
||||||
"add-waypoint": "Dodaj Punkt",
|
"add-waypoint": "Dodaj Punkt",
|
||||||
"added-trail-to": "Dodaj ścieżkę do",
|
"added-trail-to": "Dodaj ścieżkę do",
|
||||||
|
"after": "",
|
||||||
"all-activities": "",
|
"all-activities": "",
|
||||||
"alphabetical": "Alfabetyczne",
|
"alphabetical": "Alfabetyczne",
|
||||||
"already-account": "Czy masz już konto?",
|
"already-account": "Czy masz już konto?",
|
||||||
"altitude": "Wysokość",
|
"altitude": "Wysokość",
|
||||||
"api-documentation": "Dokumentacja API",
|
"api-documentation": "Dokumentacja API",
|
||||||
"avatar": "Awatar",
|
"avatar": "Awatar",
|
||||||
|
"average-speed": "",
|
||||||
"basic-info": "Podstawowe informacje",
|
"basic-info": "Podstawowe informacje",
|
||||||
|
"before": "",
|
||||||
"can": "",
|
"can": "",
|
||||||
"cancel": "Anuluj",
|
"cancel": "Anuluj",
|
||||||
"card": "{n, plural, =1 {Karta} other {Karty}}",
|
"card": "{n, plural, =1 {Karta} other {Karty}}",
|
||||||
@@ -88,6 +91,7 @@
|
|||||||
"export-all-trails": "",
|
"export-all-trails": "",
|
||||||
"features": "Funkcje",
|
"features": "Funkcje",
|
||||||
"file-format": "",
|
"file-format": "",
|
||||||
|
"filter-categories": "",
|
||||||
"focus-map-on": "",
|
"focus-map-on": "",
|
||||||
"french": "Francuski",
|
"french": "Francuski",
|
||||||
"german": "Niemiecki",
|
"german": "Niemiecki",
|
||||||
@@ -142,6 +146,7 @@
|
|||||||
"new-password": "",
|
"new-password": "",
|
||||||
"new-trail": "Nowa ścieżka",
|
"new-trail": "Nowa ścieżka",
|
||||||
"no-account": "Nie masz konta?",
|
"no-account": "Nie masz konta?",
|
||||||
|
"no-data": "",
|
||||||
"no-preference": "Brak preferencji",
|
"no-preference": "Brak preferencji",
|
||||||
"no-results": "Brak wyników",
|
"no-results": "Brak wyników",
|
||||||
"not-a-valid-email-address": "Nieprawidłowy adres email",
|
"not-a-valid-email-address": "Nieprawidłowy adres email",
|
||||||
|
|||||||
@@ -12,13 +12,16 @@
|
|||||||
"add-to-list": "Adicionar à lista",
|
"add-to-list": "Adicionar à lista",
|
||||||
"add-waypoint": "Adicionar ponto de vista",
|
"add-waypoint": "Adicionar ponto de vista",
|
||||||
"added-trail-to": "Trilha adicionada para",
|
"added-trail-to": "Trilha adicionada para",
|
||||||
|
"after": "",
|
||||||
"all-activities": "",
|
"all-activities": "",
|
||||||
"alphabetical": "Alfabético",
|
"alphabetical": "Alfabético",
|
||||||
"already-account": "Já tem uma conta?",
|
"already-account": "Já tem uma conta?",
|
||||||
"altitude": "Altitude",
|
"altitude": "Altitude",
|
||||||
"api-documentation": "Documentação da API",
|
"api-documentation": "Documentação da API",
|
||||||
"avatar": "Avatar",
|
"avatar": "Avatar",
|
||||||
|
"average-speed": "",
|
||||||
"basic-info": "Informações básicas",
|
"basic-info": "Informações básicas",
|
||||||
|
"before": "",
|
||||||
"can": "",
|
"can": "",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"card": "{n, plural, =1 {Cartão} other {cartãos}}",
|
"card": "{n, plural, =1 {Cartão} other {cartãos}}",
|
||||||
@@ -88,6 +91,7 @@
|
|||||||
"export-all-trails": "",
|
"export-all-trails": "",
|
||||||
"features": "Características",
|
"features": "Características",
|
||||||
"file-format": "",
|
"file-format": "",
|
||||||
|
"filter-categories": "",
|
||||||
"focus-map-on": "",
|
"focus-map-on": "",
|
||||||
"french": "Francês",
|
"french": "Francês",
|
||||||
"german": "Alemão",
|
"german": "Alemão",
|
||||||
@@ -142,6 +146,7 @@
|
|||||||
"new-password": "",
|
"new-password": "",
|
||||||
"new-trail": "Nova trilha",
|
"new-trail": "Nova trilha",
|
||||||
"no-account": "Não tem uma conta?",
|
"no-account": "Não tem uma conta?",
|
||||||
|
"no-data": "",
|
||||||
"no-preference": "Nenhuma preferência",
|
"no-preference": "Nenhuma preferência",
|
||||||
"no-results": "Nenhum resultado encontrado",
|
"no-results": "Nenhum resultado encontrado",
|
||||||
"not-a-valid-email-address": "Não um endereço de e-mail válido",
|
"not-a-valid-email-address": "Não um endereço de e-mail válido",
|
||||||
|
|||||||
@@ -12,13 +12,16 @@
|
|||||||
"add-to-list": "添加到列表",
|
"add-to-list": "添加到列表",
|
||||||
"add-waypoint": "添加坐标",
|
"add-waypoint": "添加坐标",
|
||||||
"added-trail-to": "添加路线到",
|
"added-trail-to": "添加路线到",
|
||||||
|
"after": "",
|
||||||
"all-activities": "",
|
"all-activities": "",
|
||||||
"alphabetical": "字母",
|
"alphabetical": "字母",
|
||||||
"already-account": "已注册账户?",
|
"already-account": "已注册账户?",
|
||||||
"altitude": "海拔",
|
"altitude": "海拔",
|
||||||
"api-documentation": "API 文档",
|
"api-documentation": "API 文档",
|
||||||
"avatar": "头像",
|
"avatar": "头像",
|
||||||
|
"average-speed": "",
|
||||||
"basic-info": "基本信息",
|
"basic-info": "基本信息",
|
||||||
|
"before": "",
|
||||||
"can": "",
|
"can": "",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"card": "{n, plural, =1 {卡片} other {卡片}}",
|
"card": "{n, plural, =1 {卡片} other {卡片}}",
|
||||||
@@ -88,6 +91,7 @@
|
|||||||
"export-all-trails": "",
|
"export-all-trails": "",
|
||||||
"features": "特性",
|
"features": "特性",
|
||||||
"file-format": "",
|
"file-format": "",
|
||||||
|
"filter-categories": "",
|
||||||
"focus-map-on": "",
|
"focus-map-on": "",
|
||||||
"french": "法语",
|
"french": "法语",
|
||||||
"german": "德语",
|
"german": "德语",
|
||||||
@@ -142,6 +146,7 @@
|
|||||||
"new-password": "",
|
"new-password": "",
|
||||||
"new-trail": "创建新路线",
|
"new-trail": "创建新路线",
|
||||||
"no-account": "还未注册?",
|
"no-account": "还未注册?",
|
||||||
|
"no-data": "",
|
||||||
"no-preference": "尚未规划",
|
"no-preference": "尚未规划",
|
||||||
"no-results": "没有找到结果",
|
"no-results": "没有找到结果",
|
||||||
"not-a-valid-email-address": "无效电子邮箱地址",
|
"not-a-valid-email-address": "无效电子邮箱地址",
|
||||||
|
|||||||
@@ -26,5 +26,10 @@ class SummitLog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SummitLogFilter {
|
||||||
|
category: string[],
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export { SummitLog };
|
export { SummitLog, type SummitLogFilter };
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import { SummitLog } from "$lib/models/summit_log";
|
import { SummitLog, type SummitLogFilter } from "$lib/models/summit_log";
|
||||||
import { pb } from "$lib/pocketbase";
|
import { pb } from "$lib/pocketbase";
|
||||||
import { ClientResponseError } from "pocketbase";
|
import { ClientResponseError } from "pocketbase";
|
||||||
import { writable, type Writable } from "svelte/store";
|
import { writable, type Writable } from "svelte/store";
|
||||||
|
import { fetchGPX } from "./trail_store";
|
||||||
|
|
||||||
export const summitLog: Writable<SummitLog> = writable(new SummitLog(new Date().toISOString().substring(0, 10)));
|
export const summitLog: Writable<SummitLog> = writable(new SummitLog(new Date().toISOString().substring(0, 10)));
|
||||||
export const summitLogs: Writable<SummitLog[]> = writable([]);
|
export const summitLogs: Writable<SummitLog[]> = writable([]);
|
||||||
|
|
||||||
export async function summit_logs_index(f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
export async function summit_logs_index(filter?: SummitLogFilter, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||||
const r = await f('/api/v1/summit-log?', {
|
const filterText = filter ? buildFilterText(filter) : "";
|
||||||
|
|
||||||
|
const r = await f('/api/v1/summit-log?' + new URLSearchParams({
|
||||||
|
filter: filterText,
|
||||||
|
}), {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -17,6 +22,18 @@ export async function summit_logs_index(f: (url: RequestInfo | URL, config?: Req
|
|||||||
|
|
||||||
const fetchedSummitLogs: SummitLog[] = await r.json();
|
const fetchedSummitLogs: SummitLog[] = await r.json();
|
||||||
|
|
||||||
|
for (const log of fetchedSummitLogs) {
|
||||||
|
if (!log.gpx) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const gpxData: string = await fetchGPX(log as any, f);
|
||||||
|
|
||||||
|
if (!log.expand) {
|
||||||
|
log.expand = {};
|
||||||
|
}
|
||||||
|
log.expand.gpx_data = gpxData;
|
||||||
|
}
|
||||||
|
|
||||||
summitLogs.set(fetchedSummitLogs);
|
summitLogs.set(fetchedSummitLogs);
|
||||||
|
|
||||||
return fetchedSummitLogs;
|
return fetchedSummitLogs;
|
||||||
@@ -95,3 +112,22 @@ export async function summit_logs_delete(summitLog: SummitLog) {
|
|||||||
throw new ClientResponseError(await r.json())
|
throw new ClientResponseError(await r.json())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildFilterText(filter: SummitLogFilter,): string {
|
||||||
|
let filterText: string = "";
|
||||||
|
|
||||||
|
if (filter.category.length > 0) {
|
||||||
|
filterText += `trails_via_summit_logs.category != null && '${filter.category.join(",")}' ~ trails_via_summit_logs.category`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.startDate) {
|
||||||
|
filterText += `${filter.category.length ? ' && ' : ''}date >= '${filter.startDate}'`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.endDate) {
|
||||||
|
filterText += `${filter.category.length || filter.startDate ? ' && ' : ''} date <= '${filter.endDate}'`
|
||||||
|
}
|
||||||
|
|
||||||
|
return filterText;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -50,6 +50,22 @@ export function formatElevation(meters?: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatSpeed(speed?: number) {
|
||||||
|
if (speed === undefined) {
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
const unit = get(page).data.settings?.unit ?? "metric";
|
||||||
|
|
||||||
|
if (unit == "metric") {
|
||||||
|
return `${Math.round(speed)} km/h`
|
||||||
|
} else {
|
||||||
|
const mph = speed * 0.621371;
|
||||||
|
|
||||||
|
return `${Math.round(mph)} mp/h`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function formatTimeSince(date: Date) {
|
export function formatTimeSince(date: Date) {
|
||||||
|
|
||||||
var seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000);
|
var seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000);
|
||||||
|
|||||||
@@ -3,10 +3,13 @@ import { pb } from '$lib/pocketbase';
|
|||||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||||
|
|
||||||
export async function GET(event: RequestEvent) {
|
export async function GET(event: RequestEvent) {
|
||||||
|
const filter = event.url.searchParams.get('filter') ?? ""
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const r: SummitLog[] = await pb.collection('summit_logs').getFullList<SummitLog>({
|
const r: SummitLog[] = await pb.collection('summit_logs').getFullList<SummitLog>({
|
||||||
expand: "trails_via_summit_logs.category",
|
expand: "trails_via_summit_logs.category",
|
||||||
sort: "+date"
|
sort: "+date",
|
||||||
|
filter: filter
|
||||||
})
|
})
|
||||||
return json(r)
|
return json(r)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { page } from "$app/stores";
|
||||||
import Calendar from "$lib/components/base/calendar.svelte";
|
import Calendar from "$lib/components/base/calendar.svelte";
|
||||||
|
import Datepicker from "$lib/components/base/datepicker.svelte";
|
||||||
|
import MultiSelect from "$lib/components/base/multi_select.svelte";
|
||||||
import Select, {
|
import Select, {
|
||||||
type SelectItem,
|
type SelectItem,
|
||||||
} from "$lib/components/base/select.svelte";
|
} from "$lib/components/base/select.svelte";
|
||||||
import SummitLogTable from "$lib/components/summit_log/summit_log_table.svelte";
|
import SummitLogTable from "$lib/components/summit_log/summit_log_table.svelte";
|
||||||
import { summitLogs } from "$lib/stores/summit_log_store";
|
import { categories } from "$lib/stores/category_store.js";
|
||||||
|
import {
|
||||||
|
summit_logs_index,
|
||||||
|
summitLogs,
|
||||||
|
} from "$lib/stores/summit_log_store";
|
||||||
import { currentUser } from "$lib/stores/user_store";
|
import { currentUser } from "$lib/stores/user_store";
|
||||||
import { getFileURL } from "$lib/util/file_util";
|
import { getFileURL } from "$lib/util/file_util";
|
||||||
import {
|
import {
|
||||||
formatDistance,
|
formatDistance,
|
||||||
formatElevation,
|
formatElevation,
|
||||||
|
formatSpeed,
|
||||||
formatTimeHHMM,
|
formatTimeHHMM,
|
||||||
} from "$lib/util/format_util";
|
} from "$lib/util/format_util";
|
||||||
import {
|
import {
|
||||||
@@ -25,6 +33,8 @@
|
|||||||
import { Bar, Pie } from "svelte-chartjs";
|
import { Bar, Pie } from "svelte-chartjs";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
|
||||||
ChartJS.register(
|
ChartJS.register(
|
||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@@ -35,6 +45,13 @@
|
|||||||
BarElement,
|
BarElement,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const filter = data.filter;
|
||||||
|
|
||||||
|
const categorySelectItems: SelectItem[] = $categories.map((c) => ({
|
||||||
|
value: c.id,
|
||||||
|
text: c.name,
|
||||||
|
}));
|
||||||
|
|
||||||
const barChartSelectItems: SelectItem[] = [
|
const barChartSelectItems: SelectItem[] = [
|
||||||
{
|
{
|
||||||
text: $_("distance"),
|
text: $_("distance"),
|
||||||
@@ -65,7 +82,13 @@
|
|||||||
"#ffafcc",
|
"#ffafcc",
|
||||||
];
|
];
|
||||||
|
|
||||||
$: categories = $summitLogs.reduce(
|
const conversionFactors = {
|
||||||
|
distance: 0.621371,
|
||||||
|
elevation_gain: 3.28084,
|
||||||
|
elevation_loss: 3.28084,
|
||||||
|
};
|
||||||
|
|
||||||
|
$: logCategories = $summitLogs.reduce(
|
||||||
(acc, log) => {
|
(acc, log) => {
|
||||||
const cat =
|
const cat =
|
||||||
log.expand.trails_via_summit_logs?.at(0)?.expand.category
|
log.expand.trails_via_summit_logs?.at(0)?.expand.category
|
||||||
@@ -76,8 +99,8 @@
|
|||||||
{} as Record<string, number>,
|
{} as Record<string, number>,
|
||||||
);
|
);
|
||||||
|
|
||||||
$: categoryLabels = Object.keys(categories).sort();
|
$: categoryLabels = Object.keys(logCategories).sort();
|
||||||
$: categoryValues = Object.values(categories);
|
$: categoryValues = Object.values(logCategories);
|
||||||
$: categoryColorMap = Object.fromEntries(
|
$: categoryColorMap = Object.fromEntries(
|
||||||
categoryLabels.map((label, index) => [
|
categoryLabels.map((label, index) => [
|
||||||
label,
|
label,
|
||||||
@@ -95,12 +118,19 @@
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
$: barChartUnit =
|
function barChartUnit() {
|
||||||
barChartSelectedValue == "duration"
|
const unit = $page.data.settings?.unit ?? "metric";
|
||||||
? "min"
|
|
||||||
: barChartSelectedValue == "distance"
|
switch (barChartSelectedValue) {
|
||||||
? "km"
|
case "duration":
|
||||||
: "m";
|
return "min";
|
||||||
|
case "elevation_gain":
|
||||||
|
case "elevation_loss":
|
||||||
|
return unit == "metric" ? "m" : "ft";
|
||||||
|
case "distance":
|
||||||
|
return unit == "metric" ? "km" : "mi";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$: barChartDataByDate = $summitLogs.reduce(
|
$: barChartDataByDate = $summitLogs.reduce(
|
||||||
(acc, log) => {
|
(acc, log) => {
|
||||||
@@ -115,6 +145,11 @@
|
|||||||
if (barChartSelectedValue === "distance") {
|
if (barChartSelectedValue === "distance") {
|
||||||
acc[date] = acc[date] / 1000;
|
acc[date] = acc[date] / 1000;
|
||||||
}
|
}
|
||||||
|
if ($page.data.settings?.unit !== "metric") {
|
||||||
|
acc[date] =
|
||||||
|
acc[date] *
|
||||||
|
(conversionFactors as any)[barChartSelectedValue];
|
||||||
|
}
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
{} as Record<string, number>,
|
{} as Record<string, number>,
|
||||||
@@ -156,11 +191,60 @@
|
|||||||
(sum, log) => sum + (log.elevation_loss ?? 0),
|
(sum, log) => sum + (log.elevation_loss ?? 0),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$: averageSpeed =
|
||||||
|
totalDuration > 0 ? totalDistance / totalDuration : undefined;
|
||||||
|
|
||||||
|
function updateFilterCategory(categories: SelectItem[]) {
|
||||||
|
filter.category = categories.map((c) => c.value);
|
||||||
|
loadSummitLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDateClick(date: Date) {
|
||||||
|
const datePlusN = new Date();
|
||||||
|
datePlusN.setDate(date.getDate() + 1);
|
||||||
|
filter.startDate = datePlusN.toISOString().slice(0, 10);
|
||||||
|
datePlusN.setDate(date.getDate() + 2);
|
||||||
|
filter.endDate = datePlusN.toISOString().slice(0, 10);
|
||||||
|
loadSummitLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSummitLogs() {
|
||||||
|
const logs = await summit_logs_index(filter);
|
||||||
|
summitLogs.set(logs);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="grid grid-cols-5 gap-4 items-start max-w-6xl mx-auto">
|
<svelte:head>
|
||||||
|
<title>{$_("profile")} | wanderer</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="border border-input-border rounded-xl p-6 col-span-2 row-span-2 space-y-6"
|
class="grid grid-cols-1 md:grid-cols-[356px_minmax(0,_1fr)] gap-y-4 items-start max-w-6xl mx-auto"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex flex-wrap md:flex-nowrap col-start-1 md:col-start-2 gap-x-4 justify-end"
|
||||||
|
>
|
||||||
|
<MultiSelect
|
||||||
|
on:change={(e) => updateFilterCategory(e.detail)}
|
||||||
|
label={$_("categories")}
|
||||||
|
items={categorySelectItems}
|
||||||
|
placeholder={`${$_("filter-categories")}...`}
|
||||||
|
></MultiSelect>
|
||||||
|
<Datepicker
|
||||||
|
on:change={loadSummitLogs}
|
||||||
|
bind:value={filter.startDate}
|
||||||
|
label={$_("after")}
|
||||||
|
></Datepicker>
|
||||||
|
<Datepicker
|
||||||
|
on:change={loadSummitLogs}
|
||||||
|
bind:value={filter.endDate}
|
||||||
|
label={$_("before")}
|
||||||
|
></Datepicker>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="border border-input-border rounded-xl p-6 lg:row-span-3 space-y-6 grow-0 md:mr-4"
|
||||||
>
|
>
|
||||||
{#if $currentUser}
|
{#if $currentUser}
|
||||||
<div class="flex items-center gap-x-6">
|
<div class="flex items-center gap-x-6">
|
||||||
@@ -188,8 +272,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<Calendar logs={$summitLogs} colorMap={categoryColorMap}></Calendar>
|
<Calendar
|
||||||
|
on:click={(e) => handleDateClick(e.detail)}
|
||||||
|
logs={$summitLogs}
|
||||||
|
colorMap={categoryColorMap}
|
||||||
|
></Calendar>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
<div
|
<div
|
||||||
class="flex flex-col items-center gap-6 border border-input-border rounded-xl p-6"
|
class="flex flex-col items-center gap-6 border border-input-border rounded-xl p-6"
|
||||||
>
|
>
|
||||||
@@ -225,7 +314,9 @@
|
|||||||
"elevation-gain",
|
"elevation-gain",
|
||||||
)}</span
|
)}</span
|
||||||
>
|
>
|
||||||
<p class="text-4xl font-bold">{formatElevation(totalElevationGain)}</p>
|
<p class="text-4xl font-bold">
|
||||||
|
{formatElevation(totalElevationGain)}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="flex flex-col items-center gap-6 border border-input-border rounded-xl p-6"
|
class="flex flex-col items-center gap-6 border border-input-border rounded-xl p-6"
|
||||||
@@ -235,13 +326,27 @@
|
|||||||
"elevation-loss",
|
"elevation-loss",
|
||||||
)}</span
|
)}</span
|
||||||
>
|
>
|
||||||
<p class="text-4xl font-bold">{formatElevation(totalElevationLoss)}</p>
|
<p class="text-4xl font-bold">
|
||||||
|
{formatElevation(totalElevationLoss)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex flex-col items-center gap-6 border border-input-border rounded-xl p-6"
|
||||||
|
>
|
||||||
|
<span class="text-gray-500 font-semibold text-lg self-start"
|
||||||
|
><i class="fa fa-arrow-trend-down mr-3"></i>{$_(
|
||||||
|
"average-speed",
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
<p class="text-4xl font-bold">{formatSpeed(averageSpeed)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="col-start-1 col-span-1 border border-input-border rounded-xl p-6 space-y-4"
|
class="col-start-1 col-span-1 border border-input-border rounded-xl p-6 space-y-4"
|
||||||
>
|
>
|
||||||
<span class="text-gray-500 font-semibold text-lg"
|
<span class="text-gray-500 font-semibold text-lg"
|
||||||
><i class="fa fa-person-hiking mr-3"></i>{$_("categories")}</span
|
><i class="fa fa-person-hiking mr-3"></i>{$_(
|
||||||
|
"categories",
|
||||||
|
)}</span
|
||||||
>
|
>
|
||||||
<Pie
|
<Pie
|
||||||
data={categoryChartData}
|
data={categoryChartData}
|
||||||
@@ -256,7 +361,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="h-full col-span-3 space-y-2 border border-input-border rounded-xl p-6"
|
class="h-full md:col-span-2 space-y-2 border border-input-border rounded-xl p-6"
|
||||||
>
|
>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<span class="text-gray-500 font-semibold text-lg"
|
<span class="text-gray-500 font-semibold text-lg"
|
||||||
@@ -280,7 +385,7 @@
|
|||||||
tooltip: {
|
tooltip: {
|
||||||
callbacks: {
|
callbacks: {
|
||||||
label: (item) =>
|
label: (item) =>
|
||||||
`${item.dataset.label}: ${item.formattedValue} ${barChartUnit}`,
|
`${item.dataset.label}: ${item.formattedValue} ${barChartUnit()}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -288,7 +393,7 @@
|
|||||||
y: {
|
y: {
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: function (value, index, ticks) {
|
callback: function (value, index, ticks) {
|
||||||
return value + " " + barChartUnit;
|
return value + " " + barChartUnit();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -296,11 +401,17 @@
|
|||||||
}}
|
}}
|
||||||
></Bar>
|
></Bar>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="col-span-5 border border-input-border rounded-xl p-6 space-y-6">
|
<div
|
||||||
|
class="col-span-1 md:col-span-2 border border-input-border rounded-xl p-6 space-y-6"
|
||||||
|
>
|
||||||
<span class="text-gray-500 font-semibold text-lg"
|
<span class="text-gray-500 font-semibold text-lg"
|
||||||
><i class="fa fa-table mr-3"></i>{$_("all-activities")}</span
|
><i class="fa fa-table mr-3"></i>{$_("all-activities")}</span
|
||||||
>
|
>
|
||||||
<SummitLogTable summitLogs={$summitLogs} showCategory></SummitLogTable>
|
<div class=" overflow-x-scroll">
|
||||||
|
<SummitLogTable summitLogs={$summitLogs} showCategory showTrail
|
||||||
|
></SummitLogTable>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import { summit_logs_index, summitLogs } from "$lib/stores/summit_log_store";
|
import type { SummitLogFilter } from "$lib/models/summit_log";
|
||||||
|
import { categories_index } from "$lib/stores/category_store";
|
||||||
|
import { summit_logs_index } from "$lib/stores/summit_log_store";
|
||||||
import { fetchGPX } from "$lib/stores/trail_store";
|
import { fetchGPX } from "$lib/stores/trail_store";
|
||||||
import { type ServerLoad } from "@sveltejs/kit";
|
import { type ServerLoad } from "@sveltejs/kit";
|
||||||
import { get } from "svelte/store";
|
|
||||||
|
|
||||||
export const load: ServerLoad = async ({ params, locals, fetch }) => {
|
export const load: ServerLoad = async ({ params, locals, fetch }) => {
|
||||||
const logs = await summit_logs_index(fetch);
|
|
||||||
|
|
||||||
for (const log of logs) {
|
const date = new Date(), y = date.getFullYear(), m = date.getMonth();
|
||||||
if (!log.gpx) {
|
const firstDay = new Date(y, m, 1);
|
||||||
continue
|
const lastDay = new Date(y, m + 1, 0);
|
||||||
|
|
||||||
|
const categories = await categories_index(fetch)
|
||||||
|
|
||||||
|
const filter: SummitLogFilter = {
|
||||||
|
startDate: firstDay.toISOString().slice(0, 10),
|
||||||
|
endDate: lastDay.toISOString().slice(0, 10),
|
||||||
|
category: []
|
||||||
}
|
}
|
||||||
const gpxData: string = await fetchGPX(log as any, fetch);
|
const logs = await summit_logs_index(filter, fetch);
|
||||||
|
|
||||||
if (!log.expand) {
|
|
||||||
log.expand = {};
|
|
||||||
}
|
|
||||||
log.expand.gpx_data = gpxData;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(logs);
|
|
||||||
|
|
||||||
|
return {filter}
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user