feat: add flags to edit profile page
This commit is contained in:
		
							parent
							
								
									21cce9c5af
								
							
						
					
					
						commit
						4ebc5d5003
					
				
					 4 changed files with 141 additions and 1 deletions
				
			
		|  | @ -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; | ||||
|  |  | |||
							
								
								
									
										26
									
								
								frontend/src/routes/edit/FlagButton.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								frontend/src/routes/edit/FlagButton.svelte
									
										
									
									
									
										Normal 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> | ||||
|  | @ -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} | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue