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[];
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;

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,
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 @@
</Button>
</div>
</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">
<div class="mt-3">
{#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 { error } from "@sveltejs/kit";
@ -10,10 +10,12 @@ export const ssr = false;
export const load = async () => {
try {
const user = await apiFetchClient<MeUser>(`/users/@me`);
const flags = await apiFetchClient<PrideFlag[]>("/users/@me/flags");
return {
user,
pronouns: pronouns.autocomplete,
flags,
};
} catch (e) {
throw error((e as APIError).code, (e as APIError).message);