feat(frontend): links editor
This commit is contained in:
parent
b0a286dd9f
commit
c6eba5b51a
5 changed files with 141 additions and 12 deletions
|
@ -0,0 +1,84 @@
|
|||
<script lang="ts">
|
||||
import type { RawApiError } from "$api/error";
|
||||
import IconButton from "$components/IconButton.svelte";
|
||||
import { t } from "$lib/i18n";
|
||||
import FormStatusMarker from "./FormStatusMarker.svelte";
|
||||
|
||||
type Props = {
|
||||
currentLinks: string[];
|
||||
save(links: string[]): Promise<void>;
|
||||
form: { ok: boolean; error: RawApiError | null } | null;
|
||||
};
|
||||
let { currentLinks, save, form }: Props = $props();
|
||||
|
||||
let links = $state(currentLinks);
|
||||
let newEntry = $state("");
|
||||
|
||||
const moveValue = (index: number, up: boolean) => {
|
||||
if (up && index == 0) return;
|
||||
if (!up && index == links.length - 1) return;
|
||||
|
||||
const newIndex = up ? index - 1 : index + 1;
|
||||
const temp = links[index];
|
||||
links[index] = links[newIndex];
|
||||
links[newIndex] = temp;
|
||||
links = [...links];
|
||||
};
|
||||
|
||||
const removeValue = (index: number) => {
|
||||
links.splice(index, 1);
|
||||
links = [...links];
|
||||
};
|
||||
|
||||
const addEntry = (event: Event) => {
|
||||
event.preventDefault();
|
||||
if (!newEntry) return;
|
||||
|
||||
links = [...links, newEntry];
|
||||
newEntry = "";
|
||||
};
|
||||
</script>
|
||||
|
||||
<h4>
|
||||
{$t("editor.links-header")}
|
||||
<button type="button" class="btn btn-primary" onclick={() => save(links)}>
|
||||
{$t("save-changes")}
|
||||
</button>
|
||||
</h4>
|
||||
|
||||
<FormStatusMarker {form} />
|
||||
|
||||
{#each links as _, index}
|
||||
<div class="input-group m-1">
|
||||
<IconButton
|
||||
icon="chevron-up"
|
||||
color="secondary"
|
||||
tooltip={$t("editor.move-entry-up")}
|
||||
onclick={() => moveValue(index, true)}
|
||||
/>
|
||||
<IconButton
|
||||
icon="chevron-down"
|
||||
color="secondary"
|
||||
tooltip={$t("editor.move-entry-down")}
|
||||
onclick={() => moveValue(index, false)}
|
||||
/>
|
||||
<input type="text" class="form-control" bind:value={links[index]} autocomplete="off" />
|
||||
<IconButton
|
||||
color="danger"
|
||||
icon="trash3"
|
||||
tooltip={$t("editor.remove-entry")}
|
||||
onclick={() => removeValue(index)}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<form class="input-group m-1" onsubmit={addEntry}>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
bind:value={newEntry}
|
||||
placeholder={$t("editor.new-entry")}
|
||||
autocomplete="off"
|
||||
/>
|
||||
<IconButton type="submit" color="success" icon="plus" tooltip={$t("editor.add-entry")} />
|
||||
</form>
|
|
@ -9,7 +9,7 @@
|
|||
if (raw.startsWith("https://")) out = raw.substring("https://".length);
|
||||
else if (raw.startsWith("http://")) out = raw.substring("http://".length);
|
||||
|
||||
if (raw.endsWith("/")) out = raw.substring(0, raw.length - 1);
|
||||
if (out.endsWith("/")) out = out.substring(0, out.length - 1);
|
||||
|
||||
return out;
|
||||
};
|
||||
|
|
|
@ -202,7 +202,8 @@
|
|||
"flag-search-no-flags": "No flags matched your search query.",
|
||||
"flag-search-no-account-flags": "You haven't uploaded any flags yet.",
|
||||
"flag-search-hint": "Can't find the flag you're looking for? Try using the search bar above.",
|
||||
"flag-manage-your-flags": "Manage your flags"
|
||||
"flag-manage-your-flags": "Manage your flags",
|
||||
"links-header": "Links"
|
||||
},
|
||||
"cancel": "Cancel"
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { fastRequest } from "$api";
|
||||
import type { RawApiError } from "$api/error";
|
||||
import ApiError from "$api/error";
|
||||
import LinksEditor from "$components/editor/LinksEditor.svelte";
|
||||
import ProfileFlagsEditor from "$components/editor/ProfileFlagsEditor.svelte";
|
||||
import log from "$lib/log";
|
||||
import type { PageData } from "./$types";
|
||||
|
@ -9,20 +10,41 @@
|
|||
type Props = { data: PageData };
|
||||
let { data }: Props = $props();
|
||||
|
||||
let form: { ok: boolean; error: RawApiError | null } | null = $state(null);
|
||||
let flagForm: { ok: boolean; error: RawApiError | null } | null = $state(null);
|
||||
let linksForm: { ok: boolean; error: RawApiError | null } | null = $state(null);
|
||||
|
||||
const save = async (flags: string[]) => {
|
||||
const flagSave = async (flags: string[]) => {
|
||||
try {
|
||||
await fastRequest("PATCH", `/users/@me/members/${data.member.id}`, {
|
||||
body: { flags },
|
||||
token: data.token,
|
||||
});
|
||||
form = { ok: true, error: null };
|
||||
flagForm = { ok: true, error: null };
|
||||
} catch (e) {
|
||||
log.error("Could not update profile flags for member %s:", data.member.id, e);
|
||||
if (e instanceof ApiError) form = { ok: false, error: e.obj };
|
||||
if (e instanceof ApiError) flagForm = { ok: false, error: e.obj };
|
||||
}
|
||||
};
|
||||
|
||||
const linksSave = async (links: string[]) => {
|
||||
try {
|
||||
await fastRequest("PATCH", "/users/@me", {
|
||||
body: { links },
|
||||
token: data.token,
|
||||
});
|
||||
linksForm = { ok: true, error: null };
|
||||
} catch (e) {
|
||||
log.error("Could not update profile links:", e);
|
||||
if (e instanceof ApiError) linksForm = { ok: false, error: e.obj };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<ProfileFlagsEditor profileFlags={data.member.flags} allFlags={data.flags} {save} {form} />
|
||||
<ProfileFlagsEditor
|
||||
profileFlags={data.member.flags}
|
||||
allFlags={data.flags}
|
||||
save={flagSave}
|
||||
form={flagForm}
|
||||
/>
|
||||
|
||||
<LinksEditor currentLinks={data.member.links} save={linksSave} form={linksForm} />
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { fastRequest } from "$api";
|
||||
import type { RawApiError } from "$api/error";
|
||||
import ApiError from "$api/error";
|
||||
import LinksEditor from "$components/editor/LinksEditor.svelte";
|
||||
import ProfileFlagsEditor from "$components/editor/ProfileFlagsEditor.svelte";
|
||||
import log from "$lib/log";
|
||||
import type { PageData } from "./$types";
|
||||
|
@ -9,20 +10,41 @@
|
|||
type Props = { data: PageData };
|
||||
let { data }: Props = $props();
|
||||
|
||||
let form: { ok: boolean; error: RawApiError | null } | null = $state(null);
|
||||
let flagForm: { ok: boolean; error: RawApiError | null } | null = $state(null);
|
||||
let linksForm: { ok: boolean; error: RawApiError | null } | null = $state(null);
|
||||
|
||||
const save = async (flags: string[]) => {
|
||||
const flagSave = async (flags: string[]) => {
|
||||
try {
|
||||
await fastRequest("PATCH", "/users/@me", {
|
||||
body: { flags },
|
||||
token: data.token,
|
||||
});
|
||||
form = { ok: true, error: null };
|
||||
flagForm = { ok: true, error: null };
|
||||
} catch (e) {
|
||||
log.error("Could not update profile flags:", e);
|
||||
if (e instanceof ApiError) form = { ok: false, error: e.obj };
|
||||
if (e instanceof ApiError) flagForm = { ok: false, error: e.obj };
|
||||
}
|
||||
};
|
||||
|
||||
const linksSave = async (links: string[]) => {
|
||||
try {
|
||||
await fastRequest("PATCH", "/users/@me", {
|
||||
body: { links },
|
||||
token: data.token,
|
||||
});
|
||||
linksForm = { ok: true, error: null };
|
||||
} catch (e) {
|
||||
log.error("Could not update profile links:", e);
|
||||
if (e instanceof ApiError) linksForm = { ok: false, error: e.obj };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<ProfileFlagsEditor profileFlags={data.user.flags} allFlags={data.flags} {save} {form} />
|
||||
<ProfileFlagsEditor
|
||||
profileFlags={data.user.flags}
|
||||
allFlags={data.flags}
|
||||
save={flagSave}
|
||||
form={flagForm}
|
||||
/>
|
||||
|
||||
<LinksEditor currentLinks={data.user.links} save={linksSave} form={linksForm} />
|
||||
|
|
Loading…
Reference in a new issue