440 lines
15 KiB
JavaScript
440 lines
15 KiB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
};
|
|
import { Dom, Disposable, FunctionExt } from '@antv/x6-common';
|
|
import { CellView, NodeView, EdgeView } from '../view';
|
|
import { JobQueue, JOB_PRIORITY } from './queueJob';
|
|
export class Scheduler extends Disposable {
|
|
get model() {
|
|
return this.graph.model;
|
|
}
|
|
get container() {
|
|
return this.graph.view.stage;
|
|
}
|
|
constructor(graph) {
|
|
super();
|
|
this.views = {};
|
|
this.willRemoveViews = {};
|
|
this.queue = new JobQueue();
|
|
this.graph = graph;
|
|
this.init();
|
|
}
|
|
init() {
|
|
this.startListening();
|
|
this.renderViews(this.model.getCells());
|
|
}
|
|
startListening() {
|
|
this.model.on('reseted', this.onModelReseted, this);
|
|
this.model.on('cell:added', this.onCellAdded, this);
|
|
this.model.on('cell:removed', this.onCellRemoved, this);
|
|
this.model.on('cell:change:zIndex', this.onCellZIndexChanged, this);
|
|
this.model.on('cell:change:visible', this.onCellVisibleChanged, this);
|
|
}
|
|
stopListening() {
|
|
this.model.off('reseted', this.onModelReseted, this);
|
|
this.model.off('cell:added', this.onCellAdded, this);
|
|
this.model.off('cell:removed', this.onCellRemoved, this);
|
|
this.model.off('cell:change:zIndex', this.onCellZIndexChanged, this);
|
|
this.model.off('cell:change:visible', this.onCellVisibleChanged, this);
|
|
}
|
|
onModelReseted({ options }) {
|
|
this.queue.clearJobs();
|
|
this.removeZPivots();
|
|
this.resetViews();
|
|
const cells = this.model.getCells();
|
|
this.renderViews(cells, Object.assign(Object.assign({}, options), { queue: cells.map((cell) => cell.id) }));
|
|
}
|
|
onCellAdded({ cell, options }) {
|
|
this.renderViews([cell], options);
|
|
}
|
|
onCellRemoved({ cell }) {
|
|
this.removeViews([cell]);
|
|
}
|
|
onCellZIndexChanged({ cell, options, }) {
|
|
const viewItem = this.views[cell.id];
|
|
if (viewItem) {
|
|
this.requestViewUpdate(viewItem.view, Scheduler.FLAG_INSERT, options, JOB_PRIORITY.Update, true);
|
|
}
|
|
}
|
|
onCellVisibleChanged({ cell, current, }) {
|
|
this.toggleVisible(cell, !!current);
|
|
}
|
|
requestViewUpdate(view, flag, options = {}, priority = JOB_PRIORITY.Update, flush = true) {
|
|
const id = view.cell.id;
|
|
const viewItem = this.views[id];
|
|
if (!viewItem) {
|
|
return;
|
|
}
|
|
viewItem.flag = flag;
|
|
viewItem.options = options;
|
|
const priorAction = view.hasAction(flag, ['translate', 'resize', 'rotate']);
|
|
if (priorAction || options.async === false) {
|
|
priority = JOB_PRIORITY.PRIOR; // eslint-disable-line
|
|
flush = false; // eslint-disable-line
|
|
}
|
|
this.queue.queueJob({
|
|
id,
|
|
priority,
|
|
cb: () => {
|
|
this.renderViewInArea(view, flag, options);
|
|
const queue = options.queue;
|
|
if (queue) {
|
|
const index = queue.indexOf(view.cell.id);
|
|
if (index >= 0) {
|
|
queue.splice(index, 1);
|
|
}
|
|
if (queue.length === 0) {
|
|
this.graph.trigger('render:done');
|
|
}
|
|
}
|
|
},
|
|
});
|
|
const effectedEdges = this.getEffectedEdges(view);
|
|
effectedEdges.forEach((edge) => {
|
|
this.requestViewUpdate(edge.view, edge.flag, options, priority, false);
|
|
});
|
|
if (flush) {
|
|
this.flush();
|
|
}
|
|
}
|
|
setRenderArea(area) {
|
|
this.renderArea = area;
|
|
this.flushWaitingViews();
|
|
}
|
|
isViewMounted(view) {
|
|
if (view == null) {
|
|
return false;
|
|
}
|
|
const viewItem = this.views[view.cell.id];
|
|
if (!viewItem) {
|
|
return false;
|
|
}
|
|
return viewItem.state === Scheduler.ViewState.MOUNTED;
|
|
}
|
|
renderViews(cells, options = {}) {
|
|
cells.sort((c1, c2) => {
|
|
if (c1.isNode() && c2.isEdge()) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
});
|
|
cells.forEach((cell) => {
|
|
const id = cell.id;
|
|
const views = this.views;
|
|
let flag = 0;
|
|
let viewItem = views[id];
|
|
if (viewItem) {
|
|
flag = Scheduler.FLAG_INSERT;
|
|
}
|
|
else {
|
|
const cellView = this.createCellView(cell);
|
|
if (cellView) {
|
|
cellView.graph = this.graph;
|
|
flag = Scheduler.FLAG_INSERT | cellView.getBootstrapFlag();
|
|
viewItem = {
|
|
view: cellView,
|
|
flag,
|
|
options,
|
|
state: Scheduler.ViewState.CREATED,
|
|
};
|
|
this.views[id] = viewItem;
|
|
}
|
|
}
|
|
if (viewItem) {
|
|
this.requestViewUpdate(viewItem.view, flag, options, this.getRenderPriority(viewItem.view), false);
|
|
}
|
|
});
|
|
this.flush();
|
|
}
|
|
renderViewInArea(view, flag, options = {}) {
|
|
const cell = view.cell;
|
|
const id = cell.id;
|
|
const viewItem = this.views[id];
|
|
if (!viewItem) {
|
|
return;
|
|
}
|
|
let result = 0;
|
|
if (this.isUpdatable(view)) {
|
|
result = this.updateView(view, flag, options);
|
|
viewItem.flag = result;
|
|
}
|
|
else {
|
|
if (viewItem.state === Scheduler.ViewState.MOUNTED) {
|
|
result = this.updateView(view, flag, options);
|
|
viewItem.flag = result;
|
|
}
|
|
else {
|
|
viewItem.state = Scheduler.ViewState.WAITING;
|
|
}
|
|
}
|
|
if (result) {
|
|
if (cell.isEdge() &&
|
|
(result & view.getFlag(['source', 'target'])) === 0) {
|
|
this.queue.queueJob({
|
|
id,
|
|
priority: JOB_PRIORITY.RenderEdge,
|
|
cb: () => {
|
|
this.updateView(view, flag, options);
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
removeViews(cells) {
|
|
cells.forEach((cell) => {
|
|
const id = cell.id;
|
|
const viewItem = this.views[id];
|
|
if (viewItem) {
|
|
this.willRemoveViews[id] = viewItem;
|
|
delete this.views[id];
|
|
this.queue.queueJob({
|
|
id,
|
|
priority: this.getRenderPriority(viewItem.view),
|
|
cb: () => {
|
|
this.removeView(viewItem.view);
|
|
},
|
|
});
|
|
}
|
|
});
|
|
this.flush();
|
|
}
|
|
flush() {
|
|
this.graph.options.async
|
|
? this.queue.queueFlush()
|
|
: this.queue.queueFlushSync();
|
|
}
|
|
flushWaitingViews() {
|
|
Object.values(this.views).forEach((viewItem) => {
|
|
if (viewItem && viewItem.state === Scheduler.ViewState.WAITING) {
|
|
const { view, flag, options } = viewItem;
|
|
this.requestViewUpdate(view, flag, options, this.getRenderPriority(view), false);
|
|
}
|
|
});
|
|
this.flush();
|
|
}
|
|
updateView(view, flag, options = {}) {
|
|
if (view == null) {
|
|
return 0;
|
|
}
|
|
if (CellView.isCellView(view)) {
|
|
if (flag & Scheduler.FLAG_REMOVE) {
|
|
this.removeView(view.cell);
|
|
return 0;
|
|
}
|
|
if (flag & Scheduler.FLAG_INSERT) {
|
|
this.insertView(view);
|
|
flag ^= Scheduler.FLAG_INSERT; // eslint-disable-line
|
|
}
|
|
}
|
|
if (!flag) {
|
|
return 0;
|
|
}
|
|
return view.confirmUpdate(flag, options);
|
|
}
|
|
insertView(view) {
|
|
const viewItem = this.views[view.cell.id];
|
|
if (viewItem) {
|
|
const zIndex = view.cell.getZIndex();
|
|
const pivot = this.addZPivot(zIndex);
|
|
this.container.insertBefore(view.container, pivot);
|
|
if (!view.cell.isVisible()) {
|
|
this.toggleVisible(view.cell, false);
|
|
}
|
|
viewItem.state = Scheduler.ViewState.MOUNTED;
|
|
this.graph.trigger('view:mounted', { view });
|
|
}
|
|
}
|
|
resetViews() {
|
|
this.willRemoveViews = Object.assign(Object.assign({}, this.views), this.willRemoveViews);
|
|
Object.values(this.willRemoveViews).forEach((viewItem) => {
|
|
if (viewItem) {
|
|
this.removeView(viewItem.view);
|
|
}
|
|
});
|
|
this.views = {};
|
|
this.willRemoveViews = {};
|
|
}
|
|
removeView(view) {
|
|
const cell = view.cell;
|
|
const viewItem = this.willRemoveViews[cell.id];
|
|
if (viewItem && view) {
|
|
viewItem.view.remove();
|
|
delete this.willRemoveViews[cell.id];
|
|
this.graph.trigger('view:unmounted', { view });
|
|
}
|
|
}
|
|
toggleVisible(cell, visible) {
|
|
const edges = this.model.getConnectedEdges(cell);
|
|
for (let i = 0, len = edges.length; i < len; i += 1) {
|
|
const edge = edges[i];
|
|
if (visible) {
|
|
const source = edge.getSourceCell();
|
|
const target = edge.getTargetCell();
|
|
if ((source && !source.isVisible()) ||
|
|
(target && !target.isVisible())) {
|
|
continue;
|
|
}
|
|
this.toggleVisible(edge, true);
|
|
}
|
|
else {
|
|
this.toggleVisible(edge, false);
|
|
}
|
|
}
|
|
const viewItem = this.views[cell.id];
|
|
if (viewItem) {
|
|
Dom.css(viewItem.view.container, {
|
|
display: visible ? 'unset' : 'none',
|
|
});
|
|
}
|
|
}
|
|
addZPivot(zIndex = 0) {
|
|
if (this.zPivots == null) {
|
|
this.zPivots = {};
|
|
}
|
|
const pivots = this.zPivots;
|
|
let pivot = pivots[zIndex];
|
|
if (pivot) {
|
|
return pivot;
|
|
}
|
|
pivot = pivots[zIndex] = document.createComment(`z-index:${zIndex + 1}`);
|
|
let neighborZ = -Infinity;
|
|
// eslint-disable-next-line
|
|
for (const key in pivots) {
|
|
const currentZ = +key;
|
|
if (currentZ < zIndex && currentZ > neighborZ) {
|
|
neighborZ = currentZ;
|
|
if (neighborZ === zIndex - 1) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
const layer = this.container;
|
|
if (neighborZ !== -Infinity) {
|
|
const neighborPivot = pivots[neighborZ];
|
|
layer.insertBefore(pivot, neighborPivot.nextSibling);
|
|
}
|
|
else {
|
|
layer.insertBefore(pivot, layer.firstChild);
|
|
}
|
|
return pivot;
|
|
}
|
|
removeZPivots() {
|
|
if (this.zPivots) {
|
|
Object.values(this.zPivots).forEach((elem) => {
|
|
if (elem && elem.parentNode) {
|
|
elem.parentNode.removeChild(elem);
|
|
}
|
|
});
|
|
}
|
|
this.zPivots = {};
|
|
}
|
|
createCellView(cell) {
|
|
const options = { graph: this.graph };
|
|
const createViewHook = this.graph.options.createCellView;
|
|
if (createViewHook) {
|
|
const ret = FunctionExt.call(createViewHook, this.graph, cell);
|
|
if (ret) {
|
|
return new ret(cell, options); // eslint-disable-line new-cap
|
|
}
|
|
if (ret === null) {
|
|
// null means not render
|
|
return null;
|
|
}
|
|
}
|
|
const view = cell.view;
|
|
if (view != null && typeof view === 'string') {
|
|
const def = CellView.registry.get(view);
|
|
if (def) {
|
|
return new def(cell, options); // eslint-disable-line new-cap
|
|
}
|
|
return CellView.registry.onNotFound(view);
|
|
}
|
|
if (cell.isNode()) {
|
|
return new NodeView(cell, options);
|
|
}
|
|
if (cell.isEdge()) {
|
|
return new EdgeView(cell, options);
|
|
}
|
|
return null;
|
|
}
|
|
getEffectedEdges(view) {
|
|
const effectedEdges = [];
|
|
const cell = view.cell;
|
|
const edges = this.model.getConnectedEdges(cell);
|
|
for (let i = 0, n = edges.length; i < n; i += 1) {
|
|
const edge = edges[i];
|
|
const viewItem = this.views[edge.id];
|
|
if (!viewItem) {
|
|
continue;
|
|
}
|
|
const edgeView = viewItem.view;
|
|
if (!this.isViewMounted(edgeView)) {
|
|
continue;
|
|
}
|
|
const flagLabels = ['update'];
|
|
if (edge.getTargetCell() === cell) {
|
|
flagLabels.push('target');
|
|
}
|
|
if (edge.getSourceCell() === cell) {
|
|
flagLabels.push('source');
|
|
}
|
|
effectedEdges.push({
|
|
id: edge.id,
|
|
view: edgeView,
|
|
flag: edgeView.getFlag(flagLabels),
|
|
});
|
|
}
|
|
return effectedEdges;
|
|
}
|
|
isUpdatable(view) {
|
|
if (view.isNodeView()) {
|
|
if (this.renderArea) {
|
|
return this.renderArea.isIntersectWithRect(view.cell.getBBox());
|
|
}
|
|
return true;
|
|
}
|
|
if (view.isEdgeView()) {
|
|
const edge = view.cell;
|
|
const sourceCell = edge.getSourceCell();
|
|
const targetCell = edge.getTargetCell();
|
|
if (this.renderArea && sourceCell && targetCell) {
|
|
return (this.renderArea.isIntersectWithRect(sourceCell.getBBox()) ||
|
|
this.renderArea.isIntersectWithRect(targetCell.getBBox()));
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
getRenderPriority(view) {
|
|
return view.cell.isNode()
|
|
? JOB_PRIORITY.RenderNode
|
|
: JOB_PRIORITY.RenderEdge;
|
|
}
|
|
dispose() {
|
|
this.stopListening();
|
|
// clear views
|
|
Object.keys(this.views).forEach((id) => {
|
|
this.views[id].view.dispose();
|
|
});
|
|
this.views = {};
|
|
}
|
|
}
|
|
__decorate([
|
|
Disposable.dispose()
|
|
], Scheduler.prototype, "dispose", null);
|
|
(function (Scheduler) {
|
|
Scheduler.FLAG_INSERT = 1 << 30;
|
|
Scheduler.FLAG_REMOVE = 1 << 29;
|
|
Scheduler.FLAG_RENDER = (1 << 26) - 1;
|
|
})(Scheduler || (Scheduler = {}));
|
|
(function (Scheduler) {
|
|
let ViewState;
|
|
(function (ViewState) {
|
|
ViewState[ViewState["CREATED"] = 0] = "CREATED";
|
|
ViewState[ViewState["MOUNTED"] = 1] = "MOUNTED";
|
|
ViewState[ViewState["WAITING"] = 2] = "WAITING";
|
|
})(ViewState = Scheduler.ViewState || (Scheduler.ViewState = {}));
|
|
})(Scheduler || (Scheduler = {}));
|
|
//# sourceMappingURL=scheduler.js.map
|