From 7c2c89d4e26c3daa7d4abc5dc5d1d371568a9c12 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 16 Oct 2025 11:35:15 +0300 Subject: [PATCH] feat(collection/presentation): load themes --- .../collections/presentation/index.tsx | 31 +++++++++++++------ .../collections/presentation/themes.spec.ts | 10 ++++++ .../collections/presentation/themes.ts | 24 +++++++------- 3 files changed, 45 insertions(+), 20 deletions(-) create mode 100644 apps/client/src/widgets/collections/presentation/themes.spec.ts diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 7f40e8923..6c8e530ff 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -2,7 +2,6 @@ import { ViewModeProps } from "../interface"; import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks"; import Reveal from "reveal.js"; import slideBaseStylesheet from "reveal.js/dist/reveal.css?raw"; -import slideThemeStylesheet from "reveal.js/dist/theme/black.css?raw"; import slideCustomStylesheet from "./slidejs.css?raw"; import { buildPresentationModel, PresentationModel, PresentationSlideBaseModel } from "./model"; import ShadowDom from "../../react/ShadowDom"; @@ -10,19 +9,16 @@ import ActionButton from "../../react/ActionButton"; import "./index.css"; import { RefObject } from "preact"; import { openInCurrentNoteContext } from "../../../components/note_context"; -import { useTriliumEvent } from "../../react/hooks"; +import { useNoteLabelWithDefault, useTriliumEvent } from "../../react/hooks"; import { t } from "../../../services/i18n"; - -const stylesheets = [ - slideBaseStylesheet, - slideThemeStylesheet, - slideCustomStylesheet -].map(stylesheet => stylesheet.replace(/:root/g, ":host")); +import { DEFAULT_THEME, loadPresentationTheme } from "./themes"; +import FNote from "../../../entities/fnote"; export default function PresentationView({ note, noteIds }: ViewModeProps<{}>) { const [ presentation, setPresentation ] = useState(); const containerRef = useRef(null); const [ api, setApi ] = useState(); + const stylesheets = usePresentationStylesheets(note); function refresh() { buildPresentationModel(note).then(setPresentation); @@ -36,7 +32,7 @@ export default function PresentationView({ note, noteIds }: ViewModeProps<{}>) { useLayoutEffect(refresh, [ note, noteIds ]); - return presentation && ( + return presentation && stylesheets && ( <> ) { ) } +function usePresentationStylesheets(note: FNote) { + const [ themeName ] = useNoteLabelWithDefault(note, "presentation:theme", DEFAULT_THEME); + const [ stylesheets, setStylesheets ] = useState(); + + useLayoutEffect(() => { + loadPresentationTheme(themeName).then((themeStylesheet) => { + setStylesheets([ + slideBaseStylesheet, + themeStylesheet, + slideCustomStylesheet + ].map(stylesheet => stylesheet.replace(/:root/g, ":host"))); + }); + }, [ themeName ]); + + return stylesheets; +} + function ButtonOverlay({ containerRef, api }: { containerRef: RefObject, api: Reveal.Api | undefined }) { const [ isOverviewActive, setIsOverviewActive ] = useState(false); useEffect(() => { diff --git a/apps/client/src/widgets/collections/presentation/themes.spec.ts b/apps/client/src/widgets/collections/presentation/themes.spec.ts new file mode 100644 index 000000000..854cad190 --- /dev/null +++ b/apps/client/src/widgets/collections/presentation/themes.spec.ts @@ -0,0 +1,10 @@ +import { it, describe } from "vitest"; +import { getPresentationThemes, loadPresentationTheme } from "./themes"; + +describe("Presentation themes", () => { + it("can load all themes", async () => { + const themes = getPresentationThemes(); + + await Promise.all(themes.map(theme => loadPresentationTheme(theme.id))); + }); +}); diff --git a/apps/client/src/widgets/collections/presentation/themes.ts b/apps/client/src/widgets/collections/presentation/themes.ts index dca1b8760..a5422cef5 100644 --- a/apps/client/src/widgets/collections/presentation/themes.ts +++ b/apps/client/src/widgets/collections/presentation/themes.ts @@ -1,3 +1,5 @@ +export const DEFAULT_THEME = "white"; + interface ThemeDefinition { name: string; loadTheme: () => Promise; @@ -10,39 +12,39 @@ const themes: Record = { }, white: { name: "White", - loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + loadTheme: () => import("reveal.js/dist/theme/white.css?raw") }, beige: { name: "Beige", - loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + loadTheme: () => import("reveal.js/dist/theme/beige.css?raw") }, serif: { name: "Serif", - loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + loadTheme: () => import("reveal.js/dist/theme/serif.css?raw") }, simple: { name: "Simple", - loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + loadTheme: () => import("reveal.js/dist/theme/simple.css?raw") }, solarized: { name: "Solarized", - loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + loadTheme: () => import("reveal.js/dist/theme/solarized.css?raw") }, moon: { name: "Moon", - loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + loadTheme: () => import("reveal.js/dist/theme/moon.css?raw") }, dracula: { name: "Dracula", - loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + loadTheme: () => import("reveal.js/dist/theme/dracula.css?raw") }, sky: { name: "Sky", - loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + loadTheme: () => import("reveal.js/dist/theme/sky.css?raw") }, blood: { name: "Blood", - loadTheme: () => import("reveal.js/dist/theme/black.css?raw") + loadTheme: () => import("reveal.js/dist/theme/blood.css?raw") } } as const; @@ -54,8 +56,8 @@ export function getPresentationThemes() { } export async function loadPresentationTheme(name: keyof typeof themes) { - const theme = themes[name]; - if (!theme) return; + let theme = themes[name]; + if (!theme) theme = themes[DEFAULT_THEME]; return (await theme.loadTheme()).default; }