mirror of
https://github.com/pocket-id/pocket-id.git
synced 2025-12-09 22:52:58 +03:00
feat!: add ability to set light and dark mode logo
This commit is contained in:
3
backend/images/logoDark.svg
Normal file
3
backend/images/logoDark.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="a" viewBox="0 0 1015 1015">
|
||||
<path fill="white" d="M506.6,0c209.52,0,379.98,170.45,379.98,379.96,0,82.33-25.9,160.68-74.91,226.54-48.04,64.59-113.78,111.51-190.13,135.71l-21.1,6.7-50.29-248.04,13.91-6.73c45.41-21.95,74.76-68.71,74.76-119.11,0-72.91-59.31-132.23-132.21-132.23s-132.23,59.32-132.23,132.23c0,50.4,29.36,97.16,74.77,119.11l13.65,6.61-81.01,499.24h-226.36V0h351.18Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 434 B |
3
backend/images/logoLight.svg
Normal file
3
backend/images/logoLight.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="a" viewBox="0 0 1015 1015">
|
||||
<path fill="black" d="M506.6,0c209.52,0,379.98,170.45,379.98,379.96,0,82.33-25.9,160.68-74.91,226.54-48.04,64.59-113.78,111.51-190.13,135.71l-21.1,6.7-50.29-248.04,13.91-6.73c45.41-21.95,74.76-68.71,74.76-119.11,0-72.91-59.31-132.23-132.21-132.23s-132.23,59.32-132.23,132.23c0,50.4,29.36,97.16,74.77,119.11l13.65,6.61-81.01,499.24h-226.36V0h351.18Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 434 B |
@@ -5,24 +5,53 @@ import (
|
||||
"github.com/stonith404/pocket-id/backend/internal/utils"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// initApplicationImages copies the images from the images directory to the application-images directory
|
||||
func initApplicationImages() {
|
||||
dirPath := common.EnvConfig.UploadPath + "/application-images"
|
||||
|
||||
files, err := os.ReadDir(dirPath)
|
||||
sourceFiles, err := os.ReadDir("./images")
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
log.Fatalf("Error reading directory: %v", err)
|
||||
}
|
||||
|
||||
// Skip if files already exist
|
||||
if len(files) > 1 {
|
||||
return
|
||||
destinationFiles, err := os.ReadDir(dirPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
log.Fatalf("Error reading directory: %v", err)
|
||||
}
|
||||
|
||||
// Copy files from source to destination
|
||||
err = utils.CopyDirectory("./images", dirPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Error copying directory: %v", err)
|
||||
// Copy images from the images directory to the application-images directory if they don't already exist
|
||||
for _, sourceFile := range sourceFiles {
|
||||
if sourceFile.IsDir() || imageAlreadyExists(sourceFile.Name(), destinationFiles) {
|
||||
continue
|
||||
}
|
||||
srcFilePath := "./images/" + sourceFile.Name()
|
||||
destFilePath := dirPath + "/" + sourceFile.Name()
|
||||
|
||||
err := utils.CopyFile(srcFilePath, destFilePath)
|
||||
if err != nil {
|
||||
log.Fatalf("Error copying file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func imageAlreadyExists(fileName string, destinationFiles []os.DirEntry) bool {
|
||||
for _, destinationFile := range destinationFiles {
|
||||
sourceFileWithoutExtension := getImageNameWithoutExtension(fileName)
|
||||
destinationFileWithoutExtension := getImageNameWithoutExtension(destinationFile.Name())
|
||||
|
||||
if sourceFileWithoutExtension == destinationFileWithoutExtension {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func getImageNameWithoutExtension(fileName string) string {
|
||||
splitted := strings.Split(fileName, ".")
|
||||
return strings.Join(splitted[:len(splitted)-1], ".")
|
||||
}
|
||||
|
||||
@@ -91,8 +91,20 @@ func (acc *AppConfigController) updateAppConfigHandler(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (acc *AppConfigController) getLogoHandler(c *gin.Context) {
|
||||
imageType := acc.appConfigService.DbConfig.LogoImageType.Value
|
||||
acc.getImage(c, "logo", imageType)
|
||||
lightLogo := c.DefaultQuery("light", "true") == "true"
|
||||
|
||||
var imageName string
|
||||
var imageType string
|
||||
|
||||
if lightLogo {
|
||||
imageName = "logoLight"
|
||||
imageType = acc.appConfigService.DbConfig.LogoLightImageType.Value
|
||||
} else {
|
||||
imageName = "logoDark"
|
||||
imageType = acc.appConfigService.DbConfig.LogoDarkImageType.Value
|
||||
}
|
||||
|
||||
acc.getImage(c, imageName, imageType)
|
||||
}
|
||||
|
||||
func (acc *AppConfigController) getFaviconHandler(c *gin.Context) {
|
||||
@@ -105,8 +117,20 @@ func (acc *AppConfigController) getBackgroundImageHandler(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (acc *AppConfigController) updateLogoHandler(c *gin.Context) {
|
||||
imageType := acc.appConfigService.DbConfig.LogoImageType.Value
|
||||
acc.updateImage(c, "logo", imageType)
|
||||
lightLogo := c.DefaultQuery("light", "true") == "true"
|
||||
|
||||
var imageName string
|
||||
var imageType string
|
||||
|
||||
if lightLogo {
|
||||
imageName = "logoLight"
|
||||
imageType = acc.appConfigService.DbConfig.LogoLightImageType.Value
|
||||
} else {
|
||||
imageName = "logoDark"
|
||||
imageType = acc.appConfigService.DbConfig.LogoDarkImageType.Value
|
||||
}
|
||||
|
||||
acc.updateImage(c, imageName, imageType)
|
||||
}
|
||||
|
||||
func (acc *AppConfigController) updateFaviconHandler(c *gin.Context) {
|
||||
|
||||
@@ -11,7 +11,8 @@ type AppConfigVariable struct {
|
||||
type AppConfig struct {
|
||||
AppName AppConfigVariable
|
||||
BackgroundImageType AppConfigVariable
|
||||
LogoImageType AppConfigVariable
|
||||
LogoLightImageType AppConfigVariable
|
||||
LogoDarkImageType AppConfigVariable
|
||||
SessionDuration AppConfigVariable
|
||||
|
||||
EmailEnabled AppConfigVariable
|
||||
|
||||
@@ -47,8 +47,14 @@ var defaultDbConfig = model.AppConfig{
|
||||
IsInternal: true,
|
||||
Value: "jpg",
|
||||
},
|
||||
LogoImageType: model.AppConfigVariable{
|
||||
Key: "logoImageType",
|
||||
LogoLightImageType: model.AppConfigVariable{
|
||||
Key: "logoLightImageType",
|
||||
Type: "string",
|
||||
IsInternal: true,
|
||||
Value: "svg",
|
||||
},
|
||||
LogoDarkImageType: model.AppConfigVariable{
|
||||
Key: "logoDarkImageType",
|
||||
Type: "string",
|
||||
IsInternal: true,
|
||||
Value: "svg",
|
||||
|
||||
@@ -38,7 +38,7 @@ func CopyDirectory(srcDir, destDir string) error {
|
||||
srcFilePath := filepath.Join(srcDir, file.Name())
|
||||
destFilePath := filepath.Join(destDir, file.Name())
|
||||
|
||||
err := copyFile(srcFilePath, destFilePath)
|
||||
err := CopyFile(srcFilePath, destFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -47,7 +47,7 @@ func CopyDirectory(srcDir, destDir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFile(srcFilePath, destFilePath string) error {
|
||||
func CopyFile(srcFilePath, destFilePath string) error {
|
||||
srcFile, err := os.Open(srcFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -97,16 +97,4 @@
|
||||
font-weight: 700;
|
||||
src: url('/fonts/PlayfairDisplay-Bold.woff') format('woff');
|
||||
}
|
||||
}
|
||||
@layer components {
|
||||
.application-images-grid {
|
||||
@apply flex flex-wrap justify-between gap-x-5 gap-y-8;
|
||||
}
|
||||
|
||||
@media (max-width: 1127px) {
|
||||
.application-images-grid {
|
||||
justify-content: flex-start;
|
||||
@apply gap-x-20;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,10 @@
|
||||
<img class={$$restProps.class} src="/api/application-configuration/logo" alt="Logo" />
|
||||
<script lang="ts">
|
||||
import { mode } from 'mode-watcher';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let { ...props }: HTMLAttributes<HTMLImageElement> = $props();
|
||||
|
||||
const isDarkMode = $derived($mode === 'dark');
|
||||
</script>
|
||||
|
||||
<img {...props} src="/api/application-configuration/logo?light={!isDarkMode}" alt="Logo" />
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import type {
|
||||
AllAppConfig,
|
||||
AppConfigRawResponse
|
||||
} from '$lib/types/application-configuration';
|
||||
import type { AllAppConfig, AppConfigRawResponse } from '$lib/types/application-configuration';
|
||||
import APIService from './api-service';
|
||||
|
||||
export default class AppConfigService extends APIService {
|
||||
@@ -33,11 +30,13 @@ export default class AppConfigService extends APIService {
|
||||
await this.api.put(`/application-configuration/favicon`, formData);
|
||||
}
|
||||
|
||||
async updateLogo(logo: File) {
|
||||
async updateLogo(logo: File, light = true) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', logo!);
|
||||
|
||||
await this.api.put(`/application-configuration/logo`, formData);
|
||||
await this.api.put(`/application-configuration/logo`, formData, {
|
||||
params: { light }
|
||||
});
|
||||
}
|
||||
|
||||
async updateBackgroundImage(backgroundImage: File) {
|
||||
|
||||
@@ -28,17 +28,19 @@
|
||||
}
|
||||
|
||||
async function updateImages(
|
||||
logo: File | null,
|
||||
logoLight: File | null,
|
||||
logoDark: File | null,
|
||||
backgroundImage: File | null,
|
||||
favicon: File | null
|
||||
) {
|
||||
const faviconPromise = favicon ? appConfigService.updateFavicon(favicon) : Promise.resolve();
|
||||
const logoPromise = logo ? appConfigService.updateLogo(logo) : Promise.resolve();
|
||||
const lightLogoPromise = logoLight ? appConfigService.updateLogo(logoLight, true) : Promise.resolve();
|
||||
const darkLogoPromise = logoDark ? appConfigService.updateLogo(logoDark, false) : Promise.resolve();
|
||||
const backgroundImagePromise = backgroundImage
|
||||
? appConfigService.updateBackgroundImage(backgroundImage)
|
||||
: Promise.resolve();
|
||||
|
||||
await Promise.all([logoPromise, backgroundImagePromise, faviconPromise])
|
||||
await Promise.all([lightLogoPromise, darkLogoPromise, backgroundImagePromise, faviconPromise])
|
||||
.then(() => toast.success('Images updated successfully'))
|
||||
.catch(axiosErrorToast);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
image = $bindable(),
|
||||
imageURL,
|
||||
accept = 'image/png, image/jpeg, image/svg+xml',
|
||||
forceColorScheme,
|
||||
...restProps
|
||||
}: HTMLAttributes<HTMLDivElement> & {
|
||||
id: string;
|
||||
@@ -18,6 +19,7 @@
|
||||
label: string;
|
||||
image: File | null;
|
||||
imageURL: string;
|
||||
forceColorScheme?: 'light' | 'dark';
|
||||
accept?: string;
|
||||
} = $props();
|
||||
|
||||
@@ -37,10 +39,16 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div {...restProps}>
|
||||
<Label for={id}>{label}</Label>
|
||||
<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="bg-muted group relative flex items-center rounded">
|
||||
<div
|
||||
class="{forceColorScheme === 'light'
|
||||
? 'bg-[#F1F1F5]'
|
||||
: forceColorScheme === 'dark'
|
||||
? 'bg-[#27272A]'
|
||||
: 'bg-muted'} group relative flex items-center rounded"
|
||||
>
|
||||
<img
|
||||
class={cn(
|
||||
'h-full w-full rounded object-cover p-3 transition-opacity duration-200 group-hover:opacity-10',
|
||||
|
||||
@@ -5,15 +5,21 @@
|
||||
let {
|
||||
callback
|
||||
}: {
|
||||
callback: (logo: File | null, backgroundImage: File | null, favicon: File | null) => void;
|
||||
callback: (
|
||||
logoLight: File | null,
|
||||
logoDark: File | null,
|
||||
backgroundImage: File | null,
|
||||
favicon: File | null
|
||||
) => void;
|
||||
} = $props();
|
||||
|
||||
let logo = $state<File | null>(null);
|
||||
let logoLight = $state<File | null>(null);
|
||||
let logoDark = $state<File | null>(null);
|
||||
let backgroundImage = $state<File | null>(null);
|
||||
let favicon = $state<File | null>(null);
|
||||
</script>
|
||||
|
||||
<div class="application-images-grid">
|
||||
<div class="flex flex-col gap-8">
|
||||
<ApplicationImage
|
||||
id="favicon"
|
||||
imageClass="h-14 w-14 p-2"
|
||||
@@ -23,15 +29,23 @@
|
||||
accept="image/x-icon"
|
||||
/>
|
||||
<ApplicationImage
|
||||
id="logo"
|
||||
id="logo-light"
|
||||
imageClass="h-32 w-32"
|
||||
label="Logo"
|
||||
bind:image={logo}
|
||||
imageURL="/api/application-configuration/logo"
|
||||
label="Light Mode Logo"
|
||||
bind:image={logoLight}
|
||||
imageURL="/api/application-configuration/logo?light=true"
|
||||
forceColorScheme="light"
|
||||
/>
|
||||
<ApplicationImage
|
||||
id="logo-dark"
|
||||
imageClass="h-32 w-32"
|
||||
label="Dark Mode Logo"
|
||||
bind:image={logoDark}
|
||||
imageURL="/api/application-configuration/logo?light=false"
|
||||
forceColorScheme="dark"
|
||||
/>
|
||||
<ApplicationImage
|
||||
id="background-image"
|
||||
class="basis-full lg:basis-auto"
|
||||
imageClass="h-[350px] max-w-[500px]"
|
||||
label="Background Image"
|
||||
bind:image={backgroundImage}
|
||||
@@ -39,5 +53,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<Button class="mt-5" onclick={() => callback(logo, backgroundImage, favicon)}>Save</Button>
|
||||
<Button class="mt-5" onclick={() => callback(logoLight, logoDark, backgroundImage, favicon)}
|
||||
>Save</Button
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -52,7 +52,8 @@ test('Update application images', async ({ page }) => {
|
||||
await page.goto('/settings/admin/application-configuration');
|
||||
|
||||
await page.getByLabel('Favicon').setInputFiles('tests/assets/w3-schools-favicon.ico');
|
||||
await page.getByLabel('Logo').setInputFiles('tests/assets/pingvin-share-logo.png');
|
||||
await page.getByLabel('Light Mode Logo').setInputFiles('tests/assets/pingvin-share-logo.png');
|
||||
await page.getByLabel('Dark Mode Logo').setInputFiles('tests/assets/nextcloud-logo.png');
|
||||
await page.getByLabel('Background Image').setInputFiles('tests/assets/clouds.jpg');
|
||||
await page.getByRole('button', { name: 'Save' }).nth(1).click();
|
||||
|
||||
@@ -62,9 +63,11 @@ test('Update application images', async ({ page }) => {
|
||||
.get('/api/application-configuration/favicon')
|
||||
.then((res) => expect.soft(res.status()).toBe(200));
|
||||
await page.request
|
||||
.get('/api/application-configuration/logo')
|
||||
.get('/api/application-configuration/logo?light=true')
|
||||
.then((res) => expect.soft(res.status()).toBe(200));
|
||||
await page.request
|
||||
.get('/api/application-configuration/logo?light=false')
|
||||
.then((res) => expect.soft(res.status()).toBe(200));
|
||||
|
||||
await page.request
|
||||
.get('/api/application-configuration/background-image')
|
||||
.then((res) => expect.soft(res.status()).toBe(200));
|
||||
|
||||
Reference in New Issue
Block a user