diff --git a/examples/demo/App.tsx b/examples/demo/App.tsx index 9ec8c6ca..f2060388 100755 --- a/examples/demo/App.tsx +++ b/examples/demo/App.tsx @@ -135,6 +135,22 @@ function App() { // console.log("Added tab", addedTab); } + const onAddToLeftEmptyTabset = (event: React.MouseEvent) => { + (layoutRef!.current!).addTabToTabSet("mwLeftTabSet", { + component: "grid", + icon: "images/article.svg", + name: "Grid " + nextGridIndex.current++ + }); + } + + const onAddToRightEmptyTabset = (event: React.MouseEvent) => { + (layoutRef!.current!).addTabToTabSet("mwRightTabSet", { + component: "grid", + icon: "images/article.svg", + name: "Grid " + nextGridIndex.current++ + }); + } + const onAddFromTabSetButton = (node: TabSetNode | BorderNode) => { const addedTab = (layoutRef!.current!).addTabToTabSet(node.getId(), { component: "grid", @@ -532,6 +548,7 @@ function App() { +
@@ -581,6 +598,12 @@ function App() { Add Drag + {layoutFile === "ecmind" && +
+ + +
+ }
{contents} diff --git a/examples/demo/layouts/ecmind.layout b/examples/demo/layouts/ecmind.layout new file mode 100755 index 00000000..ee6a12b0 --- /dev/null +++ b/examples/demo/layouts/ecmind.layout @@ -0,0 +1,106 @@ +{ + "global": { + "splitterEnableHandle": true, + "tabEnablePopout": true, + "tabSetEnableActiveIcon": true, + "tabSetMinWidth": 130, + "tabSetMinHeight": 100, + "tabSetEnableTabScrollbar": true, + "borderMinSize": 100, + "borderEnableTabScrollbar": true, + "tabSetEnableDeleteWhenEmpty": false, + "tabSetEnableHideWhenEmpty": true, + "borderEnableAutoHide": false + }, + "borders": [ + { + "type": "border", + "location": "bottom", + "children": [ + { + "type": "tab", + "id": "#0ae8e0fb-dba2-4b14-9d75-08781231479a", + "name": "Output", + "component": "grid", + "enableClose": false, + "icon": "images/bar_chart.svg" + }, + { + "type": "tab", + "id": "#803a2efe-e507-4735-9c2a-46ce6042c1a2", + "name": "Terminal", + "component": "grid", + "enableClose": false, + "icon": "images/terminal.svg" + }, + { + "type": "tab", + "id": "#7bac972e-fd5f-4582-a511-4feede448394", + "name": "Layout JSON", + "component": "json" + } + ] + }, + { + "type": "border", + "location": "left", + "children": [ + { + "type": "tab", + "id": "#21c49854-be85-4e32-96c3-61962f71bc15", + "name": "Navigation", + "altName": "The Navigation Tab", + "component": "grid", + "enableClose": false, + "icon": "images/folder.svg" + } + ] + }, + { + "type": "border", + "location": "right", + "children": [ + { + "type": "tab", + "id": "#ec253996-0724-416b-a097-23f85a89afbe", + "name": "Options", + "component": "grid", + "enableClose": false, + "icon": "images/settings.svg" + } + ] + } + ], + "layout": { + "type": "row", + "id": "#11b6dde6-2808-4a87-b378-dd6ed2a92547", + "children": [ + { + "id": "mwLeftTabSet", + "type": "tabset", + "weight": 33, + "children": [] + }, + { + "id": "mwMiddleTabSet", + "type": "tabset", + "weight": 33, + "children": [ + { + "type": "tab", + "name": "OpenLayers Map", + "component": "map", + "enablePopoutOverlay": true + } + ] + }, + { + "id": "mwRightTabSet", + "type": "tabset", + "weight": 33, + "children": [] + } + ] + }, + "popouts": {} +} \ No newline at end of file diff --git a/src/model/IJsonModel.ts b/src/model/IJsonModel.ts index 9545c4b6..e8b45f49 100755 --- a/src/model/IJsonModel.ts +++ b/src/model/IJsonModel.ts @@ -376,6 +376,15 @@ export interface IGlobalAttributes { */ tabSetEnableDeleteWhenEmpty?: boolean; + /** + Value for TabSetNode attribute enableHideWhenEmpty if not overridden + + whether to hide this tabset when it has no tabs + + Default: inherited from Global attribute tabSetEnableHidWhenEmpty (default false) + */ + tabSetEnableHideWhenEmpty?: boolean; + /** Value for TabSetNode attribute enableDivide if not overridden @@ -560,6 +569,13 @@ export interface ITabSetAttributes { */ enableDeleteWhenEmpty?: boolean; + /** + whether to hide this tabset when it has no tabs + + Default: inherited from Global attribute tabSetEnableHideWhenEmpty (default false) + */ + enableHideWhenEmpty?: boolean; + /** allow user to drag tabs to region of this tabset, splitting into new tabset diff --git a/src/model/Model.ts b/src/model/Model.ts index 1e6207f4..9400a680 100755 --- a/src/model/Model.ts +++ b/src/model/Model.ts @@ -665,6 +665,7 @@ export class Model { // tabset attributeDefinitions.add("tabSetEnableDeleteWhenEmpty", true).setType(Attribute.BOOLEAN); + attributeDefinitions.add("tabSetEnableHideWhenEmpty", false).setType(Attribute.BOOLEAN); attributeDefinitions.add("tabSetEnableDrop", true).setType(Attribute.BOOLEAN); attributeDefinitions.add("tabSetEnableDrag", true).setType(Attribute.BOOLEAN); attributeDefinitions.add("tabSetEnableDivide", true).setType(Attribute.BOOLEAN); diff --git a/src/model/RowNode.ts b/src/model/RowNode.ts index d3fdae82..eba47ff8 100755 --- a/src/model/RowNode.ts +++ b/src/model/RowNode.ts @@ -95,18 +95,38 @@ export class RowNode extends Node implements IDropTarget { this.attributes.weight = weight; } + /** @internal */ + isHiddenNode(node: TabSetNode | RowNode): boolean { + return node instanceof TabSetNode && + node.getChildren().length === 0 && + node.isEnableHideWhenEmpty(); + } + /** @internal */ getSplitterBounds(index: number) { const h = this.getOrientation() === Orientation.HORZ; const c = this.getChildren(); const ss = this.model.getSplitterSize(); const fr = c[0].getRect(); - const lr = c[c.length - 1].getRect(); + let lr = c[c.length - 1].getRect(); + + // Special-case: Last node is hidden, in this case + // we take the most right node which is visible + for (let i = c.length - 1; i >= 0; i--) { + const n = c[i] as TabSetNode | RowNode; + if (!this.isHiddenNode(n)) { + lr = n.getRect(); + break; + } + } + let p = h ? [fr.x, lr.getRight()] : [fr.y, lr.getBottom()]; const q = h ? [fr.x, lr.getRight()] : [fr.y, lr.getBottom()]; for (let i = 0; i < index; i++) { const n = c[i] as TabSetNode | RowNode; + if (this.isHiddenNode(n)) + continue; p[0] += h ? n.getMinWidth() : n.getMinHeight(); q[0] += h ? n.getMaxWidth() : n.getMaxHeight(); if (i > 0) { @@ -117,6 +137,8 @@ export class RowNode extends Node implements IDropTarget { for (let i = c.length - 1; i >= index; i--) { const n = c[i] as TabSetNode | RowNode; + if (this.isHiddenNode(n)) + continue; p[1] -= (h ? n.getMinWidth() : n.getMinHeight()) + ss; q[1] -= (h ? n.getMaxWidth() : n.getMaxHeight()) + ss; } diff --git a/src/model/TabSetNode.ts b/src/model/TabSetNode.ts index 0c11c27f..c6ba216b 100755 --- a/src/model/TabSetNode.ts +++ b/src/model/TabSetNode.ts @@ -173,6 +173,10 @@ export class TabSetNode extends Node implements IDraggable, IDropTarget { return this.getAttr("enableDeleteWhenEmpty") as boolean; } + isEnableHideWhenEmpty() { + return this.getAttr("enableHideWhenEmpty") as boolean; + } + isEnableDrop() { return this.getAttr("enableDrop") as boolean; } @@ -529,6 +533,9 @@ export class TabSetNode extends Node implements IDraggable, IDropTarget { attributeDefinitions.addInherited("enableDeleteWhenEmpty", "tabSetEnableDeleteWhenEmpty").setDescription( `whether to delete this tabset when is has no tabs` ); + attributeDefinitions.addInherited("enableHideWhenEmpty", "tabSetEnableHideWhenEmpty").setDescription( + "whether to hide this tabset when it has no tabs" + ); attributeDefinitions.addInherited("enableDrop", "tabSetEnableDrop").setDescription( `allow user to drag tabs into this tabset` ); diff --git a/src/view/Row.tsx b/src/view/Row.tsx index 7615ad5c..f676cc50 100644 --- a/src/view/Row.tsx +++ b/src/view/Row.tsx @@ -26,18 +26,46 @@ export const Row = (props: IRowProps) => { const items: React.ReactNode[] = []; - let i = 0; + const isHiddenNode = (node: any): boolean => + ( + node instanceof TabSetNode && + node.getChildren().length === 0 && + node.isEnableHideWhenEmpty() + ) || + ( + node instanceof RowNode && + node.getChildren().every((cr) => isHiddenNode(cr)) + ); - for (const child of node.getChildren()) { - if (i > 0) { + const children = node.getChildren(); + + for (let i = 0; i < children.length; i++) { + + const c = children[i]; + const lc = i > 0 ? children[i - 1] : undefined; + const hidden = isHiddenNode(c); + const lcHidden = lc ? isHiddenNode(lc) : undefined; + + if (lc && !lcHidden && !hidden) { items.push() } - if (child instanceof RowNode) { - items.push(); - } else if (child instanceof TabSetNode) { - items.push(); + if (c instanceof RowNode) { + if (hidden) { + items.push(
+ +
); + } else { + items.push(); + } + } else if (c instanceof TabSetNode) { + if (!hidden) { + items.push(); + } else { + items.push(
+ +
); + } } - i++; } const style: Record = {