@jbrowse/plugin-linear-genome-view 3.5.0 → 3.6.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.
Files changed (33) hide show
  1. package/dist/BaseLinearDisplay/components/BaseLinearDisplay.js +3 -6
  2. package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +1 -0
  3. package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.js +32 -22
  4. package/dist/LinearBareDisplay/model.d.ts +1 -0
  5. package/dist/LinearBasicDisplay/model.d.ts +1 -0
  6. package/dist/LinearGenomeView/components/GetSequenceDialog.d.ts +1 -2
  7. package/dist/LinearGenomeView/components/GetSequenceDialog.js +6 -31
  8. package/dist/LinearGenomeView/components/RefNameAutocomplete/index.js +17 -7
  9. package/dist/LinearGenomeView/components/RubberbandTooltip.d.ts +1 -1
  10. package/dist/LinearGenomeView/components/RubberbandTooltip.js +13 -21
  11. package/dist/LinearGenomeView/components/SearchResultsTable.js +29 -13
  12. package/dist/LinearGenomeView/components/fetchSequence.d.ts +4 -0
  13. package/dist/LinearGenomeView/components/fetchSequence.js +29 -0
  14. package/dist/LinearGenomeView/model.d.ts +1 -0
  15. package/dist/LinearGenomeView/model.js +9 -1
  16. package/dist/index.d.ts +1 -0
  17. package/esm/BaseLinearDisplay/components/BaseLinearDisplay.js +3 -6
  18. package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +1 -0
  19. package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.js +32 -22
  20. package/esm/LinearBareDisplay/model.d.ts +1 -0
  21. package/esm/LinearBasicDisplay/model.d.ts +1 -0
  22. package/esm/LinearGenomeView/components/GetSequenceDialog.d.ts +1 -2
  23. package/esm/LinearGenomeView/components/GetSequenceDialog.js +8 -33
  24. package/esm/LinearGenomeView/components/RefNameAutocomplete/index.js +18 -8
  25. package/esm/LinearGenomeView/components/RubberbandTooltip.d.ts +1 -1
  26. package/esm/LinearGenomeView/components/RubberbandTooltip.js +14 -22
  27. package/esm/LinearGenomeView/components/SearchResultsTable.js +30 -14
  28. package/esm/LinearGenomeView/components/fetchSequence.d.ts +4 -0
  29. package/esm/LinearGenomeView/components/fetchSequence.js +26 -0
  30. package/esm/LinearGenomeView/model.d.ts +1 -0
  31. package/esm/LinearGenomeView/model.js +9 -1
  32. package/esm/index.d.ts +1 -0
  33. package/package.json +3 -4
@@ -8,7 +8,6 @@ const jsx_runtime_1 = require("react/jsx-runtime");
8
8
  const react_1 = require("react");
9
9
  const configuration_1 = require("@jbrowse/core/configuration");
10
10
  const ui_1 = require("@jbrowse/core/ui");
11
- const material_1 = require("@mui/material");
12
11
  const mobx_react_1 = require("mobx-react");
13
12
  const mui_1 = require("tss-react/mui");
14
13
  const LinearBlocks_1 = __importDefault(require("./LinearBlocks"));
@@ -23,7 +22,6 @@ const useStyles = (0, mui_1.makeStyles)()({
23
22
  });
24
23
  const BaseLinearDisplay = (0, mobx_react_1.observer)(function (props) {
25
24
  const { classes } = useStyles();
26
- const theme = (0, material_1.useTheme)();
27
25
  const ref = (0, react_1.useRef)(null);
28
26
  const [clientRect, setClientRect] = (0, react_1.useState)();
29
27
  const [offsetMouseCoord, setOffsetMouseCoord] = (0, react_1.useState)([0, 0]);
@@ -32,6 +30,7 @@ const BaseLinearDisplay = (0, mobx_react_1.observer)(function (props) {
32
30
  const { model, children } = props;
33
31
  const { TooltipComponent, DisplayMessageComponent, height } = model;
34
32
  const items = model.contextMenuItems();
33
+ const open = Boolean(contextCoord) && items.length > 0;
35
34
  return ((0, jsx_runtime_1.jsxs)("div", { ref: ref, "data-testid": `display-${(0, configuration_1.getConf)(model, 'displayId')}`, className: classes.display, onContextMenu: event => {
36
35
  event.preventDefault();
37
36
  if (contextCoord) {
@@ -49,7 +48,7 @@ const BaseLinearDisplay = (0, mobx_react_1.observer)(function (props) {
49
48
  setOffsetMouseCoord([event.clientX - left, event.clientY - top]);
50
49
  setClientMouseCoord([event.clientX, event.clientY]);
51
50
  setClientRect(rect);
52
- }, children: [DisplayMessageComponent ? ((0, jsx_runtime_1.jsx)(DisplayMessageComponent, { model: model })) : ((0, jsx_runtime_1.jsx)(LinearBlocks_1.default, { ...props })), children, (0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: null, children: (0, jsx_runtime_1.jsx)(TooltipComponent, { model: model, height: height, offsetMouseCoord: offsetMouseCoord, clientMouseCoord: clientMouseCoord, clientRect: clientRect, mouseCoord: offsetMouseCoord }) }), (0, jsx_runtime_1.jsx)(ui_1.Menu, { open: Boolean(contextCoord) && items.length > 0, onMenuItemClick: (_, callback) => {
51
+ }, children: [DisplayMessageComponent ? ((0, jsx_runtime_1.jsx)(DisplayMessageComponent, { model: model })) : ((0, jsx_runtime_1.jsx)(LinearBlocks_1.default, { ...props })), children, (0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: null, children: (0, jsx_runtime_1.jsx)(TooltipComponent, { model: model, height: height, offsetMouseCoord: offsetMouseCoord, clientMouseCoord: clientMouseCoord, clientRect: clientRect, mouseCoord: offsetMouseCoord }) }), open ? ((0, jsx_runtime_1.jsx)(ui_1.Menu, { open: true, onMenuItemClick: (_, callback) => {
53
52
  callback();
54
53
  setContextCoord(undefined);
55
54
  }, onClose: () => {
@@ -64,9 +63,7 @@ const BaseLinearDisplay = (0, mobx_react_1.observer)(function (props) {
64
63
  },
65
64
  }, anchorReference: "anchorPosition", anchorPosition: contextCoord
66
65
  ? { top: contextCoord[1], left: contextCoord[0] }
67
- : undefined, style: {
68
- zIndex: theme.zIndex.tooltip,
69
- }, menuItems: items })] }));
66
+ : undefined, menuItems: items })) : null] }));
70
67
  });
71
68
  exports.default = BaseLinearDisplay;
72
69
  var Tooltip_1 = require("./Tooltip");
@@ -197,6 +197,7 @@ export declare const BaseLinearDisplay: import("mobx-state-tree").IModelType<{
197
197
  readonly renderDelay: number;
198
198
  readonly TooltipComponent: AnyReactComponentType;
199
199
  readonly selectedFeatureId: string | undefined;
200
+ copyInfoToClipboard(feature: Feature): void;
200
201
  } & {
201
202
  readonly features: CompositeMap<string, Feature>;
202
203
  readonly featureUnderMouse: Feature | undefined;
@@ -44,7 +44,9 @@ const util_1 = require("@jbrowse/core/util");
44
44
  const compositeMap_1 = __importDefault(require("@jbrowse/core/util/compositeMap"));
45
45
  const tracks_1 = require("@jbrowse/core/util/tracks");
46
46
  const CenterFocusStrong_1 = __importDefault(require("@mui/icons-material/CenterFocusStrong"));
47
+ const ContentCopy_1 = __importDefault(require("@mui/icons-material/ContentCopy"));
47
48
  const MenuOpen_1 = __importDefault(require("@mui/icons-material/MenuOpen"));
49
+ const copy_to_clipboard_1 = __importDefault(require("copy-to-clipboard"));
48
50
  const mobx_1 = require("mobx");
49
51
  const mobx_state_tree_1 = require("mobx-state-tree");
50
52
  const FeatureDensityMixin_1 = __importDefault(require("./FeatureDensityMixin"));
@@ -93,6 +95,12 @@ function stateModelFactory() {
93
95
  }
94
96
  return undefined;
95
97
  },
98
+ copyInfoToClipboard(feature) {
99
+ const { uniqueId, ...rest } = feature.toJSON();
100
+ const session = (0, util_1.getSession)(self);
101
+ (0, copy_to_clipboard_1.default)(JSON.stringify(rest, null, 4));
102
+ session.notify('Copied to clipboard', 'success');
103
+ },
96
104
  }))
