(function () {
|
var FREE_EDITOR_MODE = 'free-v1';
|
var MAP_TRANSFER_FORMAT = 'bas-map-editor-transfer-v2';
|
var HISTORY_LIMIT = 60;
|
var DEFAULT_CANVAS_WIDTH = 6400;
|
var DEFAULT_CANVAS_HEIGHT = 3600;
|
var MIN_ELEMENT_SIZE = 24;
|
var HANDLE_SCREEN_SIZE = 10;
|
var DRAG_START_THRESHOLD = 5;
|
var EDGE_SNAP_SCREEN_TOLERANCE = 8;
|
var COORD_EPSILON = 0.01;
|
var DEFERRED_STATIC_REBUILD_DELAY = 120;
|
var PAN_LABEL_REFRESH_DELAY = 160;
|
var ZOOM_REFRESH_DELAY = 220;
|
var POINTER_STATUS_UPDATE_INTERVAL = 48;
|
var SPATIAL_BUCKET_SIZE = 240;
|
var STATIC_VIEW_PADDING = 120;
|
var MIN_LABEL_SCALE = 0.17;
|
var ABS_MIN_LABEL_SCREEN_WIDTH = 26;
|
var ABS_MIN_LABEL_SCREEN_HEIGHT = 14;
|
var STATIC_SPRITE_SCALE_THRESHOLD = 0.85;
|
var STATIC_SIMPLIFY_SCALE_THRESHOLD = 0.22;
|
var DENSE_SIMPLIFY_SCALE_THRESHOLD = 0.8;
|
var DENSE_SIMPLIFY_ELEMENT_THRESHOLD = 1200;
|
var DENSE_LABEL_HIDE_SCALE_THRESHOLD = 1.05;
|
var DENSE_LABEL_HIDE_ELEMENT_THRESHOLD = 1200;
|
var STATIC_SPRITE_POOL_SLACK = 96;
|
var MIN_LABEL_COUNT = 180;
|
var MAX_LABEL_COUNT = 360;
|
var SHOW_CANVAS_ELEMENT_LABELS = false;
|
var DRAW_TYPES = ['shelf', 'devp', 'crn', 'dualCrn', 'rgv'];
|
var ARRAY_TEMPLATE_TYPES = ['shelf', 'crn', 'dualCrn', 'rgv'];
|
var DEVICE_CONFIG_TYPES = ['crn', 'dualCrn', 'rgv'];
|
var DEVP_DIRECTION_OPTIONS = [
|
{ key: 'top', label: '上', arrow: '↑' },
|
{ key: 'right', label: '右', arrow: '→' },
|
{ key: 'bottom', label: '下', arrow: '↓' },
|
{ key: 'left', label: '左', arrow: '←' }
|
];
|
var idSeed = Date.now();
|
|
var TYPE_META = {
|
shelf: { label: '货架', shortLabel: 'SHELF', fill: 0x7d96bf, border: 0x4f6486 },
|
devp: { label: '输送线', shortLabel: 'DEVP', fill: 0xf0b06f, border: 0xa45f21 },
|
crn: { label: '堆垛机轨道', shortLabel: 'CRN', fill: 0x68bfd0, border: 0x1d6e81 },
|
dualCrn: { label: '双工位轨道', shortLabel: 'DCRN', fill: 0x54c1a4, border: 0x0f7b62 },
|
rgv: { label: 'RGV轨道', shortLabel: 'RGV', fill: 0xc691e9, border: 0x744b98 }
|
};
|
|
function nextId() {
|
idSeed += 1;
|
return 'el_' + idSeed;
|
}
|
|
function deepClone(obj) {
|
return JSON.parse(JSON.stringify(obj == null ? null : obj));
|
}
|
|
function padNumber(value) {
|
return value < 10 ? ('0' + value) : String(value);
|
}
|
|
function authHeaders() {
|
return {
|
token: localStorage.getItem('token')
|
};
|
}
|
|
function getQueryParam(name) {
|
var search = window.location.search || '';
|
if (!search) {
|
return '';
|
}
|
var target = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
var match = search.match(new RegExp('(?:[?&])' + target + '=([^&]*)'));
|
return match ? decodeURIComponent(match[1]) : '';
|
}
|
|
function toNumber(value, defaultValue) {
|
if (value === null || value === undefined || value === '') {
|
return defaultValue;
|
}
|
var parsed = Number(value);
|
return isFinite(parsed) ? parsed : defaultValue;
|
}
|
|
function toInt(value, defaultValue) {
|
return Math.round(toNumber(value, defaultValue));
|
}
|
|
function clamp(value, min, max) {
|
return Math.max(min, Math.min(max, value));
|
}
|
|
function roundCoord(value) {
|
return Math.round(value * 1000) / 1000;
|
}
|
|
function normalizeValue(value) {
|
if (value === null || value === undefined) {
|
return '';
|
}
|
return typeof value === 'string' ? value : JSON.stringify(value);
|
}
|
|
function parseShelfLocationValue(value) {
|
var text = normalizeValue(value).trim();
|
var matched = text.match(/^(-?\d+)\s*-\s*(-?\d+)$/);
|
if (!matched) {
|
return null;
|
}
|
return {
|
row: toInt(matched[1], 0),
|
col: toInt(matched[2], 0)
|
};
|
}
|
|
function formatShelfLocationValue(row, col) {
|
return String(toInt(row, 0)) + '-' + String(toInt(col, 0));
|
}
|
|
function safeParseJson(text) {
|
if (!text || typeof text !== 'string') {
|
return null;
|
}
|
try {
|
return JSON.parse(text);
|
} catch (e) {
|
return null;
|
}
|
}
|
|
function boolFlag(value) {
|
return value === true || value === 1 || value === '1';
|
}
|
|
function normalizeDirectionList(direction) {
|
var list = Array.isArray(direction) ? direction : String(direction || '').split(/[,\s|/]+/);
|
var result = [];
|
var seen = {};
|
for (var i = 0; i < list.length; i++) {
|
var item = String(list[i] || '').trim().toLowerCase();
|
if (!item || seen[item]) {
|
continue;
|
}
|
seen[item] = true;
|
result.push(item);
|
}
|
return result;
|
}
|
|
function directionTokenToArrow(token) {
|
if (token === 'top' || token === 'up' || token === 'north' || token === 'n') {
|
return '↑';
|
}
|
if (token === 'right' || token === 'east' || token === 'e') {
|
return '→';
|
}
|
if (token === 'bottom' || token === 'down' || token === 'south' || token === 's') {
|
return '↓';
|
}
|
if (token === 'left' || token === 'west' || token === 'w') {
|
return '←';
|
}
|
return '';
|
}
|
|
function formatDirectionArrows(direction) {
|
var list = normalizeDirectionList(direction);
|
var arrows = [];
|
for (var i = 0; i < list.length; i++) {
|
var arrow = directionTokenToArrow(list[i]);
|
if (arrow) {
|
arrows.push(arrow);
|
}
|
}
|
return arrows.join('');
|
}
|
|
function isDeviceConfigType(type) {
|
return DEVICE_CONFIG_TYPES.indexOf(type) >= 0;
|
}
|
|
function pickDeviceValueKey(type, json) {
|
if (json && json.deviceNo != null) {
|
return 'deviceNo';
|
}
|
if ((type === 'crn' || type === 'dualCrn') && json && json.crnNo != null) {
|
return 'crnNo';
|
}
|
if (type === 'rgv' && json && json.rgvNo != null) {
|
return 'rgvNo';
|
}
|
return 'deviceNo';
|
}
|
|
function isInputLike(target) {
|
if (!target || !target.tagName) {
|
return false;
|
}
|
var tag = String(target.tagName || '').toLowerCase();
|
return tag === 'input' || tag === 'textarea' || tag === 'select' || !!target.isContentEditable;
|
}
|
|
function rectsOverlap(a, b) {
|
return a.x < b.x + b.width - COORD_EPSILON && a.x + a.width > b.x + COORD_EPSILON
|
&& a.y < b.y + b.height - COORD_EPSILON && a.y + a.height > b.y + COORD_EPSILON;
|
}
|
|
function rectIntersects(a, b) {
|
return a.x <= b.x + b.width && a.x + a.width >= b.x
|
&& a.y <= b.y + b.height && a.y + a.height >= b.y;
|
}
|
|
function isRectWithinCanvas(rect, canvasWidth, canvasHeight) {
|
return rect.x >= -COORD_EPSILON && rect.y >= -COORD_EPSILON
|
&& rect.x + rect.width <= canvasWidth + COORD_EPSILON
|
&& rect.y + rect.height <= canvasHeight + COORD_EPSILON;
|
}
|
|
function findDocOverlapId(doc) {
|
if (!doc || !doc.elements || !doc.elements.length) {
|
return '';
|
}
|
var buckets = {};
|
var elements = doc.elements;
|
for (var i = 0; i < elements.length; i++) {
|
var element = elements[i];
|
var minX = Math.floor(element.x / SPATIAL_BUCKET_SIZE);
|
var maxX = Math.floor((element.x + element.width) / SPATIAL_BUCKET_SIZE);
|
var minY = Math.floor(element.y / SPATIAL_BUCKET_SIZE);
|
var maxY = Math.floor((element.y + element.height) / SPATIAL_BUCKET_SIZE);
|
for (var bx = minX; bx <= maxX; bx++) {
|
for (var by = minY; by <= maxY; by++) {
|
var key = bucketKey(bx, by);
|
var bucket = buckets[key];
|
if (!bucket || !bucket.length) {
|
continue;
|
}
|
for (var j = 0; j < bucket.length; j++) {
|
if (rectsOverlap(element, bucket[j])) {
|
return element.id || ('el_' + i);
|
}
|
}
|
}
|
}
|
for (bx = minX; bx <= maxX; bx++) {
|
for (by = minY; by <= maxY; by++) {
|
key = bucketKey(bx, by);
|
if (!buckets[key]) {
|
buckets[key] = [];
|
}
|
buckets[key].push(element);
|
}
|
}
|
}
|
return '';
|
}
|
|
function buildRectFromPoints(a, b) {
|
var left = Math.min(a.x, b.x);
|
var top = Math.min(a.y, b.y);
|
var right = Math.max(a.x, b.x);
|
var bottom = Math.max(a.y, b.y);
|
return {
|
x: roundCoord(left),
|
y: roundCoord(top),
|
width: roundCoord(right - left),
|
height: roundCoord(bottom - top)
|
};
|
}
|
|
function getTypeMeta(type) {
|
return TYPE_META[type] || TYPE_META.shelf;
|
}
|
|
function rangesNearOrOverlap(a1, a2, b1, b2, tolerance) {
|
return a1 <= b2 + tolerance && a2 >= b1 - tolerance;
|
}
|
|
function bucketKey(x, y) {
|
return x + ':' + y;
|
}
|
|
function getPreferredResolution() {
|
return Math.min(window.devicePixelRatio || 1, 1.25);
|
}
|
|
new Vue({
|
el: '#app',
|
data: function () {
|
return {
|
remoteLevOptions: [],
|
levOptions: [],
|
currentLev: null,
|
floorPickerLev: null,
|
draftDocs: {},
|
doc: null,
|
activeTool: 'select',
|
toolPanelCollapsed: false,
|
inspectorPanelCollapsed: false,
|
interactionTools: [
|
{ key: 'select', label: '选择 / 移动', desc: '点击元素选择,拖拽移动,空白处拖动画布' },
|
{ key: 'marquee', label: '框选', desc: '在画布中框选一组元素' },
|
{ key: 'array', label: '阵列', desc: '选中一个货架 / 轨道后拖一条线自动生成一排' },
|
{ key: 'pan', label: '平移', desc: '专门用于拖动画布和观察全图' }
|
],
|
drawTools: [
|
{ key: 'shelf', label: '货架', desc: '自由拉出货架矩形' },
|
{ key: 'devp', label: '输送线', desc: '拉出站点 / 输送线矩形' },
|
{ key: 'crn', label: 'CRN', desc: '拉出堆垛机轨道矩形' },
|
{ key: 'dualCrn', label: '双工位', desc: '拉出双工位轨道矩形' },
|
{ key: 'rgv', label: 'RGV', desc: '拉出 RGV 轨道矩形' }
|
],
|
pixiApp: null,
|
mapRoot: null,
|
gridLayer: null,
|
trackLayer: null,
|
nodeLayer: null,
|
patchObjectLayer: null,
|
activeLayer: null,
|
labelLayer: null,
|
selectionLayer: null,
|
guideLayer: null,
|
guideText: null,
|
hoverLayer: null,
|
labelPool: [],
|
renderQueued: false,
|
gridSceneDirty: true,
|
staticSceneDirty: true,
|
spatialIndexDirty: true,
|
spatialBuckets: null,
|
gridRenderRect: null,
|
gridRenderKey: '',
|
staticRenderRect: null,
|
staticRenderKey: '',
|
staticExcludedKey: '',
|
camera: {
|
x: 80,
|
y: 80,
|
scale: 1
|
},
|
viewZoom: 1,
|
selectedIds: [],
|
clipboard: [],
|
hoverElementId: '',
|
pointerStatus: '--',
|
lastPointerStatusUpdateTs: 0,
|
pixiResolution: getPreferredResolution(),
|
fpsValue: 0,
|
fpsFrameCount: 0,
|
fpsSampleStartTs: 0,
|
fpsTickerHandler: null,
|
interactionState: null,
|
isZooming: false,
|
isPanning: false,
|
zoomRefreshTimer: null,
|
panRefreshTimer: null,
|
pendingViewportRefresh: false,
|
pendingStaticCommit: null,
|
deferredStaticRebuildTimer: null,
|
currentPointerId: null,
|
boundCanvasHandlers: null,
|
boundWindowHandlers: null,
|
resizeObserver: null,
|
labelCapability: {
|
maxWidth: 0,
|
maxHeight: 0
|
},
|
labelCapabilityDirty: true,
|
undoStack: [],
|
redoStack: [],
|
savedSnapshot: '',
|
isDirty: false,
|
saving: false,
|
savingAll: false,
|
loadingFloor: false,
|
switchingFloorLev: null,
|
floorRequestSeq: 0,
|
activeFloorRequestSeq: 0,
|
blankDialogVisible: false,
|
blankForm: {
|
lev: '',
|
width: String(DEFAULT_CANVAS_WIDTH),
|
height: String(DEFAULT_CANVAS_HEIGHT)
|
},
|
canvasForm: {
|
width: String(DEFAULT_CANVAS_WIDTH),
|
height: String(DEFAULT_CANVAS_HEIGHT)
|
},
|
geometryForm: {
|
x: '',
|
y: '',
|
width: '',
|
height: ''
|
},
|
devpForm: {
|
stationId: '',
|
deviceNo: '',
|
direction: [],
|
isBarcodeStation: false,
|
barcodeIdx: '',
|
backStation: '',
|
backStationDeviceNo: '',
|
isInStation: false,
|
barcodeStation: '',
|
barcodeStationDeviceNo: '',
|
isOutStation: false,
|
runBlockReassign: false,
|
isOutOrder: false,
|
isLiftTransfer: false
|
},
|
deviceForm: {
|
valueKey: '',
|
deviceNo: ''
|
},
|
devpDirectionOptions: DEVP_DIRECTION_OPTIONS,
|
shelfFillForm: {
|
startValue: '',
|
rowStep: 'desc',
|
colStep: 'asc'
|
},
|
valueEditorText: '',
|
spacePressed: false,
|
lastCursor: 'default'
|
};
|
},
|
computed: {
|
singleSelectedElement: function () {
|
if (!this.doc || this.selectedIds.length !== 1) {
|
return null;
|
}
|
return this.findElementById(this.selectedIds[0]);
|
},
|
singleSelectedDeviceElement: function () {
|
if (!this.singleSelectedElement || !isDeviceConfigType(this.singleSelectedElement.type)) {
|
return null;
|
}
|
return this.singleSelectedElement;
|
},
|
selectedShelfElements: function () {
|
if (!this.doc || !this.selectedIds.length) {
|
return [];
|
}
|
return this.getSelectedElements().filter(function (item) {
|
return item && item.type === 'shelf';
|
});
|
},
|
devpRequiresBarcodeLink: function () {
|
return !!(this.devpForm && this.devpForm.isInStation);
|
},
|
devpRequiresBarcodeIndex: function () {
|
return !!(this.devpForm && this.devpForm.isBarcodeStation);
|
},
|
devpRequiresBackStation: function () {
|
return !!(this.devpForm && this.devpForm.isBarcodeStation);
|
},
|
arrayPreviewCount: function () {
|
if (!this.interactionState || this.interactionState.type !== 'array') {
|
return 0;
|
}
|
return this.interactionState.previewItems ? this.interactionState.previewItems.length : 0;
|
},
|
viewPercent: function () {
|
return Math.round(this.viewZoom * 100);
|
},
|
fpsText: function () {
|
return this.fpsValue > 0 ? String(this.fpsValue) : '--';
|
},
|
dirtyDraftLevs: function () {
|
var result = [];
|
var seen = {};
|
if (this.doc && this.doc.lev && this.isDirty) {
|
var currentLev = toInt(this.doc.lev, 0);
|
if (currentLev > 0) {
|
seen[currentLev] = true;
|
result.push(currentLev);
|
}
|
}
|
var self = this;
|
Object.keys(this.draftDocs || {}).forEach(function (key) {
|
var lev = toInt(key, 0);
|
if (lev <= 0 || seen[lev]) {
|
return;
|
}
|
if (self.hasDirtyDraft(lev)) {
|
seen[lev] = true;
|
result.push(lev);
|
}
|
});
|
result.sort(function (a, b) { return a - b; });
|
return result;
|
},
|
dirtyDraftCount: function () {
|
return this.dirtyDraftLevs.length;
|
}
|
},
|
mounted: function () {
|
this.initPixi();
|
this.attachEvents();
|
this.loadLevOptions();
|
var lev = toInt(getQueryParam('lev'), 0);
|
if (lev > 0) {
|
this.floorPickerLev = lev;
|
this.fetchFloor(lev);
|
} else {
|
this.createLocalBlankDoc(1, DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT, '');
|
}
|
},
|
beforeDestroy: function () {
|
this.detachEvents();
|
if (this.zoomRefreshTimer) {
|
window.clearTimeout(this.zoomRefreshTimer);
|
this.zoomRefreshTimer = null;
|
}
|
if (this.panRefreshTimer) {
|
window.clearTimeout(this.panRefreshTimer);
|
this.panRefreshTimer = null;
|
}
|
this.clearDeferredStaticCommit();
|
this.stopFpsTicker();
|
if (this.pixiApp) {
|
this.pixiApp.destroy(true, { children: true });
|
this.pixiApp = null;
|
}
|
},
|
methods: {
|
showMessage: function (type, message) {
|
if (this.$message) {
|
this.$message({ type: type, message: message });
|
}
|
},
|
formatNumber: function (value) {
|
var num = toNumber(value, 0);
|
if (Math.abs(num) >= 1000 || num === Math.round(num)) {
|
return String(Math.round(num));
|
}
|
return String(Math.round(num * 100) / 100);
|
},
|
syncFloorQueryParam: function (lev) {
|
lev = toInt(lev, 0);
|
if (lev <= 0 || !window.history || !window.history.replaceState || !window.URL) {
|
return;
|
}
|
try {
|
var url = new URL(window.location.href);
|
url.searchParams.set('lev', String(lev));
|
window.history.replaceState(null, '', url.toString());
|
} catch (e) {
|
// Ignore URL sync failures and keep editor usable.
|
}
|
},
|
toolLabel: function (tool) {
|
var list = this.interactionTools.concat(this.drawTools);
|
for (var i = 0; i < list.length; i++) {
|
if (list[i].key === tool) {
|
return list[i].label;
|
}
|
}
|
return tool || '--';
|
},
|
toggleToolPanel: function () {
|
this.toolPanelCollapsed = !this.toolPanelCollapsed;
|
},
|
toggleInspectorPanel: function () {
|
this.inspectorPanelCollapsed = !this.inspectorPanelCollapsed;
|
},
|
startFpsTicker: function () {
|
if (!this.pixiApp || !this.pixiApp.ticker || this.fpsTickerHandler) {
|
return;
|
}
|
var self = this;
|
this.fpsValue = 0;
|
this.fpsFrameCount = 0;
|
this.fpsSampleStartTs = (window.performance && performance.now) ? performance.now() : Date.now();
|
this.fpsTickerHandler = function () {
|
var now = (window.performance && performance.now) ? performance.now() : Date.now();
|
self.fpsFrameCount += 1;
|
var elapsed = now - self.fpsSampleStartTs;
|
if (elapsed < 400) {
|
return;
|
}
|
self.fpsValue = Math.max(0, Math.round(self.fpsFrameCount * 1000 / elapsed));
|
self.fpsFrameCount = 0;
|
self.fpsSampleStartTs = now;
|
};
|
this.pixiApp.ticker.add(this.fpsTickerHandler);
|
},
|
stopFpsTicker: function () {
|
if (this.pixiApp && this.pixiApp.ticker && this.fpsTickerHandler) {
|
this.pixiApp.ticker.remove(this.fpsTickerHandler);
|
}
|
this.fpsTickerHandler = null;
|
this.fpsFrameCount = 0;
|
this.fpsSampleStartTs = 0;
|
},
|
initPixi: function () {
|
var host = this.$refs.canvasHost;
|
if (!host) {
|
return;
|
}
|
var resolution = getPreferredResolution();
|
this.pixiResolution = resolution;
|
var app = new PIXI.Application({
|
width: Math.max(host.clientWidth, 320),
|
height: Math.max(host.clientHeight, 320),
|
antialias: false,
|
autoDensity: true,
|
backgroundAlpha: 1,
|
backgroundColor: 0xf6f9fc,
|
resolution: resolution,
|
powerPreference: 'high-performance'
|
});
|
host.innerHTML = '';
|
host.appendChild(app.view);
|
app.view.style.width = '100%';
|
app.view.style.height = '100%';
|
app.view.style.touchAction = 'none';
|
app.view.style.background = '#f6f9fc';
|
app.renderer.roundPixels = true;
|
|
this.pixiApp = app;
|
this.mapRoot = new PIXI.Container();
|
app.stage.addChild(this.mapRoot);
|
|
this.gridLayer = new PIXI.Graphics();
|
this.staticLayer = new PIXI.Container();
|
this.staticTrackSpriteLayer = null;
|
this.staticNodeSpriteLayer = null;
|
this.trackLayer = new PIXI.Graphics();
|
this.nodeLayer = new PIXI.Graphics();
|
this.eraseLayer = new PIXI.Graphics();
|
this.patchObjectLayer = new PIXI.Graphics();
|
this.activeLayer = new PIXI.Graphics();
|
this.labelLayer = new PIXI.Container();
|
this.selectionLayer = new PIXI.Graphics();
|
this.guideLayer = new PIXI.Graphics();
|
this.guideText = new PIXI.Text('', {
|
fontFamily: 'PingFang SC, Microsoft YaHei, sans-serif',
|
fontSize: 14,
|
fontWeight: '700',
|
fill: 0x1f4f86,
|
stroke: 0xffffff,
|
strokeThickness: 4,
|
lineJoin: 'round'
|
});
|
this.guideText.anchor.set(0.5, 1);
|
this.guideText.visible = false;
|
this.hoverLayer = new PIXI.Graphics();
|
this.staticTrackSpritePool = [];
|
this.staticNodeSpritePool = [];
|
|
this.mapRoot.addChild(this.gridLayer);
|
this.staticTrackSpriteLayer = new PIXI.ParticleContainer(12000, {
|
position: true,
|
scale: true,
|
alpha: true,
|
tint: true
|
}, 16384, true);
|
this.staticNodeSpriteLayer = new PIXI.ParticleContainer(12000, {
|
position: true,
|
scale: true,
|
alpha: true,
|
tint: true
|
}, 16384, true);
|
this.staticLayer.addChild(this.staticTrackSpriteLayer);
|
this.staticLayer.addChild(this.trackLayer);
|
this.staticLayer.addChild(this.staticNodeSpriteLayer);
|
this.staticLayer.addChild(this.nodeLayer);
|
this.mapRoot.addChild(this.staticLayer);
|
this.mapRoot.addChild(this.eraseLayer);
|
this.mapRoot.addChild(this.patchObjectLayer);
|
this.mapRoot.addChild(this.activeLayer);
|
this.mapRoot.addChild(this.labelLayer);
|
this.mapRoot.addChild(this.hoverLayer);
|
this.mapRoot.addChild(this.selectionLayer);
|
this.mapRoot.addChild(this.guideLayer);
|
this.mapRoot.addChild(this.guideText);
|
|
this.boundCanvasHandlers = {
|
pointerdown: this.onCanvasPointerDown.bind(this),
|
wheel: this.onCanvasWheel.bind(this)
|
};
|
app.view.addEventListener('pointerdown', this.boundCanvasHandlers.pointerdown);
|
app.view.addEventListener('wheel', this.boundCanvasHandlers.wheel, { passive: false });
|
|
if (window.ResizeObserver) {
|
this.resizeObserver = new ResizeObserver(this.handleResize.bind(this));
|
this.resizeObserver.observe(host);
|
}
|
this.startFpsTicker();
|
this.handleResize();
|
},
|
attachEvents: function () {
|
this.boundWindowHandlers = {
|
pointermove: this.onWindowPointerMove.bind(this),
|
pointerup: this.onWindowPointerUp.bind(this),
|
pointercancel: this.onWindowPointerUp.bind(this),
|
keydown: this.onWindowKeyDown.bind(this),
|
keyup: this.onWindowKeyUp.bind(this),
|
beforeunload: this.onBeforeUnload.bind(this),
|
resize: this.handleResize.bind(this)
|
};
|
window.addEventListener('pointermove', this.boundWindowHandlers.pointermove);
|
window.addEventListener('pointerup', this.boundWindowHandlers.pointerup);
|
window.addEventListener('pointercancel', this.boundWindowHandlers.pointercancel);
|
window.addEventListener('keydown', this.boundWindowHandlers.keydown);
|
window.addEventListener('keyup', this.boundWindowHandlers.keyup);
|
window.addEventListener('beforeunload', this.boundWindowHandlers.beforeunload);
|
window.addEventListener('resize', this.boundWindowHandlers.resize);
|
},
|
detachEvents: function () {
|
if (this.pixiApp && this.boundCanvasHandlers) {
|
this.pixiApp.view.removeEventListener('pointerdown', this.boundCanvasHandlers.pointerdown);
|
this.pixiApp.view.removeEventListener('wheel', this.boundCanvasHandlers.wheel);
|
}
|
if (this.resizeObserver) {
|
this.resizeObserver.disconnect();
|
this.resizeObserver = null;
|
}
|
if (!this.boundWindowHandlers) {
|
return;
|
}
|
window.removeEventListener('pointermove', this.boundWindowHandlers.pointermove);
|
window.removeEventListener('pointerup', this.boundWindowHandlers.pointerup);
|
window.removeEventListener('pointercancel', this.boundWindowHandlers.pointercancel);
|
window.removeEventListener('keydown', this.boundWindowHandlers.keydown);
|
window.removeEventListener('keyup', this.boundWindowHandlers.keyup);
|
window.removeEventListener('beforeunload', this.boundWindowHandlers.beforeunload);
|
window.removeEventListener('resize', this.boundWindowHandlers.resize);
|
},
|
handleResize: function () {
|
if (!this.pixiApp || !this.$refs.canvasHost) {
|
return;
|
}
|
var host = this.$refs.canvasHost;
|
var width = Math.max(host.clientWidth, 320);
|
var height = Math.max(host.clientHeight, 320);
|
this.pixiApp.renderer.resize(width, height);
|
this.markGridSceneDirty();
|
this.markStaticSceneDirty();
|
this.scheduleRender();
|
},
|
loadLevOptions: function () {
|
var self = this;
|
$.ajax({
|
url: baseUrl + '/basMap/getLevList',
|
method: 'GET',
|
headers: authHeaders(),
|
success: function (res) {
|
if (res && res.code === 200 && Array.isArray(res.data)) {
|
self.remoteLevOptions = res.data.map(function (item) {
|
return toInt(item, 0);
|
}).filter(function (item) {
|
return item > 0;
|
});
|
self.refreshLevOptions();
|
}
|
}
|
});
|
},
|
refreshLevOptions: function () {
|
var set = {};
|
var result = [];
|
var pushLev = function (lev) {
|
lev = toInt(lev, 0);
|
if (lev <= 0 || set[lev]) {
|
return;
|
}
|
set[lev] = true;
|
result.push(lev);
|
};
|
this.remoteLevOptions.forEach(pushLev);
|
Object.keys(this.draftDocs || {}).forEach(pushLev);
|
pushLev(this.currentLev);
|
pushLev(this.floorPickerLev);
|
result.sort(function (a, b) { return a - b; });
|
this.levOptions = result;
|
},
|
exportDoc: function (doc) {
|
var source = doc || this.doc || {};
|
return {
|
lev: toInt(source.lev, 0),
|
editorMode: FREE_EDITOR_MODE,
|
canvasWidth: roundCoord(toNumber(source.canvasWidth, DEFAULT_CANVAS_WIDTH)),
|
canvasHeight: roundCoord(toNumber(source.canvasHeight, DEFAULT_CANVAS_HEIGHT)),
|
elements: (source.elements || []).map(function (item, index) {
|
return {
|
id: item && item.id ? String(item.id) : ('el_' + (index + 1)),
|
type: DRAW_TYPES.indexOf(item && item.type) >= 0 ? item.type : 'shelf',
|
x: roundCoord(Math.max(0, toNumber(item && item.x, 0))),
|
y: roundCoord(Math.max(0, toNumber(item && item.y, 0))),
|
width: roundCoord(Math.max(MIN_ELEMENT_SIZE, toNumber(item && item.width, MIN_ELEMENT_SIZE))),
|
height: roundCoord(Math.max(MIN_ELEMENT_SIZE, toNumber(item && item.height, MIN_ELEMENT_SIZE))),
|
value: normalizeValue(item && item.value)
|
};
|
})
|
};
|
},
|
normalizeDoc: function (doc) {
|
var normalized = this.exportDoc(doc || {});
|
if (normalized.lev <= 0) {
|
normalized.lev = toInt(this.currentLev, 1) || 1;
|
}
|
return normalized;
|
},
|
snapshotDoc: function (doc) {
|
return JSON.stringify(this.exportDoc(doc));
|
},
|
syncDirty: function () {
|
var currentSnapshot = this.snapshotDoc(this.doc);
|
this.isDirty = currentSnapshot !== this.savedSnapshot;
|
},
|
setDraftDocEntry: function (lev, doc, savedSnapshot) {
|
lev = toInt(lev, 0);
|
if (lev <= 0 || !doc) {
|
return;
|
}
|
var entry = {
|
doc: this.exportDoc(doc),
|
savedSnapshot: savedSnapshot != null ? savedSnapshot : ''
|
};
|
if (this.$set) {
|
this.$set(this.draftDocs, lev, entry);
|
} else {
|
this.draftDocs[lev] = entry;
|
}
|
},
|
removeDraftDocEntry: function (lev) {
|
lev = toInt(lev, 0);
|
if (lev <= 0) {
|
return;
|
}
|
if (this.$delete) {
|
this.$delete(this.draftDocs, lev);
|
} else {
|
delete this.draftDocs[lev];
|
}
|
},
|
cacheCurrentDraft: function () {
|
if (!this.doc || !this.doc.lev) {
|
return;
|
}
|
this.setDraftDocEntry(this.doc.lev, this.doc, this.savedSnapshot);
|
this.refreshLevOptions();
|
},
|
clearCurrentDraftIfSaved: function () {
|
if (!this.doc || !this.doc.lev) {
|
return;
|
}
|
this.setDraftDocEntry(this.doc.lev, this.doc, this.savedSnapshot);
|
},
|
clearFloorTransientState: function () {
|
this.clearDeferredStaticCommit();
|
this.interactionState = null;
|
this.currentPointerId = null;
|
this.hoverElementId = '';
|
this.pointerStatus = '--';
|
this.lastPointerStatusUpdateTs = 0;
|
this.selectedIds = [];
|
this.isPanning = false;
|
this.isZooming = false;
|
this.pendingViewportRefresh = false;
|
if (this.zoomRefreshTimer) {
|
window.clearTimeout(this.zoomRefreshTimer);
|
this.zoomRefreshTimer = null;
|
}
|
if (this.panRefreshTimer) {
|
window.clearTimeout(this.panRefreshTimer);
|
this.panRefreshTimer = null;
|
}
|
},
|
resetRenderLayers: function () {
|
if (this.gridLayer) {
|
this.gridLayer.clear();
|
}
|
if (this.trackLayer) {
|
this.trackLayer.clear();
|
}
|
if (this.nodeLayer) {
|
this.nodeLayer.clear();
|
}
|
if (this.eraseLayer) {
|
this.eraseLayer.clear();
|
}
|
if (this.patchObjectLayer) {
|
this.patchObjectLayer.clear();
|
}
|
if (this.activeLayer) {
|
this.activeLayer.clear();
|
}
|
if (this.selectionLayer) {
|
this.selectionLayer.clear();
|
}
|
if (this.guideLayer) {
|
this.guideLayer.clear();
|
}
|
if (this.hoverLayer) {
|
this.hoverLayer.clear();
|
}
|
if (this.guideText) {
|
this.guideText.visible = false;
|
this.guideText.text = '';
|
}
|
if (this.labelLayer) {
|
this.labelLayer.visible = false;
|
}
|
for (var i = 0; i < this.labelPool.length; i++) {
|
this.labelPool[i].visible = false;
|
this.labelPool[i].text = '';
|
}
|
this.hideUnusedStaticSprites(this.staticTrackSpritePool || [], 0);
|
this.hideUnusedStaticSprites(this.staticNodeSpritePool || [], 0);
|
if (this.staticTrackSpriteLayer) {
|
this.staticTrackSpriteLayer.removeChildren();
|
this.staticTrackSpritePool = [];
|
}
|
if (this.staticNodeSpriteLayer) {
|
this.staticNodeSpriteLayer.removeChildren();
|
this.staticNodeSpritePool = [];
|
}
|
if (this.staticTrackSpriteLayer) {
|
this.staticTrackSpriteLayer.visible = false;
|
}
|
if (this.staticNodeSpriteLayer) {
|
this.staticNodeSpriteLayer.visible = false;
|
}
|
},
|
hasDirtyDraft: function (lev) {
|
lev = toInt(lev, 0);
|
if (lev <= 0) {
|
return false;
|
}
|
var entry = this.draftDocs[lev];
|
if (!entry || !entry.doc) {
|
return false;
|
}
|
var snapshot = this.snapshotDoc(entry.doc);
|
return snapshot !== (entry.savedSnapshot || '');
|
},
|
markStaticSceneDirty: function () {
|
this.staticSceneDirty = true;
|
},
|
markGridSceneDirty: function () {
|
this.gridSceneDirty = true;
|
},
|
clearRenderCaches: function () {
|
this.gridRenderRect = null;
|
this.gridRenderKey = '';
|
this.staticRenderRect = null;
|
this.staticRenderKey = '';
|
this.staticExcludedKey = '';
|
},
|
scheduleZoomRefresh: function () {
|
if (this.zoomRefreshTimer) {
|
window.clearTimeout(this.zoomRefreshTimer);
|
}
|
this.isZooming = true;
|
this.zoomRefreshTimer = window.setTimeout(function () {
|
this.zoomRefreshTimer = null;
|
this.isZooming = false;
|
if (this.isPanning || (this.interactionState && this.interactionState.type === 'pan')) {
|
this.pendingViewportRefresh = true;
|
return;
|
}
|
this.markGridSceneDirty();
|
this.markStaticSceneDirty();
|
this.scheduleRender();
|
}.bind(this), ZOOM_REFRESH_DELAY);
|
},
|
cancelPanRefresh: function () {
|
if (this.panRefreshTimer) {
|
window.clearTimeout(this.panRefreshTimer);
|
this.panRefreshTimer = null;
|
}
|
},
|
schedulePanRefresh: function () {
|
this.cancelPanRefresh();
|
this.isPanning = true;
|
this.panRefreshTimer = window.setTimeout(function () {
|
this.panRefreshTimer = null;
|
this.isPanning = false;
|
if (this.pendingViewportRefresh) {
|
this.pendingViewportRefresh = false;
|
this.markGridSceneDirty();
|
this.markStaticSceneDirty();
|
}
|
this.scheduleRender();
|
}.bind(this), PAN_LABEL_REFRESH_DELAY);
|
},
|
rebuildLabelCapability: function () {
|
var maxWidth = 0;
|
var maxHeight = 0;
|
var elements = this.doc && this.doc.elements ? this.doc.elements : [];
|
for (var i = 0; i < elements.length; i++) {
|
var element = elements[i];
|
if (element.width > maxWidth) {
|
maxWidth = element.width;
|
}
|
if (element.height > maxHeight) {
|
maxHeight = element.height;
|
}
|
}
|
this.labelCapability = {
|
maxWidth: maxWidth,
|
maxHeight: maxHeight
|
};
|
this.labelCapabilityDirty = false;
|
},
|
ensureLabelCapability: function () {
|
if (this.labelCapabilityDirty) {
|
this.rebuildLabelCapability();
|
}
|
return this.labelCapability;
|
},
|
markSpatialIndexDirty: function () {
|
this.spatialIndexDirty = true;
|
},
|
rebuildSpatialIndex: function () {
|
var buckets = {};
|
var elements = this.doc && this.doc.elements ? this.doc.elements : [];
|
for (var i = 0; i < elements.length; i++) {
|
var element = elements[i];
|
var minX = Math.floor(element.x / SPATIAL_BUCKET_SIZE);
|
var maxX = Math.floor((element.x + element.width) / SPATIAL_BUCKET_SIZE);
|
var minY = Math.floor(element.y / SPATIAL_BUCKET_SIZE);
|
var maxY = Math.floor((element.y + element.height) / SPATIAL_BUCKET_SIZE);
|
for (var bx = minX; bx <= maxX; bx++) {
|
for (var by = minY; by <= maxY; by++) {
|
var key = bucketKey(bx, by);
|
if (!buckets[key]) {
|
buckets[key] = [];
|
}
|
buckets[key].push(element);
|
}
|
}
|
}
|
this.spatialBuckets = buckets;
|
this.spatialIndexDirty = false;
|
},
|
ensureSpatialIndex: function () {
|
if (this.spatialIndexDirty || !this.spatialBuckets) {
|
this.rebuildSpatialIndex();
|
}
|
},
|
querySpatialCandidates: function (rect, padding, excludeIds) {
|
if (!this.doc || !rect) {
|
return [];
|
}
|
this.ensureSpatialIndex();
|
var excludeMap = {};
|
excludeIds = excludeIds || [];
|
for (var i = 0; i < excludeIds.length; i++) {
|
excludeMap[excludeIds[i]] = true;
|
}
|
var seen = {};
|
var result = [];
|
var pad = Math.max(0, padding || 0);
|
var minX = Math.floor((rect.x - pad) / SPATIAL_BUCKET_SIZE);
|
var maxX = Math.floor((rect.x + rect.width + pad) / SPATIAL_BUCKET_SIZE);
|
var minY = Math.floor((rect.y - pad) / SPATIAL_BUCKET_SIZE);
|
var maxY = Math.floor((rect.y + rect.height + pad) / SPATIAL_BUCKET_SIZE);
|
for (var bx = minX; bx <= maxX; bx++) {
|
for (var by = minY; by <= maxY; by++) {
|
var key = bucketKey(bx, by);
|
var bucket = this.spatialBuckets[key];
|
if (!bucket || !bucket.length) {
|
continue;
|
}
|
for (var j = 0; j < bucket.length; j++) {
|
var element = bucket[j];
|
if (!element || seen[element.id] || excludeMap[element.id]) {
|
continue;
|
}
|
seen[element.id] = true;
|
result.push(element);
|
}
|
}
|
}
|
return result;
|
},
|
cancelDeferredStaticRebuild: function () {
|
if (this.deferredStaticRebuildTimer) {
|
window.clearTimeout(this.deferredStaticRebuildTimer);
|
this.deferredStaticRebuildTimer = null;
|
}
|
},
|
stageDeferredStaticCommit: function (ids, eraseRects) {
|
this.pendingStaticCommit = {
|
ids: (ids || []).slice(),
|
eraseRects: (eraseRects || []).map(function (item) {
|
return {
|
x: item.x,
|
y: item.y,
|
width: item.width,
|
height: item.height
|
};
|
})
|
};
|
},
|
clearDeferredStaticCommit: function () {
|
this.cancelDeferredStaticRebuild();
|
this.pendingStaticCommit = null;
|
},
|
scheduleDeferredStaticRebuild: function () {
|
this.cancelDeferredStaticRebuild();
|
this.deferredStaticRebuildTimer = window.setTimeout(function () {
|
this.deferredStaticRebuildTimer = null;
|
this.pendingStaticCommit = null;
|
this.markStaticSceneDirty();
|
this.scheduleRender();
|
}.bind(this), DEFERRED_STATIC_REBUILD_DELAY);
|
},
|
selectionKey: function (ids) {
|
return (ids || []).slice().sort().join('|');
|
},
|
setSelectedIds: function (ids, options) {
|
options = options || {};
|
var nextIds = (ids || []).filter(Boolean);
|
this.selectedIds = nextIds.slice();
|
if (options.refreshInspector !== false) {
|
this.refreshInspector();
|
}
|
},
|
setCurrentDoc: function (doc, options) {
|
options = options || {};
|
var normalized = this.normalizeDoc(doc);
|
this.clearFloorTransientState();
|
this.resetRenderLayers();
|
this.clearRenderCaches();
|
this.doc = normalized;
|
this.markSpatialIndexDirty();
|
this.labelCapabilityDirty = true;
|
this.pendingViewportRefresh = false;
|
this.currentLev = normalized.lev;
|
this.floorPickerLev = normalized.lev;
|
this.switchingFloorLev = null;
|
this.loadingFloor = false;
|
this.syncFloorQueryParam(normalized.lev);
|
this.markGridSceneDirty();
|
this.markStaticSceneDirty();
|
this.undoStack = [];
|
this.redoStack = [];
|
this.savedSnapshot = options.savedSnapshot != null ? options.savedSnapshot : this.snapshotDoc(normalized);
|
this.syncDirty();
|
this.refreshInspector();
|
this.refreshLevOptions();
|
this.$nextTick(function () {
|
this.fitContent();
|
this.scheduleRender();
|
}.bind(this));
|
},
|
replaceDocFromSnapshot: function (snapshot) {
|
if (!snapshot) {
|
return;
|
}
|
try {
|
this.clearFloorTransientState();
|
this.resetRenderLayers();
|
this.clearRenderCaches();
|
this.doc = this.normalizeDoc(JSON.parse(snapshot));
|
this.markSpatialIndexDirty();
|
this.labelCapabilityDirty = true;
|
this.pendingViewportRefresh = false;
|
} catch (e) {
|
this.showMessage('error', '历史记录恢复失败');
|
return;
|
}
|
this.markGridSceneDirty();
|
this.markStaticSceneDirty();
|
this.floorPickerLev = this.doc.lev;
|
this.currentLev = this.doc.lev;
|
this.refreshInspector();
|
this.syncDirty();
|
this.cacheCurrentDraft();
|
this.scheduleRender();
|
},
|
pushUndoSnapshot: function (snapshot) {
|
if (!snapshot) {
|
return;
|
}
|
if (this.undoStack.length > 0 && this.undoStack[this.undoStack.length - 1] === snapshot) {
|
return;
|
}
|
this.undoStack.push(snapshot);
|
if (this.undoStack.length > HISTORY_LIMIT) {
|
this.undoStack.shift();
|
}
|
},
|
commitMutation: function (beforeSnapshot, options) {
|
options = options || {};
|
var afterSnapshot = this.snapshotDoc(this.doc);
|
if (beforeSnapshot === afterSnapshot) {
|
this.scheduleRender();
|
this.refreshInspector();
|
return false;
|
}
|
this.pushUndoSnapshot(beforeSnapshot);
|
this.redoStack = [];
|
this.markSpatialIndexDirty();
|
this.labelCapabilityDirty = true;
|
if (options.staticSceneDirty !== false) {
|
this.clearDeferredStaticCommit();
|
this.markStaticSceneDirty();
|
}
|
this.syncDirty();
|
this.cacheCurrentDraft();
|
this.refreshInspector();
|
this.scheduleRender();
|
return true;
|
},
|
runMutation: function (mutator) {
|
if (!this.doc) {
|
return false;
|
}
|
var beforeSnapshot = this.snapshotDoc(this.doc);
|
mutator();
|
return this.commitMutation(beforeSnapshot);
|
},
|
undo: function () {
|
if (this.undoStack.length === 0 || !this.doc) {
|
return;
|
}
|
var currentSnapshot = this.snapshotDoc(this.doc);
|
var snapshot = this.undoStack.pop();
|
this.redoStack.push(currentSnapshot);
|
this.replaceDocFromSnapshot(snapshot);
|
},
|
redo: function () {
|
if (this.redoStack.length === 0 || !this.doc) {
|
return;
|
}
|
var currentSnapshot = this.snapshotDoc(this.doc);
|
var snapshot = this.redoStack.pop();
|
this.pushUndoSnapshot(currentSnapshot);
|
this.replaceDocFromSnapshot(snapshot);
|
},
|
createLocalBlankDoc: function (lev, width, height, savedSnapshot) {
|
var doc = {
|
lev: toInt(lev, 1),
|
editorMode: FREE_EDITOR_MODE,
|
canvasWidth: Math.max(MIN_ELEMENT_SIZE * 4, toNumber(width, DEFAULT_CANVAS_WIDTH)),
|
canvasHeight: Math.max(MIN_ELEMENT_SIZE * 4, toNumber(height, DEFAULT_CANVAS_HEIGHT)),
|
elements: []
|
};
|
this.setCurrentDoc(doc, {
|
savedSnapshot: savedSnapshot != null ? savedSnapshot : ''
|
});
|
this.cacheCurrentDraft();
|
this.syncDirty();
|
},
|
openBlankDialog: function () {
|
var lev = this.currentLev || 1;
|
this.blankForm = {
|
lev: String(lev),
|
width: String(Math.round(this.doc ? this.doc.canvasWidth : DEFAULT_CANVAS_WIDTH)),
|
height: String(Math.round(this.doc ? this.doc.canvasHeight : DEFAULT_CANVAS_HEIGHT))
|
};
|
this.blankDialogVisible = true;
|
},
|
createBlankMap: function () {
|
var lev = toInt(this.blankForm.lev, 0);
|
var width = toNumber(this.blankForm.width, DEFAULT_CANVAS_WIDTH);
|
var height = toNumber(this.blankForm.height, DEFAULT_CANVAS_HEIGHT);
|
if (lev <= 0) {
|
this.showMessage('warning', '楼层不能为空');
|
return;
|
}
|
if (width <= 0 || height <= 0) {
|
this.showMessage('warning', '画布尺寸必须大于 0');
|
return;
|
}
|
this.blankDialogVisible = false;
|
this.createLocalBlankDoc(lev, width, height, '');
|
},
|
buildTransferPayload: function () {
|
var doc = this.exportDoc(this.doc);
|
return {
|
format: MAP_TRANSFER_FORMAT,
|
exportedAt: new Date().toISOString(),
|
source: {
|
lev: doc.lev,
|
editorMode: doc.editorMode
|
},
|
docs: [doc]
|
};
|
},
|
buildTransferFilename: function (docs) {
|
var levs = (docs || []).map(function (item) {
|
return toInt(item && item.lev, 0);
|
}).filter(function (lev) {
|
return lev > 0;
|
}).sort(function (a, b) {
|
return a - b;
|
});
|
var scope = levs.length <= 1
|
? (String(levs[0] || (this.currentLev || 1)) + 'F')
|
: ('all-' + levs.length + '-floors');
|
var now = new Date();
|
return [
|
'bas-map',
|
scope,
|
now.getFullYear(),
|
padNumber(now.getMonth() + 1),
|
padNumber(now.getDate()),
|
padNumber(now.getHours()),
|
padNumber(now.getMinutes()),
|
padNumber(now.getSeconds())
|
].join('-') + '.json';
|
},
|
requestEditorDoc: function (lev) {
|
return new Promise(function (resolve, reject) {
|
$.ajax({
|
url: baseUrl + '/basMap/editor/' + lev + '/auth',
|
method: 'GET',
|
headers: authHeaders(),
|
success: function (res) {
|
if (!res || res.code !== 200 || !res.data) {
|
reject(new Error((res && res.msg) ? res.msg : ('加载 ' + lev + 'F 地图失败')));
|
return;
|
}
|
resolve(res.data);
|
},
|
error: function () {
|
reject(new Error('加载 ' + lev + 'F 地图失败'));
|
}
|
});
|
});
|
},
|
collectAllTransferDocs: function () {
|
var self = this;
|
var levMap = {};
|
(this.remoteLevOptions || []).forEach(function (lev) {
|
lev = toInt(lev, 0);
|
if (lev > 0) {
|
levMap[lev] = true;
|
}
|
});
|
Object.keys(this.draftDocs || {}).forEach(function (key) {
|
var lev = toInt(key, 0);
|
if (lev > 0) {
|
levMap[lev] = true;
|
}
|
});
|
if (this.doc && this.doc.lev) {
|
levMap[toInt(this.doc.lev, 0)] = true;
|
}
|
var levs = Object.keys(levMap).map(function (key) {
|
return toInt(key, 0);
|
}).filter(function (lev) {
|
return lev > 0;
|
}).sort(function (a, b) {
|
return a - b;
|
});
|
if (!levs.length) {
|
return Promise.resolve([]);
|
}
|
return Promise.all(levs.map(function (lev) {
|
if (self.doc && self.doc.lev === lev) {
|
return Promise.resolve(self.exportDoc(self.doc));
|
}
|
if (self.draftDocs[lev] && self.draftDocs[lev].doc) {
|
return Promise.resolve(self.exportDoc(self.draftDocs[lev].doc));
|
}
|
return self.requestEditorDoc(lev).then(function (doc) {
|
return self.normalizeDoc(doc);
|
});
|
}));
|
},
|
exportMapPackage: function () {
|
var self = this;
|
if (!this.doc && (!this.remoteLevOptions || !this.remoteLevOptions.length)) {
|
this.showMessage('warning', '当前没有可导出的地图');
|
return;
|
}
|
this.collectAllTransferDocs().then(function (docs) {
|
if (!docs || !docs.length) {
|
self.showMessage('warning', '当前没有可导出的地图');
|
return;
|
}
|
var payload = {
|
format: MAP_TRANSFER_FORMAT,
|
exportedAt: new Date().toISOString(),
|
source: {
|
lev: self.currentLev || (docs[0] && docs[0].lev) || 1,
|
editorMode: FREE_EDITOR_MODE
|
},
|
docs: docs.map(function (doc) {
|
return self.exportDoc(doc);
|
})
|
};
|
var blob = new Blob([JSON.stringify(payload, null, 2)], {
|
type: 'application/json;charset=utf-8'
|
});
|
var href = window.URL.createObjectURL(blob);
|
var link = document.createElement('a');
|
link.href = href;
|
link.download = self.buildTransferFilename(payload.docs);
|
document.body.appendChild(link);
|
link.click();
|
document.body.removeChild(link);
|
window.setTimeout(function () {
|
window.URL.revokeObjectURL(href);
|
}, 0);
|
self.showMessage('success', '已导出 ' + payload.docs.length + ' 个楼层的地图包');
|
}).catch(function (error) {
|
self.showMessage('error', error && error.message ? error.message : '导出地图失败');
|
});
|
},
|
triggerImportMap: function () {
|
if (this.$refs.mapImportInput) {
|
this.$refs.mapImportInput.value = '';
|
this.$refs.mapImportInput.click();
|
}
|
},
|
parseTransferPackage: function (raw) {
|
if (!raw) {
|
return null;
|
}
|
if (raw.format === MAP_TRANSFER_FORMAT && Array.isArray(raw.docs) && raw.docs.length) {
|
return {
|
docs: raw.docs,
|
activeLev: toInt(raw.source && raw.source.lev, 0)
|
};
|
}
|
if ((raw.format === 'bas-map-editor-transfer-v1' || raw.format === MAP_TRANSFER_FORMAT) && raw.doc) {
|
return {
|
docs: [raw.doc],
|
activeLev: toInt(raw.source && raw.source.lev, 0)
|
};
|
}
|
if (raw.editorMode === FREE_EDITOR_MODE && Array.isArray(raw.elements)) {
|
return {
|
docs: [raw],
|
activeLev: toInt(raw.lev, 0)
|
};
|
}
|
return null;
|
},
|
importMapPackage: function (payload, options) {
|
options = options || {};
|
if (!payload || !Array.isArray(payload.docs) || !payload.docs.length) {
|
this.showMessage('error', '导入文件格式不正确');
|
return;
|
}
|
if (this.isDirty && options.skipConfirm !== true) {
|
if (!window.confirm('导入地图会替换当前编辑态未保存内容,是否继续?')) {
|
return;
|
}
|
}
|
if (this.doc) {
|
this.cacheCurrentDraft();
|
}
|
var self = this;
|
var normalizedDocs = payload.docs.map(function (item) {
|
return self.normalizeDoc(item);
|
}).sort(function (a, b) {
|
return toInt(a.lev, 0) - toInt(b.lev, 0);
|
});
|
normalizedDocs.forEach(function (doc) {
|
self.setDraftDocEntry(doc.lev, doc, '');
|
});
|
var activeLev = toInt(payload.activeLev, 0);
|
var targetDoc = normalizedDocs[0];
|
for (var i = 0; i < normalizedDocs.length; i++) {
|
if (normalizedDocs[i].lev === activeLev) {
|
targetDoc = normalizedDocs[i];
|
break;
|
}
|
}
|
this.refreshLevOptions();
|
this.floorPickerLev = targetDoc.lev;
|
this.setCurrentDoc(targetDoc, { savedSnapshot: '' });
|
if (normalizedDocs.length > 1) {
|
this.showMessage('success', '地图包已导入 ' + normalizedDocs.length + ' 个楼层,可点击“保存全部楼层”落库');
|
return;
|
}
|
this.showMessage('success', '地图包已导入,保存后才会覆盖运行地图');
|
},
|
handleImportMap: function (event) {
|
var file = event && event.target && event.target.files ? event.target.files[0] : null;
|
if (!file) {
|
return;
|
}
|
var self = this;
|
var reader = new FileReader();
|
reader.onload = function (loadEvent) {
|
try {
|
var text = loadEvent && loadEvent.target ? loadEvent.target.result : '';
|
var raw = JSON.parse(text || '{}');
|
var payload = self.parseTransferPackage(raw);
|
self.importMapPackage(payload);
|
} catch (e) {
|
self.showMessage('error', '地图文件解析失败');
|
}
|
};
|
reader.onerror = function () {
|
self.showMessage('error', '地图文件读取失败');
|
};
|
reader.readAsText(file, 'utf-8');
|
},
|
triggerImportExcel: function () {
|
if (this.$refs.importInput) {
|
this.$refs.importInput.value = '';
|
this.$refs.importInput.click();
|
}
|
},
|
handleImportExcel: function (event) {
|
var self = this;
|
var file = event && event.target && event.target.files ? event.target.files[0] : null;
|
if (!file) {
|
return;
|
}
|
var formData = new FormData();
|
formData.append('file', file);
|
$.ajax({
|
url: baseUrl + '/basMap/editor/importExcel/auth',
|
method: 'POST',
|
headers: authHeaders(),
|
data: formData,
|
processData: false,
|
contentType: false,
|
success: function (res) {
|
if (!res || res.code !== 200 || !Array.isArray(res.data) || res.data.length === 0) {
|
self.showMessage('error', (res && res.msg) ? res.msg : 'Excel 导入失败');
|
return;
|
}
|
res.data.forEach(function (item) {
|
var doc = self.normalizeDoc(item);
|
self.setDraftDocEntry(doc.lev, doc, '');
|
});
|
self.refreshLevOptions();
|
self.floorPickerLev = toInt(res.data[0].lev, 0);
|
self.setCurrentDoc(res.data[0], { savedSnapshot: '' });
|
self.showMessage('success', 'Excel 已导入到编辑器,保存后才会覆盖运行地图');
|
},
|
error: function () {
|
self.showMessage('error', 'Excel 导入失败');
|
}
|
});
|
},
|
handleFloorChange: function (lev) {
|
lev = toInt(lev, 0);
|
if (lev <= 0) {
|
return;
|
}
|
this.floorPickerLev = lev;
|
if (this.doc && this.doc.lev === lev && !this.loadingFloor) {
|
this.switchingFloorLev = null;
|
return;
|
}
|
if (this.doc) {
|
this.cacheCurrentDraft();
|
}
|
this.clearFloorTransientState();
|
this.resetRenderLayers();
|
this.switchingFloorLev = lev;
|
this.markGridSceneDirty();
|
this.markStaticSceneDirty();
|
this.scheduleRender();
|
this.fetchFloor(lev);
|
},
|
loadCurrentFloor: function () {
|
if (!this.currentLev) {
|
this.showMessage('warning', '请先选择楼层');
|
return;
|
}
|
if (this.isDirty && !window.confirm('重新读取会丢弃当前楼层未保存的自由画布编辑,是否继续?')) {
|
return;
|
}
|
this.removeDraftDocEntry(this.currentLev);
|
this.refreshLevOptions();
|
this.fetchFloor(this.currentLev);
|
},
|
fetchFloor: function (lev) {
|
var self = this;
|
lev = toInt(lev, 0);
|
if (lev <= 0) {
|
return;
|
}
|
var requestSeq = ++this.floorRequestSeq;
|
this.activeFloorRequestSeq = requestSeq;
|
this.loadingFloor = true;
|
this.switchingFloorLev = lev;
|
$.ajax({
|
url: baseUrl + '/basMap/editor/' + lev + '/auth',
|
method: 'GET',
|
headers: authHeaders(),
|
success: function (res) {
|
if (requestSeq !== self.activeFloorRequestSeq) {
|
return;
|
}
|
self.loadingFloor = false;
|
if (!res || res.code !== 200 || !res.data) {
|
self.switchingFloorLev = null;
|
self.floorPickerLev = self.currentLev;
|
self.markGridSceneDirty();
|
self.markStaticSceneDirty();
|
self.scheduleRender();
|
self.showMessage('error', (res && res.msg) ? res.msg : '加载地图失败');
|
return;
|
}
|
var normalized = self.normalizeDoc(res.data);
|
self.setDraftDocEntry(normalized.lev, normalized, self.snapshotDoc(normalized));
|
self.setCurrentDoc(normalized, {
|
savedSnapshot: self.snapshotDoc(normalized)
|
});
|
},
|
error: function () {
|
if (requestSeq !== self.activeFloorRequestSeq) {
|
return;
|
}
|
self.loadingFloor = false;
|
self.switchingFloorLev = null;
|
self.floorPickerLev = self.currentLev;
|
self.markGridSceneDirty();
|
self.markStaticSceneDirty();
|
self.scheduleRender();
|
self.showMessage('error', '加载地图失败');
|
}
|
});
|
},
|
validateDocBeforeSave: function (doc) {
|
var source = this.normalizeDoc(doc);
|
if (!source || !source.lev) {
|
return '楼层不能为空';
|
}
|
if (toNumber(source.canvasWidth, 0) <= 0 || toNumber(source.canvasHeight, 0) <= 0) {
|
return '画布尺寸必须大于 0';
|
}
|
var elements = source.elements || [];
|
for (var i = 0; i < elements.length; i++) {
|
var element = elements[i];
|
if (element.width <= 0 || element.height <= 0) {
|
return '存在尺寸无效的元素';
|
}
|
if (element.x < 0 || element.y < 0) {
|
return '元素坐标不能小于 0';
|
}
|
if (!isRectWithinCanvas(element, source.canvasWidth, source.canvasHeight)) {
|
return '存在超出画布边界的元素: ' + element.id;
|
}
|
if (element.type === 'devp') {
|
var value = safeParseJson(element.value);
|
if (!value || toInt(value.stationId, 0) <= 0 || toInt(value.deviceNo, 0) <= 0) {
|
return '输送线元素必须配置有效的 stationId 和 deviceNo';
|
}
|
}
|
}
|
var overlapId = findDocOverlapId(source);
|
if (overlapId) {
|
return '存在重叠元素: ' + overlapId;
|
}
|
return '';
|
},
|
validateBeforeSave: function () {
|
return this.validateDocBeforeSave(this.doc);
|
},
|
requestSaveDoc: function (doc) {
|
return new Promise(function (resolve, reject) {
|
$.ajax({
|
url: baseUrl + '/basMap/editor/save/auth',
|
method: 'POST',
|
headers: $.extend({
|
'Content-Type': 'application/json;charset=UTF-8'
|
}, authHeaders()),
|
data: JSON.stringify(doc),
|
success: function (res) {
|
if (!res || res.code !== 200) {
|
reject(new Error((res && res.msg) ? res.msg : '保存失败'));
|
return;
|
}
|
resolve(res);
|
},
|
error: function () {
|
reject(new Error('保存失败'));
|
}
|
});
|
});
|
},
|
collectDirtyDocsForSave: function () {
|
var result = [];
|
var seen = {};
|
if (this.doc && this.doc.lev && this.isDirty) {
|
var currentDoc = this.exportDoc(this.doc);
|
result.push(currentDoc);
|
seen[currentDoc.lev] = true;
|
}
|
var self = this;
|
Object.keys(this.draftDocs || {}).forEach(function (key) {
|
var lev = toInt(key, 0);
|
if (lev <= 0 || seen[lev]) {
|
return;
|
}
|
var entry = self.draftDocs[lev];
|
if (!entry || !entry.doc) {
|
return;
|
}
|
var snapshot = self.snapshotDoc(entry.doc);
|
if (snapshot === (entry.savedSnapshot || '')) {
|
return;
|
}
|
var doc = self.exportDoc(entry.doc);
|
result.push(doc);
|
seen[doc.lev] = true;
|
});
|
result.sort(function (a, b) {
|
return toInt(a.lev, 0) - toInt(b.lev, 0);
|
});
|
return result;
|
},
|
markDocSavedState: function (doc) {
|
var normalized = this.normalizeDoc(doc);
|
var savedSnapshot = this.snapshotDoc(normalized);
|
this.setDraftDocEntry(normalized.lev, normalized, savedSnapshot);
|
if (this.doc && this.doc.lev === normalized.lev) {
|
this.savedSnapshot = savedSnapshot;
|
this.syncDirty();
|
}
|
},
|
saveDoc: function () {
|
var self = this;
|
if (!this.doc) {
|
return;
|
}
|
var error = this.validateBeforeSave();
|
if (error) {
|
this.showMessage('warning', error);
|
return;
|
}
|
this.saving = true;
|
var payload = this.exportDoc(this.doc);
|
this.requestSaveDoc(payload).then(function () {
|
self.saving = false;
|
self.savedSnapshot = self.snapshotDoc(self.doc);
|
self.syncDirty();
|
self.clearCurrentDraftIfSaved();
|
self.refreshLevOptions();
|
self.showMessage('success', '当前楼层已保存并编译到运行地图');
|
}).catch(function (error) {
|
self.saving = false;
|
self.showMessage('error', error && error.message ? error.message : '保存失败');
|
});
|
},
|
saveAllDocs: function () {
|
var self = this;
|
if (this.saving || this.savingAll) {
|
return;
|
}
|
var docs = this.collectDirtyDocsForSave();
|
if (!docs.length) {
|
this.showMessage('warning', '当前没有需要保存的楼层');
|
return;
|
}
|
if (docs.length > 1 && !window.confirm('将保存 ' + docs.length + ' 个楼层到运行地图,是否继续?')) {
|
return;
|
}
|
for (var i = 0; i < docs.length; i++) {
|
var error = this.validateDocBeforeSave(docs[i]);
|
if (error) {
|
this.showMessage('warning', docs[i].lev + 'F 保存前校验失败: ' + error);
|
return;
|
}
|
}
|
this.savingAll = true;
|
var index = 0;
|
var total = docs.length;
|
var next = function () {
|
if (index >= total) {
|
self.savingAll = false;
|
self.refreshLevOptions();
|
self.showMessage('success', '已保存 ' + total + ' 个楼层到运行地图');
|
return;
|
}
|
var doc = docs[index++];
|
self.requestSaveDoc(doc).then(function () {
|
self.markDocSavedState(doc);
|
next();
|
}).catch(function (error) {
|
self.savingAll = false;
|
self.showMessage('error', doc.lev + 'F 保存失败: ' + (error && error.message ? error.message : '保存失败'));
|
});
|
};
|
next();
|
},
|
setTool: function (tool) {
|
this.activeTool = tool;
|
this.updateCursor();
|
},
|
findElementById: function (id) {
|
if (!this.doc || !id) {
|
return null;
|
}
|
var elements = this.doc.elements || [];
|
for (var i = 0; i < elements.length; i++) {
|
if (elements[i].id === id) {
|
return elements[i];
|
}
|
}
|
return null;
|
},
|
getSelectedElements: function () {
|
var self = this;
|
return this.selectedIds.map(function (id) {
|
return self.findElementById(id);
|
}).filter(Boolean);
|
},
|
refreshInspector: function () {
|
var element = this.singleSelectedElement;
|
if (!this.doc) {
|
this.canvasForm = {
|
width: String(DEFAULT_CANVAS_WIDTH),
|
height: String(DEFAULT_CANVAS_HEIGHT)
|
};
|
this.valueEditorText = '';
|
this.resetDevpForm();
|
this.resetDeviceForm();
|
return;
|
}
|
this.canvasForm = {
|
width: String(Math.round(this.doc.canvasWidth)),
|
height: String(Math.round(this.doc.canvasHeight))
|
};
|
if (!element) {
|
this.geometryForm = { x: '', y: '', width: '', height: '' };
|
this.valueEditorText = '';
|
this.resetDevpForm();
|
this.resetDeviceForm();
|
return;
|
}
|
this.geometryForm = {
|
x: String(this.formatNumber(element.x)),
|
y: String(this.formatNumber(element.y)),
|
width: String(this.formatNumber(element.width)),
|
height: String(this.formatNumber(element.height))
|
};
|
this.valueEditorText = element.value || '';
|
if (element.type === 'devp') {
|
this.loadDevpForm(element.value);
|
} else {
|
this.resetDevpForm();
|
}
|
if (isDeviceConfigType(element.type)) {
|
this.loadDeviceForm(element.type, element.value);
|
} else {
|
this.resetDeviceForm();
|
}
|
this.ensureShelfFillStartValue();
|
},
|
resetDevpForm: function () {
|
this.devpForm = {
|
stationId: '',
|
deviceNo: '',
|
direction: [],
|
isBarcodeStation: false,
|
barcodeIdx: '',
|
backStation: '',
|
backStationDeviceNo: '',
|
isInStation: false,
|
barcodeStation: '',
|
barcodeStationDeviceNo: '',
|
isOutStation: false,
|
runBlockReassign: false,
|
isOutOrder: false,
|
isLiftTransfer: false
|
};
|
},
|
resetDeviceForm: function () {
|
this.deviceForm = {
|
valueKey: '',
|
deviceNo: ''
|
};
|
},
|
ensureShelfFillStartValue: function () {
|
var element = this.singleSelectedElement;
|
if (!element || element.type !== 'shelf') {
|
return;
|
}
|
if (!this.shelfFillForm.startValue || !parseShelfLocationValue(this.shelfFillForm.startValue)) {
|
this.shelfFillForm.startValue = normalizeValue(element.value || '');
|
}
|
},
|
loadDevpForm: function (value) {
|
this.resetDevpForm();
|
var json = safeParseJson(value);
|
if (!json) {
|
return;
|
}
|
this.devpForm.stationId = json.stationId != null ? String(json.stationId) : '';
|
this.devpForm.deviceNo = json.deviceNo != null ? String(json.deviceNo) : '';
|
this.devpForm.direction = normalizeDirectionList(json.direction);
|
this.devpForm.isBarcodeStation = boolFlag(json.isBarcodeStation);
|
this.devpForm.barcodeIdx = json.barcodeIdx != null ? String(json.barcodeIdx) : '';
|
this.devpForm.backStation = json.backStation != null ? String(json.backStation) : '';
|
this.devpForm.backStationDeviceNo = json.backStationDeviceNo != null ? String(json.backStationDeviceNo) : '';
|
this.devpForm.isInStation = boolFlag(json.isInStation);
|
this.devpForm.barcodeStation = json.barcodeStation != null ? String(json.barcodeStation) : '';
|
this.devpForm.barcodeStationDeviceNo = json.barcodeStationDeviceNo != null ? String(json.barcodeStationDeviceNo) : '';
|
this.devpForm.isOutStation = boolFlag(json.isOutStation);
|
this.devpForm.runBlockReassign = boolFlag(json.runBlockReassign);
|
this.devpForm.isOutOrder = boolFlag(json.isOutOrder);
|
this.devpForm.isLiftTransfer = boolFlag(json.isLiftTransfer);
|
},
|
getDeviceConfigLabel: function (type) {
|
var meta = getTypeMeta(type);
|
return meta.label + '参数';
|
},
|
getDeviceConfigKeyLabel: function (type, valueKey) {
|
if (valueKey === 'crnNo') {
|
return 'crnNo';
|
}
|
if (valueKey === 'rgvNo') {
|
return 'rgvNo';
|
}
|
return type === 'rgv' ? 'deviceNo / rgvNo' : 'deviceNo / crnNo';
|
},
|
loadDeviceForm: function (type, value) {
|
this.resetDeviceForm();
|
if (!isDeviceConfigType(type)) {
|
return;
|
}
|
var json = safeParseJson(value);
|
var valueKey = pickDeviceValueKey(type, json);
|
var deviceNo = '';
|
if (json && json[valueKey] != null) {
|
deviceNo = String(json[valueKey]);
|
}
|
this.deviceForm = {
|
valueKey: valueKey,
|
deviceNo: deviceNo
|
};
|
},
|
isDevpDirectionActive: function (directionKey) {
|
return this.devpForm.direction.indexOf(directionKey) >= 0;
|
},
|
toggleDevpDirection: function (directionKey) {
|
if (!directionKey) {
|
return;
|
}
|
var next = this.devpForm.direction.slice();
|
var index = next.indexOf(directionKey);
|
if (index >= 0) {
|
next.splice(index, 1);
|
} else {
|
next.push(directionKey);
|
}
|
this.devpForm.direction = DEVP_DIRECTION_OPTIONS.map(function (item) {
|
return item.key;
|
}).filter(function (item) {
|
return next.indexOf(item) >= 0;
|
});
|
},
|
applyCanvasSize: function () {
|
var self = this;
|
if (!this.doc) {
|
return;
|
}
|
var width = toNumber(this.canvasForm.width, 0);
|
var height = toNumber(this.canvasForm.height, 0);
|
if (width <= 0 || height <= 0) {
|
this.showMessage('warning', '画布尺寸必须大于 0');
|
return;
|
}
|
var bounds = this.getElementBounds((this.doc.elements || []).map(function (item) {
|
return item.id;
|
}));
|
if (bounds && (width < bounds.x + bounds.width || height < bounds.y + bounds.height)) {
|
this.showMessage('warning', '画布不能小于当前元素占用范围');
|
return;
|
}
|
this.runMutation(function () {
|
self.doc.canvasWidth = roundCoord(width);
|
self.doc.canvasHeight = roundCoord(height);
|
});
|
},
|
applyGeometry: function () {
|
var self = this;
|
var element = this.singleSelectedElement;
|
if (!element) {
|
return;
|
}
|
var next = {
|
x: roundCoord(Math.max(0, toNumber(this.geometryForm.x, element.x))),
|
y: roundCoord(Math.max(0, toNumber(this.geometryForm.y, element.y))),
|
width: roundCoord(Math.max(MIN_ELEMENT_SIZE, toNumber(this.geometryForm.width, element.width))),
|
height: roundCoord(Math.max(MIN_ELEMENT_SIZE, toNumber(this.geometryForm.height, element.height)))
|
};
|
if (!this.isWithinCanvas(next)) {
|
this.showMessage('warning', '几何属性超出当前画布范围');
|
return;
|
}
|
var preview = deepClone(element);
|
preview.x = next.x;
|
preview.y = next.y;
|
preview.width = next.width;
|
preview.height = next.height;
|
if (this.hasOverlap(preview, [preview.id])) {
|
this.showMessage('warning', '调整后会与其他元素重叠');
|
return;
|
}
|
this.runMutation(function () {
|
element.x = next.x;
|
element.y = next.y;
|
element.width = next.width;
|
element.height = next.height;
|
});
|
},
|
applyRawValue: function () {
|
var self = this;
|
var element = this.singleSelectedElement;
|
if (!element || element.type === 'devp') {
|
return;
|
}
|
this.runMutation(function () {
|
element.value = normalizeValue(self.valueEditorText);
|
});
|
},
|
applyDeviceForm: function () {
|
var self = this;
|
var element = this.singleSelectedDeviceElement;
|
if (!element) {
|
return;
|
}
|
var deviceNo = toInt(this.deviceForm.deviceNo, 0);
|
if (deviceNo <= 0) {
|
this.showMessage('warning', '设备编号必须大于 0');
|
return;
|
}
|
var valueKey = this.deviceForm.valueKey || pickDeviceValueKey(element.type, safeParseJson(element.value));
|
this.runMutation(function () {
|
var payload = safeParseJson(element.value) || {};
|
delete payload.deviceNo;
|
delete payload.crnNo;
|
delete payload.rgvNo;
|
payload[valueKey] = deviceNo;
|
element.value = JSON.stringify(payload);
|
self.valueEditorText = element.value;
|
});
|
},
|
applyDevpForm: function () {
|
var self = this;
|
var element = this.singleSelectedElement;
|
if (!element || element.type !== 'devp') {
|
return;
|
}
|
var stationId = toInt(this.devpForm.stationId, 0);
|
var deviceNo = toInt(this.devpForm.deviceNo, 0);
|
if (stationId <= 0 || deviceNo <= 0) {
|
this.showMessage('warning', '站号和 PLC 编号必须大于 0');
|
return;
|
}
|
var payload = {
|
stationId: stationId,
|
deviceNo: deviceNo
|
};
|
var directionList = normalizeDirectionList(this.devpForm.direction);
|
if (directionList.length > 0) {
|
payload.direction = directionList;
|
}
|
var barcodeIdx = this.devpForm.barcodeIdx === '' ? 0 : toInt(this.devpForm.barcodeIdx, 0);
|
var backStation = this.devpForm.backStation === '' ? 0 : toInt(this.devpForm.backStation, 0);
|
var backStationDeviceNo = this.devpForm.backStationDeviceNo === '' ? 0 : toInt(this.devpForm.backStationDeviceNo, 0);
|
var barcodeStation = this.devpForm.barcodeStation === '' ? 0 : toInt(this.devpForm.barcodeStation, 0);
|
var barcodeStationDeviceNo = this.devpForm.barcodeStationDeviceNo === '' ? 0 : toInt(this.devpForm.barcodeStationDeviceNo, 0);
|
if (this.devpForm.isInStation && (barcodeStation <= 0 || barcodeStationDeviceNo <= 0)) {
|
this.showMessage('warning', '入站点必须填写条码站和条码站 PLC 编号');
|
return;
|
}
|
if (this.devpForm.isBarcodeStation && (backStation <= 0 || backStationDeviceNo <= 0 || barcodeIdx <= 0)) {
|
this.showMessage('warning', '条码站必须填写条码索引、退回站和退回站 PLC 编号');
|
return;
|
}
|
if (this.devpForm.isBarcodeStation) {
|
payload.isBarcodeStation = 1;
|
}
|
if (barcodeIdx > 0) {
|
payload.barcodeIdx = barcodeIdx;
|
}
|
if (backStation > 0) {
|
payload.backStation = backStation;
|
}
|
if (backStationDeviceNo > 0) {
|
payload.backStationDeviceNo = backStationDeviceNo;
|
}
|
if (this.devpForm.isInStation) {
|
payload.isInStation = 1;
|
}
|
if (barcodeStation > 0) {
|
payload.barcodeStation = barcodeStation;
|
}
|
if (barcodeStationDeviceNo > 0) {
|
payload.barcodeStationDeviceNo = barcodeStationDeviceNo;
|
}
|
if (this.devpForm.isOutStation) {
|
payload.isOutStation = 1;
|
}
|
if (this.devpForm.runBlockReassign) {
|
payload.runBlockReassign = 1;
|
}
|
if (this.devpForm.isOutOrder) {
|
payload.isOutOrder = 1;
|
}
|
if (this.devpForm.isLiftTransfer) {
|
payload.isLiftTransfer = 1;
|
}
|
this.runMutation(function () {
|
element.value = JSON.stringify(payload);
|
self.valueEditorText = element.value;
|
});
|
},
|
deleteSelection: function () {
|
var self = this;
|
if (!this.doc || this.selectedIds.length === 0) {
|
return;
|
}
|
var ids = this.selectedIds.slice();
|
this.runMutation(function () {
|
self.doc.elements = self.doc.elements.filter(function (item) {
|
return ids.indexOf(item.id) === -1;
|
});
|
self.selectedIds = [];
|
});
|
},
|
copySelection: function () {
|
var elements = this.getSelectedElements();
|
if (!elements.length) {
|
return;
|
}
|
this.clipboard = deepClone(elements);
|
this.showMessage('success', '已复制 ' + elements.length + ' 个元素');
|
},
|
getElementListBounds: function (elements) {
|
if (!elements || !elements.length) {
|
return null;
|
}
|
var minX = elements[0].x;
|
var minY = elements[0].y;
|
var maxX = elements[0].x + elements[0].width;
|
var maxY = elements[0].y + elements[0].height;
|
for (var i = 1; i < elements.length; i++) {
|
var element = elements[i];
|
minX = Math.min(minX, element.x);
|
minY = Math.min(minY, element.y);
|
maxX = Math.max(maxX, element.x + element.width);
|
maxY = Math.max(maxY, element.y + element.height);
|
}
|
return {
|
x: minX,
|
y: minY,
|
width: maxX - minX,
|
height: maxY - minY
|
};
|
},
|
getPasteTargetWorld: function () {
|
if (!this.doc) {
|
return { x: 0, y: 0 };
|
}
|
var visible = this.getVisibleCanvasRect ? this.getVisibleCanvasRect() : this.getVisibleWorldRect();
|
var fallback = {
|
x: visible.x + visible.width / 2,
|
y: visible.y + visible.height / 2
|
};
|
if (!this.lastPointerWorld) {
|
return fallback;
|
}
|
return {
|
x: clamp(this.lastPointerWorld.x, 0, this.doc.canvasWidth),
|
y: clamp(this.lastPointerWorld.y, 0, this.doc.canvasHeight),
|
screenX: this.lastPointerWorld.screenX,
|
screenY: this.lastPointerWorld.screenY
|
};
|
},
|
pasteClipboard: function () {
|
var self = this;
|
if (!this.doc || !this.clipboard.length) {
|
return;
|
}
|
var sourceBounds = this.getElementListBounds(this.clipboard);
|
if (!sourceBounds) {
|
return;
|
}
|
var target = this.getPasteTargetWorld();
|
var offsetX = target.x - (sourceBounds.x + sourceBounds.width / 2);
|
var offsetY = target.y - (sourceBounds.y + sourceBounds.height / 2);
|
var minOffsetX = -sourceBounds.x;
|
var maxOffsetX = this.doc.canvasWidth - (sourceBounds.x + sourceBounds.width);
|
var minOffsetY = -sourceBounds.y;
|
var maxOffsetY = this.doc.canvasHeight - (sourceBounds.y + sourceBounds.height);
|
offsetX = clamp(offsetX, minOffsetX, maxOffsetX);
|
offsetY = clamp(offsetY, minOffsetY, maxOffsetY);
|
var copies = deepClone(this.clipboard).map(function (item) {
|
item.id = nextId();
|
item.x = roundCoord(item.x + offsetX);
|
item.y = roundCoord(item.y + offsetY);
|
return item;
|
});
|
if (!this.canPlaceElements(copies, [])) {
|
this.showMessage('warning', '粘贴后的元素与现有元素重叠或超出画布');
|
return;
|
}
|
this.runMutation(function () {
|
self.doc.elements = self.doc.elements.concat(copies);
|
self.selectedIds = copies.map(function (item) { return item.id; });
|
});
|
},
|
canArrayFromElement: function (element) {
|
return !!(element && ARRAY_TEMPLATE_TYPES.indexOf(element.type) >= 0);
|
},
|
getShelfFillSteps: function () {
|
return {
|
row: this.shelfFillForm.rowStep === 'asc' ? 1 : -1,
|
col: this.shelfFillForm.colStep === 'desc' ? -1 : 1
|
};
|
},
|
applyShelfSequenceToArrayCopies: function (template, copies) {
|
if (!template || template.type !== 'shelf' || !copies || !copies.length) {
|
return copies;
|
}
|
var base = parseShelfLocationValue(template.value) || parseShelfLocationValue(this.shelfFillForm.startValue);
|
if (!base) {
|
return copies;
|
}
|
var steps = this.getShelfFillSteps();
|
var horizontal = Math.abs(copies[0].x - template.x) >= Math.abs(copies[0].y - template.y);
|
var direction = 1;
|
if (horizontal) {
|
direction = copies[0].x >= template.x ? 1 : -1;
|
} else {
|
direction = copies[0].y >= template.y ? 1 : -1;
|
}
|
for (var i = 0; i < copies.length; i++) {
|
var offset = i + 1;
|
var row = base.row;
|
var col = base.col;
|
if (horizontal) {
|
col = base.col + steps.col * direction * offset;
|
} else {
|
row = base.row + steps.row * direction * offset;
|
}
|
copies[i].value = formatShelfLocationValue(row, col);
|
}
|
return copies;
|
},
|
buildShelfGridAssignments: function (elements) {
|
if (!elements || !elements.length) {
|
return null;
|
}
|
var clusterAxis = function (list, axis, sizeKey) {
|
var sorted = list.map(function (item) {
|
return {
|
id: item.id,
|
center: item[axis] + item[sizeKey] / 2,
|
size: item[sizeKey]
|
};
|
}).sort(function (a, b) {
|
return a.center - b.center;
|
});
|
var avgSize = sorted.reduce(function (sum, item) {
|
return sum + item.size;
|
}, 0) / sorted.length;
|
var tolerance = Math.max(6, avgSize * 0.45);
|
var groups = [];
|
for (var i = 0; i < sorted.length; i++) {
|
var current = sorted[i];
|
var last = groups.length ? groups[groups.length - 1] : null;
|
if (!last || Math.abs(current.center - last.center) > tolerance) {
|
groups.push({
|
center: current.center,
|
items: [current]
|
});
|
} else {
|
last.items.push(current);
|
last.center = last.items.reduce(function (sum, item) {
|
return sum + item.center;
|
}, 0) / last.items.length;
|
}
|
}
|
var indexById = {};
|
for (var groupIndex = 0; groupIndex < groups.length; groupIndex++) {
|
for (var itemIndex = 0; itemIndex < groups[groupIndex].items.length; itemIndex++) {
|
indexById[groups[groupIndex].items[itemIndex].id] = groupIndex;
|
}
|
}
|
return indexById;
|
};
|
return {
|
rowById: clusterAxis(elements, 'y', 'height'),
|
colById: clusterAxis(elements, 'x', 'width')
|
};
|
},
|
applyShelfAutoFill: function () {
|
var self = this;
|
var shelves = this.selectedShelfElements.slice();
|
if (!shelves.length) {
|
this.showMessage('warning', '请先选中至少一个货架');
|
return;
|
}
|
var start = parseShelfLocationValue(this.shelfFillForm.startValue);
|
if (!start) {
|
this.showMessage('warning', '起始值格式必须是 排-列,例如 12-1');
|
return;
|
}
|
var grid = this.buildShelfGridAssignments(shelves);
|
if (!grid) {
|
return;
|
}
|
var steps = this.getShelfFillSteps();
|
this.runMutation(function () {
|
shelves.forEach(function (item) {
|
var rowIndex = grid.rowById[item.id] || 0;
|
var colIndex = grid.colById[item.id] || 0;
|
item.value = formatShelfLocationValue(
|
start.row + rowIndex * steps.row,
|
start.col + colIndex * steps.col
|
);
|
});
|
if (self.singleSelectedElement && self.singleSelectedElement.type === 'shelf') {
|
self.valueEditorText = self.singleSelectedElement.value || '';
|
}
|
});
|
},
|
buildArrayCopies: function (template, startWorld, currentWorld) {
|
if (!this.doc || !template || !startWorld || !currentWorld || !this.canArrayFromElement(template)) {
|
return [];
|
}
|
var deltaX = currentWorld.x - startWorld.x;
|
var deltaY = currentWorld.y - startWorld.y;
|
if (Math.abs(deltaX) < COORD_EPSILON && Math.abs(deltaY) < COORD_EPSILON) {
|
return [];
|
}
|
var horizontal = Math.abs(deltaX) >= Math.abs(deltaY);
|
var step = horizontal ? template.width : template.height;
|
if (step <= COORD_EPSILON) {
|
return [];
|
}
|
var direction = (horizontal ? deltaX : deltaY) >= 0 ? 1 : -1;
|
var distance;
|
if (horizontal) {
|
distance = direction > 0
|
? currentWorld.x - (template.x + template.width)
|
: template.x - currentWorld.x;
|
} else {
|
distance = direction > 0
|
? currentWorld.y - (template.y + template.height)
|
: template.y - currentWorld.y;
|
}
|
var count = Math.max(0, Math.floor((distance + step * 0.5) / step));
|
if (count <= 0) {
|
return [];
|
}
|
var copies = [];
|
for (var i = 1; i <= count; i++) {
|
copies.push({
|
type: template.type,
|
x: roundCoord(template.x + (horizontal ? direction * template.width * i : 0)),
|
y: roundCoord(template.y + (horizontal ? 0 : direction * template.height * i)),
|
width: template.width,
|
height: template.height,
|
value: template.value
|
});
|
}
|
return this.applyShelfSequenceToArrayCopies(template, copies);
|
},
|
duplicateSelection: function () {
|
this.copySelection();
|
this.pasteClipboard();
|
},
|
getElementBounds: function (ids) {
|
if (!this.doc) {
|
return null;
|
}
|
var elements = ids && ids.length ? this.getSelectedElements() : (this.doc.elements || []);
|
if (ids && ids.length) {
|
elements = ids.map(function (id) {
|
return this.findElementById(id);
|
}, this).filter(Boolean);
|
}
|
if (!elements.length) {
|
return null;
|
}
|
var minX = elements[0].x;
|
var minY = elements[0].y;
|
var maxX = elements[0].x + elements[0].width;
|
var maxY = elements[0].y + elements[0].height;
|
for (var i = 1; i < elements.length; i++) {
|
var element = elements[i];
|
minX = Math.min(minX, element.x);
|
minY = Math.min(minY, element.y);
|
maxX = Math.max(maxX, element.x + element.width);
|
maxY = Math.max(maxY, element.y + element.height);
|
}
|
return {
|
x: minX,
|
y: minY,
|
width: maxX - minX,
|
height: maxY - minY
|
};
|
},
|
fitContent: function () {
|
if (!this.doc || !this.pixiApp) {
|
return;
|
}
|
var contentBounds = this.getElementBounds();
|
if (contentBounds && contentBounds.width > 0 && contentBounds.height > 0) {
|
this.fitRect(contentBounds, this.pixiApp.renderer.width, this.pixiApp.renderer.height);
|
return;
|
}
|
this.fitCanvas();
|
},
|
fitCanvas: function () {
|
if (!this.doc || !this.pixiApp) {
|
return;
|
}
|
var renderer = this.pixiApp.renderer;
|
var target = {
|
x: 0,
|
y: 0,
|
width: Math.max(1, this.doc.canvasWidth),
|
height: Math.max(1, this.doc.canvasHeight)
|
};
|
this.fitRect(target, renderer.width, renderer.height);
|
},
|
fitSelection: function () {
|
if (!this.selectedIds.length || !this.pixiApp) {
|
return;
|
}
|
var bounds = this.getElementBounds(this.selectedIds);
|
if (!bounds) {
|
return;
|
}
|
this.fitRect(bounds, this.pixiApp.renderer.width, this.pixiApp.renderer.height);
|
},
|
fitRect: function (rect, viewportWidth, viewportHeight) {
|
var padding = 80;
|
var scale = Math.min(
|
(viewportWidth - padding * 2) / Math.max(rect.width, 1),
|
(viewportHeight - padding * 2) / Math.max(rect.height, 1)
|
);
|
scale = clamp(scale, 0.06, 4);
|
this.camera.scale = scale;
|
this.camera.x = Math.round((viewportWidth - rect.width * scale) / 2 - rect.x * scale);
|
this.camera.y = Math.round((viewportHeight - rect.height * scale) / 2 - rect.y * scale);
|
this.viewZoom = scale;
|
this.markGridSceneDirty();
|
this.markStaticSceneDirty();
|
this.scheduleRender();
|
},
|
resetView: function () {
|
this.fitCanvas();
|
},
|
getVisibleWorldRect: function () {
|
if (!this.pixiApp) {
|
return {
|
x: 0,
|
y: 0,
|
width: 0,
|
height: 0
|
};
|
}
|
return {
|
x: (-this.camera.x) / this.camera.scale,
|
y: (-this.camera.y) / this.camera.scale,
|
width: this.pixiApp.renderer.width / this.camera.scale,
|
height: this.pixiApp.renderer.height / this.camera.scale
|
};
|
},
|
getVisibleCanvasRect: function () {
|
if (!this.doc) {
|
return {
|
x: 0,
|
y: 0,
|
width: 0,
|
height: 0
|
};
|
}
|
var visible = this.getVisibleWorldRect();
|
var left = clamp(visible.x, 0, this.doc.canvasWidth);
|
var top = clamp(visible.y, 0, this.doc.canvasHeight);
|
var right = clamp(visible.x + visible.width, 0, this.doc.canvasWidth);
|
var bottom = clamp(visible.y + visible.height, 0, this.doc.canvasHeight);
|
return {
|
x: left,
|
y: top,
|
width: Math.max(0, right - left),
|
height: Math.max(0, bottom - top)
|
};
|
},
|
getWorldRectWithPadding: function (screenPadding) {
|
if (!this.doc) {
|
return {
|
x: 0,
|
y: 0,
|
width: 0,
|
height: 0
|
};
|
}
|
var visible = this.getVisibleWorldRect();
|
var padding = Math.max(screenPadding / this.camera.scale, 24);
|
var left = Math.max(0, visible.x - padding);
|
var top = Math.max(0, visible.y - padding);
|
var right = Math.min(this.doc.canvasWidth, visible.x + visible.width + padding);
|
var bottom = Math.min(this.doc.canvasHeight, visible.y + visible.height + padding);
|
return {
|
x: left,
|
y: top,
|
width: Math.max(0, right - left),
|
height: Math.max(0, bottom - top)
|
};
|
},
|
worldRectContains: function (outer, inner) {
|
if (!outer || !inner) {
|
return false;
|
}
|
return inner.x >= outer.x - COORD_EPSILON
|
&& inner.y >= outer.y - COORD_EPSILON
|
&& inner.x + inner.width <= outer.x + outer.width + COORD_EPSILON
|
&& inner.y + inner.height <= outer.y + outer.height + COORD_EPSILON;
|
},
|
getGridRenderKey: function () {
|
var minorStep = this.camera.scale > 1.5 ? 50 : (this.camera.scale > 0.45 ? 100 : 200);
|
return minorStep + '|' + (Math.round(this.camera.scale * 8) / 8);
|
},
|
getStaticRenderKey: function () {
|
return (this.camera.scale >= 0.85 ? 'round' : 'flat') + '|' + (Math.round(this.camera.scale * 8) / 8);
|
},
|
scheduleRender: function () {
|
if (this.renderQueued) {
|
return;
|
}
|
this.renderQueued = true;
|
window.requestAnimationFrame(function () {
|
this.renderQueued = false;
|
this.renderScene();
|
}.bind(this));
|
},
|
renderScene: function () {
|
if (!this.pixiApp || !this.doc) {
|
return;
|
}
|
this.mapRoot.position.set(this.camera.x, this.camera.y);
|
this.mapRoot.scale.set(this.camera.scale, this.camera.scale);
|
this.viewZoom = this.camera.scale;
|
var visible = this.getVisibleCanvasRect();
|
var viewportSettled = !this.isZooming && !this.isPanning && !(this.interactionState && this.interactionState.type === 'pan');
|
var gridKeyChanged = this.gridRenderKey !== this.getGridRenderKey();
|
if (this.gridSceneDirty || !this.gridRenderRect || (viewportSettled && gridKeyChanged) || (viewportSettled && !this.worldRectContains(this.gridRenderRect, visible))) {
|
this.renderGrid(this.getWorldRectWithPadding(STATIC_VIEW_PADDING));
|
this.gridSceneDirty = false;
|
}
|
var excludedKey = this.selectionKey(this.getStaticExcludedIds());
|
var staticKeyChanged = this.staticRenderKey !== this.getStaticRenderKey();
|
if (this.staticSceneDirty || !this.staticRenderRect || (viewportSettled && staticKeyChanged)
|
|| this.staticExcludedKey !== excludedKey || (viewportSettled && !this.worldRectContains(this.staticRenderRect, visible))) {
|
this.renderStaticElements(this.getWorldRectWithPadding(STATIC_VIEW_PADDING), excludedKey);
|
this.staticSceneDirty = false;
|
}
|
this.renderActiveElements();
|
this.renderLabels();
|
this.renderHover();
|
this.renderSelection();
|
this.renderGuide();
|
this.updateCursor();
|
},
|
getStaticExcludedIds: function () {
|
if (!this.interactionState) {
|
return [];
|
}
|
if (this.interactionState.type === 'move' && this.selectedIds.length) {
|
return this.selectedIds.slice();
|
}
|
if (this.interactionState.type === 'resize' && this.interactionState.elementId) {
|
return [this.interactionState.elementId];
|
}
|
return [];
|
},
|
getRenderableElements: function (excludeIds, renderRect) {
|
if (!this.doc) {
|
return [];
|
}
|
var rect = renderRect || this.getWorldRectWithPadding(STATIC_VIEW_PADDING);
|
var candidates = this.querySpatialCandidates(rect, 0, excludeIds);
|
var result = [];
|
for (var i = 0; i < candidates.length; i++) {
|
if (rectIntersects(rect, candidates[i])) {
|
result.push(candidates[i]);
|
}
|
}
|
return result;
|
},
|
renderGrid: function (renderRect) {
|
if (!this.gridLayer || !this.doc) {
|
return;
|
}
|
var visible = renderRect || this.getVisibleWorldRect();
|
var width = this.doc.canvasWidth;
|
var height = this.doc.canvasHeight;
|
var minorStep = this.camera.scale > 1.5 ? 50 : (this.camera.scale > 0.45 ? 100 : 200);
|
var majorStep = minorStep * 5;
|
var lineWidth = 1 / this.camera.scale;
|
var xStart = Math.max(0, Math.floor(visible.x / minorStep) * minorStep);
|
var yStart = Math.max(0, Math.floor(visible.y / minorStep) * minorStep);
|
var xEnd = Math.min(width, visible.x + visible.width);
|
var yEnd = Math.min(height, visible.y + visible.height);
|
|
this.gridLayer.clear();
|
this.gridLayer.beginFill(0xfafcff, 1);
|
this.gridLayer.drawRect(0, 0, width, height);
|
this.gridLayer.endFill();
|
|
this.gridLayer.lineStyle(lineWidth, 0xdbe4ee, 1);
|
this.gridLayer.drawRect(0, 0, width, height);
|
|
for (var x = xStart; x <= xEnd; x += minorStep) {
|
var colorX = (x % majorStep === 0) ? 0xc9d7e6 : 0xe4ebf3;
|
this.gridLayer.lineStyle(lineWidth, colorX, x % majorStep === 0 ? 0.95 : 0.75);
|
this.gridLayer.moveTo(x, 0);
|
this.gridLayer.lineTo(x, height);
|
}
|
for (var y = yStart; y <= yEnd; y += minorStep) {
|
var colorY = (y % majorStep === 0) ? 0xc9d7e6 : 0xe4ebf3;
|
this.gridLayer.lineStyle(lineWidth, colorY, y % majorStep === 0 ? 0.95 : 0.75);
|
this.gridLayer.moveTo(0, y);
|
this.gridLayer.lineTo(width, y);
|
}
|
this.gridRenderRect = {
|
x: visible.x,
|
y: visible.y,
|
width: visible.width,
|
height: visible.height
|
};
|
this.gridRenderKey = this.getGridRenderKey();
|
},
|
drawGridPatch: function (rects, layer) {
|
if (!this.doc || !layer || !rects || !rects.length) {
|
return;
|
}
|
var width = this.doc.canvasWidth;
|
var height = this.doc.canvasHeight;
|
var minorStep = this.camera.scale > 1.5 ? 50 : (this.camera.scale > 0.45 ? 100 : 200);
|
var majorStep = minorStep * 5;
|
var lineWidth = 1 / this.camera.scale;
|
for (var i = 0; i < rects.length; i++) {
|
var rect = rects[i];
|
var left = clamp(rect.x - lineWidth, 0, width);
|
var top = clamp(rect.y - lineWidth, 0, height);
|
var right = clamp(rect.x + rect.width + lineWidth, 0, width);
|
var bottom = clamp(rect.y + rect.height + lineWidth, 0, height);
|
if (right <= left || bottom <= top) {
|
continue;
|
}
|
layer.lineStyle(0, 0, 0, 0);
|
layer.beginFill(0xfafcff, 1);
|
layer.drawRect(left, top, right - left, bottom - top);
|
layer.endFill();
|
if (right - left < minorStep || bottom - top < minorStep) {
|
continue;
|
}
|
var xStart = Math.floor(left / minorStep) * minorStep;
|
var yStart = Math.floor(top / minorStep) * minorStep;
|
for (var x = xStart; x <= right; x += minorStep) {
|
if (x < left || x > right) {
|
continue;
|
}
|
var colorX = (x % majorStep === 0) ? 0xc9d7e6 : 0xe4ebf3;
|
layer.lineStyle(lineWidth, colorX, x % majorStep === 0 ? 0.95 : 0.75);
|
layer.moveTo(x, top);
|
layer.lineTo(x, bottom);
|
}
|
for (var y = yStart; y <= bottom; y += minorStep) {
|
if (y < top || y > bottom) {
|
continue;
|
}
|
var colorY = (y % majorStep === 0) ? 0xc9d7e6 : 0xe4ebf3;
|
layer.lineStyle(lineWidth, colorY, y % majorStep === 0 ? 0.95 : 0.75);
|
layer.moveTo(left, y);
|
layer.lineTo(right, y);
|
}
|
}
|
},
|
drawPatchObjects: function (rects, excludeIds) {
|
if (!rects || !rects.length || !this.patchObjectLayer) {
|
return;
|
}
|
var seen = {};
|
var elements = [];
|
for (var i = 0; i < rects.length; i++) {
|
var candidates = this.querySpatialCandidates(rects[i], 0, excludeIds);
|
for (var j = 0; j < candidates.length; j++) {
|
var item = candidates[j];
|
if (!seen[item.id] && rectIntersects(rects[i], item)) {
|
seen[item.id] = true;
|
elements.push(item);
|
}
|
}
|
}
|
if (!elements.length) {
|
return;
|
}
|
this.drawElementsToLayers(elements, this.patchObjectLayer, this.patchObjectLayer);
|
},
|
drawElementsToLayers: function (elements, trackLayer, nodeLayer) {
|
var lineWidth = 1 / this.camera.scale;
|
var useRounded = this.camera.scale >= 0.85;
|
var radius = Math.max(6 / this.camera.scale, 2);
|
var buckets = {};
|
for (var i = 0; i < elements.length; i++) {
|
var element = elements[i];
|
var bucketKey = (element.type === 'shelf' ? 'node' : 'track') + ':' + element.type;
|
if (!buckets[bucketKey]) {
|
buckets[bucketKey] = [];
|
}
|
buckets[bucketKey].push(element);
|
}
|
for (var bucketKey in buckets) {
|
if (!buckets.hasOwnProperty(bucketKey)) {
|
continue;
|
}
|
var parts = bucketKey.split(':');
|
var type = parts[1];
|
var meta = getTypeMeta(type);
|
var layer = parts[0] === 'node' ? nodeLayer : trackLayer;
|
layer.lineStyle(lineWidth, meta.border, 1);
|
layer.beginFill(meta.fill, 0.92);
|
var bucket = buckets[bucketKey];
|
for (var j = 0; j < bucket.length; j++) {
|
var item = bucket[j];
|
if (useRounded) {
|
layer.drawRoundedRect(item.x, item.y, item.width, item.height, radius);
|
} else {
|
layer.drawRect(item.x, item.y, item.width, item.height);
|
}
|
}
|
layer.endFill();
|
}
|
},
|
ensureStaticSprite: function (poolName, index) {
|
var pool = poolName === 'node' ? this.staticNodeSpritePool : this.staticTrackSpritePool;
|
var layer = poolName === 'node' ? this.staticNodeSpriteLayer : this.staticTrackSpriteLayer;
|
if (pool[index]) {
|
return pool[index];
|
}
|
var sprite = new PIXI.Sprite(PIXI.Texture.WHITE);
|
sprite.position.set(0, 0);
|
sprite.anchor.set(0, 0);
|
sprite.visible = false;
|
sprite.alpha = 0;
|
layer.addChild(sprite);
|
pool[index] = sprite;
|
return sprite;
|
},
|
hideUnusedStaticSprites: function (pool, fromIndex) {
|
for (var i = fromIndex; i < pool.length; i++) {
|
pool[i].visible = false;
|
pool[i].alpha = 0;
|
pool[i].width = 0;
|
pool[i].height = 0;
|
pool[i].position.set(-99999, -99999);
|
}
|
},
|
pruneStaticSpritePool: function (poolName, keepCount, slack) {
|
var pool = poolName === 'node' ? this.staticNodeSpritePool : this.staticTrackSpritePool;
|
var layer = poolName === 'node' ? this.staticNodeSpriteLayer : this.staticTrackSpriteLayer;
|
var target = Math.max(0, keepCount + Math.max(0, slack || 0));
|
if (!pool || !layer || pool.length <= target) {
|
return;
|
}
|
for (var i = pool.length - 1; i >= target; i--) {
|
var sprite = pool[i];
|
layer.removeChild(sprite);
|
if (sprite && sprite.destroy) {
|
sprite.destroy();
|
}
|
pool.pop();
|
}
|
},
|
drawElementsToSpriteLayers: function (elements) {
|
var trackCount = 0;
|
var nodeCount = 0;
|
for (var i = 0; i < elements.length; i++) {
|
var item = elements[i];
|
var meta = getTypeMeta(item.type);
|
var poolName = item.type === 'shelf' ? 'node' : 'track';
|
var sprite = this.ensureStaticSprite(poolName, poolName === 'node' ? nodeCount : trackCount);
|
sprite.visible = true;
|
sprite.position.set(item.x, item.y);
|
sprite.width = item.width;
|
sprite.height = item.height;
|
sprite.tint = meta.fill;
|
sprite.alpha = 0.92;
|
if (poolName === 'node') {
|
nodeCount += 1;
|
} else {
|
trackCount += 1;
|
}
|
}
|
this.hideUnusedStaticSprites(this.staticTrackSpritePool, trackCount);
|
this.hideUnusedStaticSprites(this.staticNodeSpritePool, nodeCount);
|
if (this.camera.scale < DENSE_SIMPLIFY_SCALE_THRESHOLD) {
|
this.pruneStaticSpritePool('track', trackCount, STATIC_SPRITE_POOL_SLACK);
|
this.pruneStaticSpritePool('node', nodeCount, STATIC_SPRITE_POOL_SLACK);
|
}
|
},
|
simplifyRenderableElements: function (elements) {
|
if (!elements || elements.length < 2) {
|
return elements || [];
|
}
|
var sorted = elements.slice().sort(function (a, b) {
|
if (a.type !== b.type) {
|
return a.type < b.type ? -1 : 1;
|
}
|
if (Math.abs(a.y - b.y) > COORD_EPSILON) {
|
return a.y - b.y;
|
}
|
if (Math.abs(a.height - b.height) > COORD_EPSILON) {
|
return a.height - b.height;
|
}
|
return a.x - b.x;
|
});
|
var result = [];
|
var current = null;
|
for (var i = 0; i < sorted.length; i++) {
|
var item = sorted[i];
|
if (!current) {
|
current = {
|
type: item.type,
|
x: item.x,
|
y: item.y,
|
width: item.width,
|
height: item.height
|
};
|
continue;
|
}
|
var currentRight = current.x + current.width;
|
var itemRight = item.x + item.width;
|
var sameBand = current.type === item.type
|
&& Math.abs(current.y - item.y) <= 0.5
|
&& Math.abs(current.height - item.height) <= 0.5;
|
var joinable = item.x <= currentRight + 0.5;
|
if (sameBand && joinable) {
|
current.width = roundCoord(Math.max(currentRight, itemRight) - current.x);
|
} else {
|
result.push(current);
|
current = {
|
type: item.type,
|
x: item.x,
|
y: item.y,
|
width: item.width,
|
height: item.height
|
};
|
}
|
}
|
if (current) {
|
result.push(current);
|
}
|
return result;
|
},
|
renderStaticElements: function (renderRect, excludedKey) {
|
if (!this.doc) {
|
return;
|
}
|
this.trackLayer.clear();
|
this.nodeLayer.clear();
|
this.eraseLayer.clear();
|
this.patchObjectLayer.clear();
|
var renderableElements = this.getRenderableElements(this.getStaticExcludedIds(), renderRect);
|
var useSpriteMode = this.camera.scale < STATIC_SPRITE_SCALE_THRESHOLD;
|
var shouldSimplify = this.camera.scale < STATIC_SIMPLIFY_SCALE_THRESHOLD
|
|| (this.camera.scale < DENSE_SIMPLIFY_SCALE_THRESHOLD && renderableElements.length > DENSE_SIMPLIFY_ELEMENT_THRESHOLD);
|
this.staticTrackSpriteLayer.visible = useSpriteMode;
|
this.staticNodeSpriteLayer.visible = useSpriteMode;
|
this.trackLayer.visible = !useSpriteMode;
|
this.nodeLayer.visible = !useSpriteMode;
|
if (useSpriteMode) {
|
if (shouldSimplify) {
|
renderableElements = this.simplifyRenderableElements(renderableElements);
|
}
|
this.drawElementsToSpriteLayers(renderableElements);
|
} else {
|
this.hideUnusedStaticSprites(this.staticTrackSpritePool, 0);
|
this.hideUnusedStaticSprites(this.staticNodeSpritePool, 0);
|
this.drawElementsToLayers(renderableElements, this.trackLayer, this.nodeLayer);
|
}
|
var rect = renderRect || this.getWorldRectWithPadding(STATIC_VIEW_PADDING);
|
this.staticRenderRect = {
|
x: rect.x,
|
y: rect.y,
|
width: rect.width,
|
height: rect.height
|
};
|
this.staticRenderKey = this.getStaticRenderKey();
|
this.staticExcludedKey = excludedKey != null ? excludedKey : this.selectionKey(this.getStaticExcludedIds());
|
this.pendingStaticCommit = null;
|
},
|
renderActiveElements: function () {
|
this.activeLayer.clear();
|
this.eraseLayer.clear();
|
this.patchObjectLayer.clear();
|
var activeIds = this.getStaticExcludedIds();
|
if (!activeIds.length) {
|
return;
|
}
|
var activeElements = [];
|
for (var idx = 0; idx < activeIds.length; idx++) {
|
var element = this.findElementById(activeIds[idx]);
|
if (element) {
|
activeElements.push(element);
|
}
|
}
|
if (!activeElements.length) {
|
return;
|
}
|
this.drawElementsToLayers(activeElements, this.activeLayer, this.activeLayer);
|
},
|
getLabelText: function (element) {
|
var meta = getTypeMeta(element.type);
|
var value = safeParseJson(element.value);
|
if (element.type === 'devp' && value) {
|
var station = value.stationId != null ? String(value.stationId) : '';
|
var arrows = formatDirectionArrows(value.direction);
|
if (station && arrows) {
|
return element.height > element.width * 1.15 ? (station + '\n' + arrows) : (station + ' ' + arrows);
|
}
|
if (station) {
|
return station;
|
}
|
if (arrows) {
|
return arrows;
|
}
|
return meta.shortLabel;
|
}
|
if ((element.type === 'crn' || element.type === 'dualCrn' || element.type === 'rgv') && value) {
|
if (value.deviceNo != null) {
|
return meta.shortLabel + ' ' + value.deviceNo;
|
}
|
if (value.crnNo != null) {
|
return meta.shortLabel + ' ' + value.crnNo;
|
}
|
if (value.rgvNo != null) {
|
return meta.shortLabel + ' ' + value.rgvNo;
|
}
|
}
|
if (element.value && element.value.length <= 18 && element.value.indexOf('{') !== 0) {
|
return element.value;
|
}
|
return meta.shortLabel;
|
},
|
ensureLabelSprite: function (index) {
|
if (this.labelPool[index]) {
|
return this.labelPool[index];
|
}
|
var label = new PIXI.Text('', {
|
fontFamily: 'Avenir Next, PingFang SC, Microsoft YaHei, sans-serif',
|
fontSize: 12,
|
fontWeight: '600',
|
fill: 0x223448,
|
align: 'center'
|
});
|
label.anchor.set(0.5);
|
this.labelLayer.addChild(label);
|
this.labelPool[index] = label;
|
return label;
|
},
|
getLabelRenderBudget: function () {
|
if (!this.pixiApp || !this.pixiApp.renderer) {
|
return MIN_LABEL_COUNT;
|
}
|
var renderer = this.pixiApp.renderer;
|
var viewportArea = renderer.width * renderer.height;
|
return clamp(Math.round(viewportArea / 12000), MIN_LABEL_COUNT, MAX_LABEL_COUNT);
|
},
|
getLabelMinScreenWidth: function (text) {
|
var lines = String(text || '').split('\n');
|
var length = 0;
|
for (var i = 0; i < lines.length; i++) {
|
length = Math.max(length, String(lines[i] || '').trim().length);
|
}
|
if (length <= 4) {
|
return 26;
|
}
|
if (length <= 8) {
|
return 40;
|
}
|
if (length <= 12) {
|
return 52;
|
}
|
return 64;
|
},
|
getLabelMinScreenHeight: function (text) {
|
var lines = String(text || '').split('\n');
|
var length = 0;
|
for (var i = 0; i < lines.length; i++) {
|
length = Math.max(length, String(lines[i] || '').trim().length);
|
}
|
var lineHeight = length <= 4 ? 14 : 18;
|
return lineHeight * Math.max(lines.length, 1);
|
},
|
renderLabels: function () {
|
if (!this.doc) {
|
return;
|
}
|
if (!SHOW_CANVAS_ELEMENT_LABELS) {
|
this.labelLayer.visible = false;
|
for (var hiddenIdx = 0; hiddenIdx < this.labelPool.length; hiddenIdx++) {
|
this.labelPool[hiddenIdx].visible = false;
|
}
|
return;
|
}
|
var capability = this.ensureLabelCapability();
|
if (capability.maxWidth * this.camera.scale < ABS_MIN_LABEL_SCREEN_WIDTH
|
|| capability.maxHeight * this.camera.scale < ABS_MIN_LABEL_SCREEN_HEIGHT) {
|
this.labelLayer.visible = false;
|
return;
|
}
|
if (this.isZooming || this.isPanning || this.camera.scale < MIN_LABEL_SCALE
|
|| (this.interactionState && (this.interactionState.type === 'move' || this.interactionState.type === 'resize' || this.interactionState.type === 'pan'))) {
|
this.labelLayer.visible = false;
|
return;
|
}
|
this.labelLayer.visible = true;
|
var visible = this.getVisibleWorldRect();
|
var elements = this.querySpatialCandidates(visible, 0, []);
|
if (elements.length > DENSE_LABEL_HIDE_ELEMENT_THRESHOLD && this.camera.scale < DENSE_LABEL_HIDE_SCALE_THRESHOLD) {
|
this.labelLayer.visible = false;
|
return;
|
}
|
var hasRoomForAnyLabel = false;
|
for (var roomIdx = 0; roomIdx < elements.length; roomIdx++) {
|
var candidate = elements[roomIdx];
|
if (candidate.width * this.camera.scale >= ABS_MIN_LABEL_SCREEN_WIDTH
|
&& candidate.height * this.camera.scale >= ABS_MIN_LABEL_SCREEN_HEIGHT) {
|
hasRoomForAnyLabel = true;
|
break;
|
}
|
}
|
if (!hasRoomForAnyLabel) {
|
this.labelLayer.visible = false;
|
return;
|
}
|
var visibleElements = [];
|
for (var i = 0; i < elements.length; i++) {
|
var element = elements[i];
|
var text = this.getLabelText(element);
|
if (!text) {
|
continue;
|
}
|
if (!rectIntersects(visible, element)) {
|
continue;
|
}
|
if (element.width * this.camera.scale < this.getLabelMinScreenWidth(text) || element.height * this.camera.scale < this.getLabelMinScreenHeight(text)) {
|
continue;
|
}
|
visibleElements.push({
|
element: element,
|
text: text
|
});
|
}
|
visibleElements.sort(function (a, b) {
|
return (b.element.width * b.element.height) - (a.element.width * a.element.height);
|
});
|
var labelBudget = this.getLabelRenderBudget();
|
if (visibleElements.length > labelBudget) {
|
visibleElements = visibleElements.slice(0, labelBudget);
|
}
|
for (var j = 0; j < visibleElements.length; j++) {
|
var item = visibleElements[j].element;
|
var label = this.ensureLabelSprite(j);
|
label.visible = true;
|
label.text = visibleElements[j].text;
|
label.position.set(item.x + item.width / 2, item.y + item.height / 2);
|
label.scale.set(1 / this.camera.scale, 1 / this.camera.scale);
|
label.alpha = this.selectedIds.indexOf(item.id) >= 0 ? 1 : 0.88;
|
}
|
for (var k = visibleElements.length; k < this.labelPool.length; k++) {
|
this.labelPool[k].visible = false;
|
}
|
},
|
renderHover: function () {
|
this.hoverLayer.clear();
|
if (this.interactionState || !this.hoverElementId || this.selectedIds.indexOf(this.hoverElementId) >= 0) {
|
return;
|
}
|
var element = this.findElementById(this.hoverElementId);
|
if (!element) {
|
return;
|
}
|
var lineWidth = 2 / this.camera.scale;
|
this.hoverLayer.lineStyle(lineWidth, 0x2f79d6, 0.95);
|
this.hoverLayer.drawRoundedRect(element.x, element.y, element.width, element.height, Math.max(6 / this.camera.scale, 2));
|
},
|
renderSelection: function () {
|
this.selectionLayer.clear();
|
if (!this.selectedIds.length || (this.interactionState && (this.interactionState.type === 'move' || this.interactionState.type === 'resize'))) {
|
return;
|
}
|
var elements = this.getSelectedElements();
|
var lineWidth = 2 / this.camera.scale;
|
for (var i = 0; i < elements.length; i++) {
|
var element = elements[i];
|
this.selectionLayer.lineStyle(lineWidth, 0x2568b8, 1);
|
this.selectionLayer.beginFill(0x2f79d6, 0.07);
|
this.selectionLayer.drawRoundedRect(element.x, element.y, element.width, element.height, Math.max(6 / this.camera.scale, 2));
|
this.selectionLayer.endFill();
|
}
|
if (elements.length !== 1) {
|
return;
|
}
|
var handleSize = HANDLE_SCREEN_SIZE / this.camera.scale;
|
var handlePositions = this.getHandlePositions(elements[0]);
|
this.selectionLayer.lineStyle(1 / this.camera.scale, 0x1d5ea9, 1);
|
this.selectionLayer.beginFill(0xffffff, 1);
|
for (var key in handlePositions) {
|
if (!handlePositions.hasOwnProperty(key)) {
|
continue;
|
}
|
var pos = handlePositions[key];
|
this.selectionLayer.drawRect(pos.x - handleSize / 2, pos.y - handleSize / 2, handleSize, handleSize);
|
}
|
this.selectionLayer.endFill();
|
},
|
renderGuide: function () {
|
this.guideLayer.clear();
|
if (this.guideText) {
|
this.guideText.visible = false;
|
}
|
if (!this.interactionState) {
|
return;
|
}
|
var state = this.interactionState;
|
if (state.type === 'draw' && state.rect && state.rect.width > 0 && state.rect.height > 0) {
|
var drawMeta = getTypeMeta(state.elementType);
|
this.guideLayer.lineStyle(2 / this.camera.scale, drawMeta.border, 0.95);
|
this.guideLayer.beginFill(drawMeta.fill, 0.18);
|
this.guideLayer.drawRoundedRect(state.rect.x, state.rect.y, state.rect.width, state.rect.height, Math.max(6 / this.camera.scale, 2));
|
this.guideLayer.endFill();
|
return;
|
}
|
if (state.type === 'array' && state.template) {
|
var previewItems = state.previewItems || [];
|
var arrayMeta = getTypeMeta(state.template.type);
|
var lineWidth = 2 / this.camera.scale;
|
var templateCenterX = state.template.x + state.template.width / 2;
|
var templateCenterY = state.template.y + state.template.height / 2;
|
this.guideLayer.lineStyle(lineWidth, arrayMeta.border, 0.9);
|
this.guideLayer.moveTo(templateCenterX, templateCenterY);
|
this.guideLayer.lineTo(state.currentWorld.x, state.currentWorld.y);
|
if (!previewItems.length) {
|
return;
|
}
|
this.guideLayer.lineStyle(1 / this.camera.scale, arrayMeta.border, 0.8);
|
this.guideLayer.beginFill(arrayMeta.fill, 0.2);
|
for (var previewIndex = 0; previewIndex < previewItems.length; previewIndex++) {
|
var preview = previewItems[previewIndex];
|
this.guideLayer.drawRoundedRect(preview.x, preview.y, preview.width, preview.height, Math.max(6 / this.camera.scale, 2));
|
}
|
this.guideLayer.endFill();
|
if (this.guideText) {
|
this.guideText.text = '将生成 ' + previewItems.length + ' 个';
|
this.guideText.position.set(state.currentWorld.x, state.currentWorld.y - 10 / this.camera.scale);
|
this.guideText.scale.set(1 / this.camera.scale);
|
this.guideText.visible = true;
|
}
|
return;
|
}
|
if (state.type === 'marquee') {
|
var rect = buildRectFromPoints(state.startWorld, state.currentWorld);
|
if (rect.width <= 0 || rect.height <= 0) {
|
return;
|
}
|
this.guideLayer.lineStyle(2 / this.camera.scale, 0x2f79d6, 0.92);
|
this.guideLayer.beginFill(0x2f79d6, 0.06);
|
this.guideLayer.drawRect(rect.x, rect.y, rect.width, rect.height);
|
this.guideLayer.endFill();
|
}
|
},
|
pointerToWorld: function (event) {
|
var rect = this.pixiApp.view.getBoundingClientRect();
|
var screenX = event.clientX - rect.left;
|
var screenY = event.clientY - rect.top;
|
return {
|
screenX: screenX,
|
screenY: screenY,
|
x: roundCoord((screenX - this.camera.x) / this.camera.scale),
|
y: roundCoord((screenY - this.camera.y) / this.camera.scale)
|
};
|
},
|
isWithinCanvas: function (rect) {
|
if (!this.doc) {
|
return false;
|
}
|
return rect.x >= -COORD_EPSILON && rect.y >= -COORD_EPSILON
|
&& rect.x + rect.width <= this.doc.canvasWidth + COORD_EPSILON
|
&& rect.y + rect.height <= this.doc.canvasHeight + COORD_EPSILON;
|
},
|
canPlaceElements: function (elements, excludeIds) {
|
excludeIds = excludeIds || [];
|
for (var i = 0; i < elements.length; i++) {
|
if (!this.isWithinCanvas(elements[i])) {
|
return false;
|
}
|
if (this.hasOverlap(elements[i], excludeIds.concat([elements[i].id]))) {
|
return false;
|
}
|
}
|
return true;
|
},
|
hasOverlap: function (candidate, excludeIds) {
|
if (!this.doc) {
|
return false;
|
}
|
var elements = this.querySpatialCandidates(candidate, COORD_EPSILON, excludeIds);
|
for (var i = 0; i < elements.length; i++) {
|
var item = elements[i];
|
if (rectsOverlap(candidate, item)) {
|
return true;
|
}
|
}
|
return false;
|
},
|
snapToleranceWorld: function () {
|
return Math.max(1, EDGE_SNAP_SCREEN_TOLERANCE / this.camera.scale);
|
},
|
collectMoveSnap: function (baseItems, dx, dy, excludeIds) {
|
if (!this.doc || !baseItems || !baseItems.length) {
|
return { dx: 0, dy: 0 };
|
}
|
var tolerance = this.snapToleranceWorld();
|
var bestDx = null;
|
var bestDy = null;
|
for (var i = 0; i < baseItems.length; i++) {
|
var moving = baseItems[i];
|
var movedLeft = moving.x + dx;
|
var movedRight = movedLeft + moving.width;
|
var movedTop = moving.y + dy;
|
var movedBottom = movedTop + moving.height;
|
var candidates = this.querySpatialCandidates({
|
x: movedLeft,
|
y: movedTop,
|
width: moving.width,
|
height: moving.height
|
}, tolerance, excludeIds);
|
for (var j = 0; j < candidates.length; j++) {
|
var other = candidates[j];
|
var otherLeft = other.x;
|
var otherRight = other.x + other.width;
|
var otherTop = other.y;
|
var otherBottom = other.y + other.height;
|
if (rangesNearOrOverlap(movedTop, movedBottom, otherTop, otherBottom, tolerance)) {
|
var horizontalCandidates = [
|
otherLeft - movedRight,
|
otherRight - movedLeft,
|
otherLeft - movedLeft,
|
otherRight - movedRight
|
];
|
for (var hx = 0; hx < horizontalCandidates.length; hx++) {
|
var deltaX = horizontalCandidates[hx];
|
if (Math.abs(deltaX) <= tolerance && (bestDx === null || Math.abs(deltaX) < Math.abs(bestDx))) {
|
bestDx = deltaX;
|
}
|
}
|
}
|
if (rangesNearOrOverlap(movedLeft, movedRight, otherLeft, otherRight, tolerance)) {
|
var verticalCandidates = [
|
otherTop - movedBottom,
|
otherBottom - movedTop,
|
otherTop - movedTop,
|
otherBottom - movedBottom
|
];
|
for (var vy = 0; vy < verticalCandidates.length; vy++) {
|
var deltaY = verticalCandidates[vy];
|
if (Math.abs(deltaY) <= tolerance && (bestDy === null || Math.abs(deltaY) < Math.abs(bestDy))) {
|
bestDy = deltaY;
|
}
|
}
|
}
|
}
|
}
|
return {
|
dx: bestDx == null ? 0 : bestDx,
|
dy: bestDy == null ? 0 : bestDy
|
};
|
},
|
collectResizeSnap: function (rect, handle, excludeIds) {
|
if (!this.doc || !rect) {
|
return null;
|
}
|
var tolerance = this.snapToleranceWorld();
|
var left = rect.x;
|
var right = rect.x + rect.width;
|
var top = rect.y;
|
var bottom = rect.y + rect.height;
|
var bestLeft = null;
|
var bestRight = null;
|
var bestTop = null;
|
var bestBottom = null;
|
function pickBest(current, candidate) {
|
if (candidate == null) {
|
return current;
|
}
|
if (current == null || Math.abs(candidate) < Math.abs(current)) {
|
return candidate;
|
}
|
return current;
|
}
|
if (handle.indexOf('w') >= 0) {
|
bestLeft = pickBest(bestLeft, -left);
|
}
|
if (handle.indexOf('e') >= 0) {
|
bestRight = pickBest(bestRight, this.doc.canvasWidth - right);
|
}
|
if (handle.indexOf('n') >= 0) {
|
bestTop = pickBest(bestTop, -top);
|
}
|
if (handle.indexOf('s') >= 0) {
|
bestBottom = pickBest(bestBottom, this.doc.canvasHeight - bottom);
|
}
|
var elements = this.querySpatialCandidates(rect, tolerance, excludeIds);
|
for (var i = 0; i < elements.length; i++) {
|
var other = elements[i];
|
var otherLeft = other.x;
|
var otherRight = other.x + other.width;
|
var otherTop = other.y;
|
var otherBottom = other.y + other.height;
|
if (rangesNearOrOverlap(top, bottom, otherTop, otherBottom, tolerance)) {
|
if (handle.indexOf('w') >= 0) {
|
bestLeft = pickBest(bestLeft, otherLeft - left);
|
bestLeft = pickBest(bestLeft, otherRight - left);
|
}
|
if (handle.indexOf('e') >= 0) {
|
bestRight = pickBest(bestRight, otherLeft - right);
|
bestRight = pickBest(bestRight, otherRight - right);
|
}
|
}
|
if (rangesNearOrOverlap(left, right, otherLeft, otherRight, tolerance)) {
|
if (handle.indexOf('n') >= 0) {
|
bestTop = pickBest(bestTop, otherTop - top);
|
bestTop = pickBest(bestTop, otherBottom - top);
|
}
|
if (handle.indexOf('s') >= 0) {
|
bestBottom = pickBest(bestBottom, otherTop - bottom);
|
bestBottom = pickBest(bestBottom, otherBottom - bottom);
|
}
|
}
|
}
|
if (bestLeft != null && Math.abs(bestLeft) > tolerance) {
|
bestLeft = null;
|
}
|
if (bestRight != null && Math.abs(bestRight) > tolerance) {
|
bestRight = null;
|
}
|
if (bestTop != null && Math.abs(bestTop) > tolerance) {
|
bestTop = null;
|
}
|
if (bestBottom != null && Math.abs(bestBottom) > tolerance) {
|
bestBottom = null;
|
}
|
return {
|
left: bestLeft,
|
right: bestRight,
|
top: bestTop,
|
bottom: bestBottom
|
};
|
},
|
hitTestElement: function (point) {
|
if (!this.doc) {
|
return null;
|
}
|
var candidates = this.querySpatialCandidates({
|
x: point.x,
|
y: point.y,
|
width: 0,
|
height: 0
|
}, 0, []);
|
if (!candidates.length) {
|
return null;
|
}
|
var candidateMap = {};
|
for (var c = 0; c < candidates.length; c++) {
|
candidateMap[candidates[c].id] = true;
|
}
|
var elements = this.doc.elements || [];
|
for (var i = elements.length - 1; i >= 0; i--) {
|
var element = elements[i];
|
if (!candidateMap[element.id]) {
|
continue;
|
}
|
if (point.x >= element.x && point.x <= element.x + element.width
|
&& point.y >= element.y && point.y <= element.y + element.height) {
|
return element;
|
}
|
}
|
return null;
|
},
|
getHandlePositions: function (element) {
|
var x = element.x;
|
var y = element.y;
|
var w = element.width;
|
var h = element.height;
|
var cx = x + w / 2;
|
var cy = y + h / 2;
|
return {
|
nw: { x: x, y: y },
|
n: { x: cx, y: y },
|
ne: { x: x + w, y: y },
|
e: { x: x + w, y: cy },
|
se: { x: x + w, y: y + h },
|
s: { x: cx, y: y + h },
|
sw: { x: x, y: y + h },
|
w: { x: x, y: cy }
|
};
|
},
|
getResizeHandleAt: function (point, element) {
|
var handlePositions = this.getHandlePositions(element);
|
var baseTolerance = HANDLE_SCREEN_SIZE / this.camera.scale;
|
var sizeLimitedTolerance = Math.max(Math.min(element.width, element.height) / 4, 3 / this.camera.scale);
|
var tolerance = Math.min(baseTolerance, sizeLimitedTolerance);
|
var bestHandle = '';
|
var bestDistance = Infinity;
|
for (var key in handlePositions) {
|
if (!handlePositions.hasOwnProperty(key)) {
|
continue;
|
}
|
var pos = handlePositions[key];
|
var dx = Math.abs(point.x - pos.x);
|
var dy = Math.abs(point.y - pos.y);
|
if (dx <= tolerance && dy <= tolerance) {
|
var distance = dx + dy;
|
if (distance < bestDistance) {
|
bestDistance = distance;
|
bestHandle = key;
|
}
|
}
|
}
|
return bestHandle;
|
},
|
cursorForHandle: function (handle) {
|
if (handle === 'nw' || handle === 'se') {
|
return 'nwse-resize';
|
}
|
if (handle === 'ne' || handle === 'sw') {
|
return 'nesw-resize';
|
}
|
if (handle === 'n' || handle === 's') {
|
return 'ns-resize';
|
}
|
if (handle === 'e' || handle === 'w') {
|
return 'ew-resize';
|
}
|
return 'default';
|
},
|
updateCursor: function () {
|
if (!this.pixiApp) {
|
return;
|
}
|
var cursor = 'default';
|
if (this.interactionState) {
|
if (this.interactionState.type === 'pan') {
|
cursor = 'grabbing';
|
} else if (this.interactionState.type === 'draw' || this.interactionState.type === 'marquee') {
|
cursor = 'crosshair';
|
} else if (this.interactionState.type === 'array') {
|
cursor = 'crosshair';
|
} else if (this.interactionState.type === 'move') {
|
cursor = 'move';
|
} else if (this.interactionState.type === 'movePending') {
|
cursor = 'grab';
|
} else if (this.interactionState.type === 'resize') {
|
cursor = this.cursorForHandle(this.interactionState.handle);
|
}
|
} else if (this.spacePressed || this.activeTool === 'pan') {
|
cursor = 'grab';
|
} else if (DRAW_TYPES.indexOf(this.activeTool) >= 0 || this.activeTool === 'marquee' || this.activeTool === 'array') {
|
cursor = 'crosshair';
|
} else if (this.singleSelectedElement) {
|
var point = this.lastPointerWorld || null;
|
if (point) {
|
var handle = this.getResizeHandleAt(point, this.singleSelectedElement);
|
cursor = handle ? this.cursorForHandle(handle) : 'default';
|
}
|
if (cursor === 'default' && this.hoverElementId) {
|
cursor = 'move';
|
} else if (cursor === 'default') {
|
cursor = 'grab';
|
}
|
} else {
|
cursor = this.hoverElementId ? 'move' : 'grab';
|
}
|
if (cursor !== this.lastCursor) {
|
this.lastCursor = cursor;
|
this.pixiApp.view.style.cursor = cursor;
|
}
|
},
|
startPan: function (point) {
|
this.cancelDeferredStaticRebuild();
|
this.cancelPanRefresh();
|
if (this.zoomRefreshTimer) {
|
window.clearTimeout(this.zoomRefreshTimer);
|
this.zoomRefreshTimer = null;
|
this.isZooming = false;
|
this.pendingViewportRefresh = true;
|
}
|
this.isPanning = true;
|
this.interactionState = {
|
type: 'pan',
|
startScreen: {
|
x: point.screenX,
|
y: point.screenY
|
},
|
startCamera: {
|
x: this.camera.x,
|
y: this.camera.y
|
}
|
};
|
this.updateCursor();
|
},
|
startMarquee: function (point, additive) {
|
this.cancelDeferredStaticRebuild();
|
this.interactionState = {
|
type: 'marquee',
|
additive: !!additive,
|
startWorld: { x: point.x, y: point.y },
|
currentWorld: { x: point.x, y: point.y }
|
};
|
this.updateCursor();
|
},
|
startDraw: function (point) {
|
this.cancelDeferredStaticRebuild();
|
this.interactionState = {
|
type: 'draw',
|
beforeSnapshot: this.snapshotDoc(this.doc),
|
elementType: this.activeTool,
|
startWorld: { x: point.x, y: point.y },
|
rect: { x: point.x, y: point.y, width: 0, height: 0 }
|
};
|
this.updateCursor();
|
},
|
startArray: function (point, element) {
|
if (!this.canArrayFromElement(element)) {
|
this.showMessage('warning', '阵列工具当前只支持货架、CRN、双工位和 RGV');
|
return;
|
}
|
this.cancelDeferredStaticRebuild();
|
this.interactionState = {
|
type: 'array',
|
beforeSnapshot: this.snapshotDoc(this.doc),
|
template: {
|
id: element.id,
|
type: element.type,
|
x: element.x,
|
y: element.y,
|
width: element.width,
|
height: element.height,
|
value: element.value
|
},
|
startWorld: { x: point.x, y: point.y },
|
currentWorld: { x: point.x, y: point.y },
|
previewItems: []
|
};
|
this.updateCursor();
|
},
|
startMove: function (point) {
|
var selected = this.getSelectedElements();
|
if (!selected.length) {
|
return;
|
}
|
this.cancelDeferredStaticRebuild();
|
var baseItems = selected.map(function (item) {
|
return {
|
id: item.id,
|
x: item.x,
|
y: item.y,
|
width: item.width,
|
height: item.height,
|
value: item.value,
|
type: item.type
|
};
|
});
|
this.interactionState = {
|
type: 'movePending',
|
beforeSnapshot: this.snapshotDoc(this.doc),
|
startScreen: { x: point.screenX, y: point.screenY },
|
startWorld: { x: point.x, y: point.y },
|
baseItems: baseItems
|
};
|
this.updateCursor();
|
},
|
startResize: function (point, element, handle) {
|
this.cancelDeferredStaticRebuild();
|
this.interactionState = {
|
type: 'resize',
|
handle: handle,
|
elementId: element.id,
|
beforeSnapshot: this.snapshotDoc(this.doc),
|
baseRect: {
|
x: element.x,
|
y: element.y,
|
width: element.width,
|
height: element.height
|
}
|
};
|
this.markStaticSceneDirty();
|
this.scheduleRender();
|
this.updateCursor();
|
},
|
onCanvasPointerDown: function (event) {
|
if (!this.doc || !this.pixiApp) {
|
return;
|
}
|
if (event.button !== 0 && event.button !== 1) {
|
return;
|
}
|
if (this.pixiApp.view.setPointerCapture && event.pointerId != null) {
|
try {
|
this.pixiApp.view.setPointerCapture(event.pointerId);
|
} catch (ignore) {
|
}
|
}
|
this.currentPointerId = event.pointerId;
|
var point = this.pointerToWorld(event);
|
this.lastPointerWorld = point;
|
this.pointerStatus = this.formatNumber(point.x) + ', ' + this.formatNumber(point.y);
|
if (this.spacePressed || this.activeTool === 'pan' || event.button === 1) {
|
this.startPan(point);
|
return;
|
}
|
if (DRAW_TYPES.indexOf(this.activeTool) >= 0) {
|
this.startDraw(point);
|
return;
|
}
|
if (this.activeTool === 'marquee') {
|
this.startMarquee(point, event.shiftKey);
|
return;
|
}
|
if (this.activeTool === 'array') {
|
var arrayHit = this.hitTestElement(point);
|
var arrayTemplate = arrayHit || this.singleSelectedElement;
|
if (arrayHit && this.selectedIds.indexOf(arrayHit.id) < 0) {
|
this.setSelectedIds([arrayHit.id]);
|
arrayTemplate = arrayHit;
|
}
|
if (!arrayTemplate) {
|
this.showMessage('warning', '请先选中一个货架或轨道作为阵列模板');
|
return;
|
}
|
this.startArray(point, arrayTemplate);
|
return;
|
}
|
|
var selected = this.singleSelectedElement;
|
var handle = selected ? this.getResizeHandleAt(point, selected) : '';
|
if (handle) {
|
this.startResize(point, selected, handle);
|
return;
|
}
|
|
var hit = this.hitTestElement(point);
|
if (hit) {
|
if (event.shiftKey) {
|
var index = this.selectedIds.indexOf(hit.id);
|
if (index >= 0) {
|
var nextIds = this.selectedIds.slice();
|
nextIds.splice(index, 1);
|
this.setSelectedIds(nextIds);
|
} else {
|
this.setSelectedIds(this.selectedIds.concat([hit.id]));
|
}
|
this.scheduleRender();
|
return;
|
}
|
if (this.selectedIds.indexOf(hit.id) < 0) {
|
this.setSelectedIds([hit.id]);
|
this.scheduleRender();
|
}
|
this.startMove(point);
|
return;
|
}
|
|
if (this.selectedIds.length) {
|
this.setSelectedIds([]);
|
this.scheduleRender();
|
}
|
this.startPan(point);
|
},
|
onCanvasWheel: function (event) {
|
if (!this.pixiApp || !this.doc) {
|
return;
|
}
|
event.preventDefault();
|
var point = this.pointerToWorld(event);
|
var delta = event.deltaY < 0 ? 1.12 : 0.89;
|
var nextScale = clamp(this.camera.scale * delta, 0.06, 4);
|
this.camera.scale = nextScale;
|
this.camera.x = Math.round(point.screenX - point.x * nextScale);
|
this.camera.y = Math.round(point.screenY - point.y * nextScale);
|
this.viewZoom = nextScale;
|
this.scheduleZoomRefresh();
|
this.scheduleRender();
|
},
|
onWindowPointerMove: function (event) {
|
if (!this.pixiApp || !this.doc) {
|
return;
|
}
|
var point = this.pointerToWorld(event);
|
this.lastPointerWorld = point;
|
var pointerText = this.formatNumber(point.x) + ', ' + this.formatNumber(point.y);
|
var now = (window.performance && performance.now) ? performance.now() : Date.now();
|
if (pointerText !== this.pointerStatus && (now - this.lastPointerStatusUpdateTs >= POINTER_STATUS_UPDATE_INTERVAL || this.pointerStatus === '--')) {
|
this.pointerStatus = pointerText;
|
this.lastPointerStatusUpdateTs = now;
|
}
|
if (!this.interactionState) {
|
var hover = this.hitTestElement(point);
|
var hoverId = hover ? hover.id : '';
|
if (hoverId !== this.hoverElementId) {
|
this.hoverElementId = hoverId;
|
this.scheduleRender();
|
}
|
this.updateCursor();
|
return;
|
}
|
|
var state = this.interactionState;
|
if (state.type === 'pan') {
|
this.camera.x = Math.round(state.startCamera.x + (point.screenX - state.startScreen.x));
|
this.camera.y = Math.round(state.startCamera.y + (point.screenY - state.startScreen.y));
|
this.scheduleRender();
|
return;
|
}
|
|
if (state.type === 'marquee') {
|
state.currentWorld = { x: point.x, y: point.y };
|
this.scheduleRender();
|
return;
|
}
|
|
if (state.type === 'draw') {
|
var rawRect = buildRectFromPoints(state.startWorld, point);
|
var clipped = {
|
x: clamp(rawRect.x, 0, this.doc.canvasWidth),
|
y: clamp(rawRect.y, 0, this.doc.canvasHeight),
|
width: clamp(rawRect.width, 0, this.doc.canvasWidth),
|
height: clamp(rawRect.height, 0, this.doc.canvasHeight)
|
};
|
if (clipped.x + clipped.width > this.doc.canvasWidth) {
|
clipped.width = roundCoord(this.doc.canvasWidth - clipped.x);
|
}
|
if (clipped.y + clipped.height > this.doc.canvasHeight) {
|
clipped.height = roundCoord(this.doc.canvasHeight - clipped.y);
|
}
|
state.rect = clipped;
|
this.scheduleRender();
|
return;
|
}
|
if (state.type === 'array') {
|
state.currentWorld = { x: point.x, y: point.y };
|
state.previewItems = this.buildArrayCopies(state.template, state.startWorld, state.currentWorld);
|
this.scheduleRender();
|
return;
|
}
|
|
if (state.type === 'movePending') {
|
var dragDistance = Math.max(Math.abs(point.screenX - state.startScreen.x), Math.abs(point.screenY - state.startScreen.y));
|
if (dragDistance < DRAG_START_THRESHOLD) {
|
return;
|
}
|
state.type = 'move';
|
this.markStaticSceneDirty();
|
this.scheduleRender();
|
this.updateCursor();
|
}
|
|
if (state.type === 'move') {
|
var dx = point.x - state.startWorld.x;
|
var dy = point.y - state.startWorld.y;
|
var minDx = -Infinity;
|
var maxDx = Infinity;
|
var minDy = -Infinity;
|
var maxDy = Infinity;
|
for (var i = 0; i < state.baseItems.length; i++) {
|
var base = state.baseItems[i];
|
minDx = Math.max(minDx, -base.x);
|
minDy = Math.max(minDy, -base.y);
|
maxDx = Math.min(maxDx, this.doc.canvasWidth - (base.x + base.width));
|
maxDy = Math.min(maxDy, this.doc.canvasHeight - (base.y + base.height));
|
}
|
dx = clamp(dx, minDx, maxDx);
|
dy = clamp(dy, minDy, maxDy);
|
var snapDelta = this.collectMoveSnap(state.baseItems, dx, dy, this.selectedIds.slice());
|
dx = clamp(dx + snapDelta.dx, minDx, maxDx);
|
dy = clamp(dy + snapDelta.dy, minDy, maxDy);
|
for (var j = 0; j < state.baseItems.length; j++) {
|
var baseItem = state.baseItems[j];
|
var element = this.findElementById(baseItem.id);
|
if (!element) {
|
continue;
|
}
|
element.x = roundCoord(baseItem.x + dx);
|
element.y = roundCoord(baseItem.y + dy);
|
}
|
this.scheduleRender();
|
return;
|
}
|
|
if (state.type === 'resize') {
|
var target = this.findElementById(state.elementId);
|
if (!target) {
|
return;
|
}
|
var baseRect = state.baseRect;
|
var left = baseRect.x;
|
var right = baseRect.x + baseRect.width;
|
var top = baseRect.y;
|
var bottom = baseRect.y + baseRect.height;
|
if (state.handle.indexOf('w') >= 0) {
|
left = clamp(point.x, 0, right - MIN_ELEMENT_SIZE);
|
}
|
if (state.handle.indexOf('e') >= 0) {
|
right = clamp(point.x, left + MIN_ELEMENT_SIZE, this.doc.canvasWidth);
|
}
|
if (state.handle.indexOf('n') >= 0) {
|
top = clamp(point.y, 0, bottom - MIN_ELEMENT_SIZE);
|
}
|
if (state.handle.indexOf('s') >= 0) {
|
bottom = clamp(point.y, top + MIN_ELEMENT_SIZE, this.doc.canvasHeight);
|
}
|
var snapped = this.collectResizeSnap({
|
x: left,
|
y: top,
|
width: right - left,
|
height: bottom - top
|
}, state.handle, [target.id]);
|
if (snapped) {
|
if (state.handle.indexOf('w') >= 0 && snapped.left != null) {
|
left = clamp(left + snapped.left, 0, right - MIN_ELEMENT_SIZE);
|
}
|
if (state.handle.indexOf('e') >= 0 && snapped.right != null) {
|
right = clamp(right + snapped.right, left + MIN_ELEMENT_SIZE, this.doc.canvasWidth);
|
}
|
if (state.handle.indexOf('n') >= 0 && snapped.top != null) {
|
top = clamp(top + snapped.top, 0, bottom - MIN_ELEMENT_SIZE);
|
}
|
if (state.handle.indexOf('s') >= 0 && snapped.bottom != null) {
|
bottom = clamp(bottom + snapped.bottom, top + MIN_ELEMENT_SIZE, this.doc.canvasHeight);
|
}
|
}
|
target.x = roundCoord(left);
|
target.y = roundCoord(top);
|
target.width = roundCoord(right - left);
|
target.height = roundCoord(bottom - top);
|
this.scheduleRender();
|
}
|
},
|
onWindowPointerUp: function (event) {
|
if (!this.interactionState) {
|
return;
|
}
|
if (this.currentPointerId != null && event.pointerId != null && this.currentPointerId !== event.pointerId) {
|
return;
|
}
|
if (this.pixiApp && this.pixiApp.view.releasePointerCapture && event.pointerId != null) {
|
try {
|
this.pixiApp.view.releasePointerCapture(event.pointerId);
|
} catch (ignore) {
|
}
|
}
|
this.currentPointerId = null;
|
|
var state = this.interactionState;
|
this.interactionState = null;
|
|
if (state.type === 'pan') {
|
this.updateCursor();
|
this.schedulePanRefresh();
|
this.scheduleRender();
|
return;
|
}
|
|
if (state.type === 'marquee') {
|
var rect = buildRectFromPoints(state.startWorld, state.currentWorld);
|
if (rect.width > 2 && rect.height > 2) {
|
var matched = (this.doc.elements || []).filter(function (item) {
|
return rectIntersects(rect, item);
|
}).map(function (item) {
|
return item.id;
|
});
|
this.setSelectedIds(state.additive ? Array.from(new Set(this.selectedIds.concat(matched))) : matched);
|
}
|
this.scheduleRender();
|
return;
|
}
|
|
if (state.type === 'movePending') {
|
this.updateCursor();
|
return;
|
}
|
|
if (state.type === 'draw') {
|
var drawRect = state.rect;
|
if (drawRect && drawRect.width >= MIN_ELEMENT_SIZE && drawRect.height >= MIN_ELEMENT_SIZE) {
|
var newElement = {
|
id: nextId(),
|
type: state.elementType,
|
x: roundCoord(drawRect.x),
|
y: roundCoord(drawRect.y),
|
width: roundCoord(drawRect.width),
|
height: roundCoord(drawRect.height),
|
value: ''
|
};
|
if (this.hasOverlap(newElement, [])) {
|
this.showMessage('warning', '新元素不能与已有元素重叠');
|
} else if (!this.isWithinCanvas(newElement)) {
|
this.showMessage('warning', '新元素超出画布范围');
|
} else {
|
this.doc.elements.push(newElement);
|
this.selectedIds = [newElement.id];
|
this.commitMutation(state.beforeSnapshot);
|
this.refreshInspector();
|
return;
|
}
|
}
|
this.refreshInspector();
|
this.scheduleRender();
|
return;
|
}
|
if (state.type === 'array') {
|
var copies = state.previewItems && state.previewItems.length
|
? state.previewItems
|
: this.buildArrayCopies(state.template, state.startWorld, state.currentWorld || state.startWorld);
|
if (!copies.length) {
|
this.scheduleRender();
|
return;
|
}
|
if (!this.canPlaceElements(copies, [])) {
|
this.showMessage('warning', '阵列生成后会重叠或超出画布,已取消');
|
this.scheduleRender();
|
return;
|
}
|
var finalizedCopies = copies.map(function (item) {
|
return $.extend({}, item, { id: nextId() });
|
});
|
var self = this;
|
this.runMutation(function () {
|
self.doc.elements = self.doc.elements.concat(finalizedCopies);
|
self.selectedIds = [finalizedCopies[finalizedCopies.length - 1].id];
|
});
|
return;
|
}
|
|
if (state.type === 'move') {
|
var movedElements = this.getSelectedElements();
|
if (!this.canPlaceElements(movedElements, this.selectedIds.slice())) {
|
for (var i = 0; i < state.baseItems.length; i++) {
|
var base = state.baseItems[i];
|
var element = this.findElementById(base.id);
|
if (!element) {
|
continue;
|
}
|
element.x = base.x;
|
element.y = base.y;
|
}
|
this.showMessage('warning', '移动后会重叠或超出画布,已恢复');
|
this.refreshInspector();
|
this.scheduleRender();
|
return;
|
}
|
if (!this.commitMutation(state.beforeSnapshot)) {
|
this.markStaticSceneDirty();
|
this.scheduleRender();
|
}
|
return;
|
}
|
|
if (state.type === 'resize') {
|
var resized = this.findElementById(state.elementId);
|
if (resized) {
|
if (!this.isWithinCanvas(resized) || this.hasOverlap(resized, [resized.id])) {
|
resized.x = state.baseRect.x;
|
resized.y = state.baseRect.y;
|
resized.width = state.baseRect.width;
|
resized.height = state.baseRect.height;
|
this.showMessage('warning', '缩放后会重叠或超出画布,已恢复');
|
this.refreshInspector();
|
this.scheduleRender();
|
return;
|
}
|
}
|
if (!this.commitMutation(state.beforeSnapshot)) {
|
this.markStaticSceneDirty();
|
this.scheduleRender();
|
}
|
return;
|
}
|
|
this.scheduleRender();
|
},
|
onWindowKeyDown: function (event) {
|
if (event.key === ' ' && !isInputLike(event.target)) {
|
this.spacePressed = true;
|
this.updateCursor();
|
event.preventDefault();
|
}
|
if (!this.doc) {
|
return;
|
}
|
if (isInputLike(event.target)) {
|
return;
|
}
|
var ctrl = event.ctrlKey || event.metaKey;
|
if (event.key === 'Delete' || event.key === 'Backspace') {
|
event.preventDefault();
|
this.deleteSelection();
|
return;
|
}
|
if (ctrl && (event.key === 'z' || event.key === 'Z')) {
|
event.preventDefault();
|
if (event.shiftKey) {
|
this.redo();
|
} else {
|
this.undo();
|
}
|
return;
|
}
|
if (ctrl && (event.key === 'y' || event.key === 'Y')) {
|
event.preventDefault();
|
this.redo();
|
return;
|
}
|
if (ctrl && (event.key === 'c' || event.key === 'C')) {
|
event.preventDefault();
|
this.copySelection();
|
return;
|
}
|
if (ctrl && (event.key === 'v' || event.key === 'V')) {
|
event.preventDefault();
|
this.pasteClipboard();
|
return;
|
}
|
if (event.key === 'Escape') {
|
this.interactionState = null;
|
this.setSelectedIds([]);
|
this.hoverElementId = '';
|
this.scheduleRender();
|
}
|
},
|
onWindowKeyUp: function (event) {
|
if (event.key === ' ') {
|
this.spacePressed = false;
|
this.updateCursor();
|
}
|
},
|
onBeforeUnload: function (event) {
|
if (!this.isDirty) {
|
return;
|
}
|
event.preventDefault();
|
event.returnValue = '';
|
}
|
}
|
});
|
})();
|