diff --git a/frontend/src/lib/api/entities.ts b/frontend/src/lib/api/entities.ts index df2ad47..37bcd87 100644 --- a/frontend/src/lib/api/entities.ts +++ b/frontend/src/lib/api/entities.ts @@ -17,6 +17,7 @@ export interface User { pronouns: Pronoun[]; members: PartialMember[]; fields: Field[]; + flags: PrideFlag[]; custom_preferences: CustomPreferences; } @@ -83,6 +84,7 @@ export interface PartialMember { export interface Member extends PartialMember { fields: Field[]; + flags: PrideFlag[]; user: MemberPartialUser; unlisted?: boolean; diff --git a/frontend/src/routes/edit/FlagButton.svelte b/frontend/src/routes/edit/FlagButton.svelte new file mode 100644 index 0000000..19c8fbd --- /dev/null +++ b/frontend/src/routes/edit/FlagButton.svelte @@ -0,0 +1,26 @@ + + +{tooltip} + + + diff --git a/frontend/src/routes/edit/profile/+page.svelte b/frontend/src/routes/edit/profile/+page.svelte index b6fda9f..f2ee796 100644 --- a/frontend/src/routes/edit/profile/+page.svelte +++ b/frontend/src/routes/edit/profile/+page.svelte @@ -9,6 +9,7 @@ type Pronoun, PreferenceSize, type CustomPreferences, + type PrideFlag, } from "$lib/api/entities"; import FallbackImage from "$lib/components/FallbackImage.svelte"; import { userStore } from "$lib/store"; @@ -39,6 +40,7 @@ import MarkdownHelp from "../MarkdownHelp.svelte"; import prettyBytes from "pretty-bytes"; import CustomPreference from "./CustomPreference.svelte"; + import FlagButton from "../FlagButton.svelte"; const MAX_AVATAR_BYTES = 1_000_000; @@ -53,6 +55,7 @@ let names: FieldEntry[] = window.structuredClone(data.user.names); let pronouns: Pronoun[] = window.structuredClone(data.user.pronouns); let fields: Field[] = window.structuredClone(data.user.fields); + let flags: PrideFlag[] = window.structuredClone(data.user.flags); let list_private = data.user.list_private; let custom_preferences = window.structuredClone(data.user.custom_preferences); @@ -63,6 +66,18 @@ let newPronouns = ""; 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 preferenceIds: string[]; $: preferenceIds = Object.keys(custom_preferences); @@ -76,6 +91,7 @@ names, pronouns, fields, + flags, avatar, member_title, list_private, @@ -91,6 +107,7 @@ names: FieldEntry[], pronouns: Pronoun[], fields: Field[], + flags: PrideFlag[], avatar: string | null, member_title: string, list_private: boolean, @@ -101,6 +118,7 @@ if (member_title !== (user.member_title || "")) return true; if (!linksEqual(links, user.links)) return true; if (!fieldsEqual(fields, user.fields)) return true; + if (!flagsEqual(flags, user.flags)) return true; if (!namesEqual(names, user.names)) return true; if (!pronounsEqual(pronouns, user.pronouns)) return true; if (!customPreferencesEqual(custom_preferences, user.custom_preferences)) return true; @@ -145,6 +163,11 @@ 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 customPreferencesEqual = (obj1: CustomPreferences, obj2: CustomPreferences) => { if (Object.keys(obj2).some((key) => !(key in obj1))) return false; @@ -227,6 +250,26 @@ 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) => { event.preventDefault(); @@ -317,6 +360,7 @@ member_title, list_private, custom_preferences, + flags: flags.map((flag) => flag.id), }); data.user = resp; @@ -516,6 +560,72 @@ + +
+ {#each flags as _, index} + + moveFlag(index, true)} + /> + moveFlag(index, false)} + /> + removeFlag(index)} + /> + + {/each} +
+
+
+
+ +
+ {#each filteredFlags as flag (flag.id)} + addFlag(flag)} + /> + {:else} + {#if data.flags.length === 0} + You haven't uploaded any flags yet. + {:else} + There are no flags matching your search {flagSearch}. + {/if} + {/each} +
+
+
+ + {#if data.flags.length === 0} +

Why can't I see any flags?

+

+ 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 settings. Your main profile + and your member profiles can all have different flags. +

+ {:else} + To upload and delete flags, go to your settings. + {/if} +
+
+
+
{#each links as _, index} diff --git a/frontend/src/routes/edit/profile/+page.ts b/frontend/src/routes/edit/profile/+page.ts index 1054016..894eb83 100644 --- a/frontend/src/routes/edit/profile/+page.ts +++ b/frontend/src/routes/edit/profile/+page.ts @@ -1,4 +1,4 @@ -import type { APIError, MeUser, PronounsJson } from "$lib/api/entities"; +import type { PrideFlag, APIError, MeUser, PronounsJson } from "$lib/api/entities"; import { apiFetchClient } from "$lib/api/fetch"; import { error } from "@sveltejs/kit"; @@ -10,10 +10,12 @@ export const ssr = false; export const load = async () => { try { const user = await apiFetchClient(`/users/@me`); + const flags = await apiFetchClient("/users/@me/flags"); return { user, pronouns: pronouns.autocomplete, + flags, }; } catch (e) { throw error((e as APIError).code, (e as APIError).message);