@arbidocs/blocks 0.5.125 → 0.5.127

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 (200) hide show
  1. package/README.md +19 -0
  2. package/dist/{ClippingMediaViewer-ZFEX5A3U.cjs → ClippingMediaViewer-47BAXNLO.cjs} +10 -11
  3. package/dist/ClippingMediaViewer-47BAXNLO.cjs.map +1 -0
  4. package/dist/{ClippingPdfViewer-3LRTEPHV.cjs → ClippingPdfViewer-D5BYHEAH.cjs} +16 -14
  5. package/dist/ClippingPdfViewer-D5BYHEAH.cjs.map +1 -0
  6. package/dist/ConnectedCallDock-XJOYVVAH.cjs +6 -6
  7. package/dist/ConnectedCallRoom-CUDYAPQK.cjs +6 -6
  8. package/dist/{ConnectedNativeDocxComparison-7QQ55JIO.cjs → ConnectedNativeDocxComparison-JUF2TI2P.cjs} +12 -12
  9. package/dist/{ConnectedNativeDocxComparison-7QQ55JIO.cjs.map → ConnectedNativeDocxComparison-JUF2TI2P.cjs.map} +1 -1
  10. package/dist/{ConnectedPresentation-L32JPRFI.cjs → ConnectedPresentation-SI3L77J4.cjs} +35 -36
  11. package/dist/ConnectedPresentation-SI3L77J4.cjs.map +1 -0
  12. package/dist/{ConnectedProductionSetDialog-4EAY6KAZ.cjs → ConnectedProductionSetDialog-LHB4VQWC.cjs} +3 -3
  13. package/dist/{ConnectedProductionSetDialog-4EAY6KAZ.cjs.map → ConnectedProductionSetDialog-LHB4VQWC.cjs.map} +1 -1
  14. package/dist/{ConnectedSlidesTab-P7GGNUTO.cjs → ConnectedSlidesTab-E76OVY34.cjs} +38 -44
  15. package/dist/ConnectedSlidesTab-E76OVY34.cjs.map +1 -0
  16. package/dist/{ConnectedStreamedWordArtifact-7NJ3N52V.cjs → ConnectedStreamedWordArtifact-Q64DRRQD.cjs} +12 -12
  17. package/dist/{ConnectedStreamedWordArtifact-7NJ3N52V.cjs.map → ConnectedStreamedWordArtifact-Q64DRRQD.cjs.map} +1 -1
  18. package/dist/{DocumentReviewCard-BFPOFQXI.cjs → DocumentReviewCard-M53XFLI2.cjs} +16 -16
  19. package/dist/{DocumentReviewCard-BFPOFQXI.cjs.map → DocumentReviewCard-M53XFLI2.cjs.map} +1 -1
  20. package/dist/{FilesViewerDialog-MHUVEZ7M.cjs → FilesViewerDialog-V6FSRQTP.cjs} +33 -34
  21. package/dist/FilesViewerDialog-V6FSRQTP.cjs.map +1 -0
  22. package/dist/{NativeDocxEditor-SIRYPMBN.cjs → NativeDocxEditor-2PTEK6ND.cjs} +6 -6
  23. package/dist/{NativeDocxEditor-SIRYPMBN.cjs.map → NativeDocxEditor-2PTEK6ND.cjs.map} +1 -1
  24. package/dist/{SpotlightStrip-ED22UYTM.cjs → SpotlightStrip-QQ2WUI32.cjs} +28 -29
  25. package/dist/SpotlightStrip-QQ2WUI32.cjs.map +1 -0
  26. package/dist/{WorkspaceTableView-MIFYXV3G.cjs → WorkspaceTableView-3XYAV4BV.cjs} +8 -8
  27. package/dist/{WorkspaceTableView-MIFYXV3G.cjs.map → WorkspaceTableView-3XYAV4BV.cjs.map} +1 -1
  28. package/dist/agents/index.cjs +215 -215
  29. package/dist/ai-IEIR7SC2.cjs +3 -3
  30. package/dist/auth/index.cjs +1 -1
  31. package/dist/calls/index.cjs +2 -2
  32. package/dist/chat/index.cjs +6 -6
  33. package/dist/{chunk-GAMRVB3I.cjs → chunk-23I5EUZA.cjs} +65 -10
  34. package/dist/chunk-23I5EUZA.cjs.map +1 -0
  35. package/dist/{chunk-ZZZITHGK.cjs → chunk-26SFI2BH.cjs} +16 -3
  36. package/dist/chunk-26SFI2BH.cjs.map +1 -0
  37. package/dist/{chunk-OGK377NL.cjs → chunk-2SU37RZA.cjs} +409 -42
  38. package/dist/chunk-2SU37RZA.cjs.map +1 -0
  39. package/dist/{chunk-CQ2RJYE2.cjs → chunk-3IH55CDE.cjs} +3 -3
  40. package/dist/{chunk-CQ2RJYE2.cjs.map → chunk-3IH55CDE.cjs.map} +1 -1
  41. package/dist/{chunk-AS6QZETU.cjs → chunk-5QERK4PK.cjs} +35 -35
  42. package/dist/{chunk-AS6QZETU.cjs.map → chunk-5QERK4PK.cjs.map} +1 -1
  43. package/dist/{chunk-AISYXX4M.cjs → chunk-7NLO7ASW.cjs} +4 -4
  44. package/dist/{chunk-AISYXX4M.cjs.map → chunk-7NLO7ASW.cjs.map} +1 -1
  45. package/dist/{chunk-QIK7E6VF.cjs → chunk-7PN3YENO.cjs} +4 -4
  46. package/dist/{chunk-QIK7E6VF.cjs.map → chunk-7PN3YENO.cjs.map} +1 -1
  47. package/dist/{chunk-JY7UUZQG.cjs → chunk-7Q46YAX4.cjs} +74 -71
  48. package/dist/chunk-7Q46YAX4.cjs.map +1 -0
  49. package/dist/{chunk-J3FFSEKO.cjs → chunk-ADDQKZ5I.cjs} +4 -4
  50. package/dist/{chunk-J3FFSEKO.cjs.map → chunk-ADDQKZ5I.cjs.map} +1 -1
  51. package/dist/{chunk-L6HTZIBD.cjs → chunk-ALK3PUUB.cjs} +3 -3
  52. package/dist/{chunk-L6HTZIBD.cjs.map → chunk-ALK3PUUB.cjs.map} +1 -1
  53. package/dist/{chunk-3ZZZO4F7.cjs → chunk-BY3QA3S5.cjs} +9 -9
  54. package/dist/{chunk-3ZZZO4F7.cjs.map → chunk-BY3QA3S5.cjs.map} +1 -1
  55. package/dist/{chunk-3UP32FM3.cjs → chunk-CQQQIZU4.cjs} +5 -5
  56. package/dist/{chunk-3UP32FM3.cjs.map → chunk-CQQQIZU4.cjs.map} +1 -1
  57. package/dist/{chunk-YOEP3I4V.cjs → chunk-E3L5DB5F.cjs} +5 -5
  58. package/dist/{chunk-YOEP3I4V.cjs.map → chunk-E3L5DB5F.cjs.map} +1 -1
  59. package/dist/{chunk-UA7HOLAZ.cjs → chunk-ESVRVZ53.cjs} +5 -5
  60. package/dist/{chunk-UA7HOLAZ.cjs.map → chunk-ESVRVZ53.cjs.map} +1 -1
  61. package/dist/{chunk-2K6IMIVL.cjs → chunk-GNZAH6UI.cjs} +3 -3
  62. package/dist/{chunk-2K6IMIVL.cjs.map → chunk-GNZAH6UI.cjs.map} +1 -1
  63. package/dist/{chunk-KA3F6TIH.cjs → chunk-IWHP7FAB.cjs} +12 -5
  64. package/dist/chunk-IWHP7FAB.cjs.map +1 -0
  65. package/dist/{chunk-ORNLCBNE.cjs → chunk-JVEDKKNO.cjs} +43 -4
  66. package/dist/chunk-JVEDKKNO.cjs.map +1 -0
  67. package/dist/{chunk-6JMW3IJ2.cjs → chunk-JVLOIIA7.cjs} +103 -100
  68. package/dist/chunk-JVLOIIA7.cjs.map +1 -0
  69. package/dist/{chunk-UUENH4BJ.cjs → chunk-KXIRVKCJ.cjs} +3 -3
  70. package/dist/{chunk-UUENH4BJ.cjs.map → chunk-KXIRVKCJ.cjs.map} +1 -1
  71. package/dist/{chunk-YVO6Z5BS.cjs → chunk-M6WVIGYP.cjs} +10 -10
  72. package/dist/{chunk-YVO6Z5BS.cjs.map → chunk-M6WVIGYP.cjs.map} +1 -1
  73. package/dist/{chunk-PSB64T43.cjs → chunk-MLA2BJ2Y.cjs} +28 -28
  74. package/dist/{chunk-PSB64T43.cjs.map → chunk-MLA2BJ2Y.cjs.map} +1 -1
  75. package/dist/{chunk-B3R62764.cjs → chunk-MMSHIIKG.cjs} +6 -6
  76. package/dist/{chunk-B3R62764.cjs.map → chunk-MMSHIIKG.cjs.map} +1 -1
  77. package/dist/{chunk-ENOPKX7E.cjs → chunk-MXBXZXXQ.cjs} +3 -3
  78. package/dist/{chunk-ENOPKX7E.cjs.map → chunk-MXBXZXXQ.cjs.map} +1 -1
  79. package/dist/chunk-NTDQQLDD.cjs +18 -0
  80. package/dist/{chunk-IRGRMJPP.cjs.map → chunk-NTDQQLDD.cjs.map} +1 -1
  81. package/dist/{chunk-PVJ5ODDB.cjs → chunk-Q6IKXWPG.cjs} +4 -4
  82. package/dist/{chunk-PVJ5ODDB.cjs.map → chunk-Q6IKXWPG.cjs.map} +1 -1
  83. package/dist/{chunk-4LP5MJGQ.cjs → chunk-QG2U2JGX.cjs} +4 -4
  84. package/dist/{chunk-4LP5MJGQ.cjs.map → chunk-QG2U2JGX.cjs.map} +1 -1
  85. package/dist/{chunk-MVXHTAJ4.cjs → chunk-QWVUBTW3.cjs} +9 -9
  86. package/dist/{chunk-MVXHTAJ4.cjs.map → chunk-QWVUBTW3.cjs.map} +1 -1
  87. package/dist/{chunk-OHWHYVKG.cjs → chunk-T73BHNQX.cjs} +3 -3
  88. package/dist/{chunk-OHWHYVKG.cjs.map → chunk-T73BHNQX.cjs.map} +1 -1
  89. package/dist/{chunk-PWBRE7JA.cjs → chunk-VLQCLQAS.cjs} +23 -10
  90. package/dist/chunk-VLQCLQAS.cjs.map +1 -0
  91. package/dist/{chunk-ADPRIBSH.cjs → chunk-X7ALF4MP.cjs} +7 -7
  92. package/dist/{chunk-ADPRIBSH.cjs.map → chunk-X7ALF4MP.cjs.map} +1 -1
  93. package/dist/{chunk-7RJZIPLE.cjs → chunk-YP5EBGLU.cjs} +33 -33
  94. package/dist/{chunk-7RJZIPLE.cjs.map → chunk-YP5EBGLU.cjs.map} +1 -1
  95. package/dist/{chunk-TSFNLY3K.cjs → chunk-ZCU72Q76.cjs} +192 -166
  96. package/dist/chunk-ZCU72Q76.cjs.map +1 -0
  97. package/dist/{chunk-BLIO3B7Z.cjs → chunk-ZYGAXD3N.cjs} +3 -3
  98. package/dist/{chunk-BLIO3B7Z.cjs.map → chunk-ZYGAXD3N.cjs.map} +1 -1
  99. package/dist/connect/docx.cjs +16 -16
  100. package/dist/connect/index.cjs +1095 -1114
  101. package/dist/connect/index.cjs.map +1 -1
  102. package/dist/connect/viewer.cjs +26 -27
  103. package/dist/connection/ai.d.ts +1 -0
  104. package/dist/connection/docx/ConnectedNativeDocxEditor.js +4 -5
  105. package/dist/connection/docx/ConnectedNativeDocxEditor.js.map +2 -2
  106. package/dist/connection/docx/save-native-version.d.ts +3 -0
  107. package/dist/connection/docx/save-native-version.js +19 -0
  108. package/dist/connection/docx/save-native-version.js.map +7 -0
  109. package/dist/connection/presentation/ClippingPdfViewer.js +8 -5
  110. package/dist/connection/presentation/ClippingPdfViewer.js.map +2 -2
  111. package/dist/connection/presentation/ConnectedSlidesTab.js +8 -13
  112. package/dist/connection/presentation/ConnectedSlidesTab.js.map +2 -2
  113. package/dist/connection/presentation/usePresentationController.js +24 -15
  114. package/dist/connection/presentation/usePresentationController.js.map +2 -2
  115. package/dist/connection/presentation/usePresentationFiles.d.ts +2 -1
  116. package/dist/connection/presentation/usePresentationFiles.js +15 -6
  117. package/dist/connection/presentation/usePresentationFiles.js.map +2 -2
  118. package/dist/connection/remote-control/ConnectedRemoteControlConsent.js +6 -3
  119. package/dist/connection/remote-control/ConnectedRemoteControlConsent.js.map +2 -2
  120. package/dist/connection/remote-control/ConnectedRemoteControlIndicator.js +12 -2
  121. package/dist/connection/remote-control/ConnectedRemoteControlIndicator.js.map +2 -2
  122. package/dist/connection/remote-control/ConnectedRemoteControlSwitch.js +1 -1
  123. package/dist/connection/remote-control/ConnectedRemoteControlSwitch.js.map +1 -1
  124. package/dist/connection/remote-control/browser-diagnostics.d.ts +7 -0
  125. package/dist/connection/remote-control/browser-diagnostics.js +109 -0
  126. package/dist/connection/remote-control/browser-diagnostics.js.map +7 -0
  127. package/dist/connection/remote-control/remoteControl.d.ts +9 -1
  128. package/dist/connection/remote-control/remoteControl.js +130 -10
  129. package/dist/connection/remote-control/remoteControl.js.map +2 -2
  130. package/dist/connection/remote-control/screen-sharing.d.ts +11 -0
  131. package/dist/connection/remote-control/screen-sharing.js +78 -0
  132. package/dist/connection/remote-control/screen-sharing.js.map +7 -0
  133. package/dist/connection/report-problem/screen-capture.d.ts +1 -0
  134. package/dist/connection/report-problem/screen-capture.js +2 -1
  135. package/dist/connection/report-problem/screen-capture.js.map +2 -2
  136. package/dist/connection/search/ConnectedSearchPage.d.ts +0 -11
  137. package/dist/connection/search/ConnectedSearchPage.js +3 -0
  138. package/dist/connection/search/ConnectedSearchPage.js.map +2 -2
  139. package/dist/connection/search/SearchResultRows.js +75 -43
  140. package/dist/connection/search/SearchResultRows.js.map +2 -2
  141. package/dist/connection/viewer/CleanViewControl.d.ts +3 -1
  142. package/dist/connection/viewer/CleanViewControl.js +5 -1
  143. package/dist/connection/viewer/CleanViewControl.js.map +2 -2
  144. package/dist/connection/viewer/ConnectedDocumentViewer.js +1 -1
  145. package/dist/connection/viewer/ConnectedDocumentViewer.js.map +2 -2
  146. package/dist/connection/viewer/useCleanView.d.ts +1 -1
  147. package/dist/connection/viewer/useCleanView.js +2 -2
  148. package/dist/connection/viewer/useCleanView.js.map +2 -2
  149. package/dist/connection/viewer/useViewedDocument.d.ts +1 -0
  150. package/dist/document-tools/index.cjs +20 -20
  151. package/dist/docx/NativeDocxEditor.d.ts +2 -1
  152. package/dist/docx/NativeDocxEditor.js +34 -7
  153. package/dist/docx/NativeDocxEditor.js.map +2 -2
  154. package/dist/docx/index.cjs +38 -38
  155. package/dist/docx/useVersionAutosave.d.ts +10 -0
  156. package/dist/docx/useVersionAutosave.js +36 -0
  157. package/dist/docx/useVersionAutosave.js.map +7 -0
  158. package/dist/embed/index.cjs +19 -19
  159. package/dist/facts/index.cjs +64 -64
  160. package/dist/files/index.cjs +223 -223
  161. package/dist/index.cjs +542 -542
  162. package/dist/presentation/ClipCaptureOverlay.js +14 -1
  163. package/dist/presentation/ClipCaptureOverlay.js.map +2 -2
  164. package/dist/presentation/ClipImage.js +7 -4
  165. package/dist/presentation/ClipImage.js.map +2 -2
  166. package/dist/presentation/ClipTray.js +11 -7
  167. package/dist/presentation/ClipTray.js.map +2 -2
  168. package/dist/presentation/SlidesEditor.js +6 -2
  169. package/dist/presentation/SlidesEditor.js.map +2 -2
  170. package/dist/presentation/store.d.ts +2 -0
  171. package/dist/presentation/store.js +15 -0
  172. package/dist/presentation/store.js.map +3 -3
  173. package/dist/settings/index.cjs +146 -146
  174. package/dist/shell/RemoteControlPill.d.ts +2 -1
  175. package/dist/shell/RemoteControlPill.js +10 -3
  176. package/dist/shell/RemoteControlPill.js.map +2 -2
  177. package/dist/shell/index.cjs +21 -21
  178. package/dist/site/index.cjs +6 -6
  179. package/dist/studio/index.cjs +9 -9
  180. package/dist/workflow-canvas/index.cjs +34 -34
  181. package/node_modules/@arbidocs/coedit/package.json +1 -1
  182. package/package.json +5 -5
  183. package/dist/ClippingMediaViewer-ZFEX5A3U.cjs.map +0 -1
  184. package/dist/ClippingPdfViewer-3LRTEPHV.cjs.map +0 -1
  185. package/dist/ConnectedPresentation-L32JPRFI.cjs.map +0 -1
  186. package/dist/ConnectedSlidesTab-P7GGNUTO.cjs.map +0 -1
  187. package/dist/FilesViewerDialog-MHUVEZ7M.cjs.map +0 -1
  188. package/dist/SpotlightStrip-ED22UYTM.cjs.map +0 -1
  189. package/dist/chunk-6JMW3IJ2.cjs.map +0 -1
  190. package/dist/chunk-GAMRVB3I.cjs.map +0 -1
  191. package/dist/chunk-GFUOCGY6.cjs +0 -32
  192. package/dist/chunk-GFUOCGY6.cjs.map +0 -1
  193. package/dist/chunk-IRGRMJPP.cjs +0 -18
  194. package/dist/chunk-JY7UUZQG.cjs.map +0 -1
  195. package/dist/chunk-KA3F6TIH.cjs.map +0 -1
  196. package/dist/chunk-OGK377NL.cjs.map +0 -1
  197. package/dist/chunk-ORNLCBNE.cjs.map +0 -1
  198. package/dist/chunk-PWBRE7JA.cjs.map +0 -1
  199. package/dist/chunk-TSFNLY3K.cjs.map +0 -1
  200. package/dist/chunk-ZZZITHGK.cjs.map +0 -1
