feat(frontend): links editor
This commit is contained in:
		
							parent
							
								
									b0a286dd9f
								
							
						
					
					
						commit
						c6eba5b51a
					
				
					 5 changed files with 141 additions and 12 deletions
				
			
		|  | @ -0,0 +1,84 @@ | |||
| <script lang="ts"> | ||||
| 	import type { RawApiError } from "$api/error"; | ||||
| 	import IconButton from "$components/IconButton.svelte"; | ||||
| 	import { t } from "$lib/i18n"; | ||||
| 	import FormStatusMarker from "./FormStatusMarker.svelte"; | ||||
| 
 | ||||
| 	type Props = { | ||||
| 		currentLinks: string[]; | ||||
| 		save(links: string[]): Promise<void>; | ||||
| 		form: { ok: boolean; error: RawApiError | null } | null; | ||||
| 	}; | ||||
| 	let { currentLinks, save, form }: Props = $props(); | ||||
| 
 | ||||
| 	let links = $state(currentLinks); | ||||
| 	let newEntry = $state(""); | ||||
| 
 | ||||
| 	const moveValue = (index: number, up: boolean) => { | ||||
| 		if (up && index == 0) return; | ||||
| 		if (!up && index == links.length - 1) return; | ||||
| 
 | ||||
| 		const newIndex = up ? index - 1 : index + 1; | ||||
| 		const temp = links[index]; | ||||
| 		links[index] = links[newIndex]; | ||||
| 		links[newIndex] = temp; | ||||
| 		links = [...links]; | ||||
| 	}; | ||||
| 
 | ||||
| 	const removeValue = (index: number) => { | ||||
| 		links.splice(index, 1); | ||||
| 		links = [...links]; | ||||
| 	}; | ||||
| 
 | ||||
| 	const addEntry = (event: Event) => { | ||||
| 		event.preventDefault(); | ||||
| 		if (!newEntry) return; | ||||
| 
 | ||||
| 		links = [...links, newEntry]; | ||||
| 		newEntry = ""; | ||||
| 	}; | ||||
| </script> | ||||
| 
 | ||||
| <h4> | ||||
| 	{$t("editor.links-header")} | ||||
| 	<button type="button" class="btn btn-primary" onclick={() => save(links)}> | ||||
| 		{$t("save-changes")} | ||||
| 	</button> | ||||
| </h4> | ||||
| 
 | ||||
| <FormStatusMarker {form} /> | ||||
| 
 | ||||
| {#each links as _, index} | ||||
| 	<div class="input-group m-1"> | ||||
| 		<IconButton | ||||
| 			icon="chevron-up" | ||||
| 			color="secondary" | ||||
| 			tooltip={$t("editor.move-entry-up")} | ||||
| 			onclick={() => moveValue(index, true)} | ||||
| 		/> | ||||
| 		<IconButton | ||||
| 			icon="chevron-down" | ||||
| 			color="secondary" | ||||
| 			tooltip={$t("editor.move-entry-down")} | ||||
| 			onclick={() => moveValue(index, false)} | ||||
| 		/> | ||||
| 		<input type="text" class="form-control" bind:value={links[index]} autocomplete="off" /> | ||||
| 		<IconButton | ||||
| 			color="danger" | ||||
| 			icon="trash3" | ||||
| 			tooltip={$t("editor.remove-entry")} | ||||
| 			onclick={() => removeValue(index)} | ||||
| 		/> | ||||
| 	</div> | ||||
| {/each} | ||||
| 
 | ||||
| <form class="input-group m-1" onsubmit={addEntry}> | ||||
| 	<input | ||||
| 		type="text" | ||||
| 		class="form-control" | ||||
| 		bind:value={newEntry} | ||||
| 		placeholder={$t("editor.new-entry")} | ||||
| 		autocomplete="off" | ||||
| 	/> | ||||
| 	<IconButton type="submit" color="success" icon="plus" tooltip={$t("editor.add-entry")} /> | ||||
| </form> | ||||
|  | @ -9,7 +9,7 @@ | |||
| 		if (raw.startsWith("https://")) out = raw.substring("https://".length); | ||||
| 		else if (raw.startsWith("http://")) out = raw.substring("http://".length); | ||||
| 
 | ||||
| 		if (raw.endsWith("/")) out = raw.substring(0, raw.length - 1); | ||||
| 		if (out.endsWith("/")) out = out.substring(0, out.length - 1); | ||||
| 
 | ||||
| 		return out; | ||||
| 	}; | ||||
|  |  | |||
|  | @ -202,7 +202,8 @@ | |||
| 		"flag-search-no-flags": "No flags matched your search query.", | ||||
| 		"flag-search-no-account-flags": "You haven't uploaded any flags yet.", | ||||
| 		"flag-search-hint": "Can't find the flag you're looking for? Try using the search bar above.", | ||||
| 		"flag-manage-your-flags": "Manage your flags" | ||||
| 		"flag-manage-your-flags": "Manage your flags", | ||||
| 		"links-header": "Links" | ||||
| 	}, | ||||
| 	"cancel": "Cancel" | ||||
| } | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| 	import { fastRequest } from "$api"; | ||||
| 	import type { RawApiError } from "$api/error"; | ||||
| 	import ApiError from "$api/error"; | ||||
| 	import LinksEditor from "$components/editor/LinksEditor.svelte"; | ||||
| 	import ProfileFlagsEditor from "$components/editor/ProfileFlagsEditor.svelte"; | ||||
| 	import log from "$lib/log"; | ||||
| 	import type { PageData } from "./$types"; | ||||
|  | @ -9,20 +10,41 @@ | |||
| 	type Props = { data: PageData }; | ||||
| 	let { data }: Props = $props(); | ||||
| 
 | ||||
