@arbidocs/blocks 0.5.122 → 0.5.124

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 (124) hide show
  1. package/dist/{ClippingMediaViewer-IEIBT74X.cjs → ClippingMediaViewer-ZFEX5A3U.cjs} +11 -10
  2. package/dist/ClippingMediaViewer-ZFEX5A3U.cjs.map +1 -0
  3. package/dist/{ClippingPdfViewer-OGYLMVCE.cjs → ClippingPdfViewer-3LRTEPHV.cjs} +45 -11
  4. package/dist/ClippingPdfViewer-3LRTEPHV.cjs.map +1 -0
  5. package/dist/{ConnectedPresentation-AU25OW25.cjs → ConnectedPresentation-OV2KBOLK.cjs} +27 -153
  6. package/dist/ConnectedPresentation-OV2KBOLK.cjs.map +1 -0
  7. package/dist/{ConnectedSlidesTab-I75LKKN7.cjs → ConnectedSlidesTab-LUAGZ6FD.cjs} +60 -36
  8. package/dist/ConnectedSlidesTab-LUAGZ6FD.cjs.map +1 -0
  9. package/dist/{FilesViewerDialog-4352KIOG.cjs → FilesViewerDialog-JWKNJLWG.cjs} +10 -7
  10. package/dist/FilesViewerDialog-JWKNJLWG.cjs.map +1 -0
  11. package/dist/SpotlightStrip-ED22UYTM.cjs +398 -0
  12. package/dist/SpotlightStrip-ED22UYTM.cjs.map +1 -0
  13. package/dist/{chunk-LHWOKOMP.cjs → chunk-3IH55CDE.cjs} +4 -4
  14. package/dist/chunk-3IH55CDE.cjs.map +1 -0
  15. package/dist/chunk-3ZZZO4F7.cjs +145 -0
  16. package/dist/chunk-3ZZZO4F7.cjs.map +1 -0
  17. package/dist/{chunk-WFMWKPGY.cjs → chunk-ADPRIBSH.cjs} +9 -33
  18. package/dist/chunk-ADPRIBSH.cjs.map +1 -0
  19. package/dist/{chunk-UTJ223TI.cjs → chunk-BX6G2ZCI.cjs} +138 -146
  20. package/dist/chunk-BX6G2ZCI.cjs.map +1 -0
  21. package/dist/{chunk-WRLGLZIA.cjs → chunk-EMRY62HS.cjs} +2 -2
  22. package/dist/{chunk-WRLGLZIA.cjs.map → chunk-EMRY62HS.cjs.map} +1 -1
  23. package/dist/{chunk-USNTRL22.cjs → chunk-H236SNEV.cjs} +199 -1616
  24. package/dist/chunk-H236SNEV.cjs.map +1 -0
  25. package/dist/chunk-JITYC4OS.cjs +66 -0
  26. package/dist/chunk-JITYC4OS.cjs.map +1 -0
  27. package/dist/chunk-JY7UUZQG.cjs +1513 -0
  28. package/dist/chunk-JY7UUZQG.cjs.map +1 -0
  29. package/dist/chunk-MVXHTAJ4.cjs +111 -0
  30. package/dist/chunk-MVXHTAJ4.cjs.map +1 -0
  31. package/dist/{chunk-4TTX6ZNS.cjs → chunk-ORNLCBNE.cjs} +24 -2
  32. package/dist/chunk-ORNLCBNE.cjs.map +1 -0
  33. package/dist/chunk-WVZLAAIZ.cjs +31 -0
  34. package/dist/chunk-WVZLAAIZ.cjs.map +1 -0
  35. package/dist/chunk-ZZZITHGK.cjs +119 -0
  36. package/dist/chunk-ZZZITHGK.cjs.map +1 -0
  37. package/dist/connect/index.cjs +52 -52
  38. package/dist/connect/viewer.cjs +8 -5
  39. package/dist/connection/presentation/ClippingMediaViewer.js +3 -3
  40. package/dist/connection/presentation/ClippingMediaViewer.js.map +2 -2
  41. package/dist/connection/presentation/ClippingPdfViewer.d.ts +4 -3
  42. package/dist/connection/presentation/ClippingPdfViewer.js +48 -9
  43. package/dist/connection/presentation/ClippingPdfViewer.js.map +2 -2
  44. package/dist/connection/presentation/ConnectedSlidesTab.js +35 -13
  45. package/dist/connection/presentation/ConnectedSlidesTab.js.map +2 -2
  46. package/dist/connection/presentation/SpotlightLayer.d.ts +5 -0
  47. package/dist/connection/presentation/SpotlightLayer.js +19 -0
  48. package/dist/connection/presentation/SpotlightLayer.js.map +7 -0
  49. package/dist/connection/presentation/SpotlightStrip.d.ts +10 -0
  50. package/dist/connection/presentation/SpotlightStrip.js +237 -0
  51. package/dist/connection/presentation/SpotlightStrip.js.map +7 -0
  52. package/dist/connection/presentation/useViewerClipping.d.ts +4 -3
  53. package/dist/connection/presentation/useViewerClipping.js +8 -4
  54. package/dist/connection/presentation/useViewerClipping.js.map +2 -2
  55. package/dist/connection/viewer/ConnectedDocumentViewer.js +52 -61
  56. package/dist/connection/viewer/ConnectedDocumentViewer.js.map +2 -2
  57. package/dist/connection/viewer/ViewerMoreMenu.d.ts +18 -0
  58. package/dist/connection/viewer/ViewerMoreMenu.js +45 -0
  59. package/dist/connection/viewer/ViewerMoreMenu.js.map +7 -0
  60. package/dist/connection/viewer/pdf/PdfPassageHighlights.js +3 -2
  61. package/dist/connection/viewer/pdf/PdfPassageHighlights.js.map +2 -2
  62. package/dist/connection/viewer/pdf/passageHighlights.d.ts +5 -0
  63. package/dist/connection/viewer/pdf/passageHighlights.js +14 -1
  64. package/dist/connection/viewer/pdf/passageHighlights.js.map +2 -2
  65. package/dist/connection/viewer/useCleanView.d.ts +3 -1
  66. package/dist/connection/viewer/useCleanView.js +6 -3
  67. package/dist/connection/viewer/useCleanView.js.map +2 -2
  68. package/dist/document-tools/DocumentToolButtons.js +3 -3
  69. package/dist/document-tools/DocumentToolButtons.js.map +2 -2
  70. package/dist/document-tools/index.cjs +13 -13
  71. package/dist/index.cjs +53 -53
  72. package/dist/presentation/BoxActionBar.d.ts +2 -2
  73. package/dist/presentation/BoxActionBar.js +62 -30
  74. package/dist/presentation/BoxActionBar.js.map +2 -2
  75. package/dist/presentation/ClipActionBar.js +60 -7
  76. package/dist/presentation/ClipActionBar.js.map +2 -2
  77. package/dist/presentation/ClipCaptureOverlay.d.ts +7 -3
  78. package/dist/presentation/ClipCaptureOverlay.js +43 -14
  79. package/dist/presentation/ClipCaptureOverlay.js.map +2 -2
  80. package/dist/presentation/ClipViewControls.d.ts +3 -3
  81. package/dist/presentation/ClipViewControls.js +59 -64
  82. package/dist/presentation/ClipViewControls.js.map +2 -2
  83. package/dist/presentation/PresentControls.d.ts +8 -0
  84. package/dist/presentation/PresentControls.js +1 -0
  85. package/dist/presentation/PresentControls.js.map +2 -2
  86. package/dist/presentation/PresentationStage.d.ts +10 -2
  87. package/dist/presentation/PresentationStage.js +6 -3
  88. package/dist/presentation/PresentationStage.js.map +2 -2
  89. package/dist/presentation/PresenterView.js +1 -22
  90. package/dist/presentation/PresenterView.js.map +2 -2
  91. package/dist/presentation/SlidesEditor.js +56 -42
  92. package/dist/presentation/SlidesEditor.js.map +2 -2
  93. package/dist/presentation/SpotlightView.d.ts +19 -0
  94. package/dist/presentation/SpotlightView.js +180 -0
  95. package/dist/presentation/SpotlightView.js.map +7 -0
  96. package/dist/presentation/StageItem.js +0 -5
  97. package/dist/presentation/StageItem.js.map +2 -2
  98. package/dist/presentation/passage-clip.d.ts +17 -0
  99. package/dist/presentation/passage-clip.js +25 -0
  100. package/dist/presentation/passage-clip.js.map +7 -0
  101. package/dist/presentation/spotlight.d.ts +58 -0
  102. package/dist/presentation/spotlight.js +110 -0
  103. package/dist/presentation/spotlight.js.map +7 -0
  104. package/dist/presentation/store.d.ts +5 -0
  105. package/dist/presentation/store.js +19 -0
  106. package/dist/presentation/store.js.map +2 -2
  107. package/dist/presentation/usePointerActive.d.ts +2 -0
  108. package/dist/presentation/usePointerActive.js +28 -0
  109. package/dist/presentation/usePointerActive.js.map +7 -0
  110. package/dist/styles.css +1 -1
  111. package/node_modules/@arbidocs/coedit/package.json +1 -1
  112. package/package.json +5 -5
  113. package/dist/ClippingMediaViewer-IEIBT74X.cjs.map +0 -1
  114. package/dist/ClippingPdfViewer-OGYLMVCE.cjs.map +0 -1
  115. package/dist/ConnectedPresentation-AU25OW25.cjs.map +0 -1
  116. package/dist/ConnectedSlidesTab-I75LKKN7.cjs.map +0 -1
  117. package/dist/FilesViewerDialog-4352KIOG.cjs.map +0 -1
  118. package/dist/chunk-4TTX6ZNS.cjs.map +0 -1
  119. package/dist/chunk-LHWOKOMP.cjs.map +0 -1
  120. package/dist/chunk-USNTRL22.cjs.map +0 -1
  121. package/dist/chunk-UTJ223TI.cjs.map +0 -1
  122. package/dist/chunk-WFMWKPGY.cjs.map +0 -1
  123. package/dist/chunk-XK4TVJKD.cjs +0 -73
  124. package/dist/chunk-XK4TVJKD.cjs.map +0 -1
