Skip to content

Commit b929d3c

Browse files
Add AceRadarView
1 parent 8aac144 commit b929d3c

File tree

4 files changed

+134
-53
lines changed

4 files changed

+134
-53
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@
1111
],
1212
"license": "MIT",
1313
"scripts": {
14-
"build": "yarn run build:yarn && yarn run build:dist",
14+
"build": "yarn run build:yarn && yarn run build:dist && yarn run build:tsc",
1515
"build:yarn": "babel source --extensions .ts --out-dir distribution --presets=@babel/preset-env,@babel/preset-typescript",
1616
"build:dist": "cross-env NODE_OPTIONS=--openssl-legacy-provider webpack",
17+
"build:tsc": "tsc",
1718
"test": "echo 'doing nothing'",
1819
"docs": "jsdoc source/*.js -d ./docs/"
1920
},
@@ -36,6 +37,7 @@
3637
"jsdom": "^16.3.0",
3738
"mocha": "^8.1.1",
3839
"react-ace": "^14.0.1",
40+
"tsc": "^2.0.4",
3941
"typescript": "^5.7.3",
4042
"webpack": "^5.98.0",
4143
"webpack-cli": "^6.0.1"

source/sharedb-ace-binding.ts

Lines changed: 126 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,22 @@
55
* @license MIT
66
*/
77