97
105
  .views(self => ({
98
106
  get features() {
@@ -205,30 +213,32 @@ function stateModelFactory() {
205
213
  return [];
206
214
  },
207
215
  contextMenuItems() {
208
- return [
209
- ...(self.contextMenuFeature
210
- ? [
211
- {
212
- label: 'Open feature details',
213
- icon: MenuOpen_1.default,
214
- onClick: () => {
215
- if (self.contextMenuFeature) {
216
- self.selectFeature(self.contextMenuFeature);
217
- }
218
- },
216
+ const feat = self.contextMenuFeature;
217
+ return feat
218
+ ? [
219
+ {
220
+ label: 'Open feature details',
221
+ icon: MenuOpen_1.default,
222
+ onClick: () => {
223
+ self.selectFeature(feat);
224
+ },
225
+ },
226
+ {
227
+ label: 'Zoom to feature',
228
+ icon: CenterFocusStrong_1.default,
229
+ onClick: () => {
230
+ self.navToFeature(feat);
219
231
  },
220
- {
221
- label: 'Zoom to feature',
222
- icon: CenterFocusStrong_1.default,
223
- onClick: () => {
224
- if (self.contextMenuFeature) {
225
- self.navToFeature(self.contextMenuFeature);
226
- }
227
- },
232
+ },
233
+ {
234
+ label: 'Copy info to clipboard',
235
+ icon: ContentCopy_1.default,
236
+ onClick: () => {
237
+ self.copyInfoToClipboard(feat);
228
238
  },
229
- ]
230
- : []),
231
- ];
239
+ },
240
+ ]
241
+ : [];
232
242
  },
233
243
  renderProps() {
234
244
  return {
@@ -181,6 +181,7 @@ export declare function stateModelFactory(configSchema: AnyConfigurationSchemaTy
181
181
  readonly renderDelay: number;
182
182
  readonly TooltipComponent: import("@jbrowse/core/util").AnyReactComponentType;
183
183
  readonly selectedFeatureId: string | undefined;
184
+ copyInfoToClipboard(feature: import("@jbrowse/core/util").Feature): void;
184
185
  } & {
185
186
  readonly features: import("@jbrowse/core/util/compositeMap").default<string, import("@jbrowse/core/util").Feature>;
186
187
  readonly featureUnderMouse: import("@jbrowse/core/util").Feature | undefined;
@@ -189,6 +189,7 @@ declare function stateModelFactory(configSchema: AnyConfigurationSchemaType): im
189
189
  readonly renderDelay: number;
190
190
  readonly TooltipComponent: import("@jbrowse/core/util").AnyReactComponentType;
191
191
  readonly selectedFeatureId: string | undefined;
192
+ copyInfoToClipboard(feature: import("@jbrowse/core/util").Feature): void;
192
193
  } & {
193
194
  readonly features: import("@jbrowse/core/util/compositeMap").default<string, import("@jbrowse/core/util").Feature>;
194
195
  readonly featureUnderMouse: import("@jbrowse/core/util").Feature | undefined;
@@ -1,7 +1,6 @@
1
1
  import type { LinearGenomeViewModel } from '..';
2
- type LGV = LinearGenomeViewModel;
3
2
  declare const GetSequenceDialog: ({ model, handleClose, }: {
4
- model: LGV;
3
+ model: LinearGenomeViewModel;
5
4
  handleClose: () => void;
6
5
  }) => import("react/jsx-runtime").JSX.Element;
7
6
  export default GetSequenceDialog;
@@ -5,17 +5,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const jsx_runtime_1 = require("react/jsx-runtime");
7
7
  const react_1 = require("react");
8
- const configuration_1 = require("@jbrowse/core/configuration");
9
8
  const ui_1 = require("@jbrowse/core/ui");
10
- const Icons_1 = require("@jbrowse/core/ui/Icons");
11
9
  const util_1 = require("@jbrowse/core/util");
12
10
  const formatFastaStrings_1 = require("@jbrowse/core/util/formatFastaStrings");
11
+ const ContentCopy_1 = __importDefault(require("@mui/icons-material/ContentCopy"));
13
12
  const GetApp_1 = __importDefault(require("@mui/icons-material/GetApp"));
14
13
  const material_1 = require("@mui/material");
15
14
  const copy_to_clipboard_1 = __importDefault(require("copy-to-clipboard"));
16
15
  const file_saver_1 = require("file-saver");
17
16
  const mobx_react_1 = require("mobx-react");
18
17
  const mui_1 = require("tss-react/mui");
18
+ const fetchSequence_1 = require("./fetchSequence");
19
19
  const useStyles = (0, mui_1.makeStyles)()({
20
20
  dialogContent: {
21
21
  width: '80em',
@@ -27,30 +27,6 @@ const useStyles = (0, mui_1.makeStyles)()({
27
27
  marginLeft: 10,
28
28
  },
29
29
  });
30
- async function fetchSequence(model, regions) {
31
- const session = (0, util_1.getSession)(model);
32
- const { leftOffset, rightOffset } = model;
33
- if (!leftOffset || !rightOffset) {
34
- throw new Error('no offsets on model to use for range');
35
- }
36
- const assemblyNames = new Set(regions.map(r => r.assemblyName));
37
- if (assemblyNames.size > 1) {
38
- throw new Error('not able to fetch sequences from multiple assemblies currently');
39
- }
40
- const { rpcManager, assemblyManager } = session;
41
- const assemblyName = leftOffset.assemblyName || rightOffset.assemblyName || '';
42
- const assembly = assemblyManager.get(assemblyName);
43
- if (!assembly) {
44
- throw new Error(`assembly ${assemblyName} not found`);
45
- }
46
- const adapterConfig = (0, configuration_1.getConf)(assembly, ['sequence', 'adapter']);
47
- const sessionId = 'getSequence';
48
- return rpcManager.call(sessionId, 'CoreGetFeatures', {
49
- adapterConfig,
50
- regions,
51
- sessionId,
52
- });
53
- }
54
30
  const GetSequenceDialog = (0, mobx_react_1.observer)(function ({ model, handleClose, }) {
55
31
  const { classes } = useStyles();
56
32
  const [error, setError] = (0, react_1.useState)();
@@ -59,7 +35,7 @@ const GetSequenceDialog = (0, mobx_react_1.observer)(function ({ model, handleCl
59
35
  const [copied, setCopied] = (0, react_1.useState)(false);
60
36
  const [comp, setComplement] = (0, react_1.useState)(false);
61
37
  const { leftOffset, rightOffset } = model;
62
- const loading = Boolean(sequenceChunks === undefined);
38
+ const loading = sequenceChunks === undefined;
63
39
  (0, react_1.useEffect)(() => {
64
40
  const controller = new AbortController();
65
41
  (async () => {
@@ -68,8 +44,7 @@ const GetSequenceDialog = (0, mobx_react_1.observer)(function ({ model, handleCl
68
44
  if (selection.length === 0) {
69
45
  throw new Error('Selected region is out of bounds');
70
46
  }
71
- const chunks = await fetchSequence(model, selection);
72
- setSequenceChunks(chunks);
47
+ setSequenceChunks(await (0, fetchSequence_1.fetchSequence)(model, selection));
73
48
  }
74
49
  catch (e) {
75
50
  console.error(e);
@@ -106,7 +81,7 @@ const GetSequenceDialog = (0, mobx_react_1.observer)(function ({ model, handleCl
106
81
  return ((0, jsx_runtime_1.jsxs)(ui_1.Dialog, { maxWidth: "xl", open: true, onClose: () => {
107
82
  handleClose();
108
83
  model.setOffsets();
109
- }, title: "Reference sequence", children: [(0, jsx_runtime_1.jsxs)(material_1.DialogContent, { children: [error ? ((0, jsx_runtime_1.jsx)(material_1.Typography, { color: "error", children: `${error}` })) : loading ? ((0, jsx_runtime_1.jsxs)(material_1.Container, { children: ["Retrieving reference sequence...", (0, jsx_runtime_1.jsx)(material_1.CircularProgress, { className: classes.ml, size: 20, disableShrink: true })] })) : null, (0, jsx_runtime_1.jsx)(material_1.TextField, { "data-testid": "rubberband-sequence", variant: "outlined", multiline: true, minRows: 5, maxRows: 10, disabled: sequenceTooLarge, className: classes.dialogContent, fullWidth: true, value: sequenceTooLarge
84
+ }, title: "Reference sequence", children: [(0, jsx_runtime_1.jsxs)(material_1.DialogContent, { children: [error ? ((0, jsx_runtime_1.jsx)(ui_1.ErrorMessage, { error: error })) : loading ? ((0, jsx_runtime_1.jsx)(ui_1.LoadingEllipses, { message: "Retrieving sequences" })) : null, (0, jsx_runtime_1.jsx)(material_1.TextField, { "data-testid": "rubberband-sequence", variant: "outlined", multiline: true, minRows: 5, maxRows: 10, disabled: sequenceTooLarge, className: classes.dialogContent, fullWidth: true, value: sequenceTooLarge
110
85
  ? 'Reference sequence too large to display, use the download FASTA button'
111
86
  : sequence, slotProps: {
112
87
  input: {
@@ -125,7 +100,7 @@ const GetSequenceDialog = (0, mobx_react_1.observer)(function ({ model, handleCl
125
100
  setTimeout(() => {
126
101
  setCopied(false);
127
102
  }, 500);
128
- }, disabled: loading || !!error || sequenceTooLarge, color: "primary", startIcon: (0, jsx_runtime_1.jsx)(Icons_1.ContentCopy, {}), children: copied ? 'Copied' : 'Copy to clipboard' }), (0, jsx_runtime_1.jsx)(material_1.Button, { onClick: () => {
103
+ }, disabled: loading || !!error || sequenceTooLarge, color: "primary", startIcon: (0, jsx_runtime_1.jsx)(ContentCopy_1.default, {}), children: copied ? 'Copied' : 'Copy to clipboard' }), (0, jsx_runtime_1.jsx)(material_1.Button, { onClick: () => {
129
104
  (0, file_saver_1.saveAs)(new Blob([sequence || ''], {
130
105
  type: 'text/x-fasta;charset=utf-8',
131
106
  }), 'jbrowse_ref_seq.fa');
@@ -52,38 +52,48 @@ const RefNameAutocomplete = (0, mobx_react_1.observer)(function ({ model, onSele
52
52
  const [currentSearch, setCurrentSearch] = (0, react_1.useState)('');
53
53
  const [inputValue, setInputValue] = (0, react_1.useState)('');
54
54
  const [searchOptions, setSearchOptions] = (0, react_1.useState)();
55
- const debouncedSearch = (0, util_1.useDebounce)(currentSearch, 300);
55
+ const debouncedSearch = (0, util_1.useDebounce)(currentSearch, 50);
56
56
  const assembly = assemblyName ? assemblyManager.get(assemblyName) : undefined;
57
57
  const { coarseVisibleLocStrings, hasDisplayedRegions } = model;
58
58
  (0, react_1.useEffect)(() => {
59
- ;
59
+ const isCurrent = { cancelled: false };
60
60
  (async () => {
61
61
  try {
62
62
  if (debouncedSearch === '' || !assemblyName) {
63
63
  return;
64
64
  }
65
65
  setLoaded(false);
66
- setSearchOptions((0, util_2.getDeduplicatedResult)(await fetchResults(debouncedSearch)));
66
+ const results = await fetchResults(debouncedSearch);
67
+ if (!isCurrent.cancelled) {
68
+ setSearchOptions((0, util_2.getDeduplicatedResult)(results));
69
+ }
67
70
  }
68
71
  catch (e) {
69
72
  console.error(e);
70
- session.notifyError(`${e}`, e);
73
+ if (!isCurrent.cancelled) {
74
+ session.notifyError(`${e}`, e);
75
+ }
71
76
  }
72
77
  finally {
73
- setLoaded(true);
78
+ if (!isCurrent.cancelled) {
79
+ setLoaded(true);
80
+ }
74
81
  }
75
82
  })();
83
+ return () => {
84
+ isCurrent.cancelled = true;
85
+ };
76
86
  }, [assemblyName, fetchResults, debouncedSearch, session]);
77
87
  const inputBoxVal = coarseVisibleLocStrings || value || '';
78
88
  const regions = assembly === null || assembly === void 0 ? void 0 : assembly.regions;
79
- const regionOptions = (regions === null || regions === void 0 ? void 0 : regions.map(region => ({
89
+ const regionOptions = (0, react_1.useMemo)(() => (regions === null || regions === void 0 ? void 0 : regions.map(region => ({
80
90
  result: new BaseResults_1.RefSequenceResult({
81
91
  refName: region.refName,
82
92
  label: region.refName,
83
93
  displayString: region.refName,
84
94
  matchedAttribute: 'refName',
85
95
  }),
86
- }))) || [];
96
+ }))) || [], [regions]);
87
97
  return ((0, jsx_runtime_1.jsx)(material_1.Autocomplete, { "data-testid": "autocomplete", disableListWrap: true, disableClearable: true, disabled: !assemblyName, freeSolo: true, includeInputInList: true, selectOnFocus: true, style: {
88
98
  ...style,
89
99
  width: Math.min(Math.max((0, util_1.measureText)(inputBoxVal, 14) + 100, minWidth), maxWidth),
@@ -1,5 +1,5 @@
1
1
  export default function RubberbandTooltip({ anchorEl, side, text, }: {
2
2
  anchorEl: HTMLSpanElement;
3
3
  side: string;
4
- text: string;
4
+ text: React.ReactNode;
5
5
  }): import("react/jsx-runtime").JSX.Element;
@@ -3,26 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = RubberbandTooltip;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const material_1 = require("@mui/material");
6
- const mui_1 = require("tss-react/mui");
7
- const useStyles = (0, mui_1.makeStyles)()(theme => {
8
- return {
9
- popover: {
10
- mouseEvents: 'none',
11
- cursor: 'crosshair',
12
- },
13
- paper: {
14
- paddingLeft: theme.spacing(1),
15
- paddingRight: theme.spacing(1),
16
- },
17
- };
18
- });
19
6
  function RubberbandTooltip({ anchorEl, side, text, }) {
20
- const { classes } = useStyles();
21
- return ((0, jsx_runtime_1.jsx)(material_1.Popover, { className: classes.popover, classes: { paper: classes.paper }, open: true, anchorEl: anchorEl, anchorOrigin: {
22
- vertical: 'top',
23
- horizontal: side === 'left' ? 'left' : 'right',
24
- }, transformOrigin: {
25
- vertical: 'bottom',
26
- horizontal: side === 'left' ? 'right' : 'left',
27
- }, keepMounted: true, disableRestoreFocus: true, children: (0, jsx_runtime_1.jsx)(material_1.Typography, { children: text }) }));
7
+ return ((0, jsx_runtime_1.jsx)(material_1.Tooltip, { title: text, open: true, placement: side === 'left' ? 'left-start' : 'right-start', slotProps: {
8
+ popper: {
9
+ anchorEl,
10
+ modifiers: [
11
+ {
12
+ name: 'offset',
13
+ options: {
14
+ offset: [-30, -10],
15
+ },
16
+ },
17
+ ],
18
+ },
19
+ }, children: (0, jsx_runtime_1.jsx)("span", {}) }));
28
20
  }
@@ -43,18 +43,34 @@ function SearchResultsTable({ searchResults, assemblyName: optAssemblyName, mode
43
43
  session.notify(`${e}`, 'warning');
44
44
  }
45
45
  }
46
- return ((0, jsx_runtime_1.jsx)(material_1.TableContainer, { component: material_1.Paper, children: (0, jsx_runtime_1.jsxs)(material_1.Table, { children: [(0, jsx_runtime_1.jsx)(material_1.TableHead, { children: (0, jsx_runtime_1.jsxs)(material_1.TableRow, { children: [(0, jsx_runtime_1.jsx)(material_1.TableCell, { children: "Name" }), (0, jsx_runtime_1.jsx)(material_1.TableCell, { align: "right", children: "Location" }), (0, jsx_runtime_1.jsx)(material_1.TableCell, { align: "right", children: "Track" }), (0, jsx_runtime_1.jsx)(material_1.TableCell, { align: "right" })] }) }), (0, jsx_runtime_1.jsx)(material_1.TableBody, { children: searchResults.map(result => ((0, jsx_runtime_1.jsxs)(material_1.TableRow, { children: [(0, jsx_runtime_1.jsx)(material_1.TableCell, { component: "th", scope: "row", children: result.getLabel() }), (0, jsx_runtime_1.jsx)(material_1.TableCell, { align: "right", children: result.getLocation() }), (0, jsx_runtime_1.jsx)(material_1.TableCell, { align: "right", children: getTrackName(result.getTrackId()) || 'N/A' }), (0, jsx_runtime_1.jsx)(material_1.TableCell, { align: "right", children: (0, jsx_runtime_1.jsx)(material_1.Button, { onClick: async () => {
47
- try {
48
- await handleClick(result.getLocation() || result.getLabel());
49
- const resultTrackId = result.getTrackId();
50
- if (resultTrackId) {
51
- model.showTrack(resultTrackId);
46
+ return ((0, jsx_runtime_1.jsx)(material_1.TableContainer, { component: material_1.Paper, children: (0, jsx_runtime_1.jsxs)(material_1.Table, { children: [(0, jsx_runtime_1.jsx)(material_1.TableHead, { children: (0, jsx_runtime_1.jsxs)(material_1.TableRow, { children: [(0, jsx_runtime_1.jsx)(material_1.TableCell, { children: "Name" }), (0, jsx_runtime_1.jsx)(material_1.TableCell, { align: "right", children: "Location" }), (0, jsx_runtime_1.jsx)(material_1.TableCell, { align: "right", children: "Track" }), (0, jsx_runtime_1.jsx)(material_1.TableCell, { align: "right" })] }) }), (0, jsx_runtime_1.jsx)(material_1.TableBody, { children: searchResults.map(result => {
47
+ const locString = result.getLocation();
48
+ let loc;
49
+ try {
50
+ loc = locString
51
+ ? (0, util_1.parseLocString)(locString, refName => assembly.isValidRefName(refName))
52
+ : undefined;
53
+ }
54
+ catch (e) { }
55
+ return ((0, jsx_runtime_1.jsxs)(material_1.TableRow, { children: [(0, jsx_runtime_1.jsx)(material_1.TableCell, { component: "th", scope: "row", children: result.getLabel() }), (0, jsx_runtime_1.jsx)(material_1.TableCell, { align: "right", children: loc
56
+ ? (0, util_1.assembleLocString)({
57
+ ...loc,
58
+ refName: assembly.getCanonicalRefName(loc.refName) ||
59
+ loc.refName,
60
+ })
61
+ : locString }), (0, jsx_runtime_1.jsx)(material_1.TableCell, { align: "right", children: getTrackName(result.getTrackId()) || 'N/A' }), (0, jsx_runtime_1.jsx)(material_1.TableCell, { align: "right", children: (0, jsx_runtime_1.jsx)(material_1.Button, { onClick: async () => {
62
+ try {
63
+ await handleClick(result.getLocation() || result.getLabel());
64
+ const resultTrackId = result.getTrackId();
65
+ if (resultTrackId) {
66
+ model.showTrack(resultTrackId);
67
+ }
52
68
  }
53
- }
54
- catch (e) {
55
- console.error(e);
56
- session.notifyError(`${e}`, e);
57
- }
58
- handleClose();
59
- }, color: "primary", variant: "contained", children: "Go" }) })] }, result.getId()))) })] }) }));
69
+ catch (e) {
70
+ console.error(e);
71
+ session.notifyError(`${e}`, e);
72
+ }
73
+ handleClose();
74
+ }, color: "primary", variant: "contained", children: "Go" }) })] }, result.getId()));
75
+ }) })] }) }));
60
76
  }
@@ -0,0 +1,4 @@
1
+ import type { LinearGenomeViewModel } from '../model';
2
+ import type { Feature } from '@jbrowse/core/util';
3
+ import type { Region } from '@jbrowse/core/util/types';
4
+ export declare function fetchSequence(model: LinearGenomeViewModel, regions: Region[]): Promise<Feature[]>;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchSequence = fetchSequence;
4
+ const configuration_1 = require("@jbrowse/core/configuration");
5
+ const util_1 = require("@jbrowse/core/util");
6
+ async function fetchSequence(model, regions) {
7
+ const session = (0, util_1.getSession)(model);
8
+ const { leftOffset, rightOffset } = model;
9
+ if (!leftOffset || !rightOffset) {
10
+ throw new Error('no offsets on model to use for range');
11
+ }
12
+ const assemblyNames = new Set(regions.map(r => r.assemblyName));
13
+ if (assemblyNames.size > 1) {
14
+ throw new Error('not able to fetch sequences from multiple assemblies currently');
15
+ }
16
+ const { rpcManager, assemblyManager } = session;
17
+ const assemblyName = leftOffset.assemblyName || rightOffset.assemblyName || '';
18
+ const assembly = assemblyManager.get(assemblyName);
19
+ if (!assembly) {
20
+ throw new Error(`assembly ${assemblyName} not found`);
21
+ }
22
+ const adapterConfig = (0, configuration_1.getConf)(assembly, ['sequence', 'adapter']);
23
+ const sessionId = 'getSequence';
24
+ return rpcManager.call(sessionId, 'CoreGetFeatures', {
25
+ adapterConfig,
26
+ regions,
27
+ sessionId,
28
+ });
29
+ }
@@ -58,6 +58,7 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
58
58
  readonly width: number;
59
59
  readonly interRegionPaddingWidth: number;
60
60
  readonly assemblyNames: string[];
61
+ readonly assemblyDisplayNames: string[];
61
62
  readonly isTopLevelView: import("@jbrowse/core/util").AbstractViewModel | undefined;
62
63
  readonly stickyViewHeaders: boolean | undefined;
63
64
  readonly rubberbandTop: number;
@@ -136,6 +136,14 @@ function stateModelFactory(pluginManager) {
136
136
  ...new Set(self.displayedRegions.map(region => region.assemblyName)),
137
137
  ];
138
138
  },
139
+ get assemblyDisplayNames() {
140
+ const { assemblyManager } = (0, util_1.getSession)(self);
141
+ return this.assemblyNames.map(assemblyName => {
142
+ var _a;
143
+ const assembly = assemblyManager.get(assemblyName);
144
+ return (_a = assembly === null || assembly === void 0 ? void 0 : assembly.displayName) !== null && _a !== void 0 ? _a : assemblyName;
145
+ });
146
+ },
139
147
  get isTopLevelView() {
140
148
  const session = (0, util_1.getSession)(self);
141
149
  return session.views.find(r => r.id === self.id);
@@ -166,7 +174,7 @@ function stateModelFactory(pluginManager) {
166
174
  .views(self => ({
167
175
  scaleBarDisplayPrefix() {
168
176
  return (0, mobx_state_tree_1.getParent)(self, 2).type === 'LinearSyntenyView'
169
- ? self.assemblyNames[0]
177
+ ? self.assemblyDisplayNames[0]
170
178
  : '';
171
179
  },
172
180
  MiniControlsComponent() {
package/dist/index.d.ts CHANGED
@@ -186,6 +186,7 @@ export default class LinearGenomeViewPlugin extends Plugin {
186
186
  readonly renderDelay: number;
187
187
  readonly TooltipComponent: import("@jbrowse/core/util").AnyReactComponentType;
188
188
  readonly selectedFeatureId: string | undefined;
189
+ copyInfoToClipboard(feature: import("@jbrowse/core/util").Feature): void;
189
190
  } & {
190
191
  readonly features: import("@jbrowse/core/util/compositeMap").default<string, import("@jbrowse/core/util").Feature>;
191
192
  readonly featureUnderMouse: import("@jbrowse/core/util").Feature | undefined;
@@ -2,7 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Suspense, useRef, useState } from 'react';
3
3
  import { getConf } from '@jbrowse/core/configuration';
4
4
  import { Menu } from '@jbrowse/core/ui';
5
- import { useTheme } from '@mui/material';
6
5
  import { observer } from 'mobx-react';
7
6
  import { makeStyles } from 'tss-react/mui';
8
7
  import LinearBlocks from './LinearBlocks';
@@ -17,7 +16,6 @@ const useStyles = makeStyles()({
17
16
  });
18
17
  const BaseLinearDisplay = observer(function (props) {
19
18
  const { classes } = useStyles();
20
- const theme = useTheme();
21
19
  const ref = useRef(null);
22
20
  const [clientRect, setClientRect] = useState();
23
21
  const [offsetMouseCoord, setOffsetMouseCoord] = useState([0, 0]);
@@ -26,6 +24,7 @@ const BaseLinearDisplay = observer(function (props) {
26
24
  const { model, children } = props;
27
25
  const { TooltipComponent, DisplayMessageComponent, height } = model;
28
26
  const items = model.contextMenuItems();
27
+ const open = Boolean(contextCoord) && items.length > 0;
29
28
  return (_jsxs("div", { ref: ref, "data-testid": `display-${getConf(model, 'displayId')}`, className: classes.display, onContextMenu: event => {
30
29
  event.preventDefault();
31
30
  if (contextCoord) {
@@ -43,7 +42,7 @@ const BaseLinearDisplay = observer(function (props) {
43
42
  setOffsetMouseCoord([event.clientX - left, event.clientY - top]);
44
43
  setClientMouseCoord([event.clientX, event.clientY]);
45
44
  setClientRect(rect);
46
- }, children: [DisplayMessageComponent ? (_jsx(DisplayMessageComponent, { model: model })) : (_jsx(LinearBlocks, { ...props })), children, _jsx(Suspense, { fallback: null, children: _jsx(TooltipComponent, { model: model, height: height, offsetMouseCoord: offsetMouseCoord, clientMouseCoord: clientMouseCoord, clientRect: clientRect, mouseCoord: offsetMouseCoord }) }), _jsx(Menu, { open: Boolean(contextCoord) && items.length > 0, onMenuItemClick: (_, callback) => {
45
+ }, children: [DisplayMessageComponent ? (_jsx(DisplayMessageComponent, { model: model })) : (_jsx(LinearBlocks, { ...props })), children, _jsx(Suspense, { fallback: null, children: _jsx(TooltipComponent, { model: model, height: height, offsetMouseCoord: offsetMouseCoord, clientMouseCoord: clientMouseCoord, clientRect: clientRect, mouseCoord: offsetMouseCoord }) }), open ? (_jsx(Menu, { open: true, onMenuItemClick: (_, callback) => {
47
46
  callback();
48
47
  setContextCoord(undefined);
49
48
  }, onClose: () => {
@@ -58,9 +57,7 @@ const BaseLinearDisplay = observer(function (props) {
58
57
  },
59
58
  }, anchorReference: "anchorPosition", anchorPosition: contextCoord
60
59
  ? { top: contextCoord[1], left: contextCoord[0] }
61
- : undefined, style: {
62
- zIndex: theme.zIndex.tooltip,
63
- }, menuItems: items })] }));
60
+ : undefined, menuItems: items })) : null] }));
64
61
  });
65
62
  export default BaseLinearDisplay;
66
63
  export { default as Tooltip } from './Tooltip';
@@ -197,6 +197,7 @@ export declare const BaseLinearDisplay: import("mobx-state-tree").IModelType<{
197
197
  readonly renderDelay: number;
198
198
  readonly TooltipComponent: AnyReactComponentType;
199
199
  readonly selectedFeatureId: string | undefined;
200
+ copyInfoToClipboard(feature: Feature): void;
200
201
  } & {
201
202
  readonly features: CompositeMap<string, Feature>;
202
203
  readonly featureUnderMouse: Feature | undefined;
@@ -5,7 +5,9 @@ import { getContainingTrack, getContainingView, getSession, isFeature, isSelecti
5
5
  import CompositeMap from '@jbrowse/core/util/compositeMap';
6
6
  import { getParentRenderProps, getRpcSessionId, } from '@jbrowse/core/util/tracks';
7
7
  import CenterFocusStrongIcon from '@mui/icons-material/CenterFocusStrong';
8
+ import ContentCopyIcon from '@mui/icons-material/ContentCopy';
8
9
  import MenuOpenIcon from '@mui/icons-material/MenuOpen';
10
+ import copy from 'copy-to-clipboard';
9
11
  import { autorun } from 'mobx';
10
12
  import { addDisposer, isAlive, types } from 'mobx-state-tree';
11
13
  import FeatureDensityMixin from './FeatureDensityMixin';
@@ -54,6 +56,12 @@ function stateModelFactory() {
54
56
  }
55
57
  return undefined;
56
58
  },
59
+ copyInfoToClipboard(feature) {
60
+ const { uniqueId, ...rest } = feature.toJSON();
61
+ const session = getSession(self);
62
+ copy(JSON.stringify(rest, null, 4));
63
+ session.notify('Copied to clipboard', 'success');
64
+ },
57
65
  }))
58
66
  .views(self => ({
59
67
  get features() {
@@ -166,30 +174,32 @@ function stateModelFactory() {
166
174
  return [];
167
175
  },
168
176
  contextMenuItems() {
169
- return [
170
- ...(self.contextMenuFeature
171
- ? [
172
- {
173
- label: 'Open feature details',
174
- icon: MenuOpenIcon,
175
- onClick: () => {
176
- if (self.contextMenuFeature) {
177
- self.selectFeature(self.contextMenuFeature);
178
- }
179
- },
177
+ const feat = self.contextMenuFeature;
178
+ return feat
179
+ ? [
180
+ {
181
+ label: 'Open feature details',
182
+ icon: MenuOpenIcon,
183
+ onClick: () => {
184
+ self.selectFeature(feat);
185
+ },
186
+ },
187
+ {
188
+ label: 'Zoom to feature',
189
+ icon: CenterFocusStrongIcon,
190
+ onClick: () => {
191
+ self.navToFeature(feat);
180
192
  },
181
- {
182
- label: 'Zoom to feature',
183
- icon: CenterFocusStrongIcon,
184
- onClick: () => {
185
- if (self.contextMenuFeature) {
186
- self.navToFeature(self.contextMenuFeature);
187
- }
188
- },
193
+ },
194
+ {
195
+ label: 'Copy info to clipboard',
196
+ icon: ContentCopyIcon,
197
+ onClick: () => {
198
+ self.copyInfoToClipboard(feat);
189
199
  },
190
- ]
191
- : []),
192
- ];
200
+ },
201
+ ]
202
+ : [];
193
203
  },
194
204
  renderProps() {
195
205
  return {
@@ -181,6 +181,7 @@ export declare function stateModelFactory(configSchema: AnyConfigurationSchemaTy
181
181
  readonly renderDelay: number;
182
182
  readonly TooltipComponent: import("@jbrowse/core/util").AnyReactComponentType;
183
183
  readonly selectedFeatureId: string | undefined;
184
+ copyInfoToClipboard(feature: import("@jbrowse/core/util").Feature): void;
184
185
  } & {
185
186
  readonly features: import("@jbrowse/core/util/compositeMap").default<string, import("@jbrowse/core/util").Feature>;
186
187
  readonly featureUnderMouse: import("@jbrowse/core/util").Feature | undefined;
@@ -189,6 +189,7 @@ declare function stateModelFactory(configSchema: AnyConfigurationSchemaType): im
189
189
  readonly renderDelay: number;
190
190
  readonly TooltipComponent: import("@jbrowse/core/util").AnyReactComponentType;
191
191
  readonly selectedFeatureId: string | undefined;
192
+ copyInfoToClipboard(feature: import("@jbrowse/core/util").Feature): void;
192
193
  } & {
193
194
  readonly features: import("@jbrowse/core/util/compositeMap").default<string, import("@jbrowse/core/util").Feature>;
194
195
  readonly featureUnderMouse: import("@jbrowse/core/util").Feature | undefined;
@@ -1,7 +1,6 @@
1
1
  import type { LinearGenomeViewModel } from '..';
2
- type LGV = LinearGenomeViewModel;
3
2
  declare const GetSequenceDialog: ({ model, handleClose, }: {
4
- model: LGV;
3
+ model: LinearGenomeViewModel;
5
4
  handleClose: () => void;
6
5
  }) => import("react/jsx-runtime").JSX.Element;
7
6
  export default GetSequenceDialog;
@@ -1,16 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
- import { getConf } from '@jbrowse/core/configuration';
4
- import { Dialog } from '@jbrowse/core/ui';
5
- import { ContentCopy as ContentCopyIcon } from '@jbrowse/core/ui/Icons';
6
- import { complement, getSession, reverse } from '@jbrowse/core/util';
3
+ import { Dialog, ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui';
4
+ import { complement, reverse } from '@jbrowse/core/util';
7
5
  import { formatSeqFasta } from '@jbrowse/core/util/formatFastaStrings';
6
+ import ContentCopyIcon from '@mui/icons-material/ContentCopy';
8
7
  import GetAppIcon from '@mui/icons-material/GetApp';
9
- import { Button, Checkbox, CircularProgress, Container, DialogActions, DialogContent, FormControlLabel, FormGroup, TextField, Typography, } from '@mui/material';
8
+ import { Button, Checkbox, DialogActions, DialogContent, FormControlLabel, FormGroup, TextField, Typography, } from '@mui/material';
10
9
  import copy from 'copy-to-clipboard';
11
10
  import { saveAs } from 'file-saver';
12
11
  import { observer } from 'mobx-react';
13
12
  import { makeStyles } from 'tss-react/mui';
13
+ import { fetchSequence } from './fetchSequence';
14
14
  const useStyles = makeStyles()({
15
15
  dialogContent: {
16
16
  width: '80em',
@@ -22,30 +22,6 @@ const useStyles = makeStyles()({
22
22
  marginLeft: 10,
23
23
  },
24
24
  });
25
- async function fetchSequence(model, regions) {
26
- const session = getSession(model);
27
- const { leftOffset, rightOffset } = model;
28
- if (!leftOffset || !rightOffset) {
29
- throw new Error('no offsets on model to use for range');
30
- }
31
- const assemblyNames = new Set(regions.map(r => r.assemblyName));
32
- if (assemblyNames.size > 1) {
33
- throw new Error('not able to fetch sequences from multiple assemblies currently');
34
- }
35
- const { rpcManager, assemblyManager } = session;
36
- const assemblyName = leftOffset.assemblyName || rightOffset.assemblyName || '';
37
- const assembly = assemblyManager.get(assemblyName);
38
- if (!assembly) {
39
- throw new Error(`assembly ${assemblyName} not found`);
40
- }
41
- const adapterConfig = getConf(assembly, ['sequence', 'adapter']);
42
- const sessionId = 'getSequence';
43
- return rpcManager.call(sessionId, 'CoreGetFeatures', {
44
- adapterConfig,
45
- regions,
46
- sessionId,
47
- });
48
- }
49
25
  const GetSequenceDialog = observer(function ({ model, handleClose, }) {
50
26
  const { classes } = useStyles();
51
27
  const [error, setError] = useState();
@@ -54,7 +30,7 @@ const GetSequenceDialog = observer(function ({ model, handleClose, }) {
54
30
  const [copied, setCopied] = useState(false);
55
31
  const [comp, setComplement] = useState(false);
56
32
  const { leftOffset, rightOffset } = model;
57
- const loading = Boolean(sequenceChunks === undefined);
33
+ const loading = sequenceChunks === undefined;
58
34
  useEffect(() => {
59
35
  const controller = new AbortController();
60
36
  (async () => {
@@ -63,8 +39,7 @@ const GetSequenceDialog = observer(function ({ model, handleClose, }) {
63
39
  if (selection.length === 0) {
64
40
  throw new Error('Selected region is out of bounds');
65
41
  }
66
- const chunks = await fetchSequence(model, selection);
67
- setSequenceChunks(chunks);
42
+ setSequenceChunks(await fetchSequence(model, selection));
68
43
  }
69
44
  catch (e) {
70
45
  console.error(e);
@@ -101,7 +76,7 @@ const GetSequenceDialog = observer(function ({ model, handleClose, }) {
101
76
  return (_jsxs(Dialog, { maxWidth: "xl", open: true, onClose: () => {
102
77
  handleClose();
103
78
  model.setOffsets();
104
- }, title: "Reference sequence", children: [_jsxs(DialogContent, { children: [error ? (_jsx(Typography, { color: "error", children: `${error}` })) : loading ? (_jsxs(Container, { children: ["Retrieving reference sequence...", _jsx(CircularProgress, { className: classes.ml, size: 20, disableShrink: true })] })) : null, _jsx(TextField, { "data-testid": "rubberband-sequence", variant: "outlined", multiline: true, minRows: 5, maxRows: 10, disabled: sequenceTooLarge, className: classes.dialogContent, fullWidth: true, value: sequenceTooLarge
79
+ }, title: "Reference sequence", children: [_jsxs(DialogContent, { children: [error ? (_jsx(ErrorMessage, { error: error })) : loading ? (_jsx(LoadingEllipses, { message: "Retrieving sequences" })) : null, _jsx(TextField, { "data-testid": "rubberband-sequence", variant: "outlined", multiline: true, minRows: 5, maxRows: 10, disabled: sequenceTooLarge, className: classes.dialogContent, fullWidth: true, value: sequenceTooLarge
105
80
  ? 'Reference sequence too large to display, use the download FASTA button'
106
81
  : sequence, slotProps: {
107
82
  input: {
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useEffect, useState } from 'react';
2
+ import { useEffect, useMemo, useState } from 'react';
3
3
  import BaseResult, { RefSequenceResult, } from '@jbrowse/core/TextSearch/BaseResults';
4
4
  import { getSession, measureText, useDebounce } from '@jbrowse/core/util';
5
5
  import { Autocomplete } from '@mui/material';
@@ -14,38 +14,48 @@ const RefNameAutocomplete = observer(function ({ model, onSelect, assemblyName,
14
14
  const [currentSearch, setCurrentSearch] = useState('');
15
15
  const [inputValue, setInputValue] = useState('');
16
16
  const [searchOptions, setSearchOptions] = useState();
17
- const debouncedSearch = useDebounce(currentSearch, 300);
17
+ const debouncedSearch = useDebounce(currentSearch, 50);
18
18
  const assembly = assemblyName ? assemblyManager.get(assemblyName) : undefined;
19
19
  const { coarseVisibleLocStrings, hasDisplayedRegions } = model;
20
20
  useEffect(() => {
21
- ;
21
+ const isCurrent = { cancelled: false };
22
22
  (async () => {
23
23
  try {
24
24
  if (debouncedSearch === '' || !assemblyName) {
25
25
  return;
26
26
  }
27
27
  setLoaded(false);
28
- setSearchOptions(getDeduplicatedResult(await fetchResults(debouncedSearch)));
28
+ const results = await fetchResults(debouncedSearch);
29
+ if (!isCurrent.cancelled) {
30
+ setSearchOptions(getDeduplicatedResult(results));
31
+ }
29
32
  }
30
33
  catch (e) {
31
34
  console.error(e);
32
- session.notifyError(`${e}`, e);
35
+ if (!isCurrent.cancelled) {
36
+ session.notifyError(`${e}`, e);
37
+ }
33
38
  }
34
39
  finally {
35
- setLoaded(true);
40
+ if (!isCurrent.cancelled) {
41
+ setLoaded(true);
42
+ }
36
43
  }
37
44
  })();
45
+ return () => {
46
+ isCurrent.cancelled = true;
47
+ };
38
48
  }, [assemblyName, fetchResults, debouncedSearch, session]);
39
49
  const inputBoxVal = coarseVisibleLocStrings || value || '';
40
50
  const regions = assembly === null || assembly === void 0 ? void 0 : assembly.regions;
41
- const regionOptions = (regions === null || regions === void 0 ? void 0 : regions.map(region => ({
51
+ const regionOptions = useMemo(() => (regions === null || regions === void 0 ? void 0 : regions.map(region => ({
42
52
  result: new RefSequenceResult({
43
53
  refName: region.refName,
44
54
  label: region.refName,
45
55
  displayString: region.refName,
46
56
  matchedAttribute: 'refName',
47
57
  }),
48
- }))) || [];
58
+ }))) || [], [regions]);
49
59
  return (_jsx(Autocomplete, { "data-testid": "autocomplete", disableListWrap: true, disableClearable: true, disabled: !assemblyName, freeSolo: true, includeInputInList: true, selectOnFocus: true, style: {
50
60
  ...style,
51
61
  width: Math.min(Math.max(measureText(inputBoxVal, 14) + 100, minWidth), maxWidth),
@@ -1,5 +1,5 @@
1
1
  export default function RubberbandTooltip({ anchorEl, side, text, }: {
2
2
  anchorEl: HTMLSpanElement;
3
3
  side: string;
4
- text: string;
4
+ text: React.ReactNode;
5
5
  }): import("react/jsx-runtime").JSX.Element;
@@ -1,25 +1,17 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Popover, Typography } from '@mui/material';
3
- import { makeStyles } from 'tss-react/mui';
4
- const useStyles = makeStyles()(theme => {
5
- return {
6
- popover: {
7
- mouseEvents: 'none',
8
- cursor: 'crosshair',
9
- },
10
- paper: {
11
- paddingLeft: theme.spacing(1),
12
- paddingRight: theme.spacing(1),
13
- },
14
- };
15
- });
2
+ import { Tooltip } from '@mui/material';
16
3
  export default function RubberbandTooltip({ anchorEl, side, text, }) {
17
- const { classes } = useStyles();
18
- return (_jsx(Popover, { className: classes.popover, classes: { paper: classes.paper }, open: true, anchorEl: anchorEl, anchorOrigin: {
19
- vertical: 'top',
20
- horizontal: side === 'left' ? 'left' : 'right',
21
- }, transformOrigin: {
22
- vertical: 'bottom',
23
- horizontal: side === 'left' ? 'right' : 'left',
24
- }, keepMounted: true, disableRestoreFocus: true, children: _jsx(Typography, { children: text }) }));
4
+ return (_jsx(Tooltip, { title: text, open: true, placement: side === 'left' ? 'left-start' : 'right-start', slotProps: {
5
+ popper: {
6
+ anchorEl,
7
+ modifiers: [
8
+ {
9
+ name: 'offset',
10
+ options: {
11
+ offset: [-30, -10],
12
+ },
13
+ },
14
+ ],
15
+ },
16
+ }, children: _jsx("span", {}) }));
25
17
  }
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { getEnv, getSession } from '@jbrowse/core/util';
2
+ import { assembleLocString, getEnv, getSession, parseLocString, } from '@jbrowse/core/util';
3
3
  import { Button, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, } from '@mui/material';
4
4
  import { getRoot, resolveIdentifier } from 'mobx-state-tree';
5
5
  export default function SearchResultsTable({ searchResults, assemblyName: optAssemblyName, model, handleClose, }) {
@@ -40,18 +40,34 @@ export default function SearchResultsTable({ searchResults, assemblyName: optAss
40
40
  session.notify(`${e}`, 'warning');
41
41
  }
42
42
  }
43
- return (_jsx(TableContainer, { component: Paper, children: _jsxs(Table, { children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { children: "Name" }), _jsx(TableCell, { align: "right", children: "Location" }), _jsx(TableCell, { align: "right", children: "Track" }), _jsx(TableCell, { align: "right" })] }) }), _jsx(TableBody, { children: searchResults.map(result => (_jsxs(TableRow, { children: [_jsx(TableCell, { component: "th", scope: "row", children: result.getLabel() }), _jsx(TableCell, { align: "right", children: result.getLocation() }), _jsx(TableCell, { align: "right", children: getTrackName(result.getTrackId()) || 'N/A' }), _jsx(TableCell, { align: "right", children: _jsx(Button, { onClick: async () => {
44
- try {
45
- await handleClick(result.getLocation() || result.getLabel());
46
- const resultTrackId = result.getTrackId();
47
- if (resultTrackId) {
48
- model.showTrack(resultTrackId);
43
+ return (_jsx(TableContainer, { component: Paper, children: _jsxs(Table, { children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { children: "Name" }), _jsx(TableCell, { align: "right", children: "Location" }), _jsx(TableCell, { align: "right", children: "Track" }), _jsx(TableCell, { align: "right" })] }) }), _jsx(TableBody, { children: searchResults.map(result => {
44
+ const locString = result.getLocation();
45
+ let loc;
46
+ try {
47
+ loc = locString
48
+ ? parseLocString(locString, refName => assembly.isValidRefName(refName))
49
+ : undefined;
50
+ }
51
+ catch (e) { }
52
+ return (_jsxs(TableRow, { children: [_jsx(TableCell, { component: "th", scope: "row", children: result.getLabel() }), _jsx(TableCell, { align: "right", children: loc
53
+ ? assembleLocString({
54
+ ...loc,
55
+ refName: assembly.getCanonicalRefName(loc.refName) ||
56
+ loc.refName,
57
+ })
58
+ : locString }), _jsx(TableCell, { align: "right", children: getTrackName(result.getTrackId()) || 'N/A' }), _jsx(TableCell, { align: "right", children: _jsx(Button, { onClick: async () => {
59
+ try {
60
+ await handleClick(result.getLocation() || result.getLabel());
61
+ const resultTrackId = result.getTrackId();
62
+ if (resultTrackId) {
63
+ model.showTrack(resultTrackId);
64
+ }
49
65
  }
50
- }
51
- catch (e) {
52
- console.error(e);
53
- session.notifyError(`${e}`, e);
54
- }
55
- handleClose();
56
- }, color: "primary", variant: "contained", children: "Go" }) })] }, result.getId()))) })] }) }));
66
+ catch (e) {
67
+ console.error(e);
68
+ session.notifyError(`${e}`, e);
69
+ }
70
+ handleClose();
71
+ }, color: "primary", variant: "contained", children: "Go" }) })] }, result.getId()));
72
+ }) })] }) }));
57
73
  }
@@ -0,0 +1,4 @@
1
+ import type { LinearGenomeViewModel } from '../model';
2
+ import type { Feature } from '@jbrowse/core/util';
3
+ import type { Region } from '@jbrowse/core/util/types';
4
+ export declare function fetchSequence(model: LinearGenomeViewModel, regions: Region[]): Promise<Feature[]>;
@@ -0,0 +1,26 @@
1
+ import { getConf } from '@jbrowse/core/configuration';
2
+ import { getSession } from '@jbrowse/core/util';
3
+ export async function fetchSequence(model, regions) {
4
+ const session = getSession(model);
5
+ const { leftOffset, rightOffset } = model;
6
+ if (!leftOffset || !rightOffset) {
7
+ throw new Error('no offsets on model to use for range');
8
+ }
9
+ const assemblyNames = new Set(regions.map(r => r.assemblyName));
10
+ if (assemblyNames.size > 1) {
11
+ throw new Error('not able to fetch sequences from multiple assemblies currently');
12
+ }
13
+ const { rpcManager, assemblyManager } = session;
14
+ const assemblyName = leftOffset.assemblyName || rightOffset.assemblyName || '';
15
+ const assembly = assemblyManager.get(assemblyName);
16
+ if (!assembly) {
17
+ throw new Error(`assembly ${assemblyName} not found`);
18
+ }
19
+ const adapterConfig = getConf(assembly, ['sequence', 'adapter']);
20
+ const sessionId = 'getSequence';
21
+ return rpcManager.call(sessionId, 'CoreGetFeatures', {
22
+ adapterConfig,
23
+ regions,
24
+ sessionId,
25
+ });
26
+ }
@@ -58,6 +58,7 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
58
58
  readonly width: number;
59
59
  readonly interRegionPaddingWidth: number;
60
60
  readonly assemblyNames: string[];
61
+ readonly assemblyDisplayNames: string[];
61
62
  readonly isTopLevelView: import("@jbrowse/core/util").AbstractViewModel | undefined;
62
63
  readonly stickyViewHeaders: boolean | undefined;
63
64
  readonly rubberbandTop: number;
@@ -96,6 +96,14 @@ export function stateModelFactory(pluginManager) {
96
96
  ...new Set(self.displayedRegions.map(region => region.assemblyName)),
97
97
  ];
98
98
  },
99
+ get assemblyDisplayNames() {
100
+ const { assemblyManager } = getSession(self);
101
+ return this.assemblyNames.map(assemblyName => {
102
+ var _a;
103
+ const assembly = assemblyManager.get(assemblyName);
104
+ return (_a = assembly === null || assembly === void 0 ? void 0 : assembly.displayName) !== null && _a !== void 0 ? _a : assemblyName;
105
+ });
106
+ },
99
107
  get isTopLevelView() {
100
108
  const session = getSession(self);
101
109
  return session.views.find(r => r.id === self.id);
@@ -126,7 +134,7 @@ export function stateModelFactory(pluginManager) {
126
134
  .views(self => ({
127
135
  scaleBarDisplayPrefix() {
128
136
  return getParent(self, 2).type === 'LinearSyntenyView'
129
- ? self.assemblyNames[0]
137
+ ? self.assemblyDisplayNames[0]
130
138
  : '';
131
139
  },
132
140
  MiniControlsComponent() {
package/esm/index.d.ts CHANGED
@@ -186,6 +186,7 @@ export default class LinearGenomeViewPlugin extends Plugin {
186
186
  readonly renderDelay: number;
187
187
  readonly TooltipComponent: import("@jbrowse/core/util").AnyReactComponentType;
188
188
  readonly selectedFeatureId: string | undefined;
189
+ copyInfoToClipboard(feature: import("@jbrowse/core/util").Feature): void;
189
190
  } & {
190
191
  readonly features: import("@jbrowse/core/util/compositeMap").default<string, import("@jbrowse/core/util").Feature>;
191
192
  readonly featureUnderMouse: import("@jbrowse/core/util").Feature | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/plugin-linear-genome-view",
3
- "version": "3.5.0",
3
+ "version": "3.6.0",
4
4
  "description": "JBrowse 2 linear genome view",
5
5
  "keywords": [
6
6
  "jbrowse",
@@ -38,13 +38,12 @@
38
38
  "useSrc": "node ../../scripts/useSrc.js"
39
39
  },
40
40
  "dependencies": {
41
- "@jbrowse/core": "^3.5.0",
41
+ "@jbrowse/core": "^3.6.0",
42
42
  "@mui/icons-material": "^7.0.0",
43
43
  "@mui/material": "^7.0.0",
44
44
  "@types/file-saver": "^2.0.1",
45
45
  "copy-to-clipboard": "^3.3.1",
46
46
  "file-saver": "^2.0.0",
47
- "material-ui-popup-state": "^5.0.0",
48
47
  "mobx": "^6.0.0",
49
48
  "mobx-react": "^9.0.0",
50
49
  "mobx-state-tree": "^5.0.0",
@@ -58,5 +57,5 @@
58
57
  "access": "public"
59
58
  },
60
59
  "module": "esm/index.js",
61
- "gitHead": "8a8aa0aab2229dece106a5715a767e649e2fe92b"
60
+ "gitHead": "133a68815ab348d156c18d83cffc997356c3cfbb"
62
61
  }