@triiiceratops/plugin-annotation-editor 1.0.0-rc.1
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/LICENSE +21 -0
- package/dist/AnnotationManager.svelte.d.ts +260 -0
- package/dist/AnnotationStore.svelte.d.ts +211 -0
- package/dist/adapters/LocalStorageAdapter.d.ts +37 -0
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/types.d.ts +78 -0
- package/dist/catalog.d.ts +9 -0
- package/dist/components/viewerControls.d.ts +43 -0
- package/dist/contextKey.d.ts +10 -0
- package/dist/i18n.svelte.d.ts +18 -0
- package/dist/icons.d.ts +29 -0
- package/dist/iife.d.ts +24 -0
- package/dist/iife.js +1273 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +5962 -0
- package/dist/loader.svelte.d.ts +11 -0
- package/dist/mount.svelte.d.ts +3 -0
- package/dist/plugin.d.ts +37 -0
- package/dist/styles.d.ts +11 -0
- package/dist/testing/index.d.ts +48 -0
- package/dist/testing/index.js +158 -0
- package/dist/types.d.ts +177 -0
- package/dist/utils/iiifTargets.d.ts +12 -0
- package/dist/utils/languageMap.d.ts +21 -0
- package/dist/utils/pointMarker.d.ts +27 -0
- package/dist/viewerMirror.svelte.d.ts +35 -0
- package/package.json +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 David Flood
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import type { AnnotationEditorConfig, AnnotationPersistenceOp, DrawingTool } from './types';
|
|
2
|
+
import { AnnotationStore } from './AnnotationStore.svelte';
|
|
3
|
+
import type { ViewerState } from 'triiiceratops';
|
|
4
|
+
/** Every tool the plugin knows how to draw, in default button order. */
|
|
5
|
+
export declare const ALL_TOOLS: DrawingTool[];
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the effective tool set and default tool from config so the manager
|
|
8
|
+
* and controller share one source of truth (F8). An empty/absent `tools` list
|
|
9
|
+
* means "all tools"; `defaultTool` is honored only when it's within `tools`,
|
|
10
|
+
* otherwise the first available tool wins.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveTools(config: {
|
|
13
|
+
tools?: DrawingTool[];
|
|
14
|
+
defaultTool?: DrawingTool;
|
|
15
|
+
}): {
|
|
16
|
+
tools: DrawingTool[];
|
|
17
|
+
defaultTool: DrawingTool;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Manages the Annotorious instance and annotation CRUD operations.
|
|
21
|
+
* Instantiated within the controller component.
|
|
22
|
+
*/
|
|
23
|
+
export declare class AnnotationManager {
|
|
24
|
+
private static readonly ACTIVE_EDIT_ID_EVENT;
|
|
25
|
+
private static readonly DEFAULT_POINT_FILL;
|
|
26
|
+
private static readonly DEFAULT_POINT_STROKE;
|
|
27
|
+
private static readonly DEFAULT_POINT_STROKE_WIDTH;
|
|
28
|
+
private static readonly DEFAULT_DRAWING_STYLE;
|
|
29
|
+
private config;
|
|
30
|
+
private store;
|
|
31
|
+
private readonly ownsStore;
|
|
32
|
+
private readonly viewerState;
|
|
33
|
+
private annotorious;
|
|
34
|
+
private osdViewer;
|
|
35
|
+
private openHandler;
|
|
36
|
+
private canvasClickHandler;
|
|
37
|
+
private createOSDAnnotator;
|
|
38
|
+
private W3CImageFormat;
|
|
39
|
+
private OSD;
|
|
40
|
+
private editingPointOrigin;
|
|
41
|
+
private get currentManifestId();
|
|
42
|
+
private get currentCanvasId();
|
|
43
|
+
private activeEditingAnnotationId;
|
|
44
|
+
private lastHandledCanvasKey;
|
|
45
|
+
private suppressedEchoIds;
|
|
46
|
+
private isDrawingEnabled;
|
|
47
|
+
private activeTool;
|
|
48
|
+
private selectedAnnotation;
|
|
49
|
+
private readonly resolvedTools;
|
|
50
|
+
readonly resolvedDefaultTool: DrawingTool;
|
|
51
|
+
onSelectionChange?: (annotation: any | null) => void;
|
|
52
|
+
onAnnotationCreated?: (annotation: any) => void;
|
|
53
|
+
onAnnotationHydrationChange?: (isHydrating: boolean) => void;
|
|
54
|
+
onActiveEditingAnnotationChange?: (annotationId: string | null) => void;
|
|
55
|
+
constructor(config: AnnotationEditorConfig, store?: AnnotationStore, viewerState?: ViewerState);
|
|
56
|
+
init(viewer: any, canvasId: string | null): void;
|
|
57
|
+
private initAnnotorious;
|
|
58
|
+
/**
|
|
59
|
+
* Create a true IIIF `PointSelector` annotation at the exact click point
|
|
60
|
+
* (F17). The click is converted click → viewport → image coords → canvas
|
|
61
|
+
* coords and rounded once to integer canvas pixels (D2) — no synthetic
|
|
62
|
+
* fragment rectangle, no zoom-dependent geometry, no `point-` id heuristic.
|
|
63
|
+
* The annotation goes through the same store create path as drawn shapes
|
|
64
|
+
* (prepareDraft, stamping, display sync); it is already canvas-space, so the
|
|
65
|
+
* image→canvas transform is skipped. It is then opened for body editing.
|
|
66
|
+
*/
|
|
67
|
+
private handlePointClick;
|
|
68
|
+
setEditing(enabled: boolean): void;
|
|
69
|
+
private updateDrawingMode;
|
|
70
|
+
/**
|
|
71
|
+
* A point annotation is one whose target carries a `PointSelector` (F17).
|
|
72
|
+
* Recurses one level into `selector.item` for wrapped selectors, matching
|
|
73
|
+
* `annotationAdapter.ts`. No `point-` id heuristic — geometry, not id, is
|
|
74
|
+
* authoritative.
|
|
75
|
+
*/
|
|
76
|
+
private isPointAnnotation;
|
|
77
|
+
private getPointCoordinates;
|
|
78
|
+
private toPointSelectorTarget;
|
|
79
|
+
/**
|
|
80
|
+
* Convert a cached (canvas-space) annotation into the image-space shape
|
|
81
|
+
* Annotorious edits. Non-points scale straight to image space. A point has
|
|
82
|
+
* no Annotorious tool, so it becomes a small fragment rectangle centred on
|
|
83
|
+
* the point and sized in **screen pixels at selection time** (§3.2): the
|
|
84
|
+
* marker keeps a constant visual size regardless of image resolution or
|
|
85
|
+
* zoom. The exact point is recorded separately (`editingPointOrigin`) so the
|
|
86
|
+
* reverse conversion never re-derives it from the rect centre.
|
|
87
|
+
*/
|
|
88
|
+
private toAnnotoriousTarget;
|
|
89
|
+
/**
|
|
90
|
+
* Reverse of {@link toAnnotoriousTarget} for a point being edited: turn the
|
|
91
|
+
* image-space fragment rect Annotorious holds back into a canvas-space
|
|
92
|
+
* `PointSelector`. If the rect's centre still maps to the recorded origin
|
|
93
|
+
* (integer canvas px, D2), the point wasn't dragged and the origin is emitted
|
|
94
|
+
* verbatim — a bit-identical round-trip. If it moved, the new rect centre is
|
|
95
|
+
* used (§3.2).
|
|
96
|
+
*/
|
|
97
|
+
private pointFromEditingRect;
|
|
98
|
+
private parseFragmentRect;
|
|
99
|
+
/**
|
|
100
|
+
* The point editing rectangle's side length in **image units**, derived from
|
|
101
|
+
* the configured marker diameter (screen px) and the current
|
|
102
|
+
* image-units-per-screen-pixel. Falls back to treating the diameter as
|
|
103
|
+
* canvas units (scaled to image space) when the viewport can't be measured
|
|
104
|
+
* yet — e.g. before the viewer's first render.
|
|
105
|
+
*/
|
|
106
|
+
private pointEditRectImageSize;
|
|
107
|
+
/**
|
|
108
|
+
* Image units spanned by one screen pixel at the current zoom, measured the
|
|
109
|
+
* way the old point-authoring code did: convert two 1px-apart screen points
|
|
110
|
+
* to image coordinates and take the delta. Returns null when the viewport
|
|
111
|
+
* isn't available.
|
|
112
|
+
*/
|
|
113
|
+
private imageUnitsPerScreenPixel;
|
|
114
|
+
/**
|
|
115
|
+
* Annotorious `style` callback: points get the configured marker colours,
|
|
116
|
+
* every other shape gets the host's drawing style (F9). A point is
|
|
117
|
+
* identified by having an editing origin recorded for its id.
|
|
118
|
+
*/
|
|
119
|
+
private styleForAnnotation;
|
|
120
|
+
private getCurrentCanvasImageDimensions;
|
|
121
|
+
setTool(tool: DrawingTool): void;
|
|
122
|
+
get availableTools(): DrawingTool[];
|
|
123
|
+
/**
|
|
124
|
+
* The store's current unhandled persistence error for the panel's default
|
|
125
|
+
* error line, or `null` when there's nothing to show (F20).
|
|
126
|
+
*/
|
|
127
|
+
get persistenceError(): {
|
|
128
|
+
op: AnnotationPersistenceOp;
|
|
129
|
+
annotationId?: string;
|
|
130
|
+
} | null;
|
|
131
|
+
/** Dismiss the panel's persistence error line. */
|
|
132
|
+
dismissPersistenceError(): void;
|
|
133
|
+
/** Whether an undo is available (reactive; drives the panel button — F6). */
|
|
134
|
+
get canUndo(): boolean;
|
|
135
|
+
/** Whether a redo is available (reactive; drives the panel button — F6). */
|
|
136
|
+
get canRedo(): boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Reverse the most recent persisted operation through the store's op stack,
|
|
139
|
+
* replaying its inverse against the adapter so storage and display stay in
|
|
140
|
+
* agreement (F6).
|
|
141
|
+
*/
|
|
142
|
+
undo(): Promise<void>;
|
|
143
|
+
/** Re-apply the most recently undone operation (F6). */
|
|
144
|
+
redo(): Promise<void>;
|
|
145
|
+
private setupEvents;
|
|
146
|
+
private setActiveEditingAnnotationId;
|
|
147
|
+
/**
|
|
148
|
+
* Reset the manager's Annotorious-facing selection fields. Shared by the two
|
|
149
|
+
* teardown paths: `clearSelectionState()` (also notifies the host, leaves
|
|
150
|
+
* Annotorious untouched) and `clearAnnotoriousEditingAnnotation()` (also
|
|
151
|
+
* clears Annotorious's annotation set).
|
|
152
|
+
*/
|
|
153
|
+
private resetSelectionFields;
|
|
154
|
+
/**
|
|
155
|
+
* Tear down the current selection and tell the host it's gone, without
|
|
156
|
+
* touching Annotorious's own annotation set. Used on the create/delete/
|
|
157
|
+
* rollback paths where Annotorious's shape is managed separately.
|
|
158
|
+
*/
|
|
159
|
+
private clearSelectionState;
|
|
160
|
+
private clearAnnotoriousEditingAnnotation;
|
|
161
|
+
/**
|
|
162
|
+
* Clear all annotations from Annotorious, marking each so the resulting
|
|
163
|
+
* (async) `deleteAnnotation` echo is consumed by handleDeleteAnnotation and
|
|
164
|
+
* never mistaken for a user-originated deletion (F27). Only ids actually
|
|
165
|
+
* present are marked, so no stale marks accrue.
|
|
166
|
+
*/
|
|
167
|
+
private clearAnnotationsSuppressed;
|
|
168
|
+
handleCanvasChange(manifestId: string | null, canvasId: string | null): Promise<void>;
|
|
169
|
+
private loadAnnotations;
|
|
170
|
+
/**
|
|
171
|
+
* Handles Annotorious's (async) `createAnnotation` lifecycle event. Persists
|
|
172
|
+
* exactly what the host's prepareDraft/prepareAnnotation produced — not the
|
|
173
|
+
* raw event payload — so draft enrichment survives create (F2).
|
|
174
|
+
*/
|
|
175
|
+
private handleCreateAnnotation;
|
|
176
|
+
/**
|
|
177
|
+
* Handles Annotorious's (async) `updateAnnotation` lifecycle event. Echoes
|
|
178
|
+
* we triggered ourselves (e.g. pushing edited bodies back into Annotorious)
|
|
179
|
+
* are consumed here so they don't double-persist (F3).
|
|
180
|
+
*/
|
|
181
|
+
private handleUpdateAnnotation;
|
|
182
|
+
/**
|
|
183
|
+
* Handles Annotorious's (async) `deleteAnnotation` lifecycle event. Echoes
|
|
184
|
+
* from our own `clearAnnotations()` teardown are marked and consumed here so
|
|
185
|
+
* they never reach the adapter. A genuine Annotorious-originated deletion of
|
|
186
|
+
* a persisted annotation syncs the adapter, cache, and selection state (F27).
|
|
187
|
+
*/
|
|
188
|
+
private handleDeleteAnnotation;
|
|
189
|
+
/**
|
|
190
|
+
* Persist an annotation that arrived in **image space** (Annotorious event
|
|
191
|
+
* payloads, geometry edits). Transforms to canvas space, forces the target
|
|
192
|
+
* source, applies beforeSave, then persists.
|
|
193
|
+
*/
|
|
194
|
+
saveAnnotation(annotation: any): Promise<boolean>;
|
|
195
|
+
/**
|
|
196
|
+
* Convert an image-space annotation from Annotorious into canvas space for
|
|
197
|
+
* the cache/panel/store. An actively-edited point takes the lossless
|
|
198
|
+
* origin-aware path (§3.2); everything else scales normally, with legacy
|
|
199
|
+
* fragment-centre read-compat via `toPointSelectorTarget` (D3).
|
|
200
|
+
*/
|
|
201
|
+
private annotationToCanvasSpace;
|
|
202
|
+
/**
|
|
203
|
+
* Persist a freshly created annotation that is already in **canvas space**
|
|
204
|
+
* (the output of prepareAnnotation). Skips the image→canvas re-transform;
|
|
205
|
+
* beforeSave still runs last. Returns whether the write succeeded.
|
|
206
|
+
*/
|
|
207
|
+
private persistCreate;
|
|
208
|
+
/**
|
|
209
|
+
* Run a store mutation whose resulting (asynchronous) Annotorious lifecycle
|
|
210
|
+
* echo for `annotationId` must not be re-persisted.
|
|
211
|
+
*
|
|
212
|
+
* NOTE: Annotorious v3 dispatches lifecycle events via `setTimeout(…, 1)`
|
|
213
|
+
* (verified in @annotorious/core@3.7.19), so a synchronous boolean guard as
|
|
214
|
+
* originally planned would already be reset by the time the echo fires. We
|
|
215
|
+
* mark the id and let the echo consume the mark instead. A body-change
|
|
216
|
+
* update emits exactly one echo, so one mark == one consumed echo.
|
|
217
|
+
*/
|
|
218
|
+
private withSuppressedEcho;
|
|
219
|
+
/** Record one more expected self-triggered echo for this id. */
|
|
220
|
+
private markSuppressedEcho;
|
|
221
|
+
/**
|
|
222
|
+
* Consume one expected echo for this id. Returns true (and decrements the
|
|
223
|
+
* count, deleting the entry at zero) while echoes remain outstanding, so two
|
|
224
|
+
* concurrent echoes — e.g. a body-save update echo and a teardown delete echo
|
|
225
|
+
* for the same id — are each matched and neither leaks (F3/F27).
|
|
226
|
+
*/
|
|
227
|
+
private consumeSuppressedEcho;
|
|
228
|
+
/**
|
|
229
|
+
* Always overwrite `target.source` with the current canvas id. Within this
|
|
230
|
+
* plugin the target is by definition the canvas the user is annotating, so
|
|
231
|
+
* overwriting is safe regardless of what the Annotorious W3C serializer
|
|
232
|
+
* stamped at init time (see F1). All other target fields are preserved.
|
|
233
|
+
*/
|
|
234
|
+
private forceTargetSource;
|
|
235
|
+
private prepareAnnotation;
|
|
236
|
+
private applyBeforeSave;
|
|
237
|
+
private getRuntimeContext;
|
|
238
|
+
private notifyExtensionSelectionChange;
|
|
239
|
+
private hydrateAnnotation;
|
|
240
|
+
deleteAnnotation(annotationId: string): Promise<boolean>;
|
|
241
|
+
updateAnnotationBodies(annotationId: string, bodies: unknown[] | unknown): Promise<boolean>;
|
|
242
|
+
/**
|
|
243
|
+
* React to the store swapping a freshly-created annotation onto its
|
|
244
|
+
* server-assigned id (F5). If that annotation is the one currently open for
|
|
245
|
+
* editing, re-open it under the canonical id: this re-adds it to Annotorious,
|
|
246
|
+
* reselects it, and re-emits the active-edit-id signal with the new id.
|
|
247
|
+
*/
|
|
248
|
+
private handleIdReconciled;
|
|
249
|
+
/**
|
|
250
|
+
* Reconcile the open Annotorious editing session with an undo/redo replay
|
|
251
|
+
* (F6). Only the annotation currently open is affected: if the replay left
|
|
252
|
+
* it in storage, re-open it so its geometry and body reflect the restored
|
|
253
|
+
* state; if the replay removed it (e.g. undoing the create of the annotation
|
|
254
|
+
* being edited), tear the editing session down.
|
|
255
|
+
*/
|
|
256
|
+
private handleReplay;
|
|
257
|
+
selectAnnotationById(annotationId: string): Promise<void>;
|
|
258
|
+
cancelSelection(): void;
|
|
259
|
+
destroy(): void;
|
|
260
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import type { AnnotationEditorConfig, AnnotationPersistenceOp } from './types';
|
|
2
|
+
import type { W3CAnnotation } from './adapters/types';
|
|
3
|
+
/**
|
|
4
|
+
* The per-viewer display-sync surface the store writes to (ADR 0001, amended;
|
|
5
|
+
* ADR 0007). Structurally satisfied by `ViewerState`, injected via
|
|
6
|
+
* {@link AnnotationStore.setDisplayState} so the store never imports the
|
|
7
|
+
* page-shared manifest cache — annotations are scoped to the owning viewer.
|
|
8
|
+
*/
|
|
9
|
+
export interface AnnotationDisplayState {
|
|
10
|
+
setUserAnnotations(manifestId: string, canvasId: string, annotations: W3CAnnotation[]): void;
|
|
11
|
+
clearUserAnnotations(manifestId: string, canvasId: string): void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Plugin-internal persistence core. Owns everything the manager used to do "to
|
|
15
|
+
* storage": the annotation cache, per-annotation hydration state, create-vs-update
|
|
16
|
+
* resolution, the per-id save queue, the load-race token, and the raw adapter.
|
|
17
|
+
*
|
|
18
|
+
* `AnnotationManager` talks only to this store for persistence and keeps the
|
|
19
|
+
* Annotorious/OpenSeadragon mechanics (selection, tools, coordinate transforms).
|
|
20
|
+
* The store deals exclusively in **canvas-space** W3C annotations — transforms
|
|
21
|
+
* live at the manager/store boundary.
|
|
22
|
+
*
|
|
23
|
+
* This class is a refactor of code previously inlined in `AnnotationManager`
|
|
24
|
+
* (issues 01–04); behavior is intentionally unchanged (issue 05).
|
|
25
|
+
*/
|
|
26
|
+
export declare class AnnotationStore {
|
|
27
|
+
private static readonly W3C_CONTEXT;
|
|
28
|
+
private static readonly DEFAULT_MOTIVATION;
|
|
29
|
+
private adapter;
|
|
30
|
+
private config;
|
|
31
|
+
/**
|
|
32
|
+
* Notified when a `create` reconciles an annotation onto a server-assigned
|
|
33
|
+
* id (F5), so the manager can re-open it in Annotorious under the canonical
|
|
34
|
+
* id and re-emit the active-edit-id signal. Set by the manager; the loader
|
|
35
|
+
* leaves it unset.
|
|
36
|
+
*/
|
|
37
|
+
onReconcileId?: (oldId: string, canonical: W3CAnnotation) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Notified after an undo/redo replay so the manager can reconcile the open
|
|
40
|
+
* Annotorious editing session with the new storage state (F6): `annotation`
|
|
41
|
+
* is the annotation now in the cache under `affectedId`, or `null` when the
|
|
42
|
+
* replay removed it. Set by the manager; the loader leaves it unset.
|
|
43
|
+
*/
|
|
44
|
+
onReplay?: (affectedId: string, annotation: W3CAnnotation | null) => void;
|
|
45
|
+
private manifestId;
|
|
46
|
+
private canvasId;
|
|
47
|
+
private displayState;
|
|
48
|
+
private persistedAnnotations;
|
|
49
|
+
private hydrationState;
|
|
50
|
+
private saveQueue;
|
|
51
|
+
private loadSequence;
|
|
52
|
+
private injectedCanvases;
|
|
53
|
+
private static readonly UNDO_DEPTH;
|
|
54
|
+
private undoStack;
|
|
55
|
+
private redoStack;
|
|
56
|
+
private replaying;
|
|
57
|
+
private lastCreateCanonical;
|
|
58
|
+
private _canUndo;
|
|
59
|
+
private _canRedo;
|
|
60
|
+
private _panelError;
|
|
61
|
+
constructor(config: AnnotationEditorConfig);
|
|
62
|
+
/**
|
|
63
|
+
* Point display sync at the owning viewer's display state (ADR 0001,
|
|
64
|
+
* amended). Called by the loader (and the manager) when the viewer is known.
|
|
65
|
+
* Idempotent — re-attaching the same viewer is harmless.
|
|
66
|
+
*/
|
|
67
|
+
setDisplayState(displayState: AnnotationDisplayState | null): void;
|
|
68
|
+
get currentManifestId(): string | null;
|
|
69
|
+
get currentCanvasId(): string | null;
|
|
70
|
+
/** Both a manifest and a canvas are known — persistence can run. */
|
|
71
|
+
get ready(): boolean;
|
|
72
|
+
/** The adapter can lazily hydrate skeleton bodies. */
|
|
73
|
+
get hydrateSupported(): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* The most recent persistence failure the host didn't handle, for the
|
|
76
|
+
* panel's default error line. `null` when there's nothing to show or a host
|
|
77
|
+
* `onPersistenceError` handler took ownership of the failure (F20).
|
|
78
|
+
*/
|
|
79
|
+
get panelError(): {
|
|
80
|
+
op: AnnotationPersistenceOp;
|
|
81
|
+
annotationId?: string;
|
|
82
|
+
} | null;
|
|
83
|
+
/** Dismiss the default error line (user clicked the ✕). */
|
|
84
|
+
dismissError(): void;
|
|
85
|
+
/** An undo is available (reactive; drives the panel button). */
|
|
86
|
+
get canUndo(): boolean;
|
|
87
|
+
/** A redo is available (reactive; drives the panel button). */
|
|
88
|
+
get canRedo(): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Point the store at a canvas and drop the previous canvas's cache. Does
|
|
91
|
+
* not load — the manager drives load timing (and, from issue 06, display
|
|
92
|
+
* sync) around this call.
|
|
93
|
+
*/
|
|
94
|
+
setCanvas(manifestId: string | null, canvasId: string | null): void;
|
|
95
|
+
get(id: string): W3CAnnotation | null;
|
|
96
|
+
has(id: string): boolean;
|
|
97
|
+
isSkeleton(id: string): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Load the current canvas's annotations from the adapter into the cache.
|
|
100
|
+
* A newer load (e.g. a canvas change) started while we awaited discards this
|
|
101
|
+
* stale result so it can't clobber the current canvas (F14).
|
|
102
|
+
*/
|
|
103
|
+
load(): Promise<W3CAnnotation[]>;
|
|
104
|
+
/**
|
|
105
|
+
* Single chokepoint for adapter writes. Decides create-vs-update from the
|
|
106
|
+
* in-memory cache (no per-save adapter.load round-trip — F4), serializes
|
|
107
|
+
* writes per annotation id (F4), and updates the cache only after the
|
|
108
|
+
* adapter call resolves.
|
|
109
|
+
*
|
|
110
|
+
* On create the store stamps a complete W3C/IIIF annotation (F18) and, if the
|
|
111
|
+
* adapter returns a canonical annotation or id, reconciles the cache/display
|
|
112
|
+
* onto the server-assigned id and notifies the manager (F5). On update it
|
|
113
|
+
* refreshes `modified` and adopts a server-normalized copy when returned.
|
|
114
|
+
*
|
|
115
|
+
* Cache and display are only advanced *after* the adapter resolves, so a
|
|
116
|
+
* rejected write leaves both at their pre-operation state — the rollback the
|
|
117
|
+
* manager relies on to re-signal selection (F20). Returns `true` on success,
|
|
118
|
+
* `false` when the adapter rejected (the failure has been reported).
|
|
119
|
+
*/
|
|
120
|
+
persist(annotation: W3CAnnotation): Promise<boolean>;
|
|
121
|
+
/**
|
|
122
|
+
* Delete an annotation through the adapter and drop it from the cache.
|
|
123
|
+
* Cache/display are only advanced after the adapter resolves, so a rejected
|
|
124
|
+
* delete leaves the entry (and its overlay) intact — the "restore the entry"
|
|
125
|
+
* rollback (F20). Returns `true` on success, `false` when the adapter
|
|
126
|
+
* rejected (the failure has been reported).
|
|
127
|
+
*/
|
|
128
|
+
delete(id: string): Promise<boolean>;
|
|
129
|
+
/**
|
|
130
|
+
* Fetch a skeleton annotation's full body from the adapter and cache it.
|
|
131
|
+
* Returns the full annotation, or null when there is nothing to do (no
|
|
132
|
+
* hydrate support), the fetch came back empty, the canvas changed while
|
|
133
|
+
* awaiting (F14), or `shouldApply` vetoes committing the result (the manager
|
|
134
|
+
* uses this to bail if the annotation is no longer being edited).
|
|
135
|
+
*/
|
|
136
|
+
hydrate(id: string, shouldApply?: () => boolean): Promise<W3CAnnotation | null>;
|
|
137
|
+
/**
|
|
138
|
+
* Resolve the full annotation for editing: return the cached copy when it is
|
|
139
|
+
* already full, hydrate it when the cache holds a skeleton, or reload the
|
|
140
|
+
* canvas when the id isn't cached at all.
|
|
141
|
+
*/
|
|
142
|
+
resolve(id: string): Promise<W3CAnnotation | null>;
|
|
143
|
+
/**
|
|
144
|
+
* Reverse the most recent persisted operation by replaying its inverse
|
|
145
|
+
* through the normal write paths (F6): a `create` is deleted, an `update`
|
|
146
|
+
* restores the previous cached copy, a `delete` re-creates the removed copy.
|
|
147
|
+
* The reversed operation moves to the redo stack. A failed replay (the
|
|
148
|
+
* adapter rejected — the error surface fires) leaves the operation on the
|
|
149
|
+
* undo stack so it is never lost.
|
|
150
|
+
*/
|
|
151
|
+
undo(): Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* Re-apply the most recently undone operation through the normal write paths
|
|
154
|
+
* (F6). The re-applied operation moves back to the undo stack. A failed
|
|
155
|
+
* replay leaves the operation on the redo stack so it is never lost.
|
|
156
|
+
*/
|
|
157
|
+
redo(): Promise<void>;
|
|
158
|
+
destroy(): void;
|
|
159
|
+
/**
|
|
160
|
+
* Record a just-committed forward operation for undo (F6). A normal user
|
|
161
|
+
* operation becomes undoable and invalidates any redo path; while replaying
|
|
162
|
+
* an undo/redo this is suppressed (the replay pushes onto the opposite stack
|
|
163
|
+
* itself).
|
|
164
|
+
*/
|
|
165
|
+
private recordForward;
|
|
166
|
+
private pushUndo;
|
|
167
|
+
private pushRedo;
|
|
168
|
+
/** Return a failed-replay op to the undo stack so it is never lost (F6). */
|
|
169
|
+
private restoreUndo;
|
|
170
|
+
/** Return a failed-replay op to the redo stack so it is never lost (F6). */
|
|
171
|
+
private restoreRedo;
|
|
172
|
+
private pushCapped;
|
|
173
|
+
private clearHistory;
|
|
174
|
+
private refreshUndoRedoFlags;
|
|
175
|
+
/**
|
|
176
|
+
* Surface a failed persistence operation (F20). A host `onPersistenceError`
|
|
177
|
+
* handler takes full ownership of the failure (it gets a `retry` handle);
|
|
178
|
+
* without one, the store logs and records a dismissible panel error so the
|
|
179
|
+
* failure is never invisible. The cache/display rollback has already
|
|
180
|
+
* happened at the call sites (writes only commit on success).
|
|
181
|
+
*/
|
|
182
|
+
private reportError;
|
|
183
|
+
/**
|
|
184
|
+
* Commit a created annotation to the cache under its canonical id. When the
|
|
185
|
+
* adapter returns a server-assigned annotation or id string, the cache key is
|
|
186
|
+
* swapped from the local id to the canonical one, and the manager is notified
|
|
187
|
+
* so it can re-open the annotation under the new id (F5).
|
|
188
|
+
*/
|
|
189
|
+
private reconcileCreate;
|
|
190
|
+
/**
|
|
191
|
+
* Stamp a complete, valid W3C/IIIF annotation before create without
|
|
192
|
+
* clobbering host-provided values (F18). `extension.beforeSave` has already
|
|
193
|
+
* run (in the manager) and therefore still wins — stamping only fills gaps.
|
|
194
|
+
*/
|
|
195
|
+
private stampForCreate;
|
|
196
|
+
/** Refresh `modified` on an updated annotation (F18). */
|
|
197
|
+
private stampForUpdate;
|
|
198
|
+
/**
|
|
199
|
+
* Push the current canvas's cached annotations into the owning viewer's
|
|
200
|
+
* display state so the read-only overlay reflects storage. The plugin owns
|
|
201
|
+
* this — adapters are pure storage (F10). Records the canvas key so
|
|
202
|
+
* `destroy()` can clear it.
|
|
203
|
+
*/
|
|
204
|
+
private syncDisplay;
|
|
205
|
+
private cachePersistedAnnotations;
|
|
206
|
+
/**
|
|
207
|
+
* Strip plugin-internal bookkeeping markers so they never reach an adapter,
|
|
208
|
+
* the cache, or the panel.
|
|
209
|
+
*/
|
|
210
|
+
private stripInternalMarkers;
|
|
211
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { W3CAnnotation, AdapterLoadResult, AnnotationStorageAdapter } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* LocalStorage-based annotation adapter — the reference minimal adapter.
|
|
4
|
+
*
|
|
5
|
+
* It is pure storage: `localStorage` reads and writes, nothing more. Display
|
|
6
|
+
* sync (to the owning viewer's state), caching, id reconciliation, and error
|
|
7
|
+
* handling are all owned by the plugin's `AnnotationStore`, so a custom adapter
|
|
8
|
+
* only needs to implement these few storage methods (F10). This is the shape
|
|
9
|
+
* every adapter should aim for.
|
|
10
|
+
*
|
|
11
|
+
* ── LocalStorage namespace (FROZEN) ────────────────────────────────────────
|
|
12
|
+
* 1.0 writes under a new, stable, versioned, package-qualified key:
|
|
13
|
+
*
|
|
14
|
+
* @triiiceratops/plugin-annotation-editor:v1:<manifestId>:<canvasId>
|
|
15
|
+
*
|
|
16
|
+
* This key is FROZEN — it is the stable 1.0 contract and must not change without
|
|
17
|
+
* a `:v2:` bump. The prerelease adapter used a different, unversioned key
|
|
18
|
+
* (`triiiceratops:annotations:<manifestId>:<canvasId>`). Per SPEC, RC-era data is
|
|
19
|
+
* neither read, migrated, deleted, nor overwritten: this adapter never touches
|
|
20
|
+
* the old namespace, so prerelease keys are left byte-identical and untouched
|
|
21
|
+
* (they are disposable RC data). This is local/single-browser storage — not a
|
|
22
|
+
* production multi-user adapter.
|
|
23
|
+
*/
|
|
24
|
+
export declare class LocalStorageAdapter implements AnnotationStorageAdapter {
|
|
25
|
+
readonly id = "localStorage";
|
|
26
|
+
readonly name = "Local Storage";
|
|
27
|
+
/** The frozen 1.0 namespace prefix (see the class doc). */
|
|
28
|
+
private static readonly KEY_PREFIX;
|
|
29
|
+
private storageKey;
|
|
30
|
+
load(manifestId: string, canvasId: string): Promise<AdapterLoadResult[]>;
|
|
31
|
+
hydrate(manifestId: string, canvasId: string, annotationId: string): Promise<AdapterLoadResult | null>;
|
|
32
|
+
create(manifestId: string, canvasId: string, annotation: W3CAnnotation): Promise<void>;
|
|
33
|
+
update(manifestId: string, canvasId: string, annotation: W3CAnnotation): Promise<void>;
|
|
34
|
+
delete(manifestId: string, canvasId: string, annotationId: string): Promise<void>;
|
|
35
|
+
private loadFromStorage;
|
|
36
|
+
private saveToStorage;
|
|
37
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { W3CAnnotationBody, AnnotationStorageAdapter } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* IIIF/W3C media-fragment selector (`xywh=…`, `t=…`). The value carries the
|
|
4
|
+
* fragment expression.
|
|
5
|
+
*/
|
|
6
|
+
export interface FragmentSelector {
|
|
7
|
+
type: 'FragmentSelector';
|
|
8
|
+
conformsTo?: string;
|
|
9
|
+
value: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
/** IIIF `PointSelector` — a single canvas-space point (integer px per D2). */
|
|
13
|
+
export interface PointSelector {
|
|
14
|
+
type: 'PointSelector';
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
/** W3C `SvgSelector` — an SVG shape (polygon, path, …) as its `value`. */
|
|
20
|
+
export interface SvgSelector {
|
|
21
|
+
type: 'SvgSelector';
|
|
22
|
+
value: string;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Escape hatch for selector types the plugin doesn't model explicitly (e.g. a
|
|
27
|
+
* `RangeSelector`, or a host-specific selector). Keeps the union open so a
|
|
28
|
+
* round-trip never narrows away an unknown selector.
|
|
29
|
+
*/
|
|
30
|
+
export interface UnknownSelector {
|
|
31
|
+
type: string;
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
}
|
|
34
|
+
/** Open selector union — never narrow away unknown selector types. */
|
|
35
|
+
export type W3CSelector = FragmentSelector | PointSelector | SvgSelector | UnknownSelector;
|
|
36
|
+
/** W3C Web Annotation target (a `SpecificResource` pointing at a canvas). */
|
|
37
|
+
export interface W3CTarget {
|
|
38
|
+
type?: string;
|
|
39
|
+
source: string;
|
|
40
|
+
selector?: W3CSelector | W3CSelector[];
|
|
41
|
+
[key: string]: unknown;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* W3C Web Annotation structure. Generic over the body shape so a host with a
|
|
45
|
+
* structured/custom body gets compiler help end-to-end; `TBody` defaults to the
|
|
46
|
+
* built-in {@link W3CAnnotationBody}. The index signature is an intentional
|
|
47
|
+
* escape hatch so host-specific fields survive a load → edit → save round-trip
|
|
48
|
+
* instead of being dropped by the type.
|
|
49
|
+
*/
|
|
50
|
+
export interface W3CAnnotation<TBody = W3CAnnotationBody> {
|
|
51
|
+
'@context'?: string | string[];
|
|
52
|
+
id: string;
|
|
53
|
+
type: 'Annotation';
|
|
54
|
+
body?: TBody | TBody[];
|
|
55
|
+
target: W3CTarget | W3CTarget[];
|
|
56
|
+
creator?: {
|
|
57
|
+
id?: string;
|
|
58
|
+
name?: string;
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
};
|
|
61
|
+
created?: string;
|
|
62
|
+
modified?: string;
|
|
63
|
+
motivation?: string | string[];
|
|
64
|
+
[key: string]: unknown;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Shape an adapter's `load()`/`hydrate()` may return. Beyond a stored
|
|
68
|
+
* annotation it may carry the internal skeleton markers the plugin reads exactly
|
|
69
|
+
* once and strips before anything enters the cache or Annotorious (ticket 03):
|
|
70
|
+
* `__fullBodyLoaded: false` signals a skeleton whose body must be fetched via
|
|
71
|
+
* `hydrate()`. These markers are NOT part of the stored annotation contract —
|
|
72
|
+
* they never round-trip — so they live here rather than on {@link W3CAnnotation}.
|
|
73
|
+
*/
|
|
74
|
+
export type AdapterLoadResult<TBody = W3CAnnotationBody> = W3CAnnotation<TBody> & {
|
|
75
|
+
__fullBodyLoaded?: boolean;
|
|
76
|
+
__bodyPreview?: string | null;
|
|
77
|
+
};
|
|
78
|
+
export type { AnnotationStorageAdapter };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LocaleCatalog } from '@triiiceratops/plugin-sdk';
|
|
2
|
+
/**
|
|
3
|
+
* The plugin's package-owned localization catalog (CONTEXT.md **Active locale**).
|
|
4
|
+
* These strings previously lived in core's `messages/en.json` / `messages/de.json`;
|
|
5
|
+
* migrating the plugin out of core moves them here so the catalog ships with (and
|
|
6
|
+
* evolves with) the plugin, and core's catalogs carry no plugin keys. `en` is the
|
|
7
|
+
* required fallback; a missing key resolves to `en` and then to the key itself.
|
|
8
|
+
*/
|
|
9
|
+
export declare const catalog: LocaleCatalog;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { getCanvasId } from 'triiiceratops/image-export';
|
|
2
|
+
export type ChoiceGroup = {
|
|
3
|
+
canvasId: string;
|
|
4
|
+
choices: any[];
|
|
5
|
+
selectedChoiceId: string | undefined;
|
|
6
|
+
side: 'left' | 'right';
|
|
7
|
+
};
|
|
8
|
+
export type VisibleCanvasEntry = {
|
|
9
|
+
canvasId: string;
|
|
10
|
+
canvas: any;
|
|
11
|
+
};
|
|
12
|
+
export type PagedCanvasGroup = {
|
|
13
|
+
startIndex: number;
|
|
14
|
+
endIndex: number;
|
|
15
|
+
entries: VisibleCanvasEntry[];
|
|
16
|
+
};
|
|
17
|
+
export type CanvasNavDirection = 'previous' | 'next';
|
|
18
|
+
export type CanvasNavIcon = 'left' | 'right' | 'up' | 'down';
|
|
19
|
+
export type CanvasNavLayout = {
|
|
20
|
+
leftButton: CanvasNavDirection;
|
|
21
|
+
rightButton: CanvasNavDirection;
|
|
22
|
+
leftIcon: CanvasNavIcon;
|
|
23
|
+
rightIcon: CanvasNavIcon;
|
|
24
|
+
};
|
|
25
|
+
export declare function shouldUseAbbreviatedChoiceLabels(viewingMode: ViewingMode, visibleChoiceGroups: ChoiceGroup[]): boolean;
|
|
26
|
+
export declare function getCanvasNavLayout(viewingDirection: ViewingDirection): CanvasNavLayout;
|
|
27
|
+
type ViewingMode = 'individuals' | 'paged' | 'continuous';
|
|
28
|
+
type ViewingDirection = 'left-to-right' | 'right-to-left' | 'top-to-bottom' | 'bottom-to-top';
|
|
29
|
+
type VisibleChoiceGroupArgs = {
|
|
30
|
+
canvases: any[];
|
|
31
|
+
currentCanvasId: string | null;
|
|
32
|
+
currentCanvasIndex: number;
|
|
33
|
+
viewingMode: ViewingMode;
|
|
34
|
+
pagedOffset: number;
|
|
35
|
+
viewingDirection: ViewingDirection;
|
|
36
|
+
getSelectedChoice: (canvasId: string) => string | undefined;
|
|
37
|
+
};
|
|
38
|
+
export { getCanvasId };
|
|
39
|
+
export declare function getCanvasChoices(canvas: any): any;
|
|
40
|
+
export declare function getCanvasBehaviors(canvas: any): string[];
|
|
41
|
+
export declare function getPagedCanvasGroups(canvases: any[], pagedOffset: number): PagedCanvasGroup[];
|
|
42
|
+
export declare function getVisibleCanvasEntries({ canvases, currentCanvasId, currentCanvasIndex, viewingMode, pagedOffset, }: Omit<VisibleChoiceGroupArgs, 'viewingDirection' | 'getSelectedChoice'>): VisibleCanvasEntry[];
|
|
43
|
+
export declare function getVisibleChoiceGroups({ canvases, currentCanvasId, currentCanvasIndex, viewingMode, pagedOffset, viewingDirection, getSelectedChoice, }: VisibleChoiceGroupArgs): ChoiceGroup[];
|