8+
// TODO: support reconnects with same id
9+
10+
// TODO: We keep getting:
11+
// connection to 'ws...' failed: Invalid frame header
12+
813
import WebSocket from 'reconnecting-websocket';
914
import Logdown from 'logdown';
1015
import sharedb from 'sharedb/lib/sharedb';
1116
import type {
1217
AceMultiCursorManager,
1318
AceMultiSelectionManager,
19+
AceRadarView,
1420
IRangeData
1521
} from '@convergencelabs/ace-collab-ext';
16-
import { AceRangeUtil } from '@convergencelabs/ace-collab-ext';
22+
import { IIndexRange } from '@convergencelabs/ace-collab-ext/dist/types/IndexRange';
23+
import { AceViewportUtil, AceRangeUtil } from '@convergencelabs/ace-collab-ext';
1724
import type { Ace, EditSession } from 'ace-builds';
1825
import type { IAceEditor } from 'react-ace/lib/types';
1926
import type { SharedbAcePlugin, SharedbAceUser } from './types';
@@ -31,6 +38,7 @@ interface SharedbAceBindingOptions {
3138
user: SharedbAceUser;
3239
cursorManager: AceMultiCursorManager;
3340
selectionManager: AceMultiSelectionManager;
41+
radarManager: AceRadarView;
3442
usersPresence: sharedb.Presence;
3543
pluginWS?: WebSocket;
3644
path: string[];
@@ -41,7 +49,9 @@ interface SharedbAceBindingOptions {
4149
interface PresenceUpdate {
4250
user: SharedbAceUser;
4351
cursorPos?: Ace.Point;
44-
ranges?: IRangeData[];
52+
selectionRange?: IRangeData[];
53+
radarViewRows?: IIndexRange;
54+
radarCursorRow?: number;
4555
}
4656

4757
class SharedbAceBinding {
@@ -55,6 +65,7 @@ class SharedbAceBinding {
5565

5666
cursorManager: AceMultiCursorManager;
5767
selectionManager: AceMultiSelectionManager;
68+
radarManager: AceRadarView;
5869

5970
usersPresence: sharedb.Presence;
6071

@@ -64,7 +75,7 @@ class SharedbAceBinding {
6475
// This events need to be suppressed to prevent infinite looping
6576
suppress = false;
6677

67-
localPresence?: sharedb.LocalPresence;
78+
localPresence?: sharedb.LocalPresence<PresenceUpdate>;
6879

6980
onError: (err: unknown) => unknown;
7081

@@ -112,6 +123,7 @@ class SharedbAceBinding {
112123
this.user = options.user;
113124
this.cursorManager = options.cursorManager;
114125
this.selectionManager = options.selectionManager;
126+
this.radarManager = options.radarManager;
115127
this.usersPresence = options.usersPresence;
116128
this.onError = options.onError;
117129
this.logger = Logdown('shareace');
@@ -139,7 +151,9 @@ class SharedbAceBinding {
139151

140152
this.cursorManager.removeAll();
141153
this.selectionManager.removeAll();
142-
this.initializePresence();
154+
// TODO: Remove all views for radarManager
155+
// this.radarManager.removeView();
156+
this.initializeLocalPresence();
143157
for (const [id, update] of Object.entries(this.usersPresence.remotePresences)) {
144158
this.initializeRemotePresence(id, update);
145159
}
@@ -149,11 +163,13 @@ class SharedbAceBinding {
149163
* Listens to the changes
150164
*/
151165
listen = () => {
166+
// TODO: Also update view on window resize
152167
this.session.on('change', this.onLocalChange);
168+
this.session.on('changeScrollTop', this.onLocalChangeScrollTop);
153169
this.doc.on('op', this.onRemoteChange);
154170
this.doc.on('load', this.onRemoteReload);
155171

156-
this.usersPresence.on('receive', this.onRemotePresenceUpdate);
172+
this.usersPresence.on('receive', this.onPresenceUpdate);
157173
this.session.selection.on('changeCursor', this.onLocalCursorChange);
158174
this.session.selection.on('changeSelection', this.onLocalSelectionChange);
159175
};
@@ -163,10 +179,11 @@ class SharedbAceBinding {
163179
*/
164180
unlisten = () => {
165181
this.session.removeListener('change', this.onLocalChange);
182+
this.session.off('changeScrollTop', this.onLocalChangeScrollTop);
166183
this.doc.off('op', this.onRemoteChange);
167184
this.doc.off('load', this.onRemoteReload);
168185

169-
this.usersPresence.off('receive', this.onRemotePresenceUpdate);
186+
this.usersPresence.off('receive', this.onPresenceUpdate);
170187
this.session.selection.off('changeCursor', this.onLocalCursorChange);
171188
this.session.selection.off('changeSelection', this.onLocalSelectionChange);
172189
};
@@ -179,6 +196,7 @@ class SharedbAceBinding {
179196
* @throws {Error} throws error if delta is malformed
180197
*/
181198
deltaTransform = (delta: Ace.Delta): sharedb.Op => {
199+
// TODO: Use SubtypeOp to declare new operations
182200
const aceDoc = this.session.getDocument();
183201
const start = aceDoc.positionToIndex(delta.start);
184202
const end = aceDoc.positionToIndex(delta.end);
@@ -329,7 +347,7 @@ class SharedbAceBinding {
329347
}
330348
};
331349

332-
onRemotePresenceUpdate = (id: string, update: PresenceUpdate) => {
350+
onPresenceUpdate = (id: string, update: PresenceUpdate) => {
333351
// TODO: logger and error handling
334352
// TODO: separate into multiple handlers
335353
if (update === null) {
@@ -342,77 +360,136 @@ class SharedbAceBinding {
342360
this.selectionManager.removeSelection(id);
343361
// eslint-disable-next-line no-empty
344362
} catch {}
345-
} else {
346-
if (update.cursorPos) {
347-
try {
348-
this.cursorManager.setCursor(id, update.cursorPos);
349-
} catch {
350-
this.cursorManager.addCursor(id, update.user.name, update.user.color, update.cursorPos);
351-
}
363+
364+
try {
365+
this.radarManager.removeView(id);
366+
// eslint-disable-next-line no-empty
367+
} catch {}
368+
369+
return;
370+
}
371+
372+
if (update.cursorPos) {
373+
try {
374+
this.cursorManager.setCursor(id, update.cursorPos);
375+
} catch {
376+
this.cursorManager.addCursor(id, update.user.name, update.user.color, update.cursorPos);
352377
}
378+
}
353379

354-
if (update.ranges) {
355-
const ranges = AceRangeUtil.fromJson(update.ranges);
356-
try {
357-
this.selectionManager.setSelection(id, ranges);
358-
} catch {
359-
this.selectionManager.addSelection(id, update.user.name, update.user.color, ranges);
360-
}
380+
if (update.selectionRange) {
381+
const ranges = AceRangeUtil.fromJson(update.selectionRange);
382+
try {
383+
this.selectionManager.setSelection(id, ranges);
384+
} catch {
385+
this.selectionManager.addSelection(id, update.user.name, update.user.color, ranges);
386+
}
387+
}
388+
389+
if (update.radarViewRows) {
390+
const intialRows = AceViewportUtil.indicesToRows(
391+
this.editor,
392+
update.radarViewRows.start,
393+
update.radarViewRows.end
394+
);
395+
try {
396+
this.radarManager.setViewRows(id, intialRows);
397+
} catch {
398+
this.radarManager.addView(
399+
id,
400+
update.user.name,
401+
update.user.color,
402+
intialRows,
403+
update.radarCursorRow || 0
404+
);
361405
}
362406
}
363407
};
364408

409+
onLocalChangeScrollTop = (scrollTop: number) => {
410+
// TODO: logger and error handling
411+
const viewportIndices = AceViewportUtil.getVisibleIndexRange(this.editor);
412+
this.localPresence?.submit({
413+
user: this.user,
414+
radarViewRows: viewportIndices
415+
});
416+
};
417+
365418
onLocalCursorChange = () => {
419+
// TODO: logger and error handling
366420
const pos = this.session.selection.getCursor();
367-
this.updateCursorPresence(pos);
421+
this.localPresence?.submit({
422+
user: this.user,
423+
cursorPos: pos,
424+
radarCursorRow: pos.row
425+
});
368426
};
369427

370428
onLocalSelectionChange = () => {
429+
// TODO: logger and error handling
371430
const ranges = this.session.selection.getAllRanges();
372-
this.updateSelectionPresence(AceRangeUtil.toJson(ranges));
431+
this.localPresence?.submit({
432+
user: this.user,
433+
selectionRange: AceRangeUtil.toJson(ranges)
434+
});
373435
};
374436

375-
initializePresence = () => {
437+
initializeLocalPresence = () => {
376438
// TODO: logger and error handling
377439
this.localPresence = this.usersPresence.create();
378440
const cursorPos = this.session.selection.getCursor();
379441
const ranges = this.session.selection.getAllRanges();
442+
443+
const initialIndices = AceViewportUtil.getVisibleIndexRange(this.editor);
444+
380445
this.localPresence.submit({
381446
user: this.user,
382447
cursorPos,
383-
ranges
448+
selectionRange: ranges,
449+
radarViewRows: initialIndices,
450+
radarCursorRow: cursorPos.row
384451
});
385452
};
386453

387-
initializeRemotePresence = (id: string, update: Required<PresenceUpdate>) => {
388-
try {
389-
this.cursorManager.setCursor(id, update.cursorPos);
390-
} catch {
391-
this.cursorManager.addCursor(id, update.user.name, update.user.color, update.cursorPos);
454+
// TODO: Actually the same as onPresenceUpdate
455+
initializeRemotePresence = (id: string, update: PresenceUpdate) => {
456+
if (update.cursorPos) {
457+
try {
458+
this.cursorManager.setCursor(id, update.cursorPos);
459+
} catch {
460+
this.cursorManager.addCursor(id, update.user.name, update.user.color, update.cursorPos);
461+
}
392462
}
393463

394-
const ranges = AceRangeUtil.fromJson(update.ranges);
395-
try {
396-
this.selectionManager.setSelection(id, ranges);
397-
} catch {
398-
this.selectionManager.addSelection(id, update.user.name, update.user.color, ranges);
464+
if (update.selectionRange) {
465+
const ranges = AceRangeUtil.fromJson(update.selectionRange);
466+
try {
467+
this.selectionManager.setSelection(id, ranges);
468+
} catch {
469+
this.selectionManager.addSelection(id, update.user.name, update.user.color, ranges);
470+
}
399471
}
400-
};
401472

402-
updateCursorPresence = (newCursorPos: Ace.Point) => {
403-
// TODO: logger and error handling
404-
this.localPresence?.submit({
405-
user: this.user,
406-
cursorPos: newCursorPos
407-
});
408-
};
473+
if (update.radarViewRows) {
474+
const rows = AceViewportUtil.indicesToRows(
475+
this.editor,
476+
update.radarViewRows.start,
477+
update.radarViewRows.end
478+
);
409479

410-
updateSelectionPresence = (newRanges: IRangeData[]) => {
411-
// TODO: logger and error handling
412-
this.localPresence?.submit({
413-
user: this.user,
414-
ranges: newRanges
415-
});
480+
try {
481+
this.radarManager.setViewRows(id, rows);
482+
this.radarManager.setCursorRow(id, update.radarCursorRow || 0);
483+
} catch {
484+
this.radarManager.addView(
485+
id,
486+
update.user.name,
487+
update.user.color,
488+
rows,
489+
update.radarCursorRow || 0
490+
);
491+
}
492+
}
416493
};
417494

418495
destroyPresence = () => {

source/sharedb-ace.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,19 +132,20 @@ class SharedbAce extends EventEmitter {
132132
ace: IAceEditor,
133133
cursorManager: AceMultiCursorManager,
134134
selectionManager: AceMultiSelectionManager,
135-
// TODO: Add this back in
136-
// radarManager: AceRadarView,
135+
radarManager: AceRadarView,
137136
path: string[],
138137
plugins: SharedbAcePlugin[]
139138
) => {
139+
// TODO: Make these managers optional
140+
140141
const sharePath = path || [];
141142
const binding = new SharedbAceBinding({
142143
ace,
143144
doc: this.doc,
144145
user: this.user,
145146
cursorManager,
146147
selectionManager,
147-
// radarManager,
148+
radarManager,
148149
usersPresence: this.usersPresence,
149150
path: sharePath,
150151
pluginWS: this.pluginWS,

source/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type WebSocket from 'reconnecting-websocket';
22

33
export interface SharedbAceUser {
4+
id: string;
45
name: string;
56
color: string;
67
}

0 commit comments

Comments
 (0)