@annotorious/core 3.0.0-rc.3 → 3.0.0-rc.30

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 (94) hide show
  1. package/dist/annotorious-core.es.js +784 -0
  2. package/dist/annotorious-core.es.js.map +1 -0
  3. package/{src/index.ts → dist/index.d.ts} +1 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/lifecycle/Lifecycle.d.ts +11 -0
  6. package/dist/lifecycle/Lifecycle.d.ts.map +1 -0
  7. package/dist/lifecycle/LifecycleEvents.d.ts +13 -0
  8. package/dist/lifecycle/LifecycleEvents.d.ts.map +1 -0
  9. package/dist/lifecycle/index.d.ts +3 -0
  10. package/dist/lifecycle/index.d.ts.map +1 -0
  11. package/dist/model/Annotation.d.ts +34 -0
  12. package/dist/model/Annotation.d.ts.map +1 -0
  13. package/dist/model/AnnotationState.d.ts +8 -0
  14. package/dist/model/AnnotationState.d.ts.map +1 -0
  15. package/dist/model/Annotator.d.ts +65 -0
  16. package/dist/model/Annotator.d.ts.map +1 -0
  17. package/dist/model/DrawingStyle.d.ts +17 -0
  18. package/dist/model/DrawingStyle.d.ts.map +1 -0
  19. package/dist/model/Filter.d.ts +4 -0
  20. package/dist/model/Filter.d.ts.map +1 -0
  21. package/dist/model/FormatAdapter.d.ts +16 -0
  22. package/dist/model/FormatAdapter.d.ts.map +1 -0
  23. package/dist/model/User.d.ts +11 -0
  24. package/dist/model/User.d.ts.map +1 -0
  25. package/dist/model/W3CAnnotation.d.ts +43 -0
  26. package/dist/model/W3CAnnotation.d.ts.map +1 -0
  27. package/{src/model/index.ts → dist/model/index.d.ts} +3 -1
  28. package/dist/model/index.d.ts.map +1 -0
  29. package/dist/presence/Appearance.d.ts +6 -0
  30. package/dist/presence/Appearance.d.ts.map +1 -0
  31. package/dist/presence/AppearanceProvider.d.ts +17 -0
  32. package/dist/presence/AppearanceProvider.d.ts.map +1 -0
  33. package/dist/presence/ColorPalette.d.ts +3 -0
  34. package/dist/presence/ColorPalette.d.ts.map +1 -0
  35. package/dist/presence/PresenceEvents.d.ts +7 -0
  36. package/dist/presence/PresenceEvents.d.ts.map +1 -0
  37. package/dist/presence/PresenceProvider.d.ts +6 -0
  38. package/dist/presence/PresenceProvider.d.ts.map +1 -0
  39. package/dist/presence/PresenceState.d.ts +19 -0
  40. package/dist/presence/PresenceState.d.ts.map +1 -0
  41. package/dist/presence/PresentUser.d.ts +8 -0
  42. package/dist/presence/PresentUser.d.ts.map +1 -0
  43. package/{src/presence/index.ts → dist/presence/index.d.ts} +2 -1
  44. package/dist/presence/index.d.ts.map +1 -0
  45. package/dist/state/Hover.d.ts +10 -0
  46. package/dist/state/Hover.d.ts.map +1 -0
  47. package/dist/state/Selection.d.ts +31 -0
  48. package/dist/state/Selection.d.ts.map +1 -0
  49. package/dist/state/Store.d.ts +35 -0
  50. package/dist/state/Store.d.ts.map +1 -0
  51. package/dist/state/StoreObserver.d.ts +58 -0
  52. package/dist/state/StoreObserver.d.ts.map +1 -0
  53. package/dist/state/SvelteStore.d.ts +23 -0
  54. package/dist/state/SvelteStore.d.ts.map +1 -0
  55. package/dist/state/UndoStack.d.ts +19 -0
  56. package/dist/state/UndoStack.d.ts.map +1 -0
  57. package/dist/state/Viewport.d.ts +6 -0
  58. package/dist/state/Viewport.d.ts.map +1 -0
  59. package/{src/state/index.ts → dist/state/index.d.ts} +2 -1
  60. package/dist/state/index.d.ts.map +1 -0
  61. package/dist/utils/annotationUtils.d.ts +12 -0
  62. package/dist/utils/annotationUtils.d.ts.map +1 -0
  63. package/dist/utils/diffAnnotations.d.ts +5 -0
  64. package/dist/utils/diffAnnotations.d.ts.map +1 -0
  65. package/{src/utils/index.ts → dist/utils/index.d.ts} +1 -1
  66. package/dist/utils/index.d.ts.map +1 -0
  67. package/package.json +20 -17
  68. package/src/lifecycle/Lifecycle.ts +0 -197
  69. package/src/lifecycle/LifecycleEvents.ts +0 -21
  70. package/src/lifecycle/index.ts +0 -2
  71. package/src/model/Annotation.ts +0 -73
  72. package/src/model/Annotator.ts +0 -161
  73. package/src/model/DrawingStyle.ts +0 -19
  74. package/src/model/Filter.ts +0 -3
  75. package/src/model/FormatAdapter.ts +0 -36
  76. package/src/model/User.ts +0 -19
  77. package/src/model/W3CAnnotation.ts +0 -118
  78. package/src/presence/Appearance.ts +0 -9
  79. package/src/presence/AppearanceProvider.ts +0 -53
  80. package/src/presence/ColorPalette.ts +0 -14
  81. package/src/presence/PresenceEvents.ts +0 -9
  82. package/src/presence/PresenceProvider.ts +0 -7
  83. package/src/presence/PresenceState.ts +0 -145
  84. package/src/presence/PresentUser.ts +0 -10
  85. package/src/state/Hover.ts +0 -34
  86. package/src/state/Selection.ts +0 -113
  87. package/src/state/Store.ts +0 -344
  88. package/src/state/StoreObserver.ts +0 -194
  89. package/src/state/SvelteStore.ts +0 -54
  90. package/src/state/UndoStack.ts +0 -112
  91. package/src/state/Viewport.ts +0 -14
  92. package/src/utils/annotationUtils.ts +0 -33
  93. package/src/utils/diffAnnotations.ts +0 -39
  94. package/src/vite-env.d.ts +0 -1
