@opengeni/react 7.4.0-canary.36844267522001 → 7.4.0

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.
Files changed (113) hide show
  1. package/README.md +0 -10
  2. package/dist/artifacts-document.js +2 -3
  3. package/dist/artifacts-presentation.js +2 -3
  4. package/dist/artifacts-spreadsheet.js +2 -2
  5. package/dist/artifacts.d.ts +0 -1
  6. package/dist/artifacts.js +1402 -505
  7. package/dist/artifacts.js.map +1 -1
  8. package/dist/{browser-viewer-AJO7LMVE.js → browser-viewer-2E6R2Y4W.js} +2 -2
  9. package/dist/{chunk-WUISZ3WU.js → chunk-3E2AITTZ.js} +28 -32
  10. package/dist/{chunk-WUISZ3WU.js.map → chunk-3E2AITTZ.js.map} +1 -1
  11. package/dist/{chunk-Y2PRMYLC.js → chunk-DUEBTL3N.js} +3 -4
  12. package/dist/{chunk-Y2PRMYLC.js.map → chunk-DUEBTL3N.js.map} +1 -1
  13. package/dist/{chunk-7UAMYXJY.js → chunk-KNDIXZN6.js} +18 -25
  14. package/dist/{chunk-7UAMYXJY.js.map → chunk-KNDIXZN6.js.map} +1 -1
  15. package/dist/{chunk-XVYRQ4PK.js → chunk-L4OSXQ6H.js} +116 -277
  16. package/dist/chunk-L4OSXQ6H.js.map +1 -0
  17. package/dist/{chunk-7B44WVGH.js → chunk-S5JQ4R22.js} +12 -31
  18. package/dist/chunk-S5JQ4R22.js.map +1 -0
  19. package/dist/{chunk-A4UJB2MM.js → chunk-SEEVCT36.js} +2 -2
  20. package/dist/chunk-UGMKPUEM.js +299 -0
  21. package/dist/chunk-UGMKPUEM.js.map +1 -0
  22. package/dist/{chunk-OQVBTOBJ.js → chunk-USXVQACU.js} +3 -3
  23. package/dist/{chunk-ASCSKOWU.js → chunk-V2ABIVYJ.js} +3 -50
  24. package/dist/chunk-V2ABIVYJ.js.map +1 -0
  25. package/dist/{chunk-UQUR5WG5.js → chunk-X4HIN6TV.js} +157 -66
  26. package/dist/chunk-X4HIN6TV.js.map +1 -0
  27. package/dist/{chunk-QLG6W33X.js → chunk-XEJEOAOV.js} +35 -46
  28. package/dist/chunk-XEJEOAOV.js.map +1 -0
  29. package/dist/components/artifacts/editable-artifact-ui.d.ts +1 -2
  30. package/dist/components/artifacts/index.d.ts +0 -7
  31. package/dist/components/message-timeline.d.ts +1 -7
  32. package/dist/components/open-geni-links.d.ts +0 -16
  33. package/dist/components/session-conversation.d.ts +1 -13
  34. package/dist/{computer-viewer-NB6O6BTV.js → computer-viewer-WMJE6OA3.js} +2 -2
  35. package/dist/connect.js +1 -2
  36. package/dist/connect.js.map +1 -1
  37. package/dist/hooks/latest-question.d.ts +1 -1
  38. package/dist/index.d.ts +2 -3
  39. package/dist/index.js +12 -22
  40. package/dist/index.js.map +1 -1
  41. package/dist/interaction.js +2 -2
  42. package/dist/{latest-question-3HGQUI2E.js → latest-question-QB4EUCW5.js} +2 -2
  43. package/dist/{markdown-table-layout-QXSLK4BY.js → markdown-table-layout-PFUEZ2U4.js} +5 -37
  44. package/dist/markdown-table-layout-PFUEZ2U4.js.map +1 -0
  45. package/dist/session-ui.d.ts +2 -3
  46. package/dist/session-ui.js +9 -18
  47. package/dist/session-ui.js.map +1 -1
  48. package/dist/session.js +2 -2
  49. package/dist/sites.js +289 -4
  50. package/dist/sites.js.map +1 -1
  51. package/package.json +3 -3
  52. package/src/artifacts.ts +0 -37
  53. package/src/components/artifacts/editable-artifact-ui.tsx +1 -3
  54. package/src/components/artifacts/editable-artifact-workbench.tsx +2 -5
  55. package/src/components/artifacts/editable-document.tsx +36 -34
  56. package/src/components/artifacts/editable-presentation.tsx +16 -21
  57. package/src/components/artifacts/index.ts +0 -41
  58. package/src/components/browser-viewer.tsx +20 -59
  59. package/src/components/computer-viewer.tsx +23 -51
  60. package/src/components/markdown-table-layout.ts +5 -49
  61. package/src/components/markdown.tsx +2 -5
  62. package/src/components/message-timeline.tsx +7 -59
  63. package/src/components/open-geni-links.tsx +0 -24
  64. package/src/components/session-conversation.tsx +20 -120
  65. package/src/index.ts +0 -7
  66. package/src/session-ui.ts +0 -7
  67. package/src/timeline/projection.ts +4 -54
  68. package/styles/compiled.css +0 -220
  69. package/dist/chat-interactive-block-VAGABN7J.js +0 -11
  70. package/dist/chunk-5CVXZA3Y.js +0 -292
  71. package/dist/chunk-5CVXZA3Y.js.map +0 -1
  72. package/dist/chunk-7B44WVGH.js.map +0 -1
  73. package/dist/chunk-ASCSKOWU.js.map +0 -1
  74. package/dist/chunk-QLG6W33X.js.map +0 -1
  75. package/dist/chunk-RAIQYITC.js +0 -1772
  76. package/dist/chunk-RAIQYITC.js.map +0 -1
  77. package/dist/chunk-SCOTPE2I.js +0 -589
  78. package/dist/chunk-SCOTPE2I.js.map +0 -1
  79. package/dist/chunk-UQUR5WG5.js.map +0 -1
  80. package/dist/chunk-WEHMPOXK.js +0 -39
  81. package/dist/chunk-WEHMPOXK.js.map +0 -1
  82. package/dist/chunk-XCHWDJ22.js +0 -107
  83. package/dist/chunk-XCHWDJ22.js.map +0 -1
  84. package/dist/chunk-XVYRQ4PK.js.map +0 -1
  85. package/dist/components/artifacts/artifact-chrome.d.ts +0 -120
  86. package/dist/components/artifacts/artifact-sandbox.d.ts +0 -27
  87. package/dist/components/artifacts/chat-interactive-block.d.ts +0 -29
  88. package/dist/components/artifacts/deferred-chat-media.d.ts +0 -15
  89. package/dist/components/artifacts/editable-artifact-view.d.ts +0 -46
  90. package/dist/components/artifacts/latest-task-runner.d.ts +0 -15
  91. package/dist/components/artifacts/session-artifact-viewer.d.ts +0 -39
  92. package/dist/components/artifacts/site-view.d.ts +0 -23
  93. package/dist/components/timeline-pill-geometry.d.ts +0 -6
  94. package/dist/components/timeline-pill-placement.d.ts +0 -18
  95. package/dist/computer-viewer-NB6O6BTV.js.map +0 -1
  96. package/dist/markdown-table-layout-QXSLK4BY.js.map +0 -1
  97. package/dist/timeline-pill-geometry-2G6EK47V.js +0 -73
  98. package/dist/timeline-pill-geometry-2G6EK47V.js.map +0 -1
  99. package/src/components/artifacts/artifact-chrome.tsx +0 -413
  100. package/src/components/artifacts/artifact-sandbox.tsx +0 -185
  101. package/src/components/artifacts/chat-interactive-block.tsx +0 -260
  102. package/src/components/artifacts/deferred-chat-media.tsx +0 -67
  103. package/src/components/artifacts/editable-artifact-view.tsx +0 -187
  104. package/src/components/artifacts/latest-task-runner.ts +0 -49
  105. package/src/components/artifacts/session-artifact-viewer.tsx +0 -376
  106. package/src/components/artifacts/site-view.tsx +0 -116
  107. package/src/components/timeline-pill-geometry.ts +0 -112
  108. package/src/components/timeline-pill-placement.ts +0 -81
  109. /package/dist/{browser-viewer-AJO7LMVE.js.map → browser-viewer-2E6R2Y4W.js.map} +0 -0
  110. /package/dist/{chunk-A4UJB2MM.js.map → chunk-SEEVCT36.js.map} +0 -0
  111. /package/dist/{chunk-OQVBTOBJ.js.map → chunk-USXVQACU.js.map} +0 -0
  112. /package/dist/{chat-interactive-block-VAGABN7J.js.map → computer-viewer-WMJE6OA3.js.map} +0 -0
  113. /package/dist/{latest-question-3HGQUI2E.js.map → latest-question-QB4EUCW5.js.map} +0 -0
