UI/allow logo selection (#4982)

# Description of Changes

- Allow switching between logos in-app using the same section in
settings

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
This commit is contained in:
EthanHealy01
2025-11-25 15:22:14 +00:00
committed by GitHub
parent ae5b1a4b02
commit a6614e1bfb
110 changed files with 651 additions and 189 deletions
@@ -0,0 +1,57 @@
import { describe, expect, test } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
import { LOGO_FOLDER_BY_VARIANT } from '@app/constants/logo';
import type { LogoVariant } from '@app/services/preferencesService';
/**
* Tests that all required logo assets exist for each logo variant.
* This ensures that when useLogoAssets returns paths, those files actually exist.
*/
describe('useLogoAssets - Logo Asset Files', () => {
const publicDir = path.resolve(__dirname, '../../../public');
// All asset files that useLogoAssets references
const requiredAssets = [
'logo-tooltip.svg',
'Firstpage.png',
'favicon.ico',
'logo192.png',
'logo512.png',
'StirlingPDFLogoWhiteText.svg',
'StirlingPDFLogoBlackText.svg',
'StirlingPDFLogoGreyText.svg',
];
const logoVariants: LogoVariant[] = ['modern', 'classic'];
describe.each(logoVariants)('%s logo variant', (variant) => {
const folder = LOGO_FOLDER_BY_VARIANT[variant];
const folderPath = path.join(publicDir, folder);
test(`folder "${folder}" should exist`, () => {
expect(fs.existsSync(folderPath)).toBe(true);
});
test.each(requiredAssets)('should have %s', (assetName) => {
const assetPath = path.join(folderPath, assetName);
expect(
fs.existsSync(assetPath),
`Missing asset: ${folder}/${assetName}`
).toBe(true);
});
});
describe('manifest files', () => {
test('manifest.json should exist for modern variant', () => {
const manifestPath = path.join(publicDir, 'manifest.json');
expect(fs.existsSync(manifestPath)).toBe(true);
});
test('manifest-classic.json should exist for classic variant', () => {
const manifestPath = path.join(publicDir, 'manifest-classic.json');
expect(fs.existsSync(manifestPath)).toBe(true);
});
});
});
+34
View File
@@ -0,0 +1,34 @@
import { useMemo } from 'react';
import { BASE_PATH } from '@app/constants/app';
import { getLogoFolder } from '@app/constants/logo';
import { useLogoVariant } from '@app/hooks/useLogoVariant';
export function useLogoAssets() {
const logoVariant = useLogoVariant();
return useMemo(() => {
const folder = getLogoFolder(logoVariant);
const folderPath = `${BASE_PATH}/${folder}`;
return {
logoVariant,
folder,
folderPath,
getAssetPath: (name: string) => `${folderPath}/${name}`,
tooltipLogo: `${folderPath}/logo-tooltip.svg`,
firstPage: `${folderPath}/Firstpage.png`,
favicon: `${folderPath}/favicon.ico`,
logo192: `${folderPath}/logo192.png`,
logo512: `${folderPath}/logo512.png`,
wordmark: {
white: `${folderPath}/StirlingPDFLogoWhiteText.svg`,
black: `${folderPath}/StirlingPDFLogoBlackText.svg`,
grey: `${folderPath}/StirlingPDFLogoGreyText.svg`,
},
manifestHref: logoVariant === 'classic'
? `${BASE_PATH}/manifest-classic.json`
: `${BASE_PATH}/manifest.json`,
};
}, [logoVariant]);
}
+6 -15
View File
@@ -1,31 +1,22 @@
import { useMemo } from 'react';
import { useAppConfig } from '@app/contexts/AppConfigContext';
import { useMantineColorScheme } from '@mantine/core';
import { BASE_PATH } from '@app/constants/app';
import { useLogoAssets } from '@app/hooks/useLogoAssets';
/**
* Hook to get the correct logo path based on app config (logo style) and theme (light/dark)
*
* Logo styles:
* - classic: branding/old/favicon.svg (classic S logo - default)
* - modern: StirlingPDFLogoNoText{Light|Dark}.svg (minimalist modern design)
* - classic: classic S logo stored in /classic-logo
* - modern: minimalist logo stored in /modern-logo
*
* @returns The path to the appropriate logo SVG file
*/
export function useLogoPath(): string {
const { config } = useAppConfig();
const { colorScheme } = useMantineColorScheme();
const { folderPath } = useLogoAssets();
return useMemo(() => {
const logoStyle = config?.logoStyle || 'classic';
if (logoStyle === 'classic') {
// Classic logo (old favicon) - same for both light and dark modes
return `${BASE_PATH}/branding/old/favicon.svg`;
}
// Modern logo - different for light and dark modes
const themeSuffix = colorScheme === 'dark' ? 'Dark' : 'Light';
return `${BASE_PATH}/branding/StirlingPDFLogoNoText${themeSuffix}.svg`;
}, [config?.logoStyle, colorScheme]);
return `${folderPath}/StirlingPDFLogoNoText${themeSuffix}.svg`;
}, [colorScheme, folderPath]);
}
+18
View File
@@ -0,0 +1,18 @@
import { useMemo } from 'react';
import { usePreferences } from '@app/contexts/PreferencesContext';
import { useAppConfig } from '@app/contexts/AppConfigContext';
import type { LogoVariant } from '@app/services/preferencesService';
import { ensureLogoVariant } from '@app/constants/logo';
export function useLogoVariant(): LogoVariant {
const { preferences } = usePreferences();
const { config } = useAppConfig();
return useMemo(() => {
// Check local storage first, then fall back to server config
const preferenceVariant = preferences.logoVariant;
const configVariant = config?.logoStyle;
return ensureLogoVariant(preferenceVariant ?? configVariant);
}, [config?.logoStyle, preferences.logoVariant]);
}