Skip to content

Commit b9b516f

Browse files
committed
Enable a basic collaborative model
1 parent 7380454 commit b9b516f

File tree

6 files changed

+183
-64
lines changed

6 files changed

+183
-64
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,11 @@
6161
"@lumino/signaling": "^1.4.3",
6262
"@lumino/widgets": "^1.19.0",
6363
"react": "^17.0.1",
64-
"react-dom": "^17.0.1"
64+
"react-dom": "^17.0.1",
65+
"yjs": "^13.5.3"
6566
},
6667
"devDependencies": {
67-
"@jupyterlab/builder": "^3.0.5",
68+
"@jupyterlab/builder": "^3.1.0-alpha.7",
6869
"@typescript-eslint/eslint-plugin": "^2.27.0",
6970
"@typescript-eslint/parser": "^2.27.0",
7071
"eslint": "^7.5.0",

src/factory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export class DrawIOWidgetFactory extends ABCWidgetFactory<
3333
return new DrawIODocumentWidget({
3434
context,
3535
commands: this._commands,
36-
content: new DrawIOWidget()
36+
content: new DrawIOWidget(context.model)
3737
});
3838
}
3939

src/model.ts

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import { DocumentRegistry } from '@jupyterlab/docregistry';
1616

17-
import { ISharedDocument, YFile } from '@jupyterlab/shared-models';
17+
import { ISharedFile, YDocument, YFile, Delta } from '@jupyterlab/shared-models';
1818

1919
import { IChangedArgs } from '@jupyterlab/coreutils';
2020

@@ -24,6 +24,14 @@ import { PartialJSONValue, ReadonlyPartialJSONValue } from '@lumino/coreutils';
2424

2525
import { ISignal, Signal } from '@lumino/signaling';
2626

27+
import {
28+
XmlFragment,
29+
XmlElement,
30+
XmlText,
31+
YEvent,
32+
YXmlEvent
33+
} from 'yjs';
34+
2735

