jbrowse-plugin-msaview 3.1.0 → 3.2.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.
- package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +1 -1
- package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +1 -1
- package/dist/LaunchMsaView/components/BlastQuery/blastLaunchView.js +6 -4
- package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +1 -1
- package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js +4 -2
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +1 -1
- package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.js +4 -2
- package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +1 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.js +4 -2
- package/dist/LaunchMsaView/components/SubmitCancelActions.d.ts +4 -1
- package/dist/LaunchMsaView/components/SubmitCancelActions.js +34 -10
- package/dist/LaunchMsaView/components/SubmitCancelActions.test.d.ts +1 -0
- package/dist/LaunchMsaView/components/SubmitCancelActions.test.js +55 -0
- package/dist/LaunchMsaViewExtensionPoint/index.js +2 -2
- package/dist/index.js +4 -1
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +28 -28
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/launchMsaView.d.ts +19 -0
- package/dist/utils/launchMsaView.js +13 -0
- package/dist/utils/workspaces.d.ts +34 -0
- package/dist/utils/workspaces.js +100 -0
- package/dist/utils/workspaces.test.d.ts +1 -0
- package/dist/utils/workspaces.test.js +100 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -1
- package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +1 -0
- package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +1 -0
- package/src/LaunchMsaView/components/BlastQuery/blastLaunchView.ts +7 -4
- package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +1 -0
- package/src/LaunchMsaView/components/ManualMSALoader/launchView.ts +5 -2
- package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +1 -1
- package/src/LaunchMsaView/components/OrthologQuery/orthologLaunchView.ts +5 -2
- package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +1 -0
- package/src/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.ts +5 -2
- package/src/LaunchMsaView/components/SubmitCancelActions.test.tsx +95 -0
- package/src/LaunchMsaView/components/SubmitCancelActions.tsx +80 -22
- package/src/LaunchMsaViewExtensionPoint/index.ts +21 -2
- package/src/index.ts +4 -1
- package/src/utils/launchMsaView.ts +30 -0
- package/src/utils/workspaces.test.ts +132 -0
- package/src/utils/workspaces.ts +146 -0
- package/src/version.ts +1 -1
|
@@ -50,7 +50,7 @@ const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, ch
|
|
|
50
50
|
React.createElement(Typography, null, "Previous BLAST Results")),
|
|
51
51
|
React.createElement(AccordionDetails, null,
|
|
52
52
|
React.createElement(CachedBlastResults, { model: model, handleClose: handleClose, feature: feature })))) : null),
|
|
53
|
-
React.createElement(SubmitCancelActions, { submitDisabled: !proteinSequence, onSubmit: () => {
|
|
53
|
+
React.createElement(SubmitCancelActions, { model: model, submitDisabled: !proteinSequence, onSubmit: () => {
|
|
54
54
|
try {
|
|
55
55
|
if (selectedTranscript) {
|
|
56
56
|
setLaunchViewError(undefined);
|
|
@@ -79,7 +79,7 @@ const BlastManualPanel = observer(function ({ handleClose, feature, model, child
|
|
|
79
79
|
setTreeText(event.target.value);
|
|
80
80
|
} }),
|
|
81
81
|
React.createElement(QueryRowSelector, { ...queryRow })))),
|
|
82
|
-
React.createElement(SubmitCancelActions, { submitDisabled: !selectedTranscript || !msaText.trim(), onSubmit: () => {
|
|
82
|
+
React.createElement(SubmitCancelActions, { model: model, submitDisabled: !selectedTranscript || !msaText.trim(), onSubmit: () => {
|
|
83
83
|
try {
|
|
84
84
|
if (selectedTranscript) {
|
|
85
85
|
setLaunchViewError(undefined);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { getSession } from '@jbrowse/core/util';
|
|
2
|
+
import { launchMsaView } from '../../../utils/launchMsaView';
|
|
3
|
+
import { readLaunchPlacement } from '../../../utils/workspaces';
|
|
2
4
|
export function blastLaunchView({ newViewTitle, view, feature, blastParams, }) {
|
|
3
|
-
getSession(view)
|
|
4
|
-
|
|
5
|
+
launchMsaView(getSession(view), {
|
|
6
|
+
placement: readLaunchPlacement(),
|
|
5
7
|
displayName: newViewTitle,
|
|
6
8
|
connectedViewId: view.id,
|
|
7
9
|
connectedFeature: feature.toJSON(),
|
|
@@ -12,8 +14,8 @@ export function blastLaunchView({ newViewTitle, view, feature, blastParams, }) {
|
|
|
12
14
|
});
|
|
13
15
|
}
|
|
14
16
|
export function blastLaunchViewFromCache({ newViewTitle, view, cached, connectedFeature, }) {
|
|
15
|
-
getSession(view)
|
|
16
|
-
|
|
17
|
+
launchMsaView(getSession(view), {
|
|
18
|
+
placement: readLaunchPlacement(),
|
|
17
19
|
displayName: newViewTitle,
|
|
18
20
|
connectedViewId: view.id,
|
|
19
21
|
connectedFeature,
|
|
@@ -58,7 +58,7 @@ const ManualMSALoader = observer(function PreLoadedMSA2({ model, feature, handle
|
|
|
58
58
|
} })))),
|
|
59
59
|
React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection }),
|
|
60
60
|
React.createElement(QueryRowSelector, { ...queryRow })),
|
|
61
|
-
React.createElement(SubmitCancelActions, { submitDisabled: !selectedTranscript ||
|
|
61
|
+
React.createElement(SubmitCancelActions, { model: model, submitDisabled: !selectedTranscript ||
|
|
62
62
|
(inputMethod === 'file' && !msaFileLocation) ||
|
|
63
63
|
(inputMethod === 'text' && !msaText.trim()), onSubmit: () => {
|
|
64
64
|
try {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { getSession } from '@jbrowse/core/util';
|
|
2
|
+
import { launchMsaView } from '../../../utils/launchMsaView';
|
|
3
|
+
import { readLaunchPlacement } from '../../../utils/workspaces';
|
|
2
4
|
export function launchView({ newViewTitle, view, feature, msaFilehandle, treeFilehandle, querySeqName, data, }) {
|
|
3
|
-
getSession(view)
|
|
4
|
-
|
|
5
|
+
launchMsaView(getSession(view), {
|
|
6
|
+
placement: readLaunchPlacement(),
|
|
5
7
|
displayName: newViewTitle,
|
|
6
8
|
connectedViewId: view.id,
|
|
7
9
|
connectedFeature: feature.toJSON(),
|
|
@@ -40,7 +40,7 @@ const OrthologPanel = observer(function ({ handleClose, feature, model, }) {
|
|
|
40
40
|
setMaxSpecies(event.target.value);
|
|
41
41
|
}, error: !rowCountValid, helperText: "the closest N species NCBI has" })),
|
|
42
42
|
React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection })),
|
|
43
|
-
React.createElement(SubmitCancelActions, { submitDisabled: !proteinSequence || !rowCountValid, onSubmit: () => {
|
|
43
|
+
React.createElement(SubmitCancelActions, { model: model, submitDisabled: !proteinSequence || !rowCountValid, onSubmit: () => {
|
|
44
44
|
try {
|
|
45
45
|
if (selectedTranscript) {
|
|
46
46
|
setLaunchViewError(undefined);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { getSession } from '@jbrowse/core/util';
|
|
2
|
+
import { launchMsaView } from '../../../utils/launchMsaView';
|
|
3
|
+
import { readLaunchPlacement } from '../../../utils/workspaces';
|
|
2
4
|
export function orthologLaunchView({ newViewTitle, view, feature, orthologParams, }) {
|
|
3
|
-
getSession(view)
|
|
4
|
-
|
|
5
|
+
launchMsaView(getSession(view), {
|
|
6
|
+
placement: readLaunchPlacement(),
|
|
5
7
|
displayName: newViewTitle,
|
|
6
8
|
connectedViewId: view.id,
|
|
7
9
|
connectedFeature: feature.toJSON(),
|
|
@@ -58,7 +58,7 @@ const PreLoadedMSA = observer(function ({ model, feature, handleClose, }) {
|
|
|
58
58
|
msaList ? (React.createElement("div", null,
|
|
59
59
|
React.createElement(SanitizedHTML, { html: selectedDataset.description }),
|
|
60
60
|
React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection }))) : null)) : null),
|
|
61
|
-
React.createElement(SubmitCancelActions, { submitDisabled: !selectedTranscript || !msaData?.length, onSubmit: () => {
|
|
61
|
+
React.createElement(SubmitCancelActions, { model: model, submitDisabled: !selectedTranscript || !msaData?.length, onSubmit: () => {
|
|
62
62
|
try {
|
|
63
63
|
if (selectedTranscript && msaData) {
|
|
64
64
|
const querySeqName = `${selectedId}_${assemblyNames[0]}`;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { getSession } from '@jbrowse/core/util';
|
|
2
|
+
import { launchMsaView } from '../../../utils/launchMsaView';
|
|
3
|
+
import { readLaunchPlacement } from '../../../utils/workspaces';
|
|
2
4
|
export function preCalculatedLaunchView({ newViewTitle, view, feature, data, querySeqName, }) {
|
|
3
|
-
getSession(view)
|
|
4
|
-
|
|
5
|
+
launchMsaView(getSession(view), {
|
|
6
|
+
placement: readLaunchPlacement(),
|
|
5
7
|
displayName: newViewTitle,
|
|
6
8
|
treeAreaWidth: 200,
|
|
7
9
|
querySeqName,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
|
|
2
|
+
import type { AbstractTrackModel } from '@jbrowse/core/util';
|
|
3
|
+
export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, submitLabel, cancelLabel, model, }: {
|
|
3
4
|
onSubmit: () => void;
|
|
4
5
|
onCancel: () => void;
|
|
5
6
|
submitDisabled?: boolean;
|
|
6
7
|
submitLabel?: string;
|
|
7
8
|
cancelLabel?: string;
|
|
9
|
+
/** omitted by a panel that submits something other than a view launch */
|
|
10
|
+
model?: AbstractTrackModel;
|
|
8
11
|
}): React.JSX.Element;
|
|
@@ -1,11 +1,35 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { getSession } from '@jbrowse/core/util';
|
|
3
|
+
import { Button, Checkbox, DialogActions, FormControlLabel, } from '@mui/material';
|
|
4
|
+
import { readLaunchPlacement, sessionSupportsPlacement, writeLaunchPlacement, } from '../../utils/workspaces';
|
|
5
|
+
/**
|
|
6
|
+
* Where the launch puts the view, offered wherever a launch is submitted.
|
|
7
|
+
*
|
|
8
|
+
* A checkbox rather than a menu of the three placements: the choice a reader
|
|
9
|
+
* has at this point is "beside the genome view or under it", and `newTab` is a
|
|
10
|
+
* spec's to state, not a thing to pick before you have seen the alignment.
|
|
11
|
+
*
|
|
12
|
+
* Absent entirely on a host that cannot tile — an embedded session, or a
|
|
13
|
+
* release that places views its own way — because the box would do nothing
|
|
14
|
+
* there and every launch would quietly ignore it.
|
|
15
|
+
*/
|
|
16
|
+
function PlacementToggle({ model }) {
|
|
17
|
+
const session = getSession(model);
|
|
18
|
+
const [sideBySide, setSideBySide] = useState(() => readLaunchPlacement() === 'splitRight');
|
|
19
|
+
return sessionSupportsPlacement(session) ? (React.createElement(FormControlLabel, { label: "Open beside the genome view", control: React.createElement(Checkbox, { checked: sideBySide, onChange: event => {
|
|
20
|
+
const { checked } = event.target;
|
|
21
|
+
setSideBySide(checked);
|
|
22
|
+
writeLaunchPlacement(checked ? 'splitRight' : 'stack');
|
|
23
|
+
} }) })) : null;
|
|
24
|
+
}
|
|
25
|
+
export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, submitLabel = 'Submit', cancelLabel = 'Cancel', model, }) {
|
|
26
|
+
return (React.createElement(DialogActions, { sx: { flexWrap: 'wrap', rowGap: 1 } },
|
|
27
|
+
model ? React.createElement(PlacementToggle, { model: model }) : null,
|
|
28
|
+
React.createElement("div", { style: { display: 'flex', gap: 8, marginLeft: 'auto' } },
|
|
29
|
+
React.createElement(Button, { sx: { flexShrink: 0 }, color: "primary", variant: "contained", disabled: submitDisabled, onClick: () => {
|
|
30
|
+
onSubmit();
|
|
31
|
+
} }, submitLabel),
|
|
32
|
+
React.createElement(Button, { sx: { flexShrink: 0 }, color: "secondary", variant: "contained", onClick: () => {
|
|
33
|
+
onCancel();
|
|
34
|
+
} }, cancelLabel))));
|
|
11
35
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { cleanup, render, screen } from '@testing-library/react';
|
|
4
|
+
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
|
|
5
|
+
import SubmitCancelActions from './SubmitCancelActions';
|
|
6
|
+
import { LAUNCH_PLACEMENT_KEY } from '../../utils/workspaces';
|
|
7
|
+
// getSession walks the MST tree, and this component only wants the two actions
|
|
8
|
+
// off the far end of that walk
|
|
9
|
+
vi.mock('@jbrowse/core/util', () => ({
|
|
10
|
+
getSession: (model) => model.session,
|
|
11
|
+
}));
|
|
12
|
+
function trackModel(session) {
|
|
13
|
+
return { session };
|
|
14
|
+
}
|
|
15
|
+
const tiling = { setUseWorkspaces() { }, setPendingMove() { } };
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
localStorage.clear();
|
|
18
|
+
});
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
cleanup();
|
|
21
|
+
});
|
|
22
|
+
function toggle() {
|
|
23
|
+
return screen.queryByRole('checkbox');
|
|
24
|
+
}
|
|
25
|
+
test('a host that can tile offers the choice, checked by default', () => {
|
|
26
|
+
render(React.createElement(SubmitCancelActions, { model: trackModel(tiling), onSubmit: () => { }, onCancel: () => { } }));
|
|
27
|
+
expect(toggle()).toBeTruthy();
|
|
28
|
+
expect(toggle().checked).toBe(true);
|
|
29
|
+
expect(localStorage.getItem(LAUNCH_PLACEMENT_KEY)).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
// the box would do nothing on an embedded session, and a control that silently
|
|
32
|
+
// does nothing is worse than one that is not there
|
|
33
|
+
test('a host that cannot tile does not offer it', () => {
|
|
34
|
+
render(React.createElement(SubmitCancelActions, { model: trackModel({}), onSubmit: () => { }, onCancel: () => { } }));
|
|
35
|
+
expect(toggle()).toBeNull();
|
|
36
|
+
expect(screen.getByText('Submit')).toBeTruthy();
|
|
37
|
+
});
|
|
38
|
+
test('a panel that launches nothing passes no model, and gets no box', () => {
|
|
39
|
+
render(React.createElement(SubmitCancelActions, { onSubmit: () => { }, onCancel: () => { } }));
|
|
40
|
+
expect(toggle()).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
test('clicking it writes the placement the next launch will read', () => {
|
|
43
|
+
render(React.createElement(SubmitCancelActions, { model: trackModel(tiling), onSubmit: () => { }, onCancel: () => { } }));
|
|
44
|
+
toggle().click();
|
|
45
|
+
expect(localStorage.getItem(LAUNCH_PLACEMENT_KEY)).toBe('stack');
|
|
46
|
+
expect(toggle().checked).toBe(false);
|
|
47
|
+
toggle().click();
|
|
48
|
+
expect(localStorage.getItem(LAUNCH_PLACEMENT_KEY)).toBe('splitRight');
|
|
49
|
+
expect(toggle().checked).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
test('a stored choice is what the box opens on', () => {
|
|
52
|
+
localStorage.setItem(LAUNCH_PLACEMENT_KEY, 'stack');
|
|
53
|
+
render(React.createElement(SubmitCancelActions, { model: trackModel(tiling), onSubmit: () => { }, onCancel: () => { } }));
|
|
54
|
+
expect(toggle().checked).toBe(false);
|
|
55
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { launchMsaView } from '../utils/launchMsaView';
|
|
1
2
|
export default function LaunchMsaViewExtensionPointF(pluginManager) {
|
|
2
3
|
pluginManager.addToExtensionPoint('LaunchView-MsaView', (args) => {
|
|
3
4
|
const { session, data, msaFileLocation, msaIndexedLocation, msaName, treeFileLocation, querySeqName, ...rest } = args;
|
|
@@ -14,8 +15,7 @@ export default function LaunchMsaViewExtensionPointF(pluginManager) {
|
|
|
14
15
|
// directly, and so is orthologParams (the model's own autorun picks it up).
|
|
15
16
|
// Only sources needing launch-time resolution go through `init`: msaUrl
|
|
16
17
|
// (AlphaFold sniff) and the name-indexed bgzip block (no native loader).
|
|
17
|
-
session
|
|
18
|
-
type: 'MsaView',
|
|
18
|
+
launchMsaView(session, {
|
|
19
19
|
...rest,
|
|
20
20
|
data,
|
|
21
21
|
...(treeFileLocation
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import BgzipFastaMsaAdapterF from './BgzipFastaMsaAdapter';
|
|
|
8
8
|
import LaunchMsaViewF from './LaunchMsaView';
|
|
9
9
|
import LaunchMsaViewExtensionPointF from './LaunchMsaViewExtensionPoint';
|
|
10
10
|
import MsaViewF from './MsaViewPanel';
|
|
11
|
+
import { launchMsaView } from './utils/launchMsaView';
|
|
11
12
|
import { version } from './version';
|
|
12
13
|
export default class MsaViewPlugin extends Plugin {
|
|
13
14
|
constructor() {
|
|
@@ -49,7 +50,9 @@ export default class MsaViewPlugin extends Plugin {
|
|
|
49
50
|
label: 'Multiple sequence alignment view',
|
|
50
51
|
icon: GridOn,
|
|
51
52
|
onClick: (session) => {
|
|
52
|
-
|
|
53
|
+
// stacked, by default: nothing was launched from, so there is no
|
|
54
|
+
// connected view for it to sit beside
|
|
55
|
+
launchMsaView(session, {});
|
|
53
56
|
},
|
|
54
57
|
});
|
|
55
58
|
}
|