import * as util from '../util';
import * as is from '../is';
import Map from '../map';
import Set from '../set';
import Element from './element';
import algorithms from './algorithms';
import animation from './animation';
import classNames from './class';
import comparators from './comparators';
import compounds from './compounds';
import data from './data';
import degree from './degree';
import dimensions from './dimensions';
import events from './events';
import filter from './filter';
import group from './group';
import iteration from './iteration';
import layout from './layout';
import style from './style';
import switchFunctions from './switch-functions';
import traversing from './traversing';
let Collection = function( cy, elements, unique = false, removed = false ){
if( cy === undefined ){
util.error( 'A collection must have a reference to the core' );
return;
}
let map = new Map();
let createdElements = false;
if( !elements ){
elements = [];
} else if( elements.length > 0 && is.plainObject( elements[0] ) && !is.element( elements[0] ) ){
createdElements = true;
let eles = [];
let elesIds = new Set();
for( let i = 0, l = elements.length; i < l; i++ ){
let json = elements[ i ];
if( json.data == null ){
json.data = {};
}
let data = json.data;
if( data.id == null ){
data.id = util.uuid();
} else if( cy.hasElementWithId( data.id ) || elesIds.has( data.id ) ){
continue; }
let ele = new Element( cy, json, false );
eles.push( ele );
elesIds.add( data.id );
}
elements = eles;
}
this.length = 0;
for( let i = 0, l = elements.length; i < l; i++ ){
let element = elements[i][0]; if( element == null ){ continue; }
let id = element._private.data.id;
if( !unique || !map.has(id) ){
if( unique ){
map.set( id, {
index: this.length,
ele: element
} );
}
this[ this.length ] = element;
this.length++;
}
}
this._private = {
eles: this,
cy: cy,
get map(){
if( this.lazyMap == null ){
this.rebuildMap();
}
return this.lazyMap;
},
set map(m){
this.lazyMap = m;
},
rebuildMap(){
const m = this.lazyMap = new Map();
const eles = this.eles;
for( let i = 0; i < eles.length; i++ ){
const ele = eles[i];
m.set(ele.id(), { index: i, ele });
}
}
};
if( unique ){
this._private.map = map;
}
if( createdElements && !removed ){
this.restore();
}
};
let elesfn = Element.prototype = Collection.prototype = Object.create(Array.prototype);
elesfn.instanceString = function(){
return 'collection';
};
elesfn.spawn = function( eles, unique ){
return new Collection( this.cy(), eles, unique );
};
elesfn.spawnSelf = function(){
return this.spawn( this );
};
elesfn.cy = function(){
return this._private.cy;
};
elesfn.renderer = function(){
return this._private.cy.renderer();
};
elesfn.element = function(){
return this[0];
};
elesfn.collection = function(){
if( is.collection( this ) ){
return this;
} else { return new Collection( this._private.cy, [ this ] );
}
};
elesfn.unique = function(){
return new Collection( this._private.cy, this, true );
};
elesfn.hasElementWithId = function( id ){
id = '' + id;
return this._private.map.has( id );
};
elesfn.getElementById = function( id ){
id = '' + id;
let cy = this._private.cy;
let entry = this._private.map.get( id );
return entry ? entry.ele : new Collection( cy ); };
elesfn.$id = elesfn.getElementById;
elesfn.poolIndex = function(){
let cy = this._private.cy;
let eles = cy._private.elements;
let id = this[0]._private.data.id;
return eles._private.map.get( id ).index;
};
elesfn.indexOf = function( ele ){
let id = ele[0]._private.data.id;
return this._private.map.get( id ).index;
};
elesfn.indexOfId = function( id ){
id = '' + id;
return this._private.map.get( id ).index;
};
elesfn.json = function( obj ){
let ele = this.element();
let cy = this.cy();
if( ele == null && obj ){ return this; }
if( ele == null ){ return undefined; }
let p = ele._private;
if( is.plainObject( obj ) ){
cy.startBatch();
if( obj.data ){
ele.data( obj.data );
let data = p.data;
if( ele.isEdge() ){ let move = false;
let spec = {};
let src = obj.data.source;
let tgt = obj.data.target;
if( src != null && src != data.source ){
spec.source = '' + src; move = true;
}
if( tgt != null && tgt != data.target ){
spec.target = '' + tgt; move = true;
}
if( move ){
ele = ele.move(spec);
}
} else { let newParentValSpecd = 'parent' in obj.data;
let parent = obj.data.parent;
if( newParentValSpecd && (parent != null || data.parent != null) && parent != data.parent ){
if( parent === undefined ){ parent = null;
}
if( parent != null ){
parent = '' + parent; }
ele = ele.move({ parent });
}
}
}
if( obj.position ){
ele.position( obj.position );
}
let checkSwitch = function( k, trueFnName, falseFnName ){
let obj_k = obj[ k ];
if( obj_k != null && obj_k !== p[ k ] ){
if( obj_k ){
ele[ trueFnName ]();
} else {
ele[ falseFnName ]();
}
}
};
checkSwitch( 'removed', 'remove', 'restore' );
checkSwitch( 'selected', 'select', 'unselect' );
checkSwitch( 'selectable', 'selectify', 'unselectify' );
checkSwitch( 'locked', 'lock', 'unlock' );
checkSwitch( 'grabbable', 'grabify', 'ungrabify' );
checkSwitch( 'pannable', 'panify', 'unpanify' );
if( obj.classes != null ){
ele.classes( obj.classes );
}
cy.endBatch();
return this;
} else if( obj === undefined ){
let json = {
data: util.copy( p.data ),
position: util.copy( p.position ),
group: p.group,
removed: p.removed,
selected: p.selected,
selectable: p.selectable,
locked: p.locked,
grabbable: p.grabbable,
pannable: p.pannable,
classes: null
};
json.classes = '';
let i = 0;
p.classes.forEach( cls => json.classes += ( i++ === 0 ? cls : ' ' + cls ) );
return json;
}
};
elesfn.jsons = function(){
let jsons = [];
for( let i = 0; i < this.length; i++ ){
let ele = this[ i ];
let json = ele.json();
jsons.push( json );
}
return jsons;
};
elesfn.clone = function(){
let cy = this.cy();
let elesArr = [];
for( let i = 0; i < this.length; i++ ){
let ele = this[ i ];
let json = ele.json();
let clone = new Element( cy, json, false );
elesArr.push( clone );
}
return new Collection( cy, elesArr );
};
elesfn.copy = elesfn.clone;
elesfn.restore = function( notifyRenderer = true, addToPool = true ){
let self = this;
let cy = self.cy();
let cy_p = cy._private;
let nodes = [];
let edges = [];
let elements;
for( let i = 0, l = self.length; i < l; i++ ){
let ele = self[ i ];
if( addToPool && !ele.removed() ){
continue;
}
if( ele.isNode() ){ nodes.push( ele );
} else { edges.push( ele );
}
}
elements = nodes.concat( edges );
let i;
let removeFromElements = function(){
elements.splice( i, 1 );
i--;
};
for( i = 0; i < elements.length; i++ ){
let ele = elements[ i ];
let _private = ele._private;
let data = _private.data;
ele.clearTraversalCache();
if( !addToPool && !_private.removed ){
} else if( data.id === undefined ){
data.id = util.uuid();
} else if( is.number( data.id ) ){
data.id = '' + data.id;
} else if( is.emptyString( data.id ) || !is.string( data.id ) ){
util.error( 'Can not create element with invalid string ID `' + data.id + '`' );
removeFromElements();
continue;
} else if( cy.hasElementWithId( data.id ) ){
util.error( 'Can not create second element with ID `' + data.id + '`' );
removeFromElements();
continue;
}
let id = data.id;
if( ele.isNode() ){ let pos = _private.position;
if( pos.x == null ){
pos.x = 0;
}
if( pos.y == null ){
pos.y = 0;
}
}
if( ele.isEdge() ){
let edge = ele;
let fields = [ 'source', 'target' ];
let fieldsLength = fields.length;
let badSourceOrTarget = false;
for( let j = 0; j < fieldsLength; j++ ){
let field = fields[ j ];
let val = data[ field ];
if( is.number( val ) ){
val = data[ field ] = '' + data[ field ]; }
if( val == null || val === '' ){
util.error( 'Can not create edge `' + id + '` with unspecified ' + field );
badSourceOrTarget = true;
} else if( !cy.hasElementWithId( val ) ){
util.error( 'Can not create edge `' + id + '` with nonexistant ' + field + ' `' + val + '`' );
badSourceOrTarget = true;
}
}
if( badSourceOrTarget ){ removeFromElements(); continue; }
let src = cy.getElementById( data.source );
let tgt = cy.getElementById( data.target );
if (src.same(tgt)) {
src._private.edges.push( edge );
} else {
src._private.edges.push( edge );
tgt._private.edges.push( edge );
}
edge._private.source = src;
edge._private.target = tgt;
}
_private.map = new Map();
_private.map.set( id, { ele: ele, index: 0 } );
_private.removed = false;
if( addToPool ){
cy.addToPool( ele );
}
}
for( let i = 0; i < nodes.length; i++ ){ let node = nodes[ i ];
let data = node._private.data;
if( is.number( data.parent ) ){ data.parent = '' + data.parent;
}
let parentId = data.parent;
let specifiedParent = parentId != null;
if( specifiedParent || node._private.parent ){
let parent = node._private.parent ? cy.collection().merge(node._private.parent) : cy.getElementById( parentId );
if( parent.empty() ){
data.parent = undefined;
} else if( parent[0].removed() ) {
util.warn('Node added with missing parent, reference to parent removed');
data.parent = undefined;
node._private.parent = null;
} else {
let selfAsParent = false;
let ancestor = parent;
while( !ancestor.empty() ){
if( node.same( ancestor ) ){
selfAsParent = true;
data.parent = undefined;
break;
}
ancestor = ancestor.parent();
}
if( !selfAsParent ){
parent[0]._private.children.push( node );
node._private.parent = parent[0];
cy_p.hasCompoundNodes = true;
}
} } }
if( elements.length > 0 ){
let restored = elements.length === self.length ? self : new Collection( cy, elements );
for( let i = 0; i < restored.length; i++ ){
let ele = restored[i];
if( ele.isNode() ){ continue; }
ele.parallelEdges().clearTraversalCache();
ele.source().clearTraversalCache();
ele.target().clearTraversalCache();
}
let toUpdateStyle;
if( cy_p.hasCompoundNodes ){
toUpdateStyle = cy.collection().merge( restored ).merge( restored.connectedNodes() ).merge( restored.parent() );
} else {
toUpdateStyle = restored;
}
toUpdateStyle.dirtyCompoundBoundsCache().dirtyBoundingBoxCache().updateStyle( notifyRenderer );
if( notifyRenderer ){
restored.emitAndNotify( 'add' );
} else if( addToPool ){
restored.emit( 'add' );
}
}
return self; };
elesfn.removed = function(){
let ele = this[0];
return ele && ele._private.removed;
};
elesfn.inside = function(){
let ele = this[0];
return ele && !ele._private.removed;
};
elesfn.remove = function( notifyRenderer = true, removeFromPool = true ){
let self = this;
let elesToRemove = [];
let elesToRemoveIds = {};
let cy = self._private.cy;
function addConnectedEdges( node ){
let edges = node._private.edges;
for( let i = 0; i < edges.length; i++ ){
add( edges[ i ] );
}
}
function addChildren( node ){
let children = node._private.children;
for( let i = 0; i < children.length; i++ ){
add( children[ i ] );
}
}
function add( ele ){
let alreadyAdded = elesToRemoveIds[ ele.id() ];
if( (removeFromPool && ele.removed()) || alreadyAdded ){
return;
} else {
elesToRemoveIds[ ele.id() ] = true;
}
if( ele.isNode() ){
elesToRemove.push( ele );
addConnectedEdges( ele );
addChildren( ele );
} else {
elesToRemove.unshift( ele ); }
}
for( let i = 0, l = self.length; i < l; i++ ){
let ele = self[ i ];
add( ele );
}
function removeEdgeRef( node, edge ){
let connectedEdges = node._private.edges;
util.removeFromArray( connectedEdges, edge );
node.clearTraversalCache();
}
function removeParallelRef( pllEdge ){
pllEdge.clearTraversalCache();
}
let alteredParents = [];
alteredParents.ids = {};
function removeChildRef( parent, ele ){
ele = ele[0];
parent = parent[0];
let children = parent._private.children;
let pid = parent.id();
util.removeFromArray( children, ele );
ele._private.parent = null;
if( !alteredParents.ids[ pid ] ){
alteredParents.ids[ pid ] = true;
alteredParents.push( parent );
}
}
self.dirtyCompoundBoundsCache();
if( removeFromPool ){
cy.removeFromPool( elesToRemove ); }
for( let i = 0; i < elesToRemove.length; i++ ){
let ele = elesToRemove[ i ];
if( ele.isEdge() ){ let src = ele.source()[0];
let tgt = ele.target()[0];
removeEdgeRef( src, ele );
removeEdgeRef( tgt, ele );
let pllEdges = ele.parallelEdges();
for( let j = 0; j < pllEdges.length; j++ ){
let pllEdge = pllEdges[j];
removeParallelRef(pllEdge);
if( pllEdge.isBundledBezier() ){
pllEdge.dirtyBoundingBoxCache();
}
}
} else { let parent = ele.parent();
if( parent.length !== 0 ){
removeChildRef( parent, ele );
}
}
if( removeFromPool ){
ele._private.removed = true;
}
}
let elesStillInside = cy._private.elements;
cy._private.hasCompoundNodes = false;
for( let i = 0; i < elesStillInside.length; i++ ){
let ele = elesStillInside[ i ];
if( ele.isParent() ){
cy._private.hasCompoundNodes = true;
break;
}
}
let removedElements = new Collection( this.cy(), elesToRemove );
if( removedElements.size() > 0 ){
if( notifyRenderer ){
removedElements.emitAndNotify('remove');
} else if( removeFromPool ){
removedElements.emit('remove');
}
}
for( let i = 0; i < alteredParents.length; i++ ){
let ele = alteredParents[ i ];
if( !removeFromPool || !ele.removed() ){
ele.updateStyle();
}
}
return removedElements;
};
elesfn.move = function( struct ){
let cy = this._private.cy;
let eles = this;
let notifyRenderer = false;
let modifyPool = false;
let toString = id => id == null ? id : '' + id;
if( struct.source !== undefined || struct.target !== undefined ){
let srcId = toString(struct.source);
let tgtId = toString(struct.target);
let srcExists = srcId != null && cy.hasElementWithId( srcId );
let tgtExists = tgtId != null && cy.hasElementWithId( tgtId );
if( srcExists || tgtExists ){
cy.batch(() => { eles.remove( notifyRenderer, modifyPool ); eles.emitAndNotify('moveout');
for( let i = 0; i < eles.length; i++ ){
let ele = eles[i];
let data = ele._private.data;
if( ele.isEdge() ){
if( srcExists ){ data.source = srcId; }
if( tgtExists ){ data.target = tgtId; }
}
}
eles.restore( notifyRenderer, modifyPool ); });
eles.emitAndNotify('move');
}
} else if( struct.parent !== undefined ){ let parentId = toString(struct.parent);
let parentExists = parentId === null || cy.hasElementWithId( parentId );
if( parentExists ){
let pidToAssign = parentId === null ? undefined : parentId;
cy.batch(() => { let updated = eles.remove( notifyRenderer, modifyPool ); updated.emitAndNotify('moveout');
for( let i = 0; i < eles.length; i++ ){
let ele = eles[i];
let data = ele._private.data;
if( ele.isNode() ){
data.parent = pidToAssign;
}
}
updated.restore( notifyRenderer, modifyPool ); });
eles.emitAndNotify('move');
}
}
return this;
};
[
algorithms,
animation,
classNames,
comparators,
compounds,
data,
degree,
dimensions,
events,
filter,
group,
iteration,
layout,
style,
switchFunctions,
traversing
].forEach( function( props ){
util.extend( elesfn, props );
} );
export default Collection;