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

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@annotorious/core",
3
- "version": "3.0.0-rc.3",
3
+ "version": "3.0.0-rc.4",
4
4
  "description": "Annotorious core types and functions",
5
5
  "author": "Rainer Simon",
6
6
  "license": "BSD-3-Clause",
@@ -1,20 +1,20 @@
1
1
  import { dequal } from 'dequal/lite';
2
- import type { Annotation, FormatAdapter } from '../model';
3
- import { Origin } from '../state';
4
- import type { HoverState, SelectionState, Store, ViewportState } from '../state';
2
+ import type { Annotation, AnnotatorState, FormatAdapter } from '../model';
3
+ import { Origin, type ChangeSet, type UndoStack } from '../state';
4
+ import type { ViewportState } from '../state';
5
5
  import type { LifecycleEvents } from './LifecycleEvents';
6
6
 
7
7
  export type Lifecycle<I extends Annotation, E extends unknown> =
8
8
  ReturnType<typeof createLifecyleObserver<I, E>>;
9
9
 
10
10
  export const createLifecyleObserver = <I extends Annotation, E extends unknown>(
11
- store: Store<I>,
12
- selectionState: SelectionState<I>,
13
- hoverState: HoverState<I>,
14
- viewportState?: ViewportState,
11
+ state: AnnotatorState<I>,
12
+ undoStack: UndoStack<I>,
15
13
  adapter?: FormatAdapter<I, E>,
16
14
  autoSave?: boolean
17
15
  ) => {
16
+ const { store, selection, hover, viewport } = state;
17
+
18
18
  const observers: Map<keyof LifecycleEvents, Function[]> = new Map();
19
19
 
20
20
  // The currently selected annotations, in the state when they were selected
@@ -62,7 +62,7 @@ export const createLifecyleObserver = <I extends Annotation, E extends unknown>(
62
62
  }
63
63
 
64
64
  const onIdleUpdate = () => {
65
- const { selected } = selectionState;
65
+ const { selected } = selection;
66
66
 
67
67
  // User idle after activity - fire update events for selected
68
68
  // annotations that changed
@@ -81,7 +81,7 @@ export const createLifecyleObserver = <I extends Annotation, E extends unknown>(
81
81
  });
82
82
  }
83
83
 