@@ -0,0 +1,110 @@
1
+ "use client";
2
+ import { create } from "zustand";
3
+ import { releasePdfRegionSource } from "@arbidocs/react/viewers";
4
+ import { autoLayout, defaultCaption } from "./model.js";
5
+ import { reserveOf, usePresentation } from "./store.js";
6
+ const SPOTLIGHT_PER_SLIDE = 4;
7
+ function spotlightSteps(clips) {
8
+ const steps = [];
9
+ for (let i = 0; i < clips.length; i += SPOTLIGHT_PER_SLIDE) {
10
+ const group = clips.slice(i, i + SPOTLIGHT_PER_SLIDE).map((clip) => clip.id);
11
+ steps.push(group);
12
+ if (group.length > 1) steps.push(...group.map((id) => [id]));
13
+ }
14
+ return steps;
15
+ }
16
+ function spotlightSlide(clips, ids, note, look) {
17
+ const shown = ids.map((id) => clips.find((clip) => clip.id === id)).filter((clip) => !!clip);
18
+ const base = { caption: note.trim(), layout: "clips" };
19
+ const frames = autoLayout(
20
+ shown.map((clip) => clip.aspect),
21
+ reserveOf(base, look)
22
+ );
23
+ return {
24
+ ...base,
25
+ id: "spotlight",
26
+ auto: true,
27
+ points: "",
28
+ boxes: [],
29
+ items: shown.map((clip, i) => ({ clipId: clip.id, frame: frames[i] }))
30
+ };
31
+ }
32
+ let sequence = 0;
33
+ function release(gone, kept) {
34
+ const inUse = new Set([...kept, ...usePresentation.getState().clips].map((clip) => clip.source));
35
+ for (const source of new Set(gone.map((clip) => clip.source))) {
36
+ if (!inUse.has(source)) releasePdfRegionSource(source);
37
+ }
38
+ }
39
+ const EMPTY = {
40
+ clips: [],
41
+ note: "",
42
+ showing: false,
43
+ step: 0
44
+ };
45
+ const useSpotlight = create()((set, get) => ({
46
+ ...EMPTY,
47
+ owner: null,
48
+ clipping: false,
49
+ gather(clip) {
50
+ const id = `spot-${Date.now().toString(36)}-${++sequence}`;
51
+ const full = {
52
+ ...clip,
53
+ id,
54
+ caption: clip.caption ?? defaultCaption(clip.fileName, clip.page),
55
+ zoomBase: clip.zoomBase ?? clip.rect.w
56
+ };
57
+ set((state) => ({ clips: [...state.clips, full] }));
58
+ return id;
59
+ },
60
+ remove(clipId) {
61
+ const { clips, step } = get();
62
+ const gone = clips.filter((clip) => clip.id === clipId);
63
+ if (!gone.length) return;
64
+ const rest = clips.filter((clip) => clip.id !== clipId);
65
+ const steps = spotlightSteps(rest).length;
66
+ set({
67
+ clips: rest,
68
+ step: Math.min(step, Math.max(0, steps - 1)),
69
+ ...rest.length ? {} : { showing: false, note: "" }
70
+ });
71
+ release(gone, rest);
72
+ },
73
+ setNote: (note) => set({ note }),
74
+ show: () => set((state) => state.clips.length ? { showing: true, step: 0 } : state),
75
+ hide: () => set({ showing: false }),
76
+ goTo: (step) => set(
77
+ (state) => step < 0 || step >= spotlightSteps(state.clips).length || step === state.step ? state : { step }
78
+ ),
79
+ keep() {
80
+ const { clips, step, note } = get();
81
+ const ids = spotlightSteps(clips)[step];
82
+ if (!ids) return;
83
+ usePresentation.getState().addSlideOf(
84
+ ids.map((id) => clips.find((clip) => clip.id === id)),
85
+ note.trim()
86
+ );
87
+ },
88
+ addToTray() {
89
+ usePresentation.getState().adoptClips(get().clips);
90
+ set({ ...EMPTY });
91
+ },
92
+ clear() {
93
+ const gone = get().clips;
94
+ set({ ...EMPTY });
95
+ release(gone, []);
96
+ },
97
+ own(userId) {
98
+ if (get().owner === userId) return;
99
+ get().clear();
100
+ set({ owner: userId, clipping: false });
101
+ },
102
+ setClipping: (clipping) => set({ clipping })
103
+ }));
104
+ export {
105
+ SPOTLIGHT_PER_SLIDE,
106
+ spotlightSlide,
107
+ spotlightSteps,
108
+ useSpotlight
109
+ };
110
+ //# sourceMappingURL=spotlight.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/presentation/spotlight.ts"],
4
+ "sourcesContent": ["/**\n * The spotlight: clips gathered in the document viewer (from one document or several) and shown,\n * on request, as a slide over the viewer itself \u2014 the same window, so a call sharing it keeps\n * sharing it. It is transient: nothing reaches the presentation unless \"Keep as slide\" (or \"Add to\n * the presentation\") puts it there.\n *\n * Kept in memory only, like the presentation (a clip holds its decrypted file), and cleared when\n * another user signs in (`owner`).\n */\n\nimport { create } from 'zustand'\nimport { releasePdfRegionSource } from '@arbidocs/react/viewers'\nimport { autoLayout, defaultCaption, type Clip, type Slide } from './model'\nimport { reserveOf, usePresentation, type NewClip } from './store'\n\n/** A spotlight slide holds this many clips at most; more are shown on further slides. */\nexport const SPOTLIGHT_PER_SLIDE = 4\n\ninterface SpotlightState {\n clips: Clip[]\n /** A line of text shown as the spotlight's title (empty: none). */\n note: string\n /** The spotlight is on screen. */\n showing: boolean\n /** Which of its steps (`spotlightSteps`) is shown. */\n step: number\n /** The user the clips were gathered by. */\n owner: string | null\n /**\n * The viewer's Clip mode is on: kept here, not in the viewer, so it stays on as the viewer opens\n * one document after another.\n */\n clipping: boolean\n}\n\ninterface SpotlightActions {\n gather(clip: NewClip): string\n remove(clipId: string): void\n setNote(note: string): void\n show(): void\n hide(): void\n goTo(step: number): void\n /** The step on screen, as a slide of the open presentation (after its current slide). */\n keep(): void\n /** The gathered clips, into the presentation's tray (not placed); the spotlight is emptied. */\n addToTray(): void\n clear(): void\n /** Gathering for this user: another's clips are dropped. */\n own(userId: string | null): void\n setClipping(on: boolean): void\n}\n\nexport type SpotlightStore = SpotlightState & SpotlightActions\n\n/**\n * What \u2190 \u2192 step through: the clips together (up to four a slide), and, where a slide shows more\n * than one, each of them alone after it. Each step lists clip ids.\n */\nexport function spotlightSteps(clips: readonly Pick<Clip, 'id'>[]): string[][] {\n const steps: string[][] = []\n for (let i = 0; i < clips.length; i += SPOTLIGHT_PER_SLIDE) {\n const group = clips.slice(i, i + SPOTLIGHT_PER_SLIDE).map((clip) => clip.id)\n steps.push(group)\n if (group.length > 1) steps.push(...group.map((id) => [id]))\n }\n return steps\n}\n\n/**\n * A step as a slide, arranged as the presentation arranges one (`autoLayout`: two tall clips go\n * side by side, two wide ones stack), with the note as its title and the presentation's look.\n */\nexport function spotlightSlide(\n clips: readonly Clip[],\n ids: readonly string[],\n note: string,\n look: Parameters<typeof reserveOf>[1]\n): Slide {\n const shown = ids\n .map((id) => clips.find((clip) => clip.id === id))\n .filter((clip): clip is Clip => !!clip)\n const base = { caption: note.trim(), layout: 'clips' as const }\n const frames = autoLayout(\n shown.map((clip) => clip.aspect),\n reserveOf(base, look)\n )\n return {\n ...base,\n id: 'spotlight',\n auto: true,\n points: '',\n boxes: [],\n items: shown.map((clip, i) => ({ clipId: clip.id, frame: frames[i] })),\n }\n}\n\nlet sequence = 0\n\n/** Its file goes from the PDF engine once neither the spotlight nor the presentation shows it. */\nfunction release(gone: readonly Clip[], kept: readonly Clip[]) {\n const inUse = new Set([...kept, ...usePresentation.getState().clips].map((clip) => clip.source))\n for (const source of new Set(gone.map((clip) => clip.source))) {\n if (!inUse.has(source)) releasePdfRegionSource(source)\n }\n}\n\nconst EMPTY: Omit<SpotlightState, 'owner' | 'clipping'> = {\n clips: [],\n note: '',\n showing: false,\n step: 0,\n}\n\nexport const useSpotlight = create<SpotlightStore>()((set, get) => ({\n ...EMPTY,\n owner: null,\n clipping: false,\n\n gather(clip) {\n const id = `spot-${Date.now().toString(36)}-${++sequence}`\n const full: Clip = {\n ...clip,\n id,\n caption: clip.caption ?? defaultCaption(clip.fileName, clip.page),\n zoomBase: clip.zoomBase ?? clip.rect.w,\n }\n set((state) => ({ clips: [...state.clips, full] }))\n return id\n },\n\n remove(clipId) {\n const { clips, step } = get()\n const gone = clips.filter((clip) => clip.id === clipId)\n if (!gone.length) return\n const rest = clips.filter((clip) => clip.id !== clipId)\n const steps = spotlightSteps(rest).length\n set({\n clips: rest,\n step: Math.min(step, Math.max(0, steps - 1)),\n ...(rest.length ? {} : { showing: false, note: '' }),\n })\n release(gone, rest)\n },\n\n setNote: (note) => set({ note }),\n show: () => set((state) => (state.clips.length ? { showing: true, step: 0 } : state)),\n hide: () => set({ showing: false }),\n goTo: (step) =>\n set((state) =>\n step < 0 || step >= spotlightSteps(state.clips).length || step === state.step\n ? state\n : { step }\n ),\n\n keep() {\n const { clips, step, note } = get()\n const ids = spotlightSteps(clips)[step]\n if (!ids) return\n usePresentation.getState().addSlideOf(\n ids.map((id) => clips.find((clip) => clip.id === id)!),\n note.trim()\n )\n },\n\n addToTray() {\n usePresentation.getState().adoptClips(get().clips)\n set({ ...EMPTY })\n },\n\n clear() {\n const gone = get().clips\n set({ ...EMPTY })\n release(gone, [])\n },\n\n own(userId) {\n if (get().owner === userId) return\n get().clear()\n set({ owner: userId, clipping: false })\n },\n\n setClipping: (clipping) => set({ clipping }),\n}))\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -154,6 +154,11 @@ interface PresentationActions {
154
154
  bind(boundTo: string | null): void;
155
155
  /** Clips gathered before (with no presentation open), added to the tray, not placed. */
156
156
  adoptClips(clips: readonly Clip[]): void;
157
+ /**
158
+ * A slide of these clips (added to the tray too), arranged, with `caption` as its title: after
159
+ * the current slide, or in its place while that is empty. It becomes the current slide.
160
+ */
161
+ addSlideOf(clips: readonly Clip[], caption?: string): void;
157
162
  /** Replace the presentation with one opened from a file (its clips' sources fetched). */
158
163
  load(opened: {
159
164
  title: string;
@@ -371,6 +371,25 @@ const usePresentation = create()((set, get) => {
371
371
  ...adopted.filter((clip) => !state.clips.some((entry) => entry.id === clip.id))
372
372
  ]
373
373
  })),
374
+ addSlideOf: (added, caption = "") => set((state) => {
375
+ const fresh = added.map((clip) => ({ ...clip, id: nextId("clip") }));
376
+ const clips = [...state.clips, ...fresh];
377
+ const slide = arranged(
378
+ {
379
+ ...emptySlide("clips"),
380
+ caption,
381
+ items: fresh.map((clip) => ({ clipId: clip.id, frame: fitFrame(clip.aspect) }))
382
+ },
383
+ clips,
384
+ state
385
+ );
386
+ const here = state.slides[state.current];
387
+ const empty = !!here && !here.items.length && !here.boxes.length && !here.caption.trim() && !here.points.trim();
388
+ const at = empty ? state.current : state.current + 1;
389
+ const slides = state.slides.slice();
390
+ slides.splice(at, empty ? 1 : 0, slide);
391
+ return { clips, slides, current: at };
392
+ }),
374
393
  load(opened) {
375
394
  get().reset();
376
395
  const clips = opened.clips.map((clip) => ({ ...clip, id: nextId("clip") }));
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/presentation/store.ts"],
4
- "sourcesContent": ["/**\n * The presentation of this session: the clips taken, the slides they are arranged on, the slide on\n * the stage, whether the stage is blanked and whether the presentation window presents (shows only\n * the stage, for sharing) or edits. Clips taken in the app's viewer land in it at once.\n *\n * Kept in memory only: a clip holds the decrypted PDF it is drawn from, so nothing is written to\n * storage, and everything is cleared when the session signs out (`ConnectedPresentation`).\n */\n\nimport { create } from 'zustand'\nimport { releasePdfRegionSource } from '@arbidocs/react/viewers'\nimport {\n alignFrame,\n autoLayout,\n defaultCaption,\n dropFrame,\n fitFrame,\n fullHeightFrame,\n fullWidthFrame,\n moveFrame,\n type Alignment,\n type Clip,\n type Rect,\n type Reserve,\n type Frame,\n type Slide,\n type SlideLayout,\n} from './model'\nimport { slideGeometry } from './slide-layout'\nimport { DEFAULT_THEME, type Theme } from './theme'\nimport { serializePresentation } from './persistence'\nimport { nextBoxId, withText, type SlideBackground, type SlideBox } from './boxes'\nimport type { SavedBox } from './saved-boxes'\n\n/** The smallest a clip's area can be zoomed to (a fraction of its source, either side). */\nconst MIN_CLIP_AREA = 0.02\n\n/** A slide holds a handful of clips: more would be too small to read on a shared screen. */\nexport const MAX_ITEMS_PER_SLIDE = 6\n/** A new clip goes on the slide by itself while the slide has fewer than this. */\nconst AUTO_PLACE_BELOW = 2\n\nexport type NewClip = Omit<Clip, 'id' | 'caption'> & { caption?: string }\nexport type FrameCommand = 'fullWidth' | 'fullHeight' | 'fit'\n\ninterface PresentationState {\n clips: Clip[]\n slides: Slide[]\n /** Index of the slide on the stage. */\n current: number\n /** The stage shows nothing (black) while true. */\n blank: boolean\n captions: boolean\n /** One look for every slide: background, typeface, accent, logo. Saved. */\n theme: Theme\n /** The theme's logo image (this session only; fetched by the host from `theme.logo`). */\n logoImage: Blob | null\n /**\n * The annotation column (a tag's id) whose value for each clip's document leads its label: an\n * Exhibit column's \"R-100\". Saved with the presentation.\n */\n labelColumn: string | null\n /** Each clip document's value in `labelColumn`, kept current by the host (not saved). */\n columnValues: Readonly<Record<string, string>>\n /** Present mode: the presentation window shows only the stage (what is shared). */\n presenting: boolean\n /** The presentation's name (its file's, once saved). */\n title: string\n /** The document it is saved as, once saved: saving again adds a version to it. */\n savedId: string | null\n /** Which version of that document this is (as opened or saved), when known. */\n version: number | null\n /**\n * The document (or draft) this presentation is edited as, in the viewer: its slides tab shows\n * it. Null: clips gathered with no presentation open yet (a new one takes them).\n */\n boundTo: string | null\n /** The presentation as last opened or saved (serialised), to tell unsaved changes; null: never. */\n clean: string | null\n /**\n * A message for the presentation window itself (saved, failed\u2026): the app's notifications show in\n * the app's window, out of sight behind a popped-out presentation.\n */\n notice: { text: string; kind: 'success' | 'error'; id: number } | null\n /** The clip being dragged from the tray, while it is (the drop targets light up). */\n dragging: string | null\n /** Images embedded in the presentation (an imported deck's pictures), as data URLs, by key. */\n media: Readonly<Record<string, string>>\n}\n\ninterface PresentationActions {\n /** Add a clip; it is placed on the current slide while that has fewer than two. */\n addClip(clip: NewClip): string\n removeClip(clipId: string): void\n setCaption(clipId: string, caption: string): void\n /** Put a clip on the current slide (at a point, when dropped there). */\n place(clipId: string, at?: { x: number; y: number }): void\n unplace(clipId: string): void\n /** Move or resize a clip by hand: the slide stops arranging itself. */\n setFrame(clipId: string, frame: Frame): void\n applyFrame(clipId: string, command: FrameCommand): void\n /** Overlapping clips: this one on top of the others (later items are drawn over earlier ones). */\n bringToFront(clipId: string): void\n /** Overlapping clips: this one under the others. */\n sendToBack(clipId: string): void\n /**\n * Scroll what a clip shows over its page, image or video by `dx, dy` (fractions of the source),\n * its size kept and kept within the source: e.g. to show the lines above or below a passage.\n */\n panClip(clipId: string, dx: number, dy: number): void\n /**\n * Zoom what a clip shows by `factor` (2: twice as close) about its centre: its area over the\n * source shrinks or grows, its shape kept, never past the source's edges.\n */\n zoomClip(clipId: string, factor: number): void\n /** Zoom a clip to `percent` of how close it was cut (100: as cut), about its centre. */\n setClipZoom(clipId: string, percent: number): void\n /** Align a clip on the stage (inside the margins and the slide's reserved room). */\n align(clipId: string, alignment: Alignment): void\n /** Nudge a clip by `dx, dy` (stage fractions), kept on the stage, not snapped. */\n nudge(clipId: string, dx: number, dy: number): void\n /** The current slide's caption (shown across its top; empty for none). */\n /** A slide's title (default the current slide's). */\n setSlideCaption(caption: string, index?: number): void\n /** The current slide's layout: it is arranged for it again. */\n setSlideLayout(layout: SlideLayout, index?: number): void\n /** The current slide's text (its points, or subtitle). */\n setSlidePoints(points: string, index?: number): void\n /** Change the theme; slides arranging themselves make room for a logo (or give it back). */\n setTheme(change: Partial<Theme>): void\n setLogoImage(image: Blob | null): void\n /** Arrange the current slide automatically again. */\n arrange(): void\n /** A new slide after the current one (by default with the current slide's layout). */\n addSlide(layout?: SlideLayout, after?: number): void\n /** Remove a slide (default the current one); the last one left is emptied instead. */\n removeSlide(index?: number): void\n /** A copy of a slide (its title, points and clips), right after it. */\n duplicateSlide(index: number): void\n /** Move a slide to `to` (the others shift up or down). */\n moveSlide(index: number, to: number): void\n goTo(index: number): void\n setBlank(blank: boolean): void\n setCaptions(captions: boolean): void\n setLabelColumn(tagId: string | null): void\n setColumnValues(values: Readonly<Record<string, string>>): void\n setPresenting(presenting: boolean): void\n setDragging(clipId: string | null): void\n setTitle(title: string): void\n notify(text: string, kind: 'success' | 'error'): void\n clearNotice(id: number): void\n /** Show another part of an image or video clip's source (a new crop): its area and shape. */\n cropClip(clipId: string, rect: Rect, aspect: number): void\n /**\n * A clip reshaped on the current slide (`reshapeFrame`): it shows `rect` of its source, in\n * `frame`, at the same scale and zoom. Slides arranging themselves arrange around its new shape.\n */\n reshapeClip(clipId: string, frame: Frame, rect: Rect, aspect: number): void\n /** A text box or shape on a slide (default the current one), on top; resolves with its id. */\n addBox(box: Omit<SlideBox, 'id'>, index?: number): string\n /** Change a box (wherever it is). */\n updateBox(boxId: string, change: (box: SlideBox) => SlideBox): void\n /** Type a box's text (one paragraph per line, each keeping its style). */\n setBoxText(boxId: string, text: string): void\n removeBox(boxId: string): void\n /** A box over the slide's clips and other boxes, or under them all. */\n restackBox(boxId: string, to: 'front' | 'back'): void\n /** A slide's speaker notes (default the current slide's). */\n setSlideNotes(notes: string, index?: number): void\n /** A slide's own background (none: the theme's). */\n setSlideBackground(background: SlideBackground | undefined, index?: number): void\n /** Saved as this document (under this title), as its `version`. */\n setSaved(savedId: string, title: string, version?: number | null): void\n /** Edited as this document or draft (see `boundTo`). */\n bind(boundTo: string | null): void\n /** Clips gathered before (with no presentation open), added to the tray, not placed. */\n adoptClips(clips: readonly Clip[]): void\n /** Replace the presentation with one opened from a file (its clips' sources fetched). */\n load(opened: {\n title: string\n /** The document it is saved as; null for a new one (from a template). */\n savedId: string | null\n /** Its version, when known. */\n version?: number | null\n /** Edited as (default `savedId`): an older file opened unsaved is still its own. */\n boundTo?: string | null\n captions: boolean\n labelColumn?: string | null\n theme?: Theme\n clips: Omit<Clip, 'id'>[]\n slides: {\n caption: string\n auto: boolean\n layout?: SlideLayout\n points?: string\n items: { clip: number; frame: Frame }[]\n boxes?: readonly SavedBox[]\n background?: SlideBackground\n notes?: string\n }[]\n media?: Readonly<Record<string, string>>\n }): void\n reset(): void\n}\n\nexport type PresentationStore = PresentationState & PresentationActions\n\nlet sequence = 0\nconst nextId = (kind: string) => `${kind}-${Date.now().toString(36)}-${++sequence}`\n\nconst emptySlide = (layout: SlideLayout = 'clips'): Slide => ({\n id: nextId('slide'),\n items: [],\n auto: true,\n caption: '',\n layout,\n points: '',\n boxes: [],\n})\n\nconst initialState = (): PresentationState => ({\n clips: [],\n slides: [emptySlide()],\n current: 0,\n blank: false,\n captions: true,\n theme: DEFAULT_THEME,\n logoImage: null,\n labelColumn: null,\n columnValues: {},\n presenting: false,\n dragging: null,\n notice: null,\n title: 'Untitled presentation',\n savedId: null,\n version: null,\n boundTo: null,\n clean: null,\n media: {},\n})\n\n/** What arranging a slide depends on besides its clips: labels shown, a logo in the footer. */\ntype Look = Pick<PresentationState, 'captions' | 'theme'>\n\n/** The slide arranged again from its clips' aspects, in the room its layout leaves them. */\nfunction arranged(slide: Slide, clips: readonly Clip[], look: Look): Slide {\n const aspects = slide.items.map((item) => aspectOf(clips, item.clipId))\n const frames = autoLayout(aspects, reserveOf(slide, look))\n return {\n ...slide,\n auto: true,\n items: slide.items.map((item, i) => ({ ...item, frame: frames[i] })),\n }\n}\n\n/**\n * The room a slide keeps free of clips: its title and text (by its layout), each clip's label\n * under it, the footer while a logo shows.\n */\nexport const reserveOf = (slide: Pick<Slide, 'caption' | 'layout'>, look: Look): Reserve =>\n slideGeometry(slide, { labels: look.captions, logo: look.theme.logo?.corner ?? null }).reserve\n\nfunction aspectOf(clips: readonly Clip[], clipId: string): number {\n return clips.find((clip) => clip.id === clipId)?.aspect ?? 1\n}\n\nexport const usePresentation = create<PresentationStore>()((set, get) => {\n /** Change the current slide; an auto slide is arranged again after the change. */\n const updateSlide = (change: (slide: Slide, clips: readonly Clip[]) => Slide, at?: number) =>\n set((state) => {\n const index = at ?? state.current\n const slide = state.slides[index]\n if (!slide) return state\n const next = change(slide, state.clips)\n if (next === slide) return state\n const slides = state.slides.slice()\n slides[index] = next.auto ? arranged(next, state.clips, state) : next\n return { slides }\n })\n\n /** Change the boxes of the slide holding `boxId` (`at`: the box's index there). */\n const updateBoxes = (\n boxId: string,\n change: (boxes: readonly SlideBox[], at: number) => SlideBox[]\n ) =>\n set((state) => {\n const index = state.slides.findIndex((slide) => slide.boxes.some((box) => box.id === boxId))\n if (index < 0) return state\n const slide = state.slides[index]\n const slides = state.slides.slice()\n slides[index] = {\n ...slide,\n boxes: change(\n slide.boxes,\n slide.boxes.findIndex((box) => box.id === boxId)\n ),\n }\n return { slides }\n })\n\n /** Restack a clip on the current slide: only the drawing order changes, never a position. */\n const restack = (clipId: string, to: 'front' | 'back') =>\n set((state) => {\n const slide = state.slides[state.current]\n const item = slide?.items.find((entry) => entry.clipId === clipId)\n if (!slide || !item) return state\n const rest = slide.items.filter((entry) => entry !== item)\n const items = to === 'front' ? [...rest, item] : [item, ...rest]\n if (items.every((entry, i) => entry === slide.items[i])) return state\n const slides = state.slides.slice()\n slides[state.current] = { ...slide, items }\n return { slides }\n })\n\n return {\n ...initialState(),\n\n addClip(clip) {\n const id = nextId('clip')\n const full: Clip = {\n ...clip,\n id,\n caption: clip.caption ?? defaultCaption(clip.fileName, clip.page),\n zoomBase: clip.zoomBase ?? clip.rect.w,\n }\n set((state) => ({ clips: [...state.clips, full] }))\n const slide = get().slides[get().current]\n if (slide && slide.items.length < AUTO_PLACE_BELOW) get().place(id)\n return id\n },\n\n removeClip(clipId) {\n const { clips, slides } = get()\n const removed = clips.find((clip) => clip.id === clipId)\n if (!removed) return\n const rest = clips.filter((clip) => clip.id !== clipId)\n set({\n clips: rest,\n slides: slides.map((slide) => {\n if (!slide.items.some((item) => item.clipId === clipId)) return slide\n const next = { ...slide, items: slide.items.filter((item) => item.clipId !== clipId) }\n return next.auto ? arranged(next, rest, get()) : next\n }),\n })\n // The PDF engine's copy of the file goes once no clip is drawn from it.\n if (!rest.some((clip) => clip.source === removed.source))\n releasePdfRegionSource(removed.source)\n },\n\n setCaption: (clipId, caption) =>\n set((state) => ({\n clips: state.clips.map((clip) => (clip.id === clipId ? { ...clip, caption } : clip)),\n })),\n\n place: (clipId, at) =>\n updateSlide((slide, clips) => {\n if (!clips.some((clip) => clip.id === clipId)) return slide\n const others = slide.items.filter((item) => item.clipId !== clipId)\n if (!at && others.length < slide.items.length) return slide\n if (others.length >= MAX_ITEMS_PER_SLIDE) return slide\n const aspect = aspectOf(clips, clipId)\n // An auto slide arranges it with the rest. A slide arranged by hand stays so: the clip goes\n // where it was dropped, or in the middle.\n const frame = slide.auto ? fitFrame(aspect) : dropFrame(aspect, at ?? { x: 0.5, y: 0.5 })\n return { ...slide, items: [...others, { clipId, frame }] }\n }),\n\n unplace: (clipId) =>\n updateSlide((slide) =>\n slide.items.some((item) => item.clipId === clipId)\n ? { ...slide, items: slide.items.filter((item) => item.clipId !== clipId) }\n : slide\n ),\n\n setFrame: (clipId, frame) =>\n updateSlide((slide) => ({\n ...slide,\n auto: false,\n items: slide.items.map((item) => (item.clipId === clipId ? { ...item, frame } : item)),\n })),\n\n applyFrame(clipId, command) {\n const slide = get().slides[get().current]\n const item = slide?.items.find((entry) => entry.clipId === clipId)\n if (!item) return\n const aspect = aspectOf(get().clips, clipId)\n const reserve = reserveOf(slide, get())\n const frame =\n command === 'fullWidth'\n ? fullWidthFrame(item.frame, aspect, reserve)\n : command === 'fullHeight'\n ? fullHeightFrame(item.frame, aspect, reserve)\n : fitFrame(aspect, reserve)\n get().setFrame(clipId, frame)\n // Made the size of the stage, it goes on top of the others.\n get().bringToFront(clipId)\n },\n\n bringToFront: (clipId) => restack(clipId, 'front'),\n sendToBack: (clipId) => restack(clipId, 'back'),\n\n panClip: (clipId, dx, dy) =>\n set((state) => {\n const clip = state.clips.find((entry) => entry.id === clipId)\n if (!clip) return state\n const { rect } = clip\n const x = Math.min(Math.max(rect.x + dx, 0), 1 - rect.w)\n const y = Math.min(Math.max(rect.y + dy, 0), 1 - rect.h)\n if (x === rect.x && y === rect.y) return state\n return {\n clips: state.clips.map((entry) =>\n entry === clip ? { ...clip, rect: { ...rect, x, y } } : entry\n ),\n }\n }),\n\n zoomClip: (clipId, factor) =>\n set((state) => {\n const clip = state.clips.find((entry) => entry.id === clipId)\n if (!clip || !(factor > 0)) return state\n const { rect } = clip\n // Its area scales by 1/factor; never larger than the source, nor smaller than a sliver.\n const largest = Math.min(1 / rect.w, 1 / rect.h)\n const smallest = Math.max(MIN_CLIP_AREA / rect.w, MIN_CLIP_AREA / rect.h)\n const scale = Math.min(Math.max(1 / factor, smallest), largest)\n if (scale === 1) return state\n const w = rect.w * scale\n const h = rect.h * scale\n const x = Math.min(Math.max(rect.x + (rect.w - w) / 2, 0), 1 - w)\n const y = Math.min(Math.max(rect.y + (rect.h - h) / 2, 0), 1 - h)\n return {\n clips: state.clips.map((entry) =>\n entry === clip ? { ...clip, rect: { x, y, w, h } } : entry\n ),\n }\n }),\n\n setClipZoom(clipId, percent) {\n const clip = get().clips.find((entry) => entry.id === clipId)\n if (!clip || !(percent > 0)) return\n const width = (clip.zoomBase ?? clip.rect.w) / (percent / 100)\n get().zoomClip(clipId, clip.rect.w / width)\n },\n\n align(clipId, alignment) {\n const slide = get().slides[get().current]\n const item = slide?.items.find((entry) => entry.clipId === clipId)\n if (!slide || !item) return\n const aspect = aspectOf(get().clips, clipId)\n get().setFrame(clipId, alignFrame(item.frame, aspect, alignment, reserveOf(slide, get())))\n },\n\n nudge(clipId, dx, dy) {\n const slide = get().slides[get().current]\n const item = slide?.items.find((entry) => entry.clipId === clipId)\n if (!item) return\n const aspect = aspectOf(get().clips, clipId)\n get().setFrame(clipId, moveFrame(item.frame, aspect, dx, dy, [], true).frame)\n },\n\n // An auto slide makes (or gives back) the caption's room; one arranged by hand keeps its places.\n // A blank slide's title is its title box's text.\n setSlideCaption: (caption, index) =>\n updateSlide(\n (slide) => ({\n ...slide,\n caption,\n boxes: slide.boxes.map((box) => (box.role === 'title' ? withText(box, caption) : box)),\n }),\n index\n ),\n setSlideLayout: (layout, index) =>\n updateSlide((slide) => ({ ...slide, layout, auto: true }), index),\n setSlidePoints: (points, index) => updateSlide((slide) => ({ ...slide, points }), index),\n\n setTheme: (change) =>\n set((state) => {\n const theme = { ...state.theme, ...change }\n const logoMoved = (theme.logo?.corner ?? null) !== (state.theme.logo?.corner ?? null)\n return {\n theme,\n ...(theme.logo?.documentId !== state.theme.logo?.documentId ? { logoImage: null } : {}),\n slides: logoMoved\n ? state.slides.map((slide) =>\n slide.auto ? arranged(slide, state.clips, { ...state, theme }) : slide\n )\n : state.slides,\n }\n }),\n setLogoImage: (logoImage) => set({ logoImage }),\n\n arrange: () => updateSlide((slide) => ({ ...slide, auto: true })),\n\n addSlide: (layout, after) =>\n set((state) => {\n const at = (after ?? state.current) + 1\n const slides = state.slides.slice()\n const before = state.slides[at - 1]\n const slide = emptySlide(layout ?? before?.layout ?? 'clips')\n // The slide it follows keeps its look (an imported deck's background).\n slides.splice(\n at,\n 0,\n before?.background ? { ...slide, background: before.background } : slide\n )\n return { slides, current: at }\n }),\n\n removeSlide: (index) =>\n set((state) => {\n const target = index ?? state.current\n if (state.slides.length <= 1) return { slides: [emptySlide()], current: 0 }\n const slides = state.slides.filter((_, i) => i !== target)\n const current = state.current > target ? state.current - 1 : state.current\n return { slides, current: Math.min(current, slides.length - 1) }\n }),\n\n duplicateSlide: (index) =>\n set((state) => {\n const slide = state.slides[index]\n if (!slide) return state\n const slides = state.slides.slice()\n slides.splice(index + 1, 0, {\n ...slide,\n id: nextId('slide'),\n items: slide.items.map((item) => ({ ...item, frame: { ...item.frame } })),\n boxes: slide.boxes.map((box) => ({ ...box, id: nextBoxId() })),\n })\n return { slides, current: index + 1 }\n }),\n\n moveSlide: (index, to) =>\n set((state) => {\n if (to < 0 || to >= state.slides.length || index === to || !state.slides[index]) {\n return state\n }\n const slides = state.slides.slice()\n const [slide] = slides.splice(index, 1)\n slides.splice(to, 0, slide)\n return { slides, current: to }\n }),\n\n goTo: (index) =>\n set((state) =>\n index < 0 || index >= state.slides.length || index === state.current\n ? state\n : { current: index }\n ),\n\n setBlank: (blank) => set({ blank }),\n // Slides arranged automatically make (or give back) the captions' room.\n setCaptions: (captions) =>\n set((state) => ({\n captions,\n slides: state.slides.map((slide) =>\n slide.auto ? arranged(slide, state.clips, { ...state, captions }) : slide\n ),\n })),\n setLabelColumn: (labelColumn) => set({ labelColumn }),\n setColumnValues: (columnValues) => set({ columnValues }),\n setPresenting: (presenting) => set({ presenting }),\n setDragging: (dragging) => set({ dragging }),\n setTitle: (title) => set({ title }),\n notify: (text, kind) => set({ notice: { text, kind, id: ++sequence } }),\n clearNotice: (id) => set((state) => (state.notice?.id === id ? { notice: null } : state)),\n\n cropClip: (clipId, rect, aspect) =>\n set((state) => {\n const clips = state.clips.map((clip) =>\n clip.id === clipId ? { ...clip, rect, aspect } : clip\n )\n return {\n clips,\n // Its shape changed: slides arranged automatically arrange again around it.\n slides: state.slides.map((slide) =>\n slide.auto && slide.items.some((item) => item.clipId === clipId)\n ? arranged(slide, clips, state)\n : slide\n ),\n }\n }),\n reshapeClip: (clipId, frame, rect, aspect) =>\n set((state) => {\n const clip = state.clips.find((entry) => entry.id === clipId)\n if (!clip) return state\n // Its zoom reads against its width when cut: that scales with the width it now shows.\n const zoomBase = ((clip.zoomBase ?? clip.rect.w) * rect.w) / clip.rect.w\n const clips = state.clips.map((entry) =>\n entry === clip ? { ...clip, rect, aspect, zoomBase } : entry\n )\n return {\n clips,\n slides: state.slides.map((slide, index) => {\n if (index === state.current) {\n return {\n ...slide,\n auto: false,\n items: slide.items.map((item) =>\n item.clipId === clipId ? { ...item, frame } : item\n ),\n }\n }\n return slide.auto && slide.items.some((item) => item.clipId === clipId)\n ? arranged(slide, clips, state)\n : slide\n }),\n }\n }),\n addBox(box, index) {\n const id = nextBoxId()\n updateSlide((slide) => ({ ...slide, boxes: [...slide.boxes, { ...box, id }] }), index)\n return id\n },\n updateBox: (boxId, change) =>\n updateBoxes(boxId, (boxes, at) => {\n const next = boxes.slice()\n next[at] = change(boxes[at])\n return next\n }),\n setBoxText(boxId, text) {\n get().updateBox(boxId, (box) => withText(box, text))\n // A blank slide's title box is its title too (what an outline reads).\n set((state) => ({\n slides: state.slides.map((slide) =>\n slide.boxes.some((box) => box.id === boxId && box.role === 'title')\n ? { ...slide, caption: text.replace(/\\s*\\n\\s*/g, ' ').trim() }\n : slide\n ),\n }))\n },\n removeBox: (boxId) => updateBoxes(boxId, (boxes, at) => boxes.filter((_, i) => i !== at)),\n restackBox: (boxId, to) =>\n updateBoxes(boxId, (boxes, at) => {\n const box = { ...boxes[at], front: to === 'front' }\n const rest = boxes.filter((_, i) => i !== at)\n return to === 'front' ? [...rest, box] : [box, ...rest]\n }),\n setSlideNotes: (notes, index) => updateSlide((slide) => ({ ...slide, notes }), index),\n setSlideBackground: (background, index) =>\n updateSlide((slide) => {\n const { background: _old, ...rest } = slide\n void _old\n return background ? { ...rest, background } : rest\n }, index),\n setSaved: (savedId, title, version = null) => {\n set({ savedId, title, boundTo: savedId, version })\n set({ clean: snapshotOf(get()) })\n },\n bind: (boundTo) => set({ boundTo }),\n adoptClips: (adopted) =>\n set((state) => ({\n clips: [\n ...state.clips,\n ...adopted.filter((clip) => !state.clips.some((entry) => entry.id === clip.id)),\n ],\n })),\n\n load(opened) {\n get().reset()\n const clips: Clip[] = opened.clips.map((clip) => ({ ...clip, id: nextId('clip') }))\n const slides: Slide[] = opened.slides.map((slide) => ({\n id: nextId('slide'),\n caption: slide.caption,\n auto: slide.auto,\n layout: slide.layout ?? 'clips',\n points: slide.points ?? '',\n // Items whose clip could not be opened are left out.\n items: slide.items\n .filter((item) => clips[item.clip])\n .map((item) => ({ clipId: clips[item.clip].id, frame: item.frame })),\n boxes: (slide.boxes ?? []).map((box) => ({ ...box, id: nextBoxId() })),\n ...(slide.background ? { background: slide.background } : {}),\n ...(slide.notes ? { notes: slide.notes } : {}),\n }))\n set({\n title: opened.title,\n savedId: opened.savedId,\n version: opened.version ?? null,\n boundTo: opened.boundTo ?? opened.savedId,\n captions: opened.captions,\n labelColumn: opened.labelColumn ?? null,\n theme: opened.theme ?? DEFAULT_THEME,\n clips,\n slides: slides.length ? slides : [emptySlide()],\n current: 0,\n media: opened.media ?? {},\n })\n set({ clean: opened.savedId ? snapshotOf(get()) : null })\n },\n\n reset() {\n for (const source of new Set(get().clips.map((clip) => clip.source))) {\n releasePdfRegionSource(source)\n }\n set(initialState())\n },\n }\n})\n\n/** The slide on the stage. */\nexport const currentSlide = (state: PresentationState): Slide | undefined =>\n state.slides[state.current]\n\n/** What is saved of a presentation, as text (to compare). */\nfunction snapshotOf(state: PresentationState): string {\n // Embedded images change only with the clips that show them: left out, so comparing stays cheap.\n return JSON.stringify(serializePresentation({ ...state, media: undefined }))\n}\n\n/**\n * The presentation has changes not saved: since it was opened or saved; or, never saved, anything\n * in it at all (a clip, a title, a point).\n */\nexport function hasUnsavedChanges(state: PresentationState): boolean {\n if (state.clean !== null) return snapshotOf(state) !== state.clean\n return (\n state.clips.length > 0 ||\n state.slides.some(\n (slide) =>\n slide.items.length || slide.boxes.length || slide.caption.trim() || slide.points.trim()\n )\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * The presentation of this session: the clips taken, the slides they are arranged on, the slide on\n * the stage, whether the stage is blanked and whether the presentation window presents (shows only\n * the stage, for sharing) or edits. Clips taken in the app's viewer land in it at once.\n *\n * Kept in memory only: a clip holds the decrypted PDF it is drawn from, so nothing is written to\n * storage, and everything is cleared when the session signs out (`ConnectedPresentation`).\n */\n\nimport { create } from 'zustand'\nimport { releasePdfRegionSource } from '@arbidocs/react/viewers'\nimport {\n alignFrame,\n autoLayout,\n defaultCaption,\n dropFrame,\n fitFrame,\n fullHeightFrame,\n fullWidthFrame,\n moveFrame,\n type Alignment,\n type Clip,\n type Rect,\n type Reserve,\n type Frame,\n type Slide,\n type SlideLayout,\n} from './model'\nimport { slideGeometry } from './slide-layout'\nimport { DEFAULT_THEME, type Theme } from './theme'\nimport { serializePresentation } from './persistence'\nimport { nextBoxId, withText, type SlideBackground, type SlideBox } from './boxes'\nimport type { SavedBox } from './saved-boxes'\n\n/** The smallest a clip's area can be zoomed to (a fraction of its source, either side). */\nconst MIN_CLIP_AREA = 0.02\n\n/** A slide holds a handful of clips: more would be too small to read on a shared screen. */\nexport const MAX_ITEMS_PER_SLIDE = 6\n/** A new clip goes on the slide by itself while the slide has fewer than this. */\nconst AUTO_PLACE_BELOW = 2\n\nexport type NewClip = Omit<Clip, 'id' | 'caption'> & { caption?: string }\nexport type FrameCommand = 'fullWidth' | 'fullHeight' | 'fit'\n\ninterface PresentationState {\n clips: Clip[]\n slides: Slide[]\n /** Index of the slide on the stage. */\n current: number\n /** The stage shows nothing (black) while true. */\n blank: boolean\n captions: boolean\n /** One look for every slide: background, typeface, accent, logo. Saved. */\n theme: Theme\n /** The theme's logo image (this session only; fetched by the host from `theme.logo`). */\n logoImage: Blob | null\n /**\n * The annotation column (a tag's id) whose value for each clip's document leads its label: an\n * Exhibit column's \"R-100\". Saved with the presentation.\n */\n labelColumn: string | null\n /** Each clip document's value in `labelColumn`, kept current by the host (not saved). */\n columnValues: Readonly<Record<string, string>>\n /** Present mode: the presentation window shows only the stage (what is shared). */\n presenting: boolean\n /** The presentation's name (its file's, once saved). */\n title: string\n /** The document it is saved as, once saved: saving again adds a version to it. */\n savedId: string | null\n /** Which version of that document this is (as opened or saved), when known. */\n version: number | null\n /**\n * The document (or draft) this presentation is edited as, in the viewer: its slides tab shows\n * it. Null: clips gathered with no presentation open yet (a new one takes them).\n */\n boundTo: string | null\n /** The presentation as last opened or saved (serialised), to tell unsaved changes; null: never. */\n clean: string | null\n /**\n * A message for the presentation window itself (saved, failed\u2026): the app's notifications show in\n * the app's window, out of sight behind a popped-out presentation.\n */\n notice: { text: string; kind: 'success' | 'error'; id: number } | null\n /** The clip being dragged from the tray, while it is (the drop targets light up). */\n dragging: string | null\n /** Images embedded in the presentation (an imported deck's pictures), as data URLs, by key. */\n media: Readonly<Record<string, string>>\n}\n\ninterface PresentationActions {\n /** Add a clip; it is placed on the current slide while that has fewer than two. */\n addClip(clip: NewClip): string\n removeClip(clipId: string): void\n setCaption(clipId: string, caption: string): void\n /** Put a clip on the current slide (at a point, when dropped there). */\n place(clipId: string, at?: { x: number; y: number }): void\n unplace(clipId: string): void\n /** Move or resize a clip by hand: the slide stops arranging itself. */\n setFrame(clipId: string, frame: Frame): void\n applyFrame(clipId: string, command: FrameCommand): void\n /** Overlapping clips: this one on top of the others (later items are drawn over earlier ones). */\n bringToFront(clipId: string): void\n /** Overlapping clips: this one under the others. */\n sendToBack(clipId: string): void\n /**\n * Scroll what a clip shows over its page, image or video by `dx, dy` (fractions of the source),\n * its size kept and kept within the source: e.g. to show the lines above or below a passage.\n */\n panClip(clipId: string, dx: number, dy: number): void\n /**\n * Zoom what a clip shows by `factor` (2: twice as close) about its centre: its area over the\n * source shrinks or grows, its shape kept, never past the source's edges.\n */\n zoomClip(clipId: string, factor: number): void\n /** Zoom a clip to `percent` of how close it was cut (100: as cut), about its centre. */\n setClipZoom(clipId: string, percent: number): void\n /** Align a clip on the stage (inside the margins and the slide's reserved room). */\n align(clipId: string, alignment: Alignment): void\n /** Nudge a clip by `dx, dy` (stage fractions), kept on the stage, not snapped. */\n nudge(clipId: string, dx: number, dy: number): void\n /** The current slide's caption (shown across its top; empty for none). */\n /** A slide's title (default the current slide's). */\n setSlideCaption(caption: string, index?: number): void\n /** The current slide's layout: it is arranged for it again. */\n setSlideLayout(layout: SlideLayout, index?: number): void\n /** The current slide's text (its points, or subtitle). */\n setSlidePoints(points: string, index?: number): void\n /** Change the theme; slides arranging themselves make room for a logo (or give it back). */\n setTheme(change: Partial<Theme>): void\n setLogoImage(image: Blob | null): void\n /** Arrange the current slide automatically again. */\n arrange(): void\n /** A new slide after the current one (by default with the current slide's layout). */\n addSlide(layout?: SlideLayout, after?: number): void\n /** Remove a slide (default the current one); the last one left is emptied instead. */\n removeSlide(index?: number): void\n /** A copy of a slide (its title, points and clips), right after it. */\n duplicateSlide(index: number): void\n /** Move a slide to `to` (the others shift up or down). */\n moveSlide(index: number, to: number): void\n goTo(index: number): void\n setBlank(blank: boolean): void\n setCaptions(captions: boolean): void\n setLabelColumn(tagId: string | null): void\n setColumnValues(values: Readonly<Record<string, string>>): void\n setPresenting(presenting: boolean): void\n setDragging(clipId: string | null): void\n setTitle(title: string): void\n notify(text: string, kind: 'success' | 'error'): void\n clearNotice(id: number): void\n /** Show another part of an image or video clip's source (a new crop): its area and shape. */\n cropClip(clipId: string, rect: Rect, aspect: number): void\n /**\n * A clip reshaped on the current slide (`reshapeFrame`): it shows `rect` of its source, in\n * `frame`, at the same scale and zoom. Slides arranging themselves arrange around its new shape.\n */\n reshapeClip(clipId: string, frame: Frame, rect: Rect, aspect: number): void\n /** A text box or shape on a slide (default the current one), on top; resolves with its id. */\n addBox(box: Omit<SlideBox, 'id'>, index?: number): string\n /** Change a box (wherever it is). */\n updateBox(boxId: string, change: (box: SlideBox) => SlideBox): void\n /** Type a box's text (one paragraph per line, each keeping its style). */\n setBoxText(boxId: string, text: string): void\n removeBox(boxId: string): void\n /** A box over the slide's clips and other boxes, or under them all. */\n restackBox(boxId: string, to: 'front' | 'back'): void\n /** A slide's speaker notes (default the current slide's). */\n setSlideNotes(notes: string, index?: number): void\n /** A slide's own background (none: the theme's). */\n setSlideBackground(background: SlideBackground | undefined, index?: number): void\n /** Saved as this document (under this title), as its `version`. */\n setSaved(savedId: string, title: string, version?: number | null): void\n /** Edited as this document or draft (see `boundTo`). */\n bind(boundTo: string | null): void\n /** Clips gathered before (with no presentation open), added to the tray, not placed. */\n adoptClips(clips: readonly Clip[]): void\n /**\n * A slide of these clips (added to the tray too), arranged, with `caption` as its title: after\n * the current slide, or in its place while that is empty. It becomes the current slide.\n */\n addSlideOf(clips: readonly Clip[], caption?: string): void\n /** Replace the presentation with one opened from a file (its clips' sources fetched). */\n load(opened: {\n title: string\n /** The document it is saved as; null for a new one (from a template). */\n savedId: string | null\n /** Its version, when known. */\n version?: number | null\n /** Edited as (default `savedId`): an older file opened unsaved is still its own. */\n boundTo?: string | null\n captions: boolean\n labelColumn?: string | null\n theme?: Theme\n clips: Omit<Clip, 'id'>[]\n slides: {\n caption: string\n auto: boolean\n layout?: SlideLayout\n points?: string\n items: { clip: number; frame: Frame }[]\n boxes?: readonly SavedBox[]\n background?: SlideBackground\n notes?: string\n }[]\n media?: Readonly<Record<string, string>>\n }): void\n reset(): void\n}\n\nexport type PresentationStore = PresentationState & PresentationActions\n\nlet sequence = 0\nconst nextId = (kind: string) => `${kind}-${Date.now().toString(36)}-${++sequence}`\n\nconst emptySlide = (layout: SlideLayout = 'clips'): Slide => ({\n id: nextId('slide'),\n items: [],\n auto: true,\n caption: '',\n layout,\n points: '',\n boxes: [],\n})\n\nconst initialState = (): PresentationState => ({\n clips: [],\n slides: [emptySlide()],\n current: 0,\n blank: false,\n captions: true,\n theme: DEFAULT_THEME,\n logoImage: null,\n labelColumn: null,\n columnValues: {},\n presenting: false,\n dragging: null,\n notice: null,\n title: 'Untitled presentation',\n savedId: null,\n version: null,\n boundTo: null,\n clean: null,\n media: {},\n})\n\n/** What arranging a slide depends on besides its clips: labels shown, a logo in the footer. */\ntype Look = Pick<PresentationState, 'captions' | 'theme'>\n\n/** The slide arranged again from its clips' aspects, in the room its layout leaves them. */\nfunction arranged(slide: Slide, clips: readonly Clip[], look: Look): Slide {\n const aspects = slide.items.map((item) => aspectOf(clips, item.clipId))\n const frames = autoLayout(aspects, reserveOf(slide, look))\n return {\n ...slide,\n auto: true,\n items: slide.items.map((item, i) => ({ ...item, frame: frames[i] })),\n }\n}\n\n/**\n * The room a slide keeps free of clips: its title and text (by its layout), each clip's label\n * under it, the footer while a logo shows.\n */\nexport const reserveOf = (slide: Pick<Slide, 'caption' | 'layout'>, look: Look): Reserve =>\n slideGeometry(slide, { labels: look.captions, logo: look.theme.logo?.corner ?? null }).reserve\n\nfunction aspectOf(clips: readonly Clip[], clipId: string): number {\n return clips.find((clip) => clip.id === clipId)?.aspect ?? 1\n}\n\nexport const usePresentation = create<PresentationStore>()((set, get) => {\n /** Change the current slide; an auto slide is arranged again after the change. */\n const updateSlide = (change: (slide: Slide, clips: readonly Clip[]) => Slide, at?: number) =>\n set((state) => {\n const index = at ?? state.current\n const slide = state.slides[index]\n if (!slide) return state\n const next = change(slide, state.clips)\n if (next === slide) return state\n const slides = state.slides.slice()\n slides[index] = next.auto ? arranged(next, state.clips, state) : next\n return { slides }\n })\n\n /** Change the boxes of the slide holding `boxId` (`at`: the box's index there). */\n const updateBoxes = (\n boxId: string,\n change: (boxes: readonly SlideBox[], at: number) => SlideBox[]\n ) =>\n set((state) => {\n const index = state.slides.findIndex((slide) => slide.boxes.some((box) => box.id === boxId))\n if (index < 0) return state\n const slide = state.slides[index]\n const slides = state.slides.slice()\n slides[index] = {\n ...slide,\n boxes: change(\n slide.boxes,\n slide.boxes.findIndex((box) => box.id === boxId)\n ),\n }\n return { slides }\n })\n\n /** Restack a clip on the current slide: only the drawing order changes, never a position. */\n const restack = (clipId: string, to: 'front' | 'back') =>\n set((state) => {\n const slide = state.slides[state.current]\n const item = slide?.items.find((entry) => entry.clipId === clipId)\n if (!slide || !item) return state\n const rest = slide.items.filter((entry) => entry !== item)\n const items = to === 'front' ? [...rest, item] : [item, ...rest]\n if (items.every((entry, i) => entry === slide.items[i])) return state\n const slides = state.slides.slice()\n slides[state.current] = { ...slide, items }\n return { slides }\n })\n\n return {\n ...initialState(),\n\n addClip(clip) {\n const id = nextId('clip')\n const full: Clip = {\n ...clip,\n id,\n caption: clip.caption ?? defaultCaption(clip.fileName, clip.page),\n zoomBase: clip.zoomBase ?? clip.rect.w,\n }\n set((state) => ({ clips: [...state.clips, full] }))\n const slide = get().slides[get().current]\n if (slide && slide.items.length < AUTO_PLACE_BELOW) get().place(id)\n return id\n },\n\n removeClip(clipId) {\n const { clips, slides } = get()\n const removed = clips.find((clip) => clip.id === clipId)\n if (!removed) return\n const rest = clips.filter((clip) => clip.id !== clipId)\n set({\n clips: rest,\n slides: slides.map((slide) => {\n if (!slide.items.some((item) => item.clipId === clipId)) return slide\n const next = { ...slide, items: slide.items.filter((item) => item.clipId !== clipId) }\n return next.auto ? arranged(next, rest, get()) : next\n }),\n })\n // The PDF engine's copy of the file goes once no clip is drawn from it.\n if (!rest.some((clip) => clip.source === removed.source))\n releasePdfRegionSource(removed.source)\n },\n\n setCaption: (clipId, caption) =>\n set((state) => ({\n clips: state.clips.map((clip) => (clip.id === clipId ? { ...clip, caption } : clip)),\n })),\n\n place: (clipId, at) =>\n updateSlide((slide, clips) => {\n if (!clips.some((clip) => clip.id === clipId)) return slide\n const others = slide.items.filter((item) => item.clipId !== clipId)\n if (!at && others.length < slide.items.length) return slide\n if (others.length >= MAX_ITEMS_PER_SLIDE) return slide\n const aspect = aspectOf(clips, clipId)\n // An auto slide arranges it with the rest. A slide arranged by hand stays so: the clip goes\n // where it was dropped, or in the middle.\n const frame = slide.auto ? fitFrame(aspect) : dropFrame(aspect, at ?? { x: 0.5, y: 0.5 })\n return { ...slide, items: [...others, { clipId, frame }] }\n }),\n\n unplace: (clipId) =>\n updateSlide((slide) =>\n slide.items.some((item) => item.clipId === clipId)\n ? { ...slide, items: slide.items.filter((item) => item.clipId !== clipId) }\n : slide\n ),\n\n setFrame: (clipId, frame) =>\n updateSlide((slide) => ({\n ...slide,\n auto: false,\n items: slide.items.map((item) => (item.clipId === clipId ? { ...item, frame } : item)),\n })),\n\n applyFrame(clipId, command) {\n const slide = get().slides[get().current]\n const item = slide?.items.find((entry) => entry.clipId === clipId)\n if (!item) return\n const aspect = aspectOf(get().clips, clipId)\n const reserve = reserveOf(slide, get())\n const frame =\n command === 'fullWidth'\n ? fullWidthFrame(item.frame, aspect, reserve)\n : command === 'fullHeight'\n ? fullHeightFrame(item.frame, aspect, reserve)\n : fitFrame(aspect, reserve)\n get().setFrame(clipId, frame)\n // Made the size of the stage, it goes on top of the others.\n get().bringToFront(clipId)\n },\n\n bringToFront: (clipId) => restack(clipId, 'front'),\n sendToBack: (clipId) => restack(clipId, 'back'),\n\n panClip: (clipId, dx, dy) =>\n set((state) => {\n const clip = state.clips.find((entry) => entry.id === clipId)\n if (!clip) return state\n const { rect } = clip\n const x = Math.min(Math.max(rect.x + dx, 0), 1 - rect.w)\n const y = Math.min(Math.max(rect.y + dy, 0), 1 - rect.h)\n if (x === rect.x && y === rect.y) return state\n return {\n clips: state.clips.map((entry) =>\n entry === clip ? { ...clip, rect: { ...rect, x, y } } : entry\n ),\n }\n }),\n\n zoomClip: (clipId, factor) =>\n set((state) => {\n const clip = state.clips.find((entry) => entry.id === clipId)\n if (!clip || !(factor > 0)) return state\n const { rect } = clip\n // Its area scales by 1/factor; never larger than the source, nor smaller than a sliver.\n const largest = Math.min(1 / rect.w, 1 / rect.h)\n const smallest = Math.max(MIN_CLIP_AREA / rect.w, MIN_CLIP_AREA / rect.h)\n const scale = Math.min(Math.max(1 / factor, smallest), largest)\n if (scale === 1) return state\n const w = rect.w * scale\n const h = rect.h * scale\n const x = Math.min(Math.max(rect.x + (rect.w - w) / 2, 0), 1 - w)\n const y = Math.min(Math.max(rect.y + (rect.h - h) / 2, 0), 1 - h)\n return {\n clips: state.clips.map((entry) =>\n entry === clip ? { ...clip, rect: { x, y, w, h } } : entry\n ),\n }\n }),\n\n setClipZoom(clipId, percent) {\n const clip = get().clips.find((entry) => entry.id === clipId)\n if (!clip || !(percent > 0)) return\n const width = (clip.zoomBase ?? clip.rect.w) / (percent / 100)\n get().zoomClip(clipId, clip.rect.w / width)\n },\n\n align(clipId, alignment) {\n const slide = get().slides[get().current]\n const item = slide?.items.find((entry) => entry.clipId === clipId)\n if (!slide || !item) return\n const aspect = aspectOf(get().clips, clipId)\n get().setFrame(clipId, alignFrame(item.frame, aspect, alignment, reserveOf(slide, get())))\n },\n\n nudge(clipId, dx, dy) {\n const slide = get().slides[get().current]\n const item = slide?.items.find((entry) => entry.clipId === clipId)\n if (!item) return\n const aspect = aspectOf(get().clips, clipId)\n get().setFrame(clipId, moveFrame(item.frame, aspect, dx, dy, [], true).frame)\n },\n\n // An auto slide makes (or gives back) the caption's room; one arranged by hand keeps its places.\n // A blank slide's title is its title box's text.\n setSlideCaption: (caption, index) =>\n updateSlide(\n (slide) => ({\n ...slide,\n caption,\n boxes: slide.boxes.map((box) => (box.role === 'title' ? withText(box, caption) : box)),\n }),\n index\n ),\n setSlideLayout: (layout, index) =>\n updateSlide((slide) => ({ ...slide, layout, auto: true }), index),\n setSlidePoints: (points, index) => updateSlide((slide) => ({ ...slide, points }), index),\n\n setTheme: (change) =>\n set((state) => {\n const theme = { ...state.theme, ...change }\n const logoMoved = (theme.logo?.corner ?? null) !== (state.theme.logo?.corner ?? null)\n return {\n theme,\n ...(theme.logo?.documentId !== state.theme.logo?.documentId ? { logoImage: null } : {}),\n slides: logoMoved\n ? state.slides.map((slide) =>\n slide.auto ? arranged(slide, state.clips, { ...state, theme }) : slide\n )\n : state.slides,\n }\n }),\n setLogoImage: (logoImage) => set({ logoImage }),\n\n arrange: () => updateSlide((slide) => ({ ...slide, auto: true })),\n\n addSlide: (layout, after) =>\n set((state) => {\n const at = (after ?? state.current) + 1\n const slides = state.slides.slice()\n const before = state.slides[at - 1]\n const slide = emptySlide(layout ?? before?.layout ?? 'clips')\n // The slide it follows keeps its look (an imported deck's background).\n slides.splice(\n at,\n 0,\n before?.background ? { ...slide, background: before.background } : slide\n )\n return { slides, current: at }\n }),\n\n removeSlide: (index) =>\n set((state) => {\n const target = index ?? state.current\n if (state.slides.length <= 1) return { slides: [emptySlide()], current: 0 }\n const slides = state.slides.filter((_, i) => i !== target)\n const current = state.current > target ? state.current - 1 : state.current\n return { slides, current: Math.min(current, slides.length - 1) }\n }),\n\n duplicateSlide: (index) =>\n set((state) => {\n const slide = state.slides[index]\n if (!slide) return state\n const slides = state.slides.slice()\n slides.splice(index + 1, 0, {\n ...slide,\n id: nextId('slide'),\n items: slide.items.map((item) => ({ ...item, frame: { ...item.frame } })),\n boxes: slide.boxes.map((box) => ({ ...box, id: nextBoxId() })),\n })\n return { slides, current: index + 1 }\n }),\n\n moveSlide: (index, to) =>\n set((state) => {\n if (to < 0 || to >= state.slides.length || index === to || !state.slides[index]) {\n return state\n }\n const slides = state.slides.slice()\n const [slide] = slides.splice(index, 1)\n slides.splice(to, 0, slide)\n return { slides, current: to }\n }),\n\n goTo: (index) =>\n set((state) =>\n index < 0 || index >= state.slides.length || index === state.current\n ? state\n : { current: index }\n ),\n\n setBlank: (blank) => set({ blank }),\n // Slides arranged automatically make (or give back) the captions' room.\n setCaptions: (captions) =>\n set((state) => ({\n captions,\n slides: state.slides.map((slide) =>\n slide.auto ? arranged(slide, state.clips, { ...state, captions }) : slide\n ),\n })),\n setLabelColumn: (labelColumn) => set({ labelColumn }),\n setColumnValues: (columnValues) => set({ columnValues }),\n setPresenting: (presenting) => set({ presenting }),\n setDragging: (dragging) => set({ dragging }),\n setTitle: (title) => set({ title }),\n notify: (text, kind) => set({ notice: { text, kind, id: ++sequence } }),\n clearNotice: (id) => set((state) => (state.notice?.id === id ? { notice: null } : state)),\n\n cropClip: (clipId, rect, aspect) =>\n set((state) => {\n const clips = state.clips.map((clip) =>\n clip.id === clipId ? { ...clip, rect, aspect } : clip\n )\n return {\n clips,\n // Its shape changed: slides arranged automatically arrange again around it.\n slides: state.slides.map((slide) =>\n slide.auto && slide.items.some((item) => item.clipId === clipId)\n ? arranged(slide, clips, state)\n : slide\n ),\n }\n }),\n reshapeClip: (clipId, frame, rect, aspect) =>\n set((state) => {\n const clip = state.clips.find((entry) => entry.id === clipId)\n if (!clip) return state\n // Its zoom reads against its width when cut: that scales with the width it now shows.\n const zoomBase = ((clip.zoomBase ?? clip.rect.w) * rect.w) / clip.rect.w\n const clips = state.clips.map((entry) =>\n entry === clip ? { ...clip, rect, aspect, zoomBase } : entry\n )\n return {\n clips,\n slides: state.slides.map((slide, index) => {\n if (index === state.current) {\n return {\n ...slide,\n auto: false,\n items: slide.items.map((item) =>\n item.clipId === clipId ? { ...item, frame } : item\n ),\n }\n }\n return slide.auto && slide.items.some((item) => item.clipId === clipId)\n ? arranged(slide, clips, state)\n : slide\n }),\n }\n }),\n addBox(box, index) {\n const id = nextBoxId()\n updateSlide((slide) => ({ ...slide, boxes: [...slide.boxes, { ...box, id }] }), index)\n return id\n },\n updateBox: (boxId, change) =>\n updateBoxes(boxId, (boxes, at) => {\n const next = boxes.slice()\n next[at] = change(boxes[at])\n return next\n }),\n setBoxText(boxId, text) {\n get().updateBox(boxId, (box) => withText(box, text))\n // A blank slide's title box is its title too (what an outline reads).\n set((state) => ({\n slides: state.slides.map((slide) =>\n slide.boxes.some((box) => box.id === boxId && box.role === 'title')\n ? { ...slide, caption: text.replace(/\\s*\\n\\s*/g, ' ').trim() }\n : slide\n ),\n }))\n },\n removeBox: (boxId) => updateBoxes(boxId, (boxes, at) => boxes.filter((_, i) => i !== at)),\n restackBox: (boxId, to) =>\n updateBoxes(boxId, (boxes, at) => {\n const box = { ...boxes[at], front: to === 'front' }\n const rest = boxes.filter((_, i) => i !== at)\n return to === 'front' ? [...rest, box] : [box, ...rest]\n }),\n setSlideNotes: (notes, index) => updateSlide((slide) => ({ ...slide, notes }), index),\n setSlideBackground: (background, index) =>\n updateSlide((slide) => {\n const { background: _old, ...rest } = slide\n void _old\n return background ? { ...rest, background } : rest\n }, index),\n setSaved: (savedId, title, version = null) => {\n set({ savedId, title, boundTo: savedId, version })\n set({ clean: snapshotOf(get()) })\n },\n bind: (boundTo) => set({ boundTo }),\n adoptClips: (adopted) =>\n set((state) => ({\n clips: [\n ...state.clips,\n ...adopted.filter((clip) => !state.clips.some((entry) => entry.id === clip.id)),\n ],\n })),\n\n addSlideOf: (added, caption = '') =>\n set((state) => {\n const fresh = added.map((clip) => ({ ...clip, id: nextId('clip') }))\n const clips = [...state.clips, ...fresh]\n const slide = arranged(\n {\n ...emptySlide('clips'),\n caption,\n items: fresh.map((clip) => ({ clipId: clip.id, frame: fitFrame(clip.aspect) })),\n },\n clips,\n state\n )\n const here = state.slides[state.current]\n const empty =\n !!here &&\n !here.items.length &&\n !here.boxes.length &&\n !here.caption.trim() &&\n !here.points.trim()\n const at = empty ? state.current : state.current + 1\n const slides = state.slides.slice()\n slides.splice(at, empty ? 1 : 0, slide)\n return { clips, slides, current: at }\n }),\n\n load(opened) {\n get().reset()\n const clips: Clip[] = opened.clips.map((clip) => ({ ...clip, id: nextId('clip') }))\n const slides: Slide[] = opened.slides.map((slide) => ({\n id: nextId('slide'),\n caption: slide.caption,\n auto: slide.auto,\n layout: slide.layout ?? 'clips',\n points: slide.points ?? '',\n // Items whose clip could not be opened are left out.\n items: slide.items\n .filter((item) => clips[item.clip])\n .map((item) => ({ clipId: clips[item.clip].id, frame: item.frame })),\n boxes: (slide.boxes ?? []).map((box) => ({ ...box, id: nextBoxId() })),\n ...(slide.background ? { background: slide.background } : {}),\n ...(slide.notes ? { notes: slide.notes } : {}),\n }))\n set({\n title: opened.title,\n savedId: opened.savedId,\n version: opened.version ?? null,\n boundTo: opened.boundTo ?? opened.savedId,\n captions: opened.captions,\n labelColumn: opened.labelColumn ?? null,\n theme: opened.theme ?? DEFAULT_THEME,\n clips,\n slides: slides.length ? slides : [emptySlide()],\n current: 0,\n media: opened.media ?? {},\n })\n set({ clean: opened.savedId ? snapshotOf(get()) : null })\n },\n\n reset() {\n for (const source of new Set(get().clips.map((clip) => clip.source))) {\n releasePdfRegionSource(source)\n }\n set(initialState())\n },\n }\n})\n\n/** The slide on the stage. */\nexport const currentSlide = (state: PresentationState): Slide | undefined =>\n state.slides[state.current]\n\n/** What is saved of a presentation, as text (to compare). */\nfunction snapshotOf(state: PresentationState): string {\n // Embedded images change only with the clips that show them: left out, so comparing stays cheap.\n return JSON.stringify(serializePresentation({ ...state, media: undefined }))\n}\n\n/**\n * The presentation has changes not saved: since it was opened or saved; or, never saved, anything\n * in it at all (a clip, a title, a point).\n */\nexport function hasUnsavedChanges(state: PresentationState): boolean {\n if (state.clean !== null) return snapshotOf(state) !== state.clean\n return (\n state.clips.length > 0 ||\n state.slides.some(\n (slide) =>\n slide.items.length || slide.boxes.length || slide.caption.trim() || slide.points.trim()\n )\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["slide"]
7
7
  }
@@ -0,0 +1,2 @@
1
+ /** The pointer has moved (or a key was pressed) in `view` within the last moment. */
2
+ export declare function usePointerActive(view: Window | null): boolean;
@@ -0,0 +1,28 @@
1
+ "use client";
2
+ import { useEffect, useState } from "react";
3
+ const IDLE_MS = 2200;
4
+ function usePointerActive(view) {
5
+ const [active, setActive] = useState(true);
6
+ useEffect(() => {
7
+ if (!view) return;
8
+ setActive(true);
9
+ let timer = view.setTimeout(() => setActive(false), IDLE_MS);
10
+ const wake = () => {
11
+ setActive(true);
12
+ view.clearTimeout(timer);
13
+ timer = view.setTimeout(() => setActive(false), IDLE_MS);
14
+ };
15
+ view.addEventListener("pointermove", wake);
16
+ view.addEventListener("keydown", wake);
17
+ return () => {
18
+ view.clearTimeout(timer);
19
+ view.removeEventListener("pointermove", wake);
20
+ view.removeEventListener("keydown", wake);
21
+ };
22
+ }, [view]);
23
+ return active;
24
+ }
25
+ export {
26
+ usePointerActive
27
+ };
28
+ //# sourceMappingURL=usePointerActive.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/presentation/usePointerActive.ts"],
4
+ "sourcesContent": ["import { useEffect, useState } from 'react'\n\n/** How long the pointer (or a key) has to rest before the controls fade. */\nconst IDLE_MS = 2200\n\n/** The pointer has moved (or a key was pressed) in `view` within the last moment. */\nexport function usePointerActive(view: Window | null): boolean {\n const [active, setActive] = useState(true)\n useEffect(() => {\n if (!view) return\n setActive(true)\n let timer = view.setTimeout(() => setActive(false), IDLE_MS)\n const wake = () => {\n setActive(true)\n view.clearTimeout(timer)\n timer = view.setTimeout(() => setActive(false), IDLE_MS)\n }\n view.addEventListener('pointermove', wake)\n view.addEventListener('keydown', wake)\n return () => {\n view.clearTimeout(timer)\n view.removeEventListener('pointermove', wake)\n view.removeEventListener('keydown', wake)\n }\n }, [view])\n return active\n}\n"],
5
+ "mappings": ";AAAA,SAAS,WAAW,gBAAgB;AAGpC,MAAM,UAAU;AAGT,SAAS,iBAAiB,MAA8B;AAC7D,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,IAAI;AACzC,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AACX,cAAU,IAAI;AACd,QAAI,QAAQ,KAAK,WAAW,MAAM,UAAU,KAAK,GAAG,OAAO;AAC3D,UAAM,OAAO,MAAM;AACjB,gBAAU,IAAI;AACd,WAAK,aAAa,KAAK;AACvB,cAAQ,KAAK,WAAW,MAAM,UAAU,KAAK,GAAG,OAAO;AAAA,IACzD;AACA,SAAK,iBAAiB,eAAe,IAAI;AACzC,SAAK,iBAAiB,WAAW,IAAI;AACrC,WAAO,MAAM;AACX,WAAK,aAAa,KAAK;AACvB,WAAK,oBAAoB,eAAe,IAAI;AAC5C,WAAK,oBAAoB,WAAW,IAAI;AAAA,IAC1C;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AACT,SAAO;AACT;",
6
+ "names": []
7
+ }