diff --git a/frontend/locales/en.json b/frontend/locales/en.json
index 9f01a108..f24fb428 100644
--- a/frontend/locales/en.json
+++ b/frontend/locales/en.json
@@ -27,12 +27,14 @@
"anErrorHappened": "An error happened",
"apiKeys": "API keys",
"apiKeysSettingsDescription": "Add and revoke API keys for external access to your server",
+ "appDefaultTypography": "Application default typography ({value})",
"appName": "App name",
"appVersion": "App version",
"appearsOn": "Appearances",
"art": "Art",
"artist": "Artist",
"artists": "Artists",
+ "askAgain": "Ask again",
"audio": "Audio",
"auto": "Automatic",
"backdrop": "Backdrop",
@@ -72,6 +74,7 @@
"createKeySuccess": "Successfully created a new API key",
"crew": "Crew",
"criticRating": "Critic rating",
+ "currentAppTypography": "Current application typography ({value})",
"currentPassword": "Current password",
"customRating": "Custom rating",
"dateAdded": "Date added",
@@ -369,6 +372,7 @@
"switchToLightMode": "Switch to light mode",
"syncPlayGroups": "SyncPlay groups",
"syncingSettingsInProgress": "Syncing settingsā¦",
+ "systemTypography": "System typography",
"tagName": "Tag name",
"tagline": "Tagline",
"tags": "Tags",
diff --git a/frontend/src/assets/styles/global.css b/frontend/src/assets/styles/global.css
index 726c5992..cf18c603 100644
--- a/frontend/src/assets/styles/global.css
+++ b/frontend/src/assets/styles/global.css
@@ -1,5 +1,5 @@
* {
- font-family: 'Figtree Variable', sans-serif, system-ui !important;
+ font-family: var(--j-font-family), sans-serif, system-ui !important;
}
html {
diff --git a/frontend/src/components/Playback/SubtitleTrack.vue b/frontend/src/components/Playback/SubtitleTrack.vue
index b7fdf400..64c44ae1 100644
--- a/frontend/src/components/Playback/SubtitleTrack.vue
+++ b/frontend/src/components/Playback/SubtitleTrack.vue
@@ -5,26 +5,32 @@
:srclang="playerElement.currentExternalSubtitleTrack?.srcLang"
:src="playerElement.currentExternalSubtitleTrack?.src">
+
+ {{ $t('subtitlePreviewText') }}
+
- {{ previewText }}
+
+
diff --git a/frontend/src/components/Selectors/FontSelector.vue b/frontend/src/components/Selectors/FontSelector.vue
index 8f507d80..9bc199e6 100644
--- a/frontend/src/components/Selectors/FontSelector.vue
+++ b/frontend/src/components/Selectors/FontSelector.vue
@@ -6,7 +6,6 @@
icon="$warning">
{{ $t('queryLocalFontsNotSupportedWarning') }}
-
{{ $t('enablePermission') }}
+
+ {{ $t('askAgain') }}
+
diff --git a/frontend/src/components/lib/JApp.vue b/frontend/src/components/lib/JApp.vue
index 8ac2d96e..349887a1 100644
--- a/frontend/src/components/lib/JApp.vue
+++ b/frontend/src/components/lib/JApp.vue
@@ -11,6 +11,7 @@
cursor: wait;
--j-color-background: rgb(var(--v-theme-background));
+ --j-font-family: '{{ typography }}';
}
@@ -22,7 +23,20 @@
/**
* TODO: Investigate or propose an RFC to allow style tags inside SFCs
*/
+import { computed } from 'vue';
import { useLoading } from '@/composables/use-loading';
+import { DEFAULT_TYPOGRAPHY } from '@/store';
+import { clientSettings } from '@/store/client-settings';
const { isLoading } = useLoading();
+
+const typography = computed(() => {
+ if (clientSettings.typography === 'system') {
+ return 'system-ui';
+ } else if (clientSettings.typography === 'default') {
+ return DEFAULT_TYPOGRAPHY;
+ } else {
+ return clientSettings.typography;
+ }
+});
diff --git a/frontend/src/composables/use-font.ts b/frontend/src/composables/use-font.ts
deleted file mode 100644
index 2a483a97..00000000
--- a/frontend/src/composables/use-font.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { ref } from 'vue';
-
-const currentFont = ref('');
-
-/**
- * Updates the currentFont reactive reference based on the font family of the document body.
- * It retrieves the computed font style of the body and sets the currentFont to the primary font.
- */
-const updateFont = () => {
- const body = document.body;
- const style = window.getComputedStyle(body);
-
- // Remove fallback fonts and quotes around the font name
- const fontFamily = style.fontFamily.split(', ')[0].replaceAll(/["']/g, '');
-
- currentFont.value = fontFamily;
-};
-
-// Event listener to update the font when the font loading is done
-document.fonts.addEventListener('loadingdone', () => {
- updateFont();
-});
-
-// Initial font retrieval
-updateFont();
-
-/**
- * Provides a reactive reference for the current font.
- *
- * @returns An object containing the `currentFont` reactive reference.
- */
-export function useFont() {
- return { currentFont };
-}
diff --git a/frontend/src/pages/settings/subtitles.vue b/frontend/src/pages/settings/subtitles.vue
index 405c40a4..adca8765 100644
--- a/frontend/src/pages/settings/subtitles.vue
+++ b/frontend/src/pages/settings/subtitles.vue
@@ -1,48 +1,45 @@
- {{ t('subtitles') }}
+ {{ $t('subtitles') }}
+ class="uno-pb-4 uno-pt-0">
-
+
diff --git a/frontend/src/store/client-settings/index.ts b/frontend/src/store/client-settings/index.ts
index 18ed744f..dd7a23d8 100644
--- a/frontend/src/store/client-settings/index.ts
+++ b/frontend/src/store/client-settings/index.ts
@@ -8,6 +8,7 @@ import { remote } from '@/plugins/remote';
import { vuetify } from '@/plugins/vuetify';
import { sealed } from '@/utils/validation';
import { SyncedStore } from '@/store/super/synced-store';
+import type { TypographyChoices } from '@/store';
/**
* == INTERFACES AND TYPES ==
@@ -15,6 +16,7 @@ import { SyncedStore } from '@/store/super/synced-store';
*/
export interface ClientSettingsState {
+ typography: TypographyChoices;
darkMode: 'auto' | boolean;
locale: string;
}
@@ -44,6 +46,14 @@ class ClientSettingsStore extends SyncedStore {
return this._state.locale;
}
+ public get typography() {
+ return this._state.typography;
+ }
+
+ public set typography(newVal: ClientSettingsState['typography']) {
+ this._state.typography = newVal;
+ }
+
public set darkMode(newVal: 'auto' | boolean) {
this._state.darkMode = newVal;
}
@@ -72,6 +82,7 @@ class ClientSettingsStore extends SyncedStore {
public constructor() {
super('clientSettings', {
+ typography: 'default',
darkMode: 'auto',
locale: 'auto'
}, 'localStorage');
diff --git a/frontend/src/store/client-settings/subtitle-settings.ts b/frontend/src/store/client-settings/subtitle-settings.ts
index 9b9887d2..5951ae4e 100644
--- a/frontend/src/store/client-settings/subtitle-settings.ts
+++ b/frontend/src/store/client-settings/subtitle-settings.ts
@@ -2,13 +2,22 @@ import { watch } from 'vue';
import { remote } from '@/plugins/remote';
import { sealed } from '@/utils/validation';
import { SyncedStore } from '@/store/super/synced-store';
+import type { TypographyChoices } from '@/store';
/**
* == INTERFACES AND TYPES ==
*/
export interface SubtitleSettingsState {
- fontFamily: string;
+ /**
+ * default: Default application typography.
+ *
+ * system: System typography
+ *
+ * auto: Selects the current selected typography for the application
+ * @default: auto
+ */
+ fontFamily: 'auto' | TypographyChoices;
fontSize: number;
positionFromBottom: number;
backdrop: boolean;
diff --git a/frontend/src/store/index.ts b/frontend/src/store/index.ts
index a371fd46..b3134723 100644
--- a/frontend/src/store/index.ts
+++ b/frontend/src/store/index.ts
@@ -10,6 +10,16 @@ import { isNil } from '@/utils/validation';
* efficient to reuse those, both in components and TS files.
*/
+export const DEFAULT_TYPOGRAPHY = 'Figtree Variable';
+/**
+ * Type for the different typography choices across the application
+ *
+ * default: Default application typography.
+ *
+ * system: System typography
+ */
+export type TypographyChoices = 'default' | 'system' | (string & {});
+
/**
* == BLURHASH DEFAULTS ==
* By default, 20x20 pixels with a punch of 1 is returned.
diff --git a/frontend/src/store/super/synced-store.ts b/frontend/src/store/super/synced-store.ts
index 5602c956..5559588b 100644
--- a/frontend/src/store/super/synced-store.ts
+++ b/frontend/src/store/super/synced-store.ts
@@ -110,21 +110,19 @@ export abstract class SyncedStore extends CommonStore {
try {
const data = await this._fetchState();
- if (data) {
- for (const watcher of this._pausableWatchers) {
- watcher.pause();
- }
+ for (const watcher of this._pausableWatchers) {
+ watcher.pause();
+ }
- const newState = {
- ...toRaw(this._state),
- ...data
- };
+ const newState = {
+ ...toRaw(this._state),
+ ...data
+ };
- Object.assign(this._state, newState);
+ Object.assign(this._state, newState);
- for (const watcher of this._pausableWatchers) {
- watcher.resume();
- }
+ for (const watcher of this._pausableWatchers) {
+ watcher.resume();
}
} catch {
useSnackbar(i18n.t('failedSyncingUserSettings'), 'error');
@@ -144,12 +142,12 @@ export abstract class SyncedStore extends CommonStore {
if (keys) {
for (const key of keys) {
this._pausableWatchers.push(
- watchPausable(() => this._state[key], this._updateState)
+ watchPausable(() => this._state[key], this._updateState, { deep: true })
);
}
} else {
this._pausableWatchers.push(
- watchPausable(this._state, this._updateState)
+ watchPausable(this._state, this._updateState, { deep: true })
);
}