84
- selectionState.subscribe(({ selected })=> {
84
+ selection.subscribe(({ selected })=> {
85
85
  if (initialSelection.length === 0 && selected.length === 0)
86
86
  return;
87
87
 
@@ -125,7 +125,7 @@ export const createLifecyleObserver = <I extends Annotation, E extends unknown>(
125
125
  emit('selectionChanged', initialSelection);
126
126
  });
127
127
 
128
- hoverState.subscribe(id => {
128
+ hover.subscribe(id => {
129
129
  if (!currentHover && id) {
130
130
  emit('mouseEnterAnnotation', store.getAnnotation(id));
131
131
  } else if (currentHover && !id) {
@@ -138,7 +138,7 @@ export const createLifecyleObserver = <I extends Annotation, E extends unknown>(
138
138
  currentHover = id;
139
139
  });
140
140
 
141
- viewportState?.subscribe(ids =>
141
+ viewport?.subscribe(ids =>
142
142
  emit('viewportIntersect', ids.map(store.getAnnotation)));
143
143
 
144
144
  store.observe(event => {
@@ -192,6 +192,20 @@ export const createLifecyleObserver = <I extends Annotation, E extends unknown>(
192
192
  }
193
193
  }, { origin: Origin.REMOTE });
194
194
 
195
+ const onUndoOrRedo = (undo: boolean) => (changes: ChangeSet<I>) => {
196
+ const { created, deleted, updated } = changes;
197
+ created.forEach(a => emit('createAnnotation', a));
198
+ deleted.forEach(a => emit('deleteAnnotation', a));
199
+
200
+ if (undo)
201
+ updated.forEach(t => emit('updateAnnotation', t.oldValue, t.newValue));
202
+ else
203
+ updated.forEach(t => emit('updateAnnotation', t.newValue, t.oldValue));
204
+ }
205
+
206
+ undoStack.on('undo', onUndoOrRedo(true));
207
+ undoStack.on('redo', onUndoOrRedo(false));
208
+
195
209
  return { on, off, emit }
196
210
 
197
211
  }
@@ -16,6 +16,12 @@ export interface Annotator<I extends Annotation = Annotation, E extends unknown
16
16
 
17
17
  addAnnotation(annotation: E): void;
18
18
 
19
+ cancelSelected(): void;
20
+
21
+ canRedo(): boolean;
22
+
23
+ canUndo(): boolean;
24
+
19
25
  clearAnnotations(): void;
20
26
 
21
27
  destroy(): void;
@@ -24,6 +30,8 @@ export interface Annotator<I extends Annotation = Annotation, E extends unknown
24
30
 
25
31
  getAnnotations(): E[];
26
32
 
33
+ getSelected(): E[];
34
+
27
35
  getUser(): User;
28
36
 
29
37
  loadAnnotations(url: string): Promise<E[]>;
@@ -69,11 +77,13 @@ export interface AnnotatorState<A extends Annotation> {
69
77
  }
70
78
 
71
79
  export const createBaseAnnotator = <I extends Annotation, E extends unknown>(
72
- store: Store<I>,
73
- undoStack: UndoStack,
80
+ state: AnnotatorState<I>,
81
+ undoStack: UndoStack<I>,
74
82
  adapter?: FormatAdapter<I, E>
75
83
  ) => {
76
84
 
85
+ const { store, selection } = state;
86
+
77
87
  const addAnnotation = (annotation: E) => {
78
88
  if (adapter) {
79
89
  const { parsed, error } = adapter.parse(annotation);
@@ -87,6 +97,8 @@ export const createBaseAnnotator = <I extends Annotation, E extends unknown>(
87
97
  }
88
98
  }
89
99
 
100
+ const cancelSelected = () => selection.clear();
101
+
90
102
  const clearAnnotations = () => store.clear();
91
103
 
92
104
  const getAnnotationById = (id: string): E | undefined => {
@@ -98,6 +110,16 @@ export const createBaseAnnotator = <I extends Annotation, E extends unknown>(
98
110
  const getAnnotations = () =>
99
111
  (adapter ? store.all().map(adapter.serialize) : store.all()) as E[];
100
112
 
113
+ const getSelected = () => {
114
+ const selectedIds = selection.selected?.map(s => s.id) || [];
115
+
116
+ const selected = selectedIds.map(id => store.getAnnotation(id));
117
+
118
+ return adapter
119
+ ? selected.map(adapter.serialize)
120
+ : selected as unknown as E[];
121
+ }
122
+
101
123
  const loadAnnotations = (url: string) =>
102
124
  fetch(url)
103
125
  .then((response) => response.json())
@@ -132,6 +154,14 @@ export const createBaseAnnotator = <I extends Annotation, E extends unknown>(
132
154
  }
133
155
  }
134
156
 
157
+ const setSelected = (arg?: string | string[]) => {
158
+ if (arg) {
159
+ selection.setSelected(arg);
160
+ } else {
161
+ selection.clear();
162
+ }
163
+ }
164
+
135
165
  const updateAnnotation = (updated: E): E => {
136
166
  if (adapter) {
137
167
  const crosswalked = adapter.parse(updated).parsed;
@@ -145,15 +175,23 @@ export const createBaseAnnotator = <I extends Annotation, E extends unknown>(
145
175
  }
146
176
  }
147
177
 
178
+ // Note that we don't spread the undoStack - it has a .destroy()
179
+ // method that would likely get overwritten by other Annotator implementations
180
+ // if people are not careful.
148
181
  return {
149
182
  addAnnotation,
183
+ cancelSelected,
184
+ canRedo: undoStack.canRedo,
185
+ canUndo: undoStack.canUndo,
150
186
  clearAnnotations,
151
187
  getAnnotationById,
152
188
  getAnnotations,
189
+ getSelected,
153
190
  loadAnnotations,
154
191
  redo: undoStack.redo,
155
192
  removeAnnotation,
156
193
  setAnnotations,
194
+ setSelected,
157
195
  undo: undoStack.undo,
158
196
  updateAnnotation
159
197
  }
@@ -1,3 +1,4 @@
1
+ import { createNanoEvents, type Unsubscribe } from 'nanoevents';
1
2
  import type { Annotation } from '../model';
2
3
  import type { Store } from './Store';
3
4
  import { Origin } from './StoreObserver';
@@ -8,17 +9,33 @@ import { mergeChanges, type ChangeSet, type StoreChangeEvent, type Update } from
8
9
  // as a new undo/redo step.
9
10
  const DEBOUNCE = 250;
10
11
 
11
- export interface UndoStack {
12
+ export interface UndoStack <T extends Annotation> {
13
+
14
+ canRedo(): boolean;
15
+
16
+ canUndo(): boolean;
12
17
 
13
18
  destroy(): void;
14
19
 
20
+ on<E extends keyof UndoStackEvents<T>>(event: E, callback: UndoStackEvents<T>[E]): Unsubscribe;
21
+
15
22
  undo(): void;
16
23
 
17
24
  redo(): void;
18
25
 
19
26
  }
20
27
 
21
- export const createUndoStack = <T extends Annotation>(store: Store<T>): UndoStack => {
28
+ export interface UndoStackEvents <T extends Annotation> {
29
+
30
+ redo(change: ChangeSet<T>): void;
31
+
32
+ undo(change: ChangeSet<T>): void;
33
+
34
+ }
35
+
36
+ export const createUndoStack = <T extends Annotation>(store: Store<T>): UndoStack<T> => {
37
+
38
+ const emitter = createNanoEvents<UndoStackEvents<T>>();
22
39
 
23
40
  const changeStack: ChangeSet<T>[] = [];
24
41
 
@@ -77,16 +94,20 @@ export const createUndoStack = <T extends Annotation>(store: Store<T>): UndoStac
77
94
  if (pointer > -1) {
78
95
  muteEvents = true;
79
96
 
80
- const { created, updated, deleted} = changeStack[pointer];
97
+ const { created, updated, deleted } = changeStack[pointer];
81
98
 
82
99
  undoCreated(created);
83
100
  undoUpdated(updated);
84
101
  undoDeleted(deleted);
85
102
 
103
+ emitter.emit('undo', changeStack[pointer]);
104
+
86
105
  pointer -= 1;
87
106
  }
88
107
  }
89
108
 
109
+ const canUndo = () => pointer > -1;
110
+
90
111
  const redo = () => {
91
112
  if (changeStack.length - 1 > pointer) {
92
113
  muteEvents = true;
@@ -97,14 +118,24 @@ export const createUndoStack = <T extends Annotation>(store: Store<T>): UndoStac
97
118
  redoUpdated(updated);
98
119
  redoDeleted(deleted);
99
120
 
121
+ emitter.emit('redo', changeStack[pointer + 1]);
122
+
100
123
  pointer += 1;
101
124
  }
102
125
  }
103
126
 
127
+ const canRedo = () => changeStack.length - 1 > pointer;
128
+
104
129
  const destroy = () => store.unobserve(onChange);
105
130
 
131
+ const on = <E extends keyof UndoStackEvents<T>>(event: E, callback: UndoStackEvents<T>[E]) =>
132
+ emitter.on(event, callback);
133
+
106
134
  return {
135
+ canRedo,
136
+ canUndo,
107
137
  destroy,
138
+ on,
108
139
  redo,
109
140
  undo
110
141
  }