Skip to content

Commit 0fd6f54

Browse files
committed
Add observer sync from editor
1 parent 56bd140 commit 0fd6f54

File tree

3 files changed

+292
-17
lines changed

3 files changed

+292
-17
lines changed

src/index.ts

+7-16
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,10 @@
77
*
88
* @module Observer
99
*/
10-
11-
import { EventHandle } from './event-handle';
12-
import { Events } from './events';
13-
import { History } from './history';
14-
import { Observer } from './observer';
15-
import { ObserverHistory } from './observer-history';
16-
import { ObserverList } from './observer-list';
17-
18-
export {
19-
EventHandle,
20-
Events,
21-
History,
22-
Observer,
23-
ObserverHistory,
24-
ObserverList
25-
};
10+
export * from './event-handle';
11+
export * from './events';
12+
export * from './history';
13+
export * from './observer';
14+
export * from './observer-history';
15+
export * from './observer-list';
16+
export * from './observer-sync';

src/observer-sync.ts

+283
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
import { Events } from './events';
2+
import { Observer } from './observer';
3+
import { ObserverList } from './observer-list';
4+
5+
/**
6+
* Observer sync arguments
7+
*/
8+
export type ObserverSyncArgs = {
9+
item: Observer;
10+
enabled?: boolean;
11+
prefix?: (string | number)[];
12+
paths?: string[];
13+
}
14+
15+
/**
16+
* ShareDB operation
17+
*/
18+
export type ShareDBOperation = {
19+
p: (string | number)[];
20+
oi?: any;
21+
li?: any;
22+
lm?: any;
23+
od?: any;
24+
}
25+
26+
/**
27+
* Observer sync class
28+
*/
29+
class ObserverSync extends Events {
30+
item: Observer;
31+
32+
private _enabled: boolean = true;
33+
34+
private _prefix: (string | number)[] = [];
35+
36+
private _paths: string[] | null = null;
37+
38+
constructor(args: ObserverSyncArgs) {
39+
super();
40+
41+
this.item = args.item;
42+
this._enabled = args.enabled || true;
43+
this._prefix = args.prefix || [];
44+
this._paths = args.paths || null;
45+
46+
this._initialize();
47+
}
48+
49+
private _initialize() {
50+
const self = this;
51+
const item = this.item;
52+
53+
// object/array set
54+
item.on('*:set', function (path: string, value: any, valueOld: any) {
55+
if (!self._enabled) return;
56+
57+
// if this happens it's a bug
58+
if (item.sync !== self) {
59+
console.error('Garbage Observer Sync still pointing to item', item);
60+
}
61+
62+
// check if path is allowed
63+
if (self._paths) {
64+
let allowedPath = false;
65+
for (let i = 0; i < self._paths.length; i++) {
66+
if (path.indexOf(self._paths[i]) !== -1) {
67+
allowedPath = true;
68+
break;
69+
}
70+
}
71+
72+
// path is not allowed
73+
if (!allowedPath) {
74+
return;
75+
}
76+
}
77+
78+
// full path
79+
const p = self._prefix.concat(path.split('.'));
80+
81+
// need jsonify
82+
if (value instanceof Observer || value instanceof ObserverList) {
83+
value = value.json();
84+
}
85+
86+
// can be array value
87+
const ind = path.lastIndexOf('.');
88+
if (ind !== -1 && (this.get(path.slice(0, ind)) instanceof Array)) {
89+
// array index should be int
90+
p[p.length - 1] = parseInt(p[p.length - 1] as string, 10);
91+
92+
// emit operation: list item set
93+
self.emit('op', {
94+
p: p,
95+
li: value,
96+
ld: valueOld
97+
});
98+
} else {
99+
// emit operation: object item set
100+
const obj: {
101+
p: (string | number)[];
102+
oi: any;
103+
od?: any;
104+
} = {
105+
p: p,
106+
oi: value
107+
};
108+
109+
if (valueOld !== undefined) {
110+
obj.od = valueOld;
111+
}
112+
113+
self.emit('op', obj);
114+
}
115+
});
116+
117+
// unset
118+
item.on('*:unset', (path: string, _value: any) => {
119+
if (!self._enabled) return;
120+
121+
self.emit('op', {
122+
p: self._prefix.concat(path.split('.')),
123+
od: null
124+
});
125+
});
126+
127+
// list move
128+
item.on('*:move', (path: string, _value: any, ind: number, indOld: number) => {
129+
if (!self._enabled) return;
130+
self.emit('op', {
131+
p: self._prefix.concat(path.split('.')).concat([indOld]),
132+
lm: ind
133+
});
134+
});
135+
136+
// list remove
137+
item.on('*:remove', (path: string, value: any, ind: number) => {
138+
if (!self._enabled) return;
139+
140+
// need jsonify
141+
if (value instanceof Observer || value instanceof ObserverList) {
142+
value = value.json();
143+
}
144+
145+
self.emit('op', {
146+
p: self._prefix.concat(path.split('.')).concat([ind]),
147+
ld: value
148+
});
149+
});
150+
151+
// list insert
152+
item.on('*:insert', (path: string, value: any, ind: number) => {
153+
if (!self._enabled) return;
154+
155+
// need jsonify
156+
if (value instanceof Observer || value instanceof ObserverList) {
157+
value = value.json();
158+
}
159+
160+
self.emit('op', {
161+
p: self._prefix.concat(path.split('.')).concat([ind]),
162+
li: value
163+
});
164+
});
165+
}
166+
167+
/**
168+
* Write operation to the observer
169+
*
170+
* @param op - The sharedb operation
171+
*/
172+
write(op: ShareDBOperation) {
173+
// disable history if available
174+
let historyReEnable = false;
175+
if (this.item.history && this.item.history.enabled) {
176+
historyReEnable = true;
177+
this.item.history.enabled = false;
178+
}
179+
180+
if (op.hasOwnProperty('oi')) {
181+
// set key value
182+
const path = op.p.slice(this._prefix.length).join('.');
183+
184+
this._enabled = false;
185+
this.item.set(path, op.oi, false, true);
186+
this._enabled = true;
187+
188+
189+
} else if (op.hasOwnProperty('ld') && op.hasOwnProperty('li')) {
190+
// set array value
191+
const path = op.p.slice(this._prefix.length).join('.');
192+
193+
this._enabled = false;
194+
this.item.set(path, op.li, false, true);
195+
this._enabled = true;
196+
197+
198+
} else if (op.hasOwnProperty('ld')) {
199+
// delete item
200+
const path = op.p.slice(this._prefix.length, -1).join('.');
201+
const ind = op.p[op.p.length - 1] as number;
202+
203+
this._enabled = false;
204+
this.item.remove(path, ind, false, true);
205+
this._enabled = true;
206+
207+
208+
} else if (op.hasOwnProperty('li')) {
209+
// add item
210+
const path = op.p.slice(this._prefix.length, -1).join('.');
211+
const ind = op.p[op.p.length - 1] as number;
212+
213+
this._enabled = false;
214+
this.item.insert(path, op.li, ind, false, true);
215+
this._enabled = true;
216+
217+
218+
} else if (op.hasOwnProperty('lm')) {
219+
// item moved
220+
const path = op.p.slice(this._prefix.length, -1).join('.');
221+
const indOld = op.p[op.p.length - 1] as number;
222+
const ind = op.lm;
223+
224+
this._enabled = false;
225+
this.item.move(path, indOld, ind, false, true);
226+
this._enabled = true;
227+
228+
229+
} else if (op.hasOwnProperty('od')) {
230+
// unset key value
231+
const path = op.p.slice(this._prefix.length).join('.');
232+
this._enabled = false;
233+
this.item.unset(path, false, true);
234+
this._enabled = true;
235+
236+
237+
} else {
238+
console.log('unknown operation', op);
239+
}
240+
241+
// reenable history
242+
if (historyReEnable) {
243+
this.item.history.enabled = true;
244+
}
245+
246+
this.emit('sync', op);
247+
}
248+
249+
/**
250+
* Enabled state of the observer sync
251+
*/
252+
set enabled(value) {
253+
this._enabled = !!value;
254+
}
255+
256+
get enabled() {
257+
return this._enabled;
258+
}
259+
260+
/**
261+
* Prefix of the observer sync
262+
*/
263+
set prefix(value) {
264+
this._prefix = value || [];
265+
}
266+
267+
get prefix() {
268+
return this._prefix;
269+
}
270+
271+
/**
272+
* Paths of the observer sync
273+
*/
274+
set paths(value) {
275+
this._paths = value || null;
276+
}
277+
278+
get paths() {
279+
return this._paths;
280+
}
281+
}
282+
283+
export { ObserverSync };

src/observer.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Events } from './events';
22
import { ObserverHistory } from './observer-history';
3+
import { ObserverSync } from './observer-sync';
34
import { arrayEquals } from './utils';
45

56
/**
@@ -51,7 +52,7 @@ class Observer extends Events {
5152

5253
history: ObserverHistory;
5354

54-
sync: any;
55+
sync: ObserverSync;
5556

5657
schema: any;
5758

0 commit comments

Comments
 (0)