Skip to content

Commit 90ffffc

Browse files
Demo Synchronizer Visualizer
1 parent f7fd170 commit 90ffffc

16 files changed

+489
-26
lines changed

.eslintignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
build
22
node_modules
3-
lib/types/webgme.ts
3+
src/common/lib/types/webgme.ts
44
config/config.webgme.js

lib/index.ts

-3
This file was deleted.

package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
"start": "node app.js",
55
"postinstall": "npm run build",
66
"lint": "node ./node_modules/.bin/eslint ./",
7-
"build": "tsc",
8-
"watch-lib": "tsc --watch",
7+
"build": "npm run build:lib && npm run build:svelte",
8+
"build:lib": "tsc",
9+
"build:svelte": "vite build",
10+
"watch:lib": "tsc --watch",
911
"test": "node ./node_modules/mocha/bin/mocha --recursive test"
1012
},
1113
"version": "1.0.0",

src/common/WJIDiffSync.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/* globals define */
2+
/* eslint-env node, browser */
3+
4+
define([
5+
'./lib/build/index'
6+
], function (
7+
WJIDiffSync
8+
) {
9+
return WJIDiffSync;
10+
});

lib/DiffSyncLib.ts src/common/lib/DiffSyncLib.ts

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
1-
export type DiffFunction = (input: WJIJson, newState: WJIJson) => Delta;
2-
export type NodeChangeSet = any;
1+
export type DiffFunction = (input: WJIJson, newState: WJIJson) => NodeChangeSet[];
2+
3+
enum NodeChangeSetType {
4+
put='put',
5+
del='del'
6+
}
7+
8+
export interface NodeChangeSet {
9+
parentPath: string,
10+
nodeId: string,
11+
type: NodeChangeSetType,
12+
key: string[],
13+
value: any
14+
}
15+
316
export type WJIImporterType = any;
417

