You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
487 lines
20 KiB
487 lines
20 KiB
|
4 days ago
|
(function (global) {
|
||
|
|
'use strict';
|
||
|
|
var WorkbenchLayout = global.PWLayout;
|
||
|
|
if (!WorkbenchLayout) {
|
||
|
|
console.error('[调试工作台] layout-dock 需先加载 layout-core');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
WorkbenchLayout.prototype._beginDrag = function (panelId, ev) {
|
||
|
|
if (this.mode === 'compact') {
|
||
|
|
ev.preventDefault();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this._dragPanel = panelId;
|
||
|
|
this._hoverGroupCols = null;
|
||
|
|
this._setGameIframePassThrough(true);
|
||
|
|
ev.dataTransfer.setData('text/pw-panel', panelId);
|
||
|
|
ev.dataTransfer.effectAllowed = 'move';
|
||
|
|
if (this.dropOverlay) this.dropOverlay.hidden = true;
|
||
|
|
};
|
||
|
|
|
||
|
|
WorkbenchLayout.prototype._onDocDragEnd = function () {
|
||
|
|
var self = this;
|
||
|
|
this._setGameIframePassThrough(false);
|
||
|
|
// drop 常在 dragend 之后触发,延迟清理避免丢掉落点信息
|
||
|
|
setTimeout(function () {
|
||
|
|
self._dragPanel = null;
|
||
|
|
self._hoverTargetId = null;
|
||
|
|
self._hoverEdge = null;
|
||
|
|
self._hoverGroup = null;
|
||
|
|
self._hoverGroupCols = null;
|
||
|
|
if (self.dropOverlay) {
|
||
|
|
self.dropOverlay.hidden = true;
|
||
|
|
self.dropOverlay.className = 'pw-drop-overlay';
|
||
|
|
}
|
||
|
|
}, 0);
|
||
|
|
};
|
||
|
|
|
||
|
|
WorkbenchLayout.prototype._clearDragVisual = function () {
|
||
|
|
if (this.dropOverlay) {
|
||
|
|
this.dropOverlay.hidden = true;
|
||
|
|
this.dropOverlay.className = 'pw-drop-overlay';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
WorkbenchLayout.prototype._onDocDragOver = function (ev) {
|
||
|
|
if (!this._dragPanel) return;
|
||
|
|
// 按指针坐标几何命中(iframe/浮层下 ev.target 不可靠)
|
||
|
|
var info = this._findLeafAtClientPoint(ev.clientX, ev.clientY);
|
||
|
|
if (!info || !info.el) {
|
||
|
|
if (this.dropOverlay) this.dropOverlay.hidden = true;
|
||
|
|
this._hoverTargetId = null;
|
||
|
|
this._hoverEdge = null;
|
||
|
|
this._hoverGroup = null;
|
||
|
|
this._hoverGroupCols = null;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
ev.preventDefault();
|
||
|
|
this._updateDockHoverFromPoint(ev.clientX, ev.clientY, this._dragPanel, info, ev.target);
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 按客户区坐标找最深(面积最小)的 leaf;不依赖 elementFromPoint(游戏 iframe 会挡住)。
|
||
|
|
* @param {number} cx
|
||
|
|
* @param {number} cy
|
||
|
|
* @returns {{el:HTMLElement, node:object}|null}
|
||
|
|
*/
|
||
|
|
WorkbenchLayout.prototype._findLeafAtClientPoint = function (cx, cy) {
|
||
|
|
var best = null;
|
||
|
|
var bestArea = Infinity;
|
||
|
|
for (var id in this._leafEls) {
|
||
|
|
if (!Object.prototype.hasOwnProperty.call(this._leafEls, id)) continue;
|
||
|
|
var info = this._leafEls[id];
|
||
|
|
if (!info || !info.el || !info.node) continue;
|
||
|
|
var r = info.el.getBoundingClientRect();
|
||
|
|
if (cx < r.left || cx > r.right || cy < r.top || cy > r.bottom) continue;
|
||
|
|
var area = Math.max(1, r.width) * Math.max(1, r.height);
|
||
|
|
if (area < bestArea) {
|
||
|
|
bestArea = area;
|
||
|
|
best = info;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return best;
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 根据指针位置更新停靠悬停状态与蓝框。
|
||
|
|
* @param {number} cx
|
||
|
|
* @param {number} cy
|
||
|
|
* @param {string} panelId
|
||
|
|
* @param {{el:HTMLElement, node:object}|null} info
|
||
|
|
* @param {EventTarget=} hitTarget
|
||
|
|
*/
|
||
|
|
WorkbenchLayout.prototype._updateDockHoverFromPoint = function (cx, cy, panelId, info, hitTarget) {
|
||
|
|
if (!info || !info.el || !info.node) {
|
||
|
|
this._clearDragVisual();
|
||
|
|
this._hoverTargetId = null;
|
||
|
|
this._hoverEdge = null;
|
||
|
|
this._hoverGroup = null;
|
||
|
|
this._hoverGroupCols = null;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
var leafEl = info.el;
|
||
|
|
var rect = leafEl.getBoundingClientRect();
|
||
|
|
var x = (cx - rect.left) / Math.max(1, rect.width);
|
||
|
|
var y = (cy - rect.top) / Math.max(1, rect.height);
|
||
|
|
var edge = this._hitEdge(info.node, x, y, hitTarget || null);
|
||
|
|
// 仅工作区左右外缘才整树新建列(勿把任意 leaf 的左右边误当成整树)
|
||
|
|
var ws = this.workspace ? this.workspace.getBoundingClientRect() : null;
|
||
|
|
if (ws && ws.width > 40) {
|
||
|
|
var margin = Math.min(48, ws.width * 0.08);
|
||
|
|
var atRight = cx >= ws.right - margin;
|
||
|
|
var atLeft = cx <= ws.left + margin;
|
||
|
|
if (atRight || atLeft) {
|
||
|
|
var rootEdge = this._resolveWorkspaceSideDrop(atRight ? 'right' : 'left');
|
||
|
|
if (rootEdge) {
|
||
|
|
this._hoverTargetId = '__workspace__';
|
||
|
|
this._hoverEdge = rootEdge.edge;
|
||
|
|
this._hoverGroup = null;
|
||
|
|
this._hoverGroupCols = null;
|
||
|
|
this._paintDockHover(rootEdge, leafEl);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
var resolved = this._resolveDropTarget(info.node, edge);
|
||
|
|
this._hoverTargetId = resolved.node ? resolved.node.id : null;
|
||
|
|
this._hoverEdge = resolved.edge;
|
||
|
|
this._hoverGroup = resolved.group || null;
|
||
|
|
this._hoverGroupCols = (resolved.group && resolved.group.length)
|
||
|
|
? this._snapshotGroupColumns(resolved.group, panelId)
|
||
|
|
: null;
|
||
|
|
this._paintDockHover(resolved, leafEl);
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 工作区左右外缘 → 相对整棵树新建列。
|
||
|
|
* @param {string} edge
|
||
|
|
* @returns {{node:object, edge:string, group:null}|null}
|
||
|
|
*/
|
||
|
|
WorkbenchLayout.prototype._resolveWorkspaceSideDrop = function (edge) {
|
||
|
|
if (!this._tree) return null;
|
||
|
|
this._ensureId(this._tree);
|
||
|
|
// node 仅用于绘制蓝框;真正落点用 __workspace__ 哨兵,避免 cleanup 丢 id 后找不到
|
||
|
|
return { node: this._tree, edge: edge, group: null };
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 绘制停靠蓝框。
|
||
|
|
* @param {{node:object|null, edge:string, group:Array|null}} resolved
|
||
|
|
* @param {HTMLElement} fallbackEl
|
||
|
|
*/
|
||
|
|
WorkbenchLayout.prototype._paintDockHover = function (resolved, fallbackEl) {
|
||
|
|
if (resolved.group && resolved.group.length) {
|
||
|
|
this._showOverlayRect(this._edgeStripRect(this._unionNodeRects(resolved.group), resolved.edge), 'region', false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (resolved.node && resolved.node.type === 'split' && resolved.edge !== 'center') {
|
||
|
|
var splitInfo = this._nodeEls[resolved.node.id];
|
||
|
|
if (splitInfo && splitInfo.el) {
|
||
|
|
var sr = splitInfo.el.getBoundingClientRect();
|
||
|
|
var rootRect = this.root.getBoundingClientRect();
|
||
|
|
this._showOverlayRect(this._edgeStripRect({
|
||
|
|
left: sr.left - rootRect.left,
|
||
|
|
top: sr.top - rootRect.top,
|
||
|
|
width: sr.width,
|
||
|
|
height: sr.height,
|
||
|
|
}, resolved.edge), 'region', false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
var targetInfo = resolved.node ? this._nodeEls[resolved.node.id] : null;
|
||
|
|
var targetEl = targetInfo && targetInfo.el ? targetInfo.el : fallbackEl;
|
||
|
|
if (!targetEl && resolved.node === this._tree && this.workspace) {
|
||
|
|
targetEl = this.workspace;
|
||
|
|
}
|
||
|
|
if (!targetEl) {
|
||
|
|
this._clearDragVisual();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (resolved.node === this._tree && (resolved.edge === 'left' || resolved.edge === 'right')) {
|
||
|
|
var wr = this.workspace.getBoundingClientRect();
|
||
|
|
var rr = this.root.getBoundingClientRect();
|
||
|
|
this._showOverlayRect(this._edgeStripRect({
|
||
|
|
left: wr.left - rr.left,
|
||
|
|
top: wr.top - rr.top,
|
||
|
|
width: wr.width,
|
||
|
|
height: wr.height,
|
||
|
|
}, resolved.edge), 'region', false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this._showOverlay(targetEl, resolved.edge, resolved.node && resolved.node.type === 'game');
|
||
|
|
};
|
||
|
|
|
||
|
|
/** 取目标矩形某一侧的新建区预览条带 */
|
||
|
|
WorkbenchLayout.prototype._edgeStripRect = function (rect, edge) {
|
||
|
|
var r = { left: rect.left, top: rect.top, width: rect.width, height: rect.height };
|
||
|
|
var ratio = 0.36;
|
||
|
|
if (edge === 'bottom') {
|
||
|
|
r.top = rect.top + rect.height * (1 - ratio);
|
||
|
|
r.height = rect.height * ratio;
|
||
|
|
} else if (edge === 'top') {
|
||
|
|
r.height = rect.height * ratio;
|
||
|
|
} else if (edge === 'left') {
|
||
|
|
r.width = rect.width * ratio;
|
||
|
|
} else if (edge === 'right') {
|
||
|
|
r.left = rect.left + rect.width * (1 - ratio);
|
||
|
|
r.width = rect.width * ratio;
|
||
|
|
}
|
||
|
|
return r;
|
||
|
|
};
|
||
|
|
|
||
|
|
WorkbenchLayout.prototype._isGameNode = function (node) {
|
||
|
|
return !!(node && node.type === 'game');
|
||
|
|
};
|
||
|
|
|
||
|
|
WorkbenchLayout.prototype._splitHasGame = function (split) {
|
||
|
|
if (!split || split.type !== 'split') return false;
|
||
|
|
for (var i = 0; i < split.children.length; i++) {
|
||
|
|
if (this._isGameNode(split.children[i])) return true;
|
||
|
|
if (split.children[i].type === 'split' && this._splitHasGame(split.children[i])) return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 取横/竖排中与 leaf 相邻的连续「非游戏」兄弟,用于跨多列/多行建区且不吞掉游戏区。
|
||
|
|
*/
|
||
|
|
WorkbenchLayout.prototype._contiguousNonGameSiblings = function (parent, leafIndex) {
|
||
|
|
var kids = parent.children || [];
|
||
|
|
var start = leafIndex;
|
||
|
|
var end = leafIndex;
|
||
|
|
while (start > 0 && !this._isGameNode(kids[start - 1])) start--;
|
||
|
|
while (end < kids.length - 1 && !this._isGameNode(kids[end + 1])) end++;
|
||
|
|
var group = [];
|
||
|
|
for (var i = start; i <= end; i++) group.push(kids[i]);
|
||
|
|
return { start: start, end: end, nodes: group };
|
||
|
|
};
|
||
|
|
|
||
|
|
WorkbenchLayout.prototype._unionNodeRects = function (nodes) {
|
||
|
|
var rootRect = this.root.getBoundingClientRect();
|
||
|
|
var left = Infinity;
|
||
|
|
var top = Infinity;
|
||
|
|
var right = -Infinity;
|
||
|
|
var bottom = -Infinity;
|
||
|
|
for (var i = 0; i < nodes.length; i++) {
|
||
|
|
this._ensureId(nodes[i]);
|
||
|
|
var info = this._nodeEls[nodes[i].id];
|
||
|
|
if (!info || !info.el) continue;
|
||
|
|
var r = info.el.getBoundingClientRect();
|
||
|
|
left = Math.min(left, r.left);
|
||
|
|
top = Math.min(top, r.top);
|
||
|
|
right = Math.max(right, r.right);
|
||
|
|
bottom = Math.max(bottom, r.bottom);
|
||
|
|
}
|
||
|
|
if (!isFinite(left)) {
|
||
|
|
return { left: 0, top: 0, width: 0, height: 0 };
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
left: left - rootRect.left,
|
||
|
|
top: top - rootRect.top,
|
||
|
|
width: right - left,
|
||
|
|
height: bottom - top,
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 边沿停靠解析:
|
||
|
|
* - 标题中心 → 合并页签
|
||
|
|
* - 横排含游戏区时的上/下 → 只包住连续非游戏兄弟(红框范围),不横跨游戏
|
||
|
|
* - 竖排含游戏区时的左/右 → 同上
|
||
|
|
* - 不含游戏的横排上/下 → 提到该横排父级整行
|
||
|
|
*/
|
||
|
|
WorkbenchLayout.prototype._resolveDropTarget = function (leafNode, edge) {
|
||
|
|
if (edge === 'center') return { node: leafNode, edge: edge, group: null };
|
||
|
|
|
||
|
|
var parentInfo = this._findParent(this._tree, leafNode);
|
||
|
|
if (!parentInfo || !parentInfo.parent || parentInfo.parent.type !== 'split') {
|
||
|
|
this._ensureId(leafNode);
|
||
|
|
return { node: leafNode, edge: edge, group: null };
|
||
|
|
}
|
||
|
|
var parent = parentInfo.parent;
|
||
|
|
var index = parentInfo.index;
|
||
|
|
|
||
|
|
// 横排 + 上/下:若同排有游戏,只圈选连续非游戏列
|
||
|
|
if ((edge === 'top' || edge === 'bottom') && parent.dir === 'h') {
|
||
|
|
if (this._splitHasGame(parent) || this._parentRowHasGameSibling(parent)) {
|
||
|
|
var g = this._contiguousNonGameSiblings(parent, index);
|
||
|
|
if (g.nodes.length >= 1) {
|
||
|
|
return { node: null, edge: edge, group: g.nodes, groupParent: parent, groupStart: g.start, groupEnd: g.end };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
this._ensureId(parent);
|
||
|
|
return { node: parent, edge: edge, group: null };
|
||
|
|
}
|
||
|
|
|
||
|
|
// 竖排 + 左/右:若同列有游戏,只圈选连续非游戏行
|
||
|
|
if ((edge === 'left' || edge === 'right') && parent.dir === 'v') {
|
||
|
|
if (this._splitHasGame(parent) || this._parentRowHasGameSibling(parent)) {
|
||
|
|
var g2 = this._contiguousNonGameSiblings(parent, index);
|
||
|
|
if (g2.nodes.length >= 1) {
|
||
|
|
return { node: null, edge: edge, group: g2.nodes, groupParent: parent, groupStart: g2.start, groupEnd: g2.end };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
this._ensureId(parent);
|
||
|
|
return { node: parent, edge: edge, group: null };
|
||
|
|
}
|
||
|
|
|
||
|
|
// 外侧边:提升到父级(仍避开把游戏一并包进「整行上下」)
|
||
|
|
if (parent.dir === 'h' && edge === 'left' && index === 0) {
|
||
|
|
this._ensureId(parent);
|
||
|
|
return { node: parent, edge: edge, group: null };
|
||
|
|
}
|
||
|
|
if (parent.dir === 'h' && edge === 'right' && index === parent.children.length - 1) {
|
||
|
|
this._ensureId(parent);
|
||
|
|
return { node: parent, edge: edge, group: null };
|
||
|
|
}
|
||
|
|
if (parent.dir === 'v' && edge === 'top' && index === 0) {
|
||
|
|
this._ensureId(parent);
|
||
|
|
return { node: parent, edge: edge, group: null };
|
||
|
|
}
|
||
|
|
if (parent.dir === 'v' && edge === 'bottom' && index === parent.children.length - 1) {
|
||
|
|
this._ensureId(parent);
|
||
|
|
return { node: parent, edge: edge, group: null };
|
||
|
|
}
|
||
|
|
|
||
|
|
this._ensureId(leafNode);
|
||
|
|
return { node: leafNode, edge: edge, group: null };
|
||
|
|
};
|
||
|
|
|
||
|
|
WorkbenchLayout.prototype._parentRowHasGameSibling = function (split) {
|
||
|
|
// 父级自身子节点含 game
|
||
|
|
for (var i = 0; i < split.children.length; i++) {
|
||
|
|
if (this._isGameNode(split.children[i])) return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
};
|
||
|
|
|
||
|
|
WorkbenchLayout.prototype._hitEdge = function (node, x, y, target) {
|
||
|
|
// 标题栏优先:合并
|
||
|
|
if (target && target.closest && target.closest('.pw-leaf-tabbar, .pw-leaf-tab')) {
|
||
|
|
return 'center';
|
||
|
|
}
|
||
|
|
var m = 0.22;
|
||
|
|
var onLeft = x < m;
|
||
|
|
var onRight = x > 1 - m;
|
||
|
|
var onTop = y < m;
|
||
|
|
var onBottom = y > 1 - m;
|
||
|
|
// 角落按更近边
|
||
|
|
if (onLeft && onTop) return x < y ? 'left' : 'top';
|
||
|
|
if (onRight && onTop) return (1 - x) < y ? 'right' : 'top';
|
||
|
|
if (onLeft && onBottom) return x < (1 - y) ? 'left' : 'bottom';
|
||
|
|
if (onRight && onBottom) return (1 - x) < (1 - y) ? 'right' : 'bottom';
|
||
|
|
if (onLeft) return 'left';
|
||
|
|
if (onRight) return 'right';
|
||
|
|
if (onTop) return 'top';
|
||
|
|
if (onBottom) return 'bottom';
|
||
|
|
// 游戏区域中心不允许合并页签,默认下方提示
|
||
|
|
if (node.type === 'game') return 'bottom';
|
||
|
|
return 'center';
|
||
|
|
};
|
||
|
|
|
||
|
|
WorkbenchLayout.prototype._showOverlay = function (leafEl, edge, isGame) {
|
||
|
|
var rect = leafEl.getBoundingClientRect();
|
||
|
|
var rootRect = this.root.getBoundingClientRect();
|
||
|
|
this._showOverlayRect({
|
||
|
|
left: rect.left - rootRect.left,
|
||
|
|
top: rect.top - rootRect.top,
|
||
|
|
width: rect.width,
|
||
|
|
height: rect.height,
|
||
|
|
}, edge, isGame);
|
||
|
|
};
|
||
|
|
|
||
|
|
WorkbenchLayout.prototype._showOverlayRect = function (rect, edge, isGame) {
|
||
|
|
var overlay = this.dropOverlay;
|
||
|
|
if (!overlay) return;
|
||
|
|
overlay.hidden = false;
|
||
|
|
overlay.style.left = rect.left + 'px';
|
||
|
|
overlay.style.top = rect.top + 'px';
|
||
|
|
overlay.style.width = Math.max(0, rect.width) + 'px';
|
||
|
|
overlay.style.height = Math.max(0, rect.height) + 'px';
|
||
|
|
var isRegion = edge === 'region';
|
||
|
|
overlay.className = 'pw-drop-overlay edge-' + (isRegion ? 'center' : edge) +
|
||
|
|
(isGame ? ' is-game' : '') + (isRegion ? ' is-region' : '');
|
||
|
|
var center = overlay.querySelector('.pw-drop-e-center');
|
||
|
|
if (center) {
|
||
|
|
center.hidden = !!isGame;
|
||
|
|
center.textContent = isRegion ? '在此新建区域' : '合并到此区域';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
WorkbenchLayout.prototype._onDocDrop = function (ev) {
|
||
|
|
var panelId = ev.dataTransfer.getData('text/pw-panel') || this._dragPanel;
|
||
|
|
var targetId = this._hoverTargetId;
|
||
|
|
var edge = this._hoverEdge;
|
||
|
|
var group = this._hoverGroup;
|
||
|
|
var groupCols = this._hoverGroupCols;
|
||
|
|
this._clearDragVisual();
|
||
|
|
this._dragPanel = null;
|
||
|
|
this._hoverTargetId = null;
|
||
|
|
this._hoverEdge = null;
|
||
|
|
this._hoverGroup = null;
|
||
|
|
this._hoverGroupCols = null;
|
||
|
|
if (!panelId || !edge) return;
|
||
|
|
if (!targetId && !(groupCols && groupCols.length) && !(group && group.length)) return;
|
||
|
|
ev.preventDefault();
|
||
|
|
this._applyDrop(panelId, targetId, edge, group, groupCols);
|
||
|
|
};
|
||
|
|
|
||
|
|
WorkbenchLayout.prototype._applyDrop = function (panelId, targetId, edge, group, groupCols) {
|
||
|
|
// 组停靠:用「列内容快照」而非节点引用,避免 remove/cleanup 后 id 失效导致只包一列
|
||
|
|
if (edge !== 'center' && ((groupCols && groupCols.length) || (group && group.length))) {
|
||
|
|
this._closeFloat(panelId, false);
|
||
|
|
var cols = (groupCols && groupCols.length)
|
||
|
|
? groupCols
|
||
|
|
: this._snapshotGroupColumns(group, panelId);
|
||
|
|
this._removePanel(panelId);
|
||
|
|
var nodes = this._resolveColumnsFromSnapshot(cols);
|
||
|
|
if (nodes.length >= 1) {
|
||
|
|
this._splitBesideGroup(nodes, edge, panelId);
|
||
|
|
} else {
|
||
|
|
this._openFloat(panelId, null);
|
||
|
|
}
|
||
|
|
this._dirty = true;
|
||
|
|
this._tree = this._cleanup(this._tree) || this._presetTree('minimal');
|
||
|
|
this.rebuild();
|
||
|
|
this._rememberPanelFromTree(panelId);
|
||
|
|
this._save();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 整树左右外侧:直接相对当前根拆分,不依赖可能被 cleanup 丢掉的根 id
|
||
|
|
if (targetId === '__workspace__' && (edge === 'left' || edge === 'right')) {
|
||
|
|
this._closeFloat(panelId, false);
|
||
|
|
this._removePanel(panelId);
|
||
|
|
this._splitBeside(this._tree, edge, panelId);
|
||
|
|
this._dirty = true;
|
||
|
|
this._tree = this._cleanup(this._tree) || this._presetTree('minimal');
|
||
|
|
this.rebuild();
|
||
|
|
this._rememberPanelFromTree(panelId);
|
||
|
|
this._save();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
var target = this._findNodeById(this._tree, targetId);
|
||
|
|
if (!target) {
|
||
|
|
console.warn('[调试工作台] 停靠失败:找不到目标', targetId, edge);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (target.type === 'tabs' && (target.panels || []).indexOf(panelId) >= 0 && edge === 'center') {
|
||
|
|
this._closeFloat(panelId, false);
|
||
|
|
target.active = panelId;
|
||
|
|
this.rebuild();
|
||
|
|
this._save();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this._closeFloat(panelId, false);
|
||
|
|
this._removePanel(panelId);
|
||
|
|
target = this._findNodeById(this._tree, targetId);
|
||
|
|
if (!target) {
|
||
|
|
// cleanup 可能重建了 split 节点;若原目标就是根,回退整树停靠
|
||
|
|
if (edge === 'left' || edge === 'right') {
|
||
|
|
this._splitBeside(this._tree, edge, panelId);
|
||
|
|
} else {
|
||
|
|
this._openFloat(panelId, null);
|
||
|
|
this._dirty = true;
|
||
|
|
this._save();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
} else if (edge === 'center') {
|
||
|
|
if (target.type === 'game' || target.type === 'split') {
|
||
|
|
this._splitBeside(target, 'bottom', panelId);
|
||
|
|
} else if (target.type === 'tabs') {
|
||
|
|
if (target.panels.indexOf(panelId) < 0) target.panels.push(panelId);
|
||
|
|
target.active = panelId;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
this._splitBeside(target, edge, panelId);
|
||
|
|
}
|
||
|
|
this._dirty = true;
|
||
|
|
this._tree = this._cleanup(this._tree) || this._presetTree('minimal');
|
||
|
|
this.rebuild();
|
||
|
|
this._rememberPanelFromTree(panelId);
|
||
|
|
this._save();
|
||
|
|
};
|
||
|
|
})(window);
|