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 slideCustomStylesheet from "./slidejs.css?raw"; import { buildPresentationModel, PresentationModel, PresentationSlideBaseModel } from "./model"; import ShadowDom from "../../react/ShadowDom"; import ActionButton from "../../react/ActionButton"; import "./index.css"; import { RefObject } from "preact"; import { openInCurrentNoteContext } from "../../../components/note_context"; import { useNoteLabelWithDefault, useTriliumEvent } from "../../react/hooks"; import { t } from "../../../services/i18n"; 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); } useTriliumEvent("entitiesReloaded", ({ loadResults }) => { if (loadResults.getNoteIds().find(noteId => noteIds.includes(noteId))) { refresh(); } }); useLayoutEffect(refresh, [ note, noteIds ]); return presentation && stylesheets && ( <> {stylesheets.map(stylesheet => )} ) } 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(() => { if (!api) return; setIsOverviewActive(api.isOverview()); const onEnabled = () => setIsOverviewActive(true); const onDisabled = () => setIsOverviewActive(false); api.on("overviewshown", onEnabled); api.on("overviewhidden", onDisabled); return () => { api.off("overviewshown", onEnabled); api.off("overviewhidden", onDisabled); }; }, [ api ]); return (
{ const currentSlide = api?.getCurrentSlide(); const noteId = getNoteIdFromSlide(currentSlide); if (noteId) { openInCurrentNoteContext(e, noteId); } }} /> api?.toggleOverview()} /> containerRef.current?.requestFullscreen()} />
) } function Presentation({ presentation, setApi } : { presentation: PresentationModel, setApi: (api: Reveal.Api | undefined) => void }) { const containerRef = useRef(null); const [revealApi, setRevealApi] = useState(); useEffect(() => { if (!containerRef.current) return; const api = new Reveal(containerRef.current, { transition: "slide", embedded: true, keyboardCondition(event) { // Full-screen requests sometimes fail, we rely on the UI button instead. if (event.key === "f") { return false; } return true; }, }); api.initialize().then(() => { setRevealApi(api); setApi(api); }); return () => { api.destroy(); setRevealApi(undefined); setApi(undefined); } }, []); useEffect(() => { revealApi?.sync(); }, [ presentation, revealApi ]); return (
{presentation.slides?.map(slide => { if (!slide.verticalSlides) { return } else { return (
{slide.verticalSlides.map(slide => )}
); } })}
) } function Slide({ slide }: { slide: PresentationSlideBaseModel }) { return (
); } function getNoteIdFromSlide(slide: HTMLElement | undefined) { if (!slide) return; return slide.dataset.noteId; }