Skip to content

Commit

Permalink
feat!: add ability to set light and dark mode logo
Browse files Browse the repository at this point in the history
  • Loading branch information
stonith404 committed Oct 3, 2024
1 parent 9e94a43 commit be45eed
Show file tree
Hide file tree
Showing 14 changed files with 146 additions and 55 deletions.
3 changes: 3 additions & 0 deletions backend/images/logoDark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions backend/images/logoLight.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 37 additions & 8 deletions backend/internal/bootstrap/application_images_bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -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], ".")
}
32 changes: 28 additions & 4 deletions backend/internal/controller/app_config_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion backend/internal/model/app_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ type AppConfigVariable struct {
type AppConfig struct {
AppName AppConfigVariable
BackgroundImageType AppConfigVariable
LogoImageType AppConfigVariable
LogoLightImageType AppConfigVariable
LogoDarkImageType AppConfigVariable
SessionDuration AppConfigVariable

EmailEnabled AppConfigVariable
Expand Down
10 changes: 8 additions & 2 deletions backend/internal/service/app_config_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions backend/internal/utils/file_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
Expand Down
14 changes: 1 addition & 13 deletions frontend/src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
}
11 changes: 10 additions & 1 deletion frontend/src/lib/components/logo.svelte
Original file line number Diff line number Diff line change
@@ -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" />
11 changes: 5 additions & 6 deletions frontend/src/lib/services/app-config-service.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
image = $bindable(),
imageURL,
accept = 'image/png, image/jpeg, image/svg+xml',
forceColorScheme,
...restProps
}: HTMLAttributes<HTMLDivElement> & {
id: string;
imageClass: string;
label: string;
image: File | null;
imageURL: string;
forceColorScheme?: 'light' | 'dark';
accept?: string;
} = $props();
Expand All @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -23,21 +29,31 @@
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}
imageURL="/api/application-configuration/background-image"
/>
</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>
9 changes: 6 additions & 3 deletions frontend/tests/application-configuration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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));
Expand Down

0 comments on commit be45eed

Please sign in to comment.