Migrates to svelte 5
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
import { page } from "$app/state";
|
||||
import type { SelectItem } from "$lib/components/base/select.svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
import type { Snippet } from "svelte";
|
||||
interface Props {
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
let { children }: Props = $props();
|
||||
|
||||
const settingsLinks: SelectItem[] = [
|
||||
{ text: $_("profile"), value: "/settings/profile" },
|
||||
@@ -22,11 +28,12 @@
|
||||
},
|
||||
];
|
||||
|
||||
$: activeIndex = settingsLinks.findIndex(
|
||||
(l) => l.value === $page.url.pathname,
|
||||
let activeIndex = $derived(
|
||||
settingsLinks.findIndex((l) => l.value === page.url.pathname),
|
||||
);
|
||||
|
||||
function handleItemClick(item: SelectItem) {
|
||||
function handleItemClick(e: Event, item: SelectItem) {
|
||||
e.stopPropagation();
|
||||
if (item.value != settingsLinks.at(-1)?.value) {
|
||||
goto(item.value);
|
||||
} else {
|
||||
@@ -44,13 +51,13 @@
|
||||
class="menu-item flex items-center px-4 py-3 my-1 cursor-pointer hover:bg-menu-item-background-hover focus:bg-menu-item-background-focus transition-colors rounded-md"
|
||||
class:bg-menu-item-background-hover={i == activeIndex}
|
||||
role="presentation"
|
||||
on:mousedown|stopPropagation={() => handleItemClick(item)}
|
||||
onmousedown={(e) => handleItemClick(e, item)}
|
||||
>
|
||||
{item.text}
|
||||
</li>
|
||||
{/each}
|
||||
</menu>
|
||||
<div class="settings-content">
|
||||
<slot></slot>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
import { page } from "$app/state";
|
||||
import ConfirmModal from "$lib/components/confirm_modal.svelte";
|
||||
import EmailModal from "$lib/components/settings/email_modal.svelte";
|
||||
import PasswordModal from "$lib/components/settings/password_modal.svelte";
|
||||
@@ -14,16 +14,16 @@
|
||||
import { onMount } from "svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
const settings = $page.data.settings;
|
||||
const settings = page.data.settings;
|
||||
|
||||
let selectedLanguage = "en";
|
||||
let selectedMapFocus = "trails";
|
||||
|
||||
let citySearchQuery: string = "";
|
||||
|
||||
let openConfirmModal: () => void;
|
||||
let openEmailModal: () => void;
|
||||
let openPasswordModal: () => void;
|
||||
let confirmModal: ConfirmModal;
|
||||
let emailModal: EmailModal;
|
||||
let passwordModal: PasswordModal;
|
||||
|
||||
onMount(() => {
|
||||
citySearchQuery = settings?.location?.name ?? "";
|
||||
@@ -84,33 +84,37 @@
|
||||
<hr class="mt-4 mb-6 border-input-border" />
|
||||
<div class="space-y-6">
|
||||
<h4 class="text-xl font-medium">{$_("login-details")}</h4>
|
||||
<button class="btn-secondary block" on:click={openEmailModal}
|
||||
>{$_("change-email")}</button
|
||||
<button
|
||||
class="btn-secondary block"
|
||||
onclick={() => emailModal.openModal()}>{$_("change-email")}</button
|
||||
>
|
||||
<button class="btn-secondary" on:click={openPasswordModal}
|
||||
<button class="btn-secondary" onclick={() => passwordModal.openModal()}
|
||||
>{$_("change-password")}</button
|
||||
>
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-xl text-red-400 font-medium">
|
||||
{$_("danger-zone")}
|
||||
</h4>
|
||||
<button id="delete-account" class="btn-danger" on:click={openConfirmModal}
|
||||
<button
|
||||
id="delete-account"
|
||||
class="btn-danger"
|
||||
onclick={() => confirmModal.openModal()}
|
||||
>{$_("delete-account")}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<EmailModal
|
||||
email={$currentUser.email}
|
||||
on:save={(e) => updateEmail(e.detail)}
|
||||
bind:openModal={openEmailModal}
|
||||
onsave={updateEmail}
|
||||
bind:this={emailModal}
|
||||
></EmailModal>
|
||||
<PasswordModal
|
||||
on:save={(e) => updatePassword(e.detail)}
|
||||
bind:openModal={openPasswordModal}
|
||||
onsave={updatePassword}
|
||||
bind:this={passwordModal}
|
||||
></PasswordModal>
|
||||
{/if}
|
||||
<ConfirmModal
|
||||
text={$_("account-delete-confirm")}
|
||||
bind:openModal={openConfirmModal}
|
||||
on:confirm={deleteAccount}
|
||||
bind:this={confirmModal}
|
||||
onconfirm={deleteAccount}
|
||||
></ConfirmModal>
|
||||
|
||||
@@ -13,22 +13,22 @@
|
||||
import JSZip from "jszip";
|
||||
import { _ } from "svelte-i18n";
|
||||
import { linear } from "svelte/easing";
|
||||
import { tweened } from "svelte/motion";
|
||||
import { Tween } from "svelte/motion";
|
||||
import emptyStateUploadDark from "$lib/assets/svgs/empty_states/empty_state_upload_dark.svg";
|
||||
import emptyStateUploadLight from "$lib/assets/svgs/empty_states/empty_state_upload_light.svg";
|
||||
import { theme } from "$lib/stores/theme_store";
|
||||
import { onMount } from "svelte";
|
||||
import JSConfetti from "js-confetti";
|
||||
|
||||
const uploadProgress = tweened(0, {
|
||||
const uploadProgress = new Tween(0, {
|
||||
duration: 300,
|
||||
easing: linear,
|
||||
});
|
||||
|
||||
let openExportModal: () => void;
|
||||
let exportModal: TrailExportModal;
|
||||
|
||||
let offerUpload: boolean = false;
|
||||
let uploading: boolean = false;
|
||||
let offerUpload: boolean = $state(false);
|
||||
let uploading: boolean = $state(false);
|
||||
|
||||
let jsConfetti: JSConfetti;
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
uploading = false;
|
||||
jsConfetti.addConfetti({
|
||||
confettiRadius: 4,
|
||||
emojis: ['🍃', '🍁'],
|
||||
emojis: ["🍃", "🍁"],
|
||||
});
|
||||
}, 500);
|
||||
|
||||
@@ -191,13 +191,18 @@
|
||||
: 'border border-content border-dashed'} rounded-xl flex items-center justify-center text-gray-500 bg-background cursor-pointer hover:bg-menu-item-background-hover focus:bg-menu-item-background-focus transition-colors"
|
||||
class:bg-menu-item-background-hover={uploading}
|
||||
class:border-2={offerUpload && !uploading}
|
||||
style="--progress: {$uploadProgress}%"
|
||||
on:click={openFileBrowser}
|
||||
on:dragover={handleDragOver}
|
||||
on:dragleave={handleDragLeave}
|
||||
on:drop={handleDrop}
|
||||
style="--progress: {uploadProgress.current}%"
|
||||
onclick={openFileBrowser}
|
||||
ondragover={handleDragOver}
|
||||
ondragleave={handleDragLeave}
|
||||
ondrop={handleDrop}
|
||||
>
|
||||
<canvas id="confetti-canvas" class="absolute" style="width: 100%; height: 200%"> </canvas>
|
||||
<canvas
|
||||
id="confetti-canvas"
|
||||
class="absolute"
|
||||
style="width: 100%; height: 200%"
|
||||
>
|
||||
</canvas>
|
||||
<div class="">
|
||||
<img
|
||||
class="rounded-full aspect-square mx-auto"
|
||||
@@ -216,18 +221,18 @@
|
||||
accept=".gpx,.GPX,.tcx,.TCX,.kml,.KML,.fit,.FIT"
|
||||
multiple={true}
|
||||
style="display: none;"
|
||||
on:change={() => handleFileSelection()}
|
||||
onchange={() => handleFileSelection()}
|
||||
/>
|
||||
<h3 class="text-2xl font-semibold">{$_("export")}</h3>
|
||||
<hr class="mt-4 mb-6 border-input-border" />
|
||||
<Button secondary={true} on:click={() => openExportModal()}
|
||||
<Button secondary={true} onclick={() => exportModal.openModal()}
|
||||
>{$_("export-all-trails")}</Button
|
||||
>
|
||||
</div>
|
||||
|
||||
<TrailExportModal
|
||||
bind:openModal={openExportModal}
|
||||
on:export={(e) => exportTrails(e.detail)}
|
||||
bind:this={exportModal}
|
||||
onexport={exportTrails}
|
||||
></TrailExportModal>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/stores";
|
||||
import { page } from "$app/state";
|
||||
import RadioGroup, {
|
||||
type RadioItem,
|
||||
} from "$lib/components/base/radio_group.svelte";
|
||||
@@ -10,7 +10,7 @@
|
||||
import { settings_update } from "$lib/stores/settings_store";
|
||||
import { _, locale } from "svelte-i18n";
|
||||
|
||||
const settings = $page.data.settings;
|
||||
const settings = page.data.settings;
|
||||
|
||||
const languages: SelectItem[] = [
|
||||
{ text: $_("chinese"), value: "zh" },
|
||||
@@ -30,7 +30,7 @@
|
||||
{ text: $_("imperial"), value: "imperial" },
|
||||
];
|
||||
|
||||
let selectedLanguage = settings?.language ?? "en";
|
||||
let selectedLanguage = $state(settings?.language ?? "en");
|
||||
|
||||
async function handleLanguageSelection(
|
||||
value: Language,
|
||||
@@ -59,12 +59,12 @@
|
||||
<Select
|
||||
items={languages}
|
||||
bind:value={selectedLanguage}
|
||||
on:change={(e) => handleLanguageSelection(e.detail)}
|
||||
onchange={handleLanguageSelection}
|
||||
></Select>
|
||||
<h4 class="text-xl font-medium mt-6 mb-2">{$_("units")}</h4>
|
||||
<RadioGroup
|
||||
name="unit"
|
||||
items={units}
|
||||
selected={settings?.unit == "metric" ? 0 : 1}
|
||||
on:change={(e) => handleUnitSelection(e.detail)}
|
||||
onchange={handleUnitSelection}
|
||||
></RadioGroup>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/stores";
|
||||
import { page } from "$app/state";
|
||||
import Search, {
|
||||
type SearchItem,
|
||||
} from "$lib/components/base/search.svelte";
|
||||
@@ -14,7 +14,7 @@
|
||||
import { onMount } from "svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
$: settings = $page.data.settings;
|
||||
let settings = $derived(page.data.settings);
|
||||
|
||||
const mapFocus: SelectItem[] = [
|
||||
{ text: $_("trail", { values: { n: 2 } }), value: "trails" },
|
||||
@@ -22,15 +22,15 @@
|
||||
];
|
||||
|
||||
let selectedLanguage = "en";
|
||||
let selectedMapFocus = "trails";
|
||||
let selectedMapFocus = $state("trails");
|
||||
|
||||
let searchDropdownItems: SearchItem[] = [];
|
||||
let citySearchQuery: string = "";
|
||||
let searchDropdownItems: SearchItem[] = $state([]);
|
||||
let citySearchQuery: string = $state("");
|
||||
|
||||
let customTilesetName: string = "";
|
||||
let customTilesetURL: string = "";
|
||||
let terrainURL: string = "";
|
||||
let hillshadingURL: string = "";
|
||||
let customTilesetName: string = $state("");
|
||||
let customTilesetURL: string = $state("");
|
||||
let terrainURL: string = $state("");
|
||||
let hillshadingURL: string = $state("");
|
||||
|
||||
onMount(() => {
|
||||
citySearchQuery = settings?.location?.name ?? "";
|
||||
@@ -106,9 +106,10 @@
|
||||
});
|
||||
}
|
||||
|
||||
$: terrainSaveEnabled =
|
||||
let terrainSaveEnabled = $derived(
|
||||
terrainURL !== settings?.terrain?.terrain ||
|
||||
hillshadingURL !== settings?.terrain?.hillshading;
|
||||
hillshadingURL !== settings?.terrain?.hillshading,
|
||||
);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -123,7 +124,7 @@
|
||||
<Select
|
||||
items={mapFocus}
|
||||
bind:value={selectedMapFocus}
|
||||
on:change={(e) => handleMapFocusSelection(e.detail)}
|
||||
onchange={handleMapFocusSelection}
|
||||
></Select>
|
||||
{#if selectedMapFocus == "location"}
|
||||
<div class="mt-3">
|
||||
@@ -132,8 +133,8 @@
|
||||
placeholder="{$_('search-cities')}..."
|
||||
clearAfterSelect={false}
|
||||
bind:value={citySearchQuery}
|
||||
on:update={(e) => searchCities(e.detail)}
|
||||
on:click={(e) => handleSearchClick(e.detail)}
|
||||
onupdate={searchCities}
|
||||
onclick={handleSearchClick}
|
||||
></Search>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -150,8 +151,9 @@
|
||||
<p class="text-sm text-gray-500">{tileset.url}</p>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Delete tileset"
|
||||
class="btn-icon"
|
||||
on:click={() => handleTilesetDelete(i)}
|
||||
onclick={() => handleTilesetDelete(i)}
|
||||
><i class="fa fa-trash text-red-500"></i></button
|
||||
>
|
||||
</div>
|
||||
@@ -170,7 +172,10 @@
|
||||
placeholder="https://.../style.json"
|
||||
></TextField>
|
||||
</div>
|
||||
<button class="btn-icon mt-6" on:click={handleTilesetAdd}
|
||||
<button
|
||||
aria-label="Add tileset"
|
||||
class="btn-icon mt-6"
|
||||
onclick={handleTilesetAdd}
|
||||
><i class="fa fa-plus"></i></button
|
||||
>
|
||||
</div>
|
||||
@@ -195,10 +200,11 @@
|
||||
></TextField>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Save terrain settings"
|
||||
disabled={!terrainSaveEnabled}
|
||||
class="btn-icon mt-6"
|
||||
class:hover:!bg-background={!terrainSaveEnabled}
|
||||
on:click={handleTerrainAdd}
|
||||
onclick={handleTerrainAdd}
|
||||
class:text-gray-500={!terrainSaveEnabled}
|
||||
><i class="fa fa-save"></i></button
|
||||
>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/stores";
|
||||
import { page } from "$app/state";
|
||||
import Toggle from "$lib/components/base/toggle.svelte";
|
||||
import { NotificationType } from "$lib/models/notification";
|
||||
import type { Settings } from "$lib/models/settings";
|
||||
import { settings_update } from "$lib/stores/settings_store";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
const notifications = ($page.data.settings as Settings)?.notifications ?? {
|
||||
const notifications = $state((page.data.settings as Settings)?.notifications ?? {
|
||||
list_create: {
|
||||
web: true,
|
||||
email: true,
|
||||
@@ -31,7 +31,7 @@
|
||||
web: true,
|
||||
email: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const notificationItems: { text: string; key: NotificationType }[] = [
|
||||
{
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||
async function updateNotificationSettings() {
|
||||
await settings_update({
|
||||
id: $page.data.settings!.id,
|
||||
id: page.data.settings!.id,
|
||||
notifications,
|
||||
});
|
||||
}
|
||||
@@ -85,13 +85,13 @@
|
||||
<p>{item.text}</p>
|
||||
<div>
|
||||
<Toggle
|
||||
on:change={updateNotificationSettings}
|
||||
onchange={updateNotificationSettings}
|
||||
bind:value={notifications[item.key].web}
|
||||
></Toggle>
|
||||
</div>
|
||||
<div>
|
||||
<Toggle
|
||||
on:change={updateNotificationSettings}
|
||||
onchange={updateNotificationSettings}
|
||||
bind:value={notifications[item.key].email}
|
||||
></Toggle>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/stores";
|
||||
import { page } from "$app/state";
|
||||
import RadioGroup, {
|
||||
type RadioItem,
|
||||
} from "$lib/components/base/radio_group.svelte";
|
||||
@@ -7,7 +7,7 @@
|
||||
import { settings_update } from "$lib/stores/settings_store";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
$: settings = $page.data.settings as Settings;
|
||||
let settings = $derived(page.data.settings as Settings);
|
||||
|
||||
const accoutPrivacyItems: RadioItem[] = [
|
||||
{
|
||||
@@ -93,7 +93,7 @@
|
||||
name="account"
|
||||
items={accoutPrivacyItems}
|
||||
selected={settings?.privacy?.account == "private" ? 1 : 0}
|
||||
on:change={(e) => handleAccountSelection(e.detail)}
|
||||
onchange={handleAccountSelection}
|
||||
></RadioGroup>
|
||||
|
||||
<h4 class="text-xl font-medium mt-12 mb-4">
|
||||
@@ -103,7 +103,7 @@
|
||||
name="trails"
|
||||
items={trailPrivacyItems}
|
||||
selected={settings?.privacy?.trails == "public" ? 0 : 1}
|
||||
on:change={(e) => handleTrailsSelection(e.detail)}
|
||||
onchange={handleTrailsSelection}
|
||||
></RadioGroup>
|
||||
|
||||
<h4 class="text-xl font-medium mt-12 mb-4">
|
||||
@@ -113,5 +113,5 @@
|
||||
name="lists"
|
||||
items={listPrivacyItems}
|
||||
selected={settings?.privacy?.lists == "public" ? 0 : 1}
|
||||
on:change={(e) => handleListsSelection(e.detail)}
|
||||
onchange={handleListsSelection}
|
||||
></RadioGroup>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/stores";
|
||||
import { page } from "$app/state";
|
||||
import Button from "$lib/components/base/button.svelte";
|
||||
import type { SelectItem } from "$lib/components/base/select.svelte";
|
||||
import Select from "$lib/components/base/select.svelte";
|
||||
@@ -11,12 +11,15 @@
|
||||
import { getFileURL } from "$lib/util/file_util";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
export let data;
|
||||
let { data } = $props();
|
||||
|
||||
let selectedCategory =
|
||||
$page.data.settings?.category || data.categories[0].id;
|
||||
let settings = $state(data.settings)
|
||||
|
||||
let bio = data.settings?.bio ?? "";
|
||||
let selectedCategory = $state(
|
||||
data.settings?.category || data.categories[0].id,
|
||||
);
|
||||
|
||||
let bio = $state(data.settings?.bio ?? "");
|
||||
|
||||
const categoryItems: SelectItem[] = data.categories.map((c: Category) => ({
|
||||
text: $_(c.name),
|
||||
@@ -43,12 +46,12 @@
|
||||
}
|
||||
|
||||
async function handleBioSave() {
|
||||
if (!data.settings) {
|
||||
if (!settings) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
data.settings.bio = bio;
|
||||
await settings_update(data.settings);
|
||||
settings.bio = bio;
|
||||
await settings_update(settings);
|
||||
} catch (e) {
|
||||
show_toast({
|
||||
type: "error",
|
||||
@@ -61,7 +64,7 @@
|
||||
|
||||
async function handleCategorySelection(value: string) {
|
||||
await settings_update({
|
||||
id: $page.data.settings!.id,
|
||||
id: settings.id,
|
||||
category: value,
|
||||
});
|
||||
}
|
||||
@@ -85,8 +88,9 @@
|
||||
alt="avatar"
|
||||
/>
|
||||
<button
|
||||
aria-label="Open file browser"
|
||||
class="absolute top-0 w-24 aspect-square opacity-0 group-hover:opacity-100 flex justify-center items-center bg-black/50 focus:bg-black/60 text-white cursor-pointer transition-opacity"
|
||||
on:click={openFileBrowser}
|
||||
onclick={openFileBrowser}
|
||||
>
|
||||
<i class="fa fa-pen"></i>
|
||||
</button>
|
||||
@@ -96,7 +100,7 @@
|
||||
id="avatarInput"
|
||||
accept="image/*"
|
||||
style="display: none;"
|
||||
on:change={handleAvatarSelection}
|
||||
onchange={handleAvatarSelection}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -109,9 +113,9 @@
|
||||
<Textarea bind:value={bio} rows={5}></Textarea>
|
||||
<div class="mt-3">
|
||||
<Button
|
||||
on:click={() => handleBioSave()}
|
||||
onclick={() => handleBioSave()}
|
||||
primary
|
||||
disabled={data.settings.bio === bio}>{$_("save")}</Button
|
||||
disabled={settings.bio === bio}>{$_("save")}</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -121,7 +125,7 @@
|
||||
<Select
|
||||
items={categoryItems}
|
||||
bind:value={selectedCategory}
|
||||
on:change={(e) => handleCategorySelection(e.detail)}
|
||||
onchange={handleCategorySelection}
|
||||
></Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user