@sqlrooms/layout 0.28.0-rc.0 → 0.28.1-rc.0
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 +80 -71
- package/dist/LayoutSlice.d.ts +2 -0
- package/dist/LayoutSlice.d.ts.map +1 -1
- package/dist/LayoutSlice.js +189 -69
- package/dist/LayoutSlice.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
Layout slice and mosaic utilities for SQLRooms panel-based UIs.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
The `@sqlrooms/layout` package offers a set of components and utilities for creating dynamic, multi-pane layouts in SQLRooms applications. It's primarily built around the [react-mosaic](https://nomcopter.github.io/react-mosaic/) library, which provides a powerful windowing system similar to the one found in advanced IDEs.
|
|
6
|
-
|
|
7
|
-
> **Note:** This package uses [react-mosaic](https://nomcopter.github.io/react-mosaic/) which should not be confused with [uwdata/mosaic](https://github.com/uwdata/mosaic) used in the [`@sqlrooms/mosaic`](/api/mosaic/) package for data visualization purposes.
|
|
3
|
+
This package uses `react-mosaic` to compose resizable panel layouts.
|
|
8
4
|
|
|
9
5
|
## Installation
|
|
10
6
|
|
|
@@ -12,78 +8,91 @@ The `@sqlrooms/layout` package offers a set of components and utilities for crea
|
|
|
12
8
|
npm install @sqlrooms/layout
|
|
13
9
|
```
|
|
14
10
|
|
|
15
|
-
##
|
|
11
|
+
## Main exports
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
- `createLayoutSlice()`, `useStoreWithLayout()`
|
|
14
|
+
- `MosaicLayout` component
|
|
15
|
+
- mosaic helpers:
|
|
16
|
+
- `makeMosaicStack`
|
|
17
|
+
- `visitMosaicLeafNodes`
|
|
18
|
+
- `getVisibleMosaicLayoutPanels`
|
|
19
|
+
- `findMosaicNodePathByKey`
|
|
20
|
+
- `removeMosaicNodeByKey`
|
|
21
|
+
- layout config schemas/types re-exported from `@sqlrooms/layout-config`
|
|
22
|
+
|
|
23
|
+
## Store usage
|
|
18
24
|
|
|
19
25
|
```tsx
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
26
|
+
import {
|
|
27
|
+
LayoutSliceState,
|
|
28
|
+
LayoutTypes,
|
|
29
|
+
MAIN_VIEW,
|
|
30
|
+
createLayoutSlice,
|
|
31
|
+
} from '@sqlrooms/layout';
|
|
32
|
+
import {
|
|
33
|
+
BaseRoomStoreState,
|
|
34
|
+
createBaseRoomSlice,
|
|
35
|
+
createRoomStore,
|
|
36
|
+
} from '@sqlrooms/room-store';
|
|
37
|
+
|
|
38
|
+
function DataPanel() {
|
|
39
|
+
return <div>Data</div>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function MainPanel() {
|
|
43
|
+
return <div>Main</div>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type State = BaseRoomStoreState & LayoutSliceState;
|
|
47
|
+
|
|
48
|
+
export const {roomStore, useRoomStore} = createRoomStore<State>(
|
|
49
|
+
(set, get, store) => ({
|
|
50
|
+
...createBaseRoomSlice()(set, get, store),
|
|
51
|
+
...createLayoutSlice({
|
|
52
|
+
config: {
|
|
53
|
+
type: LayoutTypes.enum.mosaic,
|
|
54
|
+
nodes: {
|
|
55
|
+
direction: 'row',
|
|
56
|
+
first: 'data',
|
|
57
|
+
second: MAIN_VIEW,
|
|
58
|
+
splitPercentage: 30,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
panels: {
|
|
62
|
+
data: {
|
|
63
|
+
title: 'Data',
|
|
64
|
+
component: DataPanel,
|
|
65
|
+
placement: 'sidebar',
|
|
66
|
+
},
|
|
67
|
+
main: {
|
|
68
|
+
title: 'Main',
|
|
69
|
+
component: MainPanel,
|
|
70
|
+
placement: 'main',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
})(set, get, store),
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
47
76
|
```
|
|
48
77
|
|
|
49
|
-
##
|
|
50
|
-
|
|
51
|
-
### MosaicLayout
|
|
52
|
-
|
|
53
|
-
A wrapper around the `Mosaic` component from react-mosaic-component that provides a customized look and feel consistent with SQLRooms styling.
|
|
78
|
+
## Programmatic panel visibility
|
|
54
79
|
|
|
55
80
|
```tsx
|
|
56
|
-
import {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
81
|
+
import {Button} from '@sqlrooms/ui';
|
|
82
|
+
|
|
83
|
+
function PanelButtons() {
|
|
84
|
+
const togglePanel = useRoomStore((state) => state.layout.togglePanel);
|
|
85
|
+
const togglePanelPin = useRoomStore((state) => state.layout.togglePanelPin);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className="flex gap-2">
|
|
89
|
+
<Button onClick={() => togglePanel('data')}>Toggle Data Panel</Button>
|
|
90
|
+
<Button onClick={() => togglePanelPin('data')}>Pin/Unpin Data Panel</Button>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
64
94
|
```
|
|
65
95
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
A component for rendering individual tiles within the mosaic layout.
|
|
69
|
-
|
|
70
|
-
## Utility Functions
|
|
71
|
-
|
|
72
|
-
The package provides several utility functions for working with mosaic layouts:
|
|
73
|
-
|
|
74
|
-
- `makeMosaicStack`: Creates a stack of mosaic nodes with specified weights and direction
|
|
75
|
-
- `visitMosaicLeafNodes`: Traverses all leaf nodes in a mosaic tree
|
|
76
|
-
- `getVisibleMosaicLayoutPanels`: Gets an array of all visible panel IDs
|
|
77
|
-
- `findMosaicNodePathByKey`: Finds the path to a specific node by its key
|
|
78
|
-
- `removeMosaicNodeByKey`: Removes a node from the mosaic tree by its key
|
|
79
|
-
|
|
80
|
-
## Learn More
|
|
81
|
-
|
|
82
|
-
For more information about the underlying react-mosaic library, visit:
|
|
83
|
-
|
|
84
|
-
- [react-mosaic documentation](https://nomcopter.github.io/react-mosaic/)
|
|
85
|
-
- [react-mosaic GitHub repository](https://github.com/nomcopter/react-mosaic)
|
|
86
|
-
|
|
87
|
-
## License
|
|
96
|
+
## Note
|
|
88
97
|
|
|
89
|
-
|
|
98
|
+
`@sqlrooms/layout` (react-mosaic layout) is different from `@sqlrooms/mosaic` (UW IDL data visualization package).
|
package/dist/LayoutSlice.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export type LayoutSliceConfig = z.infer<typeof LayoutSliceConfig>;
|
|
|
20
20
|
export declare function createDefaultLayoutConfig(): LayoutSliceConfig;
|
|
21
21
|
export type LayoutSliceState = {
|
|
22
22
|
layout: {
|
|
23
|
+
initialize?: () => Promise<void>;
|
|
24
|
+
destroy?: () => Promise<void>;
|
|
23
25
|
config: LayoutSliceConfig;
|
|
24
26
|
panels: Record<string, RoomPanelInfo>;
|
|
25
27
|
setConfig(layout: LayoutConfig): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LayoutSlice.d.ts","sourceRoot":"","sources":["../src/LayoutSlice.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,YAAY,EAGb,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"LayoutSlice.d.ts","sourceRoot":"","sources":["../src/LayoutSlice.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,YAAY,EAGb,MAAM,yBAAyB,CAAC;AAUjC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,EAAC,YAAY,EAAC,MAAM,SAAS,CAAC;AAwBrC,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;IACjD,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC;IAC/B,SAAS,EAAE,SAAS,GAAG,gBAAgB,GAAG,MAAM,GAAG,MAAM,CAAC;CAC3D,CAAC;AAEF,eAAO,MAAM,iBAAiB;;;;;2BAAe,CAAC;AAE9C,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,wBAAgB,yBAAyB,IAAI,iBAAiB,CAE7D;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE;QACN,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;QACjC,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,EAAE,iBAAiB,CAAC;QAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;QACtC,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;QACtC,wCAAwC;QACxC,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;QACtC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;QACrD,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;KACzC,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CACxC,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EAAE,aAA2C,EACnD,MAAW,GACZ,GAAE,sBAA2B,GAAG,YAAY,CAAC,gBAAgB,CAAC,CA0H9D;AAmGD,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,CAAC,GACvC,CAAC,CAEH"}
|
package/dist/LayoutSlice.js
CHANGED
|
@@ -1,92 +1,212 @@
|
|
|
1
1
|
import { DEFAULT_MOSAIC_LAYOUT, LayoutConfig, MAIN_VIEW, isMosaicLayoutParent, } from '@sqlrooms/layout-config';
|
|
2
|
-
import { createSlice, useBaseRoomStore } from '@sqlrooms/room-store';
|
|
2
|
+
import { createSlice, registerCommandsForOwner, unregisterCommandsForOwner, useBaseRoomStore, } from '@sqlrooms/room-store';
|
|
3
3
|
import { produce } from 'immer';
|
|
4
|
+
import { z } from 'zod';
|
|
4
5
|
import { makeMosaicStack, removeMosaicNodeByKey } from './mosaic';
|
|
6
|
+
const LAYOUT_COMMAND_OWNER = '@sqlrooms/layout/panels';
|
|
7
|
+
const ToggleLayoutPanelCommandInput = z.object({
|
|
8
|
+
panelId: z
|
|
9
|
+
.string()
|
|
10
|
+
.describe('ID of the panel to show/hide, e.g. "sql-editor".'),
|
|
11
|
+
show: z
|
|
12
|
+
.boolean()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe('Optional explicit visibility. True shows, false hides, omitted toggles.'),
|
|
15
|
+
});
|
|
16
|
+
const ToggleLayoutPanelPinInput = z.object({
|
|
17
|
+
panelId: z.string().describe('ID of the panel to pin/unpin.'),
|
|
18
|
+
});
|
|
5
19
|
export const LayoutSliceConfig = LayoutConfig;
|
|
6
20
|
export function createDefaultLayoutConfig() {
|
|
7
21
|
return DEFAULT_MOSAIC_LAYOUT;
|
|
8
22
|
}
|
|
9
23
|
export function createLayoutSlice({ config: initialConfig = createDefaultLayoutConfig(), panels = {}, } = {}) {
|
|
10
|
-
return createSlice((set, get) =>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
panels
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
return createSlice((set, get, store) => {
|
|
25
|
+
let unsubscribePanelChanges;
|
|
26
|
+
const registerPanelCommands = () => {
|
|
27
|
+
const panelCommands = createLayoutPanelCommands(get().layout.panels);
|
|
28
|
+
registerCommandsForOwner(store, LAYOUT_COMMAND_OWNER, panelCommands);
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
layout: {
|
|
32
|
+
initialize: async () => {
|
|
33
|
+
registerPanelCommands();
|
|
34
|
+
unsubscribePanelChanges?.();
|
|
35
|
+
unsubscribePanelChanges = store.subscribe((state, prevState) => {
|
|
36
|
+
if (state.layout.panels !== prevState.layout.panels) {
|
|
37
|
+
registerPanelCommands();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
destroy: async () => {
|
|
42
|
+
unsubscribePanelChanges?.();
|
|
43
|
+
unsubscribePanelChanges = undefined;
|
|
44
|
+
unregisterCommandsForOwner(store, LAYOUT_COMMAND_OWNER);
|
|
45
|
+
},
|
|
46
|
+
config: initialConfig,
|
|
47
|
+
panels,
|
|
48
|
+
setConfig: (config) => set((state) => produce(state, (draft) => {
|
|
49
|
+
draft.layout.config = config;
|
|
50
|
+
})),
|
|
51
|
+
setLayout: (layout) => get().layout.setConfig(layout),
|
|
52
|
+
togglePanel: (panel, show) => {
|
|
53
|
+
if (get().layout.config?.nodes === panel) {
|
|
54
|
+
// don't hide the view if it's the only one
|
|
27
55
|
return;
|
|
28
56
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
|
|
57
|
+
const result = removeMosaicNodeByKey(get().layout.config?.nodes, panel);
|
|
58
|
+
const isShown = result.success;
|
|
59
|
+
if (isShown) {
|
|
60
|
+
if (show || panel === MAIN_VIEW /*&& areViewsReadyToRender()*/) {
|
|
61
|
+
return;
|
|
34
62
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
63
|
+
set((state) => produce(state, (draft) => {
|
|
64
|
+
const layout = draft.layout.config;
|
|
65
|
+
layout.nodes = result.nextTree;
|
|
66
|
+
if (layout.pinned?.includes(panel)) {
|
|
67
|
+
layout.pinned = layout.pinned.filter((p) => p !== panel);
|
|
68
|
+
}
|
|
69
|
+
}));
|
|
40
70
|
}
|
|
71
|
+
else {
|
|
72
|
+
if (show === false) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
set((state) => produce(state, (draft) => {
|
|
76
|
+
const layout = draft.layout.config;
|
|
77
|
+
const root = layout.nodes;
|
|
78
|
+
const placement = draft.layout.panels[panel]?.placement;
|
|
79
|
+
const side = placement === 'sidebar' ? 'first' : 'second';
|
|
80
|
+
const toReplace = isMosaicLayoutParent(root)
|
|
81
|
+
? root[side]
|
|
82
|
+
: undefined;
|
|
83
|
+
if (toReplace &&
|
|
84
|
+
isMosaicLayoutParent(root) &&
|
|
85
|
+
!isMosaicLayoutParent(toReplace) &&
|
|
86
|
+
toReplace !== MAIN_VIEW &&
|
|
87
|
+
!layout.fixed?.includes(toReplace) &&
|
|
88
|
+
!layout.pinned?.includes(toReplace)) {
|
|
89
|
+
// replace first un-pinned leaf
|
|
90
|
+
root[side] = panel;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
const panelNode = { node: panel, weight: 1 };
|
|
94
|
+
const restNode = {
|
|
95
|
+
node: draft.layout.config?.nodes,
|
|
96
|
+
weight: 3,
|
|
97
|
+
};
|
|
98
|
+
// add to to the left
|
|
99
|
+
layout.nodes = makeMosaicStack(placement === 'sidebar-bottom' ? 'column' : 'row', side === 'first'
|
|
100
|
+
? [panelNode, restNode]
|
|
101
|
+
: [restNode, panelNode]);
|
|
102
|
+
}
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
/**
|
|
107
|
+
* Toggle the pin state of a panel.
|
|
108
|
+
* @param panel - The panel to toggle the pin state of.
|
|
109
|
+
*/
|
|
110
|
+
togglePanelPin: (panel) => {
|
|
41
111
|
set((state) => produce(state, (draft) => {
|
|
42
112
|
const layout = draft.layout.config;
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const toReplace = isMosaicLayoutParent(root)
|
|
47
|
-
? root[side]
|
|
48
|
-
: undefined;
|
|
49
|
-
if (toReplace &&
|
|
50
|
-
isMosaicLayoutParent(root) &&
|
|
51
|
-
!isMosaicLayoutParent(toReplace) &&
|
|
52
|
-
toReplace !== MAIN_VIEW &&
|
|
53
|
-
!layout.fixed?.includes(toReplace) &&
|
|
54
|
-
!layout.pinned?.includes(toReplace)) {
|
|
55
|
-
// replace first un-pinned leaf
|
|
56
|
-
root[side] = panel;
|
|
113
|
+
const pinned = layout.pinned ?? [];
|
|
114
|
+
if (pinned.includes(panel)) {
|
|
115
|
+
layout.pinned = pinned.filter((p) => p !== panel);
|
|
57
116
|
}
|
|
58
117
|
else {
|
|
59
|
-
|
|
60
|
-
const restNode = {
|
|
61
|
-
node: draft.layout.config?.nodes,
|
|
62
|
-
weight: 3,
|
|
63
|
-
};
|
|
64
|
-
// add to to the left
|
|
65
|
-
layout.nodes = makeMosaicStack(placement === 'sidebar-bottom' ? 'column' : 'row', side === 'first'
|
|
66
|
-
? [panelNode, restNode]
|
|
67
|
-
: [restNode, panelNode]);
|
|
118
|
+
layout.pinned = [...pinned, panel];
|
|
68
119
|
}
|
|
69
120
|
}));
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
/**
|
|
73
|
-
* Toggle the pin state of a panel.
|
|
74
|
-
* @param panel - The panel to toggle the pin state of.
|
|
75
|
-
*/
|
|
76
|
-
togglePanelPin: (panel) => {
|
|
77
|
-
set((state) => produce(state, (draft) => {
|
|
78
|
-
const layout = draft.layout.config;
|
|
79
|
-
const pinned = layout.pinned ?? [];
|
|
80
|
-
if (pinned.includes(panel)) {
|
|
81
|
-
layout.pinned = pinned.filter((p) => p !== panel);
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
layout.pinned = [...pinned, panel];
|
|
85
|
-
}
|
|
86
|
-
}));
|
|
121
|
+
},
|
|
87
122
|
},
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function createLayoutPanelCommands(panels) {
|
|
127
|
+
const byIdCommand = {
|
|
128
|
+
id: 'layout.panel.toggle',
|
|
129
|
+
name: 'Toggle panel by ID',
|
|
130
|
+
description: 'Show or hide a panel by its ID',
|
|
131
|
+
group: 'Layout',
|
|
132
|
+
keywords: ['layout', 'panel', 'toggle', 'show', 'hide', 'id'],
|
|
133
|
+
inputSchema: ToggleLayoutPanelCommandInput,
|
|
134
|
+
inputDescription: 'Provide panelId and optional show. If show is omitted, the panel visibility is toggled.',
|
|
135
|
+
metadata: {
|
|
136
|
+
readOnly: false,
|
|
137
|
+
idempotent: true,
|
|
138
|
+
riskLevel: 'low',
|
|
139
|
+
},
|
|
140
|
+
validateInput: (input, { getState }) => {
|
|
141
|
+
const { panelId } = input;
|
|
142
|
+
if (!getState().layout.panels[panelId]) {
|
|
143
|
+
throw new Error(`Unknown panel ID "${panelId}".`);
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
execute: ({ getState }, input) => {
|
|
147
|
+
const { panelId, show } = input;
|
|
148
|
+
getState().layout.togglePanel(panelId, show);
|
|
149
|
+
return {
|
|
150
|
+
success: true,
|
|
151
|
+
commandId: 'layout.panel.toggle',
|
|
152
|
+
message: `Toggled panel "${panelId}".`,
|
|
153
|
+
};
|
|
88
154
|
},
|
|
89
|
-
}
|
|
155
|
+
};
|
|
156
|
+
const pinByIdCommand = {
|
|
157
|
+
id: 'layout.panel.toggle-pin',
|
|
158
|
+
name: 'Toggle panel pin by ID',
|
|
159
|
+
description: 'Pin or unpin a panel by its ID',
|
|
160
|
+
group: 'Layout',
|
|
161
|
+
keywords: ['layout', 'panel', 'pin', 'unpin', 'id'],
|
|
162
|
+
inputSchema: ToggleLayoutPanelPinInput,
|
|
163
|
+
inputDescription: 'Provide panelId to toggle pin state.',
|
|
164
|
+
metadata: {
|
|
165
|
+
readOnly: false,
|
|
166
|
+
idempotent: true,
|
|
167
|
+
riskLevel: 'low',
|
|
168
|
+
},
|
|
169
|
+
validateInput: (input, { getState }) => {
|
|
170
|
+
const { panelId } = input;
|
|
171
|
+
if (!getState().layout.panels[panelId]) {
|
|
172
|
+
throw new Error(`Unknown panel ID "${panelId}".`);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
execute: ({ getState }, input) => {
|
|
176
|
+
const { panelId } = input;
|
|
177
|
+
getState().layout.togglePanelPin(panelId);
|
|
178
|
+
return {
|
|
179
|
+
success: true,
|
|
180
|
+
commandId: 'layout.panel.toggle-pin',
|
|
181
|
+
message: `Toggled pin state for panel "${panelId}".`,
|
|
182
|
+
};
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
const panelShortcutCommands = Object.entries(panels).map(([panelId, panelInfo]) => {
|
|
186
|
+
const title = panelInfo.title ?? panelId;
|
|
187
|
+
const keywords = [panelId, panelInfo.title, panelInfo.placement].filter((value) => Boolean(value));
|
|
188
|
+
return {
|
|
189
|
+
id: `layout.panel.toggle.${panelId}`,
|
|
190
|
+
name: `Toggle panel: ${title}`,
|
|
191
|
+
description: `Show or hide the ${title} panel`,
|
|
192
|
+
group: 'Layout',
|
|
193
|
+
keywords,
|
|
194
|
+
metadata: {
|
|
195
|
+
readOnly: false,
|
|
196
|
+
idempotent: true,
|
|
197
|
+
riskLevel: 'low',
|
|
198
|
+
},
|
|
199
|
+
execute: ({ getState }) => {
|
|
200
|
+
getState().layout.togglePanel(panelId);
|
|
201
|
+
return {
|
|
202
|
+
success: true,
|
|
203
|
+
commandId: `layout.panel.toggle.${panelId}`,
|
|
204
|
+
message: `Toggled panel "${panelId}".`,
|
|
205
|
+
};
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
return [byIdCommand, pinByIdCommand, ...panelShortcutCommands];
|
|
90
210
|
}
|
|
91
211
|
export function useStoreWithLayout(selector) {
|
|
92
212
|
return useBaseRoomStore((state) => selector(state));
|
package/dist/LayoutSlice.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LayoutSlice.js","sourceRoot":"","sources":["../src/LayoutSlice.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DEFAULT_MOSAIC_LAYOUT,\n LayoutConfig,\n MAIN_VIEW,\n isMosaicLayoutParent,\n} from '@sqlrooms/layout-config';\nimport {createSlice, useBaseRoomStore} from '@sqlrooms/room-store';\nimport {produce} from 'immer';\nimport React from 'react';\nimport {z} from 'zod';\nimport {StateCreator} from 'zustand';\nimport {makeMosaicStack, removeMosaicNodeByKey} from './mosaic';\n\nexport type RoomPanelInfo = {\n title?: string;\n icon?: React.ComponentType<{className?: string}>;\n component: React.ComponentType;\n placement: 'sidebar' | 'sidebar-bottom' | 'main' | string;\n};\n\nexport const LayoutSliceConfig = LayoutConfig;\n\nexport type LayoutSliceConfig = z.infer<typeof LayoutSliceConfig>;\n\nexport function createDefaultLayoutConfig(): LayoutSliceConfig {\n return DEFAULT_MOSAIC_LAYOUT;\n}\n\nexport type LayoutSliceState = {\n layout: {\n config: LayoutSliceConfig;\n panels: Record<string, RoomPanelInfo>;\n setConfig(layout: LayoutConfig): void;\n /** @deprecated Use setConfig instead */\n setLayout(layout: LayoutConfig): void;\n togglePanel: (panel: string, show?: boolean) => void;\n togglePanelPin: (panel: string) => void;\n };\n};\n\nexport type CreateLayoutSliceProps = {\n config?: LayoutSliceConfig;\n panels?: Record<string, RoomPanelInfo>;\n};\n\nexport function createLayoutSlice({\n config: initialConfig = createDefaultLayoutConfig(),\n panels = {},\n}: CreateLayoutSliceProps = {}): StateCreator<LayoutSliceState> {\n return createSlice<LayoutSliceState>((set, get) => ({\n layout: {\n config: initialConfig,\n panels,\n setConfig: (config) =>\n set((state) =>\n produce(state, (draft) => {\n draft.layout.config = config;\n }),\n ),\n setLayout: (layout) => get().layout.setConfig(layout),\n togglePanel: (panel, show) => {\n if (get().layout.config?.nodes === panel) {\n // don't hide the view if it's the only one\n return;\n }\n const result = removeMosaicNodeByKey(get().layout.config?.nodes, panel);\n const isShown = result.success;\n if (isShown) {\n if (show || panel === MAIN_VIEW /*&& areViewsReadyToRender()*/) {\n return;\n }\n set((state) =>\n produce(state, (draft) => {\n const layout = draft.layout.config;\n layout.nodes = result.nextTree;\n if (layout.pinned?.includes(panel)) {\n layout.pinned = layout.pinned.filter(\n (p: string) => p !== panel,\n );\n }\n }),\n );\n } else {\n if (show === false) {\n return;\n }\n set((state) =>\n produce(state, (draft) => {\n const layout = draft.layout.config;\n const root = layout.nodes;\n const placement = draft.layout.panels[panel]?.placement;\n const side = placement === 'sidebar' ? 'first' : 'second';\n const toReplace = isMosaicLayoutParent(root)\n ? root[side]\n : undefined;\n if (\n toReplace &&\n isMosaicLayoutParent(root) &&\n !isMosaicLayoutParent(toReplace) &&\n toReplace !== MAIN_VIEW &&\n !layout.fixed?.includes(toReplace) &&\n !layout.pinned?.includes(toReplace)\n ) {\n // replace first un-pinned leaf\n root[side] = panel;\n } else {\n const panelNode = {node: panel, weight: 1};\n const restNode = {\n node: draft.layout.config?.nodes,\n weight: 3,\n };\n // add to to the left\n layout.nodes = makeMosaicStack(\n placement === 'sidebar-bottom' ? 'column' : 'row',\n side === 'first'\n ? [panelNode, restNode]\n : [restNode, panelNode],\n );\n }\n }),\n );\n }\n },\n\n /**\n * Toggle the pin state of a panel.\n * @param panel - The panel to toggle the pin state of.\n */\n togglePanelPin: (panel: string) => {\n set((state) =>\n produce(state, (draft) => {\n const layout = draft.layout.config;\n const pinned = layout.pinned ?? [];\n if (pinned.includes(panel)) {\n layout.pinned = pinned.filter((p: string) => p !== panel);\n } else {\n layout.pinned = [...pinned, panel];\n }\n }),\n );\n },\n },\n }));\n}\n\nexport function useStoreWithLayout<T>(\n selector: (state: LayoutSliceState) => T,\n): T {\n return useBaseRoomStore<LayoutSliceState, T>((state) => selector(state));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"LayoutSlice.js","sourceRoot":"","sources":["../src/LayoutSlice.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DEFAULT_MOSAIC_LAYOUT,\n LayoutConfig,\n MAIN_VIEW,\n isMosaicLayoutParent,\n} from '@sqlrooms/layout-config';\nimport {\n BaseRoomStoreState,\n createSlice,\n registerCommandsForOwner,\n RoomCommand,\n unregisterCommandsForOwner,\n useBaseRoomStore,\n} from '@sqlrooms/room-store';\nimport {produce} from 'immer';\nimport React from 'react';\nimport {z} from 'zod';\nimport {StateCreator} from 'zustand';\nimport {makeMosaicStack, removeMosaicNodeByKey} from './mosaic';\n\nconst LAYOUT_COMMAND_OWNER = '@sqlrooms/layout/panels';\nconst ToggleLayoutPanelCommandInput = z.object({\n panelId: z\n .string()\n .describe('ID of the panel to show/hide, e.g. \"sql-editor\".'),\n show: z\n .boolean()\n .optional()\n .describe(\n 'Optional explicit visibility. True shows, false hides, omitted toggles.',\n ),\n});\ntype ToggleLayoutPanelCommandInput = z.infer<\n typeof ToggleLayoutPanelCommandInput\n>;\n\nconst ToggleLayoutPanelPinInput = z.object({\n panelId: z.string().describe('ID of the panel to pin/unpin.'),\n});\ntype ToggleLayoutPanelPinInput = z.infer<typeof ToggleLayoutPanelPinInput>;\n\nexport type RoomPanelInfo = {\n title?: string;\n icon?: React.ComponentType<{className?: string}>;\n component: React.ComponentType;\n placement: 'sidebar' | 'sidebar-bottom' | 'main' | string;\n};\n\nexport const LayoutSliceConfig = LayoutConfig;\n\nexport type LayoutSliceConfig = z.infer<typeof LayoutSliceConfig>;\n\nexport function createDefaultLayoutConfig(): LayoutSliceConfig {\n return DEFAULT_MOSAIC_LAYOUT;\n}\n\nexport type LayoutSliceState = {\n layout: {\n initialize?: () => Promise<void>;\n destroy?: () => Promise<void>;\n config: LayoutSliceConfig;\n panels: Record<string, RoomPanelInfo>;\n setConfig(layout: LayoutConfig): void;\n /** @deprecated Use setConfig instead */\n setLayout(layout: LayoutConfig): void;\n togglePanel: (panel: string, show?: boolean) => void;\n togglePanelPin: (panel: string) => void;\n };\n};\n\nexport type CreateLayoutSliceProps = {\n config?: LayoutSliceConfig;\n panels?: Record<string, RoomPanelInfo>;\n};\n\nexport function createLayoutSlice({\n config: initialConfig = createDefaultLayoutConfig(),\n panels = {},\n}: CreateLayoutSliceProps = {}): StateCreator<LayoutSliceState> {\n return createSlice<LayoutSliceState, BaseRoomStoreState & LayoutSliceState>(\n (set, get, store) => {\n let unsubscribePanelChanges: (() => void) | undefined;\n const registerPanelCommands = () => {\n const panelCommands = createLayoutPanelCommands(get().layout.panels);\n registerCommandsForOwner(store, LAYOUT_COMMAND_OWNER, panelCommands);\n };\n\n return {\n layout: {\n initialize: async () => {\n registerPanelCommands();\n unsubscribePanelChanges?.();\n unsubscribePanelChanges = store.subscribe((state, prevState) => {\n if (state.layout.panels !== prevState.layout.panels) {\n registerPanelCommands();\n }\n });\n },\n destroy: async () => {\n unsubscribePanelChanges?.();\n unsubscribePanelChanges = undefined;\n unregisterCommandsForOwner(store, LAYOUT_COMMAND_OWNER);\n },\n config: initialConfig,\n panels,\n setConfig: (config) =>\n set((state) =>\n produce(state, (draft) => {\n draft.layout.config = config;\n }),\n ),\n setLayout: (layout) => get().layout.setConfig(layout),\n togglePanel: (panel, show) => {\n if (get().layout.config?.nodes === panel) {\n // don't hide the view if it's the only one\n return;\n }\n const result = removeMosaicNodeByKey(\n get().layout.config?.nodes,\n panel,\n );\n const isShown = result.success;\n if (isShown) {\n if (show || panel === MAIN_VIEW /*&& areViewsReadyToRender()*/) {\n return;\n }\n set((state) =>\n produce(state, (draft) => {\n const layout = draft.layout.config;\n layout.nodes = result.nextTree;\n if (layout.pinned?.includes(panel)) {\n layout.pinned = layout.pinned.filter(\n (p: string) => p !== panel,\n );\n }\n }),\n );\n } else {\n if (show === false) {\n return;\n }\n set((state) =>\n produce(state, (draft) => {\n const layout = draft.layout.config;\n const root = layout.nodes;\n const placement = draft.layout.panels[panel]?.placement;\n const side = placement === 'sidebar' ? 'first' : 'second';\n const toReplace = isMosaicLayoutParent(root)\n ? root[side]\n : undefined;\n if (\n toReplace &&\n isMosaicLayoutParent(root) &&\n !isMosaicLayoutParent(toReplace) &&\n toReplace !== MAIN_VIEW &&\n !layout.fixed?.includes(toReplace) &&\n !layout.pinned?.includes(toReplace)\n ) {\n // replace first un-pinned leaf\n root[side] = panel;\n } else {\n const panelNode = {node: panel, weight: 1};\n const restNode = {\n node: draft.layout.config?.nodes,\n weight: 3,\n };\n // add to to the left\n layout.nodes = makeMosaicStack(\n placement === 'sidebar-bottom' ? 'column' : 'row',\n side === 'first'\n ? [panelNode, restNode]\n : [restNode, panelNode],\n );\n }\n }),\n );\n }\n },\n\n /**\n * Toggle the pin state of a panel.\n * @param panel - The panel to toggle the pin state of.\n */\n togglePanelPin: (panel: string) => {\n set((state) =>\n produce(state, (draft) => {\n const layout = draft.layout.config;\n const pinned = layout.pinned ?? [];\n if (pinned.includes(panel)) {\n layout.pinned = pinned.filter((p: string) => p !== panel);\n } else {\n layout.pinned = [...pinned, panel];\n }\n }),\n );\n },\n },\n };\n },\n );\n}\n\ntype LayoutCommandStoreState = BaseRoomStoreState & LayoutSliceState;\n\nfunction createLayoutPanelCommands(\n panels: Record<string, RoomPanelInfo>,\n): RoomCommand<LayoutCommandStoreState>[] {\n const byIdCommand: RoomCommand<LayoutCommandStoreState> = {\n id: 'layout.panel.toggle',\n name: 'Toggle panel by ID',\n description: 'Show or hide a panel by its ID',\n group: 'Layout',\n keywords: ['layout', 'panel', 'toggle', 'show', 'hide', 'id'],\n inputSchema: ToggleLayoutPanelCommandInput,\n inputDescription:\n 'Provide panelId and optional show. If show is omitted, the panel visibility is toggled.',\n metadata: {\n readOnly: false,\n idempotent: true,\n riskLevel: 'low',\n },\n validateInput: (input, {getState}) => {\n const {panelId} = input as ToggleLayoutPanelCommandInput;\n if (!getState().layout.panels[panelId]) {\n throw new Error(`Unknown panel ID \"${panelId}\".`);\n }\n },\n execute: ({getState}, input) => {\n const {panelId, show} = input as ToggleLayoutPanelCommandInput;\n getState().layout.togglePanel(panelId, show);\n return {\n success: true,\n commandId: 'layout.panel.toggle',\n message: `Toggled panel \"${panelId}\".`,\n };\n },\n };\n\n const pinByIdCommand: RoomCommand<LayoutCommandStoreState> = {\n id: 'layout.panel.toggle-pin',\n name: 'Toggle panel pin by ID',\n description: 'Pin or unpin a panel by its ID',\n group: 'Layout',\n keywords: ['layout', 'panel', 'pin', 'unpin', 'id'],\n inputSchema: ToggleLayoutPanelPinInput,\n inputDescription: 'Provide panelId to toggle pin state.',\n metadata: {\n readOnly: false,\n idempotent: true,\n riskLevel: 'low',\n },\n validateInput: (input, {getState}) => {\n const {panelId} = input as ToggleLayoutPanelPinInput;\n if (!getState().layout.panels[panelId]) {\n throw new Error(`Unknown panel ID \"${panelId}\".`);\n }\n },\n execute: ({getState}, input) => {\n const {panelId} = input as ToggleLayoutPanelPinInput;\n getState().layout.togglePanelPin(panelId);\n return {\n success: true,\n commandId: 'layout.panel.toggle-pin',\n message: `Toggled pin state for panel \"${panelId}\".`,\n };\n },\n };\n\n const panelShortcutCommands: RoomCommand<LayoutCommandStoreState>[] =\n Object.entries(panels).map(([panelId, panelInfo]) => {\n const title = panelInfo.title ?? panelId;\n const keywords = [panelId, panelInfo.title, panelInfo.placement].filter(\n (value): value is string => Boolean(value),\n );\n return {\n id: `layout.panel.toggle.${panelId}`,\n name: `Toggle panel: ${title}`,\n description: `Show or hide the ${title} panel`,\n group: 'Layout',\n keywords,\n metadata: {\n readOnly: false,\n idempotent: true,\n riskLevel: 'low',\n },\n execute: ({getState}) => {\n getState().layout.togglePanel(panelId);\n return {\n success: true,\n commandId: `layout.panel.toggle.${panelId}`,\n message: `Toggled panel \"${panelId}\".`,\n };\n },\n };\n });\n\n return [byIdCommand, pinByIdCommand, ...panelShortcutCommands];\n}\n\nexport function useStoreWithLayout<T>(\n selector: (state: LayoutSliceState) => T,\n): T {\n return useBaseRoomStore<LayoutSliceState, T>((state) => selector(state));\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sqlrooms/layout",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.1-rc.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@sqlrooms/layout-config": "0.28.
|
|
25
|
-
"@sqlrooms/room-store": "0.28.
|
|
26
|
-
"@sqlrooms/ui": "0.28.
|
|
24
|
+
"@sqlrooms/layout-config": "0.28.1-rc.0",
|
|
25
|
+
"@sqlrooms/room-store": "0.28.1-rc.0",
|
|
26
|
+
"@sqlrooms/ui": "0.28.1-rc.0",
|
|
27
27
|
"immer": "^11.0.1",
|
|
28
28
|
"zod": "^4.1.8",
|
|
29
29
|
"zustand": "^5.0.8"
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"typecheck": "tsc --noEmit",
|
|
41
41
|
"typedoc": "typedoc"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "1e0dcae95d1ccdbcd1b32df1d647d0f794b94e5e"
|
|
44
44
|
}
|