255 lines
8.9 KiB
Svelte
255 lines
8.9 KiB
Svelte
<script lang="ts">
|
|
import { page } from "$app/stores";
|
|
import RadioGroup, {
|
|
type RadioItem,
|
|
} from "$lib/components/base/radio_group.svelte";
|
|
import Search, {
|
|
type SearchItem,
|
|
} from "$lib/components/base/search.svelte";
|
|
import Select, {
|
|
type SelectItem,
|
|
} from "$lib/components/base/select.svelte";
|
|
|
|
import TextField from "$lib/components/base/text_field.svelte";
|
|
import Settings from "$lib/components/profile/settings.svelte";
|
|
import type { Category } from "$lib/models/category.js";
|
|
import { settings_update } from "$lib/stores/settings_store";
|
|
import { currentUser } from "$lib/stores/user_store";
|
|
import { country_codes } from "$lib/util/country_code_util";
|
|
import { onMount } from "svelte";
|
|
import { _ } from "svelte-i18n";
|
|
|
|
export let data;
|
|
|
|
$: settings = $page.data.settings;
|
|
|
|
const mapFocus: SelectItem[] = [
|
|
{ text: $_("trail", { values: { n: 2 } }), value: "trails" },
|
|
{ text: $_("location"), value: "location" },
|
|
];
|
|
|
|
const categoryItems: SelectItem[] = data.categories.map((c: Category) => ({
|
|
text: $_(c.name),
|
|
value: c.id,
|
|
}));
|
|
|
|
const units: RadioItem[] = [
|
|
{ text: $_("metric"), value: "metric" },
|
|
{ text: $_("imperial"), value: "imperial" },
|
|
];
|
|
|
|
let selectedLanguage = "en";
|
|
let selectedMapFocus = "trails";
|
|
|
|
let selectedCategory =
|
|
$page.data.settings?.category || data.categories[0].id;
|
|
|
|
let searchDropdownItems: SearchItem[] = [];
|
|
let citySearchQuery: string = "";
|
|
|
|
let customTilesetName: string = "";
|
|
let customTilesetURL: string = "";
|
|
let terrainURL: string = "";
|
|
let hillshadingURL: string = "";
|
|
|
|
onMount(() => {
|
|
citySearchQuery = settings?.location?.name ?? "";
|
|
selectedLanguage = settings?.language || "en";
|
|
selectedMapFocus = settings?.mapFocus ?? "trails";
|
|
|
|
terrainURL = settings?.terrain?.terrain ?? "";
|
|
hillshadingURL = settings?.terrain?.hillshading ?? "";
|
|
});
|
|
|
|
async function searchCities(q: string) {
|
|
const r = await fetch("/api/v1/search/cities500", {
|
|
method: "POST",
|
|
body: JSON.stringify({ q: q, options: { limit: 5 } }),
|
|
});
|
|
const result = await r.json();
|
|
searchDropdownItems = result.hits.map((h: Record<string, any>) => ({
|
|
text: h.name,
|
|
description: `${h.division ? `${h.division} | ` : ""}${
|
|
country_codes[h["country code"] as keyof typeof country_codes]
|
|
}`,
|
|
value: h,
|
|
icon: "city",
|
|
}));
|
|
}
|
|
|
|
async function handleSearchClick(item: SearchItem) {
|
|
citySearchQuery = item.text;
|
|
await settings_update({
|
|
id: settings?.id!,
|
|
location: {
|
|
name: item.value.name,
|
|
lat: item.value.lat,
|
|
lon: item.value.lon,
|
|
},
|
|
});
|
|
}
|
|
|
|
async function handleUnitSelection(e: RadioItem) {
|
|
await settings_update({
|
|
id: settings!.id,
|
|
unit: e.value as "imperial" | "metric",
|
|
});
|
|
}
|
|
|
|
async function handleMapFocusSelection(value: "trails" | "location") {
|
|
await settings_update({
|
|
id: settings!.id,
|
|
mapFocus: value,
|
|
});
|
|
}
|
|
|
|
async function handleCategorySelection(value: string) {
|
|
await settings_update({
|
|
id: settings!.id,
|
|
category: value,
|
|
});
|
|
}
|
|
|
|
async function handleTilesetAdd() {
|
|
if (!customTilesetName || !customTilesetURL) {
|
|
return;
|
|
}
|
|
await settings_update({
|
|
id: settings!.id,
|
|
tilesets: [
|
|
...(settings.tilesets ?? []),
|
|
{ name: customTilesetName, url: customTilesetURL },
|
|
],
|
|
});
|
|
customTilesetName = "";
|
|
customTilesetURL = "";
|
|
}
|
|
|
|
async function handleTilesetDelete(index: number) {
|
|
settings.tilesets.splice(index, 1);
|
|
await settings_update({
|
|
id: settings!.id,
|
|
tilesets: settings.tilesets,
|
|
});
|
|
}
|
|
|
|
async function handleTerrainAdd() {
|
|
await settings_update({
|
|
id: settings!.id,
|
|
terrain: { terrain: terrainURL, hillshading: hillshadingURL },
|
|
});
|
|
}
|
|
|
|
$: terrainSaveEnabled =
|
|
terrainURL !== settings?.terrain?.terrain ||
|
|
hillshadingURL !== settings?.terrain?.hillshading;
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<title>{$_("settings")} | wanderer</title>
|
|
</svelte:head>
|
|
<main>
|
|
{#if $currentUser}
|
|
<Settings selected={1}>
|
|
<div class="space-y-6">
|
|
<h3 class="text-2xl font-semibold">{$_("units")}</h3>
|
|
<RadioGroup
|
|
name="unit"
|
|
items={units}
|
|
selected={settings?.unit == "metric" ? 0 : 1}
|
|
on:change={(e) => handleUnitSelection(e.detail)}
|
|
></RadioGroup>
|
|
<h3 class="text-2xl font-semibold">{$_("focus-map-on")}</h3>
|
|
|
|
<Select
|
|
items={mapFocus}
|
|
bind:value={selectedMapFocus}
|
|
on:change={(e) => handleMapFocusSelection(e.detail)}
|
|
></Select>
|
|
{#if selectedMapFocus == "location"}
|
|
<div class="mt-3">
|
|
<Search
|
|
items={searchDropdownItems}
|
|
placeholder="{$_('search-cities')}..."
|
|
clearAfterSelect={false}
|
|
bind:value={citySearchQuery}
|
|
on:update={(e) => searchCities(e.detail)}
|
|
on:click={(e) => handleSearchClick(e.detail)}
|
|
></Search>
|
|
</div>
|
|
{/if}
|
|
<h3 class="text-2xl font-semibold">{$_("default-category")}</h3>
|
|
<Select
|
|
items={categoryItems}
|
|
bind:value={selectedCategory}
|
|
on:change={(e) => handleCategorySelection(e.detail)}
|
|
></Select>
|
|
<h3 class="text-2xl font-semibold">{$_("tilesets")}</h3>
|
|
|
|
{#each settings.tilesets ?? [] as tileset, i}
|
|
<div
|
|
class="flex items-center justify-between px-4 py-2 border border-input-border rounded-xl"
|
|
>
|
|
<div>
|
|
<p class="font-medium">{tileset.name}</p>
|
|
<p class=" text-sm">{tileset.url}</p>
|
|
</div>
|
|
<button
|
|
class="btn-icon"
|
|
on:click={() => handleTilesetDelete(i)}
|
|
><i class="fa fa-trash text-red-500"></i></button
|
|
>
|
|
</div>
|
|
{/each}
|
|
|
|
<div class="flex gap-4 items-center">
|
|
<TextField
|
|
label={$_("name")}
|
|
bind:value={customTilesetName}
|
|
placeholder={$_("name")}
|
|
></TextField>
|
|
<div class="flex items-center basis-full gap-2">
|
|
<div class="flex-grow">
|
|
<TextField
|
|
label="URL"
|
|
bind:value={customTilesetURL}
|
|
placeholder="https://.../style.json"
|
|
></TextField>
|
|
</div>
|
|
<button
|
|
class="btn-icon mt-6"
|
|
on:click={handleTilesetAdd}
|
|
><i class="fa fa-plus"></i></button
|
|
>
|
|
</div>
|
|
</div>
|
|
<h3 class="text-2xl font-semibold">{$_("Terrain")}</h3>
|
|
<div class="flex items-center gap-2">
|
|
<div class="basis-full">
|
|
<TextField
|
|
label="Terrain URL"
|
|
bind:value={terrainURL}
|
|
placeholder="https://.../tiles.json"
|
|
></TextField>
|
|
</div>
|
|
<div class="basis-full">
|
|
<TextField
|
|
label="Hillshading URL"
|
|
bind:value={hillshadingURL}
|
|
placeholder="https://.../tiles.json"
|
|
></TextField>
|
|
</div>
|
|
<button
|
|
disabled={!terrainSaveEnabled}
|
|
class="btn-icon mt-6"
|
|
class:hover:!bg-background={!terrainSaveEnabled}
|
|
on:click={handleTerrainAdd}
|
|
class:text-gray-500={!terrainSaveEnabled}
|
|
><i class="fa fa-save"></i></button
|
|
>
|
|
</div>
|
|
</div>
|
|
</Settings>
|
|
{/if}
|
|
</main>
|