full settings overhaul
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
export let name: string = "";
|
||||
export let value: string | number = "";
|
||||
export let placeholder: string = "";
|
||||
export let disabled: boolean = false;
|
||||
export let label: string = "";
|
||||
export let error: string = "";
|
||||
export let icon: string = "";
|
||||
@@ -28,6 +29,8 @@
|
||||
class="bg-input-background border border-input-border rounded-md p-3 transition-colors focus:border-input-border-focus focus:outline-none focus:ring-0 w-full {extraClasses}"
|
||||
class:border-red-400={error.length > 0}
|
||||
class:bg-input-background-error={error.length > 0}
|
||||
class:text-gray-500={disabled}
|
||||
{disabled}
|
||||
{autocomplete}
|
||||
use:typeAction
|
||||
bind:value
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
];
|
||||
|
||||
const dropdownItems = [
|
||||
{ text: $_("profile"), value: "profile", icon: "user" },
|
||||
{ text: $_("settings"), value: "settings", icon: "cog" },
|
||||
{ text: $_("logout"), value: "logout", icon: "right-from-bracket" },
|
||||
];
|
||||
|
||||
@@ -79,8 +79,8 @@
|
||||
if (item.value == "logout") {
|
||||
logout();
|
||||
window.location.href = "/";
|
||||
} else if (item.value == "profile") {
|
||||
goto("/profile");
|
||||
} else if (item.value == "settings") {
|
||||
goto("/settings/account");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -203,11 +203,11 @@
|
||||
let:toggleMenu={openDropdown}
|
||||
>
|
||||
<button
|
||||
class="rounded-full bg-white text-black hover:bg-gray-200 focus:ring-4 ring-gray-100/50 transition-colors h-10 w-10"
|
||||
class="rounded-full bg-white text-black hover:bg-gray-200 focus:ring-4 ring-gray-100/50 transition-colors max-h-11 aspect-square"
|
||||
on:click={openDropdown}
|
||||
>
|
||||
<img
|
||||
class="rounded-full"
|
||||
class="rounded-full w-full h-full"
|
||||
src={getFileURL($currentUser, $currentUser.avatar) ||
|
||||
`https://api.dicebear.com/7.x/initials/svg?seed=${$currentUser.username}&backgroundType=gradientLinear`}
|
||||
alt="avatar"
|
||||
|
||||
60
web/src/lib/components/profile/email_modal.svelte
Normal file
60
web/src/lib/components/profile/email_modal.svelte
Normal file
@@ -0,0 +1,60 @@
|
||||
<script lang="ts">
|
||||
import Modal from "$lib/components/base/modal.svelte";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
import TextField from "../base/text_field.svelte";
|
||||
import { createForm } from "$lib/vendor/svelte-form-lib";
|
||||
import { object, string } from "yup";
|
||||
|
||||
let _openModal: (() => void) | undefined = undefined;
|
||||
export let closeModal: (() => void) | undefined = undefined;
|
||||
export function openModal() {
|
||||
$form.email = email
|
||||
$errors.email = "";
|
||||
_openModal!();
|
||||
}
|
||||
|
||||
export let email = "";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
const { form, errors, handleChange, handleSubmit } = createForm<{
|
||||
email: string;
|
||||
}>({
|
||||
initialValues: { email: email },
|
||||
validationSchema: object({
|
||||
email: string()
|
||||
.email($_("not-a-valid-email-address"))
|
||||
.required($_("required")),
|
||||
}),
|
||||
onSubmit: async (_) => {
|
||||
dispatch("save", $form.email);
|
||||
closeModal!();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
id="email-modal"
|
||||
size="max-w-xl"
|
||||
title={$_("change-email")}
|
||||
bind:openModal={_openModal}
|
||||
bind:closeModal
|
||||
>
|
||||
<form id="email-form" slot="content" on:submit={handleSubmit}>
|
||||
<TextField
|
||||
name="email"
|
||||
bind:value={$form.email}
|
||||
error={$errors.email}
|
||||
on:change={handleChange}
|
||||
></TextField>
|
||||
</form>
|
||||
<div slot="footer" class="flex items-center gap-4">
|
||||
<button class="btn-secondary" on:click={closeModal}
|
||||
>{$_("cancel")}</button
|
||||
>
|
||||
<button class="btn-primary" type="submit" form="email-form" name="save"
|
||||
>{$_("save")}</button
|
||||
>
|
||||
</div></Modal
|
||||
>
|
||||
84
web/src/lib/components/profile/password_modal.svelte
Normal file
84
web/src/lib/components/profile/password_modal.svelte
Normal file
@@ -0,0 +1,84 @@
|
||||
<script lang="ts">
|
||||
import Modal from "$lib/components/base/modal.svelte";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
import TextField from "../base/text_field.svelte";
|
||||
import { createForm } from "$lib/vendor/svelte-form-lib";
|
||||
import { object, string } from "yup";
|
||||
|
||||
let _openModal: (() => void) | undefined = undefined;
|
||||
export let closeModal: (() => void) | undefined = undefined;
|
||||
export function openModal() {
|
||||
$form.password = ""
|
||||
$errors.password = "";
|
||||
$form.oldPassword = ""
|
||||
$errors.oldPassword = "";
|
||||
_openModal!();
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
const { form, errors, handleChange, handleSubmit } = createForm<{
|
||||
oldPassword: string;
|
||||
password: string;
|
||||
}>({
|
||||
initialValues: { oldPassword: "", password: "" },
|
||||
validationSchema: object({
|
||||
oldPassword: string()
|
||||
.min(
|
||||
8,
|
||||
$_("must-be-at-least-n-characters-long", {
|
||||
values: { n: 8 },
|
||||
}),
|
||||
)
|
||||
.required($_("required")),
|
||||
password: string()
|
||||
.min(
|
||||
8,
|
||||
$_("must-be-at-least-n-characters-long", {
|
||||
values: { n: 8 },
|
||||
}),
|
||||
)
|
||||
.required($_("required")),
|
||||
}),
|
||||
onSubmit: async (_) => {
|
||||
dispatch("save", {oldPassword: $form.oldPassword, password: $form.password, passwordConfirm: $form.password});
|
||||
closeModal!();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
id="password-modal"
|
||||
size="max-w-xl"
|
||||
title={$_("change-password")}
|
||||
bind:openModal={_openModal}
|
||||
bind:closeModal
|
||||
>
|
||||
<form id="password-form" slot="content" on:submit={handleSubmit}>
|
||||
<TextField
|
||||
name="oldPassword"
|
||||
label={$_("current-password")}
|
||||
type="password"
|
||||
bind:value={$form.oldPassword}
|
||||
error={$errors.oldPassword}
|
||||
on:change={handleChange}
|
||||
></TextField>
|
||||
<TextField
|
||||
name="password"
|
||||
label={$_("new-password")}
|
||||
type="password"
|
||||
bind:value={$form.password}
|
||||
error={$errors.password}
|
||||
on:change={handleChange}
|
||||
></TextField>
|
||||
</form>
|
||||
<div slot="footer" class="flex items-center gap-4">
|
||||
<button class="btn-secondary" on:click={closeModal}
|
||||
>{$_("cancel")}</button
|
||||
>
|
||||
<button class="btn-primary" type="submit" form="password-form" name="save"
|
||||
>{$_("save")}</button
|
||||
>
|
||||
</div></Modal
|
||||
>
|
||||
41
web/src/lib/components/profile/settings.svelte
Normal file
41
web/src/lib/components/profile/settings.svelte
Normal file
@@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import type { DropdownItem } from "../base/dropdown.svelte";
|
||||
|
||||
export let selected: number;
|
||||
|
||||
const items: DropdownItem[] = [
|
||||
{ text: "My Account", value: "/settings/account" },
|
||||
{ text: "Display", value: "/settings/display" },
|
||||
{ text: "Import/Export", value: "/settings/export" },
|
||||
{ text: "Help", value: "https://wanderer.to" },
|
||||
];
|
||||
|
||||
function handleItemClick(item: DropdownItem) {
|
||||
if(item.text != "Help") {
|
||||
goto(item.value);
|
||||
}else {
|
||||
window.open(item.value, '_blank');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="grid grid-cols-1 md:grid-cols-[256px_1fr] max-w-4xl mx-4 md:mx-auto gap-x-12 items-start"
|
||||
>
|
||||
<menu class="p-4 border border-input-border rounded-xl mb-4">
|
||||
{#each items as item, i}
|
||||
<li
|
||||
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 == selected}
|
||||
role="presentation"
|
||||
on:mousedown|stopPropagation={() => handleItemClick(item)}
|
||||
>
|
||||
{item.text}
|
||||
</li>
|
||||
{/each}
|
||||
</menu>
|
||||
<div class="settings-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/stores";
|
||||
import { Settings } from "$lib/models/settings";
|
||||
import type { Trail } from "$lib/models/trail";
|
||||
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
|
||||
import "$lib/vendor/leaflet-elevation/src/index.css";
|
||||
@@ -93,33 +94,39 @@
|
||||
);
|
||||
|
||||
const topoLayer = L.tileLayer(
|
||||
"https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png ",
|
||||
"https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png",
|
||||
{
|
||||
attribution: "© OpenStreetMap contributors",
|
||||
},
|
||||
);
|
||||
|
||||
const baseMaps = {
|
||||
const baseMaps: Record<string, L.TileLayer> = {
|
||||
OpenStreetMaps: baseLayer,
|
||||
OpenTopoMaps: topoLayer,
|
||||
};
|
||||
...($page.data.settings as Settings).tilesets?.reduce<
|
||||
Record<string, string>
|
||||
>((t, current) => {
|
||||
t[current.name] = L.tileLayer(current.url);
|
||||
return t;
|
||||
}, {}),
|
||||
};
|
||||
|
||||
L.control.layers(baseMaps).addTo(map);
|
||||
|
||||
switch (localStorage.getItem("layer")) {
|
||||
case "OpenTopoMaps":
|
||||
topoLayer.addTo(map);
|
||||
break;
|
||||
default:
|
||||
baseLayer.addTo(map);
|
||||
break;
|
||||
const layerPreference = localStorage.getItem("layer")
|
||||
|
||||
if (layerPreference && Object.keys(baseMaps).includes(layerPreference)) {
|
||||
baseMaps[layerPreference].addTo(map!)
|
||||
}else {
|
||||
baseLayer.addTo(map)
|
||||
}
|
||||
|
||||
map!.on("baselayerchange", function (e) {
|
||||
localStorage.setItem("layer", e.name);
|
||||
});
|
||||
|
||||
const localMetric = localStorage.getItem("gradient") as any ?? "altitude";
|
||||
const localMetric =
|
||||
(localStorage.getItem("gradient") as any) ?? "altitude";
|
||||
selectedMetric = localMetric === "false" ? false : localMetric;
|
||||
|
||||
const default_elevation_options = {
|
||||
|
||||
@@ -161,7 +161,7 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="trail-info-panel mx-auto border border-input-border rounded-3xl h-full" style="max-width: min(100%, 64rem)"
|
||||
class="trail-info-panel mx-auto border border-input-border rounded-3xl h-full" style="max-width: min(100%, 64rem);"
|
||||
>
|
||||
<div class="trail-info-panel-header">
|
||||
<section class="relative h-80">
|
||||
|
||||
Reference in New Issue
Block a user