UNPKG

rc-dock

Version:

dock layout for react component

827 lines (826 loc) 28.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.findNearestPanel = exports.getFloatPanelSize = exports.replacePanel = exports.fixLayoutData = exports.fixFloatPanelPos = exports.maximize = exports.moveToFront = exports.removeFromLayout = exports.panelToWindow = exports.floatPanel = exports.dockPanelToBox = exports.dockPanelToPanel = exports.converToPanel = exports.addTabToPanel = exports.addNextToTab = exports.find = exports.Filter = exports.nextZIndex = exports.nextId = exports.getUpdatedObject = void 0; const DockData_1 = require("./DockData"); let _watchObjectChange = new WeakMap(); function getUpdatedObject(obj) { let result = _watchObjectChange.get(obj); if (result) { return getUpdatedObject(result); } return obj; } exports.getUpdatedObject = getUpdatedObject; function clearObjectCache() { _watchObjectChange = new WeakMap(); } function clone(value, extra) { let newValue = Object.assign(Object.assign({}, value), extra); if (Array.isArray(newValue.tabs)) { newValue.tabs = newValue.tabs.concat(); } if (Array.isArray(newValue.children)) { newValue.children = newValue.children.concat(); } _watchObjectChange.set(value, newValue); return newValue; } function maxFlex(currentFlex, newFlex) { if (currentFlex == null) { return newFlex; } return Math.max(currentFlex, newFlex); } function mergeFlex(currentFlex, newFlex) { if (currentFlex == null) { return newFlex; } if (currentFlex === newFlex) { return newFlex; } if (currentFlex >= 1) { if (newFlex <= 1) { return 1; } return Math.min(currentFlex, newFlex); } else { if (newFlex >= 1) { return 1; } return Math.max(currentFlex, newFlex); } } let _idCount = 0; function nextId() { ++_idCount; return `+${_idCount}`; } exports.nextId = nextId; let _zCount = 0; function nextZIndex(current) { if (current === _zCount) { // already the top return current; } return ++_zCount; } exports.nextZIndex = nextZIndex; function compareFindId(item, id) { return item && (typeof id === 'function' ? id(item) : item.id === id); } function findInPanel(panel, id, filter) { if (compareFindId(panel, id) && (filter & Filter.Panel)) { return panel; } if (filter & Filter.Tab) { for (let tab of panel.tabs) { if (compareFindId(tab, id)) { return tab; } } } return undefined; } function findInBox(box, id, filter) { let result; if ((filter | Filter.Box) && compareFindId(box, id)) { return box; } if (!(box === null || box === void 0 ? void 0 : box.children)) { return undefined; } for (let child of box.children) { if ('children' in child) { if (result = findInBox(child, id, filter)) { break; } } else if ('tabs' in child) { if (result = findInPanel(child, id, filter)) { break; } } } return result; } var Filter; (function (Filter) { Filter[Filter["Tab"] = 1] = "Tab"; Filter[Filter["Panel"] = 2] = "Panel"; Filter[Filter["Box"] = 4] = "Box"; Filter[Filter["Docked"] = 8] = "Docked"; Filter[Filter["Floated"] = 16] = "Floated"; Filter[Filter["Windowed"] = 32] = "Windowed"; Filter[Filter["Max"] = 64] = "Max"; Filter[Filter["EveryWhere"] = 120] = "EveryWhere"; Filter[Filter["AnyTab"] = 121] = "AnyTab"; Filter[Filter["AnyPanel"] = 122] = "AnyPanel"; Filter[Filter["AnyTabPanel"] = 123] = "AnyTabPanel"; Filter[Filter["All"] = 127] = "All"; })(Filter = exports.Filter || (exports.Filter = {})); function find(layout, id, filter = Filter.AnyTabPanel) { let result; if (filter & Filter.Docked) { result = findInBox(layout.dockbox, id, filter); } if (result) return result; if (filter & Filter.Floated) { result = findInBox(layout.floatbox, id, filter); } if (result) return result; if (filter & Filter.Windowed) { result = findInBox(layout.windowbox, id, filter); } if (result) return result; if (filter & Filter.Max) { result = findInBox(layout.maxbox, id, filter); } return result; } exports.find = find; function addNextToTab(layout, source, target, direction) { let pos = target.parent.tabs.indexOf(target); if (pos >= 0) { if (direction === 'after-tab') { ++pos; } return addTabToPanel(layout, source, target.parent, pos); } return layout; } exports.addNextToTab = addNextToTab; function addTabToPanel(layout, source, panel, idx = -1) { if (idx === -1) { idx = panel.tabs.length; } let tabs; let activeId; if ('tabs' in source) { // source is PanelData tabs = source.tabs; activeId = source.activeId; } else { // source is TabData tabs = [source]; } if (tabs.length) { let newPanel = clone(panel); newPanel.tabs.splice(idx, 0, ...tabs); newPanel.activeId = tabs.at(-1).id; for (let tab of tabs) { tab.parent = newPanel; } if (activeId) { newPanel.activeId = activeId; } layout = replacePanel(layout, panel, newPanel); } return layout; } exports.addTabToPanel = addTabToPanel; function converToPanel(source) { if ('tabs' in source) { // source is already PanelData return source; } else { let newPanel = { tabs: [source], group: source.group, activeId: source.id }; source.parent = newPanel; return newPanel; } } exports.converToPanel = converToPanel; function dockPanelToPanel(layout, newPanel, panel, direction) { let box = panel.parent; let dockMode = (direction === 'left' || direction === 'right') ? 'horizontal' : 'vertical'; let afterPanel = (direction === 'bottom' || direction === 'right'); let pos = box.children.indexOf(panel); if (pos >= 0) { let newBox = clone(box); if (dockMode === box.mode) { if (afterPanel) { ++pos; } // HINT: The size remains the same, preventing flex-grow less than 1 newPanel.size = panel.size; newBox.children.splice(pos, 0, newPanel); } else { let newChildBox = { mode: dockMode, children: [] }; newChildBox.size = panel.size; if (afterPanel) { newChildBox.children = [panel, newPanel]; } else { newChildBox.children = [newPanel, panel]; } panel.parent = newChildBox; panel.size = 200; newPanel.parent = newChildBox; newPanel.size = 200; newBox.children[pos] = newChildBox; newChildBox.parent = newBox; } return replaceBox(layout, box, newBox); } return layout; } exports.dockPanelToPanel = dockPanelToPanel; function dockPanelToBox(layout, newPanel, box, direction) { let parentBox = box.parent; let dockMode = (direction === 'left' || direction === 'right') ? 'horizontal' : 'vertical'; let afterPanel = (direction === 'bottom' || direction === 'right'); if (parentBox) { let pos = parentBox.children.indexOf(box); if (pos >= 0) { let newParentBox = clone(parentBox); if (dockMode === parentBox.mode) { if (afterPanel) { ++pos; } newPanel.size = box.size * 0.3; box.size *= 0.7; newParentBox.children.splice(pos, 0, newPanel); } else { let newChildBox = { mode: dockMode, children: [] }; newChildBox.size = box.size; if (afterPanel) { newChildBox.children = [box, newPanel]; } else { newChildBox.children = [newPanel, box]; } box.parent = newChildBox; box.size = 280; newPanel.parent = newChildBox; newPanel.size = 120; newParentBox.children[pos] = newChildBox; } return replaceBox(layout, parentBox, newParentBox); } } else if (box === layout.dockbox) { let newBox = clone(box); if (dockMode === box.mode) { let pos = 0; if (afterPanel) { pos = newBox.children.length; } newPanel.size = box.size * 0.3; box.size *= 0.7; newBox.children.splice(pos, 0, newPanel); return replaceBox(layout, box, newBox); } else { // replace root dockbox let newDockBox = { mode: dockMode, children: [] }; newDockBox.size = box.size; if (afterPanel) { newDockBox.children = [newBox, newPanel]; } else { newDockBox.children = [newPanel, newBox]; } newBox.size = 280; newPanel.size = 120; return replaceBox(layout, box, newDockBox); } } else if (box === layout.maxbox) { let newBox = clone(box); newBox.children.push(newPanel); return replaceBox(layout, box, newBox); } return layout; } exports.dockPanelToBox = dockPanelToBox; function floatPanel(layout, newPanel, rect) { let newBox = clone(layout.floatbox); if (rect) { newPanel.x = rect.left; newPanel.y = rect.top; newPanel.w = rect.width; newPanel.h = rect.height; } newBox.children.push(newPanel); return replaceBox(layout, layout.floatbox, newBox); } exports.floatPanel = floatPanel; function panelToWindow(layout, newPanel) { let newBox = clone(layout.windowbox); newBox.children.push(newPanel); return replaceBox(layout, layout.windowbox, newBox); } exports.panelToWindow = panelToWindow; function removeFromLayout(layout, source) { if (source) { let panelData; if ('tabs' in source) { panelData = source; layout = removePanel(layout, panelData); } else { panelData = source.parent; layout = removeTab(layout, source); } if (panelData && panelData.parent && panelData.parent.mode === 'maximize') { let newPanel = layout.maxbox.children[0]; if (!newPanel || (newPanel.tabs.length === 0 && !newPanel.panelLock)) { // max panel is gone, remove the place holder let placeHolder = find(layout, DockData_1.maximePlaceHolderId); if (placeHolder) { return removePanel(layout, placeHolder); } } } } return layout; } exports.removeFromLayout = removeFromLayout; function removePanel(layout, panel) { let box = panel.parent; if (box) { let pos = box.children.indexOf(panel); if (pos >= 0) { let newBox = clone(box); newBox.children.splice(pos, 1); return replaceBox(layout, box, newBox); } } return layout; } function removeTab(layout, tab) { let panel = tab.parent; if (panel) { let pos = panel.tabs.indexOf(tab); if (pos >= 0) { let newPanel = clone(panel); newPanel.tabs.splice(pos, 1); if (newPanel.activeId === tab.id) { // update selection id if (newPanel.tabs.length > pos) { newPanel.activeId = newPanel.tabs[pos].id; } else if (newPanel.tabs.length) { newPanel.activeId = newPanel.tabs[0].id; } } return replacePanel(layout, panel, newPanel); } } return layout; } function moveToFront(layout, source) { if (source) { let panelData; let needUpdate = false; let changes = {}; if ('tabs' in source) { panelData = source; } else { panelData = source.parent; if (panelData.activeId !== source.id) { // move tab to front changes.activeId = source.id; needUpdate = true; } } if (panelData && panelData.parent && panelData.parent.mode === 'float') { // move float panel to front let newZ = nextZIndex(panelData.z); if (newZ !== panelData.z) { changes.z = newZ; needUpdate = true; } } if (needUpdate) { layout = replacePanel(layout, panelData, clone(panelData, changes)); } } return layout; } exports.moveToFront = moveToFront; // maximize or restore the panel function maximize(layout, source) { if (source) { if ('tabs' in source) { if (source.parent.mode === 'maximize') { return restorePanel(layout, source); } else { return maximizePanel(layout, source); } } else { return maximizeTab(layout, source); } } return layout; } exports.maximize = maximize; function maximizePanel(layout, panel) { let maxbox = layout.maxbox; if (maxbox.children.length) { // invalid maximize return layout; } let placeHodlerPanel = Object.assign(Object.assign({}, panel), { id: DockData_1.maximePlaceHolderId, tabs: [], panelLock: {} }); layout = replacePanel(layout, panel, placeHodlerPanel); layout = dockPanelToBox(layout, panel, layout.maxbox, 'middle'); return layout; } function restorePanel(layout, panel) { layout = removePanel(layout, panel); let placeHolder = find(layout, DockData_1.maximePlaceHolderId); if (placeHolder) { let { x, y, z, w, h } = placeHolder; panel = Object.assign(Object.assign({}, panel), { x, y, z, w, h }); return replacePanel(layout, placeHolder, panel); } else { return dockPanelToBox(layout, panel, layout.dockbox, 'right'); } } function maximizeTab(layout, tab) { // TODO to be implemented return layout; } // move float panel into the screen function fixFloatPanelPos(layout, layoutWidth, layoutHeight) { let layoutChanged = false; if (layout && layout.floatbox && layoutWidth > 200 && layoutHeight > 200) { let newFloatChildren = layout.floatbox.children.concat(); for (let i = 0; i < newFloatChildren.length; ++i) { let panel = newFloatChildren[i]; let panelChange = {}; if (!(panel.w > 0)) { panelChange.w = Math.round(layoutWidth / 3); } else if (panel.w > layoutWidth) { panelChange.w = layoutWidth; } if (!(panel.h > 0)) { panelChange.h = Math.round(layoutHeight / 3); } else if (panel.h > layoutHeight) { panelChange.h = layoutHeight; } if (typeof panel.y !== 'number') { panelChange.y = (layoutHeight - (panelChange.h || panel.h)) >> 1; } else if (panel.y > layoutHeight - 16) { panelChange.y = Math.max(layoutHeight - 16 - (panel.h >> 1), 0); } else if (!(panel.y >= 0)) { panelChange.y = 0; } if (typeof panel.x !== 'number') { panelChange.x = (layoutWidth - (panelChange.w || panel.w)) >> 1; } else if (panel.x + panel.w < 16) { panelChange.x = 16 - (panel.w >> 1); } else if (panel.x > layoutWidth - 16) { panelChange.x = layoutWidth - 16 - (panel.w >> 1); } if (Object.keys(panelChange).length) { newFloatChildren[i] = clone(panel, panelChange); layoutChanged = true; } } if (layoutChanged) { let newBox = clone(layout.floatbox); newBox.children = newFloatChildren; return replaceBox(layout, layout.floatbox, newBox); } } return layout; } exports.fixFloatPanelPos = fixFloatPanelPos; function fixLayoutData(layout, groups, loadTab) { function fixPanelOrBox(d) { if (d.id == null) { d.id = nextId(); } else if (d.id.startsWith('+')) { let idnum = Number(d.id); if (idnum > _idCount) { // make sure generated id is unique _idCount = idnum; } } if (!(d.size >= 0)) { d.size = 200; } d.minWidth = 0; d.minHeight = 0; d.widthFlex = null; d.heightFlex = null; } function fixPanelData(panel) { fixPanelOrBox(panel); let findActiveId = false; if (loadTab) { for (let i = 0; i < panel.tabs.length; ++i) { panel.tabs[i] = loadTab(panel.tabs[i]); } } if (panel.group == null && panel.tabs.length) { panel.group = panel.tabs[0].group; } let tabGroup = groups === null || groups === void 0 ? void 0 : groups[panel.group]; if (tabGroup) { if (tabGroup.widthFlex != null) { panel.widthFlex = tabGroup.widthFlex; } if (tabGroup.heightFlex != null) { panel.heightFlex = tabGroup.heightFlex; } } for (let child of panel.tabs) { child.parent = panel; if (child.id === panel.activeId) { findActiveId = true; } if (child.minWidth > panel.minWidth) panel.minWidth = child.minWidth; if (child.minHeight > panel.minHeight) panel.minHeight = child.minHeight; } if (!findActiveId && panel.tabs.length) { panel.activeId = panel.tabs[0].id; } if (panel.minWidth <= 0) { panel.minWidth = 1; } if (panel.minHeight <= 0) { panel.minHeight = 1; } let { panelLock } = panel; if (panelLock) { if (panel.minWidth < panelLock.minWidth) { panel.minWidth = panelLock.minWidth; } if (panel.minHeight < panelLock.minHeight) { panel.minHeight = panelLock.minHeight; } if (panel.panelLock.widthFlex != null) { panel.widthFlex = panelLock.widthFlex; } if (panel.panelLock.heightFlex != null) { panel.heightFlex = panelLock.heightFlex; } } if (panel.z > _zCount) { // make sure next zIndex is on top _zCount = panel.z; } return panel; } function fixBoxData(box) { fixPanelOrBox(box); for (let i = 0; i < box.children.length; ++i) { let child = box.children[i]; child.parent = box; if ('children' in child) { fixBoxData(child); if (child.children.length === 0) { // remove box with no child box.children.splice(i, 1); --i; } else if (child.children.length === 1) { // box with one child should be merged back to parent box let subChild = child.children[0]; if (subChild.mode === box.mode) { // sub child is another box that can be merged into current box let totalSubSize = 0; for (let subsubChild of subChild.children) { totalSubSize += subsubChild.size; } let sizeScale = child.size / totalSubSize; for (let subsubChild of subChild.children) { subsubChild.size *= sizeScale; } // merge children up box.children.splice(i, 1, ...subChild.children); } else { // sub child can be moved up one layer subChild.size = child.size; box.children[i] = subChild; } --i; } } else if ('tabs' in child) { fixPanelData(child); if (child.tabs.length === 0) { // remove panel with no tab if (!child.panelLock) { box.children.splice(i, 1); --i; } else if (child.group === DockData_1.placeHolderStyle && (box.children.length > 1 || box.parent)) { // remove placeHolder Group box.children.splice(i, 1); --i; } } } // merge min size switch (box.mode) { case 'horizontal': if (child.minWidth > 0) box.minWidth += child.minWidth; if (child.minHeight > box.minHeight) box.minHeight = child.minHeight; if (child.widthFlex != null) { box.widthFlex = maxFlex(box.widthFlex, child.widthFlex); } if (child.heightFlex != null) { box.heightFlex = mergeFlex(box.heightFlex, child.heightFlex); } break; case 'vertical': if (child.minWidth > box.minWidth) box.minWidth = child.minWidth; if (child.minHeight > 0) box.minHeight += child.minHeight; if (child.heightFlex != null) { box.heightFlex = maxFlex(box.heightFlex, child.heightFlex); } if (child.widthFlex != null) { box.widthFlex = mergeFlex(box.widthFlex, child.widthFlex); } break; } } // add divider size if (box.children.length > 1) { switch (box.mode) { case 'horizontal': box.minWidth += (box.children.length - 1) * 4; break; case 'vertical': box.minHeight += (box.children.length - 1) * 4; break; } } return box; } if (layout.floatbox) { layout.floatbox.mode = 'float'; } else { layout.floatbox = { mode: 'float', children: [], size: 1 }; } if (layout.windowbox) { layout.windowbox.mode = 'window'; } else { layout.windowbox = { mode: 'window', children: [], size: 1 }; } if (layout.maxbox) { layout.maxbox.mode = 'maximize'; } else { layout.maxbox = { mode: 'maximize', children: [], size: 1 }; } fixBoxData(layout.dockbox); fixBoxData(layout.floatbox); fixBoxData(layout.windowbox); fixBoxData(layout.maxbox); if (layout.dockbox.children.length === 0) { // add place holder panel when root box is empty let newPanel = { id: '+0', group: DockData_1.placeHolderStyle, panelLock: {}, size: 200, tabs: [] }; newPanel.parent = layout.dockbox; layout.dockbox.children.push(newPanel); } else { // merge and replace root box when box has only one child while (layout.dockbox.children.length === 1 && 'children' in layout.dockbox.children[0]) { let newDockBox = clone(layout.dockbox.children[0]); layout.dockbox = newDockBox; for (let child of newDockBox.children) { child.parent = newDockBox; } } } layout.dockbox.parent = null; layout.floatbox.parent = null; layout.windowbox.parent = null; layout.maxbox.parent = null; clearObjectCache(); return layout; } exports.fixLayoutData = fixLayoutData; function replacePanel(layout, panel, newPanel) { for (let tab of newPanel.tabs) { tab.parent = newPanel; } let box = panel.parent; if (box) { let pos = box.children.indexOf(panel); if (pos >= 0) { let newBox = clone(box); newBox.children[pos] = newPanel; return replaceBox(layout, box, newBox); } } return layout; } exports.replacePanel = replacePanel; function replaceBox(layout, box, newBox) { for (let child of newBox.children) { child.parent = newBox; } let parentBox = box.parent; if (parentBox) { let pos = parentBox.children.indexOf(box); if (pos >= 0) { let newParentBox = clone(parentBox); newParentBox.children[pos] = newBox; return replaceBox(layout, parentBox, newParentBox); } } else { if (box.id === layout.dockbox.id || box === layout.dockbox) { return Object.assign(Object.assign({}, layout), { dockbox: newBox }); } else if (box.id === layout.floatbox.id || box === layout.floatbox) { return Object.assign(Object.assign({}, layout), { floatbox: newBox }); } else if (box.id === layout.windowbox.id || box === layout.windowbox) { return Object.assign(Object.assign({}, layout), { windowbox: newBox }); } else if (box.id === layout.maxbox.id || box === layout.maxbox) { return Object.assign(Object.assign({}, layout), { maxbox: newBox }); } } return layout; } function getFloatPanelSize(panel, tabGroup) { if (!panel) { return [300, 300]; } let panelWidth = panel.offsetWidth; let panelHeight = panel.offsetHeight; let [minWidth, maxWidth] = tabGroup.preferredFloatWidth || [100, 600]; let [minHeight, maxHeight] = tabGroup.preferredFloatHeight || [50, 500]; if (!(panelWidth >= minWidth)) { panelWidth = minWidth; } else if (!(panelWidth <= maxWidth)) { panelWidth = maxWidth; } if (!(panelHeight >= minHeight)) { panelHeight = minHeight; } else if (!(panelHeight <= maxHeight)) { panelHeight = maxHeight; } return [panelWidth, panelHeight]; } exports.getFloatPanelSize = getFloatPanelSize; function findNearestPanel(rectFrom, rectTo, direction) { let distance = -1; let overlap = -1; let alignment = 0; switch (direction) { case 'ArrowUp': { distance = rectFrom.top - rectTo.bottom + rectFrom.height; overlap = Math.min(rectFrom.right, rectTo.right) - Math.max(rectFrom.left, rectTo.left); break; } case 'ArrowDown': { distance = rectTo.top - rectFrom.bottom + rectFrom.height; overlap = Math.min(rectFrom.right, rectTo.right) - Math.max(rectFrom.left, rectTo.left); break; } case 'ArrowLeft': { distance = rectFrom.left - rectTo.right + rectFrom.width; overlap = Math.min(rectFrom.bottom, rectTo.bottom) - Math.max(rectFrom.top, rectTo.top); alignment = Math.abs(rectFrom.top - rectTo.top); break; } case 'ArrowRight': { distance = rectTo.left - rectFrom.right + rectFrom.width; overlap = Math.min(rectFrom.bottom, rectTo.bottom) - Math.max(rectFrom.top, rectTo.top); alignment = Math.abs(rectFrom.top - rectTo.top); break; } } if (distance < 0 || overlap <= 0) { return -1; } return distance * (alignment + 1) - overlap * 0.001; } exports.findNearestPanel = findNearestPanel;