@@ -31,9 +31,10 @@ export interface SavedPresentationEntry {
31
31
  updatedAt: string | null;
32
32
  }
33
33
  export declare function usePresentationFiles(): {
34
- save: (title: string, { asNew, template }?: {
34
+ save: (title: string, { asNew, template, autosave, }?: {
35
35
  asNew?: boolean;
36
36
  template?: boolean;
37
+ autosave?: boolean;
37
38
  }) => Promise<string>;
38
39
  exportPdf: (title: string) => Promise<string>;
39
40
  list: (scope?: "presentations" | "templates") => Promise<SavedPresentationEntry[]>;
@@ -80,7 +80,11 @@ function usePresentationFiles() {
80
80
  return writePptxPresentationData(deck, JSON.stringify(data, null, 2));
81
81
  }, []);
82
82
  const save = useCallback(
83
- async (title, { asNew = false, template = false } = {}) => {
83
+ async (title, {
84
+ asNew = false,
85
+ template = false,
86
+ autosave = false
87
+ } = {}) => {
84
88
  if (!arbi || !workspaceId) throw new Error("Open a workspace to save the presentation.");
85
89
  const state = usePresentation.getState();
86
90
  const versionOf = asNew || template ? null : state.savedId;
@@ -89,13 +93,14 @@ function usePresentationFiles() {
89
93
  const result = await arbi.documents.upload([file], {
90
94
  folder: template ? TEMPLATES_FOLDER : PRESENTATIONS_FOLDER,
91
95
  workspaceExtId: workspaceId,
92
- ...versionOf ? { versionOf } : {}
96
+ ...versionOf ? { versionOf, autosave } : {}
93
97
  });
94
98
  const id = versionOf ?? result.doc_ext_ids?.[0];
95
99
  if (!id) throw new Error("The presentation could not be saved.");
96
100
  if (!template) {
97
101
  const [doc] = await arbi.documents.get([id]).catch(() => []);
98
- state.setSaved(id, title, doc?.current_version ?? null);
102
+ state.setSaved(id, title, result.version ?? doc?.current_version ?? null);
103
+ usePresentation.setState({ autosave: !!versionOf && autosave });
99
104
  }
100
105
  return id;
101
106
  },
@@ -145,6 +150,7 @@ function usePresentationFiles() {
145
150
  const text = await presentationData(bytes);
146
151
  const [doc] = await arbi.documents.get([id]).catch(() => []);
147
152
  const openedVersion = version ?? doc?.current_version ?? null;
153
+ const autosave = !!(version ? doc?.versions?.find((entry) => entry.n === version)?.autosave : doc?.autosave);
148
154
  if (text === null) {
149
155
  const imported = await importPptx(bytes, {
150
156
  title: presentationTitle(doc?.file_name ?? "Presentation.pptx"),
@@ -156,7 +162,8 @@ function usePresentationFiles() {
156
162
  saved: imported.saved,
157
163
  legacy: false,
158
164
  imported: { notes: imported.notes },
159
- openedVersion
165
+ openedVersion,
166
+ autosave
160
167
  };
161
168
  }
162
169
  let saved = parsePresentation(text);
@@ -182,7 +189,8 @@ function usePresentationFiles() {
182
189
  // file, not a version of this one (a document's versions keep one format).
183
190
  legacy: !isZipBytes(bytes),
184
191
  changedOutside,
185
- openedVersion
192
+ openedVersion,
193
+ autosave
186
194
  };
187
195
  },
188
196
  [arbi]
@@ -213,7 +221,7 @@ function usePresentationFiles() {
213
221
  const open = useCallback(
214
222
  async (id, version) => {
215
223
  if (!arbi) throw new Error("Sign in to open a presentation.");
216
- const { saved, legacy, imported, changedOutside, openedVersion } = await readSaved(
224
+ const { saved, legacy, imported, changedOutside, openedVersion, autosave } = await readSaved(
217
225
  id,
218
226
  version
219
227
  );
@@ -271,6 +279,7 @@ function usePresentationFiles() {
271
279
  title: saved.title,
272
280
  savedId: legacy ? null : id,
273
281
  version: legacy ? null : openedVersion,
282
+ autosave,
274
283
  boundTo: id,
275
284
  captions: saved.captions,
276
285
  labelColumn: saved.labelColumn ?? null,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/connection/presentation/usePresentationFiles.ts"],
4
- "sourcesContent": ["/**\n * Saving and opening presentations as documents of the open workspace's `Presentations` folder:\n * a PowerPoint file (`<title>.pptx`: its slides as editable PowerPoint, the presentation's own data\n * inside it, `pptx-data`). Saving again adds a version to the same document; Export PDF saves a\n * PDF of the slides.\n *\n * Opening fetches each clip's source again: the PDF the viewer shows for a page clip (so Word and\n * text documents work too), the original file for an image or a video. A page clip's redactions\n * are read again from the markup; a clip whose source or redactions cannot be read is left out\n * (never shown unredacted), and how many were is reported.\n *\n * Presentations saved before they were PowerPoint files (a PDF with the data attached, or the data\n * alone) still open; their first save makes a PowerPoint file of them.\n */\n\nimport { useCallback } from 'react'\nimport { useArbi, useArbiSessionState } from '@arbidocs/react'\nimport { isPresentationFileName } from '@arbidocs/react/ui'\nimport {\n createMarkupStore,\n fetchDocumentPdf,\n imagesToPdf,\n MarkupStoreError,\n readPdfAttachment,\n useDocumentTools,\n} from '@arbidocs/react/document-tools'\nimport type { Clip, Rect, Slide } from '../../presentation/model'\nimport {\n EXPORTS_FOLDER,\n PRESENTATIONS_FOLDER,\n PRESENTATION_ATTACHMENT,\n TEMPLATES_FOLDER,\n parsePresentation,\n presentationFileName,\n presentationTitle,\n safeFileStem,\n serializePresentation,\n type SavedClip,\n} from '../../presentation/persistence'\nimport { usePresentation } from '../../presentation/store'\nimport { renderSlide } from '../../presentation/render-slides'\nimport { buildPptx } from '../../presentation/export-pptx'\nimport {\n isZipBytes,\n readPptxPresentationData,\n writePptxPresentationData,\n} from '../../presentation/pptx-data'\nimport { importPptx, slideFingerprints } from '../../presentation/import-pptx'\nimport { mergeOutsideEdits } from '../../presentation/merge-outside-edits'\nimport type { SavedPresentation } from '../../presentation/persistence'\n\nconst PPTX_TYPE = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'\n\n/** A slide's page in the exported PDF, in points: 16:9. */\nconst SLIDE_PAGE = { width: 1280, height: 720 }\n\n/** An image embedded as a data URL, as a file. */\nfunction dataUrlBlob(url: string): Blob {\n const [head, body] = url.split(',', 2)\n const type = /^data:([^;]+)/.exec(head)?.[1] ?? 'application/octet-stream'\n const binary = atob(body ?? '')\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)\n return new Blob([bytes], { type })\n}\n\n/** What opening a presentation found besides its content. */\nexport interface OpenedPresentation {\n /** Clips whose source could not be opened (left out). */\n missing: number\n /** Changed outside the editor (an agent): the slides are drawn again on the next save. */\n stale: boolean\n /** A PowerPoint made elsewhere, imported: what did not come across as it was. */\n imported?: { notes: string[] }\n /** Slides (1-based) changed in PowerPoint since the editor saved it, brought in. */\n changedOutside?: number[]\n}\n\n/**\n * The editable data of a saved presentation: inside its PowerPoint file; or, saved before that,\n * attached to its PDF, or the file itself.\n */\nasync function presentationData(bytes: Uint8Array): Promise<string | null> {\n if (isZipBytes(bytes)) return readPptxPresentationData(bytes)\n const isPdf = bytes[0] === 0x25 && bytes[1] === 0x50 && bytes[2] === 0x44 && bytes[3] === 0x46\n if (!isPdf) return new TextDecoder().decode(bytes)\n const data = await readPdfAttachment(bytes, PRESENTATION_ATTACHMENT)\n if (!data) {\n throw new Error(\n 'This PDF has no editable presentation in it (it may have been changed outside ARBI).'\n )\n }\n return new TextDecoder().decode(data)\n}\n\nexport interface SavedPresentationEntry {\n id: string\n title: string\n updatedAt: string | null\n}\n\n/** The presentation as it is now, or (`withoutClips`) its look and slides only: a template. */\nfunction current(withoutClips: boolean) {\n const state = usePresentation.getState()\n return withoutClips\n ? {\n ...state,\n clips: [],\n slides: state.slides.map((slide): Slide => ({ ...slide, items: [] })),\n }\n : state\n}\n\nconst lookOf = (state: ReturnType<typeof current>) => ({\n labels: state.captions,\n columnValues: state.columnValues,\n theme: state.theme,\n logo: state.logoImage,\n media: state.media,\n})\n\nexport function usePresentationFiles() {\n const arbi = useArbi()\n const { client } = useDocumentTools()\n const workspaceId = useArbiSessionState((s) => s.workspaceId)\n\n /** The presentation as its PowerPoint file, its editable data inside. */\n const buildFile = useCallback(async (title: string, withoutClips: boolean): Promise<Blob> => {\n const state = current(withoutClips)\n const deck = await buildPptx({ title, slides: state.slides, clips: state.clips }, lookOf(state))\n // Drawn just now from the presentation itself: never stale. Each slide's fingerprint tells,\n // when it is opened again, which slides were changed in PowerPoint meanwhile.\n const fingerprints = await slideFingerprints(deck)\n const data = serializePresentation({ ...state, title, fingerprints })\n return writePptxPresentationData(deck, JSON.stringify(data, null, 2))\n }, [])\n\n /**\n * Save the presentation: `<title>.pptx` in Presentations, through the ordinary upload, so it is\n * viewed, searched and downloaded like any document (and opens in PowerPoint). A new version of\n * the document it was saved as, or (never saved, or `asNew`: Save as) a new document named\n * `title`; a template (its look and slides, no clips) goes to Presentations/Templates. Resolves\n * with its id.\n */\n const save = useCallback(\n async (\n title: string,\n { asNew = false, template = false }: { asNew?: boolean; template?: boolean } = {}\n ): Promise<string> => {\n if (!arbi || !workspaceId) throw new Error('Open a workspace to save the presentation.')\n const state = usePresentation.getState()\n // A template is always a new document; the open presentation stays what it was.\n const versionOf = asNew || template ? null : state.savedId\n const deck = await buildFile(title, template)\n const file = new File([deck], presentationFileName(title), { type: PPTX_TYPE })\n const result = await arbi.documents.upload([file], {\n folder: template ? TEMPLATES_FOLDER : PRESENTATIONS_FOLDER,\n workspaceExtId: workspaceId,\n ...(versionOf ? { versionOf } : {}),\n })\n const id = versionOf ?? result.doc_ext_ids?.[0]\n if (!id) throw new Error('The presentation could not be saved.')\n if (!template) {\n // The version just saved: a newer one (saved by ARBI, or someone else) is then told apart.\n const [doc] = await arbi.documents.get([id]).catch(() => [])\n state.setSaved(id, title, doc?.current_version ?? null)\n }\n return id\n },\n [arbi, workspaceId, buildFile]\n )\n\n /**\n * Export the slides as a PDF named `title` (in Presentations/Exports): to send outside or file\n * in a bundle, each slide a page as presented, redactions burnt in.\n */\n const exportPdf = useCallback(\n async (title: string): Promise<string> => {\n if (!arbi || !workspaceId) throw new Error('Open a workspace to export the PDF.')\n const state = current(false)\n const pages = []\n for (const slide of state.slides) {\n const { image, text } = await renderSlide(slide, state.clips, lookOf(state))\n pages.push({ image, text, width: SLIDE_PAGE.width, height: SLIDE_PAGE.height })\n }\n const pdf = await imagesToPdf(pages, { title })\n const result = await arbi.documents.upload(\n [new File([pdf], `${safeFileStem(title)}.pdf`, { type: 'application/pdf' })],\n { folder: EXPORTS_FOLDER, workspaceExtId: workspaceId }\n )\n const id = result.doc_ext_ids?.[0]\n if (!id) throw new Error('The PDF could not be saved.')\n return id\n },\n [arbi, workspaceId]\n )\n\n /** The presentations saved in the open workspace (or its templates), newest first. */\n const list = useCallback(\n async (\n scope: 'presentations' | 'templates' = 'presentations'\n ): Promise<SavedPresentationEntry[]> => {\n if (!arbi || !workspaceId) return []\n const docs = await arbi.documents.list({ workspaceId })\n return docs\n .filter((doc) => {\n const folder = doc.folder ?? ''\n if (!isPresentationFileName(doc.file_name, folder)) return false\n return scope === 'templates'\n ? folder === TEMPLATES_FOLDER\n : folder.startsWith(PRESENTATIONS_FOLDER) &&\n folder !== TEMPLATES_FOLDER &&\n !folder.startsWith(EXPORTS_FOLDER)\n })\n .map((doc) => ({\n id: doc.external_id,\n title: presentationTitle(doc.file_name ?? ''),\n updatedAt: doc.updated_at ?? doc.created_at ?? null,\n }))\n .sort((a, b) => ((a.updatedAt ?? '') < (b.updatedAt ?? '') ? 1 : -1))\n },\n [arbi, workspaceId]\n )\n\n /**\n * A saved presentation's data: from its PowerPoint file (slides changed in PowerPoint since\n * brought in), its PDF's attachment or a file saved before that; a PowerPoint made elsewhere is\n * imported.\n */\n const readSaved = useCallback(\n async (id: string, version?: number) => {\n if (!arbi) throw new Error('Sign in to open a presentation.')\n const response = await arbi.documents.download(id, version)\n const bytes = new Uint8Array(await response.arrayBuffer())\n const text = await presentationData(bytes)\n const [doc] = await arbi.documents.get([id]).catch(() => [])\n const openedVersion = version ?? doc?.current_version ?? null\n if (text === null) {\n // A PowerPoint made elsewhere: imported, the version imported pinned (what cannot be edited\n // here is shown from that version's PDF).\n const imported = await importPptx(bytes, {\n title: presentationTitle(doc?.file_name ?? 'Presentation.pptx'),\n documentId: id,\n version: version ?? doc?.current_version,\n fileName: doc?.file_name ?? undefined,\n })\n return {\n saved: imported.saved,\n legacy: false,\n imported: { notes: imported.notes },\n openedVersion,\n }\n }\n let saved: SavedPresentation = parsePresentation(text)\n let changedOutside: number[] | undefined\n if (isZipBytes(bytes) && saved.slides.some((slide) => slide.fingerprint)) {\n const now = await slideFingerprints(bytes)\n const unchanged =\n now.length === saved.slides.length &&\n now.every((fingerprint, i) => fingerprint === saved.slides[i].fingerprint)\n if (!unchanged) {\n const imported = await importPptx(bytes, {\n title: saved.title,\n documentId: id,\n version: version ?? doc?.current_version,\n fileName: doc?.file_name ?? undefined,\n })\n const merged = mergeOutsideEdits(saved, imported.saved, now)\n saved = merged.saved\n changedOutside = merged.changed\n }\n }\n return {\n saved,\n // Saved before presentations were PowerPoint files: its next save is a new PowerPoint\n // file, not a version of this one (a document's versions keep one format).\n legacy: !isZipBytes(bytes),\n changedOutside,\n openedVersion,\n }\n },\n [arbi]\n )\n\n /** A new presentation from a template: its look and slides, without clips, unsaved. */\n const startFromTemplate = useCallback(\n async (id: string) => {\n const { saved } = await readSaved(id)\n usePresentation.getState().load({\n title: 'Untitled presentation',\n savedId: null,\n captions: saved.captions,\n labelColumn: saved.labelColumn ?? null,\n theme: saved.theme,\n clips: [],\n slides: saved.slides.map((slide) => ({ ...slide, items: [] })),\n media: saved.media,\n })\n },\n [readSaved]\n )\n\n /** A file of the workspace (e.g. the theme's logo). */\n const downloadFile = useCallback(\n async (id: string): Promise<Blob> => {\n if (!arbi) throw new Error('Sign in to read the file.')\n return (await arbi.documents.download(id)).blob()\n },\n [arbi]\n )\n\n /**\n * Open a saved presentation (as of `version`; default its current one) in place of this one.\n * Resolves with how many clips were left out, and whether its slides are stale (its data was\n * changed outside the editor: the next save draws them again).\n */\n const open = useCallback(\n async (id: string, version?: number): Promise<OpenedPresentation> => {\n if (!arbi) throw new Error('Sign in to open a presentation.')\n const { saved, legacy, imported, changedOutside, openedVersion } = await readSaved(\n id,\n version\n )\n const media = saved.media ?? {}\n\n // Each source fetched once, however many clips come from it.\n const sources = new Map<string, Promise<Blob>>()\n const sourceOf = (clip: SavedClip) => {\n if (clip.media) {\n const url = media[clip.media]\n return url ? Promise.resolve(dataUrlBlob(url)) : Promise.reject(new Error('No image'))\n }\n const key = `${clip.kind === 'pdf' ? 'pdf' : 'file'}:${clip.documentId}@${clip.version ?? ''}`\n let pending = sources.get(key)\n if (!pending) {\n pending =\n clip.kind === 'pdf'\n ? fetchDocumentPdf(client, clip.documentId, { version: clip.version ?? null })\n : arbi.documents.download(clip.documentId, clip.version).then((r) => r.blob())\n sources.set(key, pending)\n }\n return pending\n }\n // Redactions per page, read once per source version.\n const markups = new Map<string, Promise<Map<number, Rect[]>>>()\n const masksOf = (clip: SavedClip) => {\n const key = `${clip.documentId}@${clip.version ?? ''}`\n let pending = markups.get(key)\n if (!pending) {\n pending = createMarkupStore({ version: clip.version, client })\n .load(clip.documentId)\n .then((layer) => {\n const byPage = new Map<number, Rect[]>()\n for (const mark of layer.annotations) {\n if (mark.kind !== 'redaction' || mark.status === 'rejected') continue\n byPage.set(mark.page, [...(byPage.get(mark.page) ?? []), ...mark.rects])\n }\n return byPage\n })\n .catch((error: unknown) => {\n // No markup at all is no redaction; anything else is a reason not to show it.\n if (error instanceof MarkupStoreError && error.status === 404) return new Map()\n throw error\n })\n markups.set(key, pending)\n }\n return pending\n }\n\n const opened = await Promise.all(\n saved.clips.map(async (clip): Promise<Omit<Clip, 'id'> | null> => {\n try {\n const [source, masks] = await Promise.all([\n sourceOf(clip),\n clip.kind === 'pdf' && !clip.media\n ? masksOf(clip)\n : Promise.resolve(new Map<number, Rect[]>()),\n ])\n return { ...clip, source, masks: masks.get(clip.page) ?? [] }\n } catch {\n return null\n }\n })\n )\n // Keep slide items pointing at the right clips once the missing ones are dropped.\n const kept: Omit<Clip, 'id'>[] = []\n const newIndex = opened.map((clip) => (clip ? kept.push(clip) - 1 : -1))\n usePresentation.getState().load({\n title: saved.title,\n savedId: legacy ? null : id,\n version: legacy ? null : openedVersion,\n boundTo: id,\n captions: saved.captions,\n labelColumn: saved.labelColumn ?? null,\n theme: saved.theme,\n clips: kept,\n slides: saved.slides.map((slide) => ({\n ...slide,\n items: slide.items\n .filter((item) => newIndex[item.clip] >= 0)\n .map((item) => ({ ...item, clip: newIndex[item.clip] })),\n })),\n media,\n })\n return {\n missing: opened.length - kept.length,\n stale: saved.slidesStale === true,\n ...(imported ? { imported } : {}),\n ...(changedOutside?.length ? { changedOutside } : {}),\n }\n },\n [arbi, client, readSaved]\n )\n\n return {\n save,\n exportPdf,\n list,\n open,\n startFromTemplate,\n downloadFile,\n canSave: !!arbi && !!workspaceId,\n }\n}\n"],
5
- "mappings": ";AAeA,SAAS,mBAAmB;AAC5B,SAAS,SAAS,2BAA2B;AAC7C,SAAS,8BAA8B;AACvC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,YAAY,yBAAyB;AAC9C,SAAS,yBAAyB;AAGlC,MAAM,YAAY;AAGlB,MAAM,aAAa,EAAE,OAAO,MAAM,QAAQ,IAAI;AAG9C,SAAS,YAAY,KAAmB;AACtC,QAAM,CAAC,MAAM,IAAI,IAAI,IAAI,MAAM,KAAK,CAAC;AACrC,QAAM,OAAO,gBAAgB,KAAK,IAAI,IAAI,CAAC,KAAK;AAChD,QAAM,SAAS,KAAK,QAAQ,EAAE;AAC9B,QAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;AAC1C,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,IAAK,OAAM,CAAC,IAAI,OAAO,WAAW,CAAC;AACtE,SAAO,IAAI,KAAK,CAAC,KAAK,GAAG,EAAE,KAAK,CAAC;AACnC;AAkBA,eAAe,iBAAiB,OAA2C;AACzE,MAAI,WAAW,KAAK,EAAG,QAAO,yBAAyB,KAAK;AAC5D,QAAM,QAAQ,MAAM,CAAC,MAAM,MAAQ,MAAM,CAAC,MAAM,MAAQ,MAAM,CAAC,MAAM,MAAQ,MAAM,CAAC,MAAM;AAC1F,MAAI,CAAC,MAAO,QAAO,IAAI,YAAY,EAAE,OAAO,KAAK;AACjD,QAAM,OAAO,MAAM,kBAAkB,OAAO,uBAAuB;AACnE,MAAI,CAAC,MAAM;AACT,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO,IAAI,YAAY,EAAE,OAAO,IAAI;AACtC;AASA,SAAS,QAAQ,cAAuB;AACtC,QAAM,QAAQ,gBAAgB,SAAS;AACvC,SAAO,eACH;AAAA,IACE,GAAG;AAAA,IACH,OAAO,CAAC;AAAA,IACR,QAAQ,MAAM,OAAO,IAAI,CAAC,WAAkB,EAAE,GAAG,OAAO,OAAO,CAAC,EAAE,EAAE;AAAA,EACtE,IACA;AACN;AAEA,MAAM,SAAS,CAAC,WAAuC;AAAA,EACrD,QAAQ,MAAM;AAAA,EACd,cAAc,MAAM;AAAA,EACpB,OAAO,MAAM;AAAA,EACb,MAAM,MAAM;AAAA,EACZ,OAAO,MAAM;AACf;AAEO,SAAS,uBAAuB;AACrC,QAAM,OAAO,QAAQ;AACrB,QAAM,EAAE,OAAO,IAAI,iBAAiB;AACpC,QAAM,cAAc,oBAAoB,CAAC,MAAM,EAAE,WAAW;AAG5D,QAAM,YAAY,YAAY,OAAO,OAAe,iBAAyC;AAC3F,UAAM,QAAQ,QAAQ,YAAY;AAClC,UAAM,OAAO,MAAM,UAAU,EAAE,OAAO,QAAQ,MAAM,QAAQ,OAAO,MAAM,MAAM,GAAG,OAAO,KAAK,CAAC;AAG/F,UAAM,eAAe,MAAM,kBAAkB,IAAI;AACjD,UAAM,OAAO,sBAAsB,EAAE,GAAG,OAAO,OAAO,aAAa,CAAC;AACpE,WAAO,0BAA0B,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,EACtE,GAAG,CAAC,CAAC;AASL,QAAM,OAAO;AAAA,IACX,OACE,OACA,EAAE,QAAQ,OAAO,WAAW,MAAM,IAA6C,CAAC,MAC5D;AACpB,UAAI,CAAC,QAAQ,CAAC,YAAa,OAAM,IAAI,MAAM,4CAA4C;AACvF,YAAM,QAAQ,gBAAgB,SAAS;AAEvC,YAAM,YAAY,SAAS,WAAW,OAAO,MAAM;AACnD,YAAM,OAAO,MAAM,UAAU,OAAO,QAAQ;AAC5C,YAAM,OAAO,IAAI,KAAK,CAAC,IAAI,GAAG,qBAAqB,KAAK,GAAG,EAAE,MAAM,UAAU,CAAC;AAC9E,YAAM,SAAS,MAAM,KAAK,UAAU,OAAO,CAAC,IAAI,GAAG;AAAA,QACjD,QAAQ,WAAW,mBAAmB;AAAA,QACtC,gBAAgB;AAAA,QAChB,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,MACnC,CAAC;AACD,YAAM,KAAK,aAAa,OAAO,cAAc,CAAC;AAC9C,UAAI,CAAC,GAAI,OAAM,IAAI,MAAM,sCAAsC;AAC/D,UAAI,CAAC,UAAU;AAEb,cAAM,CAAC,GAAG,IAAI,MAAM,KAAK,UAAU,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,MAAM,CAAC,CAAC;AAC3D,cAAM,SAAS,IAAI,OAAO,KAAK,mBAAmB,IAAI;AAAA,MACxD;AACA,aAAO;AAAA,IACT;AAAA,IACA,CAAC,MAAM,aAAa,SAAS;AAAA,EAC/B;AAMA,QAAM,YAAY;AAAA,IAChB,OAAO,UAAmC;AACxC,UAAI,CAAC,QAAQ,CAAC,YAAa,OAAM,IAAI,MAAM,qCAAqC;AAChF,YAAM,QAAQ,QAAQ,KAAK;AAC3B,YAAM,QAAQ,CAAC;AACf,iBAAW,SAAS,MAAM,QAAQ;AAChC,cAAM,EAAE,OAAO,KAAK,IAAI,MAAM,YAAY,OAAO,MAAM,OAAO,OAAO,KAAK,CAAC;AAC3E,cAAM,KAAK,EAAE,OAAO,MAAM,OAAO,WAAW,OAAO,QAAQ,WAAW,OAAO,CAAC;AAAA,MAChF;AACA,YAAM,MAAM,MAAM,YAAY,OAAO,EAAE,MAAM,CAAC;AAC9C,YAAM,SAAS,MAAM,KAAK,UAAU;AAAA,QAClC,CAAC,IAAI,KAAK,CAAC,GAAG,GAAG,GAAG,aAAa,KAAK,CAAC,QAAQ,EAAE,MAAM,kBAAkB,CAAC,CAAC;AAAA,QAC3E,EAAE,QAAQ,gBAAgB,gBAAgB,YAAY;AAAA,MACxD;AACA,YAAM,KAAK,OAAO,cAAc,CAAC;AACjC,UAAI,CAAC,GAAI,OAAM,IAAI,MAAM,6BAA6B;AACtD,aAAO;AAAA,IACT;AAAA,IACA,CAAC,MAAM,WAAW;AAAA,EACpB;AAGA,QAAM,OAAO;AAAA,IACX,OACE,QAAuC,oBACD;AACtC,UAAI,CAAC,QAAQ,CAAC,YAAa,QAAO,CAAC;AACnC,YAAM,OAAO,MAAM,KAAK,UAAU,KAAK,EAAE,YAAY,CAAC;AACtD,aAAO,KACJ,OAAO,CAAC,QAAQ;AACf,cAAM,SAAS,IAAI,UAAU;AAC7B,YAAI,CAAC,uBAAuB,IAAI,WAAW,MAAM,EAAG,QAAO;AAC3D,eAAO,UAAU,cACb,WAAW,mBACX,OAAO,WAAW,oBAAoB,KACpC,WAAW,oBACX,CAAC,OAAO,WAAW,cAAc;AAAA,MACzC,CAAC,EACA,IAAI,CAAC,SAAS;AAAA,QACb,IAAI,IAAI;AAAA,QACR,OAAO,kBAAkB,IAAI,aAAa,EAAE;AAAA,QAC5C,WAAW,IAAI,cAAc,IAAI,cAAc;AAAA,MACjD,EAAE,EACD,KAAK,CAAC,GAAG,OAAQ,EAAE,aAAa,OAAO,EAAE,aAAa,MAAM,IAAI,EAAG;AAAA,IACxE;AAAA,IACA,CAAC,MAAM,WAAW;AAAA,EACpB;AAOA,QAAM,YAAY;AAAA,IAChB,OAAO,IAAY,YAAqB;AACtC,UAAI,CAAC,KAAM,OAAM,IAAI,MAAM,iCAAiC;AAC5D,YAAM,WAAW,MAAM,KAAK,UAAU,SAAS,IAAI,OAAO;AAC1D,YAAM,QAAQ,IAAI,WAAW,MAAM,SAAS,YAAY,CAAC;AACzD,YAAM,OAAO,MAAM,iBAAiB,KAAK;AACzC,YAAM,CAAC,GAAG,IAAI,MAAM,KAAK,UAAU,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,MAAM,CAAC,CAAC;AAC3D,YAAM,gBAAgB,WAAW,KAAK,mBAAmB;AACzD,UAAI,SAAS,MAAM;AAGjB,cAAM,WAAW,MAAM,WAAW,OAAO;AAAA,UACvC,OAAO,kBAAkB,KAAK,aAAa,mBAAmB;AAAA,UAC9D,YAAY;AAAA,UACZ,SAAS,WAAW,KAAK;AAAA,UACzB,UAAU,KAAK,aAAa;AAAA,QAC9B,CAAC;AACD,eAAO;AAAA,UACL,OAAO,SAAS;AAAA,UAChB,QAAQ;AAAA,UACR,UAAU,EAAE,OAAO,SAAS,MAAM;AAAA,UAClC;AAAA,QACF;AAAA,MACF;AACA,UAAI,QAA2B,kBAAkB,IAAI;AACrD,UAAI;AACJ,UAAI,WAAW,KAAK,KAAK,MAAM,OAAO,KAAK,CAAC,UAAU,MAAM,WAAW,GAAG;AACxE,cAAM,MAAM,MAAM,kBAAkB,KAAK;AACzC,cAAM,YACJ,IAAI,WAAW,MAAM,OAAO,UAC5B,IAAI,MAAM,CAAC,aAAa,MAAM,gBAAgB,MAAM,OAAO,CAAC,EAAE,WAAW;AAC3E,YAAI,CAAC,WAAW;AACd,gBAAM,WAAW,MAAM,WAAW,OAAO;AAAA,YACvC,OAAO,MAAM;AAAA,YACb,YAAY;AAAA,YACZ,SAAS,WAAW,KAAK;AAAA,YACzB,UAAU,KAAK,aAAa;AAAA,UAC9B,CAAC;AACD,gBAAM,SAAS,kBAAkB,OAAO,SAAS,OAAO,GAAG;AAC3D,kBAAQ,OAAO;AACf,2BAAiB,OAAO;AAAA,QAC1B;AAAA,MACF;AACA,aAAO;AAAA,QACL;AAAA;AAAA;AAAA,QAGA,QAAQ,CAAC,WAAW,KAAK;AAAA,QACzB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,IAAI;AAAA,EACP;AAGA,QAAM,oBAAoB;AAAA,IACxB,OAAO,OAAe;AACpB,YAAM,EAAE,MAAM,IAAI,MAAM,UAAU,EAAE;AACpC,sBAAgB,SAAS,EAAE,KAAK;AAAA,QAC9B,OAAO;AAAA,QACP,SAAS;AAAA,QACT,UAAU,MAAM;AAAA,QAChB,aAAa,MAAM,eAAe;AAAA,QAClC,OAAO,MAAM;AAAA,QACb,OAAO,CAAC;AAAA,QACR,QAAQ,MAAM,OAAO,IAAI,CAAC,WAAW,EAAE,GAAG,OAAO,OAAO,CAAC,EAAE,EAAE;AAAA,QAC7D,OAAO,MAAM;AAAA,MACf,CAAC;AAAA,IACH;AAAA,IACA,CAAC,SAAS;AAAA,EACZ;AAGA,QAAM,eAAe;AAAA,IACnB,OAAO,OAA8B;AACnC,UAAI,CAAC,KAAM,OAAM,IAAI,MAAM,2BAA2B;AACtD,cAAQ,MAAM,KAAK,UAAU,SAAS,EAAE,GAAG,KAAK;AAAA,IAClD;AAAA,IACA,CAAC,IAAI;AAAA,EACP;AAOA,QAAM,OAAO;AAAA,IACX,OAAO,IAAY,YAAkD;AACnE,UAAI,CAAC,KAAM,OAAM,IAAI,MAAM,iCAAiC;AAC5D,YAAM,EAAE,OAAO,QAAQ,UAAU,gBAAgB,cAAc,IAAI,MAAM;AAAA,QACvE;AAAA,QACA;AAAA,MACF;AACA,YAAM,QAAQ,MAAM,SAAS,CAAC;AAG9B,YAAM,UAAU,oBAAI,IAA2B;AAC/C,YAAM,WAAW,CAAC,SAAoB;AACpC,YAAI,KAAK,OAAO;AACd,gBAAM,MAAM,MAAM,KAAK,KAAK;AAC5B,iBAAO,MAAM,QAAQ,QAAQ,YAAY,GAAG,CAAC,IAAI,QAAQ,OAAO,IAAI,MAAM,UAAU,CAAC;AAAA,QACvF;AACA,cAAM,MAAM,GAAG,KAAK,SAAS,QAAQ,QAAQ,MAAM,IAAI,KAAK,UAAU,IAAI,KAAK,WAAW,EAAE;AAC5F,YAAI,UAAU,QAAQ,IAAI,GAAG;AAC7B,YAAI,CAAC,SAAS;AACZ,oBACE,KAAK,SAAS,QACV,iBAAiB,QAAQ,KAAK,YAAY,EAAE,SAAS,KAAK,WAAW,KAAK,CAAC,IAC3E,KAAK,UAAU,SAAS,KAAK,YAAY,KAAK,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC;AACjF,kBAAQ,IAAI,KAAK,OAAO;AAAA,QAC1B;AACA,eAAO;AAAA,MACT;AAEA,YAAM,UAAU,oBAAI,IAA0C;AAC9D,YAAM,UAAU,CAAC,SAAoB;AACnC,cAAM,MAAM,GAAG,KAAK,UAAU,IAAI,KAAK,WAAW,EAAE;AACpD,YAAI,UAAU,QAAQ,IAAI,GAAG;AAC7B,YAAI,CAAC,SAAS;AACZ,oBAAU,kBAAkB,EAAE,SAAS,KAAK,SAAS,OAAO,CAAC,EAC1D,KAAK,KAAK,UAAU,EACpB,KAAK,CAAC,UAAU;AACf,kBAAM,SAAS,oBAAI,IAAoB;AACvC,uBAAW,QAAQ,MAAM,aAAa;AACpC,kBAAI,KAAK,SAAS,eAAe,KAAK,WAAW,WAAY;AAC7D,qBAAO,IAAI,KAAK,MAAM,CAAC,GAAI,OAAO,IAAI,KAAK,IAAI,KAAK,CAAC,GAAI,GAAG,KAAK,KAAK,CAAC;AAAA,YACzE;AACA,mBAAO;AAAA,UACT,CAAC,EACA,MAAM,CAAC,UAAmB;AAEzB,gBAAI,iBAAiB,oBAAoB,MAAM,WAAW,IAAK,QAAO,oBAAI,IAAI;AAC9E,kBAAM;AAAA,UACR,CAAC;AACH,kBAAQ,IAAI,KAAK,OAAO;AAAA,QAC1B;AACA,eAAO;AAAA,MACT;AAEA,YAAM,SAAS,MAAM,QAAQ;AAAA,QAC3B,MAAM,MAAM,IAAI,OAAO,SAA2C;AAChE,cAAI;AACF,kBAAM,CAAC,QAAQ,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,cACxC,SAAS,IAAI;AAAA,cACb,KAAK,SAAS,SAAS,CAAC,KAAK,QACzB,QAAQ,IAAI,IACZ,QAAQ,QAAQ,oBAAI,IAAoB,CAAC;AAAA,YAC/C,CAAC;AACD,mBAAO,EAAE,GAAG,MAAM,QAAQ,OAAO,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,EAAE;AAAA,UAC9D,QAAQ;AACN,mBAAO;AAAA,UACT;AAAA,QACF,CAAC;AAAA,MACH;AAEA,YAAM,OAA2B,CAAC;AAClC,YAAM,WAAW,OAAO,IAAI,CAAC,SAAU,OAAO,KAAK,KAAK,IAAI,IAAI,IAAI,EAAG;AACvE,sBAAgB,SAAS,EAAE,KAAK;AAAA,QAC9B,OAAO,MAAM;AAAA,QACb,SAAS,SAAS,OAAO;AAAA,QACzB,SAAS,SAAS,OAAO;AAAA,QACzB,SAAS;AAAA,QACT,UAAU,MAAM;AAAA,QAChB,aAAa,MAAM,eAAe;AAAA,QAClC,OAAO,MAAM;AAAA,QACb,OAAO;AAAA,QACP,QAAQ,MAAM,OAAO,IAAI,CAAC,WAAW;AAAA,UACnC,GAAG;AAAA,UACH,OAAO,MAAM,MACV,OAAO,CAAC,SAAS,SAAS,KAAK,IAAI,KAAK,CAAC,EACzC,IAAI,CAAC,UAAU,EAAE,GAAG,MAAM,MAAM,SAAS,KAAK,IAAI,EAAE,EAAE;AAAA,QAC3D,EAAE;AAAA,QACF;AAAA,MACF,CAAC;AACD,aAAO;AAAA,QACL,SAAS,OAAO,SAAS,KAAK;AAAA,QAC9B,OAAO,MAAM,gBAAgB;AAAA,QAC7B,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,QAC/B,GAAI,gBAAgB,SAAS,EAAE,eAAe,IAAI,CAAC;AAAA,MACrD;AAAA,IACF;AAAA,IACA,CAAC,MAAM,QAAQ,SAAS;AAAA,EAC1B;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC;AAAA,EACvB;AACF;",
4
+ "sourcesContent": ["/**\n * Saving and opening presentations as documents of the open workspace's `Presentations` folder:\n * a PowerPoint file (`<title>.pptx`: its slides as editable PowerPoint, the presentation's own data\n * inside it, `pptx-data`). Saving again adds a version to the same document; Export PDF saves a\n * PDF of the slides.\n *\n * Opening fetches each clip's source again: the PDF the viewer shows for a page clip (so Word and\n * text documents work too), the original file for an image or a video. A page clip's redactions\n * are read again from the markup; a clip whose source or redactions cannot be read is left out\n * (never shown unredacted), and how many were is reported.\n *\n * Presentations saved before they were PowerPoint files (a PDF with the data attached, or the data\n * alone) still open; their first save makes a PowerPoint file of them.\n */\n\nimport { useCallback } from 'react'\nimport { useArbi, useArbiSessionState } from '@arbidocs/react'\nimport { isPresentationFileName } from '@arbidocs/react/ui'\nimport {\n createMarkupStore,\n fetchDocumentPdf,\n imagesToPdf,\n MarkupStoreError,\n readPdfAttachment,\n useDocumentTools,\n} from '@arbidocs/react/document-tools'\nimport type { Clip, Rect, Slide } from '../../presentation/model'\nimport {\n EXPORTS_FOLDER,\n PRESENTATIONS_FOLDER,\n PRESENTATION_ATTACHMENT,\n TEMPLATES_FOLDER,\n parsePresentation,\n presentationFileName,\n presentationTitle,\n safeFileStem,\n serializePresentation,\n type SavedClip,\n} from '../../presentation/persistence'\nimport { usePresentation } from '../../presentation/store'\nimport { renderSlide } from '../../presentation/render-slides'\nimport { buildPptx } from '../../presentation/export-pptx'\nimport {\n isZipBytes,\n readPptxPresentationData,\n writePptxPresentationData,\n} from '../../presentation/pptx-data'\nimport { importPptx, slideFingerprints } from '../../presentation/import-pptx'\nimport { mergeOutsideEdits } from '../../presentation/merge-outside-edits'\nimport type { SavedPresentation } from '../../presentation/persistence'\n\nconst PPTX_TYPE = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'\n\n/** A slide's page in the exported PDF, in points: 16:9. */\nconst SLIDE_PAGE = { width: 1280, height: 720 }\n\n/** An image embedded as a data URL, as a file. */\nfunction dataUrlBlob(url: string): Blob {\n const [head, body] = url.split(',', 2)\n const type = /^data:([^;]+)/.exec(head)?.[1] ?? 'application/octet-stream'\n const binary = atob(body ?? '')\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)\n return new Blob([bytes], { type })\n}\n\n/** What opening a presentation found besides its content. */\nexport interface OpenedPresentation {\n /** Clips whose source could not be opened (left out). */\n missing: number\n /** Changed outside the editor (an agent): the slides are drawn again on the next save. */\n stale: boolean\n /** A PowerPoint made elsewhere, imported: what did not come across as it was. */\n imported?: { notes: string[] }\n /** Slides (1-based) changed in PowerPoint since the editor saved it, brought in. */\n changedOutside?: number[]\n}\n\n/**\n * The editable data of a saved presentation: inside its PowerPoint file; or, saved before that,\n * attached to its PDF, or the file itself.\n */\nasync function presentationData(bytes: Uint8Array): Promise<string | null> {\n if (isZipBytes(bytes)) return readPptxPresentationData(bytes)\n const isPdf = bytes[0] === 0x25 && bytes[1] === 0x50 && bytes[2] === 0x44 && bytes[3] === 0x46\n if (!isPdf) return new TextDecoder().decode(bytes)\n const data = await readPdfAttachment(bytes, PRESENTATION_ATTACHMENT)\n if (!data) {\n throw new Error(\n 'This PDF has no editable presentation in it (it may have been changed outside ARBI).'\n )\n }\n return new TextDecoder().decode(data)\n}\n\nexport interface SavedPresentationEntry {\n id: string\n title: string\n updatedAt: string | null\n}\n\n/** The presentation as it is now, or (`withoutClips`) its look and slides only: a template. */\nfunction current(withoutClips: boolean) {\n const state = usePresentation.getState()\n return withoutClips\n ? {\n ...state,\n clips: [],\n slides: state.slides.map((slide): Slide => ({ ...slide, items: [] })),\n }\n : state\n}\n\nconst lookOf = (state: ReturnType<typeof current>) => ({\n labels: state.captions,\n columnValues: state.columnValues,\n theme: state.theme,\n logo: state.logoImage,\n media: state.media,\n})\n\nexport function usePresentationFiles() {\n const arbi = useArbi()\n const { client } = useDocumentTools()\n const workspaceId = useArbiSessionState((s) => s.workspaceId)\n\n /** The presentation as its PowerPoint file, its editable data inside. */\n const buildFile = useCallback(async (title: string, withoutClips: boolean): Promise<Blob> => {\n const state = current(withoutClips)\n const deck = await buildPptx({ title, slides: state.slides, clips: state.clips }, lookOf(state))\n // Drawn just now from the presentation itself: never stale. Each slide's fingerprint tells,\n // when it is opened again, which slides were changed in PowerPoint meanwhile.\n const fingerprints = await slideFingerprints(deck)\n const data = serializePresentation({ ...state, title, fingerprints })\n return writePptxPresentationData(deck, JSON.stringify(data, null, 2))\n }, [])\n\n /**\n * Save the presentation: `<title>.pptx` in Presentations, through the ordinary upload, so it is\n * viewed, searched and downloaded like any document (and opens in PowerPoint). A new version of\n * the document it was saved as, or (never saved, or `asNew`: Save as) a new document named\n * `title`; a template (its look and slides, no clips) goes to Presentations/Templates. Resolves\n * with its id.\n */\n const save = useCallback(\n async (\n title: string,\n {\n asNew = false,\n template = false,\n autosave = false,\n }: { asNew?: boolean; template?: boolean; autosave?: boolean } = {}\n ): Promise<string> => {\n if (!arbi || !workspaceId) throw new Error('Open a workspace to save the presentation.')\n const state = usePresentation.getState()\n // A template is always a new document; the open presentation stays what it was.\n const versionOf = asNew || template ? null : state.savedId\n const deck = await buildFile(title, template)\n const file = new File([deck], presentationFileName(title), { type: PPTX_TYPE })\n const result = await arbi.documents.upload([file], {\n folder: template ? TEMPLATES_FOLDER : PRESENTATIONS_FOLDER,\n workspaceExtId: workspaceId,\n ...(versionOf ? { versionOf, autosave } : {}),\n })\n const id = versionOf ?? result.doc_ext_ids?.[0]\n if (!id) throw new Error('The presentation could not be saved.')\n if (!template) {\n // The version just saved: a newer one (saved by ARBI, or someone else) is then told apart.\n const [doc] = await arbi.documents.get([id]).catch(() => [])\n state.setSaved(id, title, result.version ?? doc?.current_version ?? null)\n usePresentation.setState({ autosave: !!versionOf && autosave })\n }\n return id\n },\n [arbi, workspaceId, buildFile]\n )\n\n /**\n * Export the slides as a PDF named `title` (in Presentations/Exports): to send outside or file\n * in a bundle, each slide a page as presented, redactions burnt in.\n */\n const exportPdf = useCallback(\n async (title: string): Promise<string> => {\n if (!arbi || !workspaceId) throw new Error('Open a workspace to export the PDF.')\n const state = current(false)\n const pages = []\n for (const slide of state.slides) {\n const { image, text } = await renderSlide(slide, state.clips, lookOf(state))\n pages.push({ image, text, width: SLIDE_PAGE.width, height: SLIDE_PAGE.height })\n }\n const pdf = await imagesToPdf(pages, { title })\n const result = await arbi.documents.upload(\n [new File([pdf], `${safeFileStem(title)}.pdf`, { type: 'application/pdf' })],\n { folder: EXPORTS_FOLDER, workspaceExtId: workspaceId }\n )\n const id = result.doc_ext_ids?.[0]\n if (!id) throw new Error('The PDF could not be saved.')\n return id\n },\n [arbi, workspaceId]\n )\n\n /** The presentations saved in the open workspace (or its templates), newest first. */\n const list = useCallback(\n async (\n scope: 'presentations' | 'templates' = 'presentations'\n ): Promise<SavedPresentationEntry[]> => {\n if (!arbi || !workspaceId) return []\n const docs = await arbi.documents.list({ workspaceId })\n return docs\n .filter((doc) => {\n const folder = doc.folder ?? ''\n if (!isPresentationFileName(doc.file_name, folder)) return false\n return scope === 'templates'\n ? folder === TEMPLATES_FOLDER\n : folder.startsWith(PRESENTATIONS_FOLDER) &&\n folder !== TEMPLATES_FOLDER &&\n !folder.startsWith(EXPORTS_FOLDER)\n })\n .map((doc) => ({\n id: doc.external_id,\n title: presentationTitle(doc.file_name ?? ''),\n updatedAt: doc.updated_at ?? doc.created_at ?? null,\n }))\n .sort((a, b) => ((a.updatedAt ?? '') < (b.updatedAt ?? '') ? 1 : -1))\n },\n [arbi, workspaceId]\n )\n\n /**\n * A saved presentation's data: from its PowerPoint file (slides changed in PowerPoint since\n * brought in), its PDF's attachment or a file saved before that; a PowerPoint made elsewhere is\n * imported.\n */\n const readSaved = useCallback(\n async (id: string, version?: number) => {\n if (!arbi) throw new Error('Sign in to open a presentation.')\n const response = await arbi.documents.download(id, version)\n const bytes = new Uint8Array(await response.arrayBuffer())\n const text = await presentationData(bytes)\n const [doc] = await arbi.documents.get([id]).catch(() => [])\n const openedVersion = version ?? doc?.current_version ?? null\n const autosave = !!(version\n ? doc?.versions?.find((entry) => entry.n === version)?.autosave\n : doc?.autosave)\n if (text === null) {\n // A PowerPoint made elsewhere: imported, the version imported pinned (what cannot be edited\n // here is shown from that version's PDF).\n const imported = await importPptx(bytes, {\n title: presentationTitle(doc?.file_name ?? 'Presentation.pptx'),\n documentId: id,\n version: version ?? doc?.current_version,\n fileName: doc?.file_name ?? undefined,\n })\n return {\n saved: imported.saved,\n legacy: false,\n imported: { notes: imported.notes },\n openedVersion,\n autosave,\n }\n }\n let saved: SavedPresentation = parsePresentation(text)\n let changedOutside: number[] | undefined\n if (isZipBytes(bytes) && saved.slides.some((slide) => slide.fingerprint)) {\n const now = await slideFingerprints(bytes)\n const unchanged =\n now.length === saved.slides.length &&\n now.every((fingerprint, i) => fingerprint === saved.slides[i].fingerprint)\n if (!unchanged) {\n const imported = await importPptx(bytes, {\n title: saved.title,\n documentId: id,\n version: version ?? doc?.current_version,\n fileName: doc?.file_name ?? undefined,\n })\n const merged = mergeOutsideEdits(saved, imported.saved, now)\n saved = merged.saved\n changedOutside = merged.changed\n }\n }\n return {\n saved,\n // Saved before presentations were PowerPoint files: its next save is a new PowerPoint\n // file, not a version of this one (a document's versions keep one format).\n legacy: !isZipBytes(bytes),\n changedOutside,\n openedVersion,\n autosave,\n }\n },\n [arbi]\n )\n\n /** A new presentation from a template: its look and slides, without clips, unsaved. */\n const startFromTemplate = useCallback(\n async (id: string) => {\n const { saved } = await readSaved(id)\n usePresentation.getState().load({\n title: 'Untitled presentation',\n savedId: null,\n captions: saved.captions,\n labelColumn: saved.labelColumn ?? null,\n theme: saved.theme,\n clips: [],\n slides: saved.slides.map((slide) => ({ ...slide, items: [] })),\n media: saved.media,\n })\n },\n [readSaved]\n )\n\n /** A file of the workspace (e.g. the theme's logo). */\n const downloadFile = useCallback(\n async (id: string): Promise<Blob> => {\n if (!arbi) throw new Error('Sign in to read the file.')\n return (await arbi.documents.download(id)).blob()\n },\n [arbi]\n )\n\n /**\n * Open a saved presentation (as of `version`; default its current one) in place of this one.\n * Resolves with how many clips were left out, and whether its slides are stale (its data was\n * changed outside the editor: the next save draws them again).\n */\n const open = useCallback(\n async (id: string, version?: number): Promise<OpenedPresentation> => {\n if (!arbi) throw new Error('Sign in to open a presentation.')\n const { saved, legacy, imported, changedOutside, openedVersion, autosave } = await readSaved(\n id,\n version\n )\n const media = saved.media ?? {}\n\n // Each source fetched once, however many clips come from it.\n const sources = new Map<string, Promise<Blob>>()\n const sourceOf = (clip: SavedClip) => {\n if (clip.media) {\n const url = media[clip.media]\n return url ? Promise.resolve(dataUrlBlob(url)) : Promise.reject(new Error('No image'))\n }\n const key = `${clip.kind === 'pdf' ? 'pdf' : 'file'}:${clip.documentId}@${clip.version ?? ''}`\n let pending = sources.get(key)\n if (!pending) {\n pending =\n clip.kind === 'pdf'\n ? fetchDocumentPdf(client, clip.documentId, { version: clip.version ?? null })\n : arbi.documents.download(clip.documentId, clip.version).then((r) => r.blob())\n sources.set(key, pending)\n }\n return pending\n }\n // Redactions per page, read once per source version.\n const markups = new Map<string, Promise<Map<number, Rect[]>>>()\n const masksOf = (clip: SavedClip) => {\n const key = `${clip.documentId}@${clip.version ?? ''}`\n let pending = markups.get(key)\n if (!pending) {\n pending = createMarkupStore({ version: clip.version, client })\n .load(clip.documentId)\n .then((layer) => {\n const byPage = new Map<number, Rect[]>()\n for (const mark of layer.annotations) {\n if (mark.kind !== 'redaction' || mark.status === 'rejected') continue\n byPage.set(mark.page, [...(byPage.get(mark.page) ?? []), ...mark.rects])\n }\n return byPage\n })\n .catch((error: unknown) => {\n // No markup at all is no redaction; anything else is a reason not to show it.\n if (error instanceof MarkupStoreError && error.status === 404) return new Map()\n throw error\n })\n markups.set(key, pending)\n }\n return pending\n }\n\n const opened = await Promise.all(\n saved.clips.map(async (clip): Promise<Omit<Clip, 'id'> | null> => {\n try {\n const [source, masks] = await Promise.all([\n sourceOf(clip),\n clip.kind === 'pdf' && !clip.media\n ? masksOf(clip)\n : Promise.resolve(new Map<number, Rect[]>()),\n ])\n return { ...clip, source, masks: masks.get(clip.page) ?? [] }\n } catch {\n return null\n }\n })\n )\n // Keep slide items pointing at the right clips once the missing ones are dropped.\n const kept: Omit<Clip, 'id'>[] = []\n const newIndex = opened.map((clip) => (clip ? kept.push(clip) - 1 : -1))\n usePresentation.getState().load({\n title: saved.title,\n savedId: legacy ? null : id,\n version: legacy ? null : openedVersion,\n autosave,\n boundTo: id,\n captions: saved.captions,\n labelColumn: saved.labelColumn ?? null,\n theme: saved.theme,\n clips: kept,\n slides: saved.slides.map((slide) => ({\n ...slide,\n items: slide.items\n .filter((item) => newIndex[item.clip] >= 0)\n .map((item) => ({ ...item, clip: newIndex[item.clip] })),\n })),\n media,\n })\n return {\n missing: opened.length - kept.length,\n stale: saved.slidesStale === true,\n ...(imported ? { imported } : {}),\n ...(changedOutside?.length ? { changedOutside } : {}),\n }\n },\n [arbi, client, readSaved]\n )\n\n return {\n save,\n exportPdf,\n list,\n open,\n startFromTemplate,\n downloadFile,\n canSave: !!arbi && !!workspaceId,\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -18,6 +18,8 @@ import { useGuidedRuns } from "../../guided/useGuidedRuns.js";
18
18
  import { useRemoteControlActions } from "./remoteControl.js";
19
19
  function RunPlan({ request }) {
20
20
  const { sequences } = useGuidedRuns();
21
+ if (request.screenRequestId)
22
+ return /* @__PURE__ */ jsx("p", { "data-testid": "remote-control-consent-plan", children: request.screenQuestion });
21
23
  const steps = request.steps ?? [];
22
24
  if (steps.length === 0) {
23
25
  const label = request.sequenceId ? sequences[request.sequenceId]?.label : void 0;
@@ -75,7 +77,7 @@ function ConsentPrompt() {
75
77
  /* @__PURE__ */ jsxs("span", { children: [
76
78
  /* @__PURE__ */ jsx("span", { className: "font-semibold", "data-testid": "remote-control-consent-agent-name", children: pending.agentName }),
77
79
  " ",
78
- "wants to control your screen"
80
+ pending.screenRequestId ? "wants to view and control your screen" : "wants to control your screen"
79
81
  ] })
80
82
  ] }),
81
83
  /* @__PURE__ */ jsxs(DialogDescription, { className: "sr-only", children: [
@@ -86,6 +88,7 @@ function ConsentPrompt() {
86
88
  /* @__PURE__ */ jsxs(ModalSection, { className: "space-y-3 text-sm text-muted-foreground", children: [
87
89
  /* @__PURE__ */ jsx("p", { children: "Remote control lets this assistant click and type in the app on your behalf \u2014 you'll see the cursor move and click as it works." }),
88
90
  /* @__PURE__ */ jsx("p", { children: "You can turn it off at any time, and it is off by default at the start of every session." }),
91
+ pending.screenRequestId && /* @__PURE__ */ jsx("p", { children: "Choose a tab or window to share. While sharing, the assistant can inspect fresh images, recent ARBI console errors and failed requests, and click or type inside ARBI. Stop sharing ends viewing and control together. No audio or recording is captured." }),
89
92
  /* @__PURE__ */ jsx(RunPlan, { request: pending }),
90
93
  pending.agentExtId && /* @__PURE__ */ jsxs("p", { className: "text-xs", children: [
91
94
  "Requesting agent:",
@@ -107,7 +110,7 @@ function ConsentPrompt() {
107
110
  className: "w-full",
108
111
  onClick: () => remote.grantAndRun({ forSession: true }),
109
112
  "data-testid": "remote-control-consent-allow-session",
110
- children: "Allow for this session"
113
+ children: pending.screenRequestId ? "Start sharing" : "Allow for this session"
111
114
  }
112
115
  ),
113
116
  /* @__PURE__ */ jsx(
@@ -118,7 +121,7 @@ function ConsentPrompt() {
118
121
  className: "w-full",
119
122
  onClick: () => remote.grantAndRun({ forSession: false }),
120
123
  "data-testid": "remote-control-consent-allow-once",
121
- children: "Just this once"
124
+ children: pending.screenRequestId ? "Share one image" : "Just this once"
122
125
  }
123
126
  ),
124
127
  /* @__PURE__ */ jsx(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/connection/remote-control/ConnectedRemoteControlConsent.tsx"],
4
- "sourcesContent": ["/**\n * ConnectedRemoteControlConsent \u2014 the consent gate for backend-driven remote control.\n *\n * Opens whenever an `agt-\u2026` agent has requested to drive the UI over the websocket and remote\n * control is not already enabled this session (the request sits in the session's remote-control\n * `pending`). It names the requesting agent explicitly (GUARANTEE #3) so the user knows which of\n * their assistants is asking, and offers three choices:\n * - Allow for this session (primary) \u2014 enables it until the session ends.\n * - Just this once \u2014 runs the one requested action, stays off afterwards.\n * - Deny (cancel) \u2014 runs nothing.\n * It says what will run (the run's name, or the inline steps' notes and targets), and opens on\n * \"Just this once\", so Enter never grants the whole session. Nothing shows while the site has not\n * turned remote control on (`guided.remoteControl`).\n *\n * Mounted once by `ConnectedAppRuntime`. Its state lives in the session, so no props.\n */\n\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Button,\n} from '@arbidocs/react/ui'\nimport { useRef } from 'react'\nimport { useRemoteControl, type RemoteControlRequest } from '@arbidocs/react'\nimport { MousePointerClick } from 'lucide-react'\nimport { ModalSection } from '../../settings/ModalScaffold'\nimport { REMOTE_CONTROL_SURFACE, useMountedSurface } from '../../guided/surfaces'\nimport { useGuidedRuns } from '../../guided/useGuidedRuns'\nimport { useRemoteControlActions } from './remoteControl'\n\n/** What the request will run: the registered run's name, or its inline steps. */\nfunction RunPlan({ request }: { request: RemoteControlRequest }) {\n const { sequences } = useGuidedRuns()\n const steps = request.steps ?? []\n if (steps.length === 0) {\n const label = request.sequenceId ? sequences[request.sequenceId]?.label : undefined\n return (\n <p data-testid=\"remote-control-consent-plan\">\n It will run:{' '}\n <span className=\"font-medium text-foreground\">{label ?? request.sequenceId}</span>\n </p>\n )\n }\n // Every step, however many (the list scrolls): nothing runs that the user was not shown.\n return (\n <div data-testid=\"remote-control-consent-plan\">\n <p>\n It will ({steps.length} {steps.length === 1 ? 'step' : 'steps'}):\n </p>\n <ol className=\"mt-1 max-h-40 list-decimal space-y-0.5 overflow-y-auto pl-5 text-xs\">\n {steps.map((step, i) => (\n <li key={i}>\n {step.do === 'type' ? 'Type into' : 'Click'}{' '}\n <span className=\"break-all font-mono text-foreground\">{step.target}</span>\n {step.note ? <> \u2014 {step.note}</> : null}\n </li>\n ))}\n </ol>\n </div>\n )\n}\n\nexport function ConnectedRemoteControlConsent(): React.ReactElement | null {\n return useGuidedRuns().remoteControl ? <ConsentPrompt /> : null\n}\n\nfunction ConsentPrompt(): React.ReactElement | null {\n const pending = useRemoteControl((s) => s.pending)\n const remote = useRemoteControlActions()\n const onceRef = useRef<HTMLButtonElement>(null)\n // A remote request is refused while no prompt is on screen to ask.\n useMountedSurface('consent')\n\n // Closing the dialog by any means (Escape, overlay, X) is a denial.\n const handleOpenChange = (next: boolean): void => {\n if (!next) remote.deny()\n }\n\n if (!pending) return null\n\n return (\n <Dialog open={pending !== null} onOpenChange={handleOpenChange}>\n <DialogContent\n className=\"sm:max-w-md\"\n data-testid=\"remote-control-consent-modal\"\n {...{ [REMOTE_CONTROL_SURFACE]: '' }}\n // The safest choice has the focus: Enter runs it once, never grants the session.\n onOpenAutoFocus={(event) => {\n event.preventDefault()\n onceRef.current?.focus()\n }}\n >\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2\">\n <MousePointerClick className=\"h-5 w-5 text-primary\" />\n <span>\n <span className=\"font-semibold\" data-testid=\"remote-control-consent-agent-name\">\n {pending.agentName}\n </span>{' '}\n wants to control your screen\n </span>\n </DialogTitle>\n <DialogDescription className=\"sr-only\">\n {pending.agentName} is requesting permission to click and type in the app on your\n behalf. Choose whether to allow it for this session, just this once, or deny.\n </DialogDescription>\n </DialogHeader>\n\n <ModalSection className=\"space-y-3 text-sm text-muted-foreground\">\n <p>\n Remote control lets this assistant click and type in the app on your behalf \u2014\n you&apos;ll see the cursor move and click as it works.\n </p>\n <p>\n You can turn it off at any time, and it is off by default at the start of every session.\n </p>\n <RunPlan request={pending} />\n {pending.agentExtId && (\n <p className=\"text-xs\">\n Requesting agent:{' '}\n <span\n className=\"font-mono text-foreground\"\n data-testid=\"remote-control-consent-agent-id\"\n >\n {pending.agentExtId}\n </span>\n </p>\n )}\n </ModalSection>\n\n <DialogFooter className=\"flex-col gap-2 sm:flex-col sm:space-x-0\">\n <Button\n className=\"w-full\"\n onClick={() => remote.grantAndRun({ forSession: true })}\n data-testid=\"remote-control-consent-allow-session\"\n >\n Allow for this session\n </Button>\n <Button\n ref={onceRef}\n variant=\"outline\"\n className=\"w-full\"\n onClick={() => remote.grantAndRun({ forSession: false })}\n data-testid=\"remote-control-consent-allow-once\"\n >\n Just this once\n </Button>\n <Button\n variant=\"ghost\"\n className=\"w-full\"\n onClick={() => remote.deny()}\n data-testid=\"remote-control-consent-deny\"\n >\n Deny\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
- "mappings": ";AAyCM,SAiBmB,UAfjB,KAFF;AAxBN;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,cAAc;AACvB,SAAS,wBAAmD;AAC5D,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB,yBAAyB;AAC1D,SAAS,qBAAqB;AAC9B,SAAS,+BAA+B;AAGxC,SAAS,QAAQ,EAAE,QAAQ,GAAsC;AAC/D,QAAM,EAAE,UAAU,IAAI,cAAc;AACpC,QAAM,QAAQ,QAAQ,SAAS,CAAC;AAChC,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,QAAQ,QAAQ,aAAa,UAAU,QAAQ,UAAU,GAAG,QAAQ;AAC1E,WACE,qBAAC,OAAE,eAAY,+BAA8B;AAAA;AAAA,MAC9B;AAAA,MACb,oBAAC,UAAK,WAAU,+BAA+B,mBAAS,QAAQ,YAAW;AAAA,OAC7E;AAAA,EAEJ;AAEA,SACE,qBAAC,SAAI,eAAY,+BACf;AAAA,yBAAC,OAAE;AAAA;AAAA,MACS,MAAM;AAAA,MAAO;AAAA,MAAE,MAAM,WAAW,IAAI,SAAS;AAAA,MAAQ;AAAA,OACjE;AAAA,IACA,oBAAC,QAAG,WAAU,uEACX,gBAAM,IAAI,CAAC,MAAM,MAChB,qBAAC,QACE;AAAA,WAAK,OAAO,SAAS,cAAc;AAAA,MAAS;AAAA,MAC7C,oBAAC,UAAK,WAAU,uCAAuC,eAAK,QAAO;AAAA,MAClE,KAAK,OAAO,iCAAE;AAAA;AAAA,QAAI,KAAK;AAAA,SAAK,IAAM;AAAA,SAH5B,CAIT,CACD,GACH;AAAA,KACF;AAEJ;AAEO,SAAS,gCAA2D;AACzE,SAAO,cAAc,EAAE,gBAAgB,oBAAC,iBAAc,IAAK;AAC7D;AAEA,SAAS,gBAA2C;AAClD,QAAM,UAAU,iBAAiB,CAAC,MAAM,EAAE,OAAO;AACjD,QAAM,SAAS,wBAAwB;AACvC,QAAM,UAAU,OAA0B,IAAI;AAE9C,oBAAkB,SAAS;AAG3B,QAAM,mBAAmB,CAAC,SAAwB;AAChD,QAAI,CAAC,KAAM,QAAO,KAAK;AAAA,EACzB;AAEA,MAAI,CAAC,QAAS,QAAO;AAErB,SACE,oBAAC,UAAO,MAAM,YAAY,MAAM,cAAc,kBAC5C;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,eAAY;AAAA,MACX,GAAG,EAAE,CAAC,sBAAsB,GAAG,GAAG;AAAA,MAEnC,iBAAiB,CAAC,UAAU;AAC1B,cAAM,eAAe;AACrB,gBAAQ,SAAS,MAAM;AAAA,MACzB;AAAA,MAEA;AAAA,6BAAC,gBACC;AAAA,+BAAC,eAAY,WAAU,2BACrB;AAAA,gCAAC,qBAAkB,WAAU,wBAAuB;AAAA,YACpD,qBAAC,UACC;AAAA,kCAAC,UAAK,WAAU,iBAAgB,eAAY,qCACzC,kBAAQ,WACX;AAAA,cAAQ;AAAA,cAAI;AAAA,eAEd;AAAA,aACF;AAAA,UACA,qBAAC,qBAAkB,WAAU,WAC1B;AAAA,oBAAQ;AAAA,YAAU;AAAA,aAErB;AAAA,WACF;AAAA,QAEA,qBAAC,gBAAa,WAAU,2CACtB;AAAA,8BAAC,OAAE,kJAGH;AAAA,UACA,oBAAC,OAAE,sGAEH;AAAA,UACA,oBAAC,WAAQ,SAAS,SAAS;AAAA,UAC1B,QAAQ,cACP,qBAAC,OAAE,WAAU,WAAU;AAAA;AAAA,YACH;AAAA,YAClB;AAAA,cAAC;AAAA;AAAA,gBACC,WAAU;AAAA,gBACV,eAAY;AAAA,gBAEX,kBAAQ;AAAA;AAAA,YACX;AAAA,aACF;AAAA,WAEJ;AAAA,QAEA,qBAAC,gBAAa,WAAU,2CACtB;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,SAAS,MAAM,OAAO,YAAY,EAAE,YAAY,KAAK,CAAC;AAAA,cACtD,eAAY;AAAA,cACb;AAAA;AAAA,UAED;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,KAAK;AAAA,cACL,SAAQ;AAAA,cACR,WAAU;AAAA,cACV,SAAS,MAAM,OAAO,YAAY,EAAE,YAAY,MAAM,CAAC;AAAA,cACvD,eAAY;AAAA,cACb;AAAA;AAAA,UAED;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,SAAQ;AAAA,cACR,WAAU;AAAA,cACV,SAAS,MAAM,OAAO,KAAK;AAAA,cAC3B,eAAY;AAAA,cACb;AAAA;AAAA,UAED;AAAA,WACF;AAAA;AAAA;AAAA,EACF,GACF;AAEJ;",
4
+ "sourcesContent": ["/**\n * ConnectedRemoteControlConsent \u2014 the consent gate for backend-driven remote control.\n *\n * Opens whenever an `agt-\u2026` agent has requested to drive the UI over the websocket and remote\n * control is not already enabled this session (the request sits in the session's remote-control\n * `pending`). It names the requesting agent explicitly (GUARANTEE #3) so the user knows which of\n * their assistants is asking, and offers three choices:\n * - Allow for this session (primary) \u2014 enables it until the session ends.\n * - Just this once \u2014 runs the one requested action, stays off afterwards.\n * - Deny (cancel) \u2014 runs nothing.\n * It says what will run (the run's name, or the inline steps' notes and targets), and opens on\n * \"Just this once\", so Enter never grants the whole session. Nothing shows while the site has not\n * turned remote control on (`guided.remoteControl`).\n *\n * Mounted once by `ConnectedAppRuntime`. Its state lives in the session, so no props.\n */\n\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Button,\n} from '@arbidocs/react/ui'\nimport { useRef } from 'react'\nimport { useRemoteControl, type RemoteControlRequest } from '@arbidocs/react'\nimport { MousePointerClick } from 'lucide-react'\nimport { ModalSection } from '../../settings/ModalScaffold'\nimport { REMOTE_CONTROL_SURFACE, useMountedSurface } from '../../guided/surfaces'\nimport { useGuidedRuns } from '../../guided/useGuidedRuns'\nimport { useRemoteControlActions } from './remoteControl'\n\n/** What the request will run: the registered run's name, or its inline steps. */\nfunction RunPlan({ request }: { request: RemoteControlRequest }) {\n const { sequences } = useGuidedRuns()\n if (request.screenRequestId)\n return <p data-testid=\"remote-control-consent-plan\">{request.screenQuestion}</p>\n const steps = request.steps ?? []\n if (steps.length === 0) {\n const label = request.sequenceId ? sequences[request.sequenceId]?.label : undefined\n return (\n <p data-testid=\"remote-control-consent-plan\">\n It will run:{' '}\n <span className=\"font-medium text-foreground\">{label ?? request.sequenceId}</span>\n </p>\n )\n }\n // Every step, however many (the list scrolls): nothing runs that the user was not shown.\n return (\n <div data-testid=\"remote-control-consent-plan\">\n <p>\n It will ({steps.length} {steps.length === 1 ? 'step' : 'steps'}):\n </p>\n <ol className=\"mt-1 max-h-40 list-decimal space-y-0.5 overflow-y-auto pl-5 text-xs\">\n {steps.map((step, i) => (\n <li key={i}>\n {step.do === 'type' ? 'Type into' : 'Click'}{' '}\n <span className=\"break-all font-mono text-foreground\">{step.target}</span>\n {step.note ? <> \u2014 {step.note}</> : null}\n </li>\n ))}\n </ol>\n </div>\n )\n}\n\nexport function ConnectedRemoteControlConsent(): React.ReactElement | null {\n return useGuidedRuns().remoteControl ? <ConsentPrompt /> : null\n}\n\nfunction ConsentPrompt(): React.ReactElement | null {\n const pending = useRemoteControl((s) => s.pending)\n const remote = useRemoteControlActions()\n const onceRef = useRef<HTMLButtonElement>(null)\n // A remote request is refused while no prompt is on screen to ask.\n useMountedSurface('consent')\n\n // Closing the dialog by any means (Escape, overlay, X) is a denial.\n const handleOpenChange = (next: boolean): void => {\n if (!next) remote.deny()\n }\n\n if (!pending) return null\n\n return (\n <Dialog open={pending !== null} onOpenChange={handleOpenChange}>\n <DialogContent\n className=\"sm:max-w-md\"\n data-testid=\"remote-control-consent-modal\"\n {...{ [REMOTE_CONTROL_SURFACE]: '' }}\n // The safest choice has the focus: Enter runs it once, never grants the session.\n onOpenAutoFocus={(event) => {\n event.preventDefault()\n onceRef.current?.focus()\n }}\n >\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2\">\n <MousePointerClick className=\"h-5 w-5 text-primary\" />\n <span>\n <span className=\"font-semibold\" data-testid=\"remote-control-consent-agent-name\">\n {pending.agentName}\n </span>{' '}\n {pending.screenRequestId\n ? 'wants to view and control your screen'\n : 'wants to control your screen'}\n </span>\n </DialogTitle>\n <DialogDescription className=\"sr-only\">\n {pending.agentName} is requesting permission to click and type in the app on your\n behalf. Choose whether to allow it for this session, just this once, or deny.\n </DialogDescription>\n </DialogHeader>\n\n <ModalSection className=\"space-y-3 text-sm text-muted-foreground\">\n <p>\n Remote control lets this assistant click and type in the app on your behalf \u2014\n you&apos;ll see the cursor move and click as it works.\n </p>\n <p>\n You can turn it off at any time, and it is off by default at the start of every session.\n </p>\n {pending.screenRequestId && (\n <p>\n Choose a tab or window to share. While sharing, the assistant can inspect fresh\n images, recent ARBI console errors and failed requests, and click or type inside ARBI.\n Stop sharing ends viewing and control together. No audio or recording is captured.\n </p>\n )}\n <RunPlan request={pending} />\n {pending.agentExtId && (\n <p className=\"text-xs\">\n Requesting agent:{' '}\n <span\n className=\"font-mono text-foreground\"\n data-testid=\"remote-control-consent-agent-id\"\n >\n {pending.agentExtId}\n </span>\n </p>\n )}\n </ModalSection>\n\n <DialogFooter className=\"flex-col gap-2 sm:flex-col sm:space-x-0\">\n <Button\n className=\"w-full\"\n onClick={() => remote.grantAndRun({ forSession: true })}\n data-testid=\"remote-control-consent-allow-session\"\n >\n {pending.screenRequestId ? 'Start sharing' : 'Allow for this session'}\n </Button>\n <Button\n ref={onceRef}\n variant=\"outline\"\n className=\"w-full\"\n onClick={() => remote.grantAndRun({ forSession: false })}\n data-testid=\"remote-control-consent-allow-once\"\n >\n {pending.screenRequestId ? 'Share one image' : 'Just this once'}\n </Button>\n <Button\n variant=\"ghost\"\n className=\"w-full\"\n onClick={() => remote.deny()}\n data-testid=\"remote-control-consent-deny\"\n >\n Deny\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -9,12 +9,22 @@ function ConnectedRemoteControlIndicator() {
9
9
  }
10
10
  function Indicator() {
11
11
  const enabled = useRemoteControl((s) => s.enabled);
12
+ const sharing = useRemoteControl((s) => !!s.sharing);
12
13
  const controllingAgent = useRemoteControl((s) => s.controllingAgent);
13
14
  const remote = useRemoteControlActions();
14
15
  if (!enabled && !controllingAgent) return null;
15
16
  const isActive = controllingAgent !== null;
16
- const label = isActive ? `${controllingAgent.name} is controlling your screen` : "Remote control on";
17
- return /* @__PURE__ */ jsx(RemoteControlPill, { isActive, label, onDisable: () => remote.disable() });
17
+ const label = sharing ? controllingAgent ? `${controllingAgent.name} \xB7 screen sharing and control on` : "Sharing screen \xB7 remote control on" : isActive ? `${controllingAgent.name} is controlling your screen` : "Remote control on";
18
+ return /* @__PURE__ */ jsx(
19
+ RemoteControlPill,
20
+ {
21
+ isActive: isActive || sharing,
22
+ label,
23
+ disableLabel: sharing ? "Stop sharing" : void 0,
24
+ showDisableLabel: sharing,
25
+ onDisable: () => remote.disable()
26
+ }
27
+ );
18
28
  }
19
29
  export {
20
30
  ConnectedRemoteControlIndicator
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/connection/remote-control/ConnectedRemoteControlIndicator.tsx"],
4
- "sourcesContent": ["/**\n * ConnectedRemoteControlIndicator: the persistent remote-control pill (`RemoteControlPill`) on the\n * session's state. Shown while remote control is enabled this session (or a run is executing);\n * labels WHO is driving while a websocket-driven run executes, and always offers an immediate\n * disable (GUARANTEE #5). Hidden entirely when remote control is off and no run is active, and\n * while the site has not turned remote control on (`guided.remoteControl`).\n */\n\nimport { useRemoteControl } from '@arbidocs/react'\nimport { RemoteControlPill } from '../../shell/RemoteControlPill'\nimport { useGuidedRuns } from '../../guided/useGuidedRuns'\nimport { useRemoteControlActions } from './remoteControl'\n\nexport function ConnectedRemoteControlIndicator(): React.ReactElement | null {\n return useGuidedRuns().remoteControl ? <Indicator /> : null\n}\n\nfunction Indicator(): React.ReactElement | null {\n const enabled = useRemoteControl((s) => s.enabled)\n const controllingAgent = useRemoteControl((s) => s.controllingAgent)\n const remote = useRemoteControlActions()\n\n if (!enabled && !controllingAgent) return null\n\n const isActive = controllingAgent !== null\n const label = isActive\n ? `${controllingAgent.name} is controlling your screen`\n : 'Remote control on'\n\n return <RemoteControlPill isActive={isActive} label={label} onDisable={() => remote.disable()} />\n}\n"],
5
- "mappings": ";AAcyC;AANzC,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AAC9B,SAAS,+BAA+B;AAEjC,SAAS,kCAA6D;AAC3E,SAAO,cAAc,EAAE,gBAAgB,oBAAC,aAAU,IAAK;AACzD;AAEA,SAAS,YAAuC;AAC9C,QAAM,UAAU,iBAAiB,CAAC,MAAM,EAAE,OAAO;AACjD,QAAM,mBAAmB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB;AACnE,QAAM,SAAS,wBAAwB;AAEvC,MAAI,CAAC,WAAW,CAAC,iBAAkB,QAAO;AAE1C,QAAM,WAAW,qBAAqB;AACtC,QAAM,QAAQ,WACV,GAAG,iBAAiB,IAAI,gCACxB;AAEJ,SAAO,oBAAC,qBAAkB,UAAoB,OAAc,WAAW,MAAM,OAAO,QAAQ,GAAG;AACjG;",
4
+ "sourcesContent": ["/**\n * ConnectedRemoteControlIndicator: the persistent remote-control pill (`RemoteControlPill`) on the\n * session's state. Shown while remote control is enabled this session (or a run is executing);\n * labels WHO is driving while a websocket-driven run executes, and always offers an immediate\n * disable (GUARANTEE #5). Hidden entirely when remote control is off and no run is active, and\n * while the site has not turned remote control on (`guided.remoteControl`).\n */\n\nimport { useRemoteControl } from '@arbidocs/react'\nimport { RemoteControlPill } from '../../shell/RemoteControlPill'\nimport { useGuidedRuns } from '../../guided/useGuidedRuns'\nimport { useRemoteControlActions } from './remoteControl'\n\nexport function ConnectedRemoteControlIndicator(): React.ReactElement | null {\n return useGuidedRuns().remoteControl ? <Indicator /> : null\n}\n\nfunction Indicator(): React.ReactElement | null {\n const enabled = useRemoteControl((s) => s.enabled)\n const sharing = useRemoteControl((s) => !!s.sharing)\n const controllingAgent = useRemoteControl((s) => s.controllingAgent)\n const remote = useRemoteControlActions()\n\n if (!enabled && !controllingAgent) return null\n\n const isActive = controllingAgent !== null\n const label = sharing\n ? controllingAgent\n ? `${controllingAgent.name} \u00B7 screen sharing and control on`\n : 'Sharing screen \u00B7 remote control on'\n : isActive\n ? `${controllingAgent.name} is controlling your screen`\n : 'Remote control on'\n\n return (\n <RemoteControlPill\n isActive={isActive || sharing}\n label={label}\n disableLabel={sharing ? 'Stop sharing' : undefined}\n showDisableLabel={sharing}\n onDisable={() => remote.disable()}\n />\n )\n}\n"],
5
+ "mappings": ";AAcyC;AANzC,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AAClC,SAAS,qBAAqB;AAC9B,SAAS,+BAA+B;AAEjC,SAAS,kCAA6D;AAC3E,SAAO,cAAc,EAAE,gBAAgB,oBAAC,aAAU,IAAK;AACzD;AAEA,SAAS,YAAuC;AAC9C,QAAM,UAAU,iBAAiB,CAAC,MAAM,EAAE,OAAO;AACjD,QAAM,UAAU,iBAAiB,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO;AACnD,QAAM,mBAAmB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB;AACnE,QAAM,SAAS,wBAAwB;AAEvC,MAAI,CAAC,WAAW,CAAC,iBAAkB,QAAO;AAE1C,QAAM,WAAW,qBAAqB;AACtC,QAAM,QAAQ,UACV,mBACE,GAAG,iBAAiB,IAAI,wCACxB,0CACF,WACE,GAAG,iBAAiB,IAAI,gCACxB;AAEN,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,YAAY;AAAA,MACtB;AAAA,MACA,cAAc,UAAU,iBAAiB;AAAA,MACzC,kBAAkB;AAAA,MAClB,WAAW,MAAM,OAAO,QAAQ;AAAA;AAAA,EAClC;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -18,7 +18,7 @@ function RemoteControlSwitch() {
18
18
  label: "Remote control",
19
19
  labelFor: "remoteControlEnabled",
20
20
  ...{ [REMOTE_CONTROL_SURFACE]: "" },
21
- description: "Let an assistant click and type in the app on your behalf. Off by default every session \u2014 you'll always see the cursor move as it works.",
21
+ description: "Let an assistant help in the app. Screen sharing asks you to choose a tab or window; turning this off stops both sharing and control. Off by default every session.",
22
22
  children: /* @__PURE__ */ jsx(
23
23
  Switch,
24
24
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/connection/remote-control/ConnectedRemoteControlSwitch.tsx"],
4
- "sourcesContent": ["/**\n * The remote-control switch of Settings \u2192 Privacy. Letting an assistant click and type in the app\n * is a privacy-adjacent permission, so it is switched there rather than from the account menu.\n * Off by default every session. Not shown while the site has not turned remote control on\n * (`guided.remoteControl`).\n */\nimport { useRemoteControl } from '@arbidocs/react'\nimport { Switch } from '@arbidocs/react/ui'\nimport { SettingsRow } from '../../settings/SettingsPanel'\nimport { REMOTE_CONTROL_SURFACE } from '../../guided/surfaces'\nimport { useGuidedRuns } from '../../guided/useGuidedRuns'\nimport { useRemoteControlActions } from './remoteControl'\n\nexport function ConnectedRemoteControlSwitch() {\n return useGuidedRuns().remoteControl ? <RemoteControlSwitch /> : null\n}\n\nfunction RemoteControlSwitch() {\n const enabled = useRemoteControl((state) => state.enabled)\n const remote = useRemoteControlActions()\n return (\n <SettingsRow\n label=\"Remote control\"\n labelFor=\"remoteControlEnabled\"\n // The whole row, its label too, is remote control's own: a remote run never acts on it.\n {...{ [REMOTE_CONTROL_SURFACE]: '' }}\n description=\"Let an assistant click and type in the app on your behalf. Off by default every session \u2014 you'll always see the cursor move as it works.\"\n >\n <Switch\n id=\"remoteControlEnabled\"\n checked={enabled}\n onCheckedChange={(value) => (value ? remote.enable() : remote.disable())}\n data-testid=\"toggle-remote-control\"\n {...{ [REMOTE_CONTROL_SURFACE]: '' }}\n />\n </SettingsRow>\n )\n}\n"],
4
+ "sourcesContent": ["/**\n * The remote-control switch of Settings \u2192 Privacy. Letting an assistant click and type in the app\n * is a privacy-adjacent permission, so it is switched there rather than from the account menu.\n * Off by default every session. Not shown while the site has not turned remote control on\n * (`guided.remoteControl`).\n */\nimport { useRemoteControl } from '@arbidocs/react'\nimport { Switch } from '@arbidocs/react/ui'\nimport { SettingsRow } from '../../settings/SettingsPanel'\nimport { REMOTE_CONTROL_SURFACE } from '../../guided/surfaces'\nimport { useGuidedRuns } from '../../guided/useGuidedRuns'\nimport { useRemoteControlActions } from './remoteControl'\n\nexport function ConnectedRemoteControlSwitch() {\n return useGuidedRuns().remoteControl ? <RemoteControlSwitch /> : null\n}\n\nfunction RemoteControlSwitch() {\n const enabled = useRemoteControl((state) => state.enabled)\n const remote = useRemoteControlActions()\n return (\n <SettingsRow\n label=\"Remote control\"\n labelFor=\"remoteControlEnabled\"\n // The whole row, its label too, is remote control's own: a remote run never acts on it.\n {...{ [REMOTE_CONTROL_SURFACE]: '' }}\n description=\"Let an assistant help in the app. Screen sharing asks you to choose a tab or window; turning this off stops both sharing and control. Off by default every session.\"\n >\n <Switch\n id=\"remoteControlEnabled\"\n checked={enabled}\n onCheckedChange={(value) => (value ? remote.enable() : remote.disable())}\n data-testid=\"toggle-remote-control\"\n {...{ [REMOTE_CONTROL_SURFACE]: '' }}\n />\n </SettingsRow>\n )\n}\n"],
5
5
  "mappings": ";AAcyC;AARzC,SAAS,wBAAwB;AACjC,SAAS,cAAc;AACvB,SAAS,mBAAmB;AAC5B,SAAS,8BAA8B;AACvC,SAAS,qBAAqB;AAC9B,SAAS,+BAA+B;AAEjC,SAAS,+BAA+B;AAC7C,SAAO,cAAc,EAAE,gBAAgB,oBAAC,uBAAoB,IAAK;AACnE;AAEA,SAAS,sBAAsB;AAC7B,QAAM,UAAU,iBAAiB,CAAC,UAAU,MAAM,OAAO;AACzD,QAAM,SAAS,wBAAwB;AACvC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,UAAS;AAAA,MAER,GAAG,EAAE,CAAC,sBAAsB,GAAG,GAAG;AAAA,MACnC,aAAY;AAAA,MAEZ;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,SAAS;AAAA,UACT,iBAAiB,CAAC,UAAW,QAAQ,OAAO,OAAO,IAAI,OAAO,QAAQ;AAAA,UACtE,eAAY;AAAA,UACX,GAAG,EAAE,CAAC,sBAAsB,GAAG,GAAG;AAAA;AAAA,MACrC;AAAA;AAAA,EACF;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,7 @@
1
+ /** Session-only browser evidence, collected only while the user is sharing. No bodies, headers or storage. */
2
+ export declare function collectBrowserDiagnostics(): {
3
+ snapshot: () => string;
4
+ stop: () => void;
5
+ };
6
+ export declare function safeUrl(value: string): string;
7
+ export declare function sanitizeDiagnostic(value: string): string;
@@ -0,0 +1,109 @@
1
+ "use client";
2
+ import { formatErrorReport } from "../../settings/error-report.js";
3
+ function collectBrowserDiagnostics() {
4
+ const lines = [];
5
+ const add = (message) => {
6
+ lines.push(`${(/* @__PURE__ */ new Date()).toISOString()} ${sanitizeDiagnostic(message).slice(0, 1800)}`);
7
+ if (lines.length > 30) lines.shift();
8
+ };
9
+ const stringify = (value) => {
10
+ if (value instanceof Error)
11
+ return formatErrorReport({ error: value, url: safeUrl(location.href) });
12
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean")
13
+ return String(value);
14
+ return "[object omitted]";
15
+ };
16
+ const originalError = console.error;
17
+ const originalWarn = console.warn;
18
+ const error = (...args) => {
19
+ add(`error: ${args.map(stringify).join(" ")}`);
20
+ originalError.apply(console, args);
21
+ };
22
+ const warn = (...args) => {
23
+ add(`warning: ${args.map(stringify).join(" ")}`);
24
+ originalWarn.apply(console, args);
25
+ };
26
+ console.error = error;
27
+ console.warn = warn;
28
+ const onError = (event) => add(`uncaught: ${stringify(event.error ?? event.message)}`);
29
+ const onRejection = (event) => add(`unhandled rejection: ${stringify(event.reason)}`);
30
+ window.addEventListener("error", onError);
31
+ window.addEventListener("unhandledrejection", onRejection);
32
+ const originalFetch = window.fetch;
33
+ const wrappedFetch = async (...args) => {
34
+ const input = args[0];
35
+ const url = safeUrl(
36
+ typeof input === "string" ? input : input instanceof URL ? input.href : input.url
37
+ );
38
+ try {
39
+ const response = await originalFetch.apply(window, args);
40
+ if (!response.ok) add(`HTTP ${response.status}: ${url}`);
41
+ return response;
42
+ } catch (failure) {
43
+ add(`request failed: ${url}`);
44
+ throw failure;
45
+ }
46
+ };
47
+ window.fetch = wrappedFetch;
48
+ let resources;
49
+ if (typeof PerformanceObserver !== "undefined") {
50
+ try {
51
+ resources = new PerformanceObserver((list) => {
52
+ for (const entry of list.getEntries()) {
53
+ const resource = entry;
54
+ if ("responseStatus" in resource && Number(resource.responseStatus) >= 400)
55
+ add(`HTTP ${resource.responseStatus}: ${safeUrl(resource.name)}`);
56
+ }
57
+ });
58
+ resources.observe({ type: "resource" });
59
+ } catch {
60
+ resources = void 0;
61
+ }
62
+ }
63
+ return {
64
+ snapshot: () => [
65
+ `ARBI tab: ${safeUrl(location.href)}`,
66
+ `Viewport: ${innerWidth} x ${innerHeight}; DPR ${devicePixelRatio}`,
67
+ "Visible ARBI controls (selectors for control_ui):",
68
+ ...Array.from(
69
+ document.querySelectorAll(
70
+ 'button[data-testid],a[data-testid],input[data-testid]:not([type="password"]),select[data-testid],[role="button"][data-testid]'
71
+ )
72
+ ).filter(
73
+ (el) => el.getBoundingClientRect().width > 0 && !el.closest("[data-remote-control-surface]")
74
+ ).slice(0, 60).map(
75
+ (el) => `[data-testid=${JSON.stringify(el.dataset.testid)}] ${sanitizeDiagnostic(el.getAttribute("aria-label") ?? el.getAttribute("title") ?? el.textContent ?? "").slice(0, 80)}`
76
+ ),
77
+ ...lines
78
+ ].join("\n").slice(-18e3),
79
+ stop: () => {
80
+ if (console.error === error) console.error = originalError;
81
+ if (console.warn === warn) console.warn = originalWarn;
82
+ if (window.fetch === wrappedFetch) window.fetch = originalFetch;
83
+ window.removeEventListener("error", onError);
84
+ window.removeEventListener("unhandledrejection", onRejection);
85
+ resources?.disconnect();
86
+ lines.length = 0;
87
+ }
88
+ };
89
+ }
90
+ function safeUrl(value) {
91
+ try {
92
+ const url = new URL(value, location.href);
93
+ return `${url.origin}${url.pathname}`;
94
+ } catch {
95
+ return "[invalid URL]";
96
+ }
97
+ }
98
+ function sanitizeDiagnostic(value) {
99
+ return value.replace(/https?:\/\/[^\s<>"']+/g, (url) => safeUrl(url)).replace(/\bBearer\s+[^\s,;]+/gi, "Bearer [redacted]").replace(
100
+ /\b(token|password|secret|authorization|api[_-]?key)\s*[:=]\s*[^\s,;]+/gi,
101
+ "$1=[redacted]"
102
+ ).replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, "[redacted token]");
103
+ }
104
+ export {
105
+ collectBrowserDiagnostics,
106
+ safeUrl,
107
+ sanitizeDiagnostic
108
+ };
109
+ //# sourceMappingURL=browser-diagnostics.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/connection/remote-control/browser-diagnostics.ts"],
4
+ "sourcesContent": ["import { formatErrorReport } from '../../settings/error-report'\n\n/** Session-only browser evidence, collected only while the user is sharing. No bodies, headers or storage. */\nexport function collectBrowserDiagnostics() {\n const lines: string[] = []\n const add = (message: string) => {\n lines.push(`${new Date().toISOString()} ${sanitizeDiagnostic(message).slice(0, 1800)}`)\n if (lines.length > 30) lines.shift()\n }\n const stringify = (value: unknown): string => {\n if (value instanceof Error)\n return formatErrorReport({ error: value, url: safeUrl(location.href) })\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean')\n return String(value)\n return '[object omitted]'\n }\n const originalError = console.error\n const originalWarn = console.warn\n const error = (...args: unknown[]) => {\n add(`error: ${args.map(stringify).join(' ')}`)\n originalError.apply(console, args)\n }\n const warn = (...args: unknown[]) => {\n add(`warning: ${args.map(stringify).join(' ')}`)\n originalWarn.apply(console, args)\n }\n console.error = error\n console.warn = warn\n const onError = (event: ErrorEvent) => add(`uncaught: ${stringify(event.error ?? event.message)}`)\n const onRejection = (event: PromiseRejectionEvent) =>\n add(`unhandled rejection: ${stringify(event.reason)}`)\n window.addEventListener('error', onError)\n window.addEventListener('unhandledrejection', onRejection)\n const originalFetch = window.fetch\n const wrappedFetch: typeof fetch = async (...args) => {\n const input = args[0]\n const url = safeUrl(\n typeof input === 'string' ? input : input instanceof URL ? input.href : input.url\n )\n try {\n const response = await originalFetch.apply(window, args)\n if (!response.ok) add(`HTTP ${response.status}: ${url}`)\n return response\n } catch (failure) {\n add(`request failed: ${url}`)\n throw failure\n }\n }\n window.fetch = wrappedFetch\n // Resource timing also catches failures made through fetch references captured before sharing.\n let resources: PerformanceObserver | undefined\n if (typeof PerformanceObserver !== 'undefined') {\n try {\n resources = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n const resource = entry as PerformanceResourceTiming\n if ('responseStatus' in resource && Number(resource.responseStatus) >= 400)\n add(`HTTP ${resource.responseStatus}: ${safeUrl(resource.name)}`)\n }\n })\n resources.observe({ type: 'resource' })\n } catch {\n resources = undefined\n }\n }\n return {\n snapshot: () =>\n [\n `ARBI tab: ${safeUrl(location.href)}`,\n `Viewport: ${innerWidth} x ${innerHeight}; DPR ${devicePixelRatio}`,\n 'Visible ARBI controls (selectors for control_ui):',\n ...Array.from(\n document.querySelectorAll<HTMLElement>(\n 'button[data-testid],a[data-testid],input[data-testid]:not([type=\"password\"]),select[data-testid],[role=\"button\"][data-testid]'\n )\n )\n .filter(\n (el) =>\n el.getBoundingClientRect().width > 0 && !el.closest('[data-remote-control-surface]')\n )\n .slice(0, 60)\n .map(\n (el) =>\n `[data-testid=${JSON.stringify(el.dataset.testid)}] ${sanitizeDiagnostic(el.getAttribute('aria-label') ?? el.getAttribute('title') ?? el.textContent ?? '').slice(0, 80)}`\n ),\n ...lines,\n ]\n .join('\\n')\n .slice(-18_000),\n stop: () => {\n if (console.error === error) console.error = originalError\n if (console.warn === warn) console.warn = originalWarn\n if (window.fetch === wrappedFetch) window.fetch = originalFetch\n window.removeEventListener('error', onError)\n window.removeEventListener('unhandledrejection', onRejection)\n resources?.disconnect()\n lines.length = 0\n },\n }\n}\n\nexport function safeUrl(value: string): string {\n try {\n const url = new URL(value, location.href)\n return `${url.origin}${url.pathname}`\n } catch {\n return '[invalid URL]'\n }\n}\nexport function sanitizeDiagnostic(value: string): string {\n return value\n .replace(/https?:\\/\\/[^\\s<>\"']+/g, (url) => safeUrl(url))\n .replace(/\\bBearer\\s+[^\\s,;]+/gi, 'Bearer [redacted]')\n .replace(\n /\\b(token|password|secret|authorization|api[_-]?key)\\s*[:=]\\s*[^\\s,;]+/gi,\n '$1=[redacted]'\n )\n .replace(/\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\b/g, '[redacted token]')\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -21,8 +21,16 @@ export interface RemoteControl {
21
21
  /** Turn remote control ON directly — the user's own explicit choice. Runs nothing by itself. */
22
22
  enable(): void;
23
23
  }
24
+ export interface ScreenTransport {
25
+ reply(requestId: string, result: {
26
+ status: 'captured' | 'denied' | 'stopped' | 'unavailable' | 'action_finished';
27
+ screenshot_png?: string;
28
+ diagnostics?: string;
29
+ }): Promise<void>;
30
+ diagnose?(): Promise<string>;
31
+ }
24
32
  /** Remote control on a session's state, running the site's guided runs. */
25
- export declare function createRemoteControl(session: RemoteControlSession, guided: Pick<GuidedRuns, 'isSequenceId' | 'run' | 'remoteControl'>): RemoteControl;
33
+ export declare function createRemoteControl(session: RemoteControlSession, guided: Pick<GuidedRuns, 'isSequenceId' | 'run' | 'remoteControl'>, screenTransport?: ScreenTransport): RemoteControl;
26
34
  /** Remote control on the host's session and guided runs. */
27
35
  export declare function useRemoteControlActions(): RemoteControl;
28
36
  /**