From be45eed125e33e9930572660a034d5f12dc310ce Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Thu, 3 Oct 2024 11:27:31 +0200 Subject: [PATCH] feat!: add ability to set light and dark mode logo --- backend/images/logoDark.svg | 3 ++ backend/images/logoLight.svg | 3 ++ .../bootstrap/application_images_bootstrap.go | 45 +++++++++++++++---- .../controller/app_config_controller.go | 32 +++++++++++-- backend/internal/model/app_config.go | 3 +- .../internal/service/app_config_service.go | 10 ++++- backend/internal/utils/file_util.go | 4 +- frontend/src/app.css | 14 +----- frontend/src/lib/components/logo.svelte | 11 ++++- .../src/lib/services/app-config-service.ts | 11 +++-- .../application-configuration/+page.svelte | 8 ++-- .../application-image.svelte | 14 ++++-- .../update-application-images.svelte | 34 ++++++++++---- .../tests/application-configuration.spec.ts | 9 ++-- 14 files changed, 146 insertions(+), 55 deletions(-) create mode 100644 backend/images/logoDark.svg create mode 100644 backend/images/logoLight.svg diff --git a/backend/images/logoDark.svg b/backend/images/logoDark.svg new file mode 100644 index 0000000..691a485 --- /dev/null +++ b/backend/images/logoDark.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/backend/images/logoLight.svg b/backend/images/logoLight.svg new file mode 100644 index 0000000..f1f4735 --- /dev/null +++ b/backend/images/logoLight.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/backend/internal/bootstrap/application_images_bootstrap.go b/backend/internal/bootstrap/application_images_bootstrap.go index 2f08c9e..556c5fe 100644 --- a/backend/internal/bootstrap/application_images_bootstrap.go +++ b/backend/internal/bootstrap/application_images_bootstrap.go @@ -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], ".") } diff --git a/backend/internal/controller/app_config_controller.go b/backend/internal/controller/app_config_controller.go index 35dc019..c6ee106 100644 --- a/backend/internal/controller/app_config_controller.go +++ b/backend/internal/controller/app_config_controller.go @@ -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) { diff --git a/backend/internal/model/app_config.go b/backend/internal/model/app_config.go index dfa243f..9486520 100644 --- a/backend/internal/model/app_config.go +++ b/backend/internal/model/app_config.go @@ -11,7 +11,8 @@ type AppConfigVariable struct { type AppConfig struct { AppName AppConfigVariable BackgroundImageType AppConfigVariable - LogoImageType AppConfigVariable + LogoLightImageType AppConfigVariable + LogoDarkImageType AppConfigVariable SessionDuration AppConfigVariable EmailEnabled AppConfigVariable diff --git a/backend/internal/service/app_config_service.go b/backend/internal/service/app_config_service.go index e1575c8..b28e951 100644 --- a/backend/internal/service/app_config_service.go +++ b/backend/internal/service/app_config_service.go @@ -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", diff --git a/backend/internal/utils/file_util.go b/backend/internal/utils/file_util.go index 5bf45f4..daa23b4 100644 --- a/backend/internal/utils/file_util.go +++ b/backend/internal/utils/file_util.go @@ -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 diff --git a/frontend/src/app.css b/frontend/src/app.css index ad29ad5..f782a68 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -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; - } - } -} +} \ No newline at end of file diff --git a/frontend/src/lib/components/logo.svelte b/frontend/src/lib/components/logo.svelte index bce3dae..7e6fc2d 100644 --- a/frontend/src/lib/components/logo.svelte +++ b/frontend/src/lib/components/logo.svelte @@ -1 +1,10 @@ -Logo + + +Logo diff --git a/frontend/src/lib/services/app-config-service.ts b/frontend/src/lib/services/app-config-service.ts index 1730cdb..3cad01a 100644 --- a/frontend/src/lib/services/app-config-service.ts +++ b/frontend/src/lib/services/app-config-service.ts @@ -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) { diff --git a/frontend/src/routes/settings/admin/application-configuration/+page.svelte b/frontend/src/routes/settings/admin/application-configuration/+page.svelte index ef0374d..b8a6ad0 100644 --- a/frontend/src/routes/settings/admin/application-configuration/+page.svelte +++ b/frontend/src/routes/settings/admin/application-configuration/+page.svelte @@ -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); } diff --git a/frontend/src/routes/settings/admin/application-configuration/application-image.svelte b/frontend/src/routes/settings/admin/application-configuration/application-image.svelte index 94c5e84..423fbef 100644 --- a/frontend/src/routes/settings/admin/application-configuration/application-image.svelte +++ b/frontend/src/routes/settings/admin/application-configuration/application-image.svelte @@ -11,6 +11,7 @@ image = $bindable(), imageURL, accept = 'image/png, image/jpeg, image/svg+xml', + forceColorScheme, ...restProps }: HTMLAttributes & { id: string; @@ -18,6 +19,7 @@ label: string; image: File | null; imageURL: string; + forceColorScheme?: 'light' | 'dark'; accept?: string; } = $props(); @@ -37,10 +39,16 @@ } -
- +
+ -
+
void; + callback: ( + logoLight: File | null, + logoDark: File | null, + backgroundImage: File | null, + favicon: File | null + ) => void; } = $props(); - let logo = $state(null); + let logoLight = $state(null); + let logoDark = $state(null); let backgroundImage = $state(null); let favicon = $state(null); -
+
- +
diff --git a/frontend/tests/application-configuration.spec.ts b/frontend/tests/application-configuration.spec.ts index cce1174..270013d 100644 --- a/frontend/tests/application-configuration.spec.ts +++ b/frontend/tests/application-configuration.spec.ts @@ -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));