@jbrowse/app-core 4.0.2 → 4.0.4
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/esm/DockviewLayout/index.d.ts +9 -0
- package/esm/DockviewLayout/index.js +5 -1
- package/esm/ui/App/AppToolbar.js +16 -10
- package/esm/ui/App/DockviewContext.d.ts +7 -0
- package/esm/ui/App/DockviewContext.js +14 -6
- package/esm/ui/App/JBrowseViewPanel.js +1 -10
- package/esm/ui/App/JBrowseViewTab.js +1 -10
- package/esm/ui/App/TiledViewsContainer.js +169 -53
- package/esm/ui/App/ViewMenu.js +12 -24
- package/esm/ui/App/copyView.js +2 -2
- package/esm/ui/App/dockviewUtils.d.ts +2 -0
- package/esm/ui/App/dockviewUtils.js +10 -0
- package/package.json +3 -3
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import type { IAnyStateTreeNode, Instance } from '@jbrowse/mobx-state-tree';
|
|
2
2
|
import type { SerializedDockview } from 'dockview-react';
|
|
3
|
+
export interface DockviewLayoutNode {
|
|
4
|
+
viewIds?: string[];
|
|
5
|
+
direction?: 'horizontal' | 'vertical';
|
|
6
|
+
children?: DockviewLayoutNode[];
|
|
7
|
+
size?: number;
|
|
8
|
+
}
|
|
3
9
|
export declare function DockviewLayoutMixin(): import("@jbrowse/mobx-state-tree").IModelType<{
|
|
4
10
|
dockviewLayout: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").IType<SerializedDockview, SerializedDockview, SerializedDockview>>;
|
|
5
11
|
panelViewAssignments: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IMapType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").ISimpleType<string>>>, [undefined]>;
|
|
12
|
+
init: import("@jbrowse/mobx-state-tree").IType<DockviewLayoutNode | undefined, DockviewLayoutNode | undefined, DockviewLayoutNode | undefined>;
|
|
6
13
|
activePanelId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
|
|
7
14
|
}, {
|
|
8
15
|
getViewIdsForPanel(panelId: string): never[] | (import("@jbrowse/mobx-state-tree").IMSTArray<import("@jbrowse/mobx-state-tree").ISimpleType<string>> & import("@jbrowse/mobx-state-tree").IStateTreeNode<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").ISimpleType<string>>>);
|
|
9
16
|
} & {
|
|
10
17
|
setDockviewLayout(layout: SerializedDockview | undefined): void;
|
|
11
18
|
setActivePanelId(panelId: string | undefined): void;
|
|
19
|
+
setInit(init: DockviewLayoutNode | undefined): void;
|
|
12
20
|
assignViewToPanel(panelId: string, viewId: string): void;
|
|
13
21
|
removeViewFromPanel(viewId: string): void;
|
|
14
22
|
removePanel(panelId: string): void;
|
|
@@ -19,6 +27,7 @@ export declare function DockviewLayoutMixin(): import("@jbrowse/mobx-state-tree"
|
|
|
19
27
|
}, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree").ModelSnapshotType<{
|
|
20
28
|
dockviewLayout: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").IType<SerializedDockview, SerializedDockview, SerializedDockview>>;
|
|
21
29
|
panelViewAssignments: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IMapType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").ISimpleType<string>>>, [undefined]>;
|
|
30
|
+
init: import("@jbrowse/mobx-state-tree").IType<DockviewLayoutNode | undefined, DockviewLayoutNode | undefined, DockviewLayoutNode | undefined>;
|
|
22
31
|
activePanelId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
|
|
23
32
|
}>>;
|
|
24
33
|
export type DockviewLayoutMixinType = ReturnType<typeof DockviewLayoutMixin>;
|
|
@@ -4,6 +4,7 @@ export function DockviewLayoutMixin() {
|
|
|
4
4
|
.model({
|
|
5
5
|
dockviewLayout: types.maybe(types.frozen()),
|
|
6
6
|
panelViewAssignments: types.optional(types.map(types.array(types.string)), {}),
|
|
7
|
+
init: types.frozen(),
|
|
7
8
|
activePanelId: types.maybe(types.string),
|
|
8
9
|
})
|
|
9
10
|
.views(self => ({
|
|
@@ -18,6 +19,9 @@ export function DockviewLayoutMixin() {
|
|
|
18
19
|
setActivePanelId(panelId) {
|
|
19
20
|
self.activePanelId = panelId;
|
|
20
21
|
},
|
|
22
|
+
setInit(init) {
|
|
23
|
+
self.init = init;
|
|
24
|
+
},
|
|
21
25
|
assignViewToPanel(panelId, viewId) {
|
|
22
26
|
const existing = self.panelViewAssignments.get(panelId);
|
|
23
27
|
if (existing) {
|
|
@@ -91,7 +95,7 @@ export function DockviewLayoutMixin() {
|
|
|
91
95
|
if (!snap) {
|
|
92
96
|
return snap;
|
|
93
97
|
}
|
|
94
|
-
const { dockviewLayout, panelViewAssignments, activePanelId, ...rest } = snap;
|
|
98
|
+
const { dockviewLayout, panelViewAssignments, init: _init, activePanelId, ...rest } = snap;
|
|
95
99
|
return {
|
|
96
100
|
...rest,
|
|
97
101
|
...(dockviewLayout !== undefined ? { dockviewLayout } : {}),
|
package/esm/ui/App/AppToolbar.js
CHANGED
|
@@ -22,6 +22,21 @@ const useStyles = makeStyles()(theme => ({
|
|
|
22
22
|
backgroundColor: theme.palette.primary.light,
|
|
23
23
|
},
|
|
24
24
|
}));
|
|
25
|
+
function wrapMenuItems(items, session) {
|
|
26
|
+
return items.map(item => ({
|
|
27
|
+
...item,
|
|
28
|
+
...('onClick' in item
|
|
29
|
+
? {
|
|
30
|
+
onClick: () => {
|
|
31
|
+
item.onClick(session);
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
: {}),
|
|
35
|
+
...('subMenu' in item
|
|
36
|
+
? { subMenu: wrapMenuItems(item.subMenu, session) }
|
|
37
|
+
: {}),
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
25
40
|
const AppToolbar = observer(function AppToolbar({ session, HeaderButtons = _jsx("div", {}), }) {
|
|
26
41
|
const { classes } = useStyles();
|
|
27
42
|
const { name, menus } = session;
|
|
@@ -29,16 +44,7 @@ const AppToolbar = observer(function AppToolbar({ session, HeaderButtons = _jsx(
|
|
|
29
44
|
const items = typeof menu.menuItems === 'function'
|
|
30
45
|
? menu.menuItems()
|
|
31
46
|
: menu.menuItems;
|
|
32
|
-
return items
|
|
33
|
-
...arg,
|
|
34
|
-
...('onClick' in arg
|
|
35
|
-
? {
|
|
36
|
-
onClick: () => {
|
|
37
|
-
arg.onClick(session);
|
|
38
|
-
},
|
|
39
|
-
}
|
|
40
|
-
: {}),
|
|
41
|
-
}));
|
|
47
|
+
return wrapMenuItems(items, session);
|
|
42
48
|
} }, menu.label))), _jsx("div", { className: classes.grow }), _jsx(Tooltip, { title: "Rename session", arrow: true, children: _jsx(EditableTypography, { value: name, variant: "body1", classes: {
|
|
43
49
|
inputBase: classes.inputBase,
|
|
44
50
|
inputRoot: classes.inputRoot,
|
|
@@ -6,10 +6,17 @@ interface DockviewContextValue {
|
|
|
6
6
|
moveViewToNewTab: (viewId: string) => void;
|
|
7
7
|
moveViewToSplitRight: (viewId: string) => void;
|
|
8
8
|
}
|
|
9
|
+
export declare function peekPendingMoveAction(): {
|
|
10
|
+
type: "newTab" | "splitRight";
|
|
11
|
+
viewId: string;
|
|
12
|
+
} | null;
|
|
13
|
+
export declare function clearPendingMoveAction(): void;
|
|
9
14
|
export declare function getPendingMoveAction(): {
|
|
10
15
|
type: "newTab" | "splitRight";
|
|
11
16
|
viewId: string;
|
|
12
17
|
} | null;
|
|
18
|
+
export declare function setPendingMoveToNewTab(viewId: string): void;
|
|
19
|
+
export declare function setPendingMoveToSplitRight(viewId: string): void;
|
|
13
20
|
export declare const DockviewContext: import("react").Context<DockviewContextValue>;
|
|
14
21
|
export declare function useDockview(): DockviewContextValue;
|
|
15
22
|
export {};
|
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
import { createContext, useContext } from 'react';
|
|
2
2
|
let pendingMoveAction = null;
|
|
3
|
+
export function peekPendingMoveAction() {
|
|
4
|
+
return pendingMoveAction;
|
|
5
|
+
}
|
|
6
|
+
export function clearPendingMoveAction() {
|
|
7
|
+
pendingMoveAction = null;
|
|
8
|
+
}
|
|
3
9
|
export function getPendingMoveAction() {
|
|
4
10
|
const action = pendingMoveAction;
|
|
5
11
|
pendingMoveAction = null;
|
|
6
12
|
return action;
|
|
7
13
|
}
|
|
14
|
+
export function setPendingMoveToNewTab(viewId) {
|
|
15
|
+
pendingMoveAction = { type: 'newTab', viewId };
|
|
16
|
+
}
|
|
17
|
+
export function setPendingMoveToSplitRight(viewId) {
|
|
18
|
+
pendingMoveAction = { type: 'splitRight', viewId };
|
|
19
|
+
}
|
|
8
20
|
export const DockviewContext = createContext({
|
|
9
21
|
api: null,
|
|
10
22
|
rearrangePanels: () => { },
|
|
11
23
|
addEmptyTab: () => { },
|
|
12
|
-
moveViewToNewTab:
|
|
13
|
-
|
|
14
|
-
},
|
|
15
|
-
moveViewToSplitRight: (viewId) => {
|
|
16
|
-
pendingMoveAction = { type: 'splitRight', viewId };
|
|
17
|
-
},
|
|
24
|
+
moveViewToNewTab: setPendingMoveToNewTab,
|
|
25
|
+
moveViewToSplitRight: setPendingMoveToSplitRight,
|
|
18
26
|
});
|
|
19
27
|
export function useDockview() {
|
|
20
28
|
return useContext(DockviewContext);
|
|
@@ -3,7 +3,7 @@ import { Suspense, lazy } from 'react';
|
|
|
3
3
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
4
4
|
import { observer } from 'mobx-react';
|
|
5
5
|
import ViewContainer from "./ViewContainer.js";
|
|
6
|
-
import {
|
|
6
|
+
import { getViewsForPanel } from "./dockviewUtils.js";
|
|
7
7
|
const ViewLauncher = lazy(() => import("./ViewLauncher.js"));
|
|
8
8
|
const useStyles = makeStyles()(theme => ({
|
|
9
9
|
container: {
|
|
@@ -25,15 +25,6 @@ const useStyles = makeStyles()(theme => ({
|
|
|
25
25
|
height: '100%',
|
|
26
26
|
},
|
|
27
27
|
}));
|
|
28
|
-
function getViewsForPanel(panelId, session) {
|
|
29
|
-
if (!session || !isSessionWithDockviewLayout(session)) {
|
|
30
|
-
return [];
|
|
31
|
-
}
|
|
32
|
-
const viewIds = session.getViewIdsForPanel(panelId);
|
|
33
|
-
return [...viewIds]
|
|
34
|
-
.map(id => session.views.find(v => v.id === id))
|
|
35
|
-
.filter((v) => v !== undefined);
|
|
36
|
-
}
|
|
37
28
|
const JBrowseViewPanel = observer(function JBrowseViewPanel({ params, }) {
|
|
38
29
|
const { panelId, session } = params;
|
|
39
30
|
const { classes } = useStyles();
|
|
@@ -4,7 +4,7 @@ import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
|
4
4
|
import { InputBase, Typography } from '@mui/material';
|
|
5
5
|
import { observer } from 'mobx-react';
|
|
6
6
|
import JBrowseTabMenu from "./JBrowseTabMenu.js";
|
|
7
|
-
import {
|
|
7
|
+
import { getViewsForPanel } from "./dockviewUtils.js";
|
|
8
8
|
const useStyles = makeStyles()(theme => ({
|
|
9
9
|
tabContainer: {
|
|
10
10
|
display: 'flex',
|
|
@@ -39,15 +39,6 @@ function stopEvent(e) {
|
|
|
39
39
|
e.stopPropagation();
|
|
40
40
|
e.preventDefault();
|
|
41
41
|
}
|
|
42
|
-
function getViewsForPanel(panelId, session) {
|
|
43
|
-
if (!session || !isSessionWithDockviewLayout(session)) {
|
|
44
|
-
return [];
|
|
45
|
-
}
|
|
46
|
-
const viewIds = session.getViewIdsForPanel(panelId);
|
|
47
|
-
return [...viewIds]
|
|
48
|
-
.map(id => session.views.find(v => v.id === id))
|
|
49
|
-
.filter((v) => v !== undefined);
|
|
50
|
-
}
|
|
51
42
|
function getTabDisplayName(views, session) {
|
|
52
43
|
if (views.length === 0) {
|
|
53
44
|
return 'Empty';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
-
import { nanoid } from '@jbrowse/core/util/nanoid';
|
|
4
3
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
4
|
+
import { createElementId } from '@jbrowse/core/util/types/mst';
|
|
5
5
|
import { useTheme } from '@mui/material';
|
|
6
6
|
import { DockviewReact } from 'dockview-react';
|
|
7
7
|
import { autorun } from 'mobx';
|
|
8
8
|
import { observer } from 'mobx-react';
|
|
9
|
-
import { DockviewContext,
|
|
9
|
+
import { DockviewContext, clearPendingMoveAction, peekPendingMoveAction, } from "./DockviewContext.js";
|
|
10
10
|
import DockviewLeftHeaderActions from "./DockviewLeftHeaderActions.js";
|
|
11
11
|
import DockviewRightHeaderActions from "./DockviewRightHeaderActions.js";
|
|
12
12
|
import JBrowseViewPanel from "./JBrowseViewPanel.js";
|
|
@@ -21,6 +21,15 @@ const useStyles = makeStyles()(() => ({
|
|
|
21
21
|
gridRow: 'components',
|
|
22
22
|
},
|
|
23
23
|
}));
|
|
24
|
+
function getPanelPosition(group, direction) {
|
|
25
|
+
if (!group) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
if (direction) {
|
|
29
|
+
return { referenceGroup: group, direction };
|
|
30
|
+
}
|
|
31
|
+
return { referenceGroup: group };
|
|
32
|
+
}
|
|
24
33
|
const components = {
|
|
25
34
|
jbrowseView: JBrowseViewPanel,
|
|
26
35
|
};
|
|
@@ -51,46 +60,35 @@ const TiledViewsContainer = observer(function TiledViewsContainer({ session, })
|
|
|
51
60
|
if (!api) {
|
|
52
61
|
return;
|
|
53
62
|
}
|
|
54
|
-
const panelId = `panel-${
|
|
63
|
+
const panelId = `panel-${createElementId()}`;
|
|
55
64
|
const group = targetGroup ?? api.activeGroup;
|
|
56
65
|
api.addPanel({
|
|
57
66
|
...createPanelConfig(panelId, session, 'New Tab'),
|
|
58
|
-
position: group
|
|
67
|
+
position: getPanelPosition(group),
|
|
59
68
|
});
|
|
60
69
|
if (isSessionWithDockviewLayout(session)) {
|
|
61
70
|
session.setActivePanelId(panelId);
|
|
62
71
|
}
|
|
63
72
|
}, [api, session]);
|
|
64
|
-
const
|
|
73
|
+
const moveViewToPanel = useCallback((viewId, direction) => {
|
|
65
74
|
if (!api || !isSessionWithDockviewLayout(session)) {
|
|
66
75
|
return;
|
|
67
76
|
}
|
|
68
77
|
session.removeViewFromPanel(viewId);
|
|
69
|
-
const panelId = `panel-${
|
|
70
|
-
const group = api.activeGroup;
|
|
78
|
+
const panelId = `panel-${createElementId()}`;
|
|
71
79
|
api.addPanel({
|
|
72
80
|
...createPanelConfig(panelId, session, 'New Tab'),
|
|
73
|
-
position:
|
|
81
|
+
position: getPanelPosition(api.activeGroup, direction),
|
|
74
82
|
});
|
|
75
83
|
session.assignViewToPanel(panelId, viewId);
|
|
76
84
|
session.setActivePanelId(panelId);
|
|
77
85
|
}, [api, session]);
|
|
86
|
+
const moveViewToNewTab = useCallback((viewId) => {
|
|
87
|
+
moveViewToPanel(viewId);
|
|
88
|
+
}, [moveViewToPanel]);
|
|
78
89
|
const moveViewToSplitRight = useCallback((viewId) => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
session.removeViewFromPanel(viewId);
|
|
83
|
-
const panelId = `panel-${nanoid()}`;
|
|
84
|
-
const group = api.activeGroup;
|
|
85
|
-
api.addPanel({
|
|
86
|
-
...createPanelConfig(panelId, session, 'New Tab'),
|
|
87
|
-
position: group
|
|
88
|
-
? { referenceGroup: group, direction: 'right' }
|
|
89
|
-
: undefined,
|
|
90
|
-
});
|
|
91
|
-
session.assignViewToPanel(panelId, viewId);
|
|
92
|
-
session.setActivePanelId(panelId);
|
|
93
|
-
}, [api, session]);
|
|
90
|
+
moveViewToPanel(viewId, 'right');
|
|
91
|
+
}, [moveViewToPanel]);
|
|
94
92
|
const contextValue = useMemo(() => ({
|
|
95
93
|
api,
|
|
96
94
|
rearrangePanels,
|
|
@@ -98,11 +96,136 @@ const TiledViewsContainer = observer(function TiledViewsContainer({ session, })
|
|
|
98
96
|
moveViewToNewTab,
|
|
99
97
|
moveViewToSplitRight,
|
|
100
98
|
}), [api, rearrangePanels, addEmptyTab, moveViewToNewTab, moveViewToSplitRight]);
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
99
|
+
const createInitialPanels = useCallback((dockviewApi) => {
|
|
100
|
+
const session = sessionRef.current;
|
|
101
|
+
const pendingAction = peekPendingMoveAction();
|
|
102
|
+
const initLayout = isSessionWithDockviewLayout(session)
|
|
103
|
+
? session.init
|
|
104
|
+
: undefined;
|
|
105
|
+
if (initLayout && isSessionWithDockviewLayout(session)) {
|
|
106
|
+
trackedViewIdsRef.current.clear();
|
|
107
|
+
let firstPanelId;
|
|
108
|
+
const groupSizes = [];
|
|
109
|
+
function processNode(node, referenceGroup, direction) {
|
|
110
|
+
if (!node) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
if (node.viewIds !== undefined) {
|
|
114
|
+
const panelId = `panel-${createElementId()}`;
|
|
115
|
+
if (!firstPanelId) {
|
|
116
|
+
firstPanelId = panelId;
|
|
117
|
+
}
|
|
118
|
+
const position = referenceGroup && direction
|
|
119
|
+
? { referenceGroup, direction }
|
|
120
|
+
: referenceGroup
|
|
121
|
+
? { referenceGroup }
|
|
122
|
+
: undefined;
|
|
123
|
+
dockviewApi.addPanel({
|
|
124
|
+
...createPanelConfig(panelId, session, 'Tab'),
|
|
125
|
+
position,
|
|
126
|
+
});
|
|
127
|
+
for (const viewId of node.viewIds) {
|
|
128
|
+
if (isSessionWithDockviewLayout(session)) {
|
|
129
|
+
session.assignViewToPanel(panelId, viewId);
|
|
130
|
+
}
|
|
131
|
+
trackedViewIdsRef.current.add(viewId);
|
|
132
|
+
}
|
|
133
|
+
const group = dockviewApi.getPanel(panelId)?.group;
|
|
134
|
+
if (group && node.size !== undefined) {
|
|
135
|
+
groupSizes.push({ group, size: node.size });
|
|
136
|
+
}
|
|
137
|
+
return group;
|
|
138
|
+
}
|
|
139
|
+
if (node.children && node.children.length > 0) {
|
|
140
|
+
const dockviewDirection = node.direction === 'horizontal' ? 'right' : 'below';
|
|
141
|
+
let currentGroup = referenceGroup;
|
|
142
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
143
|
+
const child = node.children[i];
|
|
144
|
+
const childDirection = i === 0 ? direction : dockviewDirection;
|
|
145
|
+
const childRef = i === 0 ? referenceGroup : currentGroup;
|
|
146
|
+
const newGroup = processNode(child, childRef, childDirection);
|
|
147
|
+
if (newGroup) {
|
|
148
|
+
currentGroup = newGroup;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return currentGroup;
|
|
152
|
+
}
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
processNode(initLayout, undefined, undefined);
|
|
156
|
+
if (groupSizes.length >= 2 &&
|
|
157
|
+
initLayout.direction &&
|
|
158
|
+
groupSizes.length === initLayout.children?.length) {
|
|
159
|
+
const direction = initLayout.direction;
|
|
160
|
+
requestAnimationFrame(() => {
|
|
161
|
+
const totalSize = groupSizes.reduce((sum, g) => sum + g.size, 0);
|
|
162
|
+
if (totalSize > 0) {
|
|
163
|
+
if (direction === 'horizontal') {
|
|
164
|
+
const containerWidth = dockviewApi.width;
|
|
165
|
+
if (containerWidth > 0) {
|
|
166
|
+
for (const { group, size } of groupSizes) {
|
|
167
|
+
const width = Math.round(containerWidth * (size / totalSize));
|
|
168
|
+
group.api.setSize({ width });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
const containerHeight = dockviewApi.height;
|
|
174
|
+
if (containerHeight > 0) {
|
|
175
|
+
for (const { group, size } of groupSizes) {
|
|
176
|
+
const height = Math.round(containerHeight * (size / totalSize));
|
|
177
|
+
group.api.setSize({ height });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
session.setInit(undefined);
|
|
185
|
+
if (firstPanelId) {
|
|
186
|
+
session.setActivePanelId(firstPanelId);
|
|
187
|
+
dockviewApi.getPanel(firstPanelId)?.api.setActive();
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (isSessionWithDockviewLayout(session)) {
|
|
192
|
+
for (const panelId of session.panelViewAssignments.keys()) {
|
|
193
|
+
session.removePanel(panelId);
|
|
194
|
+
}
|
|
195
|
+
session.setDockviewLayout(undefined);
|
|
196
|
+
}
|
|
197
|
+
trackedViewIdsRef.current.clear();
|
|
198
|
+
const pendingViewExists = pendingAction && session.views.some(v => v.id === pendingAction.viewId);
|
|
199
|
+
if (pendingViewExists && isSessionWithDockviewLayout(session)) {
|
|
200
|
+
const { type, viewId: pendingViewId } = pendingAction;
|
|
201
|
+
const otherViewIds = session.views
|
|
202
|
+
.map(v => v.id)
|
|
203
|
+
.filter(id => id !== pendingViewId);
|
|
204
|
+
if (otherViewIds.length > 0) {
|
|
205
|
+
const firstPanelId = `panel-${createElementId()}`;
|
|
206
|
+
dockviewApi.addPanel(createPanelConfig(firstPanelId, session));
|
|
207
|
+
for (const viewId of otherViewIds) {
|
|
208
|
+
session.assignViewToPanel(firstPanelId, viewId);
|
|
209
|
+
trackedViewIdsRef.current.add(viewId);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const pendingPanelId = `panel-${createElementId()}`;
|
|
213
|
+
const direction = type === 'splitRight' ? 'right' : undefined;
|
|
214
|
+
dockviewApi.addPanel({
|
|
215
|
+
...createPanelConfig(pendingPanelId, session, 'New Tab'),
|
|
216
|
+
position: getPanelPosition(dockviewApi.activeGroup, direction),
|
|
217
|
+
});
|
|
218
|
+
session.assignViewToPanel(pendingPanelId, pendingViewId);
|
|
219
|
+
trackedViewIdsRef.current.add(pendingViewId);
|
|
220
|
+
session.setActivePanelId(pendingPanelId);
|
|
221
|
+
clearPendingMoveAction();
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
const panelId = `panel-${createElementId()}`;
|
|
225
|
+
dockviewApi.addPanel(createPanelConfig(panelId, session));
|
|
226
|
+
if (isSessionWithDockviewLayout(session)) {
|
|
227
|
+
session.setActivePanelId(panelId);
|
|
228
|
+
}
|
|
106
229
|
}
|
|
107
230
|
}, []);
|
|
108
231
|
const onReady = useCallback((event) => {
|
|
@@ -133,29 +256,37 @@ const TiledViewsContainer = observer(function TiledViewsContainer({ session, })
|
|
|
133
256
|
sessionRef.current.setDockviewLayout(cleanLayoutForStorage(event.api.toJSON()));
|
|
134
257
|
}
|
|
135
258
|
});
|
|
136
|
-
|
|
137
|
-
|
|
259
|
+
const hasPendingAction = peekPendingMoveAction() !== null;
|
|
260
|
+
const dockviewSession = isSessionWithDockviewLayout(sessionRef.current)
|
|
261
|
+
? sessionRef.current
|
|
262
|
+
: null;
|
|
263
|
+
const savedLayout = !hasPendingAction && dockviewSession?.dockviewLayout;
|
|
264
|
+
if (savedLayout) {
|
|
138
265
|
try {
|
|
139
266
|
rearrangingRef.current = true;
|
|
140
|
-
event.api.fromJSON(
|
|
141
|
-
updatePanelParams(event.api,
|
|
142
|
-
for (const viewIds of
|
|
267
|
+
event.api.fromJSON(savedLayout);
|
|
268
|
+
updatePanelParams(event.api, dockviewSession);
|
|
269
|
+
for (const viewIds of dockviewSession.panelViewAssignments.values()) {
|
|
143
270
|
for (const viewId of viewIds) {
|
|
144
271
|
trackedViewIdsRef.current.add(viewId);
|
|
145
272
|
}
|
|
146
273
|
}
|
|
147
|
-
|
|
274
|
+
if (event.api.panels.length === 0) {
|
|
275
|
+
throw new Error('No panels after fromJSON restore');
|
|
276
|
+
}
|
|
148
277
|
}
|
|
149
278
|
catch (e) {
|
|
150
279
|
console.error('Failed to restore dockview layout:', e);
|
|
280
|
+
createInitialPanels(event.api);
|
|
281
|
+
}
|
|
282
|
+
finally {
|
|
151
283
|
rearrangingRef.current = false;
|
|
152
|
-
createInitialPanel(event.api);
|
|
153
284
|
}
|
|
154
285
|
}
|
|
155
286
|
else {
|
|
156
|
-
|
|
287
|
+
createInitialPanels(event.api);
|
|
157
288
|
}
|
|
158
|
-
}, [
|
|
289
|
+
}, [createInitialPanels]);
|
|
159
290
|
useEffect(() => {
|
|
160
291
|
const dispose = autorun(() => {
|
|
161
292
|
if (!api) {
|
|
@@ -175,7 +306,7 @@ const TiledViewsContainer = observer(function TiledViewsContainer({ session, })
|
|
|
175
306
|
activePanelId = firstPanel.id;
|
|
176
307
|
}
|
|
177
308
|
else {
|
|
178
|
-
activePanelId = `panel-${
|
|
309
|
+
activePanelId = `panel-${createElementId()}`;
|
|
179
310
|
api.addPanel(createPanelConfig(activePanelId, session));
|
|
180
311
|
}
|
|
181
312
|
session.setActivePanelId(activePanelId);
|
|
@@ -195,21 +326,6 @@ const TiledViewsContainer = observer(function TiledViewsContainer({ session, })
|
|
|
195
326
|
});
|
|
196
327
|
return dispose;
|
|
197
328
|
}, [session, api]);
|
|
198
|
-
useEffect(() => {
|
|
199
|
-
if (!api) {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
const pendingAction = getPendingMoveAction();
|
|
203
|
-
if (pendingAction) {
|
|
204
|
-
const { type, viewId } = pendingAction;
|
|
205
|
-
if (type === 'newTab') {
|
|
206
|
-
moveViewToNewTab(viewId);
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
209
|
-
moveViewToSplitRight(viewId);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}, [api, moveViewToNewTab, moveViewToSplitRight]);
|
|
213
329
|
useEffect(() => {
|
|
214
330
|
if (!api || !isSessionWithDockviewLayout(session)) {
|
|
215
331
|
return;
|
package/esm/ui/App/ViewMenu.js
CHANGED
|
@@ -32,6 +32,14 @@ const ViewMenu = observer(function ViewMenu({ model, IconProps, }) {
|
|
|
32
32
|
const viewCount = usePanel
|
|
33
33
|
? getPanelViewCount(session, model.id)
|
|
34
34
|
: session.views.length;
|
|
35
|
+
const moveView = (panelFn, sessionFn) => {
|
|
36
|
+
if (usePanel) {
|
|
37
|
+
panelFn(model.id);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
sessionFn(model.id);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
35
43
|
return (_jsx(CascadingMenuButton, { "data-testid": "view_menu_icon", menuItems: () => [
|
|
36
44
|
{
|
|
37
45
|
label: 'View options',
|
|
@@ -66,12 +74,7 @@ const ViewMenu = observer(function ViewMenu({ model, IconProps, }) {
|
|
|
66
74
|
label: 'Move view to top',
|
|
67
75
|
icon: KeyboardDoubleArrowUpIcon,
|
|
68
76
|
onClick: () => {
|
|
69
|
-
|
|
70
|
-
session.moveViewToTopInPanel(model.id);
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
session.moveViewToTop(model.id);
|
|
74
|
-
}
|
|
77
|
+
moveView(session.moveViewToTopInPanel, session.moveViewToTop);
|
|
75
78
|
},
|
|
76
79
|
},
|
|
77
80
|
]
|
|
@@ -82,24 +85,14 @@ const ViewMenu = observer(function ViewMenu({ model, IconProps, }) {
|
|
|
82
85
|
label: 'Move view up',
|
|
83
86
|
icon: KeyboardArrowUpIcon,
|
|
84
87
|
onClick: () => {
|
|
85
|
-
|
|
86
|
-
session.moveViewUpInPanel(model.id);
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
session.moveViewUp(model.id);
|
|
90
|
-
}
|
|
88
|
+
moveView(session.moveViewUpInPanel, session.moveViewUp);
|
|
91
89
|
},
|
|
92
90
|
},
|
|
93
91
|
{
|
|
94
92
|
label: 'Move view down',
|
|
95
93
|
icon: KeyboardArrowDownIcon,
|
|
96
94
|
onClick: () => {
|
|
97
|
-
|
|
98
|
-
session.moveViewDownInPanel(model.id);
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
session.moveViewDown(model.id);
|
|
102
|
-
}
|
|
95
|
+
moveView(session.moveViewDownInPanel, session.moveViewDown);
|
|
103
96
|
},
|
|
104
97
|
},
|
|
105
98
|
]
|
|
@@ -110,12 +103,7 @@ const ViewMenu = observer(function ViewMenu({ model, IconProps, }) {
|
|
|
110
103
|
label: 'Move view to bottom',
|
|
111
104
|
icon: KeyboardDoubleArrowDownIcon,
|
|
112
105
|
onClick: () => {
|
|
113
|
-
|
|
114
|
-
session.moveViewToBottomInPanel(model.id);
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
session.moveViewToBottom(model.id);
|
|
118
|
-
}
|
|
106
|
+
moveView(session.moveViewToBottomInPanel, session.moveViewToBottom);
|
|
119
107
|
},
|
|
120
108
|
},
|
|
121
109
|
]
|
package/esm/ui/App/copyView.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createElementId } from '@jbrowse/core/util/types/mst';
|
|
2
2
|
export function renameIds(obj) {
|
|
3
3
|
const idMap = new Map();
|
|
4
4
|
function transformIds(value) {
|
|
@@ -13,7 +13,7 @@ export function renameIds(obj) {
|
|
|
13
13
|
for (const [key, val] of Object.entries(value)) {
|
|
14
14
|
if (key === 'id' && typeof val === 'string') {
|
|
15
15
|
if (!idMap.has(val)) {
|
|
16
|
-
idMap.set(val,
|
|
16
|
+
idMap.set(val, createElementId());
|
|
17
17
|
}
|
|
18
18
|
result[key] = `${val}-${idMap.get(val)}`;
|
|
19
19
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { DockviewSessionType } from './types.ts';
|
|
2
|
+
import type { AbstractViewModel } from '@jbrowse/core/util';
|
|
2
3
|
import type { DockviewApi } from 'dockview-react';
|
|
4
|
+
export declare function getViewsForPanel(panelId: string, session: DockviewSessionType | undefined): AbstractViewModel[];
|
|
3
5
|
export declare function createPanelConfig(panelId: string, session: DockviewSessionType, title?: string): {
|
|
4
6
|
id: string;
|
|
5
7
|
component: "jbrowseView";
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import { isSessionWithDockviewLayout } from "../../DockviewLayout/index.js";
|
|
2
|
+
export function getViewsForPanel(panelId, session) {
|
|
3
|
+
if (!session || !isSessionWithDockviewLayout(session)) {
|
|
4
|
+
return [];
|
|
5
|
+
}
|
|
6
|
+
const viewIds = session.getViewIdsForPanel(panelId);
|
|
7
|
+
return [...viewIds]
|
|
8
|
+
.map(id => session.views.find(v => v.id === id))
|
|
9
|
+
.filter((v) => v !== undefined);
|
|
10
|
+
}
|
|
1
11
|
export function createPanelConfig(panelId, session, title = 'Main') {
|
|
2
12
|
return {
|
|
3
13
|
id: panelId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/app-core",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.4",
|
|
4
4
|
"description": "JBrowse 2 code shared between the 'full featured' apps e.g. jbrowse-web and jbrowse-desktop",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jbrowse",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"dockview-react": "^4.13.1",
|
|
31
31
|
"mobx": "^6.15.0",
|
|
32
32
|
"mobx-react": "^9.2.1",
|
|
33
|
-
"@jbrowse/product-core": "^4.0.
|
|
34
|
-
"@jbrowse/core": "^4.0.
|
|
33
|
+
"@jbrowse/product-core": "^4.0.4",
|
|
34
|
+
"@jbrowse/core": "^4.0.4"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"react": ">=18.0.0",
|