mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-03-22 17:55:08 +00:00
Merge branch 'main' into chore/depot
This commit is contained in:
@@ -2,7 +2,9 @@ package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -34,6 +36,7 @@ func NewAppImagesController(
|
||||
group.PUT("/application-images/favicon", authMiddleware.Add(), controller.updateFaviconHandler)
|
||||
group.PUT("/application-images/default-profile-picture", authMiddleware.Add(), controller.updateDefaultProfilePicture)
|
||||
|
||||
group.DELETE("/application-images/background", authMiddleware.Add(), controller.deleteBackgroundImageHandler)
|
||||
group.DELETE("/application-images/default-profile-picture", authMiddleware.Add(), controller.deleteDefaultProfilePicture)
|
||||
}
|
||||
|
||||
@@ -192,12 +195,27 @@ func (c *AppImagesController) updateBackgroundImageHandler(ctx *gin.Context) {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// deleteBackgroundImageHandler godoc
|
||||
// @Summary Delete background image
|
||||
// @Description Delete the application background image
|
||||
// @Tags Application Images
|
||||
// @Success 204 "No Content"
|
||||
// @Router /api/application-images/background [delete]
|
||||
func (c *AppImagesController) deleteBackgroundImageHandler(ctx *gin.Context) {
|
||||
if err := c.appImagesService.DeleteImage(ctx.Request.Context(), "background"); err != nil {
|
||||
_ = ctx.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// updateFaviconHandler godoc
|
||||
// @Summary Update favicon
|
||||
// @Description Update the application favicon
|
||||
// @Tags Application Images
|
||||
// @Accept multipart/form-data
|
||||
// @Param file formData file true "Favicon file (.ico)"
|
||||
// @Param file formData file true "Favicon file (.svg/.png/.ico)"
|
||||
// @Success 204 "No Content"
|
||||
// @Router /api/application-images/favicon [put]
|
||||
func (c *AppImagesController) updateFaviconHandler(ctx *gin.Context) {
|
||||
@@ -208,8 +226,9 @@ func (c *AppImagesController) updateFaviconHandler(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
fileType := utils.GetFileExtension(file.Filename)
|
||||
if fileType != "ico" {
|
||||
_ = ctx.Error(&common.WrongFileTypeError{ExpectedFileType: ".ico"})
|
||||
mimeType := utils.GetImageMimeType(strings.ToLower(fileType))
|
||||
if !slices.Contains([]string{"image/svg+xml", "image/png", "image/x-icon"}, mimeType) {
|
||||
_ = ctx.Error(&common.WrongFileTypeError{ExpectedFileType: ".svg or .png or .ico"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
<script module lang="ts">
|
||||
// Persist the last failing background image URL across route remounts so
|
||||
// login pages without a background do not briefly retry the image and stutter.
|
||||
let persistedMissingBackgroundImageUrl: string | undefined;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
@@ -17,13 +23,32 @@
|
||||
showAlternativeSignInMethodButton?: boolean;
|
||||
} = $props();
|
||||
|
||||
let missingBackgroundImageUrl = $state<string | undefined>(persistedMissingBackgroundImageUrl);
|
||||
let loadedBackgroundImageUrl = $state<string | undefined>();
|
||||
let isInitialLoad = $state(false);
|
||||
let animate = $derived(isInitialLoad && !$appConfigStore.disableAnimations);
|
||||
let backgroundImageUrl = $derived(cachedBackgroundImage.getUrl());
|
||||
let imageError = $derived(missingBackgroundImageUrl === backgroundImageUrl);
|
||||
let imageLoaded = $derived(loadedBackgroundImageUrl === backgroundImageUrl);
|
||||
let animate = $derived(isInitialLoad && imageLoaded && !$appConfigStore.disableAnimations);
|
||||
|
||||
afterNavigate((e) => {
|
||||
isInitialLoad = !e?.from?.url;
|
||||
});
|
||||
|
||||
function onBackgroundImageLoad() {
|
||||
loadedBackgroundImageUrl = backgroundImageUrl;
|
||||
if (persistedMissingBackgroundImageUrl === backgroundImageUrl) {
|
||||
persistedMissingBackgroundImageUrl = undefined;
|
||||
missingBackgroundImageUrl = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function onBackgroundImageError() {
|
||||
loadedBackgroundImageUrl = undefined;
|
||||
persistedMissingBackgroundImageUrl = backgroundImageUrl;
|
||||
missingBackgroundImageUrl = backgroundImageUrl;
|
||||
}
|
||||
|
||||
const isDesktop = new MediaQuery('min-width: 1024px');
|
||||
let alternativeSignInButton = $state({
|
||||
href: '/login/alternative',
|
||||
@@ -46,9 +71,9 @@
|
||||
</script>
|
||||
|
||||
{#if isDesktop.current}
|
||||
<div class="h-screen items-center overflow-hidden text-center">
|
||||
<div class="h-screen items-center overflow-hidden text-center flex justify-center">
|
||||
<div
|
||||
class="relative z-10 flex h-full w-[650px] 2xl:w-[800px] p-16 {cn(
|
||||
class="flex h-full w-[650px] 2xl:w-[800px] p-16 {cn(
|
||||
showAlternativeSignInMethodButton && 'pb-0'
|
||||
)}"
|
||||
>
|
||||
@@ -69,16 +94,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Background image -->
|
||||
<div class="absolute top-0 right-0 left-500px bottom-0 z-0 overflow-hidden rounded-[40px] m-6">
|
||||
<img
|
||||
src={cachedBackgroundImage.getUrl()}
|
||||
class="{cn(
|
||||
animate && 'animate-bg-zoom'
|
||||
)} h-screen object-cover w-[calc(100vw-650px)] 2xl:w-[calc(100vw-800px)]"
|
||||
alt={m.login_background()}
|
||||
/>
|
||||
</div>
|
||||
{#if !imageError}
|
||||
<!-- Background image -->
|
||||
<div class="m-6 flex h-[calc(100vh-3rem)] overflow-hidden rounded-[40px]">
|
||||
<img
|
||||
src={backgroundImageUrl}
|
||||
class="h-full object-cover {cn(animate && 'animate-bg-zoom')}"
|
||||
alt={m.login_background()}
|
||||
onload={onBackgroundImageLoad}
|
||||
onerror={onBackgroundImageError}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
|
||||
@@ -71,6 +71,11 @@ export default class AppConfigService extends APIService {
|
||||
cachedBackgroundImage.bustCache();
|
||||
};
|
||||
|
||||
deleteBackgroundImage = async () => {
|
||||
await this.api.delete(`/application-images/background`);
|
||||
cachedBackgroundImage.bustCache();
|
||||
};
|
||||
|
||||
deleteDefaultProfilePicture = async () => {
|
||||
await this.api.delete('/application-images/default-profile-picture');
|
||||
cachedDefaultProfilePicture.bustCache();
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
logoDark: File | undefined,
|
||||
logoEmail: File | undefined,
|
||||
defaultProfilePicture: File | null | undefined,
|
||||
backgroundImage: File | undefined,
|
||||
backgroundImage: File | null | undefined,
|
||||
favicon: File | undefined
|
||||
) {
|
||||
const faviconPromise = favicon ? appConfigService.updateFavicon(favicon) : Promise.resolve();
|
||||
@@ -68,9 +68,12 @@
|
||||
? appConfigService.updateDefaultProfilePicture(defaultProfilePicture)
|
||||
: Promise.resolve();
|
||||
|
||||
const backgroundImagePromise = backgroundImage
|
||||
? appConfigService.updateBackgroundImage(backgroundImage)
|
||||
: Promise.resolve();
|
||||
const backgroundImagePromise =
|
||||
backgroundImage === null
|
||||
? appConfigService.deleteBackgroundImage()
|
||||
: backgroundImage
|
||||
? appConfigService.updateBackgroundImage(backgroundImage)
|
||||
: Promise.resolve();
|
||||
|
||||
await Promise.all([
|
||||
lightLogoPromise,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
logoDark: File | undefined,
|
||||
logoEmail: File | undefined,
|
||||
defaultProfilePicture: File | null | undefined,
|
||||
backgroundImage: File | undefined,
|
||||
backgroundImage: File | null | undefined,
|
||||
favicon: File | undefined
|
||||
) => void;
|
||||
} = $props();
|
||||
@@ -26,10 +26,11 @@
|
||||
let logoDark = $state<File | undefined>();
|
||||
let logoEmail = $state<File | undefined>();
|
||||
let defaultProfilePicture = $state<File | null | undefined>();
|
||||
let backgroundImage = $state<File | undefined>();
|
||||
let backgroundImage = $state<File | null | undefined>();
|
||||
let favicon = $state<File | undefined>();
|
||||
|
||||
let defaultProfilePictureSet = $state(true);
|
||||
let backgroundImageSet = $state(true);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-8">
|
||||
@@ -39,7 +40,7 @@
|
||||
label={m.favicon()}
|
||||
bind:image={favicon}
|
||||
imageURL="/api/application-images/favicon"
|
||||
accept="image/x-icon"
|
||||
accept="image/svg+xml, image/png, image/x-icon"
|
||||
/>
|
||||
<ApplicationImage
|
||||
id="logo-light"
|
||||
@@ -77,10 +78,12 @@
|
||||
/>
|
||||
<ApplicationImage
|
||||
id="background-image"
|
||||
imageClass="h-[350px] max-w-[500px]"
|
||||
imageClass="max-h-[350px] max-w-[500px]"
|
||||
label={m.background_image()}
|
||||
isResetable
|
||||
bind:image={backgroundImage}
|
||||
imageURL={cachedBackgroundImage.getUrl()}
|
||||
isImageSet={backgroundImageSet}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
|
||||
Reference in New Issue
Block a user