@@ -1,10 +0,0 @@
1
- import type { User } from '../model';
2
- import type { Appearance } from './Appearance';
3
-
4
- export interface PresentUser extends User {
5
-
6
- presenceKey: string;
7
-
8
- appearance: Appearance
9
-
10
- }
@@ -1,34 +0,0 @@
1
- import { writable } from 'svelte/store';
2
- import type { Annotation } from '../model';
3
- import type { Store } from './Store';
4
-
5
- export type HoverState<T extends Annotation> = ReturnType<typeof createHoverState<T>>;
6
-
7
- export const createHoverState = <T extends Annotation>(store: Store<T>) => {
8
-
9
- const { subscribe, set } = writable<string>(null);
10
-
11
- let currentHover: string = null;
12
-
13
- subscribe(updated => currentHover = updated);
14
-
15
- // Track store delete and update events
16
- store.observe(( { changes }) => {
17
- if (currentHover) {
18
- const isDeleted = changes.deleted.some(a => a.id === currentHover);
19
- if (isDeleted)
20
- set(null);
21
-
22
- const updated = changes.updated.find(({ oldValue }) => oldValue.id === currentHover);
23
- if (updated)
24
- set(updated.newValue.id);
25
- }
26
- });
27
-
28
- return {
29
- get current() { return currentHover },
30
- subscribe,
31
- set
32
- };
33
-
34
- }
@@ -1,113 +0,0 @@
1
- import { writable } from 'svelte/store';
2
- import type { Annotation } from '../model';
3
- import type { Store } from './Store';
4
-
5
- export type Selection = {
6
-
7
- selected: { id: string, editable?: boolean }[],
8
-
9
- pointerEvent?: PointerEvent;
10
-
11
- }
12
-
13
- export type SelectionState<T extends Annotation> = ReturnType<typeof createSelectionState<T>>;
14
-
15
- export enum PointerSelectAction {
16
-
17
- EDIT = 'EDIT', // Make annotation target(s) editable on pointer select
18
-
19
- SELECT = 'SELECT', // Just select, but don't make editable
20
-
21
- NONE = 'NONE' // Click won't select - annotation is completely inert
22
-
23
- }
24
-
25
- const EMPTY: Selection = { selected: [] };
26
-
27
- export const createSelectionState = <T extends Annotation>(
28
- store: Store<T>,
29
- selectAction: PointerSelectAction | ((a: Annotation) => PointerSelectAction) = PointerSelectAction.EDIT
30
- ) => {
31
- const { subscribe, set } = writable<Selection>(EMPTY);
32
-
33
- let currentSelection: Selection = EMPTY;
34
-
35
- subscribe(updated => currentSelection = updated);
36
-
37
- const clear = () => set(EMPTY);
38
-
39
- const isEmpty = () => currentSelection.selected?.length === 0;
40
-
41
- const isSelected = (annotationOrId: T | string) => {
42
- if (currentSelection.selected.length === 0)
43
- return false;
44
-
45
- const id = typeof annotationOrId === 'string' ? annotationOrId : annotationOrId.id;
46
- return currentSelection.selected.some(i => i.id === id);
47
- }
48
-
49
- // TODO enable CTRL select
50
- const clickSelect = (id: string, pointerEvent: PointerEvent) => {
51
- const annotation = store.getAnnotation(id);
52
- if (annotation) {
53
- const action = onPointerSelect(annotation, selectAction);
54
- if (action === PointerSelectAction.EDIT)
55
- set({ selected: [{ id, editable: true }], pointerEvent });
56
- else if (action === PointerSelectAction.SELECT)
57
- set({ selected: [{ id }], pointerEvent });
58
- else
59
- set({ selected: [], pointerEvent });
60
- } else {
61
- console.warn('Invalid selection: ' + id);
62
- }
63
- }
64
-
65
- const setSelected = (idOrIds: string | string[], editable: boolean = true) => {
66
- const ids = Array.isArray(idOrIds) ? idOrIds : [idOrIds];
67
-
68
- // Remove invalid
69
- const annotations =
70
- ids.map(id => store.getAnnotation(id)).filter(a => a);
71
-
72
- set({ selected: annotations.map(({ id }) => ({ id, editable })) });
73
-
74
- if (annotations.length !== ids.length)
75
- console.warn('Invalid selection', idOrIds);
76
- }
77
-
78
- const removeFromSelection = (ids: string[]) => {
79
- if (currentSelection.selected.length === 0)
80
- return false;
81
-
82
- const { selected } = currentSelection;
83
-
84
- // Checks which of the given annotations are actually in the selection
85
- const toRemove = selected.filter(({ id }) => ids.includes(id))
86
-
87
- if (toRemove.length > 0)
88
- set({ selected: selected.filter(({ id }) => !ids.includes(id)) });
89
- }
90
-
91
- // Track store delete and update events
92
- store.observe(({ changes }) =>
93
- removeFromSelection(changes.deleted.map(a => a.id)));
94
-
95
- return {
96
- clear,
97
- clickSelect,
98
- get selected() { return currentSelection ? [...currentSelection.selected ] : null},
99
- get pointerEvent() { return currentSelection ? currentSelection.pointerEvent : null },
100
- isEmpty,
101
- isSelected,
102
- setSelected,
103
- subscribe
104
- };
105
-
106
- }
107
-
108
- export const onPointerSelect = (
109
- annotation: Annotation,
110
- action?: PointerSelectAction | ((a: Annotation) => PointerSelectAction)
111
- ): PointerSelectAction => (typeof action === 'function') ?
112
- (action(annotation) || PointerSelectAction.EDIT) :
113
- (action || PointerSelectAction.EDIT);
@@ -1,344 +0,0 @@
1
- import type { Annotation, AnnotationBody, AnnotationTarget } from '../model';
2
- import { diffAnnotations } from '../utils';
3
- import { Origin, shouldNotify, type Update, type ChangeSet } from './StoreObserver';
4
- import type { StoreObserver, StoreChangeEvent, StoreObserveOptions } from './StoreObserver';
5
-
6
- // Shorthand
7
- type AnnotationBodyIdentifier = { id: string, annotation: string };
8
-
9
- export type Store<T extends Annotation> = ReturnType<typeof createStore<T>>;
10
-
11
- const isAnnotation = <T extends Annotation>(arg: any): arg is T => arg.id !== undefined;
12
-
13
- export const createStore = <T extends Annotation>() => {
14
-
15
- const annotationIndex = new Map<string, T>();
16
-
17
- const bodyIndex = new Map<string, string>();
18
-
19
- const observers: StoreObserver<T>[] = [];
20
-
21
- const observe = (onChange: { (event: StoreChangeEvent<T>): void }, options: StoreObserveOptions = {}) =>
22
- observers.push({ onChange, options });
23
-
24
- const unobserve = (onChange: { (event: StoreChangeEvent<T>): void }) => {
25
- const idx = observers.findIndex(observer => observer.onChange == onChange);
26
- if (idx > -1)
27
- observers.splice(idx, 1);
28
- }
29
-
30
- const emit = (origin: Origin, changes: ChangeSet<T>) => {
31
- const event: StoreChangeEvent<T> = {
32
- origin,
33
- changes: {
34
- created: changes.created || [],
35
- updated: changes.updated || [],
36
- deleted: changes.deleted || []
37
- },
38
- state: [...annotationIndex.values()]
39
- };
40
-
41
- observers.forEach(observer => {
42
- if (shouldNotify(observer, event))
43
- observer.onChange(event);
44
- });
45
- }
46
-
47
- const addAnnotation = (annotation: T, origin = Origin.LOCAL) => {
48
- const existing = annotationIndex.get(annotation.id);
49
-
50
- if (existing) {
51
- throw Error(`Cannot add annotation ${annotation.id} - exists already`);
52
- } else {
53
- annotationIndex.set(annotation.id, annotation);
54
-
55
- annotation.bodies.forEach(b => bodyIndex.set(b.id, annotation.id));
56
- emit(origin, { created: [annotation] });
57
- }
58
- }
59
-
60
- const updateOneAnnotation = (arg1: string | T, arg2?: T | Origin) => {
61
- const updated: T = typeof arg1 === 'string' ? arg2 as T : arg1;
62
-
63
- const oldId: string = typeof arg1 === 'string' ? arg1 : arg1.id;
64
- const oldValue = annotationIndex.get(oldId);
65
-
66
- if (oldValue) {
67
- const update: Update<T> = diffAnnotations(oldValue, updated);
68
-
69
- if (oldId === updated.id) {
70
- annotationIndex.set(oldId, updated);
71
- } else {
72
- annotationIndex.delete(oldId);
73
- annotationIndex.set(updated.id, updated);
74
- }
75
-
76
- oldValue.bodies.forEach(b => bodyIndex.delete(b.id));
77
- updated.bodies.forEach(b => bodyIndex.set(b.id, updated.id));
78
-
79
- return update;
80
- } else {
81
- console.warn(`Cannot update annotation ${oldId} - does not exist`);
82
- }
83
- }
84
-
85
- const updateAnnotation = (arg1: string | T, arg2: T | Origin = Origin.LOCAL, arg3 = Origin.LOCAL) => {
86
- const origin: Origin = isAnnotation(arg2) ? arg3 : arg2;
87
-
88
- const update = updateOneAnnotation(arg1, arg2);
89
- if (update)
90
- emit(origin, { updated: [update] })
91
- }
92
-
93
- const bulkUpdateAnnotation = (annotations: T[], origin = Origin.LOCAL) => {
94
- const updated = annotations.reduce((updated, annotation) => {
95
- const u = updateOneAnnotation(annotation);
96
- return u ? [...updated, u] : updated;
97
- }, [] as Update<T>[]);
98
-
99
- if (updated.length > 0)
100
- emit(origin, { updated });
101
- }
102
-
103
- const addBody = (body: AnnotationBody, origin = Origin.LOCAL) => {
104
- const oldValue = annotationIndex.get(body.annotation);
105
- if (oldValue) {
106
- const newValue = {
107
- ...oldValue,
108
- bodies: [ ...oldValue.bodies, body ]
109
- };
110
-
111
- annotationIndex.set(oldValue.id, newValue);
112
-
113
- bodyIndex.set(body.id, newValue.id);
114
-
115
- const update: Update<T> = {
116
- oldValue, newValue, bodiesCreated: [ body ]
117
- };
118
-
119
- emit(origin, { updated: [update] });
120
- } else {
121
- console.warn(`Attempt to add body to missing annotation: ${body.annotation}`);
122
- }
123
- }
124
-
125
- const all = () => [...annotationIndex.values()];
126
-
127
- const clear = (origin = Origin.LOCAL) => {
128
- const all = [...annotationIndex.values()];
129
-
130
- annotationIndex.clear();
131
- bodyIndex.clear();
132
-
133
- emit(origin, { deleted: all });
134
- }
135
-
136
- const bulkAddAnnotation = (annotations: T[], replace = true, origin = Origin.LOCAL) => {
137
- if (replace) {
138
- // Delete existing first
139
- const deleted = [...annotationIndex.values()];
140
- annotationIndex.clear();
141
- bodyIndex.clear();
142
-
143
- annotations.forEach(annotation => {
144
- annotationIndex.set(annotation.id, annotation);
145
- annotation.bodies.forEach(b => bodyIndex.set(b.id, annotation.id));
146
- });
147
-
148
- emit(origin, { created: annotations, deleted });
149
- } else {
150
- // Don't allow overwriting of existing annotations
151
- const existing = annotations.reduce((all, next) => {
152
- const existing = annotationIndex.get(next.id);
153
- return existing ? [...all, existing ] : all;
154
- }, []);
155
-
156
- if (existing.length > 0)
157
- throw Error(`Bulk insert would overwrite the following annotations: ${existing.map(a => a.id).join(', ')}`);
158
-
159
- annotations.forEach(annotation => {
160
- annotationIndex.set(annotation.id, annotation);
161
- annotation.bodies.forEach(b => bodyIndex.set(b.id, annotation.id));
162
- });
163
-
164
- emit(origin, { created: annotations });
165
- }
166
- }
167
-
168
- const deleteOneAnnotation = (annotationOrId: T | string) => {
169
- const id = typeof annotationOrId === 'string' ? annotationOrId : annotationOrId.id;
170
-
171
- const existing = annotationIndex.get(id);
172
- if (existing) {
173
- annotationIndex.delete(id);
174
- existing.bodies.forEach(b => bodyIndex.delete(b.id));
175
- return existing;
176
- } else {
177
- console.warn(`Attempt to delete missing annotation: ${id}`);
178
- }
179
- }
180
-
181
- const deleteAnnotation = (annotationOrId: T | string, origin = Origin.LOCAL) => {
182
- const deleted = deleteOneAnnotation(annotationOrId);
183
- if (deleted)
184
- emit(origin, { deleted: [ deleted ]});
185
- }
186
-
187
- const bulkDeleteAnnotation = (annotationsOrIds: (T | string)[], origin = Origin.LOCAL) => {
188
- const deleted = annotationsOrIds.reduce((deleted, arg) => {
189
- const existing = deleteOneAnnotation(arg);
190
- return existing ? [...deleted, existing] : deleted;
191
- }, [] as T[]);
192
-
193
- if (deleted.length > 0)
194
- emit(origin, { deleted });
195
- }
196
-
197
- const deleteBody = (body: AnnotationBodyIdentifier, origin = Origin.LOCAL) => {
198
- const oldAnnotation = annotationIndex.get(body.annotation);
199
-
200
- if (oldAnnotation) {
201
- const oldBody = oldAnnotation.bodies.find(b => b.id === body.id);
202
-
203
- if (oldBody) {
204
- bodyIndex.delete(oldBody.id);
205
-
206
- const newAnnotation = {
207
- ...oldAnnotation,
208
- bodies: oldAnnotation.bodies.filter(b => b.id !== body.id)
209
- };
210
-
211
- annotationIndex.set(oldAnnotation.id, newAnnotation);
212
-
213
- const update: Update<T> = {
214
- oldValue: oldAnnotation, newValue: newAnnotation, bodiesDeleted: [oldBody]
215
- };
216
-
217
- emit(origin, { updated: [update] });
218
- } else {
219
- console.warn(`Attempt to delete missing body ${body.id} from annotation ${body.annotation}`);
220
- }
221
- } else {
222
- console.warn(`Attempt to delete body from missing annotation ${body.annotation}`);
223
- }
224
- }
225
-
226
- const getAnnotation = (id: string): T | undefined => {
227
- const a = annotationIndex.get(id);
228
- return a ? {...a} : undefined;
229
- }
230
-
231
- const getBody = (id: string): AnnotationBody | undefined => {
232
- const annotationId = bodyIndex.get(id);
233
- if (annotationId) {
234
- const annotation = getAnnotation(annotationId);
235
- const body = annotation.bodies.find(b => b.id === id);
236
- if (body) {
237
- return body;
238
- } else {
239
- console.error(`Store integrity error: body ${id} in index, but not in annotation`);
240
- }
241
- } else {
242
- console.warn(`Attempt to retrieve missing body: ${id}`);
243
- }
244
- }
245
-
246
- const updateOneBody = (oldBodyId: AnnotationBodyIdentifier, newBody: AnnotationBody) => {
247
- if (oldBodyId.annotation !== newBody.annotation)
248
- throw 'Annotation integrity violation: annotation ID must be the same when updating bodies';
249
-
250
- const oldAnnotation = annotationIndex.get(oldBodyId.annotation);
251
- if (oldAnnotation) {
252
- const oldBody = oldAnnotation.bodies.find(b => b.id === oldBodyId.id);
253
-
254
- const newAnnotation = {
255
- ...oldAnnotation,
256
- bodies: oldAnnotation.bodies.map(b => b.id === oldBody.id ? newBody : b)
257
- };
258
-
259
- annotationIndex.set(oldAnnotation.id, newAnnotation);
260
-
261
- if (oldBody.id !== newBody.id) {
262
- bodyIndex.delete(oldBody.id);
263
- bodyIndex.set(newBody.id, newAnnotation.id);
264
- }
265
-
266
- return {
267
- oldValue: oldAnnotation,
268
- newValue: newAnnotation,
269
- bodiesUpdated: [{ oldBody, newBody }]
270
- }
271
- } else {
272
- console.warn(`Attempt to add body to missing annotation ${oldBodyId.annotation}`);
273
- }
274
- }
275
-
276
- const updateBody = (oldBodyId: AnnotationBodyIdentifier, newBody: AnnotationBody, origin = Origin.LOCAL) => {
277
- const update = updateOneBody(oldBodyId, newBody);
278
- emit(origin, { updated: [ update ]} );
279
- }
280
-
281
- const bulkUpdateBodies = (bodies: AnnotationBody[], origin = Origin.LOCAL) => {
282
- const updated = bodies.map(b => updateOneBody({ id: b.id, annotation: b.annotation }, b));
283
- emit(origin, { updated });
284
- }
285
-
286
- const updateOneTarget = (target: AnnotationTarget): Update<T> => {
287
- const oldValue = annotationIndex.get(target.annotation);
288
-
289
- if (oldValue) {
290
- const newValue = {
291
- ...oldValue,
292
- target: {
293
- ...oldValue.target,
294
- ...target
295
- }
296
- };
297
-
298
- annotationIndex.set(oldValue.id, newValue);
299
-
300
- return {
301
- oldValue, newValue, targetUpdated: {
302
- oldTarget: oldValue.target,
303
- newTarget: target
304
- }
305
- };
306
- } else {
307
- console.warn(`Attempt to update target on missing annotation: ${target.annotation}`);
308
- }
309
- }
310
-
311
- const updateTarget = (target: AnnotationTarget, origin = Origin.LOCAL) => {
312
- const update = updateOneTarget(target);
313
- if (update)
314
- emit(origin, { updated: [ update ]} );
315
- }
316
-
317
- const bulkUpdateTargets = (targets: AnnotationTarget[], origin = Origin.LOCAL) => {
318
- const updated = targets.map(updateOneTarget).filter(val => val);
319
- if (updated.length > 0)
320
- emit(origin, { updated });
321
- }
322
-
323
- return {
324
- addAnnotation,
325
- addBody,
326
- all,
327
- bulkAddAnnotation,
328
- bulkDeleteAnnotation,
329
- bulkUpdateAnnotation,
330
- bulkUpdateBodies,
331
- bulkUpdateTargets,
332
- clear,
333
- deleteAnnotation,
334
- deleteBody,
335
- getAnnotation,
336
- getBody,
337
- observe,
338
- unobserve,
339
- updateAnnotation,
340
- updateBody,
341
- updateTarget
342
- };
343
-
344
- }
@@ -1,194 +0,0 @@
1
- import type { Annotation, AnnotationBody, AnnotationTarget } from '../model';
2
- import { diffAnnotations } from '../utils';
3
-
4
- /** Interface for listening to changes in the annotation store **/
5
- export interface StoreObserver<T extends Annotation> {
6
-
7
- onChange: { (event: StoreChangeEvent<T>): void };
8
-
9
- options: StoreObserveOptions;
10
-
11
- }
12
-
13
- /** A change event fired when the store state changes **/
14
- export interface StoreChangeEvent<T extends Annotation> {
15
-
16
- origin: Origin;
17
-
18
- changes: ChangeSet<T>;
19
-
20
- state: T[];
21
-
22
- }
23
-
24
- export interface ChangeSet<T extends Annotation> {
25
-
26
- created?: T[];
27
-
28
- deleted?: T[];
29
-
30
- updated?: Update<T>[];
31
-
32
- }
33
-
34
- export interface Update<T extends Annotation> {
35
-
36
- oldValue: T;
37
-
38
- newValue: T;
39
-
40
- bodiesCreated?: AnnotationBody[];
41
-
42
- bodiesDeleted?: AnnotationBody[];
43
-
44
- bodiesUpdated?: Array<{ oldBody: AnnotationBody, newBody: AnnotationBody }>;
45
-
46
- targetUpdated?: { oldTarget: AnnotationTarget, newTarget: AnnotationTarget};
47
-
48
- }
49
-
50
- /** Options to control which events the observer wants to get notified about **/
51
- export interface StoreObserveOptions {
52
-
53
- // Observe changes on targets, bodies or both?
54
- ignore?: Ignore;
55
-
56
- // Observe changes on one more specific annotations
57
- annotations?: string | string[];
58
-
59
- // Observer changes only for a specific origin
60
- origin?: Origin
61
-
62
- }
63
-
64
- /** Allows the observer to ignore certain event types **/
65
- export enum Ignore {
66
-
67
- // Don't notify this observer for changes that involve bodies only
68
- BODY_ONLY = 'BODY_ONLY',
69
-
70
- // Don't notify for changes on targets only
71
- TARGET_ONLY = 'TARGET_ONLY'
72
-
73
- }
74
-
75
- /** Allows the observer to listen only for events that originated locally or from a remote source **/
76
- export enum Origin {
77
-
78
- LOCAL = 'LOCAL',
79
-
80
- REMOTE = 'REMOTE'
81
-
82
- }
83
-
84
- /** Tests if this observer should be notified about this event **/
85
- export const shouldNotify = <T extends Annotation>(observer: StoreObserver<T>, event: StoreChangeEvent<T>) => {
86
- const { changes, origin } = event;
87
-
88
- const isRelevantOrigin =
89
- !observer.options.origin || observer.options.origin === origin;
90
-
91
- if (!isRelevantOrigin)
92
- return false;
93
-
94
- if (observer.options.ignore) {
95
- const { ignore } = observer.options;
96
-
97
- // Shorthand
98
- const has = (arg: any[]) => arg?.length > 0;
99
-
100
- const hasAnnotationChanges =
101
- has(changes.created) || has(changes.deleted);
102
-
103
- if (!hasAnnotationChanges) {
104
- const hasBodyChanges =
105
- changes.updated?.some(u => has(u.bodiesCreated) || has(u.bodiesDeleted) || has(u.bodiesUpdated));
106
-
107
- const hasTargetChanges =
108
- changes.updated?.some(u => u.targetUpdated);
109
-
110
- if (ignore === Ignore.BODY_ONLY && hasBodyChanges && !hasTargetChanges)
111
- return false;
112
-
113
- if (ignore === Ignore.TARGET_ONLY && hasTargetChanges && !hasBodyChanges)
114
- return false;
115
- }
116
- }
117
-
118
- if (observer.options.annotations) {
119
- // This observer has a filter set on specific annotations - check affected
120
- const affectedAnnotations = new Set([
121
- ...changes.created.map(a => a.id),
122
- ...changes.deleted.map(a => a.id),
123
- ...changes.updated.map(({ oldValue }) => oldValue.id)
124
- ]);
125
-
126
- const observed = Array.isArray(observer.options.annotations) ?
127
- observer.options.annotations : [ observer.options.annotations ];
128
-
129
- return Boolean(observed.find(id => affectedAnnotations.has(id)));
130
- } else {
131
- return true;
132
- }
133
-
134
- }
135
-
136
- export const mergeChanges = <T extends Annotation>(changes: ChangeSet<T>, toMerge: ChangeSet<T>) => {
137
-
138
- const previouslyCreatedIds = new Set((changes.created || []).map(a => a.id));
139
- const previouslyUpdatedIds = new Set((changes.updated || []).map(({ newValue })=> newValue.id));
140
-
141
- const createdIds = new Set((toMerge.created || []).map(a => a.id));
142
- const deletedIds = new Set((toMerge.deleted || []).map(a => a.id));
143
- const updatedIds = new Set((toMerge.updated || []).map(({ oldValue }) => oldValue.id));
144
-
145
- // Updates that will be merged into create or previous update events
146
- const mergeableUpdates = new Set((toMerge.updated || [])
147
- .filter(({ oldValue }) => previouslyCreatedIds.has(oldValue.id) || previouslyUpdatedIds.has(oldValue.id))
148
- .map(({ oldValue }) => oldValue.id ));
149
-
150
- // * created *
151
- // - drop created that were then deleted
152
- // - merge any updates on created
153
- // - append newly created
154
- const created = [
155
- ...(changes.created || [])
156
- .filter(a => !deletedIds.has(a.id))
157
- .map(a => updatedIds.has(a.id)
158
- ? toMerge.updated.find(({ oldValue }) => oldValue.id === a.id).newValue
159
- : a),
160
- ...(toMerge.created || [])
161
- ];
162
-
163
- // * deleted *
164
- // - drop deleted that were later re-created (redo action!)
165
- // - append newly deleted, but remove any that delete annotations
166
- // that were created in the same round
167
- const deleted = [
168
- ...(changes.deleted || [])
169
- .filter(a => !createdIds.has(a.id)),
170
- ...(toMerge.deleted || [])
171
- .filter(a => !previouslyCreatedIds.has(a.id))
172
- ]
173
-
174
- // * updated *
175
- // - drop updates on deleted annotations
176
- // - merge any updates that override previous ones
177
- // - append new updates, but remove any that were merged
178
- const updated = [
179
- ...(changes.updated || [])
180
- .filter(({ newValue }) => !deletedIds.has(newValue.id))
181
- .map(update => {
182
- const { oldValue, newValue } = update;
183
- if (updatedIds.has(newValue.id)) {
184
- const updated = toMerge.updated.find(u => u.oldValue.id === newValue.id).newValue;
185
- return diffAnnotations(oldValue, updated);
186
- } else {
187
- return update;
188
- }
189
- }),
190
- ...(toMerge.updated || []).filter(({ oldValue }) => !mergeableUpdates.has(oldValue.id))
191
- ]
192
-
193
- return { created, deleted, updated };
194
- }