2836
export class DrawIODocumentModel implements DocumentRegistry.IModel {
2937

@@ -32,7 +40,6 @@ export class DrawIODocumentModel implements DocumentRegistry.IModel {
3240
*/
3341
constructor(languagePreference?: string, modelDB?: IModelDB) {
3442
this.modelDB = modelDB || new ModelDB();
35-
console.debug(this.modelDB);
3643
}
3744

3845
get dirty(): boolean {
@@ -67,21 +74,22 @@ export class DrawIODocumentModel implements DocumentRegistry.IModel {
6774

6875
readonly modelDB: IModelDB;
6976

70-
readonly sharedModel: ISharedDocument = YFile.create();
77+
readonly sharedModel: ISharedFile = YFile.create();
7178

7279
dispose(): void {
7380
this._isDisposed = true;
7481
}
7582

7683
toString(): string {
7784
// TODO: Return content from shared model
78-
console.warn("toString(): Not implemented");
79-
return '';
85+
//console.info("DrawIODocumentModel.toString():", this.sharedModel.getSource());
86+
return this.sharedModel.getSource();
8087
}
8188

8289
fromString(value: string): void {
8390
// TODO: Add content to shared model
84-
console.warn("fromString(): Not implemented");
91+
//console.info("DrawIODocumentModel.fromString():", value);
92+
this.sharedModel.setSource(value);
8593
}
8694

8795
toJSON(): PartialJSONValue {
@@ -103,3 +111,75 @@ export class DrawIODocumentModel implements DocumentRegistry.IModel {
103111
private _contentChanged = new Signal<this, void>(this);
104112
private _stateChanged = new Signal<this, IChangedArgs<any>>(this);
105113
}
114+
115+
export type XMLChange = {
116+
graphChanged?: Delta<YXmlEvent>;
117+
rootChanged?: Delta<YXmlEvent>;
118+
};
119+
120+
export class XMLModel extends YDocument<XMLChange> {
121+
constructor() {
122+
super();
123+
this._mxGraphModel = this.ydoc.getXmlFragment('mxGraphModel');
124+
this._root = new XmlElement();
125+
this._mxGraphModel.observeDeep(this._modelObserver);
126+
this.transact(() => {
127+
this._mxGraphModel.insert(0, [this._root]);
128+
});
129+
}
130+
131+
/**
132+
* Handle a change to the _mxGraphModel.
133+
*/
134+
private _modelObserver = (events: YEvent[]) => {
135+
const changes: XMLChange = {};
136+
//changes.graphChanged = events.find();.delta as any;
137+
this._changed.emit(changes);
138+
};
139+
140+
public static create(): XMLModel {
141+
return new XMLModel();
142+
}
143+
144+
/**
145+
* Gets cell's source.
146+
*
147+
* @returns Cell's source.
148+
*/
149+
public getGraphModel(): string {
150+
return this._mxGraphModel.toString();
151+
}
152+
153+
/**
154+
* Sets cell's source.
155+
*
156+
* @param value: New source.
157+
*/
158+
public setSource(value: string): void {
159+
this.transact(() => {
160+
const text = this._root;
161+
text.delete(0, text.length);
162+
text.insert(0, [new XmlText(value)]);
163+
});
164+
}
165+
166+
/**
167+
* Replace content from `start' to `end` with `value`.
168+
*
169+
* @param start: The start index of the range to replace (inclusive).
170+
*
171+
* @param end: The end index of the range to replace (exclusive).
172+
*
173+
* @param value: New source (optional).
174+
*/
175+
public updateSource(start: number, end: number, value = ''): void {
176+
this.transact(() => {
177+
const source = this._root;
178+
source.delete(start, end - start);
179+
source.insert(start, [new XmlText(value)]);
180+
});
181+
}
182+
183+
private _mxGraphModel: XmlFragment;
184+
private _root: XmlElement;
185+
}

src/panel.ts

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { Widget } from '@lumino/widgets';
22

3-
import { Signal, ISignal } from '@lumino/signaling';
4-
53
import { PromiseDelegate } from '@lumino/coreutils';
64

75
/*
@@ -15,6 +13,8 @@ import './drawio/css/common.css';
1513

1614
import './drawio/styles/grapheditor.css';
1715

16+
import { DrawIODocumentModel } from './model';
17+
1818
import { grapheditorTxt, defaultXml } from './pack';
1919

2020
const w = window as any;
@@ -42,17 +42,19 @@ export class DrawIOWidget extends Widget {
4242
*
4343
* @param info - The `DashboardView` metadata.
4444
*/
45-
constructor() {
45+
constructor(model: DrawIODocumentModel) {
4646
super();
47-
void Private.ensureMx().then(mx => this._loadDrawIO(mx));
48-
//this._loadDrawIO(MX);
47+
this._model = model;
48+
Private.ensureMx().then(mx => {
49+
this._loadDrawIO(mx);
50+
this._model.sharedModel.changed.connect(this._onContentChanged, this);
51+
});
4952
}
5053

5154
/**
5255
* Dispose of the resources held by the widget.
5356
*/
5457
dispose(): void {
55-
Signal.clearData(this);
5658
this._editor.destroy();
5759
super.dispose();
5860
}
@@ -64,10 +66,6 @@ export class DrawIOWidget extends Widget {
6466
return this._ready;
6567
}
6668

67-
get graphChanged(): ISignal<this, string> {
68-
return this._graphChanged;
69-
}
70-
7169
get mx(): any {
7270
return this._mx;
7371
}
@@ -88,21 +86,6 @@ export class DrawIOWidget extends Widget {
8886
return this._editor.actions.actions;
8987
}
9088

91-
setContent(newValue: string): void {
92-
if (this._editor === undefined) {
93-
return;
94-
}
95-
96-
const oldValue = this._mx.mxUtils.getXml(this._editor.editor.getGraphXml());
97-
98-
if (oldValue !== newValue && !this._editor.editor.graph.isEditing()) {
99-
if (newValue.length) {
100-
const xml = this._mx.mxUtils.parseXml(newValue);
101-
this._editor.editor.setGraphXml(xml.documentElement);
102-
}
103-
}
104-
}
105-
10689
//Direction
10790
public toggleCellStyles(flip: string): void {
10891
let styleFlip = this._editor.mx.mxConstants.STYLE_FLIPH;
@@ -369,6 +352,22 @@ export class DrawIOWidget extends Widget {
369352
}, true);
370353
}
371354

355+
private _onContentChanged(): void {
356+
const newValue = this._model.sharedModel.getSource();
357+
if (this._editor === undefined) {
358+
return;
359+
}
360+
361+
const oldValue = this._mx.mxUtils.getXml(this._editor.editor.getGraphXml());
362+
363+
if (oldValue !== newValue && !this._editor.editor.graph.isEditing()) {
364+
if (newValue.length) {
365+
const xml = this._mx.mxUtils.parseXml(newValue);
366+
this._editor.editor.setGraphXml(xml.documentElement);
367+
}
368+
}
369+
}
370+
372371
private _loadDrawIO(mx: Private.MX): void {
373372
this._mx = mx;
374373

@@ -389,9 +388,12 @@ export class DrawIOWidget extends Widget {
389388
this._editor = new this._mx.EditorUi(new Editor(false, themes), this.node);
390389
this._editor.refresh();
391390

391+
//console.debug(this._mx.Editor);
392+
//console.debug(this._editor.editor.graph.model);
392393
this._editor.editor.graph.model.addListener(
393394
this._mx.mxEvent.NOTIFY,
394395
(sender: any, evt: any) => {
396+
//console.debug("Event:", evt);
395397
const changes: any[] = evt.properties.changes;
396398
for (let i = 0; i < changes.length; i++) {
397399
if (changes[i].root) {
@@ -405,10 +407,29 @@ export class DrawIOWidget extends Widget {
405407

406408
const graph = this._editor.editor.getGraphXml();
407409
const xml = this._mx.mxUtils.getXml(graph);
408-
this._graphChanged.emit(xml);
410+
this._model.sharedModel.setSource(xml);
409411
}
410412
);
411413

414+
/* this._editor.editor.graph.model.addListener(
415+
this._mx.mxEvent.NOTIFY,
416+
(sender: any, evt: any) => {
417+
var changes = evt.getProperty('edit').changes;
418+
419+
for (var i = 0; i < changes.length; i++)
420+
{
421+
var change = changes[i];
422+
423+
if (
424+
change instanceof this._mx.mxChildChange &&
425+
change.change.previous == null
426+
) {
427+
this._editor.editor.graph.startEditingAtCell(change.child);
428+
break;
429+
}
430+
}
431+
}); */
432+
412433
this._promptSpacing = this._mx.mxUtils.bind(
413434
this,
414435
(defaultValue: any, fn: any) => {
@@ -426,15 +447,18 @@ export class DrawIOWidget extends Widget {
426447
dlg.init();
427448
}
428449
);
429-
450+
451+
const data = this._model.sharedModel.getSource();
452+
const xml = this._mx.mxUtils.parseXml(data);
453+
this._editor.editor.setGraphXml(xml.documentElement);
430454
this._ready.resolve(void 0);
431455
}
432456

433457
private _editor: any;
434458
private _mx: Private.MX;
435459
private _promptSpacing: any;
460+
private _model: DrawIODocumentModel;
436461
private _ready = new PromiseDelegate<void>();
437-
private _graphChanged = new Signal<this, string>(this);
438462
}
439463

440464
/**

src/widget.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,14 @@ export class DrawIODocumentWidget extends DocumentWidget<DrawIOWidget, DrawIODoc
8383

8484
this._onTitleChanged();
8585
this._addToolbarItems();
86-
this.content.setContent(this.context.model.toString());
8786
this._handleDirtyStateNew();
8887

89-
this.context.pathChanged.connect(this._onTitleChanged, this);
90-
this.context.model.contentChanged.connect(this._onContentChanged, this);
9188
this.context.model.stateChanged.connect(
9289
this._onModelStateChangedNew,
9390
this
9491
);
95-
this.content.graphChanged.connect(this._saveToContext, this);
92+
93+
this.context.pathChanged.connect(this._onTitleChanged, this);
9694
});
9795
}
9896

@@ -166,14 +164,6 @@ export class DrawIODocumentWidget extends DocumentWidget<DrawIOWidget, DrawIODoc
166164
this.title.label = PathExt.basename(this.context.localPath);
167165
}
168166

169-
private _onContentChanged(): void {
170-
this.content.setContent(this.context.model.toString());
171-
}
172-
173-
private _saveToContext(emiter: DrawIOWidget, content: string): void {
174-
this.context.model.fromString(content);
175-
}
176-
177167
private _onModelStateChangedNew(
178168
sender: DocumentRegistry.IModel,
179169
args: IChangedArgs<any>

0 commit comments

Comments
 (0)