diff --git a/components/object/preview-modal.tsx b/components/object/preview-modal.tsx index ade6621a..0dc8bbcd 100644 --- a/components/object/preview-modal.tsx +++ b/components/object/preview-modal.tsx @@ -42,6 +42,15 @@ interface ObjectPreviewModalProps { } | null } +type FullscreenDocument = Document & { + webkitExitFullscreen?: () => Promise | void + webkitFullscreenElement?: Element | null +} + +type FullscreenElement = HTMLElement & { + webkitRequestFullscreen?: () => Promise | void +} + function normalizeContentType(contentType: string) { return contentType.split(";")[0]?.trim().toLowerCase() ?? "" } @@ -78,6 +87,34 @@ function isParquetPreview(contentType: string, objectKey: string) { return PARQUET_EXTENSIONS.some((ext) => keyLower.endsWith(ext)) } +function getFullscreenElement(doc: FullscreenDocument): Element | null { + return doc.fullscreenElement ?? doc.webkitFullscreenElement ?? null +} + +function exitFullscreen(doc: FullscreenDocument): Promise { + if (typeof doc.exitFullscreen === "function") { + return Promise.resolve(doc.exitFullscreen()).then(() => undefined) + } + + if (typeof doc.webkitExitFullscreen === "function") { + return Promise.resolve(doc.webkitExitFullscreen()).then(() => undefined) + } + + return Promise.resolve() +} + +function requestFullscreen(element: FullscreenElement): Promise { + if (typeof element.requestFullscreen === "function") { + return Promise.resolve(element.requestFullscreen()).then(() => undefined) + } + + if (typeof element.webkitRequestFullscreen === "function") { + return Promise.resolve(element.webkitRequestFullscreen()).then(() => undefined) + } + + return Promise.resolve() +} + export function ObjectPreviewModal({ show, onShowChange, object }: ObjectPreviewModalProps) { const { t } = useTranslation() const [textContent, setTextContent] = React.useState("") @@ -146,7 +183,7 @@ export function ObjectPreviewModal({ show, onShowChange, object }: ObjectPreview React.useEffect(() => { const handleFullscreenChange = () => { - setIsImageFullscreen(document.fullscreenElement === imagePreviewRef.current) + setIsImageFullscreen(getFullscreenElement(document as FullscreenDocument) === imagePreviewRef.current) } document.addEventListener("fullscreenchange", handleFullscreenChange) @@ -156,8 +193,10 @@ export function ObjectPreviewModal({ show, onShowChange, object }: ObjectPreview }, []) React.useEffect(() => { - if (!show && document.fullscreenElement === imagePreviewRef.current) { - void document.exitFullscreen().catch(() => {}) + const fullscreenDocument = document as FullscreenDocument + + if (!show && getFullscreenElement(fullscreenDocument) === imagePreviewRef.current) { + void exitFullscreen(fullscreenDocument).catch(() => {}) } if (!show) { setIsImageFullscreen(false) @@ -184,20 +223,21 @@ export function ObjectPreviewModal({ show, onShowChange, object }: ObjectPreview }, [imageNaturalSize]) const toggleImageFullscreen = React.useCallback(() => { - const container = imagePreviewRef.current + const fullscreenDocument = document as FullscreenDocument + const container = imagePreviewRef.current as FullscreenElement | null if (!container) return - if (document.fullscreenElement === container) { - void document.exitFullscreen().catch(() => {}) + if (getFullscreenElement(fullscreenDocument) === container) { + void exitFullscreen(fullscreenDocument).catch(() => {}) return } - if (document.fullscreenElement) { - void document.exitFullscreen().catch(() => {}) + if (getFullscreenElement(fullscreenDocument)) { + void exitFullscreen(fullscreenDocument).catch(() => {}) return } - void container.requestFullscreen().catch(() => {}) + void requestFullscreen(container).catch(() => {}) }, []) React.useLayoutEffect(() => { diff --git a/tests/lib/object-preview-source.test.js b/tests/lib/object-preview-source.test.js new file mode 100644 index 00000000..a9fe32ed --- /dev/null +++ b/tests/lib/object-preview-source.test.js @@ -0,0 +1,15 @@ +import test from "node:test" +import assert from "node:assert/strict" +import fs from "node:fs" + +test("object preview modal falls back when standard fullscreen APIs are unavailable", () => { + const source = fs.readFileSync("components/object/preview-modal.tsx", "utf8") + + assert.equal(source.includes("webkitExitFullscreen"), true) + assert.equal(source.includes("webkitRequestFullscreen"), true) + assert.equal(source.includes("getFullscreenElement(document as FullscreenDocument)"), true) + assert.equal(source.includes("void exitFullscreen(fullscreenDocument).catch(() => {})"), true) + assert.equal(source.includes("void requestFullscreen(container).catch(() => {})"), true) + assert.equal(source.includes("void document.exitFullscreen().catch(() => {})"), false) + assert.equal(source.includes("void container.requestFullscreen().catch(() => {})"), false) +})