feat: add flags to edit profile page

This commit is contained in:
Sam 2023-05-29 02:50:38 +02:00
parent 21cce9c5af
commit 4ebc5d5003
No known key found for this signature in database
GPG key ID: B4EF20DDE721CAA1
4 changed files with 141 additions and 1 deletions

View file

@ -17,6 +17,7 @@ export interface User {
pronouns: Pronoun[]; pronouns: Pronoun[];
members: PartialMember[]; members: PartialMember[];
fields: Field[]; fields: Field[];
flags: PrideFlag[];
custom_preferences: CustomPreferences; custom_preferences: CustomPreferences;
} }
@ -83,6 +84,7 @@ export interface PartialMember {
export interface Member extends PartialMember { export interface Member extends PartialMember {
fields: Field[]; fields: Field[];
flags: PrideFlag[];
user: MemberPartialUser; user: MemberPartialUser;
unlisted?: boolean; unlisted?: boolean;

View file

@ -0,0 +1,26 @@
<script lang="ts">
import { flagURL, type PrideFlag } from "$lib/api/entities";
import { Button, Tooltip } from "sveltestrap";
export let flag: PrideFlag;
export let tooltip: string;
let className: string | null | undefined = undefined;
export { className as class };
let elem: HTMLElement;
</script>
<Tooltip target={elem} placement="top">{tooltip}</Tooltip>
<Button bind:inner={elem} class={className} on:click color="secondary" outline>
<img class="flag" src={flagURL(flag)} alt={flag.description ?? flag.name} />
{flag.name}
</Button>
<style>
.flag {
height: 1.5rem;
max-width: 200px;
border-radius: 3px;
margin-left: -5px;
}
</style>

View file

@ -9,6 +9,7 @@
type Pronoun, type Pronoun,
PreferenceSize, PreferenceSize,
type CustomPreferences, type CustomPreferences,
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 { userStore } from "$lib/store"; import { userStore } from "$lib/store";
@ -39,6 +40,7 @@
import MarkdownHelp from "../MarkdownHelp.svelte"; import MarkdownHelp from "../MarkdownHelp.svelte";
import prettyBytes from "pretty-bytes"; import prettyBytes from "pretty-bytes";
import CustomPreference from "./CustomPreference.svelte"; import CustomPreference from "./CustomPreference.svelte";
import FlagButton from "../FlagButton.svelte";
const MAX_AVATAR_BYTES = 1_000_000; const MAX_AVATAR_BYTES = 1_000_000;
@ -53,6 +55,7 @@
let names: FieldEntry[] = window.structuredClone(data.user.names); let names: FieldEntry[] = window.structuredClone(data.user.names);
let pronouns: Pronoun[] = window.structuredClone(data.user.pronouns); let pronouns: Pronoun[] = window.structuredClone(data.user.pronouns);
let fields: Field[] = window.structuredClone(data.user.fields); let fields: Field[] = window.structuredClone(data.user.fields);
let flags: PrideFlag[] = window.structuredClone(data.user.flags);
let list_private = data.user.list_private; let list_private = data.user.list_private;
let custom_preferences = window.structuredClone(data.user.custom_preferences); let custom_preferences = window.structuredClone(data.user.custom_preferences);
@ -63,6 +66,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 preferenceIds: string[]; let preferenceIds: string[];
$: preferenceIds = Object.keys(custom_preferences); $: preferenceIds = Object.keys(custom_preferences);
@ -76,6 +91,7 @@
names, names,
pronouns, pronouns,
fields, fields,
flags,
avatar, avatar,
member_title, member_title,
list_private, list_private,
@ -91,6 +107,7 @@
names: FieldEntry[], names: FieldEntry[],
pronouns: Pronoun[], pronouns: Pronoun[],
fields: Field[], fields: Field[],
flags: PrideFlag[],
avatar: string | null, avatar: string | null,
member_title: string, member_title: string,
list_private: boolean, list_private: boolean,
@ -101,6 +118,7 @@
if (member_title !== (user.member_title || "")) return true; if (member_title !== (user.member_title || "")) return true;
if (!linksEqual(links, user.links)) return true; if (!linksEqual(links, user.links)) return true;
if (!fieldsEqual(fields, user.fields)) return true; if (!fieldsEqual(fields, user.fields)) return true;
if (!flagsEqual(flags, user.flags)) return true;
if (!namesEqual(names, user.names)) return true; if (!namesEqual(names, user.names)) return true;
if (!pronounsEqual(pronouns, user.pronouns)) return true; if (!pronounsEqual(pronouns, user.pronouns)) return true;
if (!customPreferencesEqual(custom_preferences, user.custom_preferences)) return true; if (!customPreferencesEqual(custom_preferences, user.custom_preferences)) return true;
@ -145,6 +163,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 customPreferencesEqual = (obj1: CustomPreferences, obj2: CustomPreferences) => { const customPreferencesEqual = (obj1: CustomPreferences, obj2: CustomPreferences) => {
if (Object.keys(obj2).some((key) => !(key in obj1))) return false; if (Object.keys(obj2).some((key) => !(key in obj1))) return false;
@ -227,6 +250,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();
@ -317,6 +360,7 @@
member_title, member_title,
list_private, list_private,
custom_preferences, custom_preferences,
flags: flags.map((flag) => flag.id),
}); });
data.user = resp; data.user = resp;
@ -516,6 +560,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 { APIError, MeUser, PronounsJson } from "$lib/api/entities"; import type { PrideFlag, APIError, MeUser, 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";
@ -10,10 +10,12 @@ export const ssr = false;
export const load = async () => { export const load = async () => {
try { try {
const user = await apiFetchClient<MeUser>(`/users/@me`); const user = await apiFetchClient<MeUser>(`/users/@me`);
const flags = await apiFetchClient<PrideFlag[]>("/users/@me/flags");
return { return {
user, user,
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);