Skip to content

Commit 5fc8038

Browse files
authored
Merge pull request #614 from streamich/overlay-5
`Overlay` refresh improvements
2 parents 7773bec + c0e31c8 commit 5fc8038

File tree

17 files changed

+514
-166
lines changed

17 files changed

+514
-166
lines changed

src/json-crdt-extensions/peritext/Peritext.ts

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,16 @@ import {Model} from '../../json-crdt/model';
1313
import {CONST, updateNum} from '../../json-hash';
1414
import {SESSION} from '../../json-crdt-patch/constants';
1515
import {s} from '../../json-crdt-patch';
16+
import {ExtraSlices} from './slice/ExtraSlices';
1617
import type {ITimestampStruct} from '../../json-crdt-patch/clock';
1718
import type {Printable} from 'tree-dump/lib/types';
18-
import type {SliceType} from './types';
1919
import type {MarkerSlice} from './slice/MarkerSlice';
20+
import type {SliceSchema, SliceType} from './slice/types';
21+
import type {SchemaToJsonNode} from '../../json-crdt/schema/types';
22+
23+
const EXTRA_SLICES_SCHEMA = s.vec(s.arr<SliceSchema>([]));
24+
25+
type SlicesModel = Model<SchemaToJsonNode<typeof EXTRA_SLICES_SCHEMA>>;
2026

2127
/**
2228
* Context for a Peritext instance. Contains all the data and methods needed to
@@ -46,27 +52,33 @@ export class Peritext implements Printable {
4652
public readonly editor: Editor;
4753
public readonly overlay = new Overlay(this);
4854

55+
/**
56+
* Creates a new Peritext context.
57+
*
58+
* @param model JSON CRDT model of the document where the text is stored.
59+
* @param str The {@link StrNode} where the text is stored.
60+
* @param slices The {@link ArrNode} where the slices are stored.
61+
* @param extraSlicesModel The JSON CRDT model for the extra slices, which are
62+
* not persisted in the main document, but are shared with other users.
63+
* @param localSlicesModel The JSON CRDT model for the local slices, which are
64+
* not persisted in the main document and are not shared with other
65+
* users. The local slices capture current-user-only annotations, such
66+
* as the current user's selection.
67+
*/
4968
constructor(
5069
public readonly model: Model,
5170
public readonly str: StrNode,
5271
slices: ArrNode,
72+
extraSlicesModel: SlicesModel = Model.create(EXTRA_SLICES_SCHEMA, model.clock.sid - 1),
73+
localSlicesModel: SlicesModel = Model.create(EXTRA_SLICES_SCHEMA, SESSION.LOCAL),
5374
) {
5475
this.savedSlices = new Slices(this.model, slices, this.str);
55-
56-
const extraModel = Model.withLogicalClock(SESSION.GLOBAL)
57-
.setSchema(s.vec(s.arr([])))
58-
.fork(this.model.clock.sid + 1);
59-
this.extraSlices = new Slices(extraModel, extraModel.root.node().get(0)!, this.str);
60-
61-
// TODO: flush patches
62-
// TODO: remove `arr` tombstones
63-
const localModel = Model.withLogicalClock(SESSION.LOCAL).setSchema(s.vec(s.arr([])));
64-
const localApi = localModel.api;
76+
this.extraSlices = new ExtraSlices(extraSlicesModel, extraSlicesModel.root.node().get(0)!, this.str);
77+
const localApi = localSlicesModel.api;
6578
localApi.onLocalChange.listen(() => {
6679
localApi.flush();
6780
});
68-
this.localSlices = new LocalSlices(localModel, localModel.root.node().get(0)!, this.str);
69-
81+
this.localSlices = new LocalSlices(localSlicesModel, localSlicesModel.root.node().get(0)!, this.str);
7082
this.editor = new Editor(this, this.localSlices);
7183
}
7284

@@ -99,8 +111,8 @@ export class Peritext implements Printable {
99111
}
100112

