@annotorious/core 3.0.0-rc.2 → 3.0.0-rc.20
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/dist/annotorious-core.es.js +776 -0
- package/dist/annotorious-core.es.js.map +1 -0
- package/{src/index.ts → dist/index.d.ts} +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/lifecycle/Lifecycle.d.ts +10 -0
- package/dist/lifecycle/Lifecycle.d.ts.map +1 -0
- package/dist/lifecycle/LifecycleEvents.d.ts +12 -0
- package/dist/lifecycle/LifecycleEvents.d.ts.map +1 -0
- package/dist/lifecycle/index.d.ts +3 -0
- package/dist/lifecycle/index.d.ts.map +1 -0
- package/dist/model/Annotation.d.ts +32 -0
- package/dist/model/Annotation.d.ts.map +1 -0
- package/dist/model/Annotator.d.ts +63 -0
- package/dist/model/Annotator.d.ts.map +1 -0
- package/{src/model/DrawingStyle.ts → dist/model/DrawingStyle.d.ts} +8 -14
- package/dist/model/DrawingStyle.d.ts.map +1 -0
- package/{src/model/Filter.ts → dist/model/Filter.d.ts} +2 -2
- package/dist/model/Filter.d.ts.map +1 -0
- package/dist/model/FormatAdapter.d.ts +15 -0
- package/dist/model/FormatAdapter.d.ts.map +1 -0
- package/dist/model/User.d.ts +11 -0
- package/dist/model/User.d.ts.map +1 -0
- package/dist/model/W3CAnnotation.d.ts +41 -0
- package/dist/model/W3CAnnotation.d.ts.map +1 -0
- package/{src/model/index.ts → dist/model/index.d.ts} +2 -1
- package/dist/model/index.d.ts.map +1 -0
- package/dist/presence/Appearance.d.ts +6 -0
- package/dist/presence/Appearance.d.ts.map +1 -0
- package/dist/presence/AppearanceProvider.d.ts +16 -0
- package/dist/presence/AppearanceProvider.d.ts.map +1 -0
- package/dist/presence/ColorPalette.d.ts +3 -0
- package/dist/presence/ColorPalette.d.ts.map +1 -0
- package/dist/presence/PresenceEvents.d.ts +6 -0
- package/dist/presence/PresenceEvents.d.ts.map +1 -0
- package/dist/presence/PresenceProvider.d.ts +5 -0
- package/dist/presence/PresenceProvider.d.ts.map +1 -0
- package/dist/presence/PresenceState.d.ts +18 -0
- package/dist/presence/PresenceState.d.ts.map +1 -0
- package/dist/presence/PresentUser.d.ts +7 -0
- package/dist/presence/PresentUser.d.ts.map +1 -0
- package/{src/presence/index.ts → dist/presence/index.d.ts} +2 -1
- package/dist/presence/index.d.ts.map +1 -0
- package/dist/state/Hover.d.ts +10 -0
- package/dist/state/Hover.d.ts.map +1 -0
- package/dist/state/Selection.d.ts +31 -0
- package/dist/state/Selection.d.ts.map +1 -0
- package/dist/state/Store.d.ts +30 -0
- package/dist/state/Store.d.ts.map +1 -0
- package/dist/state/StoreObserver.d.ts +57 -0
- package/dist/state/StoreObserver.d.ts.map +1 -0
- package/dist/state/SvelteStore.d.ts +22 -0
- package/dist/state/SvelteStore.d.ts.map +1 -0
- package/dist/state/UndoStack.d.ts +18 -0
- package/dist/state/UndoStack.d.ts.map +1 -0
- package/dist/state/Viewport.d.ts +7 -0
- package/dist/state/Viewport.d.ts.map +1 -0
- package/{src/state/index.ts → dist/state/index.d.ts} +3 -1
- package/dist/state/index.d.ts.map +1 -0
- package/dist/utils/annotationUtils.d.ts +11 -0
- package/dist/utils/annotationUtils.d.ts.map +1 -0
- package/dist/utils/diffAnnotations.d.ts +4 -0
- package/dist/utils/diffAnnotations.d.ts.map +1 -0
- package/{src/utils/index.ts → dist/utils/index.d.ts} +1 -1
- package/dist/utils/index.d.ts.map +1 -0
- package/package.json +18 -15
- package/src/lifecycle/Lifecycle.ts +0 -197
- package/src/lifecycle/LifecycleEvents.ts +0 -21
- package/src/lifecycle/index.ts +0 -2
- package/src/model/Annotation.ts +0 -57
- package/src/model/Annotator.ts +0 -154
- package/src/model/FormatAdapter.ts +0 -36
- package/src/model/User.ts +0 -19
- package/src/model/W3CAnnotation.ts +0 -118
- package/src/presence/Appearance.ts +0 -9
- package/src/presence/AppearanceProvider.ts +0 -53
- package/src/presence/ColorPalette.ts +0 -14
- package/src/presence/PresenceEvents.ts +0 -9
- package/src/presence/PresenceProvider.ts +0 -7
- package/src/presence/PresenceState.ts +0 -145
- package/src/presence/PresentUser.ts +0 -10
- package/src/state/Hover.ts +0 -34
- package/src/state/Selection.ts +0 -113
- package/src/state/Store.ts +0 -327
- package/src/state/StoreObserver.ts +0 -149
- package/src/state/SvelteStore.ts +0 -54
- package/src/state/Viewport.ts +0 -14
- package/src/utils/annotationUtils.ts +0 -33
- package/src/utils/diffAnnotations.ts +0 -33
- package/src/vite-env.d.ts +0 -1
package/src/model/Annotation.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import type { User } from './User';
|
|
2
|
-
|
|
3
|
-
export interface Annotation {
|
|
4
|
-
|
|
5
|
-
id: string;
|
|
6
|
-
|
|
7
|
-
target: AnnotationTarget;
|
|
8
|
-
|
|
9
|
-
bodies: AnnotationBody[];
|
|
10
|
-
|
|
11
|
-
properties?: {
|
|
12
|
-
|
|
13
|
-
[key: string]: any;
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface AnnotationTarget {
|
|
20
|
-
|
|
21
|
-
annotation: string;
|
|
22
|
-
|
|
23
|
-
selector: AbstractSelector;
|
|
24
|
-
|
|
25
|
-
creator?: User;
|
|
26
|
-
|
|
27
|
-
created?: Date;
|
|
28
|
-
|
|
29
|
-
updatedBy?: User;
|
|
30
|
-
|
|
31
|
-
updated?: Date;
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface AbstractSelector { }
|
|
36
|
-
|
|
37
|
-
export interface AnnotationBody {
|
|
38
|
-
|
|
39
|
-
id: string;
|
|
40
|
-
|
|
41
|
-
annotation: string;
|
|
42
|
-
|
|
43
|
-
type?: string;
|
|
44
|
-
|
|
45
|
-
purpose?: string;
|
|
46
|
-
|
|
47
|
-
value?: string;
|
|
48
|
-
|
|
49
|
-
creator?: User;
|
|
50
|
-
|
|
51
|
-
created?: Date;
|
|
52
|
-
|
|
53
|
-
updatedBy?: User;
|
|
54
|
-
|
|
55
|
-
updated?: Date;
|
|
56
|
-
|
|
57
|
-
}
|
package/src/model/Annotator.ts
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import type { Annotation } from './Annotation';
|
|
2
|
-
import type { User } from './User';
|
|
3
|
-
import type { PresenceProvider } from '../presence';
|
|
4
|
-
import { Origin, type HoverState, type SelectionState, type Store, type ViewportState } from '../state';
|
|
5
|
-
import type { LifecycleEvents } from '../lifecycle';
|
|
6
|
-
import { parseAll, type FormatAdapter } from './FormatAdapter';
|
|
7
|
-
import type { DrawingStyle } from './DrawingStyle';
|
|
8
|
-
import type { Filter } from './Filter';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Base annotator interface.
|
|
12
|
-
* I ... internal core data model
|
|
13
|
-
* E ... external adapted representation
|
|
14
|
-
*/
|
|
15
|
-
export interface Annotator<I extends Annotation = Annotation, E extends unknown = Annotation> {
|
|
16
|
-
|
|
17
|
-
addAnnotation(annotation: E): void;
|
|
18
|
-
|
|
19
|
-
clearAnnotations(): void;
|
|
20
|
-
|
|
21
|
-
destroy(): void;
|
|
22
|
-
|
|
23
|
-
getAnnotationById(id: string): E | undefined;
|
|
24
|
-
|
|
25
|
-
getAnnotations(): E[];
|
|
26
|
-
|
|
27
|
-
getUser(): User;
|
|
28
|
-
|
|
29
|
-
loadAnnotations(url: string): Promise<E[]>;
|
|
30
|
-
|
|
31
|
-
removeAnnotation(arg: E | string): E;
|
|
32
|
-
|
|
33
|
-
setAnnotations(annotations: E[]): void;
|
|
34
|
-
|
|
35
|
-
setFilter(filter: Filter): void;
|
|
36
|
-
|
|
37
|
-
setPresenceProvider?(provider: PresenceProvider): void;
|
|
38
|
-
|
|
39
|
-
setSelected(arg?: string | string[]): void;
|
|
40
|
-
|
|
41
|
-
setStyle(arg: DrawingStyle | ((annotation: I) => DrawingStyle) | undefined): void;
|
|
42
|
-
|
|
43
|
-
setUser(user: User): void;
|
|
44
|
-
|
|
45
|
-
updateAnnotation(annotation: E): E;
|
|
46
|
-
|
|
47
|
-
on<T extends keyof LifecycleEvents<E>>(event: T, callback: LifecycleEvents<E>[T]): void;
|
|
48
|
-
|
|
49
|
-
off<T extends keyof LifecycleEvents<E>>(event: T, callback: LifecycleEvents<E>[T]): void;
|
|
50
|
-
|
|
51
|
-
state: AnnotatorState<I>;
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface AnnotatorState<A extends Annotation> {
|
|
56
|
-
|
|
57
|
-
store: Store<A>;
|
|
58
|
-
|
|
59
|
-
selection: SelectionState<A>;
|
|
60
|
-
|
|
61
|
-
hover: HoverState<A>;
|
|
62
|
-
|
|
63
|
-
viewport: ViewportState;
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export const createBaseAnnotator = <I extends Annotation, E extends unknown>(
|
|
68
|
-
store: Store<I>,
|
|
69
|
-
adapter?: FormatAdapter<I, E>
|
|
70
|
-
) => {
|
|
71
|
-
|
|
72
|
-
const addAnnotation = (annotation: E) => {
|
|
73
|
-
if (adapter) {
|
|
74
|
-
const { parsed, error } = adapter.parse(annotation);
|
|
75
|
-
if (parsed) {
|
|
76
|
-
store.addAnnotation(parsed, Origin.REMOTE);
|
|
77
|
-
} else {
|
|
78
|
-
console.error(error);
|
|
79
|
-
}
|
|
80
|
-
} else {
|
|
81
|
-
store.addAnnotation(annotation as unknown as I, Origin.REMOTE);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const clearAnnotations = () => store.clear();
|
|
86
|
-
|
|
87
|
-
const getAnnotationById = (id: string): E | undefined => {
|
|
88
|
-
const annotation = store.getAnnotation(id);
|
|
89
|
-
return (adapter && annotation) ?
|
|
90
|
-
adapter.serialize(annotation) as E : annotation as unknown as E;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const getAnnotations = () =>
|
|
94
|
-
(adapter ? store.all().map(adapter.serialize) : store.all()) as E[];
|
|
95
|
-
|
|
96
|
-
const loadAnnotations = (url: string) =>
|
|
97
|
-
fetch(url)
|
|
98
|
-
.then((response) => response.json())
|
|
99
|
-
.then((annotations) => {
|
|
100
|
-
setAnnotations(annotations);
|
|
101
|
-
return annotations;
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
const removeAnnotation = (arg: E | string): E => {
|
|
105
|
-
if (typeof arg === 'string') {
|
|
106
|
-
const annotation = store.getAnnotation(arg);
|
|
107
|
-
store.deleteAnnotation(arg);
|
|
108
|
-
|
|
109
|
-
return adapter ? adapter.serialize(annotation) : annotation as unknown as E;
|
|
110
|
-
} else {
|
|
111
|
-
const annotation = adapter ? adapter.parse(arg).parsed : (arg as unknown as I);
|
|
112
|
-
store.deleteAnnotation(annotation);
|
|
113
|
-
return arg;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const setAnnotations = (annotations: E[]) => {
|
|
118
|
-
if (adapter) {
|
|
119
|
-
const { parsed, failed } = parseAll(adapter)(annotations);
|
|
120
|
-
|
|
121
|
-
if (failed.length > 0)
|
|
122
|
-
console.warn(`Discarded ${failed.length} invalid annotations`, failed);
|
|
123
|
-
|
|
124
|
-
store.bulkAddAnnotation(parsed, true, Origin.REMOTE);
|
|
125
|
-
} else {
|
|
126
|
-
store.bulkAddAnnotation(annotations as unknown as I[], true, Origin.REMOTE);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const updateAnnotation = (updated: E): E => {
|
|
131
|
-
if (adapter) {
|
|
132
|
-
const crosswalked = adapter.parse(updated).parsed;
|
|
133
|
-
const previous = adapter.serialize(store.getAnnotation(crosswalked.id));
|
|
134
|
-
store.updateAnnotation(crosswalked);
|
|
135
|
-
return previous;
|
|
136
|
-
} else {
|
|
137
|
-
const previous = store.getAnnotation((updated as unknown as I).id);
|
|
138
|
-
store.updateAnnotation(updated as unknown as I);
|
|
139
|
-
return previous as unknown as E;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
addAnnotation,
|
|
145
|
-
clearAnnotations,
|
|
146
|
-
getAnnotationById,
|
|
147
|
-
getAnnotations,
|
|
148
|
-
loadAnnotations,
|
|
149
|
-
removeAnnotation,
|
|
150
|
-
setAnnotations,
|
|
151
|
-
updateAnnotation
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { Annotation } from './Annotation';
|
|
2
|
-
|
|
3
|
-
export interface FormatAdapter<A extends Annotation, T extends unknown> {
|
|
4
|
-
|
|
5
|
-
parse(serialized: T): ParseResult<A>;
|
|
6
|
-
|
|
7
|
-
serialize(core: A): T;
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface ParseResult<A extends Annotation> {
|
|
12
|
-
|
|
13
|
-
parsed?: A;
|
|
14
|
-
|
|
15
|
-
error?: Error;
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const serializeAll =
|
|
20
|
-
<A extends Annotation, T extends unknown>(adapter: FormatAdapter<A, T>) =>
|
|
21
|
-
(annotations: A[]) => annotations.map(a => adapter.serialize(a));
|
|
22
|
-
|
|
23
|
-
export const parseAll =
|
|
24
|
-
<A extends Annotation, T extends unknown>(adapter: FormatAdapter<A, T>) =>
|
|
25
|
-
(serialized: T[]) => serialized.reduce((result, next) => {
|
|
26
|
-
const { parsed, error } = adapter.parse(next);
|
|
27
|
-
|
|
28
|
-
return error ? {
|
|
29
|
-
parsed: result.parsed,
|
|
30
|
-
failed: [...result.failed, next ]
|
|
31
|
-
} : {
|
|
32
|
-
parsed: [...result.parsed, parsed ],
|
|
33
|
-
failed: result.failed
|
|
34
|
-
}
|
|
35
|
-
}, { parsed: [], failed: [] });
|
|
36
|
-
|
package/src/model/User.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { customAlphabet } from 'nanoid';
|
|
2
|
-
|
|
3
|
-
export interface User {
|
|
4
|
-
|
|
5
|
-
id: string;
|
|
6
|
-
|
|
7
|
-
isGuest?: boolean;
|
|
8
|
-
|
|
9
|
-
name?: string;
|
|
10
|
-
|
|
11
|
-
avatar?: string;
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const createAnonymousGuest = () => {
|
|
16
|
-
const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_', 20);
|
|
17
|
-
|
|
18
|
-
return { isGuest: true, id: nanoid() }
|
|
19
|
-
}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import type { AnnotationBody } from './Annotation';
|
|
2
|
-
|
|
3
|
-
export interface W3CAnnotation {
|
|
4
|
-
|
|
5
|
-
'@context': 'http://www.w3.org/ns/anno.jsonld';
|
|
6
|
-
|
|
7
|
-
type: 'Annotation';
|
|
8
|
-
|
|
9
|
-
id: string;
|
|
10
|
-
|
|
11
|
-
body: W3CAnnotationBody | W3CAnnotationBody[]
|
|
12
|
-
|
|
13
|
-
target: W3CAnnotationTarget | W3CAnnotationTarget[];
|
|
14
|
-
|
|
15
|
-
[key: string]: any;
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface W3CAnnotationBody {
|
|
20
|
-
|
|
21
|
-
id?: string;
|
|
22
|
-
|
|
23
|
-
type?: string;
|
|
24
|
-
|
|
25
|
-
purpose?: string;
|
|
26
|
-
|
|
27
|
-
value?: string;
|
|
28
|
-
|
|
29
|
-
source?: string;
|
|
30
|
-
|
|
31
|
-
created?: Date;
|
|
32
|
-
|
|
33
|
-
creator?: {
|
|
34
|
-
|
|
35
|
-
type?: string;
|
|
36
|
-
|
|
37
|
-
id: string;
|
|
38
|
-
|
|
39
|
-
name?: string;
|
|
40
|
-
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface W3CAnnotationTarget {
|
|
46
|
-
|
|
47
|
-
source: string;
|
|
48
|
-
|
|
49
|
-
selector?: W3CSelector | W3CSelector[];
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface W3CSelector {
|
|
54
|
-
|
|
55
|
-
type: string;
|
|
56
|
-
|
|
57
|
-
conformsTo?: string;
|
|
58
|
-
|
|
59
|
-
value: string;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// https://stackoverflow.com/questions/6122571/simple-non-secure-hash-function-for-javascript
|
|
63
|
-
const hashCode = (obj: Object): string => {
|
|
64
|
-
const str = JSON.stringify(obj);
|
|
65
|
-
|
|
66
|
-
let hash = 0;
|
|
67
|
-
|
|
68
|
-
for (let i = 0, len = str.length; i < len; i++) {
|
|
69
|
-
let chr = str.charCodeAt(i);
|
|
70
|
-
hash = (hash << 5) - hash + chr;
|
|
71
|
-
hash |= 0; // Convert to 32bit integer
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return `${hash}`;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Helper to crosswalk the W3C annotation body to a list of core AnnotationBody objects.
|
|
79
|
-
*/
|
|
80
|
-
export const parseW3CBodies = (
|
|
81
|
-
body: W3CAnnotationBody | W3CAnnotationBody[],
|
|
82
|
-
annotationId: string
|
|
83
|
-
): AnnotationBody[] => (Array.isArray(body) ? body : [body]).map(body => {
|
|
84
|
-
|
|
85
|
-
// Exctract properties that conform to the internal model, but keep custom props
|
|
86
|
-
const { id, type, purpose, value, created, creator, ...rest } = body;
|
|
87
|
-
|
|
88
|
-
// The internal model strictly requires IDs. (Because multi-user scenarios
|
|
89
|
-
// will have problems without them.) In the W3C model, bodys *may* have IDs.
|
|
90
|
-
// We'll create ad-hoc IDs for bodies without IDs, but want to make sure that
|
|
91
|
-
// generating the ID is idempotent: the same body should always get the same ID.
|
|
92
|
-
// This will avoid unexpected results when checking for equality.
|
|
93
|
-
return {
|
|
94
|
-
id: id || `temp-${hashCode(body)}`,
|
|
95
|
-
annotation: annotationId,
|
|
96
|
-
type,
|
|
97
|
-
purpose,
|
|
98
|
-
value,
|
|
99
|
-
created,
|
|
100
|
-
creator: creator ?
|
|
101
|
-
typeof creator === 'object' ? { ...creator }: creator :
|
|
102
|
-
undefined,
|
|
103
|
-
...rest
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
/** Serialization helper to remove core-specific fields from the annotation body **/
|
|
109
|
-
export const serializeW3CBodies = (bodies: AnnotationBody[]): W3CAnnotationBody[] =>
|
|
110
|
-
bodies.map(b => {
|
|
111
|
-
const w3c = { ...b };
|
|
112
|
-
delete w3c.annotation;
|
|
113
|
-
|
|
114
|
-
if (w3c.id?.startsWith('temp-'))
|
|
115
|
-
delete w3c.id;
|
|
116
|
-
|
|
117
|
-
return w3c;
|
|
118
|
-
});
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import type { User } from '../model';
|
|
2
|
-
import type { Appearance } from './Appearance';
|
|
3
|
-
import type { PresentUser } from './PresentUser';
|
|
4
|
-
import { DEFAULT_PALETTE } from './ColorPalette';
|
|
5
|
-
|
|
6
|
-
export interface AppearanceProvider {
|
|
7
|
-
|
|
8
|
-
addUser(presenceKey: string, user: User): Appearance;
|
|
9
|
-
|
|
10
|
-
removeUser(user: PresentUser): void;
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const defaultColorProvider = () => {
|
|
15
|
-
|
|
16
|
-
const unassignedColors = [...DEFAULT_PALETTE];
|
|
17
|
-
|
|
18
|
-
const assignRandomColor = () => {
|
|
19
|
-
const rnd = Math.floor(Math.random() * unassignedColors.length);
|
|
20
|
-
const color = unassignedColors[rnd];
|
|
21
|
-
|
|
22
|
-
unassignedColors.splice(rnd, 1);
|
|
23
|
-
|
|
24
|
-
return color;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const releaseColor = (color: string) =>
|
|
28
|
-
unassignedColors.push(color);
|
|
29
|
-
|
|
30
|
-
return { assignRandomColor, releaseColor };
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export const createDefaultAppearenceProvider = () => {
|
|
35
|
-
|
|
36
|
-
const colorProvider = defaultColorProvider();
|
|
37
|
-
|
|
38
|
-
const addUser = (presenceKey: string, user: User): Appearance => {
|
|
39
|
-
const color = colorProvider.assignRandomColor();
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
label: user.name || user.id,
|
|
43
|
-
avatar: user.avatar,
|
|
44
|
-
color
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const removeUser = (user: PresentUser) =>
|
|
49
|
-
colorProvider.releaseColor(user.appearance.color);
|
|
50
|
-
|
|
51
|
-
return { addUser, removeUser }
|
|
52
|
-
|
|
53
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
// SEABORN_BRIGHT
|
|
2
|
-
export const DEFAULT_PALETTE: Palette = [
|
|
3
|
-
'#ff7c00', // orange
|
|
4
|
-
'#1ac938', // green
|
|
5
|
-
'#e8000b', // red
|
|
6
|
-
'#8b2be2', // purple
|
|
7
|
-
'#9f4800', // brown
|
|
8
|
-
'#f14cc1', // pink
|
|
9
|
-
'#ffc400', // khaki
|
|
10
|
-
'#00d7ff', // cyan
|
|
11
|
-
'#023eff' // blue
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
export type Palette = string[];
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { nanoid } from 'nanoid';
|
|
2
|
-
import { createNanoEvents, type Unsubscribe } from 'nanoevents';
|
|
3
|
-
import type { User } from '../model';
|
|
4
|
-
import type { PresentUser } from './PresentUser';
|
|
5
|
-
import type { PresenceEvents } from './PresenceEvents';
|
|
6
|
-
import { createDefaultAppearenceProvider } from './AppearanceProvider';
|
|
7
|
-
import type { AppearanceProvider } from './AppearanceProvider';
|
|
8
|
-
|
|
9
|
-
export interface PresenceState {
|
|
10
|
-
|
|
11
|
-
// Get users currently present to this room
|
|
12
|
-
getPresentUsers(): PresentUser[];
|
|
13
|
-
|
|
14
|
-
// Notify of a given present user's activity on the given annotations
|
|
15
|
-
notifyActivity(presenceKey: string, annotationIds: string[]): void;
|
|
16
|
-
|
|
17
|
-
// Add a listener for the given presence event
|
|
18
|
-
on<E extends keyof PresenceEvents>(event: E, callback: PresenceEvents[E]): Unsubscribe;
|
|
19
|
-
|
|
20
|
-
// Initial sync - which users are present under which keys
|
|
21
|
-
syncUsers(state: { presenceKey: string, user: User }[]): void;
|
|
22
|
-
|
|
23
|
-
// Update the selection state for the given prresent user
|
|
24
|
-
updateSelection(presenceKey: string, selection: string[] | null): void;
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const isListEqual = (listA: any[], listB: any[]) =>
|
|
29
|
-
listA.every(a => listA.includes(a)) && listB.every(b => listA.includes(b));
|
|
30
|
-
|
|
31
|
-
// This client's presence key
|
|
32
|
-
export const PRESENCE_KEY = nanoid();
|
|
33
|
-
|
|
34
|
-
export const createPresenceState = (
|
|
35
|
-
appearanceProvider: AppearanceProvider = createDefaultAppearenceProvider()
|
|
36
|
-
): PresenceState => {
|
|
37
|
-
|
|
38
|
-
const emitter = createNanoEvents<PresenceEvents>();
|
|
39
|
-
|
|
40
|
-
const presentUsers = new Map<string, PresentUser>();
|
|
41
|
-
|
|
42
|
-
const selectionStates = new Map<string, string[]>();
|
|
43
|
-
|
|
44
|
-
const addUser = (presenceKey: string, user: User) => {
|
|
45
|
-
if (presentUsers.has(presenceKey)) {
|
|
46
|
-
console.warn('Attempt to add user that is already present', presenceKey, user);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const appearance = appearanceProvider.addUser(presenceKey, user);
|
|
51
|
-
|
|
52
|
-
presentUsers.set(presenceKey, {
|
|
53
|
-
...user,
|
|
54
|
-
presenceKey,
|
|
55
|
-
appearance
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const removeUser = (presenceKey: string) => {
|
|
60
|
-
const user = presentUsers.get(presenceKey);
|
|
61
|
-
if (!user) {
|
|
62
|
-
console.warn('Attempt to remove user that is not present', presenceKey);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
appearanceProvider.removeUser(user);
|
|
67
|
-
|
|
68
|
-
presentUsers.delete(presenceKey);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const syncUsers = (state: { presenceKey: string, user: User }[]) => {
|
|
72
|
-
// const keys = new Set(others.map(s => s.presenceKey));
|
|
73
|
-
const keys = new Set(state.map(s => s.presenceKey));
|
|
74
|
-
|
|
75
|
-
// These users need to be added to the presentUsers list
|
|
76
|
-
// const toAdd = others.filter(({ presenceKey }) => !presentUsers.has(presenceKey));
|
|
77
|
-
const toAdd = state.filter(({ presenceKey }) => !presentUsers.has(presenceKey));
|
|
78
|
-
|
|
79
|
-
// These users need to be dropped from the list
|
|
80
|
-
const toRemove = Array.from(presentUsers.values()).filter(presentUser =>
|
|
81
|
-
!keys.has(presentUser.presenceKey));
|
|
82
|
-
|
|
83
|
-
toAdd.forEach(({ presenceKey, user }) => addUser(presenceKey, user));
|
|
84
|
-
|
|
85
|
-
toRemove.forEach(user => {
|
|
86
|
-
const { presenceKey } = user;
|
|
87
|
-
|
|
88
|
-
// If this user has a selection, fire deselect event
|
|
89
|
-
if (selectionStates.has(presenceKey))
|
|
90
|
-
emitter.emit('selectionChange', user, null);
|
|
91
|
-
|
|
92
|
-
removeUser(presenceKey)
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
if (toAdd.length > 0 || toRemove.length > 0)
|
|
96
|
-
emitter.emit('presence', getPresentUsers());
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const notifyActivity = (presenceKey: string, annotationIds: string[]) => {
|
|
100
|
-
const user = presentUsers.get(presenceKey);
|
|
101
|
-
|
|
102
|
-
if (!user) {
|
|
103
|
-
console.warn('Activity notification from user that is not present');
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const currentSelection = selectionStates.get(presenceKey);
|
|
108
|
-
|
|
109
|
-
// Was there a selection change we might have missed?
|
|
110
|
-
if (!currentSelection || !isListEqual(currentSelection, annotationIds)) {
|
|
111
|
-
selectionStates.set(presenceKey, annotationIds);
|
|
112
|
-
emitter.emit('selectionChange', user, annotationIds);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const updateSelection = (presenceKey: string, selection: string[] | null) => {
|
|
117
|
-
const from = presentUsers.get(presenceKey);
|
|
118
|
-
if (!from) {
|
|
119
|
-
console.warn('Selection change for user that is not present', presenceKey);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (selection)
|
|
124
|
-
selectionStates.set(presenceKey, selection);
|
|
125
|
-
else
|
|
126
|
-
selectionStates.delete(presenceKey);
|
|
127
|
-
|
|
128
|
-
emitter.emit('selectionChange', from, selection);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const getPresentUsers = () =>
|
|
132
|
-
[...Array.from(presentUsers.values())];
|
|
133
|
-
|
|
134
|
-
const on = <E extends keyof PresenceEvents>(event: E, callback: PresenceEvents[E]) =>
|
|
135
|
-
emitter.on(event, callback);
|
|
136
|
-
|
|
137
|
-
return {
|
|
138
|
-
getPresentUsers,
|
|
139
|
-
notifyActivity,
|
|
140
|
-
on,
|
|
141
|
-
syncUsers,
|
|
142
|
-
updateSelection
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
}
|
package/src/state/Hover.ts
DELETED
|
@@ -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
|
-
}
|