@jbrowse/core 2.8.0 → 2.10.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.
@@ -3,6 +3,7 @@ import { MenuItem as JBMenuItem } from './Menu';
3
3
  import { PopupState } from 'material-ui-popup-state/hooks';
4
4
  declare function CascadingMenuChildren(props: {
5
5
  onMenuItemClick: Function;
6
+ closeAfterItemClick?: boolean;
6
7
  menuItems: JBMenuItem[];
7
8
  popupState: PopupState;
8
9
  }): React.JSX.Element;
@@ -36,19 +36,20 @@ const CascadingContext = react_1.default.createContext({
36
36
  parentPopupState: null,
37
37
  rootPopupState: null,
38
38
  });
39
- function CascadingMenuItem({ onClick, ...props }) {
39
+ function CascadingMenuItem({ onClick, closeAfterItemClick, ...props }) {
40
40
  const { rootPopupState } = (0, react_1.useContext)(CascadingContext);
41
41
  if (!rootPopupState) {
42
42
  throw new Error('must be used inside a CascadingMenu');
43
43
  }
44
- const handleClick = (0, react_1.useCallback)((event) => {
45
- rootPopupState.close();
46
- onClick === null || onClick === void 0 ? void 0 : onClick(event);
47
- }, [rootPopupState, onClick]);
48
- return react_1.default.createElement(material_1.MenuItem, { ...props, onClick: handleClick });
44
+ return (react_1.default.createElement(material_1.MenuItem, { ...props, onClick: event => {
45
+ if (closeAfterItemClick) {
46
+ rootPopupState.close();
47
+ }
48
+ onClick === null || onClick === void 0 ? void 0 : onClick(event);
49
+ } }));
49
50
  }
50
- function CascadingSubmenu({ title, inset, popupId, ...props }) {
51
- const { parentPopupState } = react_1.default.useContext(CascadingContext);
51
+ function CascadingSubmenu({ title, Icon, inset, popupId, ...props }) {
52
+ const { parentPopupState } = (0, react_1.useContext)(CascadingContext);
52
53
  const popupState = (0, hooks_1.usePopupState)({
53
54
  popupId,
54
55
  variant: 'popover',
@@ -56,6 +57,8 @@ function CascadingSubmenu({ title, inset, popupId, ...props }) {
56
57
  });
57
58
  return (react_1.default.createElement(react_1.default.Fragment, null,
58
59
  react_1.default.createElement(material_1.MenuItem, { ...(0, hooks_1.bindHover)(popupState), ...(0, hooks_1.bindFocus)(popupState) },
60
+ Icon ? (react_1.default.createElement(material_1.ListItemIcon, null,
61
+ react_1.default.createElement(Icon, null))) : null,
59
62
  react_1.default.createElement(material_1.ListItemText, { primary: title, inset: inset }),
60
63
  react_1.default.createElement(ChevronRight_1.default, null)),
61
64
  react_1.default.createElement(CascadingSubmenuHover, { ...props, anchorOrigin: { vertical: 'top', horizontal: 'right' }, transformOrigin: { vertical: 'top', horizontal: 'left' }, popupState: popupState })));
@@ -70,8 +73,8 @@ function CascadingSubmenuHover({ popupState, onMenuItemClick, menuItems, classes
70
73
  react_1.default.createElement(HoverMenu_1.default, { ...props, ...(0, hooks_1.bindMenu)(popupState) })));
71
74
  }
72
75
  function CascadingMenu({ popupState, onMenuItemClick, menuItems, ...props }) {
73
- const { rootPopupState } = react_1.default.useContext(CascadingContext);
74
- const context = react_1.default.useMemo(() => ({
76
+ const { rootPopupState } = (0, react_1.useContext)(CascadingContext);
77
+ const context = (0, react_1.useMemo)(() => ({
75
78
  rootPopupState: rootPopupState || popupState,
76
79
  parentPopupState: popupState,
77
80
  }), [rootPopupState, popupState]);
@@ -87,16 +90,16 @@ function EndDecoration({ item }) {
87
90
  }
88
91
  return null;
89
92
  }
90
- function CascadingMenuList({ onMenuItemClick, menuItems, ...props }) {
93
+ function CascadingMenuList({ onMenuItemClick, closeAfterItemClick, menuItems, ...props }) {
91
94
  function handleClick(callback) {
92
95
  return (event) => {
93
96
  onMenuItemClick(event, callback);
94
97
  };
95
98
  }
96
- const hasIcon = menuItems.some(menuItem => 'icon' in menuItem && menuItem.icon);
99
+ const hasIcon = menuItems.some(m => 'icon' in m && m.icon);
97
100
  return (react_1.default.createElement(react_1.default.Fragment, null, menuItems.map((item, idx) => {
98
- return 'subMenu' in item ? (react_1.default.createElement(CascadingSubmenu, { key: `subMenu-${item.label}-${idx}`, popupId: `subMenu-${item.label}`, title: item.label, inset: hasIcon, onMenuItemClick: onMenuItemClick, menuItems: item.subMenu },
99
- react_1.default.createElement(CascadingMenuList, { ...props, onMenuItemClick: onMenuItemClick, menuItems: item.subMenu }))) : item.type === 'divider' ? (react_1.default.createElement(material_1.Divider, { key: `divider-${idx}`, component: "li" })) : item.type === 'subHeader' ? (react_1.default.createElement(material_1.ListSubheader, { key: `subHeader-${item.label}-${idx}` }, item.label)) : (react_1.default.createElement(CascadingMenuItem, { key: `${item.label}-${idx}`, onClick: 'onClick' in item ? handleClick(item.onClick) : undefined, disabled: Boolean(item.disabled) },
101
+ return 'subMenu' in item ? (react_1.default.createElement(CascadingSubmenu, { key: `subMenu-${item.label}-${idx}`, popupId: `subMenu-${item.label}`, title: item.label, Icon: item.icon, inset: hasIcon && !item.icon, onMenuItemClick: onMenuItemClick, menuItems: item.subMenu },
102
+ react_1.default.createElement(CascadingMenuList, { ...props, closeAfterItemClick: closeAfterItemClick, onMenuItemClick: onMenuItemClick, menuItems: item.subMenu }))) : item.type === 'divider' ? (react_1.default.createElement(material_1.Divider, { key: `divider-${idx}`, component: "li" })) : item.type === 'subHeader' ? (react_1.default.createElement(material_1.ListSubheader, { key: `subHeader-${item.label}-${idx}` }, item.label)) : (react_1.default.createElement(CascadingMenuItem, { key: `${item.label}-${idx}`, closeAfterItemClick: closeAfterItemClick, onClick: 'onClick' in item ? handleClick(item.onClick) : undefined, disabled: Boolean(item.disabled) },
100
103
  item.icon ? (react_1.default.createElement(material_1.ListItemIcon, null,
101
104
  react_1.default.createElement(item.icon, null))) : null,
102
105
  ' ',
@@ -106,7 +109,8 @@ function CascadingMenuList({ onMenuItemClick, menuItems, ...props }) {
106
109
  })));
107
110
  }
108
111
  function CascadingMenuChildren(props) {
109
- return (react_1.default.createElement(CascadingMenu, { ...props },
110
- react_1.default.createElement(CascadingMenuList, { ...props })));
112
+ const { closeAfterItemClick = true, ...rest } = props;
113
+ return (react_1.default.createElement(CascadingMenu, { ...rest },
114
+ react_1.default.createElement(CascadingMenuList, { ...rest, closeAfterItemClick: closeAfterItemClick })));
111
115
  }
112
116
  exports.default = CascadingMenuChildren;
@@ -1,8 +1,12 @@
1
1
  import React from 'react';
2
2
  import { MenuItem } from '@jbrowse/core/ui';
3
- declare const CascadingMenuButton: ({ children, menuItems, ...rest }: {
3
+ declare const CascadingMenuButton: ({ children, menuItems, closeAfterItemClick, stopPropagation, setOpen, onClick: onClickExtra, ...rest }: {
4
4
  [key: string]: unknown;
5
5
  children?: React.ReactElement<any, string | React.JSXElementConstructor<any>> | undefined;
6
6
  menuItems: MenuItem[];
7
+ closeAfterItemClick?: boolean | undefined;
8
+ stopPropagation?: boolean | undefined;
9
+ onClick?: (() => void) | undefined;
10
+ setOpen?: ((arg: boolean) => void) | undefined;
7
11
  }) => React.JSX.Element;
8
12
  export default CascadingMenuButton;
@@ -1,20 +1,60 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
27
  };
5
28
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const react_1 = __importDefault(require("react"));
29
+ const react_1 = __importStar(require("react"));
7
30
  const CascadingMenu_1 = __importDefault(require("@jbrowse/core/ui/CascadingMenu"));
8
31
  const material_1 = require("@mui/material");
9
32
  const mobx_react_1 = require("mobx-react");
10
33
  const hooks_1 = require("material-ui-popup-state/hooks");
11
- const CascadingMenuButton = (0, mobx_react_1.observer)(function CascadingMenuButton({ children, menuItems, ...rest }) {
34
+ const CascadingMenuButton = (0, mobx_react_1.observer)(function CascadingMenuButton({ children, menuItems, closeAfterItemClick = true, stopPropagation, setOpen, onClick: onClickExtra, ...rest }) {
12
35
  const popupState = (0, hooks_1.usePopupState)({
13
36
  popupId: 'viewMenu',
14
37
  variant: 'popover',
15
38
  });
39
+ const { onClick, onTouchStart, ...rest2 } = (0, hooks_1.bindTrigger)(popupState);
40
+ const { isOpen } = popupState;
41
+ (0, react_1.useEffect)(() => {
42
+ setOpen === null || setOpen === void 0 ? void 0 : setOpen(isOpen);
43
+ }, [isOpen, setOpen]);
16
44
  return (react_1.default.createElement(react_1.default.Fragment, null,
17
- react_1.default.createElement(material_1.IconButton, { ...(0, hooks_1.bindTrigger)(popupState), ...rest, disabled: menuItems.length === 0 }, children),
18
- react_1.default.createElement(CascadingMenu_1.default, { ...(0, hooks_1.bindPopover)(popupState), onMenuItemClick: (_, callback) => callback(), menuItems: menuItems, popupState: popupState })));
45
+ react_1.default.createElement(material_1.IconButton, { onClick: event => {
46
+ if (stopPropagation) {
47
+ event.stopPropagation();
48
+ }
49
+ onClick(event);
50
+ onClickExtra === null || onClickExtra === void 0 ? void 0 : onClickExtra();
51
+ }, onTouchStart: event => {
52
+ if (stopPropagation) {
53
+ event.stopPropagation();
54
+ }
55
+ onTouchStart(event);
56
+ onClickExtra === null || onClickExtra === void 0 ? void 0 : onClickExtra();
57
+ }, ...rest2, ...rest, disabled: menuItems.length === 0 }, children),
58
+ react_1.default.createElement(CascadingMenu_1.default, { ...(0, hooks_1.bindPopover)(popupState), onMenuItemClick: (_, callback) => callback(), menuItems: menuItems, closeAfterItemClick: closeAfterItemClick, popupState: popupState })));
19
59
  });
20
60
  exports.default = CascadingMenuButton;
@@ -33,9 +33,10 @@ const material_1 = require("@mui/material");
33
33
  const types_1 = require("../../util/types");
34
34
  const LocalFileChooser_1 = __importDefault(require("./LocalFileChooser"));
35
35
  const UrlChooser_1 = __importDefault(require("./UrlChooser"));
36
+ const util_1 = require("../../util");
36
37
  // icons
37
38
  const ArrowDropDown_1 = __importDefault(require("@mui/icons-material/ArrowDropDown"));
38
- const util_1 = require("../../util");
39
+ const NUM_SHOWN = 2;
39
40
  function ToggleButtonWithTooltip(props) {
40
41
  const { title, children, ...other } = props;
41
42
  return (react_1.default.createElement(material_1.Tooltip, { title: title || '' },
@@ -53,35 +54,40 @@ const FileSelector = (0, mobx_react_1.observer)(function (props) {
53
54
  const [toggleButtonValue, setToggleButtonValue] = (0, react_1.useState)(location && 'internetAccountId' in location && location.internetAccountId
54
55
  ? location.internetAccountId
55
56
  : fileOrUrl);
56
- const accts = (0, types_1.isAppRootModel)(rootModel) ? [...rootModel.internetAccounts] : [];
57
- const numShown = 2;
58
- const [shownAccts, setShownAccts] = (0, react_1.useState)(accts.slice(0, numShown));
59
- const [hiddenAccts, setHiddenAccts] = (0, react_1.useState)(accts.slice(numShown));
57
+ const accounts = (0, types_1.isAppRootModel)(rootModel)
58
+ ? rootModel.internetAccounts.filter(f => f.type !== 'HTTPBasicInternetAccount')
59
+ : [];
60
+ const [recentlyUsedInternetAccounts, setRecentlyUsedInternetAccounts] = (0, util_1.useLocalStorage)('fileSelector-recentlyUsedInternetAccounts', []);
61
+ const map = Object.fromEntries(accounts.map(a => [a.internetAccountId, a]));
62
+ const arr = [...new Set(accounts.map(s => s.internetAccountId))].sort((a, b) => recentlyUsedInternetAccounts.indexOf(a) -
63
+ recentlyUsedInternetAccounts.indexOf(b));
64
+ const shownAccounts = arr.slice(0, NUM_SHOWN);
65
+ const hiddenAccounts = arr.slice(NUM_SHOWN);
60
66
  const [anchorEl, setAnchorEl] = (0, react_1.useState)(null);
61
- const selectedAcct = accts.find(i => i.internetAccountId === toggleButtonValue);
62
- const setLocationWithAcct = (0, react_1.useCallback)((location) => {
67
+ const selectedAccount = map[toggleButtonValue];
68
+ const setLocationWithAccount = (0, react_1.useCallback)((location) => {
63
69
  setLocation({
64
70
  ...location,
65
71
  ...((0, types_1.isUriLocation)(location)
66
- ? { internetAccountId: selectedAcct === null || selectedAcct === void 0 ? void 0 : selectedAcct.internetAccountId }
72
+ ? { internetAccountId: selectedAccount === null || selectedAccount === void 0 ? void 0 : selectedAccount.internetAccountId }
67
73
  : {}),
68
74
  });
69
- }, [setLocation, selectedAcct]);
75
+ }, [setLocation, selectedAccount]);
70
76
  (0, react_1.useEffect)(() => {
71
77
  // if you swap account selection after inputting url
72
- if (selectedAcct &&
78
+ if (selectedAccount &&
73
79
  (0, types_1.isUriLocation)(location) &&
74
- location.internetAccountId !== selectedAcct.internetAccountId) {
75
- setLocationWithAcct(location);
80
+ location.internetAccountId !== selectedAccount.internetAccountId) {
81
+ setLocationWithAccount(location);
76
82
  }
77
- }, [location, selectedAcct, setLocationWithAcct]);
78
- let locationInput = (react_1.default.createElement(UrlChooser_1.default, { ...props, setLocation: setLocationWithAcct, label: selectedAcct === null || selectedAcct === void 0 ? void 0 : selectedAcct.selectorLabel }));
83
+ }, [location, selectedAccount, setLocationWithAccount]);
84
+ let locationInput = (react_1.default.createElement(UrlChooser_1.default, { ...props, setLocation: setLocationWithAccount, label: selectedAccount === null || selectedAccount === void 0 ? void 0 : selectedAccount.selectorLabel }));
79
85
  if (toggleButtonValue === 'file') {
80
86
  locationInput = react_1.default.createElement(LocalFileChooser_1.default, { ...props });
81
87
  }
82
- if (selectedAcct === null || selectedAcct === void 0 ? void 0 : selectedAcct.SelectorComponent) {
83
- const { SelectorComponent } = selectedAcct;
84
- locationInput = (react_1.default.createElement(SelectorComponent, { ...props, setLocation: setLocationWithAcct }));
88
+ if (selectedAccount === null || selectedAccount === void 0 ? void 0 : selectedAccount.SelectorComponent) {
89
+ const { SelectorComponent } = selectedAccount;
90
+ locationInput = (react_1.default.createElement(SelectorComponent, { ...props, setLocation: setLocationWithAccount }));
85
91
  }
86
92
  return (react_1.default.createElement(react_1.default.Fragment, null,
87
93
  react_1.default.createElement(material_1.Box, { display: "flex" },
@@ -89,34 +95,42 @@ const FileSelector = (0, mobx_react_1.observer)(function (props) {
89
95
  react_1.default.createElement(material_1.Box, { display: "flex", flexDirection: "row" },
90
96
  react_1.default.createElement(material_1.Box, null,
91
97
  react_1.default.createElement(material_1.ToggleButtonGroup, { value: toggleButtonValue, exclusive: true, onChange: (_event, newState) => {
98
+ setRecentlyUsedInternetAccounts([
99
+ ...new Set([newState, ...recentlyUsedInternetAccounts].filter(util_1.notEmpty)),
100
+ ]);
92
101
  if (newState) {
93
102
  setToggleButtonValue(newState);
94
103
  }
95
104
  if ((0, types_1.isUriLocation)(location)) {
96
- setLocationWithAcct(location);
105
+ setLocationWithAccount(location);
97
106
  }
98
107
  }, "aria-label": "file, url, or account picker" },
99
108
  new URLSearchParams(window.location.search).get('adminKey') ? null : (react_1.default.createElement(material_1.ToggleButton, { value: "file", "aria-label": "local file" }, "File")),
100
109
  react_1.default.createElement(material_1.ToggleButton, { value: "url", "aria-label": "url" }, "URL"),
101
- shownAccts.map(({ internetAccountId, toggleContents, name }) => (react_1.default.createElement(ToggleButtonWithTooltip, { key: internetAccountId, value: internetAccountId, "aria-label": name, title: name }, typeof toggleContents === 'string'
102
- ? shorten(toggleContents, 5)
103
- : toggleContents || shorten(name, 5)))),
104
- hiddenAccts.length > 0 ? (
110
+ shownAccounts.map(id => {
111
+ const { internetAccountId, name, toggleContents } = map[id];
112
+ return (react_1.default.createElement(ToggleButtonWithTooltip, { key: id, value: internetAccountId, title: name }, typeof toggleContents === 'string'
113
+ ? shorten(toggleContents, 5)
114
+ : toggleContents || shorten(name, 5)));
115
+ }),
116
+ hiddenAccounts.length > 0 ? (
105
117
  // @ts-expect-error
106
118
  react_1.default.createElement(material_1.ToggleButton, { onClick: event => setAnchorEl(event.target), selected: false },
107
119
  "More",
108
120
  react_1.default.createElement(ArrowDropDown_1.default, null))) : null),
109
- react_1.default.createElement(material_1.Menu, { open: Boolean(anchorEl), anchorEl: anchorEl, onClose: () => setAnchorEl(null), anchorOrigin: { vertical: 'bottom', horizontal: 'center' }, transformOrigin: { vertical: 'top', horizontal: 'center' } }, hiddenAccts === null || hiddenAccts === void 0 ? void 0 : hiddenAccts.map((acct, idx) => (react_1.default.createElement(material_1.MenuItem, { key: acct.internetAccountId, value: acct.internetAccountId, onClick: () => {
110
- const prev = shownAccts.at(-1);
111
- setShownAccts([...shownAccts.slice(0, -1), acct]);
112
- setHiddenAccts([
113
- prev,
114
- ...hiddenAccts.slice(0, idx),
115
- ...hiddenAccts.slice(idx + 1),
116
- ].filter(util_1.notEmpty));
117
- setToggleButtonValue(acct.internetAccountId);
118
- setAnchorEl(null);
119
- } }, acct.name)))))),
121
+ react_1.default.createElement(material_1.Menu, { open: Boolean(anchorEl), anchorEl: anchorEl, onClose: () => setAnchorEl(null), anchorOrigin: { vertical: 'bottom', horizontal: 'center' }, transformOrigin: { vertical: 'top', horizontal: 'center' } }, hiddenAccounts === null || hiddenAccounts === void 0 ? void 0 : hiddenAccounts.map(id => {
122
+ const { internetAccountId, name } = map[id];
123
+ return (react_1.default.createElement(material_1.MenuItem, { key: id, value: internetAccountId, onClick: () => {
124
+ setRecentlyUsedInternetAccounts([
125
+ ...new Set([
126
+ internetAccountId,
127
+ ...recentlyUsedInternetAccounts,
128
+ ].filter(util_1.notEmpty)),
129
+ ]);
130
+ setToggleButtonValue(internetAccountId);
131
+ setAnchorEl(null);
132
+ } }, name));
133
+ })))),
120
134
  locationInput,
121
135
  react_1.default.createElement(material_1.FormHelperText, null, description)));
122
136
  });
package/ui/Menu.d.ts CHANGED
@@ -20,7 +20,8 @@ export interface MenuSubHeader {
20
20
  label: string;
21
21
  }
22
22
  export interface BaseMenuItem {
23
- label: string;
23
+ id?: string;
24
+ label: React.ReactNode;
24
25
  priority?: number;
25
26
  subLabel?: string;
26
27
  icon?: React.ComponentType<SvgIconProps>;
package/ui/Menu.js CHANGED
@@ -99,21 +99,18 @@ function MenuItemEndDecoration(props) {
99
99
  return react_1.default.createElement("div", { className: classes.menuItemEndDecoration }, icon);
100
100
  }
101
101
  exports.MenuItemEndDecoration = MenuItemEndDecoration;
102
+ function checkIfValid(m) {
103
+ return m.type !== 'divider' && m.type !== 'subHeader' && !m.disabled;
104
+ }
102
105
  function findNextValidIdx(menuItems, currentIdx) {
103
- const idx = menuItems
104
- .slice(currentIdx + 1)
105
- .findIndex(menuItem => menuItem.type !== 'divider' &&
106
- menuItem.type !== 'subHeader' &&
107
- !menuItem.disabled);
106
+ const idx = menuItems.slice(currentIdx + 1).findIndex(checkIfValid);
108
107
  if (idx === -1) {
109
108
  return idx;
110
109
  }
111
110
  return currentIdx + 1 + idx;
112
111
  }
113
112
  function findPreviousValidIdx(menuItems, currentIdx) {
114
- return (0, util_1.findLastIndex)(menuItems.slice(0, currentIdx), menuItem => menuItem.type !== 'divider' &&
115
- menuItem.type !== 'subHeader' &&
116
- !menuItem.disabled);
113
+ return (0, util_1.findLastIndex)(menuItems.slice(0, currentIdx), checkIfValid);
117
114
  }
118
115
  const MenuPage = react_1.default.forwardRef(function MenuPage2(props, ref) {
119
116
  const [subMenuAnchorEl, setSubMenuAnchorEl] = (0, react_1.useState)();
@@ -198,7 +195,7 @@ const MenuPage = react_1.default.forwardRef(function MenuPage2(props, ref) {
198
195
  const onClick = 'onClick' in menuItem
199
196
  ? handleClick(menuItem.onClick)
200
197
  : undefined;
201
- return (react_1.default.createElement(material_1.MenuItem, { key: menuItem.label, style: menuItemStyle, selected: idx === selectedMenuItemIdx, onClick: onClick, onMouseMove: e => {
198
+ return (react_1.default.createElement(material_1.MenuItem, { key: menuItem.id || String(menuItem.label), style: menuItemStyle, selected: idx === selectedMenuItemIdx, onClick: onClick, onMouseMove: e => {
202
199
  if (e.currentTarget !== document.activeElement) {
203
200
  e.currentTarget.focus();
204
201
  setSelectedMenuItemIdx(idx);
@@ -246,7 +243,7 @@ const MenuPage = react_1.default.forwardRef(function MenuPage2(props, ref) {
246
243
  menuItems.map((menuItem, idx) => {
247
244
  let subMenu = null;
248
245
  if ('subMenu' in menuItem) {
249
- subMenu = (react_1.default.createElement(MenuPage, { key: menuItem.label, anchorEl: subMenuAnchorEl, open: isSubMenuOpen && openSubMenuIdx === idx, onClose: () => {
246
+ subMenu = (react_1.default.createElement(MenuPage, { key: menuItem.id || String(menuItem.label), anchorEl: subMenuAnchorEl, open: isSubMenuOpen && openSubMenuIdx === idx, onClose: () => {
250
247
  setIsSubMenuOpen(false);
251
248
  setSubMenuAnchorEl(undefined);
252
249
  }, onMenuItemClick: onMenuItemClick, menuItems: menuItem.subMenu }));
@@ -1,8 +1,11 @@
1
1
  import React from 'react';
2
2
  import { MenuItem } from '@jbrowse/core/ui';
3
- declare const MenuButton: ({ children, menuItems, ...rest }: {
3
+ declare const MenuButton: ({ children, menuItems, closeAfterItemClick, stopPropagation, setOpen, ...rest }: {
4
4
  [key: string]: unknown;
5
+ closeAfterItemClick?: boolean | undefined;
5
6
  children?: React.ReactElement<any, string | React.JSXElementConstructor<any>> | undefined;
6
7
  menuItems: MenuItem[];
8
+ stopPropagation?: boolean | undefined;
9
+ setOpen?: ((arg: boolean) => void) | undefined;
7
10
  }) => React.JSX.Element;
8
11
  export default MenuButton;
package/ui/MenuButton.js CHANGED
@@ -30,13 +30,19 @@ const react_1 = __importStar(require("react"));
30
30
  const Menu_1 = __importDefault(require("@jbrowse/core/ui/Menu"));
31
31
  const material_1 = require("@mui/material");
32
32
  const mobx_react_1 = require("mobx-react");
33
- const MenuButton = (0, mobx_react_1.observer)(function MenuButton({ children, menuItems, ...rest }) {
33
+ const MenuButton = (0, mobx_react_1.observer)(function MenuButton({ children, menuItems, closeAfterItemClick = true, stopPropagation, setOpen, ...rest }) {
34
34
  const [anchorEl, setAnchorEl] = (0, react_1.useState)();
35
+ const isOpen = !!anchorEl;
36
+ (0, react_1.useEffect)(() => {
37
+ setOpen === null || setOpen === void 0 ? void 0 : setOpen(isOpen);
38
+ }, [isOpen, setOpen]);
35
39
  return (react_1.default.createElement(react_1.default.Fragment, null,
36
40
  react_1.default.createElement(material_1.IconButton, { ...rest, onClick: event => setAnchorEl(event.currentTarget) }, children),
37
41
  react_1.default.createElement(Menu_1.default, { open: !!anchorEl, anchorEl: anchorEl, onClose: () => setAnchorEl(undefined), onMenuItemClick: (_, callback) => {
38
42
  callback();
39
- setAnchorEl(undefined);
43
+ if (closeAfterItemClick) {
44
+ setAnchorEl(undefined);
45
+ }
40
46
  }, menuItems: menuItems })));
41
47
  });
42
48
  exports.default = MenuButton;
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
- export default function SanitizedHTML({ html: pre }: {
2
+ export default function SanitizedHTML({ html: pre, className, }: {
3
+ className?: string;
3
4
  html: string;
4
5
  }): React.JSX.Element;
@@ -58,7 +58,7 @@ function isHTML(str) {
58
58
  // products/jbrowse-web/src/tests/Connection.test.tsx test (can delete mock to
59
59
  // see)
60
60
  //
61
- function SanitizedHTML({ html: pre }) {
61
+ function SanitizedHTML({ html: pre, className, }) {
62
62
  // try to add links to the text first
63
63
  const html = (0, util_1.linkify)(pre);
64
64
  const value = isHTML(html) ? html : (0, escape_html_1.default)(html);
@@ -74,7 +74,7 @@ function SanitizedHTML({ html: pre }) {
74
74
  }
75
75
  });
76
76
  }
77
- return (react_1.default.createElement("span", {
77
+ return (react_1.default.createElement("span", { className: className,
78
78
  // eslint-disable-next-line react/no-danger
79
79
  dangerouslySetInnerHTML: {
80
80
  __html: dompurify_1.default.sanitize(value),
package/util/analytics.js CHANGED
@@ -59,7 +59,7 @@ async function writeGAAnalytics(rootModel, initialTimestamp) {
59
59
  var _a;
60
60
  const jbrowseUser = 'UA-7115575-5';
61
61
  const stats = {
62
- 'tracks-count': rootModel.jbrowse.tracks.length,
62
+ 'tracks-count': rootModel.jbrowse.tracks.length, // this is all possible tracks
63
63
  ver: rootModel.version,
64
64
  electron: util_1.isElectron,
65
65
  loadTime: Date.now() - initialTimestamp,
package/util/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /// <reference types="react" />
1
+ import React from 'react';
2
2
  import PluginManager from '../PluginManager';
3
3
  import { IAnyStateTreeNode, IStateTreeNode, Instance } from 'mobx-state-tree';
4
4
  import { IReactionPublic, IReactionOptions } from 'mobx';
@@ -16,7 +16,7 @@ export * from './offscreenCanvasUtils';
16
16
  export declare function useDebounce<T>(value: T, delay: number): T;
17
17
  export declare function useWidthSetter(view: {
18
18
  setWidth: (arg: number) => void;
19
- }, padding: string): import("react").RefObject<HTMLDivElement>;
19
+ }, padding: string): React.RefObject<HTMLDivElement>;
20
20
  export declare function useDebouncedCallback<T>(callback: (...args: T[]) => void, wait?: number): (...args: T[]) => void;
21
21
  /**
22
22
  * find the first node in the hierarchy that matches the given predicate
@@ -213,7 +213,7 @@ export declare function renameRegionsIfNeeded<ARGTYPE extends {
213
213
  assemblyName?: string;
214
214
  regions?: Region[];
215
215
  signal?: AbortSignal;
216
- adapterConfig: unknown;
216
+ adapterConfig: Record<string, unknown>;
217
217
  sessionId: string;
218
218
  statusCallback?: (arg: string) => void;
219
219
  }>(assemblyManager: AssemblyManager, args: ARGTYPE): Promise<ARGTYPE & {
@@ -389,6 +389,9 @@ interface BasicFeature {
389
389
  refName: string;
390
390
  }
391
391
  export declare function gatherOverlaps(regions: BasicFeature[]): BasicFeature[];
392
- export { default as SimpleFeature, type Feature, type SimpleFeatureSerialized, isFeature, } from './simpleFeature';
393
392
  export declare function stripAlpha(str: string): string;
393
+ export declare function renderToStaticMarkup(node: React.ReactElement, createRootFn?: (elt: Element | DocumentFragment) => {
394
+ render: (node: React.ReactElement) => unknown;
395
+ }): string;
396
+ export { default as SimpleFeature, type Feature, type SimpleFeatureSerialized, isFeature, } from './simpleFeature';
394
397
  export { blobToDataURL } from './blobToDataURL';
package/util/index.js CHANGED
@@ -18,7 +18,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
20
  exports.toLocale = exports.getBpDisplayStr = exports.isSupportedIndexingAdapter = exports.bytesForRegions = exports.objectHash = exports.hashCode = exports.updateStatus = exports.generateCodonTable = exports.defaultCodonTable = exports.defaultStops = exports.defaultStarts = exports.measureText = exports.rIC = exports.complement = exports.reverse = exports.revcom = exports.isElectron = exports.stringify = exports.shorten = exports.minmax = exports.renameRegionsIfNeeded = exports.renameRegionIfNeeded = exports.makeAbortableReaction = exports.findLast = exports.findLastIndex = exports.iterMap = exports.bpSpanPx = exports.featureSpanPx = exports.cartesianToPolar = exports.polarToCartesian = exports.degToRad = exports.radToDeg = exports.bpToPx = exports.clamp = exports.compareLocStrings = exports.compareLocs = exports.parseLocString = exports.parseLocStringOneBased = exports.assembleLocStringFast = exports.assembleLocString = exports.getContainingDisplay = exports.getContainingTrack = exports.getContainingView = exports.getSession = exports.findParentThatIs = exports.springAnimate = exports.findParentThat = exports.useDebouncedCallback = exports.useWidthSetter = exports.useDebounce = void 0;
21
- exports.blobToDataURL = exports.stripAlpha = exports.isFeature = exports.SimpleFeature = exports.gatherOverlaps = exports.mergeIntervals = exports.notEmpty = exports.groupBy = exports.avg = exports.sum = exports.min = exports.max = exports.localStorageSetItem = exports.localStorageGetItem = exports.getEnv = exports.measureGridWidth = exports.linkify = exports.coarseStripHTML = exports.getStr = exports.getUriLink = exports.useLocalStorage = exports.getLayoutId = exports.getViewParams = exports.getTickDisplayStr = void 0;
21
+ exports.blobToDataURL = exports.isFeature = exports.SimpleFeature = exports.renderToStaticMarkup = exports.stripAlpha = exports.gatherOverlaps = exports.mergeIntervals = exports.notEmpty = exports.groupBy = exports.avg = exports.sum = exports.min = exports.max = exports.localStorageSetItem = exports.localStorageGetItem = exports.getEnv = exports.measureGridWidth = exports.linkify = exports.coarseStripHTML = exports.getStr = exports.getUriLink = exports.useLocalStorage = exports.getLayoutId = exports.getViewParams = exports.getTickDisplayStr = void 0;
22
22
  /* eslint-disable @typescript-eslint/no-explicit-any */
23
23
  const react_1 = require("react");
24
24
  const is_object_1 = __importDefault(require("is-object"));
@@ -30,6 +30,8 @@ const types_2 = require("./types");
30
30
  // has to be the full path and not the relative path to get the jest mock
31
31
  const useMeasure_1 = __importDefault(require("@jbrowse/core/util/useMeasure"));
32
32
  const colord_1 = require("./colord");
33
+ // eslint-disable-next-line react/no-deprecated
34
+ const react_dom_1 = require("react-dom");
33
35
  __exportStar(require("./types"), exports);
34
36
  __exportStar(require("./aborting"), exports);
35
37
  __exportStar(require("./when"), exports);
@@ -1132,13 +1134,27 @@ function gatherOverlaps(regions) {
1132
1134
  return Object.values(memo).flatMap(group => mergeIntervals(group.sort((a, b) => a.start - b.start)));
1133
1135
  }
1134
1136
  exports.gatherOverlaps = gatherOverlaps;
1135
- var simpleFeature_1 = require("./simpleFeature");
1136
- Object.defineProperty(exports, "SimpleFeature", { enumerable: true, get: function () { return __importDefault(simpleFeature_1).default; } });
1137
- Object.defineProperty(exports, "isFeature", { enumerable: true, get: function () { return simpleFeature_1.isFeature; } });
1138
1137
  function stripAlpha(str) {
1139
1138
  const c = (0, colord_1.colord)(str);
1140
1139
  return c.alpha(1).toHex();
1141
1140
  }
1142
1141
  exports.stripAlpha = stripAlpha;
1142
+ // https://react.dev/reference/react-dom/server/renderToString#removing-rendertostring-from-the-client-code
1143
+ function renderToStaticMarkup(node, createRootFn) {
1144
+ const div = document.createElement('div');
1145
+ (0, react_dom_1.flushSync)(() => {
1146
+ if (createRootFn) {
1147
+ createRootFn(div).render(node);
1148
+ }
1149
+ else {
1150
+ (0, react_dom_1.render)(node, div);
1151
+ }
1152
+ });
1153
+ return div.innerHTML.replace('&gt;', '>').replace('&lt;', '<');
1154
+ }
1155
+ exports.renderToStaticMarkup = renderToStaticMarkup;
1156
+ var simpleFeature_1 = require("./simpleFeature");
1157
+ Object.defineProperty(exports, "SimpleFeature", { enumerable: true, get: function () { return __importDefault(simpleFeature_1).default; } });
1158
+ Object.defineProperty(exports, "isFeature", { enumerable: true, get: function () { return simpleFeature_1.isFeature; } });
1143
1159
  var blobToDataURL_1 = require("./blobToDataURL");
1144
1160
  Object.defineProperty(exports, "blobToDataURL", { enumerable: true, get: function () { return blobToDataURL_1.blobToDataURL; } });
@@ -1,4 +1,5 @@
1
1
  /// <reference types="node" />
2
+ import { Buffer } from 'buffer';
2
3
  import { RemoteFile, PolyfilledResponse } from 'generic-filehandle';
3
4
  export interface BinaryRangeResponse {
4
5
  headers: Record<string, string>;
@@ -14,9 +14,9 @@ function binaryRangeFetch(url, start, end, options = {}) {
14
14
  }
15
15
  const globalRangeCache = new http_range_fetcher_1.HttpRangeFetcher({
16
16
  fetch: binaryRangeFetch,
17
- size: 500 * 1024 ** 2,
18
- chunkSize: 128 * 1024,
19
- maxFetchSize: 100 * 1024 ** 2,
17
+ size: 500 * 1024 ** 2, // 500MiB
18
+ chunkSize: 128 * 1024, // 128KiB
19
+ maxFetchSize: 100 * 1024 ** 2, // 100MiB
20
20
  minimumTTL: 24 * 60 * 60 * 1000, // 1 day
21
21
  });
22
22
  function clearCache() {