@brftech/filex-core 0.43.2 → 0.44.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ArchiveViewer-D_tJxJhJ.js +236 -0
- package/dist/ArchiveViewer-D_tJxJhJ.js.map +1 -0
- package/dist/{CsvViewer-I0AceHh8.js → CsvViewer-B95fArq0.js} +2 -2
- package/dist/{CsvViewer-I0AceHh8.js.map → CsvViewer-B95fArq0.js.map} +1 -1
- package/dist/{DrawioViewer-B2vl10hY.js → DrawioViewer-BAaPMTge.js} +2 -2
- package/dist/{DrawioViewer-B2vl10hY.js.map → DrawioViewer-BAaPMTge.js.map} +1 -1
- package/dist/{EpubViewer-L3rxrx5i.js → EpubViewer-B-B7bRW7.js} +2 -2
- package/dist/{EpubViewer-L3rxrx5i.js.map → EpubViewer-B-B7bRW7.js.map} +1 -1
- package/dist/{IpynbViewer-Do8n0RzJ.js → IpynbViewer-COOTyOcu.js} +2 -2
- package/dist/{IpynbViewer-Do8n0RzJ.js.map → IpynbViewer-COOTyOcu.js.map} +1 -1
- package/dist/{MermaidViewer-0JqH6k0f.js → MermaidViewer-CjVCmR3s.js} +2 -2
- package/dist/{MermaidViewer-0JqH6k0f.js.map → MermaidViewer-CjVCmR3s.js.map} +1 -1
- package/dist/{PsdViewer-BnI-NDr8.js → PsdViewer-xsIYL9Gu.js} +2 -2
- package/dist/{PsdViewer-BnI-NDr8.js.map → PsdViewer-xsIYL9Gu.js.map} +1 -1
- package/dist/{TiffViewer-CrLDXI_f.js → TiffViewer-CtwnTxqL.js} +2 -2
- package/dist/{TiffViewer-CrLDXI_f.js.map → TiffViewer-CtwnTxqL.js.map} +1 -1
- package/dist/{Viewer3D-CpB1K0-0.js → Viewer3D-xYMwndEy.js} +2 -2
- package/dist/{Viewer3D-CpB1K0-0.js.map → Viewer3D-xYMwndEy.js.map} +1 -1
- package/dist/filex-core.js +372 -366
- package/dist/filex-core.umd.cjs +62 -62
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/{index-D9_EBsqF.js → index-BBToAjwL.js} +16563 -15727
- package/dist/index-BBToAjwL.js.map +1 -0
- package/dist/index.d.ts +185 -10
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/FileExplorer.vue +363 -23
- package/src/components/ContextMenu.vue +17 -0
- package/src/components/HomeView.vue +14 -4
- package/src/components/InspectorPanel.vue +9 -3
- package/src/components/ListView.vue +6 -3
- package/src/components/OperationsCenter.vue +6 -0
- package/src/components/PendingOpsTray.vue +5 -3
- package/src/composables/useFileApi.ts +36 -6
- package/src/composables/useLocale.ts +26 -0
- package/src/composables/useOperations.ts +7 -1
- package/src/composables/usePendingOps.ts +11 -7
- package/src/index.ts +15 -0
- package/src/lib/actionIcons.ts +6 -0
- package/src/lib/archiveFormats.ts +12 -0
- package/src/lib/archivePreviewCache.ts +61 -0
- package/src/lib/catalogCoverage.ts +137 -0
- package/src/locales/en.ts +67 -0
- package/src/locales/tr.ts +65 -0
- package/src/modals/ArchiveCreateModal.vue +192 -0
- package/src/modals/ArchiveExtractModal.vue +57 -0
- package/src/modals/ArchivePasswordModal.vue +62 -0
- package/src/modals/PreviewModal.vue +15 -1
- package/src/styles/base.css +47 -2
- package/src/types/ExplorerConfig.ts +9 -0
- package/src/types/FileNode.ts +19 -0
- package/src/viewers/ArchiveViewer.vue +145 -40
- package/dist/ArchiveViewer-Bn-pwgX8.js +0 -149
- package/dist/ArchiveViewer-Bn-pwgX8.js.map +0 -1
- package/dist/index-D9_EBsqF.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IpynbViewer-Do8n0RzJ.js","sources":["../src/viewers/IpynbViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { sayFailure } from '../lib/errorWords';\n/**\n * IpynbViewer — Jupyter notebook (.ipynb JSON) renderer.\n *\n * Pure-JS render — no peer dependency required:\n * - code cells → `<pre><code>` (highlight.js when available)\n * - markdown cells → markdown-it when available, plain `<pre>` otherwise\n * - output cells → text (stream / display_data 'text/plain'),\n * images (display_data 'image/png' base64),\n * HTML (display_data 'text/html', sanitized via\n * element-only insertion — no script tags)\n *\n * Source list / output count counters at the top so a 200-cell\n * notebook doesn't surprise the user.\n */\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';\nimport { actionIconSvg } from '../lib/actionIcons'; /* ikon:emoji */\nimport { fileIconTile } from '../lib/fileIcons'; /* ikon:emoji */\nimport { fetchViewerText } from '../composables/useViewerFetch';\nimport { ensureHighlight } from '../composables/useMonacoLoader';\n\nconst props = defineProps<{\n url: string;\n ext: string;\n t?: (key: string) => string;\n authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;\n authCredentials?: RequestCredentials;\n}>();\n\ninterface Cell {\n cell_type: 'code' | 'markdown' | 'raw' | string;\n source: string | string[];\n outputs?: any[];\n execution_count?: number | null;\n metadata?: Record<string, unknown>;\n}\n\ninterface Notebook {\n cells: Cell[];\n metadata?: { kernelspec?: { language?: string; name?: string } };\n}\n\nconst cells = ref<Cell[]>([]);\nconst language = ref<string>('python');\nconst error = ref<string | null>(null);\nconst loading = ref(true);\nconst renderedSources = ref<Map<number, string>>(new Map());\nconst renderedMarkdown = ref<Map<number, string>>(new Map());\n\nlet renderToken = 0;\nlet highlight: any = null;\nlet mdRenderer: any = null;\n\nasync function ensureMd(): Promise<any | null> {\n if (mdRenderer !== null) return mdRenderer || null;\n try {\n const mod = await import(/* @vite-ignore */ 'markdown-it');\n const Md = (mod as any).default ?? mod;\n mdRenderer = new Md({ html: false, linkify: true, breaks: true });\n return mdRenderer;\n } catch {\n mdRenderer = false;\n return null;\n }\n}\n\nfunction joinSource(source: string | string[]): string {\n return Array.isArray(source) ? source.join('') : (source ?? '');\n}\n\nasync function load(): Promise<void> {\n loading.value = true;\n error.value = null;\n cells.value = [];\n renderedSources.value = new Map();\n renderedMarkdown.value = new Map();\n const myToken = ++renderToken;\n\n let raw: string;\n try {\n raw = await fetchViewerText({\n url: props.url,\n headers: (await props.authHeaders?.()) ?? {},\n credentials: props.authCredentials,\n });\n } catch (err) {\n error.value = sayFailure(err, tt('viewer.failed_to_load', 'Failed to load file'), { t: props.t }).text;\n loading.value = false;\n return;\n }\n\n if (myToken !== renderToken) return;\n\n let nb: Notebook;\n try {\n nb = JSON.parse(raw);\n } catch (err) {\n error.value = 'Invalid notebook JSON';\n loading.value = false;\n return;\n }\n cells.value = Array.isArray(nb.cells) ? nb.cells : [];\n language.value = nb.metadata?.kernelspec?.language || 'python';\n\n // Best-effort hljs highlight per code cell.\n highlight = (await ensureHighlight()) as any;\n if (myToken !== renderToken) return;\n if (highlight) {\n const hljs = highlight.default ?? highlight;\n const langOk = hljs.getLanguage?.(language.value) ? language.value : null;\n cells.value.forEach((cell, idx) => {\n if (cell.cell_type === 'code') {\n const text = joinSource(cell.source);\n try {\n const result = langOk\n ? hljs.highlight(text, { language: langOk, ignoreIllegals: true })\n : hljs.highlightAuto(text);\n renderedSources.value.set(idx, result.value);\n } catch {\n /* leave unrendered */\n }\n }\n });\n }\n\n const md = await ensureMd();\n if (myToken !== renderToken) return;\n if (md) {\n cells.value.forEach((cell, idx) => {\n if (cell.cell_type === 'markdown') {\n try {\n renderedMarkdown.value.set(idx, md.render(joinSource(cell.source)));\n } catch {\n /* leave plain */\n }\n }\n });\n }\n\n loading.value = false;\n}\n\nfunction outputText(out: any): string | null {\n if (!out) return null;\n if (out.output_type === 'stream') {\n return joinSource(out.text);\n }\n if (out.output_type === 'error') {\n return [out.ename, out.evalue, ...(out.traceback || [])]\n .filter(Boolean)\n .join('\\n');\n }\n if (out.output_type === 'execute_result' || out.output_type === 'display_data') {\n if (out.data?.['text/plain']) {\n return joinSource(out.data['text/plain']);\n }\n }\n return null;\n}\n\nfunction outputImage(out: any): string | null {\n if (!out?.data) return null;\n if (out.data['image/png']) return `data:image/png;base64,${out.data['image/png']}`;\n if (out.data['image/jpeg']) return `data:image/jpeg;base64,${out.data['image/jpeg']}`;\n if (out.data['image/svg+xml']) {\n const svg = joinSource(out.data['image/svg+xml']);\n return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;\n }\n return null;\n}\n\nfunction outputHtml(out: any): string | null {\n if (!out?.data) return null;\n if (out.data['text/html']) {\n const html = joinSource(out.data['text/html']);\n // Strip <script> for safety — outputs from arbitrary notebooks.\n return html.replace(/<script[\\s\\S]*?<\\/script>/gi, '');\n }\n return null;\n}\n\nonMounted(load);\nonBeforeUnmount(() => {\n renderToken++;\n});\nwatch(() => props.url, load);\n\nconst stats = computed(() => {\n const codeCount = cells.value.filter((c) => c.cell_type === 'code').length;\n const mdCount = cells.value.filter((c) => c.cell_type === 'markdown').length;\n return { code: codeCount, md: mdCount, total: cells.value.length };\n});\n\nfunction tt(key: string, fallback: string): string {\n return props.t ? props.t(key) : fallback;\n}\n\n/* === ikon:emoji — the fallback screen's mark ==========================\n * Every viewer opened its \"cannot show this\" / \"still loading\" screen with a\n * 48px colour emoji, one per format, each from whatever emoji font the OS\n * shipped. The format mark is `lib/fileIcons`'s tile — the SAME tile the row\n * the person just clicked is wearing, so the fallback is recognisably about\n * that file — and \"loading\" is the stroked ring, spun by CSS, because no\n * still picture can say \"still going\". */\nconst typeTile = computed(() => fileIconTile({ type: 'file', extension: props.ext }));\n</script>\n\n<template>\n <div class=\"filex-viewer-ipynb\">\n <div class=\"filex-viewer-ipynb__pane\">\n <div v-if=\"error\" class=\"filex-viewer-fallback\">\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span class=\"filex-viewer-fallback__icon\" aria-hidden=\"true\" v-html=\"typeTile\"></span>\n <p>{{ error }}</p>\n </div>\n <div v-else-if=\"loading\" class=\"filex-viewer-fallback\">\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span\n class=\"filex-viewer-fallback__icon filex-viewer-fallback__icon--spin\"\n aria-hidden=\"true\"\n v-html=\"actionIconSvg('progress')\"\n ></span>\n <p>{{ tt('viewer.loading', 'Loading…') }}</p>\n </div>\n <div v-else>\n <div\n v-for=\"(cell, idx) in cells\"\n :key=\"idx\"\n class=\"filex-viewer-ipynb__cell\"\n :data-type=\"cell.cell_type\"\n >\n <template v-if=\"cell.cell_type === 'code'\">\n <div class=\"filex-viewer-ipynb__exec\">\n In [{{ cell.execution_count ?? ' ' }}]:\n </div>\n <pre class=\"filex-viewer-ipynb__src hljs\"><code\n v-if=\"renderedSources.get(idx)\"\n v-html=\"renderedSources.get(idx)\"\n /><code v-else>{{ joinSource(cell.source) }}</code></pre>\n <div v-if=\"cell.outputs && cell.outputs.length\" class=\"filex-viewer-ipynb__outputs\">\n <template v-for=\"(out, j) in cell.outputs\" :key=\"j\">\n <img\n v-if=\"outputImage(out)\"\n :src=\"outputImage(out) || ''\"\n class=\"filex-viewer-ipynb__img\"\n :alt=\"`output ${j}`\"\n />\n <div\n v-else-if=\"outputHtml(out)\"\n class=\"filex-viewer-ipynb__html\"\n v-html=\"outputHtml(out)\"\n />\n <pre\n v-else-if=\"outputText(out)\"\n class=\"filex-viewer-ipynb__out\"\n :data-type=\"out.output_type\"\n >{{ outputText(out) }}</pre>\n </template>\n </div>\n </template>\n\n <template v-else-if=\"cell.cell_type === 'markdown'\">\n <div\n v-if=\"renderedMarkdown.get(idx)\"\n class=\"filex-viewer-ipynb__md\"\n dir=\"auto\"\n v-html=\"renderedMarkdown.get(idx)\"\n />\n <pre v-else class=\"filex-viewer-ipynb__md-raw\">{{ joinSource(cell.source) }}</pre>\n </template>\n\n <template v-else>\n <pre class=\"filex-viewer-ipynb__src\">{{ joinSource(cell.source) }}</pre>\n </template>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n/* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to\n `.cls[data-v-HASH]`, and in the web-component build the hash baked into\n this CSS does not match the one Vue stamps onto the DOM — so every rule\n here silently stopped applying. Measured in the desktop app: the share\n dialog had `position: static`, no background and no radius, i.e. raw\n unstyled HTML, in EVERY embedded surface.\n Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so\n there is nothing here that can leak into a host page. */\n.filex-viewer-ipynb {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 70vh;\n background: var(--fe-bg, #fff);\n color: var(--fe-text, #1a1e27);\n}\n.filex-viewer-ipynb__bar {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 8px 12px;\n background: var(--fe-bg-elev, #f7f8fa);\n border-bottom: 1px solid var(--fe-border, #e2e6ed);\n font-size: 12px;\n color: var(--fe-text-muted, #5a6475);\n}\n.filex-viewer-ipynb__lang {\n font-family: var(--fe-font-mono, monospace);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n.filex-viewer-ipynb__pane {\n flex: 1;\n overflow: auto;\n padding: 16px 24px;\n}\n.filex-viewer-ipynb__cell {\n border: 1px solid var(--fe-border, #e2e6ed);\n border-radius: 6px;\n margin-bottom: 16px;\n overflow: hidden;\n}\n.filex-viewer-ipynb__cell[data-type=\"code\"] {\n background: var(--fe-bg-elev, #f7f8fa);\n}\n.filex-viewer-ipynb__exec {\n padding: 4px 12px;\n font-family: var(--fe-font-mono, monospace);\n font-size: 11px;\n color: var(--fe-text-muted, #5a6475);\n background: rgba(0, 0, 0, 0.04);\n}\n.filex-viewer-ipynb__src {\n margin: 0;\n padding: 12px;\n font-family: var(--fe-font-mono, monospace);\n font-size: 12px;\n line-height: 1.5;\n white-space: pre;\n overflow-x: auto;\n}\n.filex-viewer-ipynb__outputs {\n border-top: 1px dashed var(--fe-border, #e2e6ed);\n background: var(--fe-bg, #fff);\n padding: 8px 12px;\n}\n.filex-viewer-ipynb__out {\n margin: 0;\n padding: 4px 0;\n font-family: var(--fe-font-mono, monospace);\n font-size: 12px;\n white-space: pre-wrap;\n word-wrap: break-word;\n}\n.filex-viewer-ipynb__out[data-type=\"error\"] {\n color: var(--fe-danger, #dc2626);\n}\n.filex-viewer-ipynb__img {\n display: block;\n max-width: 100%;\n height: auto;\n margin: 8px 0;\n}\n.filex-viewer-ipynb__html {\n font-size: 13px;\n}\n.filex-viewer-ipynb__md {\n padding: 12px 16px;\n font-size: 14px;\n line-height: 1.6;\n}\n.filex-viewer-ipynb__md-raw {\n padding: 12px;\n margin: 0;\n font-family: var(--fe-font-mono, monospace);\n font-size: 12px;\n white-space: pre-wrap;\n}\n.filex-viewer-fallback {\n text-align: center;\n padding: 32px;\n color: var(--fe-text-muted, #5a6475);\n}\n.filex-viewer-fallback__icon {\n font-size: 48px;\n display: block;\n margin-bottom: 12px;\n}\n</style>\n"],"names":["props","__props","cells","ref","language","error","loading","renderedSources","renderedMarkdown","renderToken","highlight","mdRenderer","ensureMd","mod","Md","joinSource","source","load","myToken","raw","fetchViewerText","_a","err","sayFailure","tt","nb","_c","_b","ensureHighlight","hljs","langOk","_d","cell","idx","text","result","md","outputText","out","outputImage","svg","outputHtml","onMounted","onBeforeUnmount","watch","computed","codeCount","c","mdCount","key","fallback","typeTile","fileIconTile","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_hoisted_5","_unref","actionIconSvg","_hoisted_7","_Fragment","_renderList","_hoisted_9","_hoisted_10","_hoisted_12","_toDisplayString","_hoisted_13","j","_hoisted_16","_hoisted_18","_hoisted_19"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"IpynbViewer-COOTyOcu.js","sources":["../src/viewers/IpynbViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { sayFailure } from '../lib/errorWords';\n/**\n * IpynbViewer — Jupyter notebook (.ipynb JSON) renderer.\n *\n * Pure-JS render — no peer dependency required:\n * - code cells → `<pre><code>` (highlight.js when available)\n * - markdown cells → markdown-it when available, plain `<pre>` otherwise\n * - output cells → text (stream / display_data 'text/plain'),\n * images (display_data 'image/png' base64),\n * HTML (display_data 'text/html', sanitized via\n * element-only insertion — no script tags)\n *\n * Source list / output count counters at the top so a 200-cell\n * notebook doesn't surprise the user.\n */\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';\nimport { actionIconSvg } from '../lib/actionIcons'; /* ikon:emoji */\nimport { fileIconTile } from '../lib/fileIcons'; /* ikon:emoji */\nimport { fetchViewerText } from '../composables/useViewerFetch';\nimport { ensureHighlight } from '../composables/useMonacoLoader';\n\nconst props = defineProps<{\n url: string;\n ext: string;\n t?: (key: string) => string;\n authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;\n authCredentials?: RequestCredentials;\n}>();\n\ninterface Cell {\n cell_type: 'code' | 'markdown' | 'raw' | string;\n source: string | string[];\n outputs?: any[];\n execution_count?: number | null;\n metadata?: Record<string, unknown>;\n}\n\ninterface Notebook {\n cells: Cell[];\n metadata?: { kernelspec?: { language?: string; name?: string } };\n}\n\nconst cells = ref<Cell[]>([]);\nconst language = ref<string>('python');\nconst error = ref<string | null>(null);\nconst loading = ref(true);\nconst renderedSources = ref<Map<number, string>>(new Map());\nconst renderedMarkdown = ref<Map<number, string>>(new Map());\n\nlet renderToken = 0;\nlet highlight: any = null;\nlet mdRenderer: any = null;\n\nasync function ensureMd(): Promise<any | null> {\n if (mdRenderer !== null) return mdRenderer || null;\n try {\n const mod = await import(/* @vite-ignore */ 'markdown-it');\n const Md = (mod as any).default ?? mod;\n mdRenderer = new Md({ html: false, linkify: true, breaks: true });\n return mdRenderer;\n } catch {\n mdRenderer = false;\n return null;\n }\n}\n\nfunction joinSource(source: string | string[]): string {\n return Array.isArray(source) ? source.join('') : (source ?? '');\n}\n\nasync function load(): Promise<void> {\n loading.value = true;\n error.value = null;\n cells.value = [];\n renderedSources.value = new Map();\n renderedMarkdown.value = new Map();\n const myToken = ++renderToken;\n\n let raw: string;\n try {\n raw = await fetchViewerText({\n url: props.url,\n headers: (await props.authHeaders?.()) ?? {},\n credentials: props.authCredentials,\n });\n } catch (err) {\n error.value = sayFailure(err, tt('viewer.failed_to_load', 'Failed to load file'), { t: props.t }).text;\n loading.value = false;\n return;\n }\n\n if (myToken !== renderToken) return;\n\n let nb: Notebook;\n try {\n nb = JSON.parse(raw);\n } catch (err) {\n error.value = 'Invalid notebook JSON';\n loading.value = false;\n return;\n }\n cells.value = Array.isArray(nb.cells) ? nb.cells : [];\n language.value = nb.metadata?.kernelspec?.language || 'python';\n\n // Best-effort hljs highlight per code cell.\n highlight = (await ensureHighlight()) as any;\n if (myToken !== renderToken) return;\n if (highlight) {\n const hljs = highlight.default ?? highlight;\n const langOk = hljs.getLanguage?.(language.value) ? language.value : null;\n cells.value.forEach((cell, idx) => {\n if (cell.cell_type === 'code') {\n const text = joinSource(cell.source);\n try {\n const result = langOk\n ? hljs.highlight(text, { language: langOk, ignoreIllegals: true })\n : hljs.highlightAuto(text);\n renderedSources.value.set(idx, result.value);\n } catch {\n /* leave unrendered */\n }\n }\n });\n }\n\n const md = await ensureMd();\n if (myToken !== renderToken) return;\n if (md) {\n cells.value.forEach((cell, idx) => {\n if (cell.cell_type === 'markdown') {\n try {\n renderedMarkdown.value.set(idx, md.render(joinSource(cell.source)));\n } catch {\n /* leave plain */\n }\n }\n });\n }\n\n loading.value = false;\n}\n\nfunction outputText(out: any): string | null {\n if (!out) return null;\n if (out.output_type === 'stream') {\n return joinSource(out.text);\n }\n if (out.output_type === 'error') {\n return [out.ename, out.evalue, ...(out.traceback || [])]\n .filter(Boolean)\n .join('\\n');\n }\n if (out.output_type === 'execute_result' || out.output_type === 'display_data') {\n if (out.data?.['text/plain']) {\n return joinSource(out.data['text/plain']);\n }\n }\n return null;\n}\n\nfunction outputImage(out: any): string | null {\n if (!out?.data) return null;\n if (out.data['image/png']) return `data:image/png;base64,${out.data['image/png']}`;\n if (out.data['image/jpeg']) return `data:image/jpeg;base64,${out.data['image/jpeg']}`;\n if (out.data['image/svg+xml']) {\n const svg = joinSource(out.data['image/svg+xml']);\n return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;\n }\n return null;\n}\n\nfunction outputHtml(out: any): string | null {\n if (!out?.data) return null;\n if (out.data['text/html']) {\n const html = joinSource(out.data['text/html']);\n // Strip <script> for safety — outputs from arbitrary notebooks.\n return html.replace(/<script[\\s\\S]*?<\\/script>/gi, '');\n }\n return null;\n}\n\nonMounted(load);\nonBeforeUnmount(() => {\n renderToken++;\n});\nwatch(() => props.url, load);\n\nconst stats = computed(() => {\n const codeCount = cells.value.filter((c) => c.cell_type === 'code').length;\n const mdCount = cells.value.filter((c) => c.cell_type === 'markdown').length;\n return { code: codeCount, md: mdCount, total: cells.value.length };\n});\n\nfunction tt(key: string, fallback: string): string {\n return props.t ? props.t(key) : fallback;\n}\n\n/* === ikon:emoji — the fallback screen's mark ==========================\n * Every viewer opened its \"cannot show this\" / \"still loading\" screen with a\n * 48px colour emoji, one per format, each from whatever emoji font the OS\n * shipped. The format mark is `lib/fileIcons`'s tile — the SAME tile the row\n * the person just clicked is wearing, so the fallback is recognisably about\n * that file — and \"loading\" is the stroked ring, spun by CSS, because no\n * still picture can say \"still going\". */\nconst typeTile = computed(() => fileIconTile({ type: 'file', extension: props.ext }));\n</script>\n\n<template>\n <div class=\"filex-viewer-ipynb\">\n <div class=\"filex-viewer-ipynb__pane\">\n <div v-if=\"error\" class=\"filex-viewer-fallback\">\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span class=\"filex-viewer-fallback__icon\" aria-hidden=\"true\" v-html=\"typeTile\"></span>\n <p>{{ error }}</p>\n </div>\n <div v-else-if=\"loading\" class=\"filex-viewer-fallback\">\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span\n class=\"filex-viewer-fallback__icon filex-viewer-fallback__icon--spin\"\n aria-hidden=\"true\"\n v-html=\"actionIconSvg('progress')\"\n ></span>\n <p>{{ tt('viewer.loading', 'Loading…') }}</p>\n </div>\n <div v-else>\n <div\n v-for=\"(cell, idx) in cells\"\n :key=\"idx\"\n class=\"filex-viewer-ipynb__cell\"\n :data-type=\"cell.cell_type\"\n >\n <template v-if=\"cell.cell_type === 'code'\">\n <div class=\"filex-viewer-ipynb__exec\">\n In [{{ cell.execution_count ?? ' ' }}]:\n </div>\n <pre class=\"filex-viewer-ipynb__src hljs\"><code\n v-if=\"renderedSources.get(idx)\"\n v-html=\"renderedSources.get(idx)\"\n /><code v-else>{{ joinSource(cell.source) }}</code></pre>\n <div v-if=\"cell.outputs && cell.outputs.length\" class=\"filex-viewer-ipynb__outputs\">\n <template v-for=\"(out, j) in cell.outputs\" :key=\"j\">\n <img\n v-if=\"outputImage(out)\"\n :src=\"outputImage(out) || ''\"\n class=\"filex-viewer-ipynb__img\"\n :alt=\"`output ${j}`\"\n />\n <div\n v-else-if=\"outputHtml(out)\"\n class=\"filex-viewer-ipynb__html\"\n v-html=\"outputHtml(out)\"\n />\n <pre\n v-else-if=\"outputText(out)\"\n class=\"filex-viewer-ipynb__out\"\n :data-type=\"out.output_type\"\n >{{ outputText(out) }}</pre>\n </template>\n </div>\n </template>\n\n <template v-else-if=\"cell.cell_type === 'markdown'\">\n <div\n v-if=\"renderedMarkdown.get(idx)\"\n class=\"filex-viewer-ipynb__md\"\n dir=\"auto\"\n v-html=\"renderedMarkdown.get(idx)\"\n />\n <pre v-else class=\"filex-viewer-ipynb__md-raw\">{{ joinSource(cell.source) }}</pre>\n </template>\n\n <template v-else>\n <pre class=\"filex-viewer-ipynb__src\">{{ joinSource(cell.source) }}</pre>\n </template>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n/* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to\n `.cls[data-v-HASH]`, and in the web-component build the hash baked into\n this CSS does not match the one Vue stamps onto the DOM — so every rule\n here silently stopped applying. Measured in the desktop app: the share\n dialog had `position: static`, no background and no radius, i.e. raw\n unstyled HTML, in EVERY embedded surface.\n Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so\n there is nothing here that can leak into a host page. */\n.filex-viewer-ipynb {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 70vh;\n background: var(--fe-bg, #fff);\n color: var(--fe-text, #1a1e27);\n}\n.filex-viewer-ipynb__bar {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 8px 12px;\n background: var(--fe-bg-elev, #f7f8fa);\n border-bottom: 1px solid var(--fe-border, #e2e6ed);\n font-size: 12px;\n color: var(--fe-text-muted, #5a6475);\n}\n.filex-viewer-ipynb__lang {\n font-family: var(--fe-font-mono, monospace);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n.filex-viewer-ipynb__pane {\n flex: 1;\n overflow: auto;\n padding: 16px 24px;\n}\n.filex-viewer-ipynb__cell {\n border: 1px solid var(--fe-border, #e2e6ed);\n border-radius: 6px;\n margin-bottom: 16px;\n overflow: hidden;\n}\n.filex-viewer-ipynb__cell[data-type=\"code\"] {\n background: var(--fe-bg-elev, #f7f8fa);\n}\n.filex-viewer-ipynb__exec {\n padding: 4px 12px;\n font-family: var(--fe-font-mono, monospace);\n font-size: 11px;\n color: var(--fe-text-muted, #5a6475);\n background: rgba(0, 0, 0, 0.04);\n}\n.filex-viewer-ipynb__src {\n margin: 0;\n padding: 12px;\n font-family: var(--fe-font-mono, monospace);\n font-size: 12px;\n line-height: 1.5;\n white-space: pre;\n overflow-x: auto;\n}\n.filex-viewer-ipynb__outputs {\n border-top: 1px dashed var(--fe-border, #e2e6ed);\n background: var(--fe-bg, #fff);\n padding: 8px 12px;\n}\n.filex-viewer-ipynb__out {\n margin: 0;\n padding: 4px 0;\n font-family: var(--fe-font-mono, monospace);\n font-size: 12px;\n white-space: pre-wrap;\n word-wrap: break-word;\n}\n.filex-viewer-ipynb__out[data-type=\"error\"] {\n color: var(--fe-danger, #dc2626);\n}\n.filex-viewer-ipynb__img {\n display: block;\n max-width: 100%;\n height: auto;\n margin: 8px 0;\n}\n.filex-viewer-ipynb__html {\n font-size: 13px;\n}\n.filex-viewer-ipynb__md {\n padding: 12px 16px;\n font-size: 14px;\n line-height: 1.6;\n}\n.filex-viewer-ipynb__md-raw {\n padding: 12px;\n margin: 0;\n font-family: var(--fe-font-mono, monospace);\n font-size: 12px;\n white-space: pre-wrap;\n}\n.filex-viewer-fallback {\n text-align: center;\n padding: 32px;\n color: var(--fe-text-muted, #5a6475);\n}\n.filex-viewer-fallback__icon {\n font-size: 48px;\n display: block;\n margin-bottom: 12px;\n}\n</style>\n"],"names":["props","__props","cells","ref","language","error","loading","renderedSources","renderedMarkdown","renderToken","highlight","mdRenderer","ensureMd","mod","Md","joinSource","source","load","myToken","raw","fetchViewerText","_a","err","sayFailure","tt","nb","_c","_b","ensureHighlight","hljs","langOk","_d","cell","idx","text","result","md","outputText","out","outputImage","svg","outputHtml","onMounted","onBeforeUnmount","watch","computed","codeCount","c","mdCount","key","fallback","typeTile","fileIconTile","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_hoisted_5","_unref","actionIconSvg","_hoisted_7","_Fragment","_renderList","_hoisted_9","_hoisted_10","_hoisted_12","_toDisplayString","_hoisted_13","j","_hoisted_16","_hoisted_18","_hoisted_19"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAsBA,UAAMA,IAAQC,GAqBRC,IAAQC,EAAY,EAAE,GACtBC,IAAWD,EAAY,QAAQ,GAC/BE,IAAQF,EAAmB,IAAI,GAC/BG,IAAUH,EAAI,EAAI,GAClBI,IAAkBJ,EAAyB,oBAAI,KAAK,GACpDK,IAAmBL,EAAyB,oBAAI,KAAK;AAE3D,QAAIM,IAAc,GACdC,IAAiB,MACjBC,IAAkB;AAEtB,mBAAeC,IAAgC;AAC7C,UAAID,MAAe,KAAM,QAAOA,KAAc;AAC9C,UAAI;AACF,cAAME,IAAM,MAAM;AAAA;AAAA,UAA0B;AAAA,QAAA,GACtCC,IAAMD,EAAY,WAAWA;AACnC,eAAAF,IAAa,IAAIG,EAAG,EAAE,MAAM,IAAO,SAAS,IAAM,QAAQ,IAAM,GACzDH;AAAA,MACT,QAAQ;AACN,eAAAA,IAAa,IACN;AAAA,MACT;AAAA,IACF;AAEA,aAASI,EAAWC,GAAmC;AACrD,aAAO,MAAM,QAAQA,CAAM,IAAIA,EAAO,KAAK,EAAE,IAAKA,KAAU;AAAA,IAC9D;AAEA,mBAAeC,IAAsB;;AACnC,MAAAX,EAAQ,QAAQ,IAChBD,EAAM,QAAQ,MACdH,EAAM,QAAQ,CAAA,GACdK,EAAgB,4BAAY,IAAA,GAC5BC,EAAiB,4BAAY,IAAA;AAC7B,YAAMU,IAAU,EAAET;AAElB,UAAIU;AACJ,UAAI;AACF,QAAAA,IAAM,MAAMC,EAAgB;AAAA,UAC1B,KAAKpB,EAAM;AAAA,UACX,SAAU,QAAMqB,IAAArB,EAAM,gBAAN,gBAAAqB,EAAA,KAAArB,OAA0B,CAAA;AAAA,UAC1C,aAAaA,EAAM;AAAA,QAAA,CACpB;AAAA,MACH,SAASsB,GAAK;AACZ,QAAAjB,EAAM,QAAQkB,EAAWD,GAAKE,EAAG,yBAAyB,qBAAqB,GAAG,EAAE,GAAGxB,EAAM,EAAA,CAAG,EAAE,MAClGM,EAAQ,QAAQ;AAChB;AAAA,MACF;AAEA,UAAIY,MAAYT,EAAa;AAE7B,UAAIgB;AACJ,UAAI;AACF,QAAAA,IAAK,KAAK,MAAMN,CAAG;AAAA,MACrB,QAAc;AACZ,QAAAd,EAAM,QAAQ,yBACdC,EAAQ,QAAQ;AAChB;AAAA,MACF;AAMA,UALAJ,EAAM,QAAQ,MAAM,QAAQuB,EAAG,KAAK,IAAIA,EAAG,QAAQ,CAAA,GACnDrB,EAAS,UAAQsB,KAAAC,IAAAF,EAAG,aAAH,gBAAAE,EAAa,eAAb,gBAAAD,EAAyB,aAAY,UAGtDhB,IAAa,MAAMkB,EAAA,GACfV,MAAYT,EAAa;AAC7B,UAAIC,GAAW;AACb,cAAMmB,IAAOnB,EAAU,WAAWA,GAC5BoB,KAASC,IAAAF,EAAK,gBAAL,QAAAE,EAAA,KAAAF,GAAmBzB,EAAS,SAASA,EAAS,QAAQ;AACrE,QAAAF,EAAM,MAAM,QAAQ,CAAC8B,GAAMC,MAAQ;AACjC,cAAID,EAAK,cAAc,QAAQ;AAC7B,kBAAME,IAAOnB,EAAWiB,EAAK,MAAM;AACnC,gBAAI;AACF,oBAAMG,IAASL,IACXD,EAAK,UAAUK,GAAM,EAAE,UAAUJ,GAAQ,gBAAgB,GAAA,CAAM,IAC/DD,EAAK,cAAcK,CAAI;AAC3B,cAAA3B,EAAgB,MAAM,IAAI0B,GAAKE,EAAO,KAAK;AAAA,YAC7C,QAAQ;AAAA,YAER;AAAA,UACF;AAAA,QACF,CAAC;AAAA,MACH;AAEA,YAAMC,IAAK,MAAMxB,EAAA;AACjB,MAAIM,MAAYT,MACZ2B,KACFlC,EAAM,MAAM,QAAQ,CAAC8B,GAAMC,MAAQ;AACjC,YAAID,EAAK,cAAc;AACrB,cAAI;AACF,YAAAxB,EAAiB,MAAM,IAAIyB,GAAKG,EAAG,OAAOrB,EAAWiB,EAAK,MAAM,CAAC,CAAC;AAAA,UACpE,QAAQ;AAAA,UAER;AAAA,MAEJ,CAAC,GAGH1B,EAAQ,QAAQ;AAAA,IAClB;AAEA,aAAS+B,EAAWC,GAAyB;;AAC3C,aAAKA,IACDA,EAAI,gBAAgB,WACfvB,EAAWuB,EAAI,IAAI,IAExBA,EAAI,gBAAgB,UACf,CAACA,EAAI,OAAOA,EAAI,QAAQ,GAAIA,EAAI,aAAa,CAAA,CAAG,EACpD,OAAO,OAAO,EACd,KAAK;AAAA,CAAI,KAEVA,EAAI,gBAAgB,oBAAoBA,EAAI,gBAAgB,oBAC1DjB,IAAAiB,EAAI,SAAJ,QAAAjB,EAAW,gBACNN,EAAWuB,EAAI,KAAK,YAAY,CAAC,IAGrC,OAdU;AAAA,IAenB;AAEA,aAASC,EAAYD,GAAyB;AAC5C,UAAI,EAACA,KAAA,QAAAA,EAAK,MAAM,QAAO;AACvB,UAAIA,EAAI,KAAK,WAAW,UAAU,yBAAyBA,EAAI,KAAK,WAAW,CAAC;AAChF,UAAIA,EAAI,KAAK,YAAY,UAAU,0BAA0BA,EAAI,KAAK,YAAY,CAAC;AACnF,UAAIA,EAAI,KAAK,eAAe,GAAG;AAC7B,cAAME,IAAMzB,EAAWuB,EAAI,KAAK,eAAe,CAAC;AAChD,eAAO,2BAA2B,mBAAmBE,CAAG,CAAC;AAAA,MAC3D;AACA,aAAO;AAAA,IACT;AAEA,aAASC,EAAWH,GAAyB;AAC3C,aAAKA,KAAA,QAAAA,EAAK,QACNA,EAAI,KAAK,WAAW,IACTvB,EAAWuB,EAAI,KAAK,WAAW,CAAC,EAEjC,QAAQ,+BAA+B,EAAE,IAJhC;AAAA,IAOzB;AAEA,IAAAI,EAAUzB,CAAI,GACd0B,EAAgB,MAAM;AACpB,MAAAlC;AAAA,IACF,CAAC,GACDmC,EAAM,MAAM5C,EAAM,KAAKiB,CAAI,GAEb4B,EAAS,MAAM;AAC3B,YAAMC,IAAY5C,EAAM,MAAM,OAAO,CAAC6C,MAAMA,EAAE,cAAc,MAAM,EAAE,QAC9DC,IAAU9C,EAAM,MAAM,OAAO,CAAC6C,MAAMA,EAAE,cAAc,UAAU,EAAE;AACtE,aAAO,EAAE,MAAMD,GAAW,IAAIE,GAAS,OAAO9C,EAAM,MAAM,OAAA;AAAA,IAC5D,CAAC;AAED,aAASsB,EAAGyB,GAAaC,GAA0B;AACjD,aAAOlD,EAAM,IAAIA,EAAM,EAAEiD,CAAG,IAAIC;AAAA,IAClC;AASA,UAAMC,IAAWN,EAAS,MAAMO,EAAa,EAAE,MAAM,QAAQ,WAAWpD,EAAM,IAAA,CAAK,CAAC;sBAIlFqD,EAAA,GAAAC,EAqEM,OArENC,GAqEM;AAAA,MApEJC,EAmEM,OAnENC,GAmEM;AAAA,QAlEOpD,EAAA,SAAXgD,EAAA,GAAAC,EAIM,OAJNI,IAIM;AAAA,UAFNF,EAAsF,QAAA;AAAA,YAAhF,OAAM;AAAA,YAA8B,eAAY;AAAA,YAAO,WAAQL,EAAA;AAAA,UAAA;UACnEK,EAAkB,aAAZnD,EAAA,KAAK,GAAA,CAAA;AAAA,QAAA,MAEGC,EAAA,SAAhB+C,KAAAC,EAQM,OARNK,IAQM;AAAA,UANNH,EAIQ,QAAA;AAAA,YAHN,OAAM;AAAA,YACN,eAAY;AAAA,YACZ,WAAQI,EAAAC,CAAA,EAAa,UAAA;AAAA,UAAA;UAErBL,EAA6C,aAAvChC,EAAE,kBAAA,UAAA,CAAA,GAAA,CAAA;AAAA,QAAA,YAEV8B,EAmDM,OAAAQ,IAAA;AAAA,WAlDJT,EAAA,EAAA,GAAAC,EAiDMS,GAAA,MAAAC,EAhDkB9D,EAAA,OAAK,CAAnB8B,GAAMC,YADhBqB,EAiDM,OAAA;AAAA,YA/CH,KAAKrB;AAAA,YACN,OAAM;AAAA,YACL,aAAWD,EAAK;AAAA,UAAA;YAEDA,EAAK,cAAS,eAA9BsB,EA4BWS,GAAA,EAAA,KAAA,KAAA;AAAA,cA3BTP,EAEM,OAFNS,IAAsC,YAC7BjC,EAAK,mBAAe,GAAA,IAAU,OACvC,CAAA;AAAA,cACAwB,EAGyD,OAHzDU,IAGyD;AAAA,gBAFjD3D,EAAA,MAAgB,IAAI0B,CAAG,UADWqB,EAGxC,QAAA;AAAA;kBADA,WAAQ/C,EAAA,MAAgB,IAAI0B,CAAG;AAAA,gBAAA,oBAC/BoB,KAAAC,EAAiD,QAAAa,IAAAC,EAAjCrD,EAAWiB,EAAK,MAAM,CAAA,GAAA,CAAA;AAAA,cAAA;cAC7BA,EAAK,WAAWA,EAAK,QAAQ,UAAxCqB,KAAAC,EAmBM,OAnBNe,IAmBM;AAAA,iBAlBJhB,EAAA,EAAA,GAAAC,EAiBWS,WAjBkB/B,EAAK,SAAO,CAAvBM,GAAKgC,wBAA0BA,KAAC;AAAA,kBAExC/B,EAAYD,CAAG,UADvBgB,EAKE,OAAA;AAAA;oBAHC,KAAKf,EAAYD,CAAG,KAAA;AAAA,oBACrB,OAAM;AAAA,oBACL,eAAegC,CAAC;AAAA,kBAAA,mBAGN7B,EAAWH,CAAG,UAD3BgB,EAIE,OAAA;AAAA;oBAFA,OAAM;AAAA,oBACN,WAAQb,EAAWH,CAAG;AAAA,kBAAA,mBAGXD,EAAWC,CAAG,UAD3BgB,EAI4B,OAAA;AAAA;oBAF1B,OAAM;AAAA,oBACL,aAAWhB,EAAI;AAAA,kBAAA,GACd8B,EAAA/B,EAAWC,CAAG,CAAA,GAAA,GAAAiC,EAAA;;;sBAKHvC,EAAK,cAAS,mBAAnCsB,EAQWS,GAAA,EAAA,KAAA,KAAA;AAAA,cANDvD,EAAA,MAAiB,IAAIyB,CAAG,UADhCqB,EAKE,OAAA;AAAA;gBAHA,OAAM;AAAA,gBACN,KAAI;AAAA,gBACJ,WAAQ9C,EAAA,MAAiB,IAAIyB,CAAG;AAAA,cAAA,oBAElCoB,KAAAC,EAAkF,OAAlFkB,IAAkFJ,EAAhCrD,EAAWiB,EAAK,MAAM,CAAA,GAAA,CAAA;AAAA,YAAA,WAIxEqB,KAAAC,EAAwE,OAAxEmB,IAAwEL,EAAhCrD,EAAWiB,EAAK,MAAM,CAAA,GAAA,CAAA;AAAA,UAAA;;;;;;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineComponent as V, ref as s, onMounted as $, onBeforeUnmount as B, computed as E, openBlock as w, createElementBlock as x, createElementVNode as u, toDisplayString as M, unref as I, createCommentVNode as Y, withDirectives as z, normalizeStyle as K, vShow as N } from "vue";
|
|
2
|
-
import { f as U, a as W, b as X, s as T } from "./index-
|
|
2
|
+
import { f as U, a as W, b as X, s as T } from "./index-BBToAjwL.js";
|
|
3
3
|
const q = { class: "filex-viewer-mermaid" }, O = {
|
|
4
4
|
key: 0,
|
|
5
5
|
class: "filex-viewer-fallback"
|
|
@@ -133,4 +133,4 @@ const q = { class: "filex-viewer-mermaid" }, O = {
|
|
|
133
133
|
export {
|
|
134
134
|
R as default
|
|
135
135
|
};
|
|
136
|
-
//# sourceMappingURL=MermaidViewer-
|
|
136
|
+
//# sourceMappingURL=MermaidViewer-CjVCmR3s.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MermaidViewer-0JqH6k0f.js","sources":["../src/viewers/MermaidViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { sayFailure } from '../lib/errorWords';\n/**\n * MermaidViewer — render a `.mmd` / `.mermaid` source file.\n *\n * Lazy-imports the `mermaid` library (~150 KB gzipped). Source text is\n * fetched from `props.url` (with the auth headers from PreviewModal),\n * then rendered to SVG via `mermaid.render('id', src)`. The resulting\n * SVG node is inserted into a wrapper that supports pan + scale via\n * pure CSS transforms (no extra panzoom dep).\n */\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue';\nimport { actionIconSvg } from '../lib/actionIcons'; /* ikon:emoji */\nimport { fileIconTile } from '../lib/fileIcons'; /* ikon:emoji */\nimport { fetchViewerText } from '../composables/useViewerFetch';\n\nconst props = defineProps<{\n url: string;\n mime?: string;\n ext: string;\n t?: (key: string) => string;\n authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;\n authCredentials?: RequestCredentials;\n}>();\n\nconst surface = ref<HTMLDivElement | null>(null);\nconst error = ref<string | null>(null);\nconst loading = ref(true);\nconst scale = ref(1);\nconst tx = ref(0);\nconst ty = ref(0);\n\nlet dragging = false;\nlet dragStart: { x: number; y: number; tx: number; ty: number } | null = null;\nlet renderToken = 0;\n\nlet cachedMermaid: any = null;\nasync function ensureMermaid(): Promise<any | null> {\n if (cachedMermaid) return cachedMermaid;\n try {\n const mod = await import(/* @vite-ignore */ 'mermaid');\n cachedMermaid = mod.default ?? mod;\n cachedMermaid.initialize?.({\n startOnLoad: false,\n securityLevel: 'strict',\n theme: window.matchMedia?.('(prefers-color-scheme: dark)').matches\n ? 'dark'\n : 'default',\n });\n return cachedMermaid;\n } catch {\n return null;\n }\n}\n\nasync function load(): Promise<void> {\n loading.value = true;\n error.value = null;\n const myToken = ++renderToken;\n\n const mermaid = await ensureMermaid();\n if (myToken !== renderToken) return;\n if (!mermaid) {\n error.value = tt('viewer.peer_not_installed', 'This kind of file cannot be shown here. Download it to open it on your device.');\n loading.value = false;\n return;\n }\n\n let src: string;\n try {\n src = await fetchViewerText({\n url: props.url,\n headers: (await props.authHeaders?.()) ?? {},\n credentials: props.authCredentials,\n });\n } catch (err) {\n error.value = sayFailure(err, tt('viewer.failed_to_load', 'Failed to load file'), { t: props.t }).text;\n loading.value = false;\n return;\n }\n\n if (myToken !== renderToken) return;\n\n try {\n const id = `filex-mermaid-${Date.now()}`;\n const { svg } = await mermaid.render(id, src);\n if (myToken !== renderToken) return;\n if (surface.value) {\n surface.value.innerHTML = svg;\n const svgEl = surface.value.querySelector('svg');\n if (svgEl) {\n svgEl.style.maxWidth = '100%';\n svgEl.style.height = 'auto';\n }\n }\n } catch (err) {\n error.value = sayFailure(err, tt('viewer.failed_to_load', 'Failed to load file'), { t: props.t }).text;\n } finally {\n loading.value = false;\n }\n}\n\nfunction zoomIn(): void {\n scale.value = Math.min(8, scale.value * 1.25);\n}\nfunction zoomOut(): void {\n scale.value = Math.max(0.2, scale.value / 1.25);\n}\nfunction reset(): void {\n scale.value = 1;\n tx.value = 0;\n ty.value = 0;\n}\n\nfunction onWheel(ev: WheelEvent): void {\n if (!ev.ctrlKey && !ev.metaKey) return;\n ev.preventDefault();\n const delta = -ev.deltaY;\n const factor = delta > 0 ? 1.1 : 1 / 1.1;\n scale.value = Math.max(0.2, Math.min(8, scale.value * factor));\n}\n\nfunction onPointerDown(ev: PointerEvent): void {\n dragging = true;\n dragStart = { x: ev.clientX, y: ev.clientY, tx: tx.value, ty: ty.value };\n (ev.target as HTMLElement).setPointerCapture?.(ev.pointerId);\n}\nfunction onPointerMove(ev: PointerEvent): void {\n if (!dragging || !dragStart) return;\n tx.value = dragStart.tx + (ev.clientX - dragStart.x);\n ty.value = dragStart.ty + (ev.clientY - dragStart.y);\n}\nfunction onPointerUp(): void {\n dragging = false;\n dragStart = null;\n}\n\nonMounted(load);\nonBeforeUnmount(() => {\n renderToken++;\n});\n\nfunction tt(key: string, fallback: string): string {\n return props.t ? props.t(key) : fallback;\n}\n\n/* === ikon:emoji — the fallback screen's mark ==========================\n * Every viewer opened its \"cannot show this\" / \"still loading\" screen with a\n * 48px colour emoji, one per format, each from whatever emoji font the OS\n * shipped. The format mark is `lib/fileIcons`'s tile — the SAME tile the row\n * the person just clicked is wearing, so the fallback is recognisably about\n * that file — and \"loading\" is the stroked ring, spun by CSS, because no\n * still picture can say \"still going\". */\nconst typeTile = computed(() => fileIconTile({ type: 'file', extension: props.ext }));\n</script>\n\n<template>\n <div class=\"filex-viewer-mermaid\">\n <div\n class=\"filex-viewer-mermaid__pane\"\n @wheel=\"onWheel\"\n @pointerdown=\"onPointerDown\"\n @pointermove=\"onPointerMove\"\n @pointerup=\"onPointerUp\"\n @pointercancel=\"onPointerUp\"\n >\n <div\n v-if=\"error\"\n class=\"filex-viewer-fallback\"\n >\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span class=\"filex-viewer-fallback__icon\" aria-hidden=\"true\" v-html=\"typeTile\"></span>\n <p>{{ error }}</p>\n </div>\n <div\n v-else-if=\"loading\"\n class=\"filex-viewer-fallback\"\n >\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span\n class=\"filex-viewer-fallback__icon filex-viewer-fallback__icon--spin\"\n aria-hidden=\"true\"\n v-html=\"actionIconSvg('progress')\"\n ></span>\n <p>{{ tt('viewer.loading', 'Loading…') }}</p>\n </div>\n <div\n v-show=\"!loading && !error\"\n ref=\"surface\"\n class=\"filex-viewer-mermaid__surface\"\n :style=\"{ transform: `translate(${tx}px, ${ty}px) scale(${scale})` }\"\n />\n </div>\n </div>\n</template>\n\n<style>\n/* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to\n `.cls[data-v-HASH]`, and in the web-component build the hash baked into\n this CSS does not match the one Vue stamps onto the DOM — so every rule\n here silently stopped applying. Measured in the desktop app: the share\n dialog had `position: static`, no background and no radius, i.e. raw\n unstyled HTML, in EVERY embedded surface.\n Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so\n there is nothing here that can leak into a host page. */\n.filex-viewer-mermaid {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 70vh;\n background: var(--fe-bg, #fff);\n color: var(--fe-text, #1a1e27);\n}\n.filex-viewer-mermaid__bar {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 12px;\n background: var(--fe-bg-elev, #f7f8fa);\n border-bottom: 1px solid var(--fe-border, #e2e6ed);\n font-size: 13px;\n}\n.filex-viewer-mermaid__zoom {\n min-width: 50px;\n text-align: center;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n}\n.filex-viewer-mermaid__pane {\n flex: 1;\n overflow: hidden;\n position: relative;\n cursor: grab;\n background: var(--fe-bg-elev, #f7f8fa);\n touch-action: none;\n}\n.filex-viewer-mermaid__pane:active {\n cursor: grabbing;\n}\n.filex-viewer-mermaid__surface {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n transform-origin: center center;\n transition: transform 0.05s linear;\n user-select: none;\n}\n.filex-viewer-mermaid__surface svg {\n max-width: 90%;\n max-height: 90%;\n}\n.filex-viewer-fallback {\n text-align: center;\n padding: 32px;\n color: var(--fe-text-muted, #5a6475);\n width: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n}\n.filex-viewer-fallback__icon {\n font-size: 48px;\n display: block;\n margin-bottom: 12px;\n}\n.filex-viewer-btn {\n border: 1px solid var(--fe-border, #e2e6ed);\n background: var(--fe-bg, #fff);\n color: var(--fe-text, #1a1e27);\n padding: 4px 10px;\n border-radius: 4px;\n cursor: pointer;\n font: inherit;\n font-size: 12px;\n}\n.filex-viewer-btn:hover:not(:disabled) {\n background: var(--fe-bg-hover, #edf0f5);\n}\n</style>\n"],"names":["props","__props","surface","ref","error","loading","scale","tx","ty","dragging","dragStart","renderToken","cachedMermaid","ensureMermaid","mod","_b","_a","load","myToken","mermaid","tt","src","fetchViewerText","err","sayFailure","id","svg","svgEl","onWheel","ev","factor","onPointerDown","onPointerMove","onPointerUp","onMounted","onBeforeUnmount","key","fallback","typeTile","computed","fileIconTile","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_4","_unref","actionIconSvg","_normalizeStyle","_vShow"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MermaidViewer-CjVCmR3s.js","sources":["../src/viewers/MermaidViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { sayFailure } from '../lib/errorWords';\n/**\n * MermaidViewer — render a `.mmd` / `.mermaid` source file.\n *\n * Lazy-imports the `mermaid` library (~150 KB gzipped). Source text is\n * fetched from `props.url` (with the auth headers from PreviewModal),\n * then rendered to SVG via `mermaid.render('id', src)`. The resulting\n * SVG node is inserted into a wrapper that supports pan + scale via\n * pure CSS transforms (no extra panzoom dep).\n */\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue';\nimport { actionIconSvg } from '../lib/actionIcons'; /* ikon:emoji */\nimport { fileIconTile } from '../lib/fileIcons'; /* ikon:emoji */\nimport { fetchViewerText } from '../composables/useViewerFetch';\n\nconst props = defineProps<{\n url: string;\n mime?: string;\n ext: string;\n t?: (key: string) => string;\n authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;\n authCredentials?: RequestCredentials;\n}>();\n\nconst surface = ref<HTMLDivElement | null>(null);\nconst error = ref<string | null>(null);\nconst loading = ref(true);\nconst scale = ref(1);\nconst tx = ref(0);\nconst ty = ref(0);\n\nlet dragging = false;\nlet dragStart: { x: number; y: number; tx: number; ty: number } | null = null;\nlet renderToken = 0;\n\nlet cachedMermaid: any = null;\nasync function ensureMermaid(): Promise<any | null> {\n if (cachedMermaid) return cachedMermaid;\n try {\n const mod = await import(/* @vite-ignore */ 'mermaid');\n cachedMermaid = mod.default ?? mod;\n cachedMermaid.initialize?.({\n startOnLoad: false,\n securityLevel: 'strict',\n theme: window.matchMedia?.('(prefers-color-scheme: dark)').matches\n ? 'dark'\n : 'default',\n });\n return cachedMermaid;\n } catch {\n return null;\n }\n}\n\nasync function load(): Promise<void> {\n loading.value = true;\n error.value = null;\n const myToken = ++renderToken;\n\n const mermaid = await ensureMermaid();\n if (myToken !== renderToken) return;\n if (!mermaid) {\n error.value = tt('viewer.peer_not_installed', 'This kind of file cannot be shown here. Download it to open it on your device.');\n loading.value = false;\n return;\n }\n\n let src: string;\n try {\n src = await fetchViewerText({\n url: props.url,\n headers: (await props.authHeaders?.()) ?? {},\n credentials: props.authCredentials,\n });\n } catch (err) {\n error.value = sayFailure(err, tt('viewer.failed_to_load', 'Failed to load file'), { t: props.t }).text;\n loading.value = false;\n return;\n }\n\n if (myToken !== renderToken) return;\n\n try {\n const id = `filex-mermaid-${Date.now()}`;\n const { svg } = await mermaid.render(id, src);\n if (myToken !== renderToken) return;\n if (surface.value) {\n surface.value.innerHTML = svg;\n const svgEl = surface.value.querySelector('svg');\n if (svgEl) {\n svgEl.style.maxWidth = '100%';\n svgEl.style.height = 'auto';\n }\n }\n } catch (err) {\n error.value = sayFailure(err, tt('viewer.failed_to_load', 'Failed to load file'), { t: props.t }).text;\n } finally {\n loading.value = false;\n }\n}\n\nfunction zoomIn(): void {\n scale.value = Math.min(8, scale.value * 1.25);\n}\nfunction zoomOut(): void {\n scale.value = Math.max(0.2, scale.value / 1.25);\n}\nfunction reset(): void {\n scale.value = 1;\n tx.value = 0;\n ty.value = 0;\n}\n\nfunction onWheel(ev: WheelEvent): void {\n if (!ev.ctrlKey && !ev.metaKey) return;\n ev.preventDefault();\n const delta = -ev.deltaY;\n const factor = delta > 0 ? 1.1 : 1 / 1.1;\n scale.value = Math.max(0.2, Math.min(8, scale.value * factor));\n}\n\nfunction onPointerDown(ev: PointerEvent): void {\n dragging = true;\n dragStart = { x: ev.clientX, y: ev.clientY, tx: tx.value, ty: ty.value };\n (ev.target as HTMLElement).setPointerCapture?.(ev.pointerId);\n}\nfunction onPointerMove(ev: PointerEvent): void {\n if (!dragging || !dragStart) return;\n tx.value = dragStart.tx + (ev.clientX - dragStart.x);\n ty.value = dragStart.ty + (ev.clientY - dragStart.y);\n}\nfunction onPointerUp(): void {\n dragging = false;\n dragStart = null;\n}\n\nonMounted(load);\nonBeforeUnmount(() => {\n renderToken++;\n});\n\nfunction tt(key: string, fallback: string): string {\n return props.t ? props.t(key) : fallback;\n}\n\n/* === ikon:emoji — the fallback screen's mark ==========================\n * Every viewer opened its \"cannot show this\" / \"still loading\" screen with a\n * 48px colour emoji, one per format, each from whatever emoji font the OS\n * shipped. The format mark is `lib/fileIcons`'s tile — the SAME tile the row\n * the person just clicked is wearing, so the fallback is recognisably about\n * that file — and \"loading\" is the stroked ring, spun by CSS, because no\n * still picture can say \"still going\". */\nconst typeTile = computed(() => fileIconTile({ type: 'file', extension: props.ext }));\n</script>\n\n<template>\n <div class=\"filex-viewer-mermaid\">\n <div\n class=\"filex-viewer-mermaid__pane\"\n @wheel=\"onWheel\"\n @pointerdown=\"onPointerDown\"\n @pointermove=\"onPointerMove\"\n @pointerup=\"onPointerUp\"\n @pointercancel=\"onPointerUp\"\n >\n <div\n v-if=\"error\"\n class=\"filex-viewer-fallback\"\n >\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span class=\"filex-viewer-fallback__icon\" aria-hidden=\"true\" v-html=\"typeTile\"></span>\n <p>{{ error }}</p>\n </div>\n <div\n v-else-if=\"loading\"\n class=\"filex-viewer-fallback\"\n >\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span\n class=\"filex-viewer-fallback__icon filex-viewer-fallback__icon--spin\"\n aria-hidden=\"true\"\n v-html=\"actionIconSvg('progress')\"\n ></span>\n <p>{{ tt('viewer.loading', 'Loading…') }}</p>\n </div>\n <div\n v-show=\"!loading && !error\"\n ref=\"surface\"\n class=\"filex-viewer-mermaid__surface\"\n :style=\"{ transform: `translate(${tx}px, ${ty}px) scale(${scale})` }\"\n />\n </div>\n </div>\n</template>\n\n<style>\n/* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to\n `.cls[data-v-HASH]`, and in the web-component build the hash baked into\n this CSS does not match the one Vue stamps onto the DOM — so every rule\n here silently stopped applying. Measured in the desktop app: the share\n dialog had `position: static`, no background and no radius, i.e. raw\n unstyled HTML, in EVERY embedded surface.\n Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so\n there is nothing here that can leak into a host page. */\n.filex-viewer-mermaid {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 70vh;\n background: var(--fe-bg, #fff);\n color: var(--fe-text, #1a1e27);\n}\n.filex-viewer-mermaid__bar {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 12px;\n background: var(--fe-bg-elev, #f7f8fa);\n border-bottom: 1px solid var(--fe-border, #e2e6ed);\n font-size: 13px;\n}\n.filex-viewer-mermaid__zoom {\n min-width: 50px;\n text-align: center;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n}\n.filex-viewer-mermaid__pane {\n flex: 1;\n overflow: hidden;\n position: relative;\n cursor: grab;\n background: var(--fe-bg-elev, #f7f8fa);\n touch-action: none;\n}\n.filex-viewer-mermaid__pane:active {\n cursor: grabbing;\n}\n.filex-viewer-mermaid__surface {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n transform-origin: center center;\n transition: transform 0.05s linear;\n user-select: none;\n}\n.filex-viewer-mermaid__surface svg {\n max-width: 90%;\n max-height: 90%;\n}\n.filex-viewer-fallback {\n text-align: center;\n padding: 32px;\n color: var(--fe-text-muted, #5a6475);\n width: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n}\n.filex-viewer-fallback__icon {\n font-size: 48px;\n display: block;\n margin-bottom: 12px;\n}\n.filex-viewer-btn {\n border: 1px solid var(--fe-border, #e2e6ed);\n background: var(--fe-bg, #fff);\n color: var(--fe-text, #1a1e27);\n padding: 4px 10px;\n border-radius: 4px;\n cursor: pointer;\n font: inherit;\n font-size: 12px;\n}\n.filex-viewer-btn:hover:not(:disabled) {\n background: var(--fe-bg-hover, #edf0f5);\n}\n</style>\n"],"names":["props","__props","surface","ref","error","loading","scale","tx","ty","dragging","dragStart","renderToken","cachedMermaid","ensureMermaid","mod","_b","_a","load","myToken","mermaid","tt","src","fetchViewerText","err","sayFailure","id","svg","svgEl","onWheel","ev","factor","onPointerDown","onPointerMove","onPointerUp","onMounted","onBeforeUnmount","key","fallback","typeTile","computed","fileIconTile","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_4","_unref","actionIconSvg","_normalizeStyle","_vShow"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineComponent as H, ref as i, onMounted as I, onBeforeUnmount as P, watch as E, computed as M, openBlock as m, createElementBlock as _, createElementVNode as o, toDisplayString as T, unref as S, createCommentVNode as A, withDirectives as B, vShow as V } from "vue";
|
|
2
|
-
import { f as F, a as N, c as U } from "./index-
|
|
2
|
+
import { f as F, a as N, c as U } from "./index-BBToAjwL.js";
|
|
3
3
|
const j = { class: "filex-viewer-psd" }, q = { class: "filex-viewer-psd__pane" }, z = {
|
|
4
4
|
key: 0,
|
|
5
5
|
class: "filex-viewer-fallback"
|
|
@@ -118,4 +118,4 @@ const j = { class: "filex-viewer-psd" }, q = { class: "filex-viewer-psd__pane" }
|
|
|
118
118
|
export {
|
|
119
119
|
R as default
|
|
120
120
|
};
|
|
121
|
-
//# sourceMappingURL=PsdViewer-
|
|
121
|
+
//# sourceMappingURL=PsdViewer-xsIYL9Gu.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PsdViewer-BnI-NDr8.js","sources":["../src/viewers/PsdViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * PsdViewer — Photoshop document preview via `ag-psd`.\n *\n * Lazy-imports `ag-psd` (~150 KB). Renders the flattened composite to\n * a canvas via `psd.canvas`. A simple layer panel toggles the rendered\n * preview (V1: just lists names + visibility; full composite swap on\n * toggle requires re-rendering with `applyOpacity`/`composite` logic\n * — left as TODO for V2 since `ag-psd` doesn't ship a composite\n * pipeline of its own).\n */\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';\nimport { actionIconSvg } from '../lib/actionIcons'; /* ikon:emoji */\nimport { fileIconTile } from '../lib/fileIcons'; /* ikon:emoji */\nimport { fetchViewerArrayBuffer } from '../composables/useViewerFetch';\n\nconst props = defineProps<{\n url: string;\n mime?: string;\n ext: string;\n t?: (key: string) => string;\n authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;\n authCredentials?: RequestCredentials;\n}>();\n\ninterface FlatLayer {\n index: number;\n name: string;\n hidden: boolean;\n}\n\nconst canvasEl = ref<HTMLCanvasElement | null>(null);\nconst layers = ref<FlatLayer[]>([]);\nconst error = ref<string | null>(null);\nconst loading = ref(true);\nconst showLayerPanel = ref(false);\nconst dims = ref<{ width: number; height: number }>({ width: 0, height: 0 });\nconst scale = ref(1);\n\nlet renderToken = 0;\n\nlet agPsd: any = null;\nasync function ensureAgPsd(): Promise<any | null> {\n if (agPsd) return agPsd;\n try {\n const mod = await import(/* @vite-ignore */ 'ag-psd');\n agPsd = mod;\n return agPsd;\n } catch {\n return null;\n }\n}\n\nfunction flattenLayers(node: any, list: FlatLayer[]): void {\n if (!node) return;\n if (Array.isArray(node.children)) {\n for (const child of node.children) {\n list.push({\n index: list.length,\n name: child.name ?? '(unnamed)',\n hidden: !!child.hidden,\n });\n if (child.children) flattenLayers(child, list);\n }\n }\n}\n\nasync function load(): Promise<void> {\n loading.value = true;\n error.value = null;\n layers.value = [];\n const myToken = ++renderToken;\n\n const lib = await ensureAgPsd();\n if (myToken !== renderToken) return;\n if (!lib) {\n error.value = tt('viewer.peer_not_installed', 'This kind of file cannot be shown here. Download it to open it on your device.');\n loading.value = false;\n return;\n }\n\n try {\n const buf = await fetchViewerArrayBuffer({\n url: props.url,\n headers: (await props.authHeaders?.()) ?? {},\n credentials: props.authCredentials,\n });\n if (myToken !== renderToken) return;\n const psd = lib.readPsd(buf, {\n skipLayerImageData: false,\n skipCompositeImageData: false,\n skipThumbnail: true,\n });\n dims.value = { width: psd.width, height: psd.height };\n\n const composite = psd.canvas;\n if (composite && canvasEl.value) {\n const target = canvasEl.value;\n target.width = psd.width;\n target.height = psd.height;\n const ctx = target.getContext('2d');\n ctx?.drawImage(composite, 0, 0);\n } else {\n throw new Error('PSD has no composite image (skipCompositeImageData?)');\n }\n\n const flat: FlatLayer[] = [];\n flattenLayers(psd, flat);\n layers.value = flat;\n } catch (err) {\n // ⚠ The person gets the localized sentence; the decoder's own words go to\n // the console for whoever is debugging. `ag-psd` throws raw internals\n // (\"Offset is outside the bounds of the DataView\" on a truncated file),\n // and printing that verbatim put an untranslated English stack message in\n // the middle of a Turkish UI — measured 2026-09-13 on a 6-byte\n // `mockup.psd`.\n console.warn('[filex] PSD decode failed:', err);\n error.value = tt('viewer.failed_to_load', 'Failed to load file');\n } finally {\n loading.value = false;\n }\n}\n\nfunction zoomIn(): void {\n scale.value = Math.min(8, scale.value * 1.25);\n}\nfunction zoomOut(): void {\n scale.value = Math.max(0.1, scale.value / 1.25);\n}\nfunction reset(): void {\n scale.value = 1;\n}\n\nonMounted(load);\nonBeforeUnmount(() => {\n renderToken++;\n});\nwatch(() => props.url, load);\n\nfunction tt(key: string, fallback: string): string {\n return props.t ? props.t(key) : fallback;\n}\n\n/* === ikon:emoji — the fallback screen's mark ==========================\n * Every viewer opened its \"cannot show this\" / \"still loading\" screen with a\n * 48px colour emoji, one per format, each from whatever emoji font the OS\n * shipped. The format mark is `lib/fileIcons`'s tile — the SAME tile the row\n * the person just clicked is wearing, so the fallback is recognisably about\n * that file — and \"loading\" is the stroked ring, spun by CSS, because no\n * still picture can say \"still going\". */\nconst typeTile = computed(() => fileIconTile({ type: 'file', extension: props.ext }));\n</script>\n\n<template>\n <div class=\"filex-viewer-psd\">\n <div class=\"filex-viewer-psd__pane\">\n <div v-if=\"error\" class=\"filex-viewer-fallback\">\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span class=\"filex-viewer-fallback__icon\" aria-hidden=\"true\" v-html=\"typeTile\"></span>\n <p>{{ error }}</p>\n </div>\n <div v-else-if=\"loading\" class=\"filex-viewer-fallback\">\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span\n class=\"filex-viewer-fallback__icon filex-viewer-fallback__icon--spin\"\n aria-hidden=\"true\"\n v-html=\"actionIconSvg('progress')\"\n ></span>\n <p>{{ tt('viewer.loading', 'Loading…') }}</p>\n </div>\n <canvas\n v-show=\"!loading && !error\"\n ref=\"canvasEl\"\n />\n </div>\n </div>\n</template>\n\n<style>\n/* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to\n `.cls[data-v-HASH]`, and in the web-component build the hash baked into\n this CSS does not match the one Vue stamps onto the DOM — so every rule\n here silently stopped applying. Measured in the desktop app: the share\n dialog had `position: static`, no background and no radius, i.e. raw\n unstyled HTML, in EVERY embedded surface.\n Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so\n there is nothing here that can leak into a host page. */\n.filex-viewer-psd {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 70vh;\n background: var(--fe-bg, #fff);\n}\n.filex-viewer-psd__bar {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 12px;\n background: var(--fe-bg-elev, #f7f8fa);\n border-bottom: 1px solid var(--fe-border, #e2e6ed);\n font-size: 13px;\n}\n.filex-viewer-spacer { flex: 1; }\n.filex-viewer-psd__dim {\n font-size: 12px;\n color: var(--fe-text-muted, #5a6475);\n}\n.filex-viewer-psd__zoom {\n min-width: 50px;\n text-align: center;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n}\n.filex-viewer-psd__main {\n flex: 1;\n display: flex;\n min-height: 0;\n}\n.filex-viewer-psd__layers {\n width: 240px;\n border-inline-end: 1px solid var(--fe-border, #e2e6ed);\n overflow-y: auto;\n padding: 8px 0;\n background: var(--fe-bg-elev, #f7f8fa);\n}\n.filex-viewer-psd__layers ul {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n.filex-viewer-psd__layers li {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 12px;\n font-size: 12px;\n}\n.filex-viewer-psd__layer-vis {\n width: 14px;\n text-align: center;\n color: var(--fe-text-muted, #5a6475);\n}\n.filex-viewer-psd__layer-vis[data-hidden=\"1\"] { color: var(--fe-border-strong, #c7ced9); }\n.filex-viewer-psd__layer-name {\n flex: 1;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.filex-viewer-psd__pane {\n flex: 1;\n overflow: auto;\n padding: 16px;\n background: #2a2d33;\n display: flex;\n align-items: flex-start;\n justify-content: flex-start;\n}\n.filex-viewer-psd__pane canvas {\n background: #fff\n repeating-conic-gradient(#e0e0e0 0% 25%, transparent 0% 50%) 50% / 16px 16px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);\n}\n.filex-viewer-fallback {\n text-align: center;\n padding: 32px;\n color: #c8cdd6;\n margin: auto;\n}\n.filex-viewer-fallback__icon {\n font-size: 48px;\n display: block;\n margin-bottom: 12px;\n}\n.filex-viewer-btn {\n border: 1px solid var(--fe-border, #e2e6ed);\n background: var(--fe-bg, #fff);\n color: var(--fe-text, #1a1e27);\n padding: 4px 10px;\n border-radius: 4px;\n cursor: pointer;\n font: inherit;\n font-size: 12px;\n}\n.filex-viewer-btn:hover:not(:disabled) {\n background: var(--fe-bg-hover, #edf0f5);\n}\n.filex-viewer-btn.is-active {\n background: var(--fe-bg-selected, #dfe8ff);\n border-color: var(--fe-primary, #3b82f6);\n}\n</style>\n"],"names":["props","__props","canvasEl","ref","layers","error","loading","dims","renderToken","agPsd","ensureAgPsd","flattenLayers","node","list","child","load","myToken","lib","tt","buf","fetchViewerArrayBuffer","_a","psd","composite","target","ctx","flat","err","onMounted","onBeforeUnmount","watch","key","fallback","typeTile","computed","fileIconTile","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_hoisted_5","_unref","actionIconSvg","_vShow"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PsdViewer-xsIYL9Gu.js","sources":["../src/viewers/PsdViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * PsdViewer — Photoshop document preview via `ag-psd`.\n *\n * Lazy-imports `ag-psd` (~150 KB). Renders the flattened composite to\n * a canvas via `psd.canvas`. A simple layer panel toggles the rendered\n * preview (V1: just lists names + visibility; full composite swap on\n * toggle requires re-rendering with `applyOpacity`/`composite` logic\n * — left as TODO for V2 since `ag-psd` doesn't ship a composite\n * pipeline of its own).\n */\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';\nimport { actionIconSvg } from '../lib/actionIcons'; /* ikon:emoji */\nimport { fileIconTile } from '../lib/fileIcons'; /* ikon:emoji */\nimport { fetchViewerArrayBuffer } from '../composables/useViewerFetch';\n\nconst props = defineProps<{\n url: string;\n mime?: string;\n ext: string;\n t?: (key: string) => string;\n authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;\n authCredentials?: RequestCredentials;\n}>();\n\ninterface FlatLayer {\n index: number;\n name: string;\n hidden: boolean;\n}\n\nconst canvasEl = ref<HTMLCanvasElement | null>(null);\nconst layers = ref<FlatLayer[]>([]);\nconst error = ref<string | null>(null);\nconst loading = ref(true);\nconst showLayerPanel = ref(false);\nconst dims = ref<{ width: number; height: number }>({ width: 0, height: 0 });\nconst scale = ref(1);\n\nlet renderToken = 0;\n\nlet agPsd: any = null;\nasync function ensureAgPsd(): Promise<any | null> {\n if (agPsd) return agPsd;\n try {\n const mod = await import(/* @vite-ignore */ 'ag-psd');\n agPsd = mod;\n return agPsd;\n } catch {\n return null;\n }\n}\n\nfunction flattenLayers(node: any, list: FlatLayer[]): void {\n if (!node) return;\n if (Array.isArray(node.children)) {\n for (const child of node.children) {\n list.push({\n index: list.length,\n name: child.name ?? '(unnamed)',\n hidden: !!child.hidden,\n });\n if (child.children) flattenLayers(child, list);\n }\n }\n}\n\nasync function load(): Promise<void> {\n loading.value = true;\n error.value = null;\n layers.value = [];\n const myToken = ++renderToken;\n\n const lib = await ensureAgPsd();\n if (myToken !== renderToken) return;\n if (!lib) {\n error.value = tt('viewer.peer_not_installed', 'This kind of file cannot be shown here. Download it to open it on your device.');\n loading.value = false;\n return;\n }\n\n try {\n const buf = await fetchViewerArrayBuffer({\n url: props.url,\n headers: (await props.authHeaders?.()) ?? {},\n credentials: props.authCredentials,\n });\n if (myToken !== renderToken) return;\n const psd = lib.readPsd(buf, {\n skipLayerImageData: false,\n skipCompositeImageData: false,\n skipThumbnail: true,\n });\n dims.value = { width: psd.width, height: psd.height };\n\n const composite = psd.canvas;\n if (composite && canvasEl.value) {\n const target = canvasEl.value;\n target.width = psd.width;\n target.height = psd.height;\n const ctx = target.getContext('2d');\n ctx?.drawImage(composite, 0, 0);\n } else {\n throw new Error('PSD has no composite image (skipCompositeImageData?)');\n }\n\n const flat: FlatLayer[] = [];\n flattenLayers(psd, flat);\n layers.value = flat;\n } catch (err) {\n // ⚠ The person gets the localized sentence; the decoder's own words go to\n // the console for whoever is debugging. `ag-psd` throws raw internals\n // (\"Offset is outside the bounds of the DataView\" on a truncated file),\n // and printing that verbatim put an untranslated English stack message in\n // the middle of a Turkish UI — measured 2026-09-13 on a 6-byte\n // `mockup.psd`.\n console.warn('[filex] PSD decode failed:', err);\n error.value = tt('viewer.failed_to_load', 'Failed to load file');\n } finally {\n loading.value = false;\n }\n}\n\nfunction zoomIn(): void {\n scale.value = Math.min(8, scale.value * 1.25);\n}\nfunction zoomOut(): void {\n scale.value = Math.max(0.1, scale.value / 1.25);\n}\nfunction reset(): void {\n scale.value = 1;\n}\n\nonMounted(load);\nonBeforeUnmount(() => {\n renderToken++;\n});\nwatch(() => props.url, load);\n\nfunction tt(key: string, fallback: string): string {\n return props.t ? props.t(key) : fallback;\n}\n\n/* === ikon:emoji — the fallback screen's mark ==========================\n * Every viewer opened its \"cannot show this\" / \"still loading\" screen with a\n * 48px colour emoji, one per format, each from whatever emoji font the OS\n * shipped. The format mark is `lib/fileIcons`'s tile — the SAME tile the row\n * the person just clicked is wearing, so the fallback is recognisably about\n * that file — and \"loading\" is the stroked ring, spun by CSS, because no\n * still picture can say \"still going\". */\nconst typeTile = computed(() => fileIconTile({ type: 'file', extension: props.ext }));\n</script>\n\n<template>\n <div class=\"filex-viewer-psd\">\n <div class=\"filex-viewer-psd__pane\">\n <div v-if=\"error\" class=\"filex-viewer-fallback\">\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span class=\"filex-viewer-fallback__icon\" aria-hidden=\"true\" v-html=\"typeTile\"></span>\n <p>{{ error }}</p>\n </div>\n <div v-else-if=\"loading\" class=\"filex-viewer-fallback\">\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span\n class=\"filex-viewer-fallback__icon filex-viewer-fallback__icon--spin\"\n aria-hidden=\"true\"\n v-html=\"actionIconSvg('progress')\"\n ></span>\n <p>{{ tt('viewer.loading', 'Loading…') }}</p>\n </div>\n <canvas\n v-show=\"!loading && !error\"\n ref=\"canvasEl\"\n />\n </div>\n </div>\n</template>\n\n<style>\n/* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to\n `.cls[data-v-HASH]`, and in the web-component build the hash baked into\n this CSS does not match the one Vue stamps onto the DOM — so every rule\n here silently stopped applying. Measured in the desktop app: the share\n dialog had `position: static`, no background and no radius, i.e. raw\n unstyled HTML, in EVERY embedded surface.\n Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so\n there is nothing here that can leak into a host page. */\n.filex-viewer-psd {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 70vh;\n background: var(--fe-bg, #fff);\n}\n.filex-viewer-psd__bar {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 12px;\n background: var(--fe-bg-elev, #f7f8fa);\n border-bottom: 1px solid var(--fe-border, #e2e6ed);\n font-size: 13px;\n}\n.filex-viewer-spacer { flex: 1; }\n.filex-viewer-psd__dim {\n font-size: 12px;\n color: var(--fe-text-muted, #5a6475);\n}\n.filex-viewer-psd__zoom {\n min-width: 50px;\n text-align: center;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n}\n.filex-viewer-psd__main {\n flex: 1;\n display: flex;\n min-height: 0;\n}\n.filex-viewer-psd__layers {\n width: 240px;\n border-inline-end: 1px solid var(--fe-border, #e2e6ed);\n overflow-y: auto;\n padding: 8px 0;\n background: var(--fe-bg-elev, #f7f8fa);\n}\n.filex-viewer-psd__layers ul {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n.filex-viewer-psd__layers li {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 12px;\n font-size: 12px;\n}\n.filex-viewer-psd__layer-vis {\n width: 14px;\n text-align: center;\n color: var(--fe-text-muted, #5a6475);\n}\n.filex-viewer-psd__layer-vis[data-hidden=\"1\"] { color: var(--fe-border-strong, #c7ced9); }\n.filex-viewer-psd__layer-name {\n flex: 1;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.filex-viewer-psd__pane {\n flex: 1;\n overflow: auto;\n padding: 16px;\n background: #2a2d33;\n display: flex;\n align-items: flex-start;\n justify-content: flex-start;\n}\n.filex-viewer-psd__pane canvas {\n background: #fff\n repeating-conic-gradient(#e0e0e0 0% 25%, transparent 0% 50%) 50% / 16px 16px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);\n}\n.filex-viewer-fallback {\n text-align: center;\n padding: 32px;\n color: #c8cdd6;\n margin: auto;\n}\n.filex-viewer-fallback__icon {\n font-size: 48px;\n display: block;\n margin-bottom: 12px;\n}\n.filex-viewer-btn {\n border: 1px solid var(--fe-border, #e2e6ed);\n background: var(--fe-bg, #fff);\n color: var(--fe-text, #1a1e27);\n padding: 4px 10px;\n border-radius: 4px;\n cursor: pointer;\n font: inherit;\n font-size: 12px;\n}\n.filex-viewer-btn:hover:not(:disabled) {\n background: var(--fe-bg-hover, #edf0f5);\n}\n.filex-viewer-btn.is-active {\n background: var(--fe-bg-selected, #dfe8ff);\n border-color: var(--fe-primary, #3b82f6);\n}\n</style>\n"],"names":["props","__props","canvasEl","ref","layers","error","loading","dims","renderToken","agPsd","ensureAgPsd","flattenLayers","node","list","child","load","myToken","lib","tt","buf","fetchViewerArrayBuffer","_a","psd","composite","target","ctx","flat","err","onMounted","onBeforeUnmount","watch","key","fallback","typeTile","computed","fileIconTile","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_hoisted_5","_unref","actionIconSvg","_vShow"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineComponent as S, ref as u, onMounted as U, onBeforeUnmount as V, watch as N, computed as C, openBlock as h, createElementBlock as _, createElementVNode as i, toDisplayString as b, unref as A, createCommentVNode as D, withDirectives as R, vShow as G } from "vue";
|
|
2
|
-
import { f as P, a as j, c as q, s as z } from "./index-
|
|
2
|
+
import { f as P, a as j, c as q, s as z } from "./index-BBToAjwL.js";
|
|
3
3
|
const J = { class: "filex-viewer-tiff" }, K = { class: "filex-viewer-tiff__pane" }, O = {
|
|
4
4
|
key: 0,
|
|
5
5
|
class: "filex-viewer-fallback"
|
|
@@ -138,4 +138,4 @@ const J = { class: "filex-viewer-tiff" }, K = { class: "filex-viewer-tiff__pane"
|
|
|
138
138
|
export {
|
|
139
139
|
ae as default
|
|
140
140
|
};
|
|
141
|
-
//# sourceMappingURL=TiffViewer-
|
|
141
|
+
//# sourceMappingURL=TiffViewer-CtwnTxqL.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TiffViewer-CrLDXI_f.js","sources":["../src/viewers/TiffViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { sayFailure } from '../lib/errorWords';\n/**\n * TiffViewer — multi-page TIFF preview via `utif`.\n *\n * Lazy-imports `utif` (~50 KB). Decodes IFDs once, paints the active\n * page to a `<canvas>` via `UTIF.toRGBA8`. Page navigation (1/N) +\n * zoom in/out controls. Browser <img> can't render TIFF natively, so\n * the canvas pipeline is the only option short of server-side\n * conversion.\n */\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';\nimport { actionIconSvg } from '../lib/actionIcons'; /* ikon:emoji */\nimport { fileIconTile } from '../lib/fileIcons'; /* ikon:emoji */\nimport { fetchViewerArrayBuffer } from '../composables/useViewerFetch';\n\nconst props = defineProps<{\n url: string;\n mime?: string;\n ext: string;\n t?: (key: string) => string;\n authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;\n authCredentials?: RequestCredentials;\n}>();\n\nconst canvasEl = ref<HTMLCanvasElement | null>(null);\nconst error = ref<string | null>(null);\nconst loading = ref(true);\nconst scale = ref(1);\nconst pageIndex = ref(0);\nconst pageCount = ref(0);\nconst pageDims = ref<{ width: number; height: number }>({ width: 0, height: 0 });\n\nlet UTIF: any = null;\nlet ifds: any[] | null = null;\nlet renderToken = 0;\n\nasync function ensureUtif(): Promise<any | null> {\n if (UTIF) return UTIF;\n try {\n const mod = await import(/* @vite-ignore */ 'utif');\n UTIF = mod.default ?? mod;\n return UTIF;\n } catch {\n return null;\n }\n}\n\nasync function load(): Promise<void> {\n loading.value = true;\n error.value = null;\n ifds = null;\n pageIndex.value = 0;\n pageCount.value = 0;\n const myToken = ++renderToken;\n\n const lib = await ensureUtif();\n if (myToken !== renderToken) return;\n if (!lib) {\n error.value = tt('viewer.peer_not_installed', 'This kind of file cannot be shown here. Download it to open it on your device.');\n loading.value = false;\n return;\n }\n\n try {\n const buf = await fetchViewerArrayBuffer({\n url: props.url,\n headers: (await props.authHeaders?.()) ?? {},\n credentials: props.authCredentials,\n });\n if (myToken !== renderToken) return;\n ifds = lib.decode(buf);\n pageCount.value = ifds?.length ?? 0;\n if (pageCount.value === 0) {\n throw new Error('No pages decoded');\n }\n paint();\n } catch (err) {\n error.value = sayFailure(err, tt('viewer.failed_to_load', 'Failed to load file'), { t: props.t }).text;\n } finally {\n loading.value = false;\n }\n}\n\nfunction paint(): void {\n if (!UTIF || !ifds || !canvasEl.value) return;\n const idx = Math.max(0, Math.min(pageIndex.value, ifds.length - 1));\n const ifd = ifds[idx];\n try {\n UTIF.decodeImage(undefined, ifd);\n } catch {\n // some images already decoded — UTIF.decodeImage is idempotent on\n // older versions.\n }\n const rgba = UTIF.toRGBA8(ifd);\n const w = ifd.width;\n const h = ifd.height;\n pageDims.value = { width: w, height: h };\n const canvas = canvasEl.value;\n canvas.width = w;\n canvas.height = h;\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n const imageData = ctx.createImageData(w, h);\n imageData.data.set(rgba);\n ctx.putImageData(imageData, 0, 0);\n}\n\nfunction next(): void {\n if (pageIndex.value < pageCount.value - 1) {\n pageIndex.value++;\n paint();\n }\n}\nfunction prev(): void {\n if (pageIndex.value > 0) {\n pageIndex.value--;\n paint();\n }\n}\nfunction zoomIn(): void {\n scale.value = Math.min(8, scale.value * 1.25);\n}\nfunction zoomOut(): void {\n scale.value = Math.max(0.1, scale.value / 1.25);\n}\nfunction reset(): void {\n scale.value = 1;\n}\n\nonMounted(load);\nonBeforeUnmount(() => {\n renderToken++;\n ifds = null;\n});\n\nwatch(() => props.url, load);\n\nconst pageLabel = computed(() => {\n const m = (props.t && props.t('viewer.page_n_of_m')) || 'Page {n} of {m}';\n return m.replace('{n}', String(pageIndex.value + 1)).replace('{m}', String(pageCount.value));\n});\n\nfunction tt(key: string, fallback: string): string {\n return props.t ? props.t(key) : fallback;\n}\n\n/* === ikon:emoji — the fallback screen's mark ==========================\n * Every viewer opened its \"cannot show this\" / \"still loading\" screen with a\n * 48px colour emoji, one per format, each from whatever emoji font the OS\n * shipped. The format mark is `lib/fileIcons`'s tile — the SAME tile the row\n * the person just clicked is wearing, so the fallback is recognisably about\n * that file — and \"loading\" is the stroked ring, spun by CSS, because no\n * still picture can say \"still going\". */\nconst typeTile = computed(() => fileIconTile({ type: 'file', extension: props.ext }));\n</script>\n\n<template>\n <div class=\"filex-viewer-tiff\">\n <div class=\"filex-viewer-tiff__pane\">\n <div v-if=\"error\" class=\"filex-viewer-fallback\">\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span class=\"filex-viewer-fallback__icon\" aria-hidden=\"true\" v-html=\"typeTile\"></span>\n <p>{{ error }}</p>\n </div>\n <div v-else-if=\"loading\" class=\"filex-viewer-fallback\">\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span\n class=\"filex-viewer-fallback__icon filex-viewer-fallback__icon--spin\"\n aria-hidden=\"true\"\n v-html=\"actionIconSvg('progress')\"\n ></span>\n <p>{{ tt('viewer.loading', 'Loading…') }}</p>\n </div>\n <canvas\n v-show=\"!loading && !error\"\n ref=\"canvasEl\"\n :style=\"{ imageRendering: 'pixelated' }\"\n />\n <div v-if=\"!loading && !error && pageCount > 1\" class=\"filex-viewer-tiff__pager\">\n <button\n type=\"button\"\n class=\"filex-viewer-btn\"\n :disabled=\"pageIndex === 0\"\n @click=\"prev\"\n >‹</button>\n <span class=\"filex-viewer-tiff__pages\">{{ pageLabel }}</span>\n <button\n type=\"button\"\n class=\"filex-viewer-btn\"\n :disabled=\"pageIndex >= pageCount - 1\"\n @click=\"next\"\n >›</button>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n/* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to\n `.cls[data-v-HASH]`, and in the web-component build the hash baked into\n this CSS does not match the one Vue stamps onto the DOM — so every rule\n here silently stopped applying. Measured in the desktop app: the share\n dialog had `position: static`, no background and no radius, i.e. raw\n unstyled HTML, in EVERY embedded surface.\n Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so\n there is nothing here that can leak into a host page. */\n.filex-viewer-tiff__pager {\n position: absolute;\n bottom: 12px;\n left: 50%; /* rtl-physical: centred by translate(-50%) — the same place in both directions */\n transform: translateX(-50%);\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 8px;\n background: var(--fe-bg-elev, rgba(255, 255, 255, 0.9));\n border: 1px solid var(--fe-border, #e2e6ed);\n border-radius: 6px;\n backdrop-filter: blur(4px);\n}\n.filex-viewer-tiff__pages {\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n}\n.filex-viewer-tiff {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 70vh;\n background: var(--fe-bg, #fff);\n}\n.filex-viewer-tiff__bar {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 12px;\n background: var(--fe-bg-elev, #f7f8fa);\n border-bottom: 1px solid var(--fe-border, #e2e6ed);\n font-size: 13px;\n}\n.filex-viewer-tiff__pages {\n min-width: 110px;\n text-align: center;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n}\n.filex-viewer-tiff__zoom {\n min-width: 50px;\n text-align: center;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n}\n.filex-viewer-spacer { flex: 1; }\n.filex-viewer-tiff__pane {\n flex: 1;\n overflow: auto;\n padding: 16px;\n background: #2a2d33;\n display: flex;\n align-items: flex-start;\n justify-content: flex-start;\n}\n.filex-viewer-tiff__pane canvas {\n background: #fff;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);\n}\n.filex-viewer-fallback {\n text-align: center;\n padding: 32px;\n color: #c8cdd6;\n margin: auto;\n}\n.filex-viewer-fallback__icon {\n font-size: 48px;\n display: block;\n margin-bottom: 12px;\n}\n.filex-viewer-btn {\n border: 1px solid var(--fe-border, #e2e6ed);\n background: var(--fe-bg, #fff);\n color: var(--fe-text, #1a1e27);\n padding: 4px 10px;\n border-radius: 4px;\n cursor: pointer;\n font: inherit;\n font-size: 12px;\n}\n.filex-viewer-btn:hover:not(:disabled) {\n background: var(--fe-bg-hover, #edf0f5);\n}\n.filex-viewer-btn:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n</style>\n"],"names":["props","__props","canvasEl","ref","error","loading","pageIndex","pageCount","pageDims","UTIF","ifds","renderToken","ensureUtif","mod","n","load","myToken","lib","tt","buf","fetchViewerArrayBuffer","_a","paint","err","sayFailure","idx","ifd","rgba","w","h","canvas","ctx","imageData","next","prev","onMounted","onBeforeUnmount","watch","pageLabel","computed","key","fallback","typeTile","fileIconTile","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_hoisted_5","_unref","actionIconSvg","_vShow","_hoisted_7","_hoisted_8","_hoisted_9","_toDisplayString","_hoisted_10"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TiffViewer-CtwnTxqL.js","sources":["../src/viewers/TiffViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { sayFailure } from '../lib/errorWords';\n/**\n * TiffViewer — multi-page TIFF preview via `utif`.\n *\n * Lazy-imports `utif` (~50 KB). Decodes IFDs once, paints the active\n * page to a `<canvas>` via `UTIF.toRGBA8`. Page navigation (1/N) +\n * zoom in/out controls. Browser <img> can't render TIFF natively, so\n * the canvas pipeline is the only option short of server-side\n * conversion.\n */\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';\nimport { actionIconSvg } from '../lib/actionIcons'; /* ikon:emoji */\nimport { fileIconTile } from '../lib/fileIcons'; /* ikon:emoji */\nimport { fetchViewerArrayBuffer } from '../composables/useViewerFetch';\n\nconst props = defineProps<{\n url: string;\n mime?: string;\n ext: string;\n t?: (key: string) => string;\n authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;\n authCredentials?: RequestCredentials;\n}>();\n\nconst canvasEl = ref<HTMLCanvasElement | null>(null);\nconst error = ref<string | null>(null);\nconst loading = ref(true);\nconst scale = ref(1);\nconst pageIndex = ref(0);\nconst pageCount = ref(0);\nconst pageDims = ref<{ width: number; height: number }>({ width: 0, height: 0 });\n\nlet UTIF: any = null;\nlet ifds: any[] | null = null;\nlet renderToken = 0;\n\nasync function ensureUtif(): Promise<any | null> {\n if (UTIF) return UTIF;\n try {\n const mod = await import(/* @vite-ignore */ 'utif');\n UTIF = mod.default ?? mod;\n return UTIF;\n } catch {\n return null;\n }\n}\n\nasync function load(): Promise<void> {\n loading.value = true;\n error.value = null;\n ifds = null;\n pageIndex.value = 0;\n pageCount.value = 0;\n const myToken = ++renderToken;\n\n const lib = await ensureUtif();\n if (myToken !== renderToken) return;\n if (!lib) {\n error.value = tt('viewer.peer_not_installed', 'This kind of file cannot be shown here. Download it to open it on your device.');\n loading.value = false;\n return;\n }\n\n try {\n const buf = await fetchViewerArrayBuffer({\n url: props.url,\n headers: (await props.authHeaders?.()) ?? {},\n credentials: props.authCredentials,\n });\n if (myToken !== renderToken) return;\n ifds = lib.decode(buf);\n pageCount.value = ifds?.length ?? 0;\n if (pageCount.value === 0) {\n throw new Error('No pages decoded');\n }\n paint();\n } catch (err) {\n error.value = sayFailure(err, tt('viewer.failed_to_load', 'Failed to load file'), { t: props.t }).text;\n } finally {\n loading.value = false;\n }\n}\n\nfunction paint(): void {\n if (!UTIF || !ifds || !canvasEl.value) return;\n const idx = Math.max(0, Math.min(pageIndex.value, ifds.length - 1));\n const ifd = ifds[idx];\n try {\n UTIF.decodeImage(undefined, ifd);\n } catch {\n // some images already decoded — UTIF.decodeImage is idempotent on\n // older versions.\n }\n const rgba = UTIF.toRGBA8(ifd);\n const w = ifd.width;\n const h = ifd.height;\n pageDims.value = { width: w, height: h };\n const canvas = canvasEl.value;\n canvas.width = w;\n canvas.height = h;\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n const imageData = ctx.createImageData(w, h);\n imageData.data.set(rgba);\n ctx.putImageData(imageData, 0, 0);\n}\n\nfunction next(): void {\n if (pageIndex.value < pageCount.value - 1) {\n pageIndex.value++;\n paint();\n }\n}\nfunction prev(): void {\n if (pageIndex.value > 0) {\n pageIndex.value--;\n paint();\n }\n}\nfunction zoomIn(): void {\n scale.value = Math.min(8, scale.value * 1.25);\n}\nfunction zoomOut(): void {\n scale.value = Math.max(0.1, scale.value / 1.25);\n}\nfunction reset(): void {\n scale.value = 1;\n}\n\nonMounted(load);\nonBeforeUnmount(() => {\n renderToken++;\n ifds = null;\n});\n\nwatch(() => props.url, load);\n\nconst pageLabel = computed(() => {\n const m = (props.t && props.t('viewer.page_n_of_m')) || 'Page {n} of {m}';\n return m.replace('{n}', String(pageIndex.value + 1)).replace('{m}', String(pageCount.value));\n});\n\nfunction tt(key: string, fallback: string): string {\n return props.t ? props.t(key) : fallback;\n}\n\n/* === ikon:emoji — the fallback screen's mark ==========================\n * Every viewer opened its \"cannot show this\" / \"still loading\" screen with a\n * 48px colour emoji, one per format, each from whatever emoji font the OS\n * shipped. The format mark is `lib/fileIcons`'s tile — the SAME tile the row\n * the person just clicked is wearing, so the fallback is recognisably about\n * that file — and \"loading\" is the stroked ring, spun by CSS, because no\n * still picture can say \"still going\". */\nconst typeTile = computed(() => fileIconTile({ type: 'file', extension: props.ext }));\n</script>\n\n<template>\n <div class=\"filex-viewer-tiff\">\n <div class=\"filex-viewer-tiff__pane\">\n <div v-if=\"error\" class=\"filex-viewer-fallback\">\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span class=\"filex-viewer-fallback__icon\" aria-hidden=\"true\" v-html=\"typeTile\"></span>\n <p>{{ error }}</p>\n </div>\n <div v-else-if=\"loading\" class=\"filex-viewer-fallback\">\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span\n class=\"filex-viewer-fallback__icon filex-viewer-fallback__icon--spin\"\n aria-hidden=\"true\"\n v-html=\"actionIconSvg('progress')\"\n ></span>\n <p>{{ tt('viewer.loading', 'Loading…') }}</p>\n </div>\n <canvas\n v-show=\"!loading && !error\"\n ref=\"canvasEl\"\n :style=\"{ imageRendering: 'pixelated' }\"\n />\n <div v-if=\"!loading && !error && pageCount > 1\" class=\"filex-viewer-tiff__pager\">\n <button\n type=\"button\"\n class=\"filex-viewer-btn\"\n :disabled=\"pageIndex === 0\"\n @click=\"prev\"\n >‹</button>\n <span class=\"filex-viewer-tiff__pages\">{{ pageLabel }}</span>\n <button\n type=\"button\"\n class=\"filex-viewer-btn\"\n :disabled=\"pageIndex >= pageCount - 1\"\n @click=\"next\"\n >›</button>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n/* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to\n `.cls[data-v-HASH]`, and in the web-component build the hash baked into\n this CSS does not match the one Vue stamps onto the DOM — so every rule\n here silently stopped applying. Measured in the desktop app: the share\n dialog had `position: static`, no background and no radius, i.e. raw\n unstyled HTML, in EVERY embedded surface.\n Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so\n there is nothing here that can leak into a host page. */\n.filex-viewer-tiff__pager {\n position: absolute;\n bottom: 12px;\n left: 50%; /* rtl-physical: centred by translate(-50%) — the same place in both directions */\n transform: translateX(-50%);\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 8px;\n background: var(--fe-bg-elev, rgba(255, 255, 255, 0.9));\n border: 1px solid var(--fe-border, #e2e6ed);\n border-radius: 6px;\n backdrop-filter: blur(4px);\n}\n.filex-viewer-tiff__pages {\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n}\n.filex-viewer-tiff {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 70vh;\n background: var(--fe-bg, #fff);\n}\n.filex-viewer-tiff__bar {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 12px;\n background: var(--fe-bg-elev, #f7f8fa);\n border-bottom: 1px solid var(--fe-border, #e2e6ed);\n font-size: 13px;\n}\n.filex-viewer-tiff__pages {\n min-width: 110px;\n text-align: center;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n}\n.filex-viewer-tiff__zoom {\n min-width: 50px;\n text-align: center;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n}\n.filex-viewer-spacer { flex: 1; }\n.filex-viewer-tiff__pane {\n flex: 1;\n overflow: auto;\n padding: 16px;\n background: #2a2d33;\n display: flex;\n align-items: flex-start;\n justify-content: flex-start;\n}\n.filex-viewer-tiff__pane canvas {\n background: #fff;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);\n}\n.filex-viewer-fallback {\n text-align: center;\n padding: 32px;\n color: #c8cdd6;\n margin: auto;\n}\n.filex-viewer-fallback__icon {\n font-size: 48px;\n display: block;\n margin-bottom: 12px;\n}\n.filex-viewer-btn {\n border: 1px solid var(--fe-border, #e2e6ed);\n background: var(--fe-bg, #fff);\n color: var(--fe-text, #1a1e27);\n padding: 4px 10px;\n border-radius: 4px;\n cursor: pointer;\n font: inherit;\n font-size: 12px;\n}\n.filex-viewer-btn:hover:not(:disabled) {\n background: var(--fe-bg-hover, #edf0f5);\n}\n.filex-viewer-btn:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n</style>\n"],"names":["props","__props","canvasEl","ref","error","loading","pageIndex","pageCount","pageDims","UTIF","ifds","renderToken","ensureUtif","mod","n","load","myToken","lib","tt","buf","fetchViewerArrayBuffer","_a","paint","err","sayFailure","idx","ifd","rgba","w","h","canvas","ctx","imageData","next","prev","onMounted","onBeforeUnmount","watch","pageLabel","computed","key","fallback","typeTile","fileIconTile","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_hoisted_5","_unref","actionIconSvg","_vShow","_hoisted_7","_hoisted_8","_hoisted_9","_toDisplayString","_hoisted_10"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineComponent as _, computed as s, ref as c, onMounted as m, watch as h, resolveComponent as x, openBlock as n, createElementBlock as i, createBlock as y, createElementVNode as a, toDisplayString as u, unref as k } from "vue";
|
|
2
|
-
import { f as b, a as g } from "./index-
|
|
2
|
+
import { f as b, a as g } from "./index-BBToAjwL.js";
|
|
3
3
|
const T = { class: "filex-viewer-3d" }, E = {
|
|
4
4
|
key: 1,
|
|
5
5
|
class: "filex-viewer-fallback"
|
|
@@ -72,4 +72,4 @@ const T = { class: "filex-viewer-3d" }, E = {
|
|
|
72
72
|
export {
|
|
73
73
|
F as default
|
|
74
74
|
};
|
|
75
|
-
//# sourceMappingURL=Viewer3D-
|
|
75
|
+
//# sourceMappingURL=Viewer3D-xYMwndEy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Viewer3D-
|
|
1
|
+
{"version":3,"file":"Viewer3D-xYMwndEy.js","sources":["../src/viewers/Viewer3D.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Viewer3D — 3D model preview via `@google/model-viewer`.\n *\n * Lazy-imports the `@google/model-viewer` web component (~80 KB).\n *\n * Format support: model-viewer **only** understands glTF (`.gltf`,\n * `.glb`) and USDZ (iOS). For unsupported formats (`.stl`, `.obj`,\n * `.fbx`, `.3ds`) we deliberately render a download fallback instead\n * of feeding them to model-viewer — earlier code did the latter,\n * which surfaced as `JSON.parse(<ascii STL>)` SyntaxError because\n * model-viewer parses the response body as glTF JSON. (sweep-2026-05-09\n * bugs 19-20.)\n *\n * GLB rendering size: model-viewer mounts its canvas inside a\n * shadow root that doesn't always inherit `height: 100%` from\n * flexbox parents — we pin explicit sizing on the host element\n * to avoid the \"Framebuffer is incomplete: zero size\" warning.\n * (sweep-2026-05-09 bug 21.)\n */\nimport { computed, onMounted, ref, watch } from 'vue';\nimport { actionIconSvg } from '../lib/actionIcons'; /* ikon:emoji */\nimport { fileIconTile } from '../lib/fileIcons'; /* ikon:emoji */\n\nconst props = defineProps<{\n url: string;\n mime?: string;\n ext: string;\n /** Locale-aware error/loading messages. */\n t?: (key: string) => string;\n}>();\n\nconst SUPPORTED_EXTS = new Set(['glb', 'gltf', 'usdz']);\n\nconst isSupported = computed(() => SUPPORTED_EXTS.has((props.ext || '').toLowerCase()));\n\nconst error = ref<string | null>(null);\nconst ready = ref(false);\n\nasync function load(): Promise<void> {\n ready.value = false;\n error.value = null;\n // Bail out early for formats model-viewer can't parse — feeding\n // them to <model-viewer> triggers JSON.parse SyntaxError because\n // it expects glTF JSON.\n if (!isSupported.value) {\n error.value = props.t\n ? props.t('viewer.format_unsupported_3d')\n : `3D format \".${props.ext}\" not supported in browser preview — please download.`;\n return;\n }\n try {\n await import(/* @vite-ignore */ '@google/model-viewer');\n ready.value = true;\n } catch {\n error.value = props.t\n ? props.t('viewer.peer_not_installed')\n : 'This kind of file cannot be shown here. Download it to open it on your device.';\n }\n}\n\n/**\n * model-viewer failed to FETCH or PARSE the model.\n *\n * ⚠ `ready` only means the ~80 KB web component imported. The model itself is\n * fetched and decoded by model-viewer AFTER it mounts, and when that throws\n * (a truncated or non-glTF file) it renders an empty canvas and reports the\n * failure only on this event. Without the listener the person got a silent\n * black rectangle and nothing else — measured 2026-09-13 on an 8-byte\n * `turbine.glb`, where the console had `RangeError: Invalid DataView length 12`\n * and the pane had no text at all.\n */\nfunction onModelError(): void {\n ready.value = false;\n error.value = props.t ? props.t('viewer.failed_to_load') : 'Failed to load file';\n}\n\nonMounted(load);\n\nwatch(() => props.url, () => {\n // Re-evaluate on URL change (different file may need different\n // fallback message).\n load();\n});\n\n/* === ikon:emoji — the fallback screen's mark ==========================\n * Every viewer opened its \"cannot show this\" / \"still loading\" screen with a\n * 48px colour emoji, one per format, each from whatever emoji font the OS\n * shipped. The format mark is `lib/fileIcons`'s tile — the SAME tile the row\n * the person just clicked is wearing, so the fallback is recognisably about\n * that file — and \"loading\" is the stroked ring, spun by CSS, because no\n * still picture can say \"still going\". */\nconst typeTile = computed(() => fileIconTile({ type: 'file', extension: props.ext }));\n</script>\n\n<template>\n <div class=\"filex-viewer-3d\">\n <model-viewer\n v-if=\"ready && !error\"\n :src=\"url\"\n auto-rotate\n camera-controls\n touch-action=\"pan-y\"\n shadow-intensity=\"1\"\n :alt=\"ext + ' model'\"\n style=\"width: 100%; height: 100%; min-height: 480px; display: block\"\n @error=\"onModelError\"\n />\n <div v-else-if=\"error\" class=\"filex-viewer-fallback\">\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span class=\"filex-viewer-fallback__icon\" aria-hidden=\"true\" v-html=\"typeTile\"></span>\n <p>{{ error }}</p>\n </div>\n <div v-else class=\"filex-viewer-fallback\">\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span\n class=\"filex-viewer-fallback__icon filex-viewer-fallback__icon--spin\"\n aria-hidden=\"true\"\n v-html=\"actionIconSvg('progress')\"\n ></span>\n <p>{{ t ? t('viewer.loading') : 'Loading…' }}</p>\n </div>\n </div>\n</template>\n\n<style>\n/* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to\n `.cls[data-v-HASH]`, and in the web-component build the hash baked into\n this CSS does not match the one Vue stamps onto the DOM — so every rule\n here silently stopped applying. Measured in the desktop app: the share\n dialog had `position: static`, no background and no radius, i.e. raw\n unstyled HTML, in EVERY embedded surface.\n Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so\n there is nothing here that can leak into a host page. */\n.filex-viewer-3d {\n width: 100%;\n height: 100%;\n min-height: 480px;\n background: #1a1a1a;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.filex-viewer-3d model-viewer {\n width: 100%;\n height: 100%;\n min-height: 480px;\n background: #1a1a1a;\n display: block;\n}\n.filex-viewer-fallback {\n text-align: center;\n color: #c8cdd6;\n padding: 32px;\n max-width: 480px;\n}\n.filex-viewer-fallback__icon {\n font-size: 48px;\n display: block;\n margin-bottom: 12px;\n}\n</style>\n"],"names":["props","__props","SUPPORTED_EXTS","isSupported","computed","error","ref","ready","load","onModelError","onMounted","watch","typeTile","fileIconTile","_openBlock","_createElementBlock","_hoisted_1","_createBlock","_component_model_viewer","_hoisted_2","_createElementVNode","_hoisted_4","_unref","actionIconSvg","_toDisplayString"],"mappings":";;;;;;;;;;;;;;;;;AAwBA,UAAMA,IAAQC,GAQRC,IAAiB,oBAAI,IAAI,CAAC,OAAO,QAAQ,MAAM,CAAC,GAEhDC,IAAcC,EAAS,MAAMF,EAAe,KAAKF,EAAM,OAAO,IAAI,YAAA,CAAa,CAAC,GAEhFK,IAAQC,EAAmB,IAAI,GAC/BC,IAAQD,EAAI,EAAK;AAEvB,mBAAeE,IAAsB;AAMnC,UALAD,EAAM,QAAQ,IACdF,EAAM,QAAQ,MAIV,CAACF,EAAY,OAAO;AACtB,QAAAE,EAAM,QAAQL,EAAM,IAChBA,EAAM,EAAE,8BAA8B,IACtC,eAAeA,EAAM,GAAG;AAC5B;AAAA,MACF;AACA,UAAI;AACF,cAAM;AAAA;AAAA,UAA0B;AAAA,QAAA,GAChCO,EAAM,QAAQ;AAAA,MAChB,QAAQ;AACN,QAAAF,EAAM,QAAQL,EAAM,IAChBA,EAAM,EAAE,2BAA2B,IACnC;AAAA,MACN;AAAA,IACF;AAaA,aAASS,IAAqB;AAC5B,MAAAF,EAAM,QAAQ,IACdF,EAAM,QAAQL,EAAM,IAAIA,EAAM,EAAE,uBAAuB,IAAI;AAAA,IAC7D;AAEA,IAAAU,EAAUF,CAAI,GAEdG,EAAM,MAAMX,EAAM,KAAK,MAAM;AAG3B,MAAAQ,EAAA;AAAA,IACF,CAAC;AASD,UAAMI,IAAWR,EAAS,MAAMS,EAAa,EAAE,MAAM,QAAQ,WAAWb,EAAM,IAAA,CAAK,CAAC;;;AAIlF,aAAAc,EAAA,GAAAC,EA0BM,OA1BNC,GA0BM;AAAA,QAxBIT,EAAA,UAAUF,EAAA,cADlBY,EAUEC,GAAA;AAAA;UARC,KAAKjB,EAAA;AAAA,UACN,eAAA;AAAA,UACA,mBAAA;AAAA,UACA,gBAAa;AAAA,UACb,oBAAiB;AAAA,UAChB,KAAKA,EAAA,MAAG;AAAA,UACT,OAAA,EAAA,OAAA,QAAA,QAAA,QAAA,cAAA,SAAA,SAAA,QAAA;AAAA,UACC,SAAOQ;AAAA,QAAA,+BAEMJ,EAAA,SAAhBS,EAAA,GAAAC,EAIM,OAJNI,GAIM;AAAA,UAFJC,EAAsF,QAAA;AAAA,YAAhF,OAAM;AAAA,YAA8B,eAAY;AAAA,YAAO,WAAQR,EAAA;AAAA,UAAA;UACrEQ,EAAkB,aAAZf,EAAA,KAAK,GAAA,CAAA;AAAA,QAAA,OAEbS,EAAA,GAAAC,EAQM,OARNM,GAQM;AAAA,UANJD,EAIQ,QAAA;AAAA,YAHN,OAAM;AAAA,YACN,eAAY;AAAA,YACZ,WAAQE,EAAAC,CAAA,EAAa,UAAA;AAAA,UAAA;UAEvBH,EAAiD,KAAA,MAAAI,EAA3CvB,EAAA,IAAIA,EAAA,EAAC,gBAAA,IAAA,UAAA,GAAA,CAAA;AAAA,QAAA;;;;;"}
|