@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.
@@ -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 } : {}),
@@ -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.map(arg => ({
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: (viewId) => {
13
- pendingMoveAction = { type: 'newTab', viewId };
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 { isSessionWithDockviewLayout } from "../../DockviewLayout/index.js";
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 { isSessionWithDockviewLayout } from "../../DockviewLayout/index.js";
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, getPendingMoveAction } from "./DockviewContext.js";
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-${nanoid()}`;
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 ? { referenceGroup: group } : undefined,
67
+ position: getPanelPosition(group),
59
68
  });
60
69
  if (isSessionWithDockviewLayout(session)) {
61
70
  session.setActivePanelId(panelId);
62
71
  }
63
72
  }, [api, session]);
64
- const moveViewToNewTab = useCallback((viewId) => {
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-${nanoid()}`;
70
- const group = api.activeGroup;
78
+ const panelId = `panel-${createElementId()}`;
71
79
  api.addPanel({
72
80
  ...createPanelConfig(panelId, session, 'New Tab'),
73
- position: group ? { referenceGroup: group } : undefined,
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
- if (!api || !isSessionWithDockviewLayout(session)) {
80
- return;
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 createInitialPanel = useCallback((dockviewApi) => {
102
- const panelId = `panel-${nanoid()}`;
103
- dockviewApi.addPanel(createPanelConfig(panelId, sessionRef.current));
104
- if (isSessionWithDockviewLayout(sessionRef.current)) {
105
- sessionRef.current.setActivePanelId(panelId);
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
- if (isSessionWithDockviewLayout(sessionRef.current) &&
137
- sessionRef.current.dockviewLayout) {
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(sessionRef.current.dockviewLayout);
141
- updatePanelParams(event.api, sessionRef.current);
142
- for (const viewIds of sessionRef.current.panelViewAssignments.values()) {
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
- rearrangingRef.current = false;
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
- createInitialPanel(event.api);
287
+ createInitialPanels(event.api);
157
288
  }
158
- }, [createInitialPanel]);
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-${nanoid()}`;
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;
@@ -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
- if (usePanel) {
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
- if (usePanel) {
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
- if (usePanel) {
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
- if (usePanel) {
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
  ]
@@ -1,4 +1,4 @@
1
- import { nanoid } from '@jbrowse/core/util/nanoid';
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, nanoid());
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.2",
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.2",
34
- "@jbrowse/core": "^4.0.2"
33
+ "@jbrowse/product-core": "^4.0.4",
34
+ "@jbrowse/core": "^4.0.4"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "react": ">=18.0.0",