4c4d1ca664
Former-commit-id: 85b106c668
1106 lines
28 KiB
JavaScript
1106 lines
28 KiB
JavaScript
/**
|
|
* Copyright (c) 2006-2016, JGraph Ltd
|
|
* Copyright (c) 2006-2016, Gaudenz Alder
|
|
*/
|
|
/**
|
|
* Creates an object that maps all changes from the given diagramMap to the
|
|
* given graph model.
|
|
*/
|
|
function RealtimeMapping(driveRealtime, diagramMap, page)
|
|
{
|
|
this.driveRealtime = driveRealtime;
|
|
this.diagramMap = diagramMap;
|
|
this.page = page;
|
|
|
|
this.graphModel = new mxGraphModel();
|
|
|
|
if (page.root != null)
|
|
{
|
|
this.graphModel.setRoot(page.root);
|
|
}
|
|
|
|
this.ui = this.driveRealtime.ui;
|
|
this.root = this.driveRealtime.root;
|
|
this.graph = this.driveRealtime.graph;
|
|
this.rtModel = this.driveRealtime.rtModel;
|
|
};
|
|
|
|
/**
|
|
* Specifies the key of the root element in the model. Default is root.
|
|
*/
|
|
RealtimeMapping.prototype.driveRealtime = null;
|
|
|
|
/**
|
|
* Specifies the key of the root element in the model. Default is root.
|
|
*/
|
|
RealtimeMapping.prototype.diagramMap = null;
|
|
|
|
/**
|
|
* Specifies the key of the root element in the model. Default is root.
|
|
*/
|
|
RealtimeMapping.prototype.page = null;
|
|
|
|
/**
|
|
* Specifies the key of the root element in the model. Default is root.
|
|
*/
|
|
RealtimeMapping.prototype.graphModel = null;
|
|
|
|
/**
|
|
* Specifies the key of the root element in the model. Default is root.
|
|
*/
|
|
RealtimeMapping.prototype.needsUpdate = true;
|
|
|
|
/**
|
|
* Specifies the key of the root element in the model. Default is root.
|
|
*/
|
|
RealtimeMapping.prototype.selectionMap = null;
|
|
|
|
/**
|
|
* Synchronizes the collaboration model and the graph model and installs
|
|
* the required listeners to keep them in sync.
|
|
*/
|
|
RealtimeMapping.prototype.init = function()
|
|
{
|
|
this.diagramMap.addEventListener(gapi.drive.realtime.EventType.VALUE_CHANGED, mxUtils.bind(this, function(evt)
|
|
{
|
|
if (!this.driveRealtime.isLocalEvent(evt))
|
|
{
|
|
if (evt.property == this.driveRealtime.rootKey && evt.newValue != null)
|
|
{
|
|
this.beginUpdate();
|
|
this.initGraph();
|
|
this.needsUpdate = true;
|
|
}
|
|
else if (evt.property == 'name' && evt.newValue != null)
|
|
{
|
|
this.driveRealtime.ignoreChange = true;
|
|
this.graph.model.execute(new RenamePage(this.ui, this.page, evt.newValue));
|
|
this.driveRealtime.ignoreChange = false;
|
|
}
|
|
else if (evt.newValue != null)
|
|
{
|
|
if (evt.property == 'pageFormat')
|
|
{
|
|
this.realtimePageFormatChanged(evt.newValue);
|
|
}
|
|
else if (evt.property == 'pageScale')
|
|
{
|
|
this.realtimePageScaleChanged(evt.newValue);
|
|
}
|
|
else if (evt.property == 'backgroundColor')
|
|
{
|
|
this.realtimeBackgroundColorChanged(evt.newValue);
|
|
}
|
|
else if (evt.property == 'shadowVisible')
|
|
{
|
|
this.realtimeShadowVisibleChanged(evt.newValue);
|
|
}
|
|
else if (evt.property == 'foldingEnabled')
|
|
{
|
|
this.realtimeFoldingEnabledChanged(evt.newValue);
|
|
}
|
|
else if (evt.property == 'pageVisible')
|
|
{
|
|
this.realtimePageVisibleChanged(evt.newValue);
|
|
}
|
|
else if (evt.property == 'backgroundImage')
|
|
{
|
|
this.realtimeBackgroundImageChanged(evt.newValue);
|
|
}
|
|
else if (evt.property == 'mathEnabled')
|
|
{
|
|
this.realtimeMathEnabledChanged(evt.newValue);
|
|
}
|
|
}
|
|
|
|
// Marks the mapping dirty regardless of active state
|
|
if (evt.newValue != null && (evt.property == 'pageFormat' ||
|
|
evt.property == 'pageScale' || evt.property == 'shadowVisible' ||
|
|
evt.property == 'backgroundColor' || evt.property == 'foldingEnabled' ||
|
|
evt.property == 'backgroundImage' || evt.property == 'mathEnabled' ||
|
|
evt.property == 'pageVisible'))
|
|
{
|
|
this.needsUpdate = true;
|
|
}
|
|
}
|
|
}));
|
|
|
|
if (this.diagramMap.has(this.driveRealtime.rootKey))
|
|
{
|
|
this.initGraph();
|
|
}
|
|
else
|
|
{
|
|
this.initRealtime();
|
|
}
|
|
|
|
this.page.root = this.graphModel.getRoot();
|
|
this.selectionMap = this.diagramMap.get('select');
|
|
|
|
if (this.selectionMap == null)
|
|
{
|
|
this.initializeSelection();
|
|
}
|
|
|
|
// Resets selection state to ensure change event
|
|
if (this.driveRealtime.file.isEditable())
|
|
{
|
|
this.selectionMap.set(this.driveRealtime.userId, '');
|
|
}
|
|
|
|
this.installRemoteSelectionListener();
|
|
};
|
|
|
|
/**
|
|
*
|
|
*/
|
|
RealtimeMapping.prototype.initializeSelection = function()
|
|
{
|
|
this.selectionMap = this.rtModel.createMap();
|
|
|
|
if (this.driveRealtime.file.isEditable())
|
|
{
|
|
this.diagramMap.set('select', this.selectionMap);
|
|
}
|
|
//this.log('Selection list created');
|
|
};
|
|
|
|
/**
|
|
* Adds a listener for changes to the RT selection map to highlight
|
|
* remote selection
|
|
*/
|
|
RealtimeMapping.prototype.installRemoteSelectionListener = function()
|
|
{
|
|
this.selectionMap.addEventListener(gapi.drive.realtime.EventType.VALUE_CHANGED, mxUtils.bind(this, function(evt)
|
|
{
|
|
if (!this.driveRealtime.isLocalEvent(evt) && evt.newValue != null && (this.ui.currentPage == null ||
|
|
this.ui.currentPage == this.page))
|
|
{
|
|
var cellIds = evt.newValue.split(',');
|
|
|
|
for (var i = 0; i < cellIds.length; i++)
|
|
{
|
|
this.driveRealtime.highlight(this.driveRealtime.model.getCell(cellIds[i]), evt.sessionId);
|
|
}
|
|
}
|
|
}));
|
|
};
|
|
|
|
/**
|
|
* Returns true if this diagram is being displayed.
|
|
*/
|
|
RealtimeMapping.prototype.isActive = function()
|
|
{
|
|
return this.ui.currentPage == null || this.ui.currentPage.mapping == this;
|
|
};
|
|
|
|
/**
|
|
* Syncs initial state from collab model to graph model.
|
|
*/
|
|
RealtimeMapping.prototype.getGraphModel = function()
|
|
{
|
|
return (this.isActive()) ? this.driveRealtime.model : this.graphModel;
|
|
};
|
|
|
|
/**
|
|
* Syncs initial state from collab model to graph model.
|
|
*/
|
|
RealtimeMapping.prototype.initGraph = function()
|
|
{
|
|
if (this.isActive())
|
|
{
|
|
this.activate(true);
|
|
mxClient.NO_FO = (this.graph.mathEnabled) ? true : Editor.prototype.originalNoForeignObject;
|
|
|
|
// TODO: Fixes math offset - why?
|
|
this.ui.editor.graph.sizeDidChange();
|
|
}
|
|
|
|
var rtRoot = this.diagramMap.get(this.driveRealtime.rootKey);
|
|
|
|
if (rtRoot.cell == null)
|
|
{
|
|
this.createCell(rtRoot);
|
|
this.restoreCell(rtRoot);
|
|
}
|
|
else
|
|
{
|
|
this.installAllRealtimeCellListeners(rtRoot);
|
|
}
|
|
|
|
// Stores root in current model and local model
|
|
var gm = this.getGraphModel();
|
|
gm.setRoot(rtRoot.cell);
|
|
|
|
if (gm != this.graphModel)
|
|
{
|
|
this.graphModel.setRoot(gm.getRoot());
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Writes the graph properties from the realtime model to the given mxGraphModel node.
|
|
*/
|
|
RealtimeMapping.prototype.writeRealtimeToNode = function(node)
|
|
{
|
|
node.setAttribute('shadow', this.diagramMap.get('shadowVisible'));
|
|
node.setAttribute('fold', this.diagramMap.get('foldingEnabled'));
|
|
node.setAttribute('math', this.diagramMap.get('mathEnabled'));
|
|
node.setAttribute('pageScale', this.diagramMap.get('pageScale'));
|
|
|
|
var img = this.diagramMap.get('backgroundImage');
|
|
|
|
if (img != null && img.length > 0)
|
|
{
|
|
node.setAttribute('backgroundImage', img);
|
|
}
|
|
|
|
var color = this.diagramMap.get('backgroundColor');
|
|
|
|
if (color != null)
|
|
{
|
|
node.setAttribute('background', color);
|
|
}
|
|
|
|
var pf = this.diagramMap.get('pageFormat');
|
|
|
|
if (pf != null)
|
|
{
|
|
var values = pf.split(',');
|
|
|
|
if (values.length > 1)
|
|
{
|
|
node.setAttribute('pageWidth', parseInt(values[0]));
|
|
node.setAttribute('pageHeight', parseInt(values[1]));
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Writes the graph properties from the realtime model to the given mxGraphModel node.
|
|
*/
|
|
RealtimeMapping.prototype.writeNodeToRealtime = function(node)
|
|
{
|
|
this.diagramMap.set('shadowVisible', node.getAttribute('shadow'));
|
|
this.diagramMap.set('foldingEnabled', node.getAttribute('fold'));
|
|
this.diagramMap.set('mathEnabled', node.getAttribute('math'));
|
|
this.diagramMap.set('pageScale', node.getAttribute('pageScale'));
|
|
this.diagramMap.set('pageVisible', node.getAttribute('pageVisible'));
|
|
|
|
var img = node.getAttribute('backgroundImage');
|
|
|
|
if (img != null && img.length > 0)
|
|
{
|
|
this.diagramMap.set('backgroundImage', img);
|
|
}
|
|
|
|
var color = node.getAttribute('background');
|
|
|
|
if (color != null)
|
|
{
|
|
this.diagramMap.set('backgroundColor', color);
|
|
}
|
|
|
|
this.diagramMap.set('pageFormat', node.getAttribute('pageWidth') + ',' +
|
|
node.getAttribute('pageHeight'));
|
|
};
|
|
|
|
/**
|
|
* Syncs initial state from collab model to graph model.
|
|
*/
|
|
RealtimeMapping.prototype.activate = function(quiet)
|
|
{
|
|
this.realtimePageFormatChanged(this.diagramMap.get('pageFormat'), quiet);
|
|
this.realtimePageScaleChanged(this.diagramMap.get('pageScale'), quiet);
|
|
this.realtimeMathEnabledChanged(this.diagramMap.get('mathEnabled'), quiet);
|
|
this.realtimeBackgroundColorChanged(this.diagramMap.get('backgroundColor'), quiet);
|
|
this.realtimeShadowVisibleChanged(this.diagramMap.get('shadowVisible'), quiet);
|
|
this.realtimeFoldingEnabledChanged(this.diagramMap.get('foldingEnabled'), quiet);
|
|
this.realtimePageVisibleChanged(this.diagramMap.get('pageVisible'), quiet);
|
|
this.realtimeBackgroundImageChanged(this.diagramMap.get('backgroundImage'), quiet);
|
|
};
|
|
|
|
/**
|
|
* Syncs initial state from graph model to collab model.
|
|
*/
|
|
RealtimeMapping.prototype.initRealtime = function()
|
|
{
|
|
this.rtModel.beginCompoundOperation();
|
|
|
|
try
|
|
{
|
|
var rtCell = this.createRealtimeCell(this.getGraphModel().getRoot());
|
|
this.saveRealtimeCell(rtCell.cell);
|
|
this.diagramMap.set(this.driveRealtime.rootKey, rtCell);
|
|
|
|
if (this.page.graphModelNode != null)
|
|
{
|
|
this.writeNodeToRealtime(this.page.graphModelNode);
|
|
}
|
|
else
|
|
{
|
|
var vs = this.page.viewState;
|
|
var pf = (vs != null) ? vs.pageFormat : mxSettings.getPageFormat();
|
|
|
|
this.diagramMap.set('shadowVisible', (vs != null && vs.shadowVisible) ? '1' : '0');
|
|
this.diagramMap.set('foldingEnabled', (vs != null && !vs.foldingEnabled) ? '0' : '1');
|
|
this.diagramMap.set('mathEnabled', (vs != null && vs.mathEnabled) ? '1' : '0');
|
|
this.diagramMap.set('pageScale', this.graph.pageScale);
|
|
this.diagramMap.set('pageVisible', (vs != null && !vs.pageVisible) ? '0' : '1');
|
|
this.diagramMap.set('pageFormat', pf.width + ',' + pf.height);
|
|
this.diagramMap.set('backgroundImage', (vs != null && vs.backgroundImage != null) ?
|
|
JSON.stringify(vs.backgroundImage) : '');
|
|
this.diagramMap.set('backgroundColor', (vs != null && vs.background != null) ?
|
|
this.graph.background : '');
|
|
}
|
|
|
|
this.root.set('modifiedDate', new Date().getTime());
|
|
this.rtModel.endCompoundOperation();
|
|
}
|
|
catch (e)
|
|
{
|
|
this.rtModel.endCompoundOperation();
|
|
this.ui.handleError(e);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Syncs initial state from graph model to collab model.
|
|
*/
|
|
RealtimeMapping.prototype.createRealtimeCell = function(cell)
|
|
{
|
|
var rtCell = cell.rtCell;
|
|
|
|
if (rtCell == null)
|
|
{
|
|
rtCell = this.rtModel.create('Cell');
|
|
rtCell.children = this.rtModel.createList();
|
|
rtCell.cell = cell;
|
|
cell.rtCell = rtCell;
|
|
|
|
rtCell.cellId = cell.id;
|
|
rtCell.type = (cell.vertex) ? 'vertex' : ((cell.edge) ? 'edge' : '');
|
|
rtCell.connectable = (cell.connectable == null || cell.connectable) ? '1' : '0';
|
|
|
|
if (mxUtils.isNode(cell.value))
|
|
{
|
|
rtCell.xmlValue = mxUtils.getXml(cell.value);
|
|
}
|
|
else if (cell.value != null)
|
|
{
|
|
rtCell.value = cell.value;
|
|
}
|
|
|
|
rtCell.style = (cell.style != null) ? cell.style : null;
|
|
rtCell.geometry = (cell.geometry != null) ? mxUtils.getXml(this.driveRealtime.codec.encode(cell.geometry)) : null;
|
|
rtCell.visible = (cell.visible == null || cell.visible) ? '1' : '0';
|
|
rtCell.collapsed = (cell.collapsed != null && cell.collapsed) ? '1' : '0';
|
|
|
|
for (var i = 0; i < this.graphModel.getChildCount(cell); i++)
|
|
{
|
|
var child = this.graphModel.getChildAt(cell, i);
|
|
this.createRealtimeCell(child);
|
|
|
|
if (child.rtCell.parent == null)
|
|
{
|
|
child.rtCell.parent = rtCell;
|
|
rtCell.children.push(child.rtCell);
|
|
}
|
|
}
|
|
|
|
this.installRealtimeCellListeners(rtCell);
|
|
}
|
|
|
|
return rtCell;
|
|
};
|
|
|
|
/**
|
|
* Syncs initial state from graph model to collab model.
|
|
*/
|
|
RealtimeMapping.prototype.saveRealtimeCell = function(cell)
|
|
{
|
|
if (cell.source != null)
|
|
{
|
|
if (cell.source.rtCell == null)
|
|
{
|
|
this.createRealtimeCell(cell.source);
|
|
}
|
|
|
|
cell.rtCell.source = cell.source.rtCell;
|
|
}
|
|
else
|
|
{
|
|
cell.rtCell.source = null;
|
|
}
|
|
|
|
if (cell.target != null)
|
|
{
|
|
if (cell.target.rtCell == null)
|
|
{
|
|
this.createRealtimeCell(cell.target);
|
|
}
|
|
|
|
cell.rtCell.target = cell.target.rtCell;
|
|
}
|
|
else
|
|
{
|
|
cell.rtCell.target = null;
|
|
}
|
|
|
|
for (var i = 0; i < this.graphModel.getChildCount(cell); i++)
|
|
{
|
|
this.saveRealtimeCell(this.graphModel.getChildAt(cell, i));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Syncs initial state from collab model to graph model.
|
|
*/
|
|
RealtimeMapping.prototype.createCell = function(rtCell)
|
|
{
|
|
var cell = rtCell.cell;
|
|
|
|
if (cell == null)
|
|
{
|
|
cell = new mxCell();
|
|
rtCell.cell = cell;
|
|
cell.rtCell = rtCell;
|
|
|
|
cell.id = rtCell.cellId;
|
|
cell.vertex = rtCell.type == 'vertex';
|
|
cell.edge = rtCell.type == 'edge';
|
|
cell.connectable = rtCell.connectable != '0';
|
|
cell.value = (rtCell.xmlValue != null) ? mxUtils.parseXml(rtCell.xmlValue).documentElement : rtCell.value;
|
|
cell.style = rtCell.style;
|
|
cell.geometry = (rtCell.geometry != null) ? this.driveRealtime.codec.decode(mxUtils.parseXml(rtCell.geometry).documentElement) : null;
|
|
cell.visible = rtCell.visible != '0';
|
|
cell.collapsed = rtCell.collapsed == '1';
|
|
|
|
for (var i = 0; i < rtCell.children.length; i++)
|
|
{
|
|
var rtChild = rtCell.children.get(i);
|
|
this.createCell(rtChild);
|
|
|
|
if (rtChild.cell.parent == null)
|
|
{
|
|
cell.insert(rtChild.cell);
|
|
}
|
|
}
|
|
|
|
this.installRealtimeCellListeners(rtCell);
|
|
}
|
|
|
|
return cell;
|
|
};
|
|
|
|
/**
|
|
* Restores connection between edges and terminals.
|
|
*/
|
|
RealtimeMapping.prototype.restoreCell = function(rtCell)
|
|
{
|
|
var valid = true;
|
|
|
|
if (rtCell.cell != null)
|
|
{
|
|
//console.log('restoreCell', rtCell.cellId);
|
|
|
|
if (rtCell.source != null)
|
|
{
|
|
// Removes edge if source is no longer in the model
|
|
if (rtCell.source.parent == null)
|
|
{
|
|
//console.log('invalid source', valid, rtCell.cellId, rtCell.source.cellId);
|
|
rtCell.source = null;
|
|
valid = false;
|
|
}
|
|
else
|
|
{
|
|
if (rtCell.source.cell == null)
|
|
{
|
|
this.createCell(rtCell.source);
|
|
}
|
|
|
|
rtCell.source.cell.insertEdge(rtCell.cell, true);
|
|
}
|
|
}
|
|
|
|
if (valid && rtCell.target != null)
|
|
{
|
|
// Removes edge if source is no longer in the model
|
|
if (rtCell.target.parent == null)
|
|
{
|
|
//console.log('invalid target', valid, rtCell.cellId, rtCell.target.cellId);
|
|
rtCell.target = null;
|
|
valid = false;
|
|
}
|
|
else
|
|
{
|
|
if (rtCell.target.cell == null)
|
|
{
|
|
this.createCell(rtCell.target);
|
|
}
|
|
|
|
rtCell.target.cell.insertEdge(rtCell.cell, false);
|
|
}
|
|
}
|
|
|
|
// Checks if edge contains required terminals or terminal points
|
|
if (valid && this.graphModel.isEdge(rtCell.cell))
|
|
{
|
|
var geo = this.graphModel.getGeometry(rtCell.cell);
|
|
valid = geo != null &&
|
|
(this.graphModel.getTerminal(rtCell.cell, true) != null || geo.getTerminalPoint(true) != null) &&
|
|
(this.graphModel.getTerminal(rtCell.cell, false) != null || geo.getTerminalPoint(false) != null);
|
|
//console.log('geometry check', valid, rtCell.cellId);
|
|
}
|
|
}
|
|
|
|
// Removes invalid cell
|
|
if (!valid)
|
|
{
|
|
if (rtCell.parent != null)
|
|
{
|
|
rtCell.parent.children.removeValue(rtCell);
|
|
rtCell.parent = null;
|
|
}
|
|
|
|
if (rtCell.cell != null)
|
|
{
|
|
// TODO: Remove from source and target?
|
|
//console.log('remove invalid cell', rtCell.cellId);
|
|
this.getGraphModel().remove(rtCell.cell);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (var i = 0; i < rtCell.children.length; i++)
|
|
{
|
|
this.restoreCell(rtCell.children.get(i));
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Restores connection between edges and terminals.
|
|
*/
|
|
RealtimeMapping.prototype.containsRealtimeCell = function(rtCell)
|
|
{
|
|
var tmp = rtCell;
|
|
|
|
while (tmp.parent != null)
|
|
{
|
|
tmp = tmp.parent;
|
|
}
|
|
|
|
return tmp == this.diagramMap.get(this.driveRealtime.rootKey);
|
|
};
|
|
|
|
/**
|
|
*
|
|
*/
|
|
RealtimeMapping.prototype.beginUpdate = function()
|
|
{
|
|
var graphModel = this.getGraphModel();
|
|
|
|
if (!this.driveRealtime.ignoreChange)
|
|
{
|
|
this.driveRealtime.ignoreChange = true;
|
|
graphModel.beginUpdate();
|
|
|
|
window.setTimeout(mxUtils.bind(this, function()
|
|
{
|
|
graphModel.endUpdate();
|
|
this.driveRealtime.ignoreChange = false;
|
|
}), 0);
|
|
}
|
|
|
|
return graphModel;
|
|
};
|
|
|
|
/**
|
|
* Syncs initial state from collab model to graph model.
|
|
*/
|
|
RealtimeMapping.prototype.installAllRealtimeCellListeners = function(rtCell)
|
|
{
|
|
if (rtCell != null)
|
|
{
|
|
this.installRealtimeCellListeners(rtCell);
|
|
|
|
for (var i = 0; i < rtCell.children.length; i++)
|
|
{
|
|
this.installAllRealtimeCellListeners(rtCell.children.get(i));
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Adds the listener for added and removed cells in the collab model and maps
|
|
* them to the graph model.
|
|
*/
|
|
RealtimeMapping.prototype.installRealtimeCellListeners = function(rtCell)
|
|
{
|
|
rtCell.addEventListener(gapi.drive.realtime.EventType.VALUE_CHANGED, mxUtils.bind(this, function(evt)
|
|
{
|
|
this.handleValueChanged(rtCell, evt);
|
|
this.needsUpdate = true;
|
|
}));
|
|
|
|
rtCell.children.addEventListener(gapi.drive.realtime.EventType.VALUES_ADDED, mxUtils.bind(this, function(evt)
|
|
{
|
|
this.handleValuesAdded(rtCell, evt);
|
|
this.needsUpdate = true;
|
|
}));
|
|
|
|
rtCell.children.addEventListener(gapi.drive.realtime.EventType.VALUES_REMOVED, mxUtils.bind(this, function(evt)
|
|
{
|
|
this.handleValuesRemoved(rtCell, evt);
|
|
this.needsUpdate = true;
|
|
}));
|
|
};
|
|
|
|
/**
|
|
* Adds the listener for added and removed cells in the collab model and maps
|
|
* them to the graph model.
|
|
*/
|
|
RealtimeMapping.prototype.handleValueChanged = function(rtCell, evt)
|
|
{
|
|
var cell = rtCell.cell;
|
|
|
|
if (!this.driveRealtime.isLocalEvent(evt) && cell != null)
|
|
{
|
|
var value = evt.newValue;
|
|
var key = evt.property;
|
|
var graphModel = this.beginUpdate();
|
|
//console.log('valueChanged: cell=' + rtCell.cellId + ' key=' + key + ' value=' + ((value != null) ? (value.cellId || value) : '[null]'));
|
|
|
|
if (key == 'type')
|
|
{
|
|
cell.vertex = value == 'vertex';
|
|
cell.edge = value == 'edge';
|
|
}
|
|
else if (key == 'connectable')
|
|
{
|
|
cell.connectable = (value == '1');
|
|
}
|
|
else if (key == 'source' || key == 'target')
|
|
{
|
|
if (value == null)
|
|
{
|
|
if (evt.oldValue != null)
|
|
{
|
|
graphModel.setTerminal(cell, null, key == 'source');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Handles the case where an edge is connected to a vertex which is not in the model
|
|
if (value.cell == null || !this.containsRealtimeCell(value) || graphModel.getCell(value.cellId) == null)
|
|
{
|
|
if (rtCell.parent != null)
|
|
{
|
|
rtCell.parent.children.removeValue(rtCell);
|
|
rtCell.parent = null;
|
|
}
|
|
|
|
graphModel.setTerminal(cell, null, key == 'source');
|
|
graphModel.remove(rtCell.cell);
|
|
rtCell[key] = null;
|
|
}
|
|
else
|
|
{
|
|
graphModel.setTerminal(cell, value.cell, key == 'source');
|
|
}
|
|
}
|
|
}
|
|
else if (key == 'value')
|
|
{
|
|
graphModel.setValue(cell, value);
|
|
}
|
|
else if (key == 'xmlValue')
|
|
{
|
|
graphModel.setValue(cell, mxUtils.parseXml(value).documentElement);
|
|
}
|
|
else if (key == 'style')
|
|
{
|
|
graphModel.setStyle(cell, value);
|
|
}
|
|
else if (key == 'geometry')
|
|
{
|
|
var geometry = (value != null) ? this.driveRealtime.codec.decode(mxUtils.parseXml(value).documentElement) : null;
|
|
graphModel.setGeometry(cell, geometry);
|
|
}
|
|
else if (key == 'collapsed')
|
|
{
|
|
graphModel.setCollapsed(cell, value == '1');
|
|
}
|
|
else if (key == 'visible')
|
|
{
|
|
graphModel.setVisible(cell, value == '1');
|
|
}
|
|
else if (key == 'parent')
|
|
{
|
|
// Removes the child from its previous parent in the realtime model.
|
|
if (evt.oldValue != null)
|
|
{
|
|
//console.log('remove clone', 'parent', evt.oldValue.cellId, 'child', rtCell.cellId);
|
|
evt.oldValue.children.removeValue(rtCell);
|
|
}
|
|
else
|
|
{
|
|
this.createCell(rtCell);
|
|
this.restoreCell(rtCell);
|
|
}
|
|
|
|
if (value == null)
|
|
{
|
|
graphModel.remove(cell);
|
|
}
|
|
else
|
|
{
|
|
var index = value.children.indexOf(rtCell);
|
|
|
|
if (index >= 0)
|
|
{
|
|
//console.log('move child', 'parent', value.cellId, 'child', rtCell.cellId, index);
|
|
graphModel.add(value.cell, rtCell.cell, index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Adds the listener for added and removed cells in the collab model and maps
|
|
* them to the graph model.
|
|
*/
|
|
RealtimeMapping.prototype.handleValuesAdded = function(rtCell, evt)
|
|
{
|
|
if (!this.driveRealtime.isLocalEvent(evt))
|
|
{
|
|
var graphModel = this.beginUpdate();
|
|
|
|
for (var i = 0; i < evt.values.length; i++)
|
|
{
|
|
var rtChild = evt.values[i];
|
|
//console.log('valueAdded', 'parent', rtCell.cellId, 'child', rtChild.cellId, 'index', evt.index + i, rtChild);
|
|
|
|
// Removes child if the parent of the child and the parent of the children array are not
|
|
// the same. This happens if clients move a cell into different parents concurrently.
|
|
if (rtChild.parent != null)
|
|
{
|
|
if (rtChild.parent != rtCell)
|
|
{
|
|
//console.log('remove clone', 'parent', rtCell.cellId, 'child', rtChild.cellId);
|
|
rtCell.children.removeValue(rtChild);
|
|
}
|
|
else
|
|
{
|
|
if (rtChild.cell == null || rtChild.cell.parent == null)
|
|
{
|
|
this.createCell(rtChild);
|
|
this.restoreCell(rtChild);
|
|
}
|
|
|
|
// Resolves conflict when two clients change the order of a child at the same
|
|
// time which results in the same child appearing multiple times in the list.
|
|
// Note that the actual child index may be different from the event information
|
|
// at this point so a generic check for duplicates is performed based on the
|
|
// first appearance of the cell in the list.
|
|
var first = rtCell.children.indexOf(rtChild);
|
|
var last = rtCell.children.lastIndexOf(rtChild);
|
|
|
|
while (first != last)
|
|
{
|
|
//console.log('remove duplicate', rtChild.cellId, last);
|
|
rtCell.children.remove(last);
|
|
last = rtCell.children.lastIndexOf(rtChild);
|
|
}
|
|
|
|
// Inserts the child at the smallest index to maintain consistency with RT
|
|
if (rtChild.parent == rtCell)
|
|
{
|
|
//console.log('add', 'parent', rtCell.cellId, 'child', rtChild.cellId, 'index', Math.min(first, evt.index + i));
|
|
graphModel.add(rtCell.cell, rtChild.cell, Math.min(first, evt.index + i));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Adds the listener for added and removed cells in the collab model and maps
|
|
* them to the graph model.
|
|
*/
|
|
RealtimeMapping.prototype.handleValuesRemoved = function(rtCell, evt)
|
|
{
|
|
if (!this.driveRealtime.isLocalEvent(evt))
|
|
{
|
|
var graphModel = this.beginUpdate();
|
|
|
|
for (var i = 0; i < evt.values.length; i++)
|
|
{
|
|
var rtChild = evt.values[i];
|
|
|
|
if (rtChild.cell != null)
|
|
{
|
|
//console.log('valueRemoved', 'parent', rtCell.cellId, 'child', rtChild.cellId,
|
|
// 'index', evt.index + i, rtChild, rtChild.cell);
|
|
|
|
// Checks if the realtime parent and the graph parent are different and updates the parent
|
|
// in the graph to match the realtime parent. This happens if the child was removed as a
|
|
// clone in another client.
|
|
if (rtChild.parent != null && rtChild.parent != rtCell && rtChild.cell.parent != rtChild.parent.cell)
|
|
{
|
|
//console.log('move clone', rtChild.cellId, evt.index + i, rtChild.parent.cellId);
|
|
var index = rtChild.parent.children.indexOf(rtChild);
|
|
graphModel.add(rtChild.parent.cell, rtChild.cell, index);
|
|
}
|
|
else
|
|
{
|
|
// Checks if the realtime parent contains a duplicate entry of this child
|
|
// and updates the index of the child in the graph. This happens if the
|
|
// child was removed as a duplicate entry in another client.
|
|
var index = rtCell.children.indexOf(rtChild);
|
|
|
|
if (index >= 0)
|
|
{
|
|
//console.log('adjust duplicate', rtCell.cellId, evt.index + i, rtChild.cellId, 'index', index);
|
|
graphModel.add(rtCell.cell, rtChild.cell, index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Syncs initial state from collab model to graph model.
|
|
*/
|
|
RealtimeMapping.prototype.realtimePageFormatChanged = function(value, quiet)
|
|
{
|
|
if (value != null)
|
|
{
|
|
var values = value.split(',');
|
|
|
|
if (values.length > 1)
|
|
{
|
|
if (!this.isActive())
|
|
{
|
|
if (this.page.viewState != null)
|
|
{
|
|
this.page.viewState.pageFormat = new mxRectangle(0, 0, parseInt(values[0]), parseInt(values[1]));
|
|
}
|
|
}
|
|
else if (quiet)
|
|
{
|
|
this.graph.pageFormat = new mxRectangle(0, 0, parseInt(values[0]), parseInt(values[1]));
|
|
}
|
|
else
|
|
{
|
|
this.driveRealtime.ignorePageFormatChanged = true;
|
|
this.ui.setPageFormat(new mxRectangle(0, 0, parseInt(values[0]), parseInt(values[1])));
|
|
this.driveRealtime.ignorePageFormatChanged = false;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Syncs initial state from collab model to graph model.
|
|
*/
|
|
RealtimeMapping.prototype.realtimePageScaleChanged = function(value, quiet)
|
|
{
|
|
if (value != null)
|
|
{
|
|
if (!this.isActive())
|
|
{
|
|
if (this.page.viewState != null)
|
|
{
|
|
this.page.viewState.pageScale = parseFloat(value);
|
|
}
|
|
}
|
|
else if (quiet)
|
|
{
|
|
this.graph.pageScale = parseFloat(value);
|
|
}
|
|
else
|
|
{
|
|
this.driveRealtime.ignorePageScaleChanged = true;
|
|
this.ui.setPageScale(parseFloat(value));
|
|
this.driveRealtime.ignorePageScaleChanged = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Syncs initial state from collab model to graph model.
|
|
*/
|
|
RealtimeMapping.prototype.realtimeBackgroundColorChanged = function(value, quiet)
|
|
{
|
|
if (!this.isActive())
|
|
{
|
|
if (this.page.viewState != null)
|
|
{
|
|
this.page.viewState.background = (value == '') ? null : value;
|
|
}
|
|
}
|
|
else if (quiet)
|
|
{
|
|
this.graph.background = (value == '') ? null : value;
|
|
}
|
|
else
|
|
{
|
|
this.driveRealtime.ignoreBackgroundColorChanged = true;
|
|
this.ui.setBackgroundColor((value == '') ? null : value);
|
|
this.driveRealtime.ignoreBackgroundColorChanged = false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Syncs initial state from collab model to graph model.
|
|
*/
|
|
RealtimeMapping.prototype.realtimeFoldingEnabledChanged = function(value, quiet)
|
|
{
|
|
if (!this.isActive())
|
|
{
|
|
if (this.page.viewState != null)
|
|
{
|
|
this.page.viewState.foldingEnabled = value == '1';
|
|
}
|
|
}
|
|
else if (quiet)
|
|
{
|
|
this.graph.foldingEnabled = value == '1';
|
|
}
|
|
else
|
|
{
|
|
this.driveRealtime.ignoreFoldingEnabledChanged = true;
|
|
this.ui.setFoldingEnabled(value == '1');
|
|
this.driveRealtime.ignoreFoldingEnabledChanged = false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Syncs initial state from collab model to graph model.
|
|
*/
|
|
RealtimeMapping.prototype.realtimePageVisibleChanged = function(value, quiet)
|
|
{
|
|
if (!this.isActive())
|
|
{
|
|
if (this.page.viewState != null)
|
|
{
|
|
this.page.viewState.pageVisible = value != '0';
|
|
}
|
|
}
|
|
else if (quiet)
|
|
{
|
|
this.graph.pageVisible = value != '0';
|
|
this.graph.pageBreaksVisible = this.graph.pageVisible;
|
|
this.graph.preferPageSize = this.graph.pageVisible;
|
|
}
|
|
else
|
|
{
|
|
this.driveRealtime.ignorePageVisibleChanged = true;
|
|
this.ui.setPageVisible(value != '0');
|
|
this.driveRealtime.ignorePageVisibleChanged = false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Syncs initial state from collab model to graph model.
|
|
*/
|
|
RealtimeMapping.prototype.realtimeShadowVisibleChanged = function(value, quiet)
|
|
{
|
|
// Does not need quiet mode as it's handled independently of refresh
|
|
if (!this.isActive())
|
|
{
|
|
if (this.page.viewState != null)
|
|
{
|
|
this.page.viewState.shadowVisible = value == '1';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.driveRealtime.ignoreShadowVisibleChanged = true;
|
|
this.ui.editor.graph.setShadowVisible(value == '1');
|
|
this.driveRealtime.ignoreShadowVisibleChanged = false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Syncs initial state from collab model to graph model.
|
|
*/
|
|
RealtimeMapping.prototype.realtimeBackgroundImageChanged = function(value, quiet)
|
|
{
|
|
var data = (value != null && value.length > 0) ? JSON.parse(value) : null;
|
|
|
|
if (!this.isActive())
|
|
{
|
|
if (this.page.viewState != null)
|
|
{
|
|
this.page.viewState.backgroundImage = (data != null) ? new mxImage(data.src, data.width, data.height) : null;
|
|
}
|
|
}
|
|
else if (quiet)
|
|
{
|
|
this.graph.setBackgroundImage((data != null) ? new mxImage(data.src, data.width, data.height) : null);
|
|
}
|
|
else
|
|
{
|
|
this.driveRealtime.ignoreBackgroundImageChanged = true;
|
|
this.ui.setBackgroundImage((data != null) ? new mxImage(data.src, data.width, data.height) : null);
|
|
this.driveRealtime.ignoreBackgroundImageChanged = false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Syncs initial state from collab model to graph model.
|
|
*/
|
|
RealtimeMapping.prototype.realtimeMathEnabledChanged = function(value, quiet)
|
|
{
|
|
if (!this.isActive())
|
|
{
|
|
if (this.page.viewState != null)
|
|
{
|
|
this.page.viewState.mathEnabled = urlParams['math'] == '1' || value == '1';
|
|
}
|
|
}
|
|
else if (quiet)
|
|
{
|
|
this.graph.mathEnabled = urlParams['math'] == '1' || value == '1';
|
|
}
|
|
else
|
|
{
|
|
this.driveRealtime.ignoreMathEnabledChanged = true;
|
|
this.ui.setMathEnabled(urlParams['math'] == '1' || value == '1');
|
|
this.driveRealtime.ignoreMathEnabledChanged = false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Syncs initial state from collab model to graph model.
|
|
*/
|
|
RealtimeMapping.prototype.removeAllRealtimeCellListeners = function(rtCell)
|
|
{
|
|
if (rtCell != null)
|
|
{
|
|
rtCell.removeAllEventListeners();
|
|
rtCell.children.removeAllEventListeners();
|
|
|
|
for (var i = 0; i < rtCell.children.length; i++)
|
|
{
|
|
this.removeAllRealtimeCellListeners(rtCell.children.get(i));
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Syncs initial state from collab model to graph model.
|
|
*/
|
|
RealtimeMapping.prototype.destroy = function()
|
|
{
|
|
this.diagramMap.removeAllEventListeners();
|
|
this.selectionMap.removeAllEventListeners();
|
|
this.removeAllRealtimeCellListeners(this.diagramMap.get(this.driveRealtime.rootKey));
|
|
};
|