| 	let form: { ok: boolean; error: RawApiError | null } | null = $state(null); | ||||
| 	let flagForm: { ok: boolean; error: RawApiError | null } | null = $state(null); | ||||
| 	let linksForm: { ok: boolean; error: RawApiError | null } | null = $state(null); | ||||
| 
 | ||||
| 	const save = async (flags: string[]) => { | ||||
| 	const flagSave = async (flags: string[]) => { | ||||
| 		try { | ||||
| 			await fastRequest("PATCH", `/users/@me/members/${data.member.id}`, { | ||||
| 				body: { flags }, | ||||
| 				token: data.token, | ||||
| 			}); | ||||
| 			form = { ok: true, error: null }; | ||||
| 			flagForm = { ok: true, error: null }; | ||||
| 		} catch (e) { | ||||
| 			log.error("Could not update profile flags for member %s:", data.member.id, e); | ||||
| 			if (e instanceof ApiError) form = { ok: false, error: e.obj }; | ||||
| 			if (e instanceof ApiError) flagForm = { ok: false, error: e.obj }; | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	const linksSave = async (links: string[]) => { | ||||
| 		try { | ||||
| 			await fastRequest("PATCH", "/users/@me", { | ||||
| 				body: { links }, | ||||
| 				token: data.token, | ||||
| 			}); | ||||
| 			linksForm = { ok: true, error: null }; | ||||
| 		} catch (e) { | ||||
| 			log.error("Could not update profile links:", e); | ||||
| 			if (e instanceof ApiError) linksForm = { ok: false, error: e.obj }; | ||||
| 		} | ||||
| 	}; | ||||
| </script> | ||||
| 
 | ||||
| <ProfileFlagsEditor profileFlags={data.member.flags} allFlags={data.flags} {save} {form} /> | ||||
| <ProfileFlagsEditor | ||||
| 	profileFlags={data.member.flags} | ||||
| 	allFlags={data.flags} | ||||
| 	save={flagSave} | ||||
| 	form={flagForm} | ||||
| /> | ||||
| 
 | ||||
| <LinksEditor currentLinks={data.member.links} save={linksSave} form={linksForm} /> | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| 	import { fastRequest } from "$api"; | ||||
| 	import type { RawApiError } from "$api/error"; | ||||
| 	import ApiError from "$api/error"; | ||||
| 	import LinksEditor from "$components/editor/LinksEditor.svelte"; | ||||
| 	import ProfileFlagsEditor from "$components/editor/ProfileFlagsEditor.svelte"; | ||||
| 	import log from "$lib/log"; | ||||
| 	import type { PageData } from "./$types"; | ||||
|  | @ -9,20 +10,41 @@ | |||
| 	type Props = { data: PageData }; | ||||
| 	let { data }: Props = $props(); | ||||
| 
 | ||||
| 	let form: { ok: boolean; error: RawApiError | null } | null = $state(null); | ||||
| 	let flagForm: { ok: boolean; error: RawApiError | null } | null = $state(null); | ||||
| 	let linksForm: { ok: boolean; error: RawApiError | null } | null = $state(null); | ||||
| 
 | ||||
| 	const save = async (flags: string[]) => { | ||||
| 	const flagSave = async (flags: string[]) => { | ||||
| 		try { | ||||
| 			await fastRequest("PATCH", "/users/@me", { | ||||
| 				body: { flags }, | ||||
| 				token: data.token, | ||||
| 			}); | ||||
| 			form = { ok: true, error: null }; | ||||
| 			flagForm = { ok: true, error: null }; | ||||
| 		} catch (e) { | ||||
| 			log.error("Could not update profile flags:", e); | ||||
| 			if (e instanceof ApiError) form = { ok: false, error: e.obj }; | ||||
| 			if (e instanceof ApiError) flagForm = { ok: false, error: e.obj }; | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	const linksSave = async (links: string[]) => { | ||||
| 		try { | ||||
| 			await fastRequest("PATCH", "/users/@me", { | ||||
| 				body: { links }, | ||||
| 				token: data.token, | ||||
| 			}); | ||||
| 			linksForm = { ok: true, error: null }; | ||||
| 		} catch (e) { | ||||
| 			log.error("Could not update profile links:", e); | ||||
| 			if (e instanceof ApiError) linksForm = { ok: false, error: e.obj }; | ||||
| 		} | ||||
| 	}; | ||||
| </script> | ||||
| 
 | ||||
| <ProfileFlagsEditor profileFlags={data.user.flags} allFlags={data.flags} {save} {form} /> | ||||
| <ProfileFlagsEditor | ||||
| 	profileFlags={data.user.flags} | ||||
| 	allFlags={data.flags} | ||||
| 	save={flagSave} | ||||
| 	form={flagForm} | ||||
| /> | ||||
| 
 | ||||
| <LinksEditor currentLinks={data.user.links} save={linksSave} form={linksForm} /> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue