mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-02-12 16:10:15 +00:00
feat: add ability to set default profile picture (#1061)
Co-authored-by: Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com>
This commit is contained in:
@@ -40,23 +40,40 @@
|
||||
}
|
||||
|
||||
async function updateImages(
|
||||
logoLight: File | null,
|
||||
logoDark: File | null,
|
||||
backgroundImage: File | null,
|
||||
favicon: File | null
|
||||
logoLight: File | undefined,
|
||||
logoDark: File | undefined,
|
||||
defaultProfilePicture: File | null | undefined,
|
||||
backgroundImage: File | undefined,
|
||||
favicon: File | undefined
|
||||
) {
|
||||
const faviconPromise = favicon ? appConfigService.updateFavicon(favicon) : Promise.resolve();
|
||||
|
||||
const lightLogoPromise = logoLight
|
||||
? appConfigService.updateLogo(logoLight, true)
|
||||
: Promise.resolve();
|
||||
|
||||
const darkLogoPromise = logoDark
|
||||
? appConfigService.updateLogo(logoDark, false)
|
||||
: Promise.resolve();
|
||||
|
||||
const defaultProfilePicturePromise =
|
||||
defaultProfilePicture === null
|
||||
? appConfigService.deleteDefaultProfilePicture()
|
||||
: defaultProfilePicture
|
||||
? appConfigService.updateDefaultProfilePicture(defaultProfilePicture)
|
||||
: Promise.resolve();
|
||||
|
||||
const backgroundImagePromise = backgroundImage
|
||||
? appConfigService.updateBackgroundImage(backgroundImage)
|
||||
: Promise.resolve();
|
||||
|
||||
await Promise.all([lightLogoPromise, darkLogoPromise, backgroundImagePromise, faviconPromise])
|
||||
await Promise.all([
|
||||
lightLogoPromise,
|
||||
darkLogoPromise,
|
||||
defaultProfilePicturePromise,
|
||||
backgroundImagePromise,
|
||||
faviconPromise
|
||||
])
|
||||
.then(() => toast.success(m.images_updated_successfully()))
|
||||
.catch(axiosErrorToast);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script lang="ts">
|
||||
import FileInput from '$lib/components/form/file-input.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { cn } from '$lib/utils/style';
|
||||
import { LucideUpload } from '@lucide/svelte';
|
||||
import { LucideImageOff, LucideUpload, LucideX } from '@lucide/svelte';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
@@ -13,54 +14,98 @@
|
||||
imageURL,
|
||||
accept = 'image/png, image/jpeg, image/svg+xml, image/gif, image/webp, image/avif, image/heic',
|
||||
forceColorScheme,
|
||||
isResetable = false,
|
||||
isImageSet = $bindable(true),
|
||||
...restProps
|
||||
}: HTMLAttributes<HTMLDivElement> & {
|
||||
id: string;
|
||||
imageClass: string;
|
||||
label: string;
|
||||
image: File | null;
|
||||
image: File | null | undefined;
|
||||
imageURL: string;
|
||||
forceColorScheme?: 'light' | 'dark';
|
||||
accept?: string;
|
||||
isResetable?: boolean;
|
||||
isImageSet?: boolean;
|
||||
} = $props();
|
||||
|
||||
let imageDataURL = $state(imageURL);
|
||||
|
||||
$effect(() => {
|
||||
if (image) {
|
||||
isImageSet = true;
|
||||
}
|
||||
});
|
||||
|
||||
function onImageChange(e: Event) {
|
||||
const file = (e.target as HTMLInputElement).files?.[0] || null;
|
||||
const file = (e.target as HTMLInputElement).files?.[0] || undefined;
|
||||
if (!file) return;
|
||||
|
||||
image = file;
|
||||
imageDataURL = URL.createObjectURL(file);
|
||||
}
|
||||
|
||||
function onReset() {
|
||||
image = null;
|
||||
imageDataURL = imageURL;
|
||||
isImageSet = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col items-start md:flex-row md:items-center" {...restProps}>
|
||||
<Label class="w-52" for={id}>{label}</Label>
|
||||
<FileInput {id} variant="secondary" {accept} onchange={onImageChange}>
|
||||
<div
|
||||
class={{
|
||||
'group relative flex items-center rounded': true,
|
||||
class={cn('group/image relative flex items-center rounded transition-colors', {
|
||||
'bg-[#F5F5F5]': forceColorScheme === 'light',
|
||||
'bg-[#262626]': forceColorScheme === 'dark',
|
||||
'bg-muted': !forceColorScheme
|
||||
}}
|
||||
})}
|
||||
>
|
||||
<img
|
||||
class={cn(
|
||||
'h-full w-full rounded object-cover p-3 transition-opacity duration-200 group-hover:opacity-10',
|
||||
imageClass
|
||||
)}
|
||||
src={imageDataURL}
|
||||
alt={label}
|
||||
/>
|
||||
{#if !isImageSet}
|
||||
<div
|
||||
class={cn(
|
||||
'flex h-full w-full items-center justify-center p-3 transition-opacity duration-200',
|
||||
'group-hover/image:opacity-10 group-has-[button:hover]/image:opacity-100',
|
||||
imageClass
|
||||
)}
|
||||
>
|
||||
<LucideImageOff class="text-muted-foreground" />
|
||||
</div>
|
||||
{:else}
|
||||
<img
|
||||
class={cn(
|
||||
'h-full w-full rounded object-cover p-3 transition-opacity duration-200',
|
||||
'group-hover/image:opacity-10 group-has-[button:hover]/image:opacity-100',
|
||||
imageClass
|
||||
)}
|
||||
src={imageDataURL}
|
||||
alt={label}
|
||||
onerror={() => (isImageSet = false)}
|
||||
/>
|
||||
{/if}
|
||||
<LucideUpload
|
||||
class={{
|
||||
'absolute top-1/2 left-1/2 size-5 -translate-x-1/2 -translate-y-1/2 transform font-medium opacity-0 transition-opacity group-hover:opacity-100': true,
|
||||
'text-black': forceColorScheme === 'light',
|
||||
'text-white': forceColorScheme === 'dark'
|
||||
}}
|
||||
class={cn(
|
||||
'absolute top-1/2 left-1/2 size-5 -translate-x-1/2 -translate-y-1/2 transform font-medium opacity-0 transition-opacity duration-200',
|
||||
'group-hover/image:opacity-100 group-has-[button:hover]/image:opacity-0',
|
||||
{
|
||||
'text-black': forceColorScheme === 'light',
|
||||
'text-white': forceColorScheme === 'dark'
|
||||
}
|
||||
)}
|
||||
/>
|
||||
{#if isResetable && isImageSet}
|
||||
<Button
|
||||
size="icon"
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
onReset();
|
||||
}}
|
||||
class="absolute -top-2 -right-2 size-6 rounded-full shadow-md"
|
||||
>
|
||||
<LucideX class="size-3" />
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</FileInput>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,32 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/ui/button/button.svelte';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import { cachedApplicationLogo, cachedBackgroundImage } from '$lib/utils/cached-image-util';
|
||||
import {
|
||||
cachedApplicationLogo,
|
||||
cachedBackgroundImage,
|
||||
cachedDefaultProfilePicture
|
||||
} from '$lib/utils/cached-image-util';
|
||||
import ApplicationImage from './application-image.svelte';
|
||||
|
||||
let {
|
||||
callback
|
||||
}: {
|
||||
callback: (
|
||||
logoLight: File | null,
|
||||
logoDark: File | null,
|
||||
backgroundImage: File | null,
|
||||
favicon: File | null
|
||||
logoLight: File | undefined,
|
||||
logoDark: File | undefined,
|
||||
defaultProfilePicture: File | null | undefined,
|
||||
backgroundImage: File | undefined,
|
||||
favicon: File | undefined
|
||||
) => void;
|
||||
} = $props();
|
||||
|
||||
let logoLight = $state<File | null>(null);
|
||||
let logoDark = $state<File | null>(null);
|
||||
let backgroundImage = $state<File | null>(null);
|
||||
let favicon = $state<File | null>(null);
|
||||
let logoLight = $state<File | undefined>();
|
||||
let logoDark = $state<File | undefined>();
|
||||
let defaultProfilePicture = $state<File | null | undefined>();
|
||||
let backgroundImage = $state<File | undefined>();
|
||||
let favicon = $state<File | undefined>();
|
||||
|
||||
let defaultProfilePictureSet = $state(true);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-8">
|
||||
@@ -46,6 +54,15 @@
|
||||
imageURL={cachedApplicationLogo.getUrl(false)}
|
||||
forceColorScheme="dark"
|
||||
/>
|
||||
<ApplicationImage
|
||||
id="default-profile-picture"
|
||||
imageClass="size-24"
|
||||
label={m.default_profile_picture()}
|
||||
isResetable
|
||||
bind:image={defaultProfilePicture}
|
||||
imageURL={cachedDefaultProfilePicture.getUrl()}
|
||||
isImageSet={defaultProfilePictureSet}
|
||||
/>
|
||||
<ApplicationImage
|
||||
id="background-image"
|
||||
imageClass="h-[350px] max-w-[500px]"
|
||||
@@ -55,7 +72,10 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<Button class="mt-5" onclick={() => callback(logoLight, logoDark, backgroundImage, favicon)}
|
||||
<Button
|
||||
class="mt-5"
|
||||
usePromiseLoading
|
||||
onclick={() => callback(logoLight, logoDark, defaultProfilePicture, backgroundImage, favicon)}
|
||||
>{m.save()}</Button
|
||||
>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user