@av-pi-studio/web-client 0.0.70 → 0.0.71
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/web/assets/{BinaryFallbackViewer-C7bkubDd.js → BinaryFallbackViewer-BWzvrIhq.js} +2 -2
- package/dist/web/assets/{BinaryFallbackViewer-C7bkubDd.js.map → BinaryFallbackViewer-BWzvrIhq.js.map} +1 -1
- package/dist/web/assets/{ChatPanel-B25eQsNb.js → ChatPanel-CEH_GK7N.js} +2 -2
- package/dist/web/assets/{ChatPanel-B25eQsNb.js.map → ChatPanel-CEH_GK7N.js.map} +1 -1
- package/dist/web/assets/{CodeView-B2eeZM2v.js → CodeView-vzOAirYe.js} +2 -2
- package/dist/web/assets/{CodeView-B2eeZM2v.js.map → CodeView-vzOAirYe.js.map} +1 -1
- package/dist/web/assets/{FilePanel-DRuJsAWY.js → FilePanel-WQVK2120.js} +2 -2
- package/dist/web/assets/{FilePanel-DRuJsAWY.js.map → FilePanel-WQVK2120.js.map} +1 -1
- package/dist/web/assets/{ImageViewer-1z3YGCmo.js → ImageViewer-Bc0i-JGb.js} +2 -2
- package/dist/web/assets/{ImageViewer-1z3YGCmo.js.map → ImageViewer-Bc0i-JGb.js.map} +1 -1
- package/dist/web/assets/{MarkdownFileViewer-9isSXHBa.js → MarkdownFileViewer-I0b0agu_.js} +2 -2
- package/dist/web/assets/{MarkdownFileViewer-9isSXHBa.js.map → MarkdownFileViewer-I0b0agu_.js.map} +1 -1
- package/dist/web/assets/{MoleculeViewerPanel-DjqSc6I8.js → MoleculeViewerPanel-DYKfUyuF.js} +2 -2
- package/dist/web/assets/{MoleculeViewerPanel-DjqSc6I8.js.map → MoleculeViewerPanel-DYKfUyuF.js.map} +1 -1
- package/dist/web/assets/{TerminalPanel-DZ68ikRv.js → TerminalPanel-Do6WsYb9.js} +2 -2
- package/dist/web/assets/{TerminalPanel-DZ68ikRv.js.map → TerminalPanel-Do6WsYb9.js.map} +1 -1
- package/dist/web/assets/{TextViewer-BX12dzDA.js → TextViewer-C7U5fAYp.js} +2 -2
- package/dist/web/assets/{TextViewer-BX12dzDA.js.map → TextViewer-C7U5fAYp.js.map} +1 -1
- package/dist/web/assets/{VideoViewer-I1FxdZq1.js → VideoViewer-DeIrV1Ql.js} +2 -2
- package/dist/web/assets/{VideoViewer-I1FxdZq1.js.map → VideoViewer-DeIrV1Ql.js.map} +1 -1
- package/dist/web/assets/{index-C7fzFj6R.js → index-DonKPQN3.js} +3 -3
- package/dist/web/assets/{index-C7fzFj6R.js.map → index-DonKPQN3.js.map} +1 -1
- package/dist/web/assets/{markdown-BkmQhx2q.js → markdown-DRKdD_IM.js} +2 -2
- package/dist/web/assets/{markdown-BkmQhx2q.js.map → markdown-DRKdD_IM.js.map} +1 -1
- package/dist/web/assets/{use-file-download-DZsKc-9t.js → use-file-download-C2Lrmbfe.js} +2 -2
- package/dist/web/assets/{use-file-download-DZsKc-9t.js.map → use-file-download-C2Lrmbfe.js.map} +1 -1
- package/dist/web/assets/{use-file-watch-D38_XnyT.js → use-file-watch-skGpUUS7.js} +2 -2
- package/dist/web/assets/{use-file-watch-D38_XnyT.js.map → use-file-watch-skGpUUS7.js.map} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +3 -3
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as b,j as r}from"./vendor-react-CWo7Do4s.js";import{r as k,E as d,S as u,B as w,P as h}from"./index-
|
|
2
|
-
//# sourceMappingURL=TextViewer-
|
|
1
|
+
import{r as b,j as r}from"./vendor-react-CWo7Do4s.js";import{r as k,E as d,S as u,B as w,P as h}from"./index-DonKPQN3.js";import{u as S,F as _,C as y}from"./CodeView-vzOAirYe.js";import{u as U}from"./vendor-query-BGIh-vlp.js";import{u as p}from"./use-file-download-C2Lrmbfe.js";import"./vendor-04vXFIXe.js";import"./vendor-markdown-YvkQLm_s.js";import"./vendor-highlight-Efc6A-g6.js";import"./vendor-icons-CJ_dOXcR.js";import"./vendor-overlays-BnmaUbKw.js";import"./vendor-dnd-Bj2_GhfD.js";function B(n){const{enabled:s,download:e,decode:o}=n;return{isLoading:s&&(e.isLoading||e.hasObjectUrl&&o.isLoading),isError:e.isError||o.isError,error:e.isError?e.error:o.error,data:o.data}}function F(n,s=!0){var a;const e=p(n,s),o=(a=e.data)==null?void 0:a.objectUrl,t=U({queryKey:k.fileText(n,o??null),queryFn:async()=>({content:await(await fetch(o)).text()}),enabled:s&&!!o});return B({enabled:s,download:{isLoading:e.isLoading,isError:e.isError,error:e.error,hasObjectUrl:!!o},decode:{isLoading:t.isLoading,isError:t.isError,error:t.error,data:t.data}})}function N(n){const{maxDisplayBytes:s,inline:e,streamed:o,download:t}=n;if(e.isLoading)return{kind:"loading"};if(e.tooLarge){const{size:a}=e.tooLarge;return a>s?{kind:"too-large",size:a,maxDisplayBytes:s,downloading:t.requested&&t.isLoading,downloadUrl:t.objectUrl,downloadName:t.fileName}:o.isLoading?{kind:"streaming",size:a}:o.isError?{kind:"stream-error",size:a,message:o.errorMessage??"stream failed"}:o.content!==null?{kind:"streamed",size:a,content:o.content}:{kind:"loading"}}return e.isError?{kind:"error",message:e.errorMessage??"unknown error"}:e.content!==null?{kind:"inline",content:e.content}:{kind:"loading"}}const D="_note_1g5yz_1",M="_body_1g5yz_9",T="_tooLarge_1g5yz_13",m={note:D,body:M,tooLarge:T},E=30*1024*1024;function l(n){return`${(n/(1024*1024)).toFixed(1)} MB`}function Q({path:n}){var f,x,j,L;const s=S(n),e=s.error instanceof _?s.error:null,o=e!==null&&e.size<=E,t=F(n,o),[a,g]=b.useState(!1),c=p(n,a),i=N({maxDisplayBytes:E,inline:{isLoading:s.isLoading,isError:s.isError,tooLarge:e?{size:e.size}:null,errorMessage:s.error instanceof Error?s.error.message:null,content:((f=s.data)==null?void 0:f.content)??null},streamed:{isLoading:t.isLoading,isError:t.isError,errorMessage:t.error instanceof Error?t.error.message:null,content:((x=t.data)==null?void 0:x.content)??null},download:{requested:a,isLoading:c.isLoading,objectUrl:((j=c.data)==null?void 0:j.objectUrl)??null,fileName:((L=c.data)==null?void 0:L.fileName)??null}});switch(i.kind){case"loading":return r.jsxs(d,{children:[r.jsx(u,{size:"sm"})," Loading..."]});case"inline":return r.jsx(y,{path:n,content:i.content});case"streaming":return r.jsxs(d,{children:[r.jsx(u,{size:"sm"})," Streaming ",l(i.size)," file..."]});case"stream-error":return r.jsxs(d,{children:["Error: ",i.message]});case"streamed":return r.jsxs(h,{children:[r.jsxs("div",{className:m.note,children:[l(i.size)," file streamed"]}),r.jsx("div",{className:m.body,children:r.jsx(y,{path:n,content:i.content})})]});case"too-large":{const z=n.split("/").pop()||n;return r.jsx(d,{children:r.jsxs("div",{className:m.tooLarge,children:[r.jsxs("div",{children:[l(i.size)," — too large to display (display ceiling is"," ",l(i.maxDisplayBytes),")."]}),i.downloading?r.jsx(u,{size:"sm"}):i.downloadUrl?r.jsx("a",{href:i.downloadUrl,download:i.downloadName||z,children:r.jsx(w,{size:"sm",children:"Save file"})}):r.jsx(w,{size:"sm",onClick:()=>g(!0),children:"Download"})]})})}case"error":return r.jsxs(d,{children:["Error: ",i.message]})}}export{E as MAX_DISPLAY_BYTES,Q as TextViewer};
|
|
2
|
+
//# sourceMappingURL=TextViewer-C7U5fAYp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextViewer-BX12dzDA.js","sources":["../../../src/hooks/file-text-state.ts","../../../src/hooks/use-file-text.ts","../../../src/features/files/text-viewer-state.ts","../../../src/features/files/TextViewer.tsx"],"sourcesContent":["/**\n * Pure query-state merge for `use-file-text` (task-009), kept DOM/React-free so the\n * loading/error/data composition across the two dependent queries it wraps (download → decode)\n * is unit-testable directly, without `renderHook` (no jsdom test environment in this repo).\n */\n\nexport interface FileTextResult {\n content: string;\n}\n\nexport interface FileTextMergeInputs {\n enabled: boolean;\n download: { isLoading: boolean; isError: boolean; error: unknown; hasObjectUrl: boolean };\n decode: {\n isLoading: boolean;\n isError: boolean;\n error: unknown;\n data: FileTextResult | undefined;\n };\n}\n\nexport interface FileTextQueryState {\n isLoading: boolean;\n isError: boolean;\n error: unknown;\n data: FileTextResult | undefined;\n}\n\nexport function mergeFileTextState(input: FileTextMergeInputs): FileTextQueryState {\n const { enabled, download, decode } = input;\n return {\n isLoading: enabled && (download.isLoading || (download.hasObjectUrl && decode.isLoading)),\n isError: download.isError || decode.isError,\n error: download.isError ? download.error : decode.error,\n data: decode.data,\n };\n}\n","/**\n * `use-file-text` — tier-2 fallback for `TextViewer` (task-009): decodes a file's bytes to UTF-8\n * text via the already-uncapped chunked binary download path (`useFileDownload` →\n * `file_download_token_request`/`file_download_request`), for files over `file_read_request`'s\n * inline cap (`MAX_INLINE_FILE_READ_BYTES`, `packages/server/src/files/limits.ts`). Same\n * transport the molecule viewer uses for its own (binary-safe) source — no new RPC, no new\n * transport, just a text decode over the resulting blob.\n *\n * Note the daemon asymmetry documented on `useFileDownload`'s own RPCs: the download path is\n * registered only in the production bootstrap (`bootstrap.ts`), not `dev-bootstrap.ts`. Under\n * `npm run dev:daemon` a file above the inline cap fails this fetch rather than rendering — that\n * surfaces as the normal error state below, which is the intended behavior (see task-009 notes).\n *\n * The loading/error/data merge across the two dependent queries (download → decode) is the pure,\n * DOM-free `mergeFileTextState` (`file-text-state.ts`) — this hook is a thin wrapper feeding it\n * live query state.\n */\n\nimport { useQuery } from \"@tanstack/react-query\";\nimport { rpcKeys } from \"@pi-studio-ui/lib/connection/rpc-keys.js\";\nimport { useFileDownload } from \"./use-file-download.js\";\nimport { mergeFileTextState, type FileTextResult } from \"./file-text-state.js\";\n\nexport type { FileTextResult };\n\nexport function useFileText(path: string, enabled = true) {\n const download = useFileDownload(path, enabled);\n const objectUrl = download.data?.objectUrl;\n\n const decode = useQuery({\n queryKey: rpcKeys.fileText(path, objectUrl ?? null),\n queryFn: async (): Promise<FileTextResult> => {\n const content = await (await fetch(objectUrl as string)).text();\n return { content };\n },\n enabled: enabled && Boolean(objectUrl),\n });\n\n return mergeFileTextState({\n enabled,\n download: {\n isLoading: download.isLoading,\n isError: download.isError,\n error: download.error,\n hasObjectUrl: Boolean(objectUrl),\n },\n decode: {\n isLoading: decode.isLoading,\n isError: decode.isError,\n error: decode.error,\n data: decode.data,\n },\n });\n}\n","/**\n * Pure tier-selection logic for `TextViewer` (task-009), kept in its own DOM-free module so the\n * three-state branch (inline / streamed / download-only) is unit-testable without mounting\n * CodeMirror or React — mirrors `molecule-reload.ts`'s `shouldApplyRefresh` extraction for the\n * same reason: this repo has no jsdom test environment configured anywhere.\n */\n\nexport type TextViewerState =\n | { kind: \"loading\" }\n | { kind: \"inline\"; content: string }\n | { kind: \"streaming\"; size: number }\n | { kind: \"streamed\"; size: number; content: string }\n | { kind: \"stream-error\"; size: number; message: string }\n | {\n kind: \"too-large\";\n size: number;\n maxDisplayBytes: number;\n downloading: boolean;\n downloadUrl: string | null;\n downloadName: string | null;\n }\n | { kind: \"error\"; message: string };\n\nexport interface TextViewerInputs {\n maxDisplayBytes: number;\n inline: {\n isLoading: boolean;\n isError: boolean;\n /** `{ size }` when the failure was specifically `FileTooLargeError`; `null` for a generic\n * read failure (network/permission/etc). */\n tooLarge: { size: number } | null;\n errorMessage: string | null;\n content: string | null;\n };\n streamed: {\n isLoading: boolean;\n isError: boolean;\n errorMessage: string | null;\n content: string | null;\n };\n download: {\n requested: boolean;\n isLoading: boolean;\n objectUrl: string | null;\n fileName: string | null;\n };\n}\n\n/** Selects exactly one `TextViewerState` from the current query states. Pure — no hooks, no I/O. */\nexport function selectTextViewerState(input: TextViewerInputs): TextViewerState {\n const { maxDisplayBytes, inline, streamed, download } = input;\n\n if (inline.isLoading) return { kind: \"loading\" };\n\n if (inline.tooLarge) {\n const { size } = inline.tooLarge;\n if (size > maxDisplayBytes) {\n return {\n kind: \"too-large\",\n size,\n maxDisplayBytes,\n downloading: download.requested && download.isLoading,\n downloadUrl: download.objectUrl,\n downloadName: download.fileName,\n };\n }\n if (streamed.isLoading) return { kind: \"streaming\", size };\n if (streamed.isError) {\n return { kind: \"stream-error\", size, message: streamed.errorMessage ?? \"stream failed\" };\n }\n if (streamed.content !== null) return { kind: \"streamed\", size, content: streamed.content };\n return { kind: \"loading\" };\n }\n\n if (inline.isError) {\n return { kind: \"error\", message: inline.errorMessage ?? \"unknown error\" };\n }\n\n if (inline.content !== null) return { kind: \"inline\", content: inline.content };\n return { kind: \"loading\" };\n}\n","/**\n * TextViewer — the default viewer for source/text files: fetches the UTF-8 preview via\n * `useFileRead` and renders it through `CodeView` (line gutter + Shiki highlighting). Registered\n * in `viewer-registry.ts` as the fallback for any file not claimed by a more specific viewer.\n *\n * Three size tiers (task-009 — raising `file_read_request`'s old 512 KiB ceiling):\n * 1. `size <= MAX_INLINE_FILE_READ_BYTES` (server-side, 5 MiB) — the `useFileRead` JSON round\n * trip above, unchanged.\n * 2. `MAX_INLINE_FILE_READ_BYTES < size <= MAX_DISPLAY_BYTES` (30 MiB) — transparently refetch via\n * the uncapped chunked binary download path (`use-file-text.ts`) and render the same\n * `CodeView`, with a muted note that the file was streamed rather than read inline.\n * 3. `size > MAX_DISPLAY_BYTES` — a terminal state: no render attempt, just the size, why, and a\n * download action (`BinaryFallbackViewer`'s existing on-demand download pattern).\n *\n * Which tier applies is the pure, DOM-free `selectTextViewerState` (`text-viewer-state.ts`) — this\n * component is a thin switch-render over it.\n */\n\nimport { useState } from \"react\";\nimport { Button } from \"@pi-studio-ui/components/primitives/Button.js\";\nimport { Spinner } from \"@pi-studio-ui/components/primitives/Spinner.js\";\nimport { Panel } from \"@pi-studio-ui/components/primitives/Panel.js\";\nimport { EmptyState } from \"@pi-studio-ui/components/primitives/EmptyState.js\";\nimport { useFileRead, FileTooLargeError } from \"@pi-studio-ui/hooks/use-file-read.js\";\nimport { useFileText } from \"@pi-studio-ui/hooks/use-file-text.js\";\nimport { useFileDownload } from \"@pi-studio-ui/hooks/use-file-download.js\";\nimport { CodeView } from \"./CodeView.js\";\nimport { selectTextViewerState } from \"./text-viewer-state.js\";\nimport type { ViewerProps } from \"./viewer-registry.js\";\nimport textStyles from \"./TextViewer.module.css\";\n\n/** Display ceiling above the inline cap. `CodeView`'s CodeMirror instance enables\n * `EditorView.lineWrapping`, which makes it measure line heights across the whole document —\n * well past interactive above this size, so files this large get a terminal download-only state\n * instead of an attempted render. Deliberately not configurable (task-009 notes). */\nexport const MAX_DISPLAY_BYTES = 30 * 1024 * 1024;\n\nfunction formatMegabytes(bytes: number): string {\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nexport function TextViewer({ path }: ViewerProps) {\n const inline = useFileRead(path);\n const tooLarge = inline.error instanceof FileTooLargeError ? inline.error : null;\n const displayable = tooLarge !== null && tooLarge.size <= MAX_DISPLAY_BYTES;\n\n const streamed = useFileText(path, displayable);\n const [downloadRequested, setDownloadRequested] = useState(false);\n const download = useFileDownload(path, downloadRequested);\n\n const state = selectTextViewerState({\n maxDisplayBytes: MAX_DISPLAY_BYTES,\n inline: {\n isLoading: inline.isLoading,\n isError: inline.isError,\n tooLarge: tooLarge ? { size: tooLarge.size } : null,\n errorMessage: inline.error instanceof Error ? inline.error.message : null,\n content: inline.data?.content ?? null,\n },\n streamed: {\n isLoading: streamed.isLoading,\n isError: streamed.isError,\n errorMessage: streamed.error instanceof Error ? streamed.error.message : null,\n content: streamed.data?.content ?? null,\n },\n download: {\n requested: downloadRequested,\n isLoading: download.isLoading,\n objectUrl: download.data?.objectUrl ?? null,\n fileName: download.data?.fileName ?? null,\n },\n });\n\n switch (state.kind) {\n case \"loading\":\n return (\n <EmptyState>\n <Spinner size=\"sm\" /> Loading...\n </EmptyState>\n );\n case \"inline\":\n return <CodeView path={path} content={state.content} />;\n case \"streaming\":\n return (\n <EmptyState>\n <Spinner size=\"sm\" /> Streaming {formatMegabytes(state.size)} file...\n </EmptyState>\n );\n case \"stream-error\":\n return <EmptyState>Error: {state.message}</EmptyState>;\n case \"streamed\":\n return (\n <Panel>\n <div className={textStyles.note}>{formatMegabytes(state.size)} file streamed</div>\n <div className={textStyles.body}>\n <CodeView path={path} content={state.content} />\n </div>\n </Panel>\n );\n case \"too-large\": {\n const name = path.split(\"/\").pop() || path;\n return (\n <EmptyState>\n <div className={textStyles.tooLarge}>\n <div>\n {formatMegabytes(state.size)} — too large to display (display ceiling is{\" \"}\n {formatMegabytes(state.maxDisplayBytes)}).\n </div>\n {state.downloading ? (\n <Spinner size=\"sm\" />\n ) : state.downloadUrl ? (\n <a href={state.downloadUrl} download={state.downloadName || name}>\n <Button size=\"sm\">Save file</Button>\n </a>\n ) : (\n <Button size=\"sm\" onClick={() => setDownloadRequested(true)}>\n Download\n </Button>\n )}\n </div>\n </EmptyState>\n );\n }\n case \"error\":\n return <EmptyState>Error: {state.message}</EmptyState>;\n }\n}\n"],"names":["mergeFileTextState","input","enabled","download","decode","useFileText","path","useFileDownload","objectUrl","_a","useQuery","rpcKeys","selectTextViewerState","maxDisplayBytes","inline","streamed","size","MAX_DISPLAY_BYTES","formatMegabytes","bytes","TextViewer","useFileRead","tooLarge","FileTooLargeError","displayable","downloadRequested","setDownloadRequested","useState","state","_b","_c","_d","EmptyState","jsx","Spinner","CodeView","Panel","jsxs","textStyles","name","Button"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TextViewer-C7U5fAYp.js","sources":["../../../src/hooks/file-text-state.ts","../../../src/hooks/use-file-text.ts","../../../src/features/files/text-viewer-state.ts","../../../src/features/files/TextViewer.tsx"],"sourcesContent":["/**\n * Pure query-state merge for `use-file-text` (task-009), kept DOM/React-free so the\n * loading/error/data composition across the two dependent queries it wraps (download → decode)\n * is unit-testable directly, without `renderHook` (no jsdom test environment in this repo).\n */\n\nexport interface FileTextResult {\n content: string;\n}\n\nexport interface FileTextMergeInputs {\n enabled: boolean;\n download: { isLoading: boolean; isError: boolean; error: unknown; hasObjectUrl: boolean };\n decode: {\n isLoading: boolean;\n isError: boolean;\n error: unknown;\n data: FileTextResult | undefined;\n };\n}\n\nexport interface FileTextQueryState {\n isLoading: boolean;\n isError: boolean;\n error: unknown;\n data: FileTextResult | undefined;\n}\n\nexport function mergeFileTextState(input: FileTextMergeInputs): FileTextQueryState {\n const { enabled, download, decode } = input;\n return {\n isLoading: enabled && (download.isLoading || (download.hasObjectUrl && decode.isLoading)),\n isError: download.isError || decode.isError,\n error: download.isError ? download.error : decode.error,\n data: decode.data,\n };\n}\n","/**\n * `use-file-text` — tier-2 fallback for `TextViewer` (task-009): decodes a file's bytes to UTF-8\n * text via the already-uncapped chunked binary download path (`useFileDownload` →\n * `file_download_token_request`/`file_download_request`), for files over `file_read_request`'s\n * inline cap (`MAX_INLINE_FILE_READ_BYTES`, `packages/server/src/files/limits.ts`). Same\n * transport the molecule viewer uses for its own (binary-safe) source — no new RPC, no new\n * transport, just a text decode over the resulting blob.\n *\n * Note the daemon asymmetry documented on `useFileDownload`'s own RPCs: the download path is\n * registered only in the production bootstrap (`bootstrap.ts`), not `dev-bootstrap.ts`. Under\n * `npm run dev:daemon` a file above the inline cap fails this fetch rather than rendering — that\n * surfaces as the normal error state below, which is the intended behavior (see task-009 notes).\n *\n * The loading/error/data merge across the two dependent queries (download → decode) is the pure,\n * DOM-free `mergeFileTextState` (`file-text-state.ts`) — this hook is a thin wrapper feeding it\n * live query state.\n */\n\nimport { useQuery } from \"@tanstack/react-query\";\nimport { rpcKeys } from \"@pi-studio-ui/lib/connection/rpc-keys.js\";\nimport { useFileDownload } from \"./use-file-download.js\";\nimport { mergeFileTextState, type FileTextResult } from \"./file-text-state.js\";\n\nexport type { FileTextResult };\n\nexport function useFileText(path: string, enabled = true) {\n const download = useFileDownload(path, enabled);\n const objectUrl = download.data?.objectUrl;\n\n const decode = useQuery({\n queryKey: rpcKeys.fileText(path, objectUrl ?? null),\n queryFn: async (): Promise<FileTextResult> => {\n const content = await (await fetch(objectUrl as string)).text();\n return { content };\n },\n enabled: enabled && Boolean(objectUrl),\n });\n\n return mergeFileTextState({\n enabled,\n download: {\n isLoading: download.isLoading,\n isError: download.isError,\n error: download.error,\n hasObjectUrl: Boolean(objectUrl),\n },\n decode: {\n isLoading: decode.isLoading,\n isError: decode.isError,\n error: decode.error,\n data: decode.data,\n },\n });\n}\n","/**\n * Pure tier-selection logic for `TextViewer` (task-009), kept in its own DOM-free module so the\n * three-state branch (inline / streamed / download-only) is unit-testable without mounting\n * CodeMirror or React — mirrors `molecule-reload.ts`'s `shouldApplyRefresh` extraction for the\n * same reason: this repo has no jsdom test environment configured anywhere.\n */\n\nexport type TextViewerState =\n | { kind: \"loading\" }\n | { kind: \"inline\"; content: string }\n | { kind: \"streaming\"; size: number }\n | { kind: \"streamed\"; size: number; content: string }\n | { kind: \"stream-error\"; size: number; message: string }\n | {\n kind: \"too-large\";\n size: number;\n maxDisplayBytes: number;\n downloading: boolean;\n downloadUrl: string | null;\n downloadName: string | null;\n }\n | { kind: \"error\"; message: string };\n\nexport interface TextViewerInputs {\n maxDisplayBytes: number;\n inline: {\n isLoading: boolean;\n isError: boolean;\n /** `{ size }` when the failure was specifically `FileTooLargeError`; `null` for a generic\n * read failure (network/permission/etc). */\n tooLarge: { size: number } | null;\n errorMessage: string | null;\n content: string | null;\n };\n streamed: {\n isLoading: boolean;\n isError: boolean;\n errorMessage: string | null;\n content: string | null;\n };\n download: {\n requested: boolean;\n isLoading: boolean;\n objectUrl: string | null;\n fileName: string | null;\n };\n}\n\n/** Selects exactly one `TextViewerState` from the current query states. Pure — no hooks, no I/O. */\nexport function selectTextViewerState(input: TextViewerInputs): TextViewerState {\n const { maxDisplayBytes, inline, streamed, download } = input;\n\n if (inline.isLoading) return { kind: \"loading\" };\n\n if (inline.tooLarge) {\n const { size } = inline.tooLarge;\n if (size > maxDisplayBytes) {\n return {\n kind: \"too-large\",\n size,\n maxDisplayBytes,\n downloading: download.requested && download.isLoading,\n downloadUrl: download.objectUrl,\n downloadName: download.fileName,\n };\n }\n if (streamed.isLoading) return { kind: \"streaming\", size };\n if (streamed.isError) {\n return { kind: \"stream-error\", size, message: streamed.errorMessage ?? \"stream failed\" };\n }\n if (streamed.content !== null) return { kind: \"streamed\", size, content: streamed.content };\n return { kind: \"loading\" };\n }\n\n if (inline.isError) {\n return { kind: \"error\", message: inline.errorMessage ?? \"unknown error\" };\n }\n\n if (inline.content !== null) return { kind: \"inline\", content: inline.content };\n return { kind: \"loading\" };\n}\n","/**\n * TextViewer — the default viewer for source/text files: fetches the UTF-8 preview via\n * `useFileRead` and renders it through `CodeView` (line gutter + Shiki highlighting). Registered\n * in `viewer-registry.ts` as the fallback for any file not claimed by a more specific viewer.\n *\n * Three size tiers (task-009 — raising `file_read_request`'s old 512 KiB ceiling):\n * 1. `size <= MAX_INLINE_FILE_READ_BYTES` (server-side, 5 MiB) — the `useFileRead` JSON round\n * trip above, unchanged.\n * 2. `MAX_INLINE_FILE_READ_BYTES < size <= MAX_DISPLAY_BYTES` (30 MiB) — transparently refetch via\n * the uncapped chunked binary download path (`use-file-text.ts`) and render the same\n * `CodeView`, with a muted note that the file was streamed rather than read inline.\n * 3. `size > MAX_DISPLAY_BYTES` — a terminal state: no render attempt, just the size, why, and a\n * download action (`BinaryFallbackViewer`'s existing on-demand download pattern).\n *\n * Which tier applies is the pure, DOM-free `selectTextViewerState` (`text-viewer-state.ts`) — this\n * component is a thin switch-render over it.\n */\n\nimport { useState } from \"react\";\nimport { Button } from \"@pi-studio-ui/components/primitives/Button.js\";\nimport { Spinner } from \"@pi-studio-ui/components/primitives/Spinner.js\";\nimport { Panel } from \"@pi-studio-ui/components/primitives/Panel.js\";\nimport { EmptyState } from \"@pi-studio-ui/components/primitives/EmptyState.js\";\nimport { useFileRead, FileTooLargeError } from \"@pi-studio-ui/hooks/use-file-read.js\";\nimport { useFileText } from \"@pi-studio-ui/hooks/use-file-text.js\";\nimport { useFileDownload } from \"@pi-studio-ui/hooks/use-file-download.js\";\nimport { CodeView } from \"./CodeView.js\";\nimport { selectTextViewerState } from \"./text-viewer-state.js\";\nimport type { ViewerProps } from \"./viewer-registry.js\";\nimport textStyles from \"./TextViewer.module.css\";\n\n/** Display ceiling above the inline cap. `CodeView`'s CodeMirror instance enables\n * `EditorView.lineWrapping`, which makes it measure line heights across the whole document —\n * well past interactive above this size, so files this large get a terminal download-only state\n * instead of an attempted render. Deliberately not configurable (task-009 notes). */\nexport const MAX_DISPLAY_BYTES = 30 * 1024 * 1024;\n\nfunction formatMegabytes(bytes: number): string {\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nexport function TextViewer({ path }: ViewerProps) {\n const inline = useFileRead(path);\n const tooLarge = inline.error instanceof FileTooLargeError ? inline.error : null;\n const displayable = tooLarge !== null && tooLarge.size <= MAX_DISPLAY_BYTES;\n\n const streamed = useFileText(path, displayable);\n const [downloadRequested, setDownloadRequested] = useState(false);\n const download = useFileDownload(path, downloadRequested);\n\n const state = selectTextViewerState({\n maxDisplayBytes: MAX_DISPLAY_BYTES,\n inline: {\n isLoading: inline.isLoading,\n isError: inline.isError,\n tooLarge: tooLarge ? { size: tooLarge.size } : null,\n errorMessage: inline.error instanceof Error ? inline.error.message : null,\n content: inline.data?.content ?? null,\n },\n streamed: {\n isLoading: streamed.isLoading,\n isError: streamed.isError,\n errorMessage: streamed.error instanceof Error ? streamed.error.message : null,\n content: streamed.data?.content ?? null,\n },\n download: {\n requested: downloadRequested,\n isLoading: download.isLoading,\n objectUrl: download.data?.objectUrl ?? null,\n fileName: download.data?.fileName ?? null,\n },\n });\n\n switch (state.kind) {\n case \"loading\":\n return (\n <EmptyState>\n <Spinner size=\"sm\" /> Loading...\n </EmptyState>\n );\n case \"inline\":\n return <CodeView path={path} content={state.content} />;\n case \"streaming\":\n return (\n <EmptyState>\n <Spinner size=\"sm\" /> Streaming {formatMegabytes(state.size)} file...\n </EmptyState>\n );\n case \"stream-error\":\n return <EmptyState>Error: {state.message}</EmptyState>;\n case \"streamed\":\n return (\n <Panel>\n <div className={textStyles.note}>{formatMegabytes(state.size)} file streamed</div>\n <div className={textStyles.body}>\n <CodeView path={path} content={state.content} />\n </div>\n </Panel>\n );\n case \"too-large\": {\n const name = path.split(\"/\").pop() || path;\n return (\n <EmptyState>\n <div className={textStyles.tooLarge}>\n <div>\n {formatMegabytes(state.size)} — too large to display (display ceiling is{\" \"}\n {formatMegabytes(state.maxDisplayBytes)}).\n </div>\n {state.downloading ? (\n <Spinner size=\"sm\" />\n ) : state.downloadUrl ? (\n <a href={state.downloadUrl} download={state.downloadName || name}>\n <Button size=\"sm\">Save file</Button>\n </a>\n ) : (\n <Button size=\"sm\" onClick={() => setDownloadRequested(true)}>\n Download\n </Button>\n )}\n </div>\n </EmptyState>\n );\n }\n case \"error\":\n return <EmptyState>Error: {state.message}</EmptyState>;\n }\n}\n"],"names":["mergeFileTextState","input","enabled","download","decode","useFileText","path","useFileDownload","objectUrl","_a","useQuery","rpcKeys","selectTextViewerState","maxDisplayBytes","inline","streamed","size","MAX_DISPLAY_BYTES","formatMegabytes","bytes","TextViewer","useFileRead","tooLarge","FileTooLargeError","displayable","downloadRequested","setDownloadRequested","useState","state","_b","_c","_d","EmptyState","jsx","Spinner","CodeView","Panel","jsxs","textStyles","name","Button"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{j as o}from"./vendor-react-CWo7Do4s.js";import{E as e,S as t}from"./index-
|
|
2
|
-
//# sourceMappingURL=VideoViewer-
|
|
1
|
+
import{j as o}from"./vendor-react-CWo7Do4s.js";import{E as e,S as t}from"./index-DonKPQN3.js";import{u as n}from"./use-file-download-C2Lrmbfe.js";import"./vendor-query-BGIh-vlp.js";import"./vendor-04vXFIXe.js";import"./vendor-markdown-YvkQLm_s.js";import"./vendor-highlight-Efc6A-g6.js";import"./vendor-icons-CJ_dOXcR.js";import"./vendor-overlays-BnmaUbKw.js";import"./vendor-dnd-Bj2_GhfD.js";const a="_wrap_gy3e6_1",m="_video_gy3e6_10",i={wrap:a,video:m};function E({path:s}){const r=n(s);return r.isLoading?o.jsxs(e,{children:[o.jsx(t,{size:"sm"})," Loading video..."]}):r.isError?o.jsxs(e,{children:["Error: ",r.error instanceof Error?r.error.message:"unknown error"]}):r.data?o.jsx("div",{className:i.wrap,children:o.jsx("video",{className:i.video,src:r.data.objectUrl,controls:!0})}):null}export{E as VideoViewer};
|
|
2
|
+
//# sourceMappingURL=VideoViewer-DeIrV1Ql.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoViewer-
|
|
1
|
+
{"version":3,"file":"VideoViewer-DeIrV1Ql.js","sources":["../../../src/features/files/VideoViewer.tsx"],"sourcesContent":["/**\n * VideoViewer — renders a binary video file via the file-transfer binary download\n * (`useFileDownload`) into a native `<video>` element (POC_TO_APP_PLAN_UI.md §4.5 follow-up:\n * modular file preview).\n */\n\nimport { Spinner } from \"@pi-studio-ui/components/primitives/Spinner.js\";\nimport { EmptyState } from \"@pi-studio-ui/components/primitives/EmptyState.js\";\nimport { useFileDownload } from \"@pi-studio-ui/hooks/use-file-download.js\";\nimport type { ViewerProps } from \"./viewer-registry.js\";\nimport styles from \"./VideoViewer.module.css\";\n\nexport function VideoViewer({ path }: ViewerProps) {\n const query = useFileDownload(path);\n\n if (query.isLoading) {\n return (\n <EmptyState>\n <Spinner size=\"sm\" /> Loading video...\n </EmptyState>\n );\n }\n if (query.isError) {\n return (\n <EmptyState>\n Error: {query.error instanceof Error ? query.error.message : \"unknown error\"}\n </EmptyState>\n );\n }\n if (!query.data) return null;\n\n return (\n <div className={styles.wrap}>\n {/* eslint-disable-next-line jsx-a11y/media-has-caption -- arbitrary local file, no caption track available */}\n <video className={styles.video} src={query.data.objectUrl} controls />\n </div>\n );\n}\n"],"names":["VideoViewer","path","query","useFileDownload","EmptyState","jsx","Spinner","styles"],"mappings":"wcAYO,SAASA,EAAY,CAAE,KAAAC,GAAqB,CACjD,MAAMC,EAAQC,EAAgBF,CAAI,EAElC,OAAIC,EAAM,iBAELE,EAAA,CACC,SAAA,CAAAC,EAAAA,IAACC,EAAA,CAAQ,KAAK,IAAA,CAAK,EAAE,mBAAA,EACvB,EAGAJ,EAAM,eAELE,EAAA,CAAW,SAAA,CAAA,UACFF,EAAM,iBAAiB,MAAQA,EAAM,MAAM,QAAU,eAAA,EAC/D,EAGCA,EAAM,WAGR,MAAA,CAAI,UAAWK,EAAO,KAErB,eAAC,QAAA,CAAM,UAAWA,EAAO,MAAO,IAAKL,EAAM,KAAK,UAAW,SAAQ,GAAC,EACtE,EANsB,IAQ1B"}
|