feat: so much more frontend stuff
This commit is contained in:
parent
c179669799
commit
261435c252
24 changed files with 682 additions and 107 deletions
|
@ -1,14 +1,22 @@
|
|||
<script lang="ts">
|
||||
import { DEFAULT_AVATAR } from "$lib";
|
||||
|
||||
type Props = { url: string | null; alt: string; lazyLoad?: boolean; width?: number };
|
||||
let { url, alt, lazyLoad, width }: Props = $props();
|
||||
type Props = { url: string | null; alt: string; lazyLoad?: boolean };
|
||||
let { url, alt, lazyLoad }: Props = $props();
|
||||
</script>
|
||||
|
||||
<img
|
||||
class="rounded-circle img-fluid"
|
||||
src={url || DEFAULT_AVATAR}
|
||||
{alt}
|
||||
width={width || 200}
|
||||
width={200}
|
||||
loading={lazyLoad ? "lazy" : "eager"}
|
||||
/>
|
||||
|
||||
<style>
|
||||
img {
|
||||
object-fit: cover;
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
<script lang="ts">
|
||||
import Avatar from "$components/Avatar.svelte";
|
||||
import { t } from "$lib/i18n";
|
||||
import { Icon, InputGroup } from "@sveltestrap/sveltestrap";
|
||||
import { encode } from "base64-arraybuffer";
|
||||
import prettyBytes from "pretty-bytes";
|
||||
|
||||
type Props = {
|
||||
current: string | null;
|
||||
alt: string;
|
||||
onclick: (avatar: string) => Promise<void>;
|
||||
updated: boolean;
|
||||
};
|
||||
let { current, alt, onclick, updated }: Props = $props();
|
||||
|
||||
const MAX_AVATAR_BYTES = 1_000_000;
|
||||
|
||||
let avatarFiles: FileList | null = $state(null);
|
||||
let avatar: string = $state("");
|
||||
let avatarExists = $derived(avatar !== "");
|
||||
let avatarTooLarge = $derived(avatar !== "" && avatar.length > MAX_AVATAR_BYTES);
|
||||
|
||||
$effect(() => {
|
||||
getAvatar(avatarFiles);
|
||||
});
|
||||
|
||||
const getAvatar = async (list: FileList | null) => {
|
||||
if (!list || list.length === 0) {
|
||||
avatar = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const buffer = await list[0].arrayBuffer();
|
||||
const base64 = encode(buffer);
|
||||
|
||||
const uri = `data:${list[0].type};base64,${base64}`;
|
||||
avatar = uri;
|
||||
};
|
||||
</script>
|
||||
|
||||
<p class="text-center">
|
||||
<Avatar url={avatarExists ? avatar : current} {alt} />
|
||||
</p>
|
||||
|
||||
<InputGroup class="mb-2">
|
||||
<input
|
||||
class="form-control"
|
||||
id="avatar"
|
||||
type="file"
|
||||
bind:files={avatarFiles}
|
||||
accept="image/png, image/jpeg, image/gif, image/webp"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
disabled={!avatarExists || avatarTooLarge}
|
||||
onclick={() => onclick(avatar)}
|
||||
>
|
||||
{$t("edit-profile.update-avatar")}
|
||||
</button>
|
||||
</InputGroup>
|
||||
|
||||
{#if updated}
|
||||
<p class="text-success-emphasis">
|
||||
<Icon name="check-circle-fill" />
|
||||
{$t("edit-profile.avatar-updated")}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
{#if avatarTooLarge}
|
||||
<p class="text-danger-emphasis">
|
||||
<Icon name="exclamation-circle-fill" />
|
||||
{$t("edit-profile.file-too-large", {
|
||||
max: prettyBytes(MAX_AVATAR_BYTES),
|
||||
current: prettyBytes(avatar.length),
|
||||
})}
|
||||
</p>
|
||||
{/if}
|
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts">
|
||||
import { Icon } from "@sveltestrap/sveltestrap";
|
||||
import { t } from "$lib/i18n";
|
||||
import type { RawApiError } from "$api/error";
|
||||
import ErrorAlert from "$components/ErrorAlert.svelte";
|
||||
|
||||
type Props = { form: { error: RawApiError | null; ok: boolean } | null };
|
||||
let { form }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if form?.error}
|
||||
<ErrorAlert error={form.error} />
|
||||
{:else if form?.ok}
|
||||
<p class="text-success-emphasis">
|
||||
<Icon name="check-circle-fill" />
|
||||
{$t("edit-profile.saved-changes")}
|
||||
</p>
|
||||
{/if}
|
Loading…
Add table
Add a link
Reference in a new issue