101113
/**
102-
* Creates a point at a view position in the text. The `pos` argument specifies
103-
* the position of the character, not the gap between characters.
114+
* Creates a point at a view position in the text. The `pos` argument
115+
* specifies the position of the character, not the gap between characters.
104116
*
105117
* @param pos Position of the character in the text.
106118
* @param anchor Whether the point should attach before or after a character.
@@ -150,7 +162,8 @@ export class Peritext implements Printable {
150162
}
151163

152164
/**
153-
* Creates a range from two points, the points have to be in the correct order.
165+
* Creates a range from two points, the points have to be in the correct
166+
* order.
154167
*
155168
* @param start Start point of the range, must be before or equal to end.
156169
* @param end End point of the range, must be after or equal to start.
@@ -161,8 +174,8 @@ export class Peritext implements Printable {
161174
}
162175

163176
/**
164-
* A convenience method for creating a range from a view position and a length.
165-
* See {@link Range.at} for more information.
177+
* A convenience method for creating a range from a view position and a
178+
* length. See {@link Range.at} for more information.
166179
*
167180
* @param start Position in the text.
168181
* @param length Length of the range.
@@ -238,14 +251,15 @@ export class Peritext implements Printable {
238251

239252
public toString(tab: string = ''): string {
240253
const nl = () => '';
254+
const {savedSlices, extraSlices, localSlices} = this;
241255
return (
242256
this.constructor.name +
243257
printTree(tab, [
244-
(tab) => this.editor.cursor.toString(tab),
245-
nl,
246258
(tab) => this.str.toString(tab),
247259
nl,
248-
(tab) => this.savedSlices.toString(tab),
260+
savedSlices.size() ? (tab) => savedSlices.toString(tab) : null,
261+
extraSlices.size() ? (tab) => extraSlices.toString(tab) : null,
262+
localSlices.size() ? (tab) => localSlices.toString(tab) : null,
249263
nl,
250264
(tab) => this.overlay.toString(tab),
251265
])

src/json-crdt-extensions/peritext/editor/Cursor.ts

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {Point} from '../rga/Point';
2-
import {Range} from '../rga/Range';
32
import {CursorAnchor} from '../slice/constants';
43
import {PersistedSlice} from '../slice/PersistedSlice';
54

@@ -8,6 +7,8 @@ export class Cursor<T = string> extends PersistedSlice<T> {
87
return this.type as CursorAnchor;
98
}
109

10+
// ---------------------------------------------------------------- mutations
11+
1112
public set anchorSide(value: CursorAnchor) {
1213
this.update({type: value});
1314
}
@@ -20,42 +21,26 @@ export class Cursor<T = string> extends PersistedSlice<T> {
2021
return this.anchorSide === CursorAnchor.Start ? this.end : this.start;
2122
}
2223

23-
public set(start: Point<T>, end?: Point<T>, anchorSide: CursorAnchor = this.anchorSide): void {
24-
if (!end || end === start) end = start.clone();
25-
super.set(start, end);
24+
public set(start: Point<T>, end: Point<T> = start, anchorSide: CursorAnchor = this.anchorSide): void {
25+
this.start = start;
26+
this.end = end === start ? end.clone() : end;
2627
this.update({
2728
range: this,
2829
type: anchorSide,
2930
});
3031
}
3132

32-
/** TODO: Move to {@link PersistedSlice}. */
33-
public setAt(start: number, length: number = 0): void {
34-
let at = start;
35-
let len = length;
36-
if (len < 0) {
37-
at += len;
38-
len = -len;
39-
}
40-
const range = Range.at<T>(this.rga, start, length);
41-
const anchorSide = this.anchorSide;
42-
this.update({
43-
range,
44-
type: anchorSide !== this.anchorSide ? anchorSide : undefined,
45-
});
46-
}
47-
4833
/**
4934
* Move one of the edges of the cursor to a new point.
5035
*
5136
* @param point Point to set the edge to.
52-
* @param edge 0 for "focus", 1 for "anchor."
37+
* @param endpoint 0 for "focus", 1 for "anchor."
5338
*/
54-
public setEdge(point: Point<T>, edge: 0 | 1 = 0): void {
39+
public setEndpoint(point: Point<T>, endpoint: 0 | 1 = 0): void {
5540
if (this.start === this.end) this.end = this.end.clone();
5641
let anchor = this.anchor();
5742
let focus = this.focus();
58-
if (edge === 0) focus = point;
43+
if (endpoint === 0) focus = point;
5944
else anchor = point;
6045
if (focus.cmpSpatial(anchor) < 0) this.set(focus, anchor, CursorAnchor.End);
6146
else this.set(anchor, focus, CursorAnchor.Start);
@@ -64,9 +49,7 @@ export class Cursor<T = string> extends PersistedSlice<T> {
6449
public move(move: number): void {
6550
const {start, end} = this;
6651
start.move(move);
67-
if (start !== end) {
68-
end.move(move);
69-
}
52+
if (start !== end) end.move(move);
7053
this.set(start, end);
7154
}
7255

src/json-crdt-extensions/peritext/editor/Editor.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ import type {Range} from '../rga/Range';
88
import type {Peritext} from '../Peritext';
99
import type {Printable} from 'tree-dump/lib/types';
1010
import type {Point} from '../rga/Point';
11-
import type {SliceType} from '../types';
11+
import type {SliceType} from '../slice/types';
1212
import type {MarkerSlice} from '../slice/MarkerSlice';
1313
import type {Slices} from '../slice/Slices';
1414

15+
/**
16+
* Rename to `PeritextApi`.
17+
*/
1518
export class Editor implements Printable {
1619
/**
1720
* Cursor is the the current user selection. It can be a caret or a
@@ -25,6 +28,7 @@ export class Editor implements Printable {
2528
) {
2629
const point = txt.pointAbsStart();
2730
const range = txt.range(point, point.clone());
31+
// TODO: Add ability to remove cursor.
2832
this.cursor = slices.ins<Cursor, typeof Cursor>(range, SliceBehavior.Cursor, CursorAnchor.Start, undefined, Cursor);
2933
}
3034

src/json-crdt-extensions/peritext/overlay/MarkerOverlayPoint.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
import {printTree} from 'tree-dump/lib/printTree';
12
import {OverlayPoint} from './OverlayPoint';
2-
import {SliceType} from '../types';
3+
import type {SliceType} from '../slice/types';
34
import type {Anchor} from '../rga/constants';
45
import type {AbstractRga} from '../../../json-crdt/nodes/rga';
56
import type {ITimestampStruct} from '../../../json-crdt-patch/clock';
67
import type {MarkerSlice} from '../slice/MarkerSlice';
7-
import {printTree} from 'tree-dump/lib/printTree';
88

99
export class MarkerOverlayPoint extends OverlayPoint {
1010
/**

src/json-crdt-extensions/peritext/overlay/Overlay.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,18 @@ import type {Printable} from 'tree-dump/lib/types';
1717
import type {MutableSlice, Slice} from '../slice/types';
1818
import type {Slices} from '../slice/Slices';
1919

20+
/**
21+
* Overlay is a tree structure that represents all the intersections of slices
22+
* in the text. It is used to quickly find all the slices that overlap a
23+
* given point in the text. The overlay is a read-only structure, its state
24+
* is changed only by calling the `refresh` method, which updates the overlay
25+
* based on the current state of the text and slices.
26+
*/
2027
export class Overlay implements Printable, Stateful {
2128
public root: OverlayPoint | undefined = undefined;
2229

2330
constructor(protected readonly txt: Peritext) {}
2431

25-
/**
26-
* @todo Rename to .point().
27-
*/
28-
protected overlayPoint(id: ITimestampStruct, anchor: Anchor): OverlayPoint {
29-
return new OverlayPoint(this.txt.str, id, anchor);
30-
}
31-
32-
protected markerPoint(marker: MarkerSlice, anchor: Anchor): OverlayPoint {
33-
return new MarkerOverlayPoint(this.txt.str, marker.start.id, anchor, marker);
34-
}
35-
3632
public first(): OverlayPoint | undefined {
3733
return this.root ? first(this.root) : undefined;
3834
}
@@ -136,12 +132,20 @@ export class Overlay implements Printable, Stateful {
136132
return state;
137133
}
138134

135+
private point(id: ITimestampStruct, anchor: Anchor): OverlayPoint {
136+
return new OverlayPoint(this.txt.str, id, anchor);
137+
}
138+
139+
private mPoint(marker: MarkerSlice, anchor: Anchor): MarkerOverlayPoint {
140+
return new MarkerOverlayPoint(this.txt.str, marker.start.id, anchor, marker);
141+
}
142+
139143
/**
140144
* Retrieve an existing {@link OverlayPoint} or create a new one, inserted
141145
* in the tree, sorted by spatial dimension.
142146
*/
143-
protected upsertPoint(point: Point): [point: OverlayPoint, isNew: boolean] {
144-
const newPoint = this.overlayPoint(point.id, point.anchor);
147+
private upsertPoint(point: Point): [point: OverlayPoint, isNew: boolean] {
148+
const newPoint = this.point(point.id, point.anchor);
145149
const pivot = this.insPoint(newPoint);
146150
if (pivot) return [pivot, false];
147151
return [newPoint, true];
@@ -173,7 +177,7 @@ export class Overlay implements Printable, Stateful {
173177
}
174178

175179
private insMarker(slice: MarkerSlice): [start: OverlayPoint, end: OverlayPoint] {
176-
const point = this.markerPoint(slice, Anchor.Before);
180+
const point = this.mPoint(slice, Anchor.Before);
177181
const pivot = this.insPoint(point);
178182
if (!pivot) {
179183
point.refs.push(slice);

0 commit comments

Comments
 (0)