518
export interface Delta {
@@ -13,6 +26,7 @@ export interface StateTransformer<T1, T2> {
1326
}
1427

1528
export interface Differ<T2> {
29+
diffFunc: DiffFunction;
1630
diff(state: T2, newState: T2) : Delta
1731
}
1832

lib/WJIDiffSync.ts src/common/lib/WJIDiffSync.ts

+14-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {DiffFunction, Delta, Differ, GMEDiffSync, NodeChangeSet, StateTransformer, WJIImporterType, WJIJson} from './DiffSyncLib';
22

3-
class WJIDelta implements Delta {
3+
export class WJIDelta implements Delta {
44
patches: any;
55
timeStamp: number;
66

@@ -9,13 +9,13 @@ class WJIDelta implements Delta {
99
this.patches = patches;
1010
}
1111

12-
static fromNodeChangeSet(changeSet: NodeChangeSet) {
13-
return new WJIDelta(Date.now(), changeSet);
12+
static fromNodeChangeSet(changeSets: NodeChangeSet[]) {
13+
return new WJIDelta(Date.now(), changeSets);
1414
}
1515
}
1616

1717

18-
class NodeToWJITransformer implements StateTransformer<Core.Node, WJIJson> {
18+
export class NodeToWJITransformer implements StateTransformer<Core.Node, WJIJson> {
1919
importer: WJIImporterType;
2020

2121
constructor(importer: WJIImporterType) {
@@ -32,7 +32,7 @@ class NodeToWJITransformer implements StateTransformer<Core.Node, WJIJson> {
3232
}
3333
}
3434

35-
class WJIToWJITransformer implements StateTransformer<WJIJson, WJIJson> {
35+
export class WJIToWJITransformer implements StateTransformer<WJIJson, WJIJson> {
3636
apply(state: WJIJson, patch: WJIDelta): void | Promise<void> {
3737
// ToDo: Implement this logic
3838
}
@@ -42,23 +42,24 @@ class WJIToWJITransformer implements StateTransformer<WJIJson, WJIJson> {
4242
}
4343
}
4444

45-
class WJIDiff implements Differ<WJIJson> {
45+
export class WJIDiff implements Differ<WJIJson> {
4646
diffFunc: DiffFunction;
4747

4848
constructor(diff: DiffFunction) {
4949
this.diffFunc = diff;
5050
}
5151

5252
diff(state: WJIJson, newState: WJIJson): WJIDelta {
53-
return this.diffFunc(state, newState);
53+
const changeSets = this.diffFunc(state, newState);
54+
return WJIDelta.fromNodeChangeSet(changeSets);
5455
}
55-
5656
}
5757

58-
class WJIDiffSync implements GMEDiffSync<Core.Node, WJIJson, WJIJson> {
58+
export class WJIDiffSync implements GMEDiffSync<Core.Node, WJIJson, WJIJson> {
5959
shadow: WJIJson;
6060
serverTransform: NodeToWJITransformer;
6161
clientTransform: WJIToWJITransformer;
62+
importer: WJIImporterType;
6263
differ: WJIDiff;
6364
serverState: Core.Node;
6465
clientState: WJIJson;
@@ -70,14 +71,13 @@ class WJIDiffSync implements GMEDiffSync<Core.Node, WJIJson, WJIJson> {
7071
clientState: WJIJson,
7172
importer: WJIImporterType,
7273
diff: DiffFunction,
73-
clientTransform: NodeToWJITransformer,
74-
serverTransform: WJIToWJITransformer
7574
) {
7675
this.serverState = serverState;
7776
this.shadow = shadow;
77+
this.importer = importer;
7878
this.clientState = clientState;
79-
this.serverTransform = clientTransform;
80-
this.clientTransform = serverTransform;
79+
this.serverTransform = new NodeToWJITransformer(this.importer);
80+
this.clientTransform = new WJIToWJITransformer();
8181
this.differ = new WJIDiff(diff);
8282
}
8383

@@ -92,5 +92,6 @@ class WJIDiffSync implements GMEDiffSync<Core.Node, WJIJson, WJIJson> {
9292
const diff = this.differ.diff(this.shadow, serverState);
9393
this.shadow = serverState;
9494
this.clientTransform.apply(this.clientState, diff);
95+
this.clientState = this.shadow;
9596
}
9697
}

src/common/lib/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import {WJIDiffSync, WJIToWJITransformer, NodeToWJITransformer, WJIDiff, WJIDelta} from './WJIDiffSync';
2+
3+
export default WJIDiffSync;
4+
export {WJIToWJITransformer, NodeToWJITransformer, WJIDiff, WJIDelta};
File renamed without changes.

src/visualizers/Visualizers.json

+6
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,11 @@
44
"title": "JSONEditor",
55
"panel": "panels/JSONEditor/JSONEditorPanel",
66
"DEBUG_ONLY": false
7+
},
8+
{
9+
"id": "DemoDiffSync",
10+
"title": "DemoDiffSync",
11+
"panel": "panels/DemoDiffSync/DemoDiffSyncPanel",
12+
"DEBUG_ONLY": false
713
}
814
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/*globals define, WebGMEGlobal*/
2+
/**
3+
* Generated by VisualizerGenerator 1.7.0 from webgme on Mon Sep 19 2022 18:43:02 GMT-0500 (Central Daylight Time).
4+
*/
5+
6+
7+
define([
8+
'js/Constants',
9+
'webgme-json-importer/JSONImporter',
10+
'webgme-json-importer/changeset',
11+
'webgme-diffsync/WJIDiffSync',
12+
'js/Utils/GMEConcepts',
13+
'js/NodePropertyNames',
14+
'./Utils',
15+
'underscore'
16+
], function (
17+
CONSTANTS,
18+
JSONImporter,
19+
diff,
20+
DiffSyncUtils,
21+
GMEConcepts,
22+
nodePropertyNames,
23+
Utils,
24+
_
25+
) {
26+
27+
'use strict';
28+
const WJIDiffSync = DiffSyncUtils.default;
29+
const {NodeDiffFactory} = Utils;
30+
class DemoDiffSyncControl {
31+
constructor(options) {
32+
this._logger = options.logger.fork('Control');
33+
34+
this._client = options.client;
35+
this.synchronizer = null;
36+
this.numCalls = 0;
37+
// Initialize core collections and variables
38+
this._initializeWidgets(options.widgets);
39+
40+
this.pending = false;
41+
this._currentNodeId = null;
42+
this._currentNodeParentId = undefined;
43+
44+
this._initWidgetEventHandlers();
45+
46+
this._logger.debug('ctor finished');
47+
}
48+
49+
_initializeWidgets(widgets) {
50+
const [serverStateWidget, commonShadowWidget, clientStateWidget] = widgets;
51+
52+
this.serverStateWidget = serverStateWidget;
53+
this.commonShadowWidget = commonShadowWidget;
54+
this.clientStateWidget = clientStateWidget;
55+
this.serverStateWidget.$el.css('width', '30%');
56+
this.commonShadowWidget.$el.css('width', '30%');
57+
this.clientStateWidget.$el.css('width', '35%'); // More width because editable
58+
59+
60+
serverStateWidget.setTitle('Server State');
61+
commonShadowWidget.setTitle('Common Shadow');
62+
clientStateWidget.setTitle('Client State');
63+
}
64+
65+
_initWidgetEventHandlers = function () {
66+
const onChange = async (updated, previous, /*error, patch*/) => {
67+
if(!_.isEmpty(previous.json)) {
68+
if(this.pending) return;
69+
this.pending = true;
70+
await this.synchronizer.onUpdatesFromClient(updated.json);
71+
const {core, rootNode, project} = await this.getCoreInstance();
72+
const {rootHash, objects} = core.persist(rootNode);
73+
const activeNode = await core.loadByPath(rootNode, this._currentNodeId);
74+
const nodeName = core.getAttribute(activeNode, 'name');
75+
const branchName = this._client.getActiveBranchName();
76+
const parentCommit = this._client.getActiveCommitHash();
77+
await project.makeCommit(
78+
branchName,
79+
[parentCommit],
80+
rootHash,
81+
objects,
82+
`Updated ${nodeName}`,
83+
);
84+
}
85+
await this.setWidgetsState();
86+
this.pending = false;
87+
};
88+
89+
this.clientStateWidget.setOnChange(_.debounce(onChange.bind(this), 1000));
90+
};
91+
92+
async getCoreInstance() {
93+
return await new Promise((resolve, reject) => this._client.getCoreInstance(null, (err, result) => err ? reject(err) : resolve(result)));
94+
}
95+
96+
async setStateFromNode(nodeId) {
97+
if(this.pending) return;
98+
this.pending = true;
99+
const {core, rootNode} = await this.getCoreInstance();
100+
const importer = new JSONImporter(core, rootNode);
101+
const node = await core.loadByPath(rootNode, nodeId);
102+
const nodeJSON = await importer.toJSON(node);
103+
const parent = core.getParent(node);
104+
const diffFunction = NodeDiffFactory(diff, core.getPath(parent), JSONImporter.NodeChangeSet);
105+
106+
if(!this.synchronizer) {
107+
this.synchronizer = new WJIDiffSync(
108+
node,
109+
nodeJSON,
110+
nodeJSON,
111+
importer,
112+
diffFunction
113+
);
114+
} else {
115+
await this.synchronizer.onUpdatesFromServer(node);
116+
}
117+
await this.setWidgetsState();
118+
this.pending = false;
119+
}
120+
121+
async setWidgetsState() {
122+
if(!this.synchronizer) return;
123+
const importer = this.synchronizer.importer;
124+
this.serverStateWidget.setState(
125+
{json: await importer.toJSON(this.synchronizer.serverState)},
126+
true
127+
);
128+
129+
this.clientStateWidget.setState(
130+
{json: this.synchronizer.clientState},
131+
false
132+
);
133+
134+
this.commonShadowWidget.setState(
135+
{json: this.synchronizer.shadow},
136+
true
137+
);
138+
}
139+
140+
selectedObjectChanged(nodeId) {
141+
this._logger.debug('activeObject nodeId \'' + nodeId + '\'');
142+
143+
// Remove current territory patterns
144+
if (this._territoryId) {
145+
this._client.removeUI(this._territoryId);
146+
this._territoryId = null;
147+
}
148+
149+
this._currentNodeId = nodeId;
150+
if (typeof this._currentNodeId === 'string') {
151+
152+
// Put new node's info into territory rules
153+
this._selfPatterns = {};
154+
// TODO: Will this work? I can't remember tbh...
155+
this._selfPatterns[nodeId] = {children: Infinity}; // Territory "rule"
156+
157+
this._territoryId = this._client.addUI(this, async () => {
158+
this.setStateFromNode(nodeId);
159+
});
160+
this._client.updateTerritory(this._territoryId, this._selfPatterns);
161+
}
162+
}
163+
164+
_stateActiveObjectChanged(model, activeObjectId) {
165+
if (this._currentNodeId === activeObjectId) {
166+
// The same node selected as before - do not trigger
167+
} else {
168+
this.selectedObjectChanged(activeObjectId);
169+
}
170+
}
171+
172+
_getObjectDescriptor(/*nodeId*/) {
173+
return {};
174+
}
175+
176+
/* * * * * * * * Visualizer life cycle callbacks * * * * * * * */
177+
destroy() {
178+
this._detachClientEventListeners();
179+
}
180+
181+
_attachClientEventListeners() {
182+
this._detachClientEventListeners();
183+
WebGMEGlobal.State.on('change:' + CONSTANTS.STATE_ACTIVE_OBJECT, this._stateActiveObjectChanged, this);
184+
}
185+
186+
_detachClientEventListeners() {
187+
WebGMEGlobal.State.off('change:' + CONSTANTS.STATE_ACTIVE_OBJECT, this._stateActiveObjectChanged);
188+
}
189+
190+
onActivate() {
191+
this._attachClientEventListeners();
192+
if (typeof this._currentNodeId === 'string') {
193+
WebGMEGlobal.State.registerActiveObject(this._currentNodeId, {suppressVisualizerFromNode: true});
194+
}
195+
}
196+
197+
onDeactivate() {
198+
this._detachClientEventListeners();
199+
}
200+
}
201+
202+
return DemoDiffSyncControl;
203+
});

0 commit comments

Comments
 (0)