@jbrowse/plugin-linear-genome-view 3.5.0 → 3.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BaseLinearDisplay/components/BaseLinearDisplay.js +3 -6
- package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +1 -0
- package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.js +32 -22
- package/dist/LinearBareDisplay/model.d.ts +1 -0
- package/dist/LinearBasicDisplay/model.d.ts +1 -0
- package/dist/LinearGenomeView/components/GetSequenceDialog.d.ts +1 -2
- package/dist/LinearGenomeView/components/GetSequenceDialog.js +6 -31
- package/dist/LinearGenomeView/components/RefNameAutocomplete/index.js +17 -7
- package/dist/LinearGenomeView/components/SearchResultsTable.js +29 -13
- package/dist/LinearGenomeView/components/fetchSequence.d.ts +4 -0
- package/dist/LinearGenomeView/components/fetchSequence.js +29 -0
- package/dist/LinearGenomeView/model.d.ts +1 -0
- package/dist/LinearGenomeView/model.js +9 -1
- package/dist/index.d.ts +1 -0
- package/esm/BaseLinearDisplay/components/BaseLinearDisplay.js +3 -6
- package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +1 -0
- package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.js +32 -22
- package/esm/LinearBareDisplay/model.d.ts +1 -0
- package/esm/LinearBasicDisplay/model.d.ts +1 -0
- package/esm/LinearGenomeView/components/GetSequenceDialog.d.ts +1 -2
- package/esm/LinearGenomeView/components/GetSequenceDialog.js +8 -33
- package/esm/LinearGenomeView/components/RefNameAutocomplete/index.js +18 -8
- package/esm/LinearGenomeView/components/SearchResultsTable.js +30 -14
- package/esm/LinearGenomeView/components/fetchSequence.d.ts +4 -0
- package/esm/LinearGenomeView/components/fetchSequence.js +26 -0
- package/esm/LinearGenomeView/model.d.ts +1 -0
- package/esm/LinearGenomeView/model.js +9 -1
- package/esm/index.d.ts +1 -0
- 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:
|
|
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,
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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)(
|
|
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)(
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
73
|
+
if (!isCurrent.cancelled) {
|
|
74
|
+
session.notifyError(`${e}`, e);
|
|
75
|
+
}
|
|
71
76
|
}
|
|
72
77
|
finally {
|
|
73
|
-
|
|
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),
|
|
@@ -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 =>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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.
|
|
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:
|
|
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,
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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:
|
|
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 {
|
|
4
|
-
import {
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
35
|
+
if (!isCurrent.cancelled) {
|
|
36
|
+
session.notifyError(`${e}`, e);
|
|
37
|
+
}
|
|
33
38
|
}
|
|
34
39
|
finally {
|
|
35
|
-
|
|
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
|
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 =>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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.
|
|
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.
|
|
3
|
+
"version": "3.5.1",
|
|
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.
|
|
41
|
+
"@jbrowse/core": "^3.5.1",
|
|
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": "
|
|
60
|
+
"gitHead": "cb8859da9d838ad2594964777c5c54f385d98f5e"
|
|
62
61
|
}
|