@@ -1,260 +0,0 @@
1
- import type { OpenGeniClient, ToolGatewayIdentity } from "@opengeni/sdk";
2
- import { useEffect, useMemo, useState } from "react";
3
-
4
- import { loadSiteSnapshot } from "../../sites-ui";
5
- import { useOpenGeniLinkResolver } from "../open-geni-links";
6
- import {
7
- ArtifactButton,
8
- ArtifactLabelsProvider,
9
- ArtifactSelect,
10
- useArtifactLabels,
11
- type ArtifactLabels,
12
- } from "./artifact-chrome";
13
- import { ArtifactSandbox } from "./artifact-sandbox";
14
- import { DeferredChatMedia } from "./deferred-chat-media";
15
- import { inlineHtmlDocument } from "./inline-html-document";
16
- import type { PublishedHtmlArtifactToolBridge } from "./published-html-artifact-frame";
17
-
18
- // Fixed chat viewports keep async Site loads and fragment resize messages from
19
- // moving the conversation. Full-screen remains available for larger content.
20
- const INLINE_PREVIEW_HEIGHT = 360;
21
- const SITE_PREVIEW_HEIGHT = 400;
22
- const PREVIEW_CHROME_HEIGHT = 50;
23
-
24
- /** Reads a Site needs to render; the console client, the SDK client, or a proxy. */
25
- export type SiteSnapshotClient = Pick<
26
- OpenGeniClient,
27
- "getWorkspaceArtifact" | "getWorkspaceArtifactHtml"
28
- > &
29
- Partial<Pick<OpenGeniClient, "getWorkspaceArtifactContent">>;
30
-
31
- /** The exact saved Site a tool bridge is pinned to (absent for inline HTML). */
32
- export type SiteToolScope = {
33
- artifactId: string;
34
- siteVersionId: string;
35
- requestedTools: readonly ToolGatewayIdentity[];
36
- };
37
-
38
- /** Host-owned Site API access. Omit it and Sites render without workspace tools. */
39
- export type SiteToolBridgeFactory = (
40
- site?: SiteToolScope,
41
- ) => PublishedHtmlArtifactToolBridge | undefined;
42
-
43
- export type ChatInteractiveBlockProps = {
44
- workspaceId: string;
45
- kind: "html" | "site";
46
- content: string;
47
- client: SiteSnapshotClient;
48
- toolBridge?: SiteToolBridgeFactory | undefined;
49
- theme?: "light" | "dark" | undefined;
50
- /** Translations for the preview's copy; defaults to English. */
51
- labels?: Partial<ArtifactLabels> | undefined;
52
- };
53
-
54
- /**
55
- * Inline preview for an assistant `opengeni-html` or `opengeni-site` fence, as
56
- * the OpenGeni console renders it. "Open Site" goes through the nearest
57
- * `resolveLink`, so a host decides where a Site opens.
58
- */
59
- export function ChatInteractiveBlock({ labels, ...props }: ChatInteractiveBlockProps) {
60
- return (
61
- <ArtifactLabelsProvider labels={labels}>
62
- <InteractiveBlock {...props} />
63
- </ArtifactLabelsProvider>
64
- );
65
- }
66
-
67
- function InteractiveBlock(props: ChatInteractiveBlockProps) {
68
- const labels = useArtifactLabels();
69
- const height =
70
- (props.kind === "html" ? INLINE_PREVIEW_HEIGHT : SITE_PREVIEW_HEIGHT) + PREVIEW_CHROME_HEIGHT;
71
- return (
72
- <DeferredChatMedia
73
- key={`${props.workspaceId}:${props.kind}:${props.kind === "site" ? props.content : "inline"}`}
74
- height={height}
75
- actionLabel={props.kind === "html" ? labels.loadPreview : labels.loadSitePreview}
76
- >
77
- <div style={{ height }}>
78
- <LoadedChatInteractiveBlock {...props} />
79
- </div>
80
- </DeferredChatMedia>
81
- );
82
- }
83
-
84
- function LoadedChatInteractiveBlock(props: ChatInteractiveBlockProps) {
85
- const labels = useArtifactLabels();
86
- if (props.kind === "html") return <InlineHtml {...props} />;
87
- try {
88
- const value = JSON.parse(props.content);
89
- if (
90
- !value ||
91
- typeof value.siteId !== "string" ||
92
- !/^[0-9a-f-]{36}$/i.test(value.siteId) ||
93
- (value.versionId !== undefined &&
94
- (typeof value.versionId !== "string" || !/^[0-9a-f-]{36}$/i.test(value.versionId)))
95
- )
96
- throw new Error("Invalid Site reference");
97
- return (
98
- <SiteEmbed
99
- key={`${props.workspaceId}:${value.siteId}:${value.versionId ?? "current"}`}
100
- {...props}
101
- siteId={value.siteId}
102
- initialVersionId={value.versionId}
103
- />
104
- );
105
- } catch {
106
- return <p role="alert">{labels.siteReferenceInvalid}</p>;
107
- }
108
- }
109
-
110
- function InlineHtml({ content, toolBridge, theme }: ChatInteractiveBlockProps) {
111
- const labels = useArtifactLabels();
112
- const html = useMemo(() => inlineHtmlDocument(content), [content]);
113
- const bridge = useMemo(() => toolBridge?.(), [toolBridge]);
114
- return (
115
- <ArtifactSandbox
116
- title={labels.preview}
117
- showTitle={false}
118
- showLiveStatus={false}
119
- html={html}
120
- toolBridge={bridge}
121
- height={INLINE_PREVIEW_HEIGHT}
122
- className="h-full"
123
- fill
124
- theme={theme}
125
- />
126
- );
127
- }
128
-
129
- function SiteEmbed(
130
- props: ChatInteractiveBlockProps & { siteId: string; initialVersionId?: string },
131
- ) {
132
- const [versionId, setVersionId] = useState(props.initialVersionId);
133
- return (
134
- <SiteEmbedContent
135
- key={versionId ?? "current"}
136
- {...props}
137
- versionId={versionId}
138
- onVersionChange={setVersionId}
139
- />
140
- );
141
- }
142
-
143
- function SiteEmbedContent({
144
- workspaceId,
145
- client,
146
- toolBridge,
147
- theme,
148
- siteId,
149
- versionId,
150
- onVersionChange,
151
- }: ChatInteractiveBlockProps & {
152
- siteId: string;
153
- versionId?: string | undefined;
154
- onVersionChange: (id: string) => void;
155
- }) {
156
- const [loaded, setLoaded] = useState<{
157
- client: SiteSnapshotClient;
158
- snapshot: Awaited<ReturnType<typeof loadSiteSnapshot>>;
159
- } | null>(null);
160
- const snapshot = loaded?.client === client ? loaded.snapshot : null;
161
- const [error, setError] = useState(false);
162
- const [retry, setRetry] = useState(0);
163
- useEffect(() => {
164
- const abort = new AbortController();
165
- setError(false);
166
- setLoaded(null);
167
- void loadSiteSnapshot(client, workspaceId, siteId, {
168
- signal: abort.signal,
169
- ...(versionId ? { versionId } : {}),
170
- })
171
- .then((value) => {
172
- if (!abort.signal.aborted) setLoaded({ client, snapshot: value });
173
- })
174
- .catch(() => {
175
- if (!abort.signal.aborted) setError(true);
176
- });
177
- return () => abort.abort();
178
- }, [client, workspaceId, siteId, versionId, retry]);
179
- const content = snapshot?.content;
180
- const bridge = useMemo(
181
- () =>
182
- content
183
- ? toolBridge?.({
184
- artifactId: siteId,
185
- siteVersionId: content.versionId,
186
- requestedTools: content.requestedTools,
187
- })
188
- : undefined,
189
- [toolBridge, siteId, content],
190
- );
191
- const open = useOpenGeniLinkResolver()?.({ kind: "site", artifactId: siteId, workspaceId });
192
- const labels = useArtifactLabels();
193
- if (error)
194
- return (
195
- <p role="alert">
196
- {labels.siteLoadFailed}{" "}
197
- <ArtifactButton size="sm" onClick={() => setRetry((v) => v + 1)}>
198
- {labels.retry}
199
- </ArtifactButton>
200
- </p>
201
- );
202
- if (!snapshot) return <p role="status">{labels.loadingSite}</p>;
203
- if (!content) return <p>{labels.siteUnpublished}</p>;
204
- return (
205
- <ArtifactSandbox
206
- title={snapshot.detail.artifact.title}
207
- html={content.html}
208
- toolBridge={bridge}
209
- height={SITE_PREVIEW_HEIGHT}
210
- className="h-full"
211
- fill
212
- theme={theme}
213
- headerControls={
214
- <>
215
- {/* One published version has nothing to choose; keep the bar for the title. */}
216
- {snapshot.detail.versions.length > 1 ||
217
- !snapshot.detail.versions.some((v) => v.id === content.versionId) ? (
218
- <ArtifactSelect
219
- aria-label={labels.siteVersion}
220
- className="bg-transparent text-xs"
221
- value={content.versionId}
222
- onChange={(e) => onVersionChange(e.target.value)}
223
- >
224
- {!snapshot.detail.versions.some((v) => v.id === content.versionId) && (
225
- <option value={content.versionId}>{labels.savedVersion}</option>
226
- )}
227
- {snapshot.detail.versions.map((v) => (
228
- <option key={v.id} value={v.id}>
229
- {labels.version(v.revision)}
230
- </option>
231
- ))}
232
- </ArtifactSelect>
233
- ) : null}
234
- {open?.href ? (
235
- <a
236
- className="shrink-0 whitespace-nowrap text-xs underline"
237
- href={open.href}
238
- data-og-open-site=""
239
- >
240
- {labels.openSite}
241
- </a>
242
- ) : open?.open ? (
243
- <button
244
- type="button"
245
- className="shrink-0 cursor-pointer whitespace-nowrap text-xs underline"
246
- data-og-open-site=""
247
- onClick={() =>
248
- void Promise.resolve()
249
- .then(open.open)
250
- .catch(() => undefined)
251
- }
252
- >
253
- {labels.openSite}
254
- </button>
255
- ) : null}
256
- </>
257
- }
258
- />
259
- );
260
- }
@@ -1,67 +0,0 @@
1
- import { useEffect, useRef, useState, type ReactNode } from "react";
2
-
3
- /**
4
- * Reserve the chat slot before starting expensive media work. Activation is
5
- * one-way: scrolling away must not discard an interactive preview's state.
6
- * The timeline parks at its initial tip in layout effects before this observer
7
- * is attached, so historical previews above the entry viewport stay dormant.
8
- */
9
- export function DeferredChatMedia({
10
- children,
11
- height,
12
- label,
13
- actionLabel,
14
- }: {
15
- children: ReactNode;
16
- height: number;
17
- /** What is loading, as in "Load {label}". */
18
- label?: string | undefined;
19
- /** The complete (translated) activation label; overrides `label`. */
20
- actionLabel?: string | undefined;
21
- }) {
22
- const host = useRef<HTMLDivElement>(null);
23
- const [active, setActive] = useState(false);
24
- useEffect(() => {
25
- const node = host.current;
26
- if (active || !node) return;
27
- if (typeof IntersectionObserver === "undefined") {
28
- setActive(true);
29
- return;
30
- }
31
- let disposed = false;
32
- const observer = new IntersectionObserver(
33
- (entries) => {
34
- if (disposed || !entries.some((entry) => entry.isIntersecting)) return;
35
- setActive(true);
36
- observer.disconnect();
37
- },
38
- {
39
- root: node.closest("[data-og-timeline-scroller]"),
40
- rootMargin: "200px 0px",
41
- },
42
- );
43
- observer.observe(node);
44
- return () => {
45
- disposed = true;
46
- observer.disconnect();
47
- };
48
- }, [active]);
49
-
50
- return (
51
- <div ref={host} data-chat-media={active ? "active" : "deferred"}>
52
- {active ? (
53
- children
54
- ) : (
55
- <div className="flex w-full items-center justify-center" style={{ height }}>
56
- <button
57
- type="button"
58
- className="rounded-md px-3 py-2 text-sm text-fg-muted hover:text-fg focus-visible:outline-2 focus-visible:outline-ring"
59
- onClick={() => setActive(true)}
60
- >
61
- {actionLabel ?? `Load ${label ?? "media"}`}
62
- </button>
63
- </div>
64
- )}
65
- </div>
66
- );
67
- }
@@ -1,187 +0,0 @@
1
- import type { EditableArtifactResource } from "@opengeni/sdk/artifacts";
2
- import {
3
- createBrowserEditableArtifactSession,
4
- type CreateBrowserEditableArtifactSessionOptions,
5
- type EditableArtifactBrowserRuntime,
6
- type EditableArtifactCacheAuthority,
7
- type EditableArtifactModality,
8
- } from "@opengeni/sdk/editable-artifacts";
9
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
10
-
11
- import {
12
- ArtifactLoading,
13
- ArtifactProblem,
14
- artifactLoadErrorView,
15
- useArtifactLabels,
16
- type ArtifactLoadErrorView,
17
- } from "./artifact-chrome";
18
- import { EditableArtifactWorkbenchHost } from "./editable-artifact-workbench";
19
-
20
- /**
21
- * The browser kernels an editor needs, one per modality, plus the host-built
22
- * Worker URL (for Vite: `@opengeni/sdk/editable-artifacts/worker?worker&url`).
23
- * Kernels come from `@opengeni/artifact-kernel-wasm-{document,spreadsheet,presentation}`.
24
- */
25
- export type EditableArtifactRuntimes = Readonly<{
26
- document: Omit<EditableArtifactBrowserRuntime, "workerUrl" | "workerFactory">;
27
- spreadsheet: Omit<EditableArtifactBrowserRuntime, "workerUrl" | "workerFactory">;
28
- presentation: Omit<EditableArtifactBrowserRuntime, "workerUrl" | "workerFactory">;
29
- workerUrl: string;
30
- applicationOrigin?: string | undefined;
31
- /** Loopback development only. */
32
- allowInsecureDevelopmentAssets?: boolean | undefined;
33
- }>;
34
-
35
- /** Everything needed to open one artifact for the current principal. */
36
- export type OpenedEditableArtifact = Readonly<{
37
- artifact: EditableArtifactResource;
38
- authority: EditableArtifactCacheAuthority;
39
- replicaId: string;
40
- }>;
41
-
42
- export type EditableArtifactViewProps = Readonly<{
43
- /** Absolute API (or session proxy) base the live transport talks to. */
44
- baseUrl: URL;
45
- workspaceId: string;
46
- artifactId: string;
47
- /** Resolve metadata, cache authority, and replica; called again on retry. */
48
- open: (signal: AbortSignal) => Promise<OpenedEditableArtifact>;
49
- runtimes: EditableArtifactRuntimes;
50
- transport?: CreateBrowserEditableArtifactSessionOptions["transport"];
51
- /** Show the editor's own title header (hosts with their own header hide it). */
52
- showHeader?: boolean | undefined;
53
- /** Re-open when this changes (for example the host's credential version). */
54
- authorityKey?: string | undefined;
55
- describeError?: ((error: unknown) => ArtifactLoadErrorView) | undefined;
56
- onTitle?: ((title: string) => void) | undefined;
57
- /** Loading copy; defaults to the `opening` label. */
58
- loadingLabel?: string | undefined;
59
- }>;
60
-
61
- type LoadState =
62
- | Readonly<{ key: string | null; kind: "loading" }>
63
- | Readonly<{ key: string; kind: "ready"; value: OpenedEditableArtifact }>
64
- | Readonly<{ key: string; kind: "error"; error: unknown }>;
65
-
66
- /**
67
- * One editable document, spreadsheet, or presentation with its first-party
68
- * editor. The console dock/page and embedding hosts supply only data access.
69
- */
70
- export function EditableArtifactView({
71
- baseUrl,
72
- workspaceId,
73
- artifactId,
74
- open,
75
- runtimes,
76
- transport,
77
- showHeader = true,
78
- authorityKey,
79
- describeError,
80
- onTitle,
81
- loadingLabel,
82
- }: EditableArtifactViewProps) {
83
- const [loadEpoch, setLoadEpoch] = useState(0);
84
- const labels = useArtifactLabels();
85
- const loadKey = JSON.stringify([artifactId, authorityKey, baseUrl.href, workspaceId, loadEpoch]);
86
- const [state, setState] = useState<LoadState>({ key: null, kind: "loading" });
87
- const current = state.key === loadKey ? state : null;
88
- const openRef = useRef(open);
89
- openRef.current = open;
90
- const onTitleRef = useRef(onTitle);
91
- onTitleRef.current = onTitle;
92
- useEffect(() => {
93
- const abort = new AbortController();
94
- setState({ key: loadKey, kind: "loading" });
95
- void openRef
96
- .current(abort.signal)
97
- .then((value) => {
98
- if (abort.signal.aborted) return;
99
- setState({ key: loadKey, kind: "ready", value });
100
- onTitleRef.current?.(value.artifact.title);
101
- })
102
- .catch((error: unknown) => {
103
- if (!abort.signal.aborted) setState({ key: loadKey, kind: "error", error });
104
- });
105
- return () => abort.abort();
106
- }, [loadKey]);
107
-
108
- const ready = current?.kind === "ready" ? current.value : null;
109
- const transportRef = useRef(transport);
110
- transportRef.current = transport;
111
- const sessionKey = useMemo(
112
- () =>
113
- ready
114
- ? JSON.stringify({
115
- baseUrl: baseUrl.href,
116
- authorityKey,
117
- workspaceId,
118
- artifact: { id: ready.artifact.id, modality: ready.artifact.modality },
119
- authority: ready.authority,
120
- replicaId: ready.replicaId,
121
- workerUrl: runtimes.workerUrl,
122
- })
123
- : "",
124
- [authorityKey, baseUrl.href, ready, runtimes.workerUrl, workspaceId],
125
- );
126
- const createSession = useCallback(() => {
127
- if (!ready) throw new Error("Artifact is not open");
128
- return createBrowserEditableArtifactSession({
129
- baseUrl,
130
- workspaceId,
131
- artifact: ready.artifact,
132
- storageAuthority: ready.authority,
133
- replicaId: ready.replicaId,
134
- runtime: {
135
- ...runtimeFor(runtimes, ready.artifact.modality),
136
- workerUrl: runtimes.workerUrl,
137
- ...(runtimes.applicationOrigin ? { applicationOrigin: runtimes.applicationOrigin } : {}),
138
- ...(runtimes.allowInsecureDevelopmentAssets
139
- ? { allowInsecureDevelopmentAssets: true }
140
- : {}),
141
- },
142
- ...(transportRef.current ? { transport: transportRef.current } : {}),
143
- });
144
- }, [baseUrl, ready, runtimes, workspaceId]);
145
-
146
- if (!current || current.kind === "loading")
147
- return <ArtifactLoading label={loadingLabel ?? labels.opening} />;
148
- if (current.kind === "error") {
149
- return (
150
- <ArtifactProblem
151
- view={
152
- describeError
153
- ? describeError(current.error)
154
- : artifactLoadErrorView(current.error, "editable", labels)
155
- }
156
- onRetry={() => setLoadEpoch((value) => value + 1)}
157
- />
158
- );
159
- }
160
- const { artifact } = current.value;
161
- const surface = { title: artifact.title, showHeader };
162
- return (
163
- <div className="h-full min-h-0 bg-og-bg text-og-fg">
164
- <EditableArtifactWorkbenchHost
165
- sessionKey={sessionKey}
166
- createSession={createSession}
167
- document={surface}
168
- spreadsheet={surface}
169
- presentation={surface}
170
- />
171
- </div>
172
- );
173
- }
174
-
175
- function runtimeFor(
176
- runtimes: EditableArtifactRuntimes,
177
- modality: EditableArtifactModality,
178
- ): Omit<EditableArtifactBrowserRuntime, "workerUrl" | "workerFactory"> {
179
- switch (modality) {
180
- case "document":
181
- return runtimes.document;
182
- case "spreadsheet":
183
- return runtimes.spreadsheet;
184
- case "presentation":
185
- return runtimes.presentation;
186
- }
187
- }
@@ -1,49 +0,0 @@
1
- import { useRef } from "react";
2
-
3
- /**
4
- * Runs one task at a time; a task scheduled while another runs replaces any
5
- * task still waiting, so only the newest request starts next. Editors use it
6
- * for projection loads: replaying a long history emits one revision per
7
- * committed transaction, and composing a projection for each of them at once
8
- * would overrun the artifact Worker's bounded request queue.
9
- */
10
- export function createLatestTaskRunner(): (task: () => Promise<void>) => void {
11
- let running = false;
12
- let waiting: (() => Promise<void>) | null = null;
13
- const drain = async () => {
14
- running = true;
15
- try {
16
- while (waiting) {
17
- const task = waiting;
18
- waiting = null;
19
- try {
20
- await task();
21
- } catch {
22
- // Tasks settle their own errors; one failure must not stall the next.
23
- }
24
- }
25
- } finally {
26
- running = false;
27
- }
28
- };
29
- return (task) => {
30
- waiting = task;
31
- if (!running) void drain();
32
- };
33
- }
34
-
35
- /**
36
- * One runner per artifact session. A composition still pending on a replaced
37
- * session (for example one whose Worker stopped answering) must never hold up
38
- * the next session's first load, so the runner is scoped to the session object
39
- * rather than to the component.
40
- */
41
- export function useLatestTaskRunner(session: object): (task: () => Promise<void>) => void {
42
- const runner = useRef<{ session: object; schedule: (task: () => Promise<void>) => void } | null>(
43
- null,
44
- );
45
- if (runner.current?.session !== session) {
46
- runner.current = { session, schedule: createLatestTaskRunner() };
47
- }
48
- return runner.current.schedule;
49
- }