feat(frontend): add links and add/delete names/pronouns to edit profile page

This commit is contained in:
Sam 2023-03-13 15:36:41 +01:00
parent 57ed81add3
commit 10adeec841
No known key found for this signature in database
GPG key ID: B4EF20DDE721CAA1
2 changed files with 167 additions and 57 deletions

View file

@ -0,0 +1,16 @@
<script lang="ts">
import { Button, Icon, Tooltip } from "sveltestrap";
export let icon: string;
export let color: "primary" | "secondary" | "success" | "danger";
export let tooltip: string;
export let active: boolean = false;
export let click: (e: MouseEvent) => void;
let button: HTMLElement;
</script>
<Tooltip target={button} placement="top">{tooltip}</Tooltip>
<Button {color} {active} on:click={click} bind:inner={button}>
<Icon name={icon} />
</Button>

View file

@ -14,6 +14,7 @@
import { Alert, Button, FormGroup, Icon, Input } from "sveltestrap"; import { Alert, Button, FormGroup, Icon, Input } from "sveltestrap";
import { encode } from "base64-arraybuffer"; import { encode } from "base64-arraybuffer";
import { apiFetchClient } from "$lib/api/fetch"; import { apiFetchClient } from "$lib/api/fetch";
import IconButton from "$lib/components/IconButton.svelte";
const MAX_AVATAR_BYTES = 1_000_000; const MAX_AVATAR_BYTES = 1_000_000;
@ -25,6 +26,7 @@
let bio: string = $userStore?.bio || ""; let bio: string = $userStore?.bio || "";
let display_name: string = $userStore?.display_name || ""; let display_name: string = $userStore?.display_name || "";
let links: string[] = $userStore ? window.structuredClone($userStore.links) : [];
let names: FieldEntry[] = $userStore ? window.structuredClone($userStore.names) : []; let names: FieldEntry[] = $userStore ? window.structuredClone($userStore.names) : [];
let pronouns: Pronoun[] = $userStore ? window.structuredClone($userStore.pronouns) : []; let pronouns: Pronoun[] = $userStore ? window.structuredClone($userStore.pronouns) : [];
let fields: Field[] = $userStore ? window.structuredClone($userStore.fields) : []; let fields: Field[] = $userStore ? window.structuredClone($userStore.fields) : [];
@ -32,10 +34,15 @@
let avatar: string | null; let avatar: string | null;
let avatar_files: FileList | null; let avatar_files: FileList | null;
let newName = "";
let newPronouns = "";
let newPronounsDisplay = "";
let newLink = "";
let modified = false; let modified = false;
$: redirectIfNoAuth($userStore); $: redirectIfNoAuth($userStore);
$: modified = isModified(bio, display_name, names, pronouns, fields); $: modified = isModified(bio, display_name, links, names, pronouns, fields, avatar);
$: getAvatar(avatar_files).then((b64) => (avatar = b64)); $: getAvatar(avatar_files).then((b64) => (avatar = b64));
const redirectIfNoAuth = (user: MeUser | null) => { const redirectIfNoAuth = (user: MeUser | null) => {
@ -47,14 +54,17 @@
const isModified = ( const isModified = (
bio: string, bio: string,
display_name: string, display_name: string,
links: string[],
names: FieldEntry[], names: FieldEntry[],
pronouns: Pronoun[], pronouns: Pronoun[],
fields: Field[], fields: Field[],
avatar: string | null,
) => { ) => {
if (!$userStore) return false; if (!$userStore) return false;
if (bio !== $userStore.bio) return true; if (bio !== $userStore.bio) return true;
if (display_name !== $userStore.display_name) return true; if (display_name !== $userStore.display_name) return true;
if (!linksEqual(links, $userStore.links)) return true;
if (!fieldsEqual(fields, $userStore.fields)) return true; if (!fieldsEqual(fields, $userStore.fields)) return true;
if (!namesEqual(names, $userStore.names)) return true; if (!namesEqual(names, $userStore.names)) return true;
if (!pronounsEqual(pronouns, $userStore.pronouns)) return true; if (!pronounsEqual(pronouns, $userStore.pronouns)) return true;
@ -92,6 +102,11 @@
return true; return true;
}; };
const linksEqual = (arr1: string[], arr2: string[]) => {
if (arr1.length !== arr2.length) return false;
return arr1.every((_, i) => arr1[i] === arr2[i]);
};
const getAvatar = async (list: FileList | null) => { const getAvatar = async (list: FileList | null) => {
if (!list || list.length === 0) return null; if (!list || list.length === 0) return null;
if (list[0].size > MAX_AVATAR_BYTES) return null; if (list[0].size > MAX_AVATAR_BYTES) return null;
@ -140,12 +155,53 @@
pronouns[newIndex] = temp; pronouns[newIndex] = temp;
}; };
const addName = () => {
names = [...names, { value: newName, status: WordStatus.Okay }];
newName = "";
};
const addPronouns = () => {
pronouns = [
...pronouns,
{ pronouns: newPronouns, display_text: newPronounsDisplay || null, status: WordStatus.Okay },
];
newPronouns = "";
newPronounsDisplay = "";
};
const addLink = () => {
links = [...links, newLink];
newLink = "";
};
const removeName = (index: number) => {
if (names.length === 1) names = [];
else if (index === 0) names = names.slice(1);
else if (index === names.length - 1) names = names.slice(0, names.length - 1);
else names = [...names.slice(0, index - 1), ...names.slice(0, index + 1)];
};
const removePronoun = (index: number) => {
if (pronouns.length === 1) pronouns = [];
else if (index === 0) pronouns = pronouns.slice(1);
else if (index === pronouns.length - 1) pronouns = pronouns.slice(0, pronouns.length - 1);
else pronouns = [...pronouns.slice(0, index - 1), ...pronouns.slice(0, index + 1)];
};
const removeLink = (index: number) => {
if (links.length === 1) links = [];
else if (index === 0) links = links.slice(1);
else if (index === links.length - 1) links = links.slice(0, links.length - 1);
else links = [...links.slice(0, index - 1), ...links.slice(0, index + 1)];
};
const updateUser = async () => { const updateUser = async () => {
try { try {
const resp = await apiFetchClient<MeUser>("/users/@me", "PATCH", { const resp = await apiFetchClient<MeUser>("/users/@me", "PATCH", {
display_name, display_name,
avatar, avatar,
bio, bio,
links,
names, names,
pronouns, pronouns,
fields, fields,
@ -227,46 +283,71 @@
<Icon name="chevron-down" /> <Icon name="chevron-down" />
</Button> </Button>
<input type="text" class="form-control" bind:value={names[index].value} /> <input type="text" class="form-control" bind:value={names[index].value} />
<Button <IconButton
color="secondary" color="secondary"
on:click={() => (names[index].status = WordStatus.Favourite)} icon="heart-fill"
tooltip="Favourite"
click={() => (names[index].status = WordStatus.Favourite)}
active={names[index].status === WordStatus.Favourite} active={names[index].status === WordStatus.Favourite}
> />
<Icon name="heart-fill" /> <IconButton
</Button>
<Button
color="secondary" color="secondary"
on:click={() => (names[index].status = WordStatus.Okay)} icon="hand-thumbs-up"
tooltip="Okay"
click={() => (names[index].status = WordStatus.Okay)}
active={names[index].status === WordStatus.Okay} active={names[index].status === WordStatus.Okay}
> />
<Icon name="hand-thumbs-up" /> <IconButton
</Button>
<Button
color="secondary" color="secondary"
on:click={() => (names[index].status = WordStatus.Jokingly)} icon="emoji-laughing"
tooltip="Jokingly"
click={() => (names[index].status = WordStatus.Jokingly)}
active={names[index].status === WordStatus.Jokingly} active={names[index].status === WordStatus.Jokingly}
> />
<Icon name="emoji-laughing" /> <IconButton
</Button>
<Button
color="secondary" color="secondary"
on:click={() => (names[index].status = WordStatus.FriendsOnly)} icon="people"
tooltip="Friends only"
click={() => (names[index].status = WordStatus.FriendsOnly)}
active={names[index].status === WordStatus.FriendsOnly} active={names[index].status === WordStatus.FriendsOnly}
> />
<Icon name="people" /> <IconButton
</Button>
<Button
color="secondary" color="secondary"
on:click={() => (names[index].status = WordStatus.Avoid)} icon="hand-thumbs-down"
tooltip="Avoid"
click={() => (names[index].status = WordStatus.Avoid)}
active={names[index].status === WordStatus.Avoid} active={names[index].status === WordStatus.Avoid}
> />
<Icon name="hand-thumbs-down" /> <IconButton
</Button> color="danger"
<Button color="danger"> icon="trash3"
<Icon name="trash3" /> tooltip="Remove name"
</Button> click={() => removeName(index)}
/>
</div> </div>
{/each} {/each}
<div class="input-group m-1">
<input type="text" class="form-control" bind:value={newName} />
<IconButton color="success" icon="plus" tooltip="Add name" click={() => addName()} />
</div>
</div>
<div class="col-md">
<h4>Links</h4>
{#each links as _, index}
<div class="input-group m-1">
<input type="text" class="form-control" bind:value={links[index]} />
<IconButton
color="danger"
icon="trash3"
tooltip="Remove link"
click={() => removeLink(index)}
/>
</div>
{/each}
<div class="input-group m-1">
<input type="text" class="form-control" bind:value={newLink} />
<IconButton color="success" icon="plus" tooltip="Add link" click={() => addLink()} />
</div>
</div> </div>
</div> </div>
<div class="row m-1"> <div class="row m-1">
@ -282,46 +363,59 @@
</Button> </Button>
<input type="text" class="form-control" bind:value={pronouns[index].pronouns} /> <input type="text" class="form-control" bind:value={pronouns[index].pronouns} />
<input type="text" class="form-control" bind:value={pronouns[index].display_text} /> <input type="text" class="form-control" bind:value={pronouns[index].display_text} />
<Button <IconButton
color="secondary" color="secondary"
on:click={() => (pronouns[index].status = WordStatus.Favourite)} icon="heart-fill"
tooltip="Favourite"
click={() => (pronouns[index].status = WordStatus.Favourite)}
active={pronouns[index].status === WordStatus.Favourite} active={pronouns[index].status === WordStatus.Favourite}
> />
<Icon name="heart-fill" /> <IconButton
</Button>
<Button
color="secondary" color="secondary"
on:click={() => (pronouns[index].status = WordStatus.Okay)} icon="hand-thumbs-up"
tooltip="Okay"
click={() => (pronouns[index].status = WordStatus.Okay)}
active={pronouns[index].status === WordStatus.Okay} active={pronouns[index].status === WordStatus.Okay}
> />
<Icon name="hand-thumbs-up" /> <IconButton
</Button>
<Button
color="secondary" color="secondary"
on:click={() => (pronouns[index].status = WordStatus.Jokingly)} icon="emoji-laughing"
tooltip="Jokingly"
click={() => (pronouns[index].status = WordStatus.Jokingly)}
active={pronouns[index].status === WordStatus.Jokingly} active={pronouns[index].status === WordStatus.Jokingly}
> />
<Icon name="emoji-laughing" /> <IconButton
</Button>
<Button
color="secondary" color="secondary"
on:click={() => (pronouns[index].status = WordStatus.FriendsOnly)} icon="people"
tooltip="Friends only"
click={() => (pronouns[index].status = WordStatus.FriendsOnly)}
active={pronouns[index].status === WordStatus.FriendsOnly} active={pronouns[index].status === WordStatus.FriendsOnly}
> />
<Icon name="people" /> <IconButton
</Button>
<Button
color="secondary" color="secondary"
on:click={() => (pronouns[index].status = WordStatus.Avoid)} icon="hand-thumbs-down"
tooltip="Avoid"
click={() => (pronouns[index].status = WordStatus.Avoid)}
active={pronouns[index].status === WordStatus.Avoid} active={pronouns[index].status === WordStatus.Avoid}
> />
<Icon name="hand-thumbs-down" /> <IconButton
</Button> color="danger"
<Button color="danger"> icon="trash3"
<Icon name="trash3" /> tooltip="Remove pronouns"
</Button> click={() => removePronoun(index)}
/>
</div> </div>
{/each} {/each}
<div class="input-group m-1">
<input type="text" class="form-control" bind:value={newPronouns} />
<input type="text" class="form-control" bind:value={newPronounsDisplay} />
<IconButton
color="success"
icon="plus"
tooltip="Add pronouns"
click={() => addPronouns()}
/>
</div>
</div> </div>
</div> </div>
</div> </div>