react-simple-dock 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,12 +2,18 @@
2
2
 
3
3
  A set of React components to create a dockable interface, allowing to arrange and resize tabs.
4
4
 
5
- ## Installation
5
+ ## Installation of the javascript package
6
6
 
7
7
  ```bash
8
8
  npm install react-simple-dock
9
9
  ```
10
10
 
11
+ ## Installation of the PRET python package
12
+
13
+ ```bash
14
+ pip install pret-simple-dock
15
+ ```
16
+
11
17
  ## Demo
12
18
 
13
19
  [![Edit react-simple-dock](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/p/sandbox/zwgwp3)
@@ -16,6 +22,7 @@ npm install react-simple-dock
16
22
 
17
23
  ```tsx
18
24
  import React from "react";
25
+ import ReactDOM from "react-dom";
19
26
  import { Layout, Panel } from "react-simple-dock";
20
27
  import { DndProvider } from "react-dnd";
21
28
  import { HTML5Backend } from "react-dnd-html5-backend";
@@ -37,25 +44,74 @@ const DEFAULT_CONFIG = {
37
44
  };
38
45
 
39
46
  const App = () => (
40
- <DndProvider backend={HTML5Backend}>
41
- <div style={{ background: "#bdbdbd", width: "100vw", height: "100vh" }}>
42
- <Layout
43
- /* optional initial layout config */
44
- defaultConfig={DEFAULT_CONFIG}
45
- >
46
- <Panel key="Panel 1">
47
- <p>Content 1</p>
48
- </Panel>
49
- <Panel key="Panel 2" header={<i>Italic title</i>}>
50
- <p>Content 2</p>
51
- </Panel>
52
- <Panel key="Panel 3">
53
- <p>Content 3</p>
54
- </Panel>
55
- </Layout>
56
- </div>
57
- </DndProvider>
47
+ <div style={{ background: "#bdbdbd", width: "100vw", height: "100vh" }}>
48
+ <Layout
49
+ /* optional initial layout config */
50
+ defaultConfig={DEFAULT_CONFIG}
51
+ >
52
+ <Panel key="Panel 1">
53
+ <p>Content 1</p>
54
+ </Panel>
55
+ <Panel key="Panel 2" header={<i>Italic title</i>}>
56
+ <p>Content 2</p>
57
+ </Panel>
58
+ <Panel key="Panel 3">
59
+ <p>Content 3</p>
60
+ </Panel>
61
+ </Layout>
62
+ </div>
58
63
  );
59
64
 
60
65
  ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(<App />);
61
66
  ```
67
+
68
+ ## Development
69
+
70
+ ### Installation
71
+
72
+ Clone the repository:
73
+
74
+ ```bash
75
+ git clone https://github.com/percevalw/react-simple-dock.git
76
+ cd react-simple-dock
77
+ ```
78
+
79
+ Install the dependencies:
80
+
81
+ ```bash
82
+ yarn install
83
+ ```
84
+
85
+ Make your changes and run the demo:
86
+
87
+ ```bash
88
+ yarn start
89
+ ```
90
+
91
+ ### Build the javascript library
92
+
93
+ To build the javascript library:
94
+
95
+ ```bash
96
+ yarn build:lib
97
+ ```
98
+
99
+ ### Build the PRET python package
100
+
101
+ Ensure `pret` is installed.
102
+
103
+ ```bash
104
+ pip install pret
105
+ ```
106
+
107
+ If you have changed the signature of the components, you will need to update the python stubs.
108
+
109
+ ```bash
110
+ pret stub . SimpleDock pret/ui/simple_dock/__init__.py
111
+ ```
112
+
113
+ To build the python library and make it available in your environment:
114
+
115
+ ```bash
116
+ pip install .
117
+ ```
@@ -378,3 +378,6 @@ export function Layout(props) {
378
378
  }
379
379
  return container;
380
380
  }
