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.
Files changed (43) hide show
  1. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +1 -1
  2. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +1 -1
  3. package/dist/LaunchMsaView/components/BlastQuery/blastLaunchView.js +6 -4
  4. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +1 -1
  5. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js +4 -2
  6. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +1 -1
  7. package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.js +4 -2
  8. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +1 -1
  9. package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.js +4 -2
  10. package/dist/LaunchMsaView/components/SubmitCancelActions.d.ts +4 -1
  11. package/dist/LaunchMsaView/components/SubmitCancelActions.js +34 -10
  12. package/dist/LaunchMsaView/components/SubmitCancelActions.test.d.ts +1 -0
  13. package/dist/LaunchMsaView/components/SubmitCancelActions.test.js +55 -0
  14. package/dist/LaunchMsaViewExtensionPoint/index.js +2 -2
  15. package/dist/index.js +4 -1
  16. package/dist/jbrowse-plugin-msaview.umd.production.min.js +28 -28
  17. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  18. package/dist/utils/launchMsaView.d.ts +19 -0
  19. package/dist/utils/launchMsaView.js +13 -0
  20. package/dist/utils/workspaces.d.ts +34 -0
  21. package/dist/utils/workspaces.js +100 -0
  22. package/dist/utils/workspaces.test.d.ts +1 -0
  23. package/dist/utils/workspaces.test.js +100 -0
  24. package/dist/version.d.ts +1 -1
  25. package/dist/version.js +1 -1
  26. package/package.json +4 -1
  27. package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +1 -0
  28. package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +1 -0
  29. package/src/LaunchMsaView/components/BlastQuery/blastLaunchView.ts +7 -4
  30. package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +1 -0
  31. package/src/LaunchMsaView/components/ManualMSALoader/launchView.ts +5 -2
  32. package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +1 -1
  33. package/src/LaunchMsaView/components/OrthologQuery/orthologLaunchView.ts +5 -2
  34. package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +1 -0
  35. package/src/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.ts +5 -2
  36. package/src/LaunchMsaView/components/SubmitCancelActions.test.tsx +95 -0
  37. package/src/LaunchMsaView/components/SubmitCancelActions.tsx +80 -22
  38. package/src/LaunchMsaViewExtensionPoint/index.ts +21 -2
  39. package/src/index.ts +4 -1
  40. package/src/utils/launchMsaView.ts +30 -0
  41. package/src/utils/workspaces.test.ts +132 -0
  42. package/src/utils/workspaces.ts +146 -0
  43. 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).addView('MsaView', {
4
- type: 'MsaView',
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).addView('MsaView', {
16
- type: 'MsaView',
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).addView('MsaView', {
4
- type: 'MsaView',
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).addView('MsaView', {
4
- type: 'MsaView',
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).addView('MsaView', {
4
- type: 'MsaView',
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
- export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, submitLabel, cancelLabel, }: {
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 { Button, DialogActions } from '@mui/material';
3
- export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, submitLabel = 'Submit', cancelLabel = 'Cancel', }) {
4
- return (React.createElement(DialogActions, null,
5
- React.createElement(Button, { color: "primary", variant: "contained", disabled: submitDisabled, onClick: () => {
6
- onSubmit();
7
- } }, submitLabel),
8
- React.createElement(Button, { color: "secondary", variant: "contained", onClick: () => {
9
- onCancel();
10
- } }, cancelLabel)));
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,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.addView('MsaView', {
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
- session.addView('MsaView', {});
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
  }