@fabrika/canvas-kernel 0.1.44 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -67
- package/index.ts +19 -83
- package/package.json +68 -72
- package/src/client/AnnotationProvider.tsx +227 -268
- package/src/client/AnnotationSidebar.tsx +392 -338
- package/src/client/FileIcon.tsx +106 -65
- package/src/client/PlanRenderer.tsx +607 -551
- package/src/client/RenderErrorContext.ts +7 -7
- package/src/client/ResponsePreview.tsx +192 -173
- package/src/client/annotationDom.ts +43 -97
- package/src/client/annotationDraft.ts +98 -92
- package/src/client/appContext.tsx +40 -40
- package/src/client/components/Callout.tsx +20 -20
- package/src/client/components/Checklist.tsx +28 -28
- package/src/client/components/Choice.tsx +178 -159
- package/src/client/components/CodeBlock.tsx +26 -26
- package/src/client/components/Diff.tsx +17 -17
- package/src/client/components/FilePreview.tsx +99 -74
- package/src/client/components/Image.tsx +41 -39
- package/src/client/components/Markdown.tsx +33 -32
- package/src/client/components/Mermaid.tsx +592 -542
- package/src/client/components/Note.tsx +8 -8
- package/src/client/components/Priority.tsx +11 -11
- package/src/client/components/ResponseNote.tsx +17 -15
- package/src/client/components/Section.tsx +28 -26
- package/src/client/components/Table.tsx +34 -29
- package/src/client/components/Task.tsx +58 -60
- package/src/client/components/UserInput.tsx +169 -152
- package/src/client/components/index.ts +16 -16
- package/src/client/components/renderMarkdownChildren.tsx +86 -68
- package/src/client/diffLines.ts +51 -39
- package/src/client/generateMarkdown.ts +197 -191
- package/src/client/hostContext.tsx +34 -34
- package/src/client/index.ts +43 -125
- package/src/client/preact-all.ts +4 -4
- package/src/client/runtime.ts +58 -111
- package/src/client/styles.css +1 -74
- package/src/engine/compiler.ts +316 -313
- package/src/engine/paths.ts +44 -44
- package/src/engine/session.ts +623 -618
- package/src/engine/watcher.ts +63 -60
- package/src/engine/websocket.ts +123 -121
- package/annotate.ts +0 -94
- package/daemon.ts +0 -46
- package/src/client/AnnotationDraftFooter.tsx +0 -76
- package/src/client/AnnotationEditor.tsx +0 -320
- package/src/client/AnnotationList.tsx +0 -190
- package/src/client/Popover.tsx +0 -361
- package/src/client/SessionContext.tsx +0 -3
- package/src/client/annotationContext.ts +0 -130
- package/src/client/annotationMarkdown.ts +0 -102
- package/src/client/annotationTarget.ts +0 -73
- package/src/client/blockTarget.ts +0 -127
- package/src/client/highlightRange.ts +0 -262
- package/src/client/popoverPosition.ts +0 -46
- package/src/client/regionTarget.ts +0 -180
- package/src/client/testing/dom.ts +0 -58
- package/src/client/textTarget.ts +0 -31
- package/src/client/useRegionAnnotation.tsx +0 -188
- package/src/client/useTextAnnotation.tsx +0 -194
- package/src/client/utils.ts +0 -33
- package/src/engine/channel.ts +0 -278
- package/src/engine/http-utils.ts +0 -14
- package/src/engine/lifecycle.ts +0 -302
- package/src/engine/router.ts +0 -16
- package/src/engine/waiter.ts +0 -188
|
@@ -1,55 +1,57 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { AnnotationCtx } from '#canvas/runtime'
|
|
2
|
+
import type { Annotation, AnnotationContext, AnnotationContextValue, FeedbackEntry, PlanResponse } from '#canvas/runtime'
|
|
3
|
+
import { useAnnotationState } from '@fabrika/annotations'
|
|
4
|
+
import { AnnotationCtx as SurfaceAnnotationCtx, AnnotationHostContext } from '@fabrika/annotations/runtime'
|
|
5
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
6
|
+
import { annotationDraftKey, type AnnotationDraftPhase, clearPersistedDraft } from './annotationDraft'
|
|
7
|
+
import { canPruneResponses, pruneStaleResponses } from './generateMarkdown'
|
|
8
|
+
import { useCanvasHost } from './hostContext'
|
|
7
9
|
|
|
8
10
|
// Re-export types for convenience
|
|
9
|
-
export type { Annotation, AnnotationContext,
|
|
10
|
-
export { useAnnotations } from
|
|
11
|
+
export type { Annotation, AnnotationContext, AnnotationContextValue, FeedbackEntry, PlanResponse }
|
|
12
|
+
export { useAnnotations } from '#canvas/runtime'
|
|
11
13
|
|
|
12
14
|
export interface PersistedState {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
annotations: Annotation[]
|
|
16
|
+
generalNote: string
|
|
17
|
+
responses: [string, PlanResponse][]
|
|
18
|
+
feedbackEntries?: [string, FeedbackEntry][]
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
interface AnnotationProviderProps {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
22
|
+
sessionId: string
|
|
23
|
+
revision: number
|
|
24
|
+
isReadOnly: boolean
|
|
25
|
+
draftPhase: AnnotationDraftPhase
|
|
26
|
+
/** Remote annotations fetched from shared views. Merged read-only into
|
|
27
|
+
* the annotation list so they render alongside the author's own. */
|
|
28
|
+
remoteAnnotations?: Annotation[]
|
|
29
|
+
/** Canvas files this revision is made of. Supplying it enables pruning of
|
|
30
|
+
* answers to questions the revision no longer asks; omit it and every
|
|
31
|
+
* answer is submitted, which is what a host without canvas tabs wants. */
|
|
32
|
+
canvasFiles?: readonly string[]
|
|
33
|
+
/** Server-authoritative persistence. When supplied, the draft is loaded and
|
|
34
|
+
* saved through these instead of localStorage, so it survives reloads and
|
|
35
|
+
* follows the author across browsers. Omit for the localStorage default. */
|
|
36
|
+
loadState?: (sessionId: string, revision: number, phase: AnnotationDraftPhase) => Promise<PersistedState | null>
|
|
37
|
+
saveState?: (sessionId: string, revision: number, state: PersistedState, phase: AnnotationDraftPhase) => void
|
|
38
|
+
children: React.ReactNode
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
function loadPersisted(sessionId: string, revision: number, phase: AnnotationDraftPhase): PersistedState | null {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
try {
|
|
43
|
+
const raw = localStorage.getItem(annotationDraftKey(sessionId, revision, phase))
|
|
44
|
+
if (!raw) return null
|
|
45
|
+
return JSON.parse(raw)
|
|
46
|
+
} catch {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
function savePersisted(sessionId: string, revision: number, phase: AnnotationDraftPhase, state: PersistedState) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
try {
|
|
53
|
+
localStorage.setItem(annotationDraftKey(sessionId, revision, phase), JSON.stringify(state))
|
|
54
|
+
} catch {}
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
/** What the reader has been shown for one draft. Stamped with the draft it
|
|
@@ -57,238 +59,195 @@ function savePersisted(sessionId: string, revision: number, phase: AnnotationDra
|
|
|
57
59
|
* their own effects, which run before the provider's, so an effect-based reset
|
|
58
60
|
* would wipe registrations that already happened. */
|
|
59
61
|
interface SeenState {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
draft: string
|
|
63
|
+
responseIds: Set<string>
|
|
64
|
+
canvases: Set<string>
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
function seenFor(draft: string): SeenState {
|
|
66
|
-
|
|
68
|
+
return { draft, responseIds: new Set(), canvases: new Set() }
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
export function AnnotationProvider(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if (saveState) {
|
|
252
|
-
saveState(sessionId, revision, { annotations: [], generalNote: "", responses: [], feedbackEntries: [] }, draftPhase);
|
|
253
|
-
} else if (draftPhase === "next") {
|
|
254
|
-
localStorage.removeItem(annotationDraftKey(sessionId, revision, draftPhase));
|
|
255
|
-
} else {
|
|
256
|
-
clearPersistedDraft(localStorage, sessionId, revision);
|
|
257
|
-
}
|
|
258
|
-
}, [sessionId, revision, draftPhase, saveState]);
|
|
259
|
-
|
|
260
|
-
return (
|
|
261
|
-
<AnnotationCtx.Provider
|
|
262
|
-
value={{
|
|
263
|
-
annotations, addAnnotation, addAnnotationWithId, updateAnnotation, removeAnnotation, addAnnotationImage, removeAnnotationImage,
|
|
264
|
-
generalNote, setGeneralNote, clearAll,
|
|
265
|
-
activeAnnotationId, setActiveAnnotationId,
|
|
266
|
-
responses, setResponse, submittableResponses,
|
|
267
|
-
registerResponse, registerCanvasRendered,
|
|
268
|
-
feedbackEntries, setFeedbackEntry, removeFeedbackEntry,
|
|
269
|
-
isReadOnly,
|
|
270
|
-
}}
|
|
271
|
-
>
|
|
272
|
-
{children}
|
|
273
|
-
</AnnotationCtx.Provider>
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function removeMarksFromDom(id: string) {
|
|
278
|
-
const marks = document.querySelectorAll(`[data-annotation-id="${id}"]`);
|
|
279
|
-
for (const mark of marks) unwrapMark(mark as HTMLElement);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function unwrapMark(mark: HTMLElement) {
|
|
283
|
-
const parent = mark.parentNode;
|
|
284
|
-
if (!parent) return;
|
|
285
|
-
// A region overlay wraps no text; swapping it for its text content would
|
|
286
|
-
// leave a stray empty node inside the image.
|
|
287
|
-
if (mark.tagName !== "MARK") {
|
|
288
|
-
parent.removeChild(mark);
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
const text = document.createTextNode(mark.textContent || "");
|
|
292
|
-
parent.replaceChild(text, mark);
|
|
293
|
-
parent.normalize();
|
|
71
|
+
export function AnnotationProvider(
|
|
72
|
+
{ sessionId, revision, isReadOnly, draftPhase, remoteAnnotations, canvasFiles, loadState, saveState, children }: AnnotationProviderProps,
|
|
73
|
+
) {
|
|
74
|
+
// Server-authoritative persistence loads asynchronously, so that mode starts
|
|
75
|
+
// empty and hydrates in an effect. localStorage mode seeds synchronously.
|
|
76
|
+
const serverMode = !!loadState
|
|
77
|
+
const seed = serverMode ? null : loadPersisted(sessionId, revision, draftPhase)
|
|
78
|
+
|
|
79
|
+
const [localAnnotations, setAnnotations] = useState<Annotation[]>(() =>
|
|
80
|
+
(seed?.annotations ?? []).map((a) => ({ ...a, source: a.source ?? 'local' as const }))
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const [generalNote, setGeneralNote] = useState(() => seed?.generalNote ?? '')
|
|
84
|
+
const [responses, setResponses] = useState<Map<string, PlanResponse>>(() => seed?.responses ? new Map(seed.responses) : new Map())
|
|
85
|
+
const draftId = `${sessionId}:${revision}:${draftPhase}`
|
|
86
|
+
const draftIdRef = useRef(draftId)
|
|
87
|
+
draftIdRef.current = draftId
|
|
88
|
+
const [seen, setSeen] = useState<SeenState>(() => seenFor(draftId))
|
|
89
|
+
const [feedbackEntries, setFeedbackEntries] = useState<Map<string, FeedbackEntry>>(() =>
|
|
90
|
+
seed?.feedbackEntries ? new Map(seed.feedbackEntries) : new Map()
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
// Re-hydrate when the draft identity changes. Hosts that remount the provider
|
|
94
|
+
// per revision (via a key) get this for free from the initializers, but hosts
|
|
95
|
+
// that keep it mounted need it here — otherwise one revision's answers leak
|
|
96
|
+
// into the next and the persist effect writes them over the new revision's
|
|
97
|
+
// saved draft. `hydrated` gates that effect so an async server load cannot be
|
|
98
|
+
// clobbered by the empty initial state.
|
|
99
|
+
const [hydrated, setHydrated] = useState(!serverMode)
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
const apply = (saved: PersistedState | null) => {
|
|
102
|
+
setAnnotations((saved?.annotations ?? []).map((a) => ({ ...a, source: a.source ?? 'local' as const })))
|
|
103
|
+
setGeneralNote(saved?.generalNote ?? '')
|
|
104
|
+
setResponses(saved?.responses ? new Map(saved.responses) : new Map())
|
|
105
|
+
setFeedbackEntries(saved?.feedbackEntries ? new Map(saved.feedbackEntries) : new Map())
|
|
106
|
+
setActiveAnnotationId(null)
|
|
107
|
+
}
|
|
108
|
+
if (serverMode) {
|
|
109
|
+
let cancelled = false
|
|
110
|
+
setHydrated(false)
|
|
111
|
+
loadState!(sessionId, revision, draftPhase).then((saved) => {
|
|
112
|
+
if (cancelled) return
|
|
113
|
+
apply(saved)
|
|
114
|
+
setHydrated(true)
|
|
115
|
+
})
|
|
116
|
+
return () => {
|
|
117
|
+
cancelled = true
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
apply(loadPersisted(sessionId, revision, draftPhase))
|
|
121
|
+
setHydrated(true)
|
|
122
|
+
return undefined
|
|
123
|
+
}, [sessionId, revision, draftPhase, serverMode, loadState])
|
|
124
|
+
|
|
125
|
+
// Post-feedback drafts save immediately so a new revision can carry the latest edit.
|
|
126
|
+
const persistTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (isReadOnly && draftPhase !== 'next') return
|
|
129
|
+
if (!hydrated) return
|
|
130
|
+
if (persistTimerRef.current) clearTimeout(persistTimerRef.current)
|
|
131
|
+
const state: PersistedState = {
|
|
132
|
+
// Persist only local annotations — remote ones are re-fetched on load.
|
|
133
|
+
annotations: localAnnotations,
|
|
134
|
+
generalNote,
|
|
135
|
+
responses: Array.from(responses.entries()),
|
|
136
|
+
feedbackEntries: Array.from(feedbackEntries.entries()),
|
|
137
|
+
}
|
|
138
|
+
const persist = () => {
|
|
139
|
+
if (saveState) saveState(sessionId, revision, state, draftPhase)
|
|
140
|
+
else savePersisted(sessionId, revision, draftPhase, state)
|
|
141
|
+
}
|
|
142
|
+
if (draftPhase === 'next') {
|
|
143
|
+
persist()
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
persistTimerRef.current = setTimeout(persist, 300)
|
|
147
|
+
return () => {
|
|
148
|
+
if (persistTimerRef.current) clearTimeout(persistTimerRef.current)
|
|
149
|
+
}
|
|
150
|
+
}, [localAnnotations, generalNote, responses, feedbackEntries, sessionId, revision, isReadOnly, draftPhase, hydrated, saveState])
|
|
151
|
+
|
|
152
|
+
const setResponse = useCallback((id: string, response: PlanResponse) => {
|
|
153
|
+
setResponses((prev) => {
|
|
154
|
+
const next = new Map(prev)
|
|
155
|
+
next.set(id, response)
|
|
156
|
+
return next
|
|
157
|
+
})
|
|
158
|
+
}, [])
|
|
159
|
+
|
|
160
|
+
// Both registrations accumulate for the life of one draft and are idempotent,
|
|
161
|
+
// so re-running the caller's effect cannot loop.
|
|
162
|
+
const registerResponse = useCallback((id: string) => {
|
|
163
|
+
setSeen((prev) => {
|
|
164
|
+
const base = prev.draft === draftIdRef.current ? prev : seenFor(draftIdRef.current)
|
|
165
|
+
if (base.responseIds.has(id)) return base
|
|
166
|
+
return { ...base, responseIds: new Set(base.responseIds).add(id) }
|
|
167
|
+
})
|
|
168
|
+
}, [])
|
|
169
|
+
|
|
170
|
+
const registerCanvasRendered = useCallback((filename: string) => {
|
|
171
|
+
setSeen((prev) => {
|
|
172
|
+
const base = prev.draft === draftIdRef.current ? prev : seenFor(draftIdRef.current)
|
|
173
|
+
if (base.canvases.has(filename)) return base
|
|
174
|
+
return { ...base, canvases: new Set(base.canvases).add(filename) }
|
|
175
|
+
})
|
|
176
|
+
}, [])
|
|
177
|
+
|
|
178
|
+
// Answers survive until a canvas proves the question is gone. `seen` can lag
|
|
179
|
+
// the draft by a render when the host keeps the provider mounted across
|
|
180
|
+
// revisions, and stale evidence must never prune.
|
|
181
|
+
const current = seen.draft === draftId ? seen : null
|
|
182
|
+
const submittableResponses = useMemo(() => {
|
|
183
|
+
if (!current || !canPruneResponses(canvasFiles, current.canvases)) return responses
|
|
184
|
+
return pruneStaleResponses(responses, current.responseIds)
|
|
185
|
+
}, [current, canvasFiles, responses])
|
|
186
|
+
|
|
187
|
+
const setFeedbackEntry = useCallback((id: string, entry: FeedbackEntry) => {
|
|
188
|
+
setFeedbackEntries((prev) => {
|
|
189
|
+
const existing = prev.get(id)
|
|
190
|
+
if (existing && existing.markdown === entry.markdown && existing.label === entry.label && existing.required === entry.required) {
|
|
191
|
+
return prev // same reference → no re-render
|
|
192
|
+
}
|
|
193
|
+
const next = new Map(prev)
|
|
194
|
+
next.set(id, entry)
|
|
195
|
+
return next
|
|
196
|
+
})
|
|
197
|
+
}, [])
|
|
198
|
+
|
|
199
|
+
const removeFeedbackEntry = useCallback((id: string) => {
|
|
200
|
+
setFeedbackEntries((prev) => {
|
|
201
|
+
if (!prev.has(id)) return prev
|
|
202
|
+
const next = new Map(prev)
|
|
203
|
+
next.delete(id)
|
|
204
|
+
return next
|
|
205
|
+
})
|
|
206
|
+
}, [])
|
|
207
|
+
|
|
208
|
+
const clearCanvasState = useCallback(() => {
|
|
209
|
+
setResponses(new Map())
|
|
210
|
+
setFeedbackEntries(new Map())
|
|
211
|
+
if (saveState) {
|
|
212
|
+
saveState(sessionId, revision, { annotations: [], generalNote: '', responses: [], feedbackEntries: [] }, draftPhase)
|
|
213
|
+
} else if (draftPhase === 'next') {
|
|
214
|
+
localStorage.removeItem(annotationDraftKey(sessionId, revision, draftPhase))
|
|
215
|
+
} else {
|
|
216
|
+
clearPersistedDraft(localStorage, sessionId, revision)
|
|
217
|
+
}
|
|
218
|
+
}, [sessionId, revision, draftPhase, saveState])
|
|
219
|
+
|
|
220
|
+
const annotationState = useAnnotationState({
|
|
221
|
+
annotations: localAnnotations,
|
|
222
|
+
setAnnotations,
|
|
223
|
+
generalNote,
|
|
224
|
+
setGeneralNote,
|
|
225
|
+
remoteAnnotations,
|
|
226
|
+
isReadOnly,
|
|
227
|
+
onClear: clearCanvasState,
|
|
228
|
+
})
|
|
229
|
+
const { setActiveAnnotationId } = annotationState
|
|
230
|
+
const host = useCanvasHost()
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<AnnotationHostContext.Provider value={host}>
|
|
234
|
+
<SurfaceAnnotationCtx.Provider value={annotationState}>
|
|
235
|
+
<AnnotationCtx.Provider
|
|
236
|
+
value={{
|
|
237
|
+
...annotationState,
|
|
238
|
+
responses,
|
|
239
|
+
setResponse,
|
|
240
|
+
submittableResponses,
|
|
241
|
+
registerResponse,
|
|
242
|
+
registerCanvasRendered,
|
|
243
|
+
feedbackEntries,
|
|
244
|
+
setFeedbackEntry,
|
|
245
|
+
removeFeedbackEntry,
|
|
246
|
+
}}
|
|
247
|
+
>
|
|
248
|
+
{children}
|
|
249
|
+
</AnnotationCtx.Provider>
|
|
250
|
+
</SurfaceAnnotationCtx.Provider>
|
|
251
|
+
</AnnotationHostContext.Provider>
|
|
252
|
+
)
|
|
294
253
|
}
|