381
+ Layout.defaultProps = {
382
+ wrapDnd: true,
383
+ };
package/package.json CHANGED
@@ -1,66 +1,24 @@
1
1
  {
2
2
  "name": "react-simple-dock",
3
- "version": "0.1.1",
4
- "main": "dist/index.js",
5
- "types": "dist/index.d.ts",
6
- "type": "module",
3
+ "version": "0.1.2",
4
+ "main": "index.js",
5
+ "types": "index.d.ts",
7
6
  "description": "Simple dock component for React",
8
7
  "repository": "https://github.com/percevalw/react-simple-dock",
9
8
  "author": "Perceval Wajsbürt <perceval.wajsburt@gmail.com>",
10
- "scripts": {
11
- "start": "react-scripts start",
12
- "build": "react-scripts build",
13
- "test": "react-scripts test",
14
- "eject": "react-scripts eject",
15
- "build:lib:babel": "rm -rf dist && NODE_ENV=production babel src/lib --out-dir dist --copy-files --ignore __tests__,spec.js,test.js,__snapshots__,*.md",
16
- "build:lib": "rm -rf dist && tsc --build tsconfig.lib.json && cp src/lib/*.css dist && rm -rf lib/",
17
- "prepublishOnly": "npm run build:lib",
18
- "build:pret": "jlpm clean && jupyter labextension build .",
19
- "build:pret:dev": "jlpm clean && jupyter labextension build --development True .",
20
- "clean": "rm -rf pret/ui/simple_dock/js-extension lib",
21
- "prepare": "husky install"
22
- },
23
9
  "dependencies": {
24
10
  "react-dnd": ">=16.0.1",
25
11
  "react-dnd-html5-backend": ">=16.0.1"
26
12
  },
27
13
  "peerDependencies": {
28
- "@types/react": ">= 16",
29
- "react": ">=16.0.0",
30
- "react-dom": ">=16.0.0"
31
- },
32
- "devDependencies": {
33
- "@testing-library/dom": "^10.4.0",
34
- "@testing-library/jest-dom": "^6.6.3",
35
- "@testing-library/react": "^16.2.0",
36
- "@testing-library/user-event": "^13.5.0",
37
- "@types/jest": "^27.5.2",
38
- "@types/node": "^16.18.126",
39
- "@types/react": "^19.0.10",
40
- "@types/react-dom": "^19.0.4",
41
- "babel-cli": "^6.26.0",
42
- "husky": ">=6",
43
- "lint-staged": ">=10",
44
- "prettier": "^3.5.3",
45
- "react": "^19.0.0",
46
- "react-app-rewired": "^2.2.1",
47
- "react-dom": "^19.0.0",
48
- "react-scripts": "5.0.1",
49
- "ts-loader": "^9.5.2",
50
- "typescript": "^5.8.2",
51
- "web-vitals": "^2.1.4"
14
+ "react": ">=17.0.0",
15
+ "react-dom": ">=17.0.0"
52
16
  },
53
17
  "files": [
54
18
  "dist",
55
19
  "LICENSE",
56
20
  "README.md"
57
21
  ],
58
- "jupyterlab": {
59
- "extension": "src/plugin.ts",
60
- "schemaDir": "src/schema",
61
- "outputDir": "pret/ui/simple_dock/js-extension",
62
- "webpackConfig": "./webpack.jupyter.js"
63
- },
64
22
  "browserslist": {
65
23
  "production": [
66
24
  ">0.2%",
@@ -72,18 +30,5 @@
72
30
  "last 1 firefox version",
73
31
  "last 1 safari version"
74
32
  ]
75
- },
76
- "prettier": {
77
- "printWidth": 120,
78
- "tabWidth": 4
79
- },
80
- "eslintConfig": {
81
- "extends": [
82
- "react-app",
83
- "react-app/jest"
84
- ]
85
- },
86
- "lint-staged": {
87
- "*.{js,css,md}": "prettier --write"
88
33
  }
89
- }
34
+ }
package/dist/index.css DELETED
@@ -1,192 +0,0 @@
1
- /* Light Theme, feel free to override */
2
- :root {
3
- --sd-grid-gap: 3px;
4
- }
5
-
6
- :root[data-theme="light"] {
7
- --sd-background-color: #fff;
8
- --sd-text-color: #000;
9
- --sd-tab-border-color: #bdbdbd;
10
- --sd-tab-background-color: white;
11
- --sd-header-background-color: white;
12
- --sd-header-border-color: #cecece;
13
- --sd-overlay-color: rgba(69, 159, 232, 0.51);
14
- --sd-highlight-color: #4591e8;
15
- }
16
-
17
- /* Dark Theme, feel free to override */
18
- :root[data-theme="dark"] {
19
- --sd-background-color: #22272e;
20
- --sd-text-color: #fff;
21
- --sd-border-color: #666;
22
- --sd-tab-border-color: #111;
23
- --sd-highlight-color: #4591e8;
24
- --sd-tab-background-color: #1c2128;
25
- --sd-header-background-color: #333;
26
- --sd-header-border-color: #444;
27
- --sd-overlay-color: rgba(105, 159, 232, 0.51);
28
- }
29
-
30
- body[data-jp-theme-light] {
31
- /* don't care about the theme, just that we're in JupyterLab */
32
- --sd-grid-gap: 7px;
33
- --sd-background-color: var(--jp-layout-color3);
34
- }
35
-
36
- .resize-border {
37
- position: absolute;
38
- background-color: var(--sd-background-color, #fff);
39
- opacity: 0;
40
- z-index: 10;
41
- }
42
-
43
- .resize-border:hover {
44
- opacity: 0.8;
45
- }
46
-
47
- .resize-border.bottom {
48
- bottom: calc(0px - var(--sd-grid-gap));
49
- left: 0;
50
- right: 0;
51
- height: calc((var(--sd-grid-gap)));
52
- cursor: row-resize;
53
- }
54
-
55
- .resize-border.right {
56
- top: 0;
57
- bottom: 0;
58
- right: calc(0px - var(--sd-grid-gap));
59
- width: calc((var(--sd-grid-gap)));
60
- cursor: col-resize;
61
- }
62
-
63
- .container {
64
- width: 100%;
65
- height: 100%;
66
- }
67
-
68
- .tab-handle-overlay {
69
- position: absolute;
70
- box-sizing: border-box;
71
- display: none;
72
- top: 0;
73
- left: 0;
74
- right: 0;
75
- bottom: 0;
76
- z-index: 100;
77
- background: var(--sd-overlay-color, rgba(69, 159, 232, 0.51));
78
- border: 2px dashed var(--sd-highlight-color, #4591e8);
79
-
80
- pointer-events: none;
81
- transition: 0s linear;
82
- transition-property: left, right, top, bottom, width, height;
83
- }
84
-
85
- .panel {
86
- position: relative;
87
- display: flex;
88
- flex: 1 100000000 0;
89
- align-self: stretch;
90
- height: 100%;
91
- width: 100%;
92
- background: var(--sd-background-color);
93
- }
94
-
95
- .panel.leaf {
96
- flex-direction: column;
97
- }
98
-
99
- .row > .panel-content,
100
- .column > .panel-content {
101
- display: grid;
102
- grid-gap: var(--sd-grid-gap);
103
- position: absolute;
104
- left: 0;
105
- top: 0;
106
- width: 100%;
107
- height: 100%;
108
- }
109
-
110
- .leaf > .panel-content {
111
- position: relative;
112
- flex: 1;
113
- }
114
-
115
- .leaf > .panel-content > div {
116
- background: var(--sd-tab-background-color);
117
- position: absolute;
118
- top: 0;
119
- left: 0;
120
- width: 100%;
121
- height: 100%;
122
- overflow: scroll;
123
- }
124
-
125
- /* TAB HEADER */
126
-
127
- .tab-header {
128
- display: flex;
129
- flex-direction: row;
130
- height: var(--sd-header-height);
131
- }
132
-
133
- .tab-header-border {
134
- position: relative;
135
- height: 1px;
136
- background: var(--sd-tab-border-color, #cecece);
137
- margin-top: -2px;
138
- z-index: 0;
139
- }
140
-
141
- .tab-header-bottom {
142
- position: relative;
143
- background: var(--sd-tab-background-color, white);
144
- height: 3px;
145
- z-index: 2;
146
- border: solid var(--sd-tab-border-color, #bdbdbd);
147
- border-width: 0 1px 1px 1px;
148
- /*
149
- Why did I put this ? It messes up the overall width of the layout
150
- margin-left: -1px;
151
- margin-right: -1px;
152
- */
153
- }
154
-
155
- .tab-placeholder {
156
- width: 0;
157
- transition: width 0.1s linear;
158
- height: 0;
159
- }
160
-
161
- .tab-handle {
162
- position: relative;
163
- box-sizing: border-box;
164
- padding: 5px;
165
- color: var(--sd-text-color, #000);
166
- background: var(--sd-tab-background-color, white);
167
- font-size: calc((var(--sd-header-height) - 12px) * 0.8);
168
- border: solid var(--sd-tab-border-color, #bdbdbd);
169
- border-width: 1px 1px 1px 1px;
170
- /* Too overlap the border of the next handle and avoid double borders */
171
- margin-right: -1px;
172
- z-index: 1;
173
- }
174
-
175
- .tab-handle:nth-child(2) {
176
- border-left-width: 1px;
177
- }
178
-
179
- .tab-handle.tab-handle__hidden {
180
- z-index: 0;
181
- }
182
-
183
- .tab-handle.tab-handle__visible:before {
184
- content: "";
185
- display: block;
186
- background: var(--sd-highlight-color, #4591e8);
187
- position: absolute;
188
- top: 0;
189
- left: 0;
190
- right: 0;
191
- height: 2px;
192
- }
package/dist/index.d.ts DELETED
@@ -1,9 +0,0 @@
1
- import React from "react";
2
- import "./index.css";
3
- import { LayoutConfig, PanelProps } from "./types";
4
- export declare const Panel: (props: PanelProps) => any;
5
- export declare function Layout(props: {
6
- children: React.ReactElement<PanelProps>[] | React.ReactElement<PanelProps>;
7
- defaultConfig?: LayoutConfig;
8
- wrapDnd?: boolean;
9
- }): import("react/jsx-runtime").JSX.Element;
package/dist/types.d.ts DELETED
@@ -1,67 +0,0 @@
1
- import React from "react";
2
- /**
3
- * Leaf layout configuration.
4
- *
5
- * Represents a panel that contains one or more tabs along with the current tab index and its size.
6
- */
7
- export type LeafLayoutConfig = {
8
- kind: "leaf";
9
- tabs: string[];
10
- tabIndex: number;
11
- size: number;
12
- nesting?: number;
13
- };
14
- /**
15
- * Container layout configuration.
16
- *
17
- * Represents a container panel that arranges its child panels in either a row or a column.
18
- */
19
- export type ContainerLayoutConfig = {
20
- kind: "row" | "column";
21
- children: LayoutConfig[];
22
- size: number;
23
- nesting?: number;
24
- };
25
- /**
26
- * Layout configuration.
27
- *
28
- * A union type that represents either a leaf panel (with tabs) or a container panel (row/column with children).
29
- */
30
- export type LayoutConfig = LeafLayoutConfig | ContainerLayoutConfig;
31
- /**
32
- * Properties for a panel component.
33
- *
34
- * Defines the children to render within the panel, an optional name, and an optional header.
35
- */
36
- export type PanelProps = {
37
- children: React.ReactNode;
38
- name?: string;
39
- header?: React.ReactNode;
40
- };
41
- /**
42
- * Represents a drop zone within the layout.
43
- *
44
- * Includes the rectangular coordinates, the associated layout configuration,
45
- * an a to indicate the drop position, and a reference to the related DOM element.
46
- */
47
- export type Zone = {
48
- rect: {
49
- left: number;
50
- top: number;
51
- width: number;
52
- height: number;
53
- };
54
- config: LayoutConfig;
55
- index: "LEFT" | "RIGHT" | "TOP" | "BOTTOM" | "CENTER" | "TAB";
56
- element: HTMLElement;
57
- before?: string;
58
- };
59
- /**
60
- * Drag and drop item for tab header drag operations.
61
- *
62
- * Contains the panel name and a reference to the draggable element.
63
- */
64
- export type DnDItem = {
65
- name: string;
66
- handleElement: HTMLDivElement;
67
- };
package/dist/types.js DELETED
@@ -1,6 +0,0 @@
1
- const LEFT = 0;
2
- const RIGHT = 1;
3
- const TOP = 2;
4
- const BOTTOM = 3;
5
- const TAB = 4;
6
- export {};
package/dist/utils.d.ts DELETED
@@ -1,26 +0,0 @@
1
- import { Zone, LayoutConfig } from "./types";
2
- /**
3
- * Moves a panel within the layout based on a drop zone and panel name.
4
- *
5
- * This function traverses the layout config tree, removing the panel from its original position,
6
- * and re-inserting it based on the provided drop zone. It handles both leaf and container nodes,
7
- * ensuring proper size calculations and layout updates.
8
- *
9
- * @param zone - The drop zone where the panel is to be moved.
10
- * @param name - The name of the panel being moved.
11
- * @param inside - The layout config in which the move operation takes place.
12
- * @returns The updated layout config after moving the panel.
13
- */
14
- export declare const movePanel: (zone: Zone | null, name: string, inside: LayoutConfig) => LayoutConfig;
15
- /**
16
- * Filters panels from a layout configuration based on a provided list of panel names.
17
- *
18
- * Traverses the layout configuration tree and removes any panels (or tabs within panels)
19
- * whose names are not included in the specified list. It simplifies nodes by filtering
20
- * out unwanted tabs and recursively updating container nodes.
21
- *
22
- * @param names - An array of panel names that should remain in the layout.
23
- * @param inside - The layout configuration to filter.
24
- * @returns The updated layout configuration with only the specified panels, or null if empty.
25
- */
26
- export declare const filterPanels: (names: string[], inside: LayoutConfig) => LayoutConfig;
package/dist/utils.js DELETED
@@ -1,248 +0,0 @@
1
- /**
2
- * Simplifies a layout configuration by flattening nested layouts with the same kind.
3
- *
4
- * If the layout configuration is a row or column, this function will flatten any nested child configurations
5
- * that have the same kind as the parent. It recalculates the size of nested items based on their parent's size.
6
- *
7
- * @param config - The layout configuration to simplify.
8
- * @returns The simplified layout configuration.
9
- */
10
- const simplifyLayout = (config) => {
11
- if (config.kind === "row" || config.kind === "column") {
12
- let expandedChildren = [];
13
- let changed = false;
14
- config.children.forEach((child) => {
15
- if (child.kind === config.kind) {
16
- changed = true;
17
- const totalChildContainerSize = child.children.reduce((a, b) => a + b.size, 0);
18
- child.children.forEach((grandChild) => {
19
- expandedChildren.push({
20
- ...grandChild,
21
- size: (child.size * grandChild.size) / totalChildContainerSize,
22
- });
23
- });
24
- }
25
- else {
26
- expandedChildren.push(child);
27
- }
28
- });
29
- if (changed) {
30
- config = { ...config, children: expandedChildren };
31
- }
32
- }
33
- return config;
34
- };
35
- /**
36
- * Moves a panel within the layout based on a drop zone and panel name.
37
- *
38
- * This function traverses the layout config tree, removing the panel from its original position,
39
- * and re-inserting it based on the provided drop zone. It handles both leaf and container nodes,
40
- * ensuring proper size calculations and layout updates.
41
- *
42
- * @param zone - The drop zone where the panel is to be moved.
43
- * @param name - The name of the panel being moved.
44
- * @param inside - The layout config in which the move operation takes place.
45
- * @returns The updated layout config after moving the panel.
46
- */
47
- export const movePanel = (zone, name, inside) => {
48
- const editLayout = (visitedConfig) => {
49
- let config = visitedConfig;
50
- if (config.kind === "leaf" && config.tabs.includes(name)) {
51
- /* If it's a simple leaf, try to remove the matching tab if it was
52
- * the tab that was picked by the user (since we're moving it) */
53
- const newTabs = config.tabs.filter((tabName) => tabName !== name);
54
- config = {
55
- ...config,
56
- tabs: newTabs,
57
- tabIndex: Math.min(newTabs.length - 1, config.tabIndex),
58
- };
59
- if (config.tabs.length === 0) {
60
- config = null;
61
- }
62
- }
63
- /* If this zone is the zone targeted as drop-zone by the user */
64
- if (zone && visitedConfig === zone.config) {
65
- if (config === null) {
66
- return {
67
- kind: "leaf",
68
- tabs: [name],
69
- tabIndex: 0,
70
- size: visitedConfig.size,
71
- };
72
- }
73
- // or it's a container zone
74
- else if (config.kind === "row" || config.kind === "column") {
75
- const totalSize = config.children.reduce((a, b) => a + b.size, 0);
76
- const fraction = config.kind === "column"
77
- ? zone.rect.height / zone.element.parentElement.offsetHeight
78
- : zone.rect.width / zone.element.parentElement.offsetWidth;
79
- // x / (y + x) = a
80
- // x = a y + a x
81
- // x = a y / ( 1 - a )
82
- const newConfigKind = zone.index === "TOP" || zone.index === "BOTTOM" ? "column" : "row";
83
- if (config.kind === newConfigKind) {
84
- const newZone = {
85
- kind: "leaf",
86
- tabs: [name],
87
- tabIndex: 0,
88
- size: (totalSize * fraction) / (1 - fraction),
89
- };
90
- config = {
91
- ...config,
92
- children: zone.index === "LEFT" || zone.index === "TOP"
93
- ? [newZone, ...config.children.map(editLayout).filter((c) => c !== null)]
94
- : [...config.children.map(editLayout).filter((c) => c !== null), newZone],
95
- };
96
- }
97
- else {
98
- const newZone = {
99
- kind: "leaf",
100
- tabs: [name],
101
- tabIndex: 0,
102
- size: 50,
103
- };
104
- const oldConfig = {
105
- ...config,
106
- // Remove the panel that is being moved
107
- children: config.children.map(editLayout).filter((c) => c !== null),
108
- size: 50,
109
- };
110
- config = {
111
- kind: newConfigKind,
112
- children: zone.index === "TOP" || zone.index === "LEFT" ? [newZone, oldConfig] : [oldConfig, newZone],
113
- size: config.size,
114
- };
115
- }
116
- }
117
- // Or it's a leaf zone
118
- else if (config.kind === "leaf") {
119
- if (zone.index === "CENTER") {
120
- const newTabs = [...config.tabs.filter((tabName) => tabName !== name), name];
121
- config = {
122
- ...config,
123
- tabs: newTabs,
124
- tabIndex: newTabs.length - 1,
125
- };
126
- }
127
- else if (zone.index === "TAB") {
128
- const newTabs = [...config.tabs];
129
- let insertIndex = newTabs.findIndex((tabName) => tabName === zone.before);
130
- if (insertIndex === -1) {
131
- insertIndex = newTabs.length;
132
- }
133
- newTabs.splice(insertIndex, 0, name);
134
- config = {
135
- ...config,
136
- tabs: newTabs,
137
- tabIndex: insertIndex,
138
- };
139
- }
140
- else {
141
- const newZone = {
142
- kind: "leaf",
143
- tabs: [name],
144
- tabIndex: 0,
145
- size: 50,
146
- };
147
- config = {
148
- kind: zone.index === "TOP" || zone.index === "BOTTOM" ? "column" : "row",
149
- children: zone.index === "TOP" || zone.index === "LEFT"
150
- ? [newZone, { ...config, size: 50 }]
151
- : [{ ...config, size: 50, }, newZone],
152
- size: config.size,
153
- };
154
- }
155
- }
156
- }
157
- // If there is nothing left after removing the dropped zone
158
- else if (config === null) {
159
- return null;
160
- }
161
- // Otherwise, recurse into the node
162
- else {
163
- let hasChanged = false;
164
- if (config.kind !== "leaf") {
165
- const children = config.children
166
- .map((child) => {
167
- const updated = editLayout(child);
168
- if (updated !== child) {
169
- hasChanged = true;
170
- }
171
- return updated;
172
- })
173
- .filter((child) => child !== null);
174
- if (hasChanged) {
175
- config = { ...config, children };
176
- }
177
- }
178
- }
179
- if ((config.kind === "leaf" && config.tabs.length === 0) ||
180
- (config.kind !== "leaf" && config.children.length === 0)) {
181
- return null;
182
- }
183
- /* Simplify a node of the layout by flattening row children of rows
184
- * or column children of columns */
185
- config = simplifyLayout(config);
186
- return config;
187
- };
188
- return editLayout(inside);
189
- };
190
- /**
191
- * Filters panels from a layout configuration based on a provided list of panel names.
192
- *
193
- * Traverses the layout configuration tree and removes any panels (or tabs within panels)
194
- * whose names are not included in the specified list. It simplifies nodes by filtering
195
- * out unwanted tabs and recursively updating container nodes.
196
- *
197
- * @param names - An array of panel names that should remain in the layout.
198
- * @param inside - The layout configuration to filter.
199
- * @returns The updated layout configuration with only the specified panels, or null if empty.
200
- */
201
- export const filterPanels = (names, inside) => {
202
- const editLayout = (visitedConfig) => {
203
- let config = visitedConfig;
204
- if (config.kind === "leaf" && !config.tabs.every((name) => names.includes(name))) {
205
- /* If it's a simple leaf, try to remove the matching tab if it was
206
- * the tab that was picked by the user (since we're moving it) */
207
- config = {
208
- ...config,
209
- tabs: config.tabs.filter((name) => names.includes(name)),
210
- tabIndex: Math.min(config.tabs.length - 1, config.tabIndex),
211
- };
212
- if (config.tabs.length === 0) {
213
- config = null;
214
- }
215
- }
216
- // If there is nothing left after removing the dropped zone
217
- if (config === null) {
218
- return null;
219
- }
220
- // Otherwise, recurse into the node
221
- else {
222
- let hasChanged = false;
223
- if (config.kind !== "leaf") {
224
- const children = config.children
225
- .map((child) => {
226
- const updated = editLayout(child);
227
- if (updated !== child) {
228
- hasChanged = true;
229
- }
230
- return updated;
231
- })
232
- .filter((child) => child !== null);
233
- if (hasChanged) {
234
- config = { ...config, children };
235
- }
236
- }
237
- }
238
- if ((config.kind === "leaf" && config.tabs.length === 0) ||
239
- (config.kind !== "leaf" && config.children.length === 0)) {
240
- return null;
241
- }
242
- /* Simplify a node of the layout by flattening row children of rows
243
- * or column children of columns */
244
- config = simplifyLayout(config);
245
- return config;
246
- };
247
- return editLayout(inside);
248
- };