feat: add flags to edit member page

This commit is contained in:
Sam 2023-05-29 02:56:31 +02:00
parent 4ebc5d5003
commit 8f1d1fc87c
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
2 changed files with 113 additions and 1 deletions

View file

@ -8,6 +8,7 @@
type FieldEntry, type FieldEntry,
type Member, type Member,
type Pronoun, type Pronoun,
type PrideFlag,
} from "$lib/api/entities"; } from "$lib/api/entities";
import FallbackImage from "$lib/components/FallbackImage.svelte"; import FallbackImage from "$lib/components/FallbackImage.svelte";
import { import {
@ -40,6 +41,7 @@
import { memberNameRegex } from "$lib/api/regex"; import { memberNameRegex } from "$lib/api/regex";
import { charCount, renderMarkdown } from "$lib/utils"; import { charCount, renderMarkdown } from "$lib/utils";
import MarkdownHelp from "../../MarkdownHelp.svelte"; import MarkdownHelp from "../../MarkdownHelp.svelte";
import FlagButton from "../../FlagButton.svelte";
const MAX_AVATAR_BYTES = 1_000_000; const MAX_AVATAR_BYTES = 1_000_000;
@ -59,6 +61,7 @@
let names: FieldEntry[] = window.structuredClone(data.member.names); let names: FieldEntry[] = window.structuredClone(data.member.names);
let pronouns: Pronoun[] = window.structuredClone(data.member.pronouns); let pronouns: Pronoun[] = window.structuredClone(data.member.pronouns);
let fields: Field[] = window.structuredClone(data.member.fields); let fields: Field[] = window.structuredClone(data.member.fields);
let flags: PrideFlag[] = window.structuredClone(data.member.flags);
let unlisted: boolean = data.member.unlisted || false; let unlisted: boolean = data.member.unlisted || false;
let memberNameValid = true; let memberNameValid = true;
@ -71,6 +74,18 @@
let newPronouns = ""; let newPronouns = "";
let newLink = ""; let newLink = "";
let flagSearch = "";
let filteredFlags: PrideFlag[];
$: filteredFlags = filterFlags(flagSearch, data.flags);
const filterFlags = (search: string, flags: PrideFlag[]) => {
return (
search
? flags.filter((flag) => flag.name.toLocaleLowerCase().includes(search.toLocaleLowerCase()))
: flags
).slice(0, 25);
};
let modified = false; let modified = false;
$: modified = isModified( $: modified = isModified(
@ -82,6 +97,7 @@
names, names,
pronouns, pronouns,
fields, fields,
flags,
avatar, avatar,
unlisted, unlisted,
); );
@ -96,6 +112,7 @@
names: FieldEntry[], names: FieldEntry[],
pronouns: Pronoun[], pronouns: Pronoun[],
fields: Field[], fields: Field[],
flags: PrideFlag[],
avatar: string | null, avatar: string | null,
unlisted: boolean, unlisted: boolean,
) => { ) => {
@ -104,6 +121,7 @@
if (display_name !== member.display_name) return true; if (display_name !== member.display_name) return true;
if (!linksEqual(links, member.links)) return true; if (!linksEqual(links, member.links)) return true;
if (!fieldsEqual(fields, member.fields)) return true; if (!fieldsEqual(fields, member.fields)) return true;
if (!flagsEqual(flags, member.flags)) return true;
if (!namesEqual(names, member.names)) return true; if (!namesEqual(names, member.names)) return true;
if (!pronounsEqual(pronouns, member.pronouns)) return true; if (!pronounsEqual(pronouns, member.pronouns)) return true;
if (avatar !== null) return true; if (avatar !== null) return true;
@ -147,6 +165,11 @@
return arr1.every((_, i) => arr1[i] === arr2[i]); return arr1.every((_, i) => arr1[i] === arr2[i]);
}; };
const flagsEqual = (arr1: PrideFlag[], arr2: PrideFlag[]) => {
if (arr1.length !== arr2.length) return false;
return arr1.every((_, i) => arr1[i].id === arr2[i].id);
};
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) { if (list[0].size > MAX_AVATAR_BYTES) {
@ -211,6 +234,26 @@
links[newIndex] = temp; links[newIndex] = temp;
}; };
const moveFlag = (index: number, up: boolean) => {
if (up && index == 0) return;
if (!up && index == flags.length - 1) return;
const newIndex = up ? index - 1 : index + 1;
const temp = flags[index];
flags[index] = flags[newIndex];
flags[newIndex] = temp;
};
const addFlag = (flag: PrideFlag) => {
flags = [...flags, flag];
};
const removeFlag = (index: number) => {
flags.splice(index, 1);
flags = [...flags];
};
const addName = (event: Event) => { const addName = (event: Event) => {
event.preventDefault(); event.preventDefault();
@ -281,6 +324,7 @@
names, names,
pronouns, pronouns,
fields, fields,
flags: flags.map((flag) => flag.id),
unlisted, unlisted,
}); });
@ -541,6 +585,72 @@
</Button> </Button>
</div> </div>
</TabPane> </TabPane>
<TabPane tabId="flags" tab="Flags">
<div class="mt-3">
{#each flags as _, index}
<ButtonGroup class="m-1">
<IconButton
icon="chevron-left"
color="secondary"
tooltip="Move flag to the left"
click={() => moveFlag(index, true)}
/>
<IconButton
icon="chevron-right"
color="secondary"
tooltip="Move flag to the right"
click={() => moveFlag(index, false)}
/>
<FlagButton
flag={flags[index]}
tooltip="Remove this flag from your profile"
on:click={() => removeFlag(index)}
/>
</ButtonGroup>
{/each}
</div>
<hr />
<div class="row">
<div class="col-md">
<Input
placeholder="Filter flags"
bind:value={flagSearch}
disabled={data.flags.length === 0}
/>
<div class="p-2">
{#each filteredFlags as flag (flag.id)}
<FlagButton
{flag}
tooltip="Add this flag to your profile"
on:click={() => addFlag(flag)}
/>
{:else}
{#if data.flags.length === 0}
You haven't uploaded any flags yet.
{:else}
There are no flags matching your search <strong>{flagSearch}</strong>.
{/if}
{/each}
</div>
</div>
<div class="col-md">
<Alert color="secondary" fade={false}>
{#if data.flags.length === 0}
<p><strong>Why can't I see any flags?</strong></p>
<p>
There are thousands of pride flags, and it would be impossible to bundle all of them
by default. Many labels also have multiple different flags that are favoured by
different people. Because of this, there are no flags available by default--instead,
you can upload flags in your <a href="/settings/flags">settings</a>. Your main profile
and your member profiles can all have different flags.
</p>
{:else}
To upload and delete flags, go to your <a href="/settings/flags">settings</a>.
{/if}
</Alert>
</div>
</div>
</TabPane>
<TabPane tabId="links" tab="Links"> <TabPane tabId="links" tab="Links">
<div class="mt-3"> <div class="mt-3">
{#each links as _, index} {#each links as _, index}

View file

@ -1,4 +1,4 @@
import type { MeUser, APIError, Member, PronounsJson } from "$lib/api/entities"; import type { PrideFlag, MeUser, APIError, Member, PronounsJson } from "$lib/api/entities";
import { apiFetchClient } from "$lib/api/fetch"; import { apiFetchClient } from "$lib/api/fetch";
import { error } from "@sveltejs/kit"; import { error } from "@sveltejs/kit";
@ -11,11 +11,13 @@ export const load = async ({ params }) => {
try { try {
const user = await apiFetchClient<MeUser>(`/users/@me`); const user = await apiFetchClient<MeUser>(`/users/@me`);
const member = await apiFetchClient<Member>(`/members/${params.id}`); const member = await apiFetchClient<Member>(`/members/${params.id}`);
const flags = await apiFetchClient<PrideFlag[]>("/users/@me/flags");
return { return {
user, user,
member, member,
pronouns: pronouns.autocomplete, pronouns: pronouns.autocomplete,
flags,
}; };
} catch (e) { } catch (e) {
throw error((e as APIError).code, (e as APIError).message); throw error((e as APIError).code, (e as APIError).message);