@jbrowse/product-core 4.2.1 → 5.0.0-beta.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.
Files changed (133) hide show
  1. package/esm/RootModel/BaseRootModel.d.ts +629 -304
  2. package/esm/RootModel/BaseRootModel.js +138 -14
  3. package/esm/RootModel/HierarchicalConfig.d.ts +57 -21
  4. package/esm/RootModel/HierarchicalConfig.js +36 -0
  5. package/esm/RootModel/InternetAccounts.d.ts +18 -1
  6. package/esm/RootModel/InternetAccounts.js +120 -22
  7. package/esm/RootModel/PreferencesConfig.d.ts +53 -0
  8. package/esm/RootModel/PreferencesConfig.js +61 -0
  9. package/esm/RootModel/createConfigModel.d.ts +232 -0
  10. package/esm/RootModel/createConfigModel.js +56 -0
  11. package/esm/RootModel/index.d.ts +10 -5
  12. package/esm/RootModel/index.js +10 -5
  13. package/esm/RootModel/menuItems.d.ts +24 -0
  14. package/esm/RootModel/menuItems.js +143 -0
  15. package/esm/Session/BaseSession.d.ts +310 -11
  16. package/esm/Session/BaseSession.js +448 -14
  17. package/esm/Session/Connections.d.ts +114 -8
  18. package/esm/Session/Connections.js +195 -74
  19. package/esm/Session/DrawerWidgets.d.ts +115 -12
  20. package/esm/Session/DrawerWidgets.js +161 -42
  21. package/esm/Session/MultipleViews.d.ts +1266 -1128
  22. package/esm/Session/MultipleViews.js +357 -66
  23. package/esm/Session/Preferences.d.ts +1665 -0
  24. package/esm/Session/Preferences.js +54 -0
  25. package/esm/Session/ReferenceManagement.d.ts +28 -3
  26. package/esm/Session/ReferenceManagement.js +56 -41
  27. package/esm/Session/SessionTracks.d.ts +1275 -1138
  28. package/esm/Session/SessionTracks.js +532 -35
  29. package/esm/Session/Themes.d.ts +87 -2
  30. package/esm/Session/Themes.js +156 -23
  31. package/esm/Session/TrackMenu.d.ts +112 -0
  32. package/esm/Session/TrackMenu.js +200 -0
  33. package/esm/Session/TrackMenuSessionMixin.d.ts +27 -0
  34. package/esm/Session/TrackMenuSessionMixin.js +41 -0
  35. package/esm/Session/Tracks.d.ts +1169 -1117
  36. package/esm/Session/Tracks.js +160 -20
  37. package/esm/Session/assertSessionModel.d.ts +21 -0
  38. package/esm/Session/assertSessionModel.js +1 -0
  39. package/esm/Session/finalizeSession.d.ts +16 -0
  40. package/esm/Session/finalizeSession.js +29 -0
  41. package/esm/Session/index.d.ts +22 -10
  42. package/esm/Session/index.js +13 -10
  43. package/esm/Session/shareableSnapshot.d.ts +77 -0
  44. package/esm/Session/shareableSnapshot.js +196 -0
  45. package/esm/Session/temporaryAssemblyTracks.d.ts +23 -0
  46. package/esm/Session/temporaryAssemblyTracks.js +38 -0
  47. package/esm/assertExtends.d.ts +55 -0
  48. package/esm/assertExtends.js +1 -0
  49. package/esm/controllerTracks.d.ts +56 -0
  50. package/esm/controllerTracks.js +77 -0
  51. package/esm/destroyViewState.d.ts +21 -0
  52. package/esm/destroyViewState.js +29 -0
  53. package/esm/index.d.ts +31 -4
  54. package/esm/index.js +31 -4
  55. package/esm/loadPlugins.d.ts +33 -0
  56. package/esm/loadPlugins.js +38 -0
  57. package/esm/localFiles.d.ts +74 -0
  58. package/esm/localFiles.js +156 -0
  59. package/esm/observeSession.d.ts +56 -0
  60. package/esm/observeSession.js +91 -0
  61. package/esm/pluginInput.d.ts +32 -0
  62. package/esm/pluginInput.js +19 -0
  63. package/esm/pruneUnbuildableNodes.d.ts +109 -0
  64. package/esm/pruneUnbuildableNodes.js +568 -0
  65. package/esm/resolveAssemblies.d.ts +44 -0
  66. package/esm/resolveAssemblies.js +55 -0
  67. package/esm/rpcWorker.d.ts +26 -0
  68. package/esm/rpcWorker.js +96 -15
  69. package/esm/scheduleDetachedDestroy.d.ts +18 -0
  70. package/esm/scheduleDetachedDestroy.js +24 -0
  71. package/esm/sessionMigrations/index.d.ts +53 -0
  72. package/esm/sessionMigrations/index.js +432 -0
  73. package/esm/sessionUrl.d.ts +41 -0
  74. package/esm/sessionUrl.js +53 -0
  75. package/esm/sessionUtils.d.ts +69 -0
  76. package/esm/sessionUtils.js +613 -0
  77. package/esm/siblingCast.d.ts +14 -0
  78. package/esm/siblingCast.js +16 -0
  79. package/esm/snapshotUtils.d.ts +8 -0
  80. package/esm/snapshotUtils.js +12 -0
  81. package/esm/ui/AboutDialog.d.ts +3 -6
  82. package/esm/ui/AboutDialog.js +4 -4
  83. package/esm/ui/AboutDialogContents.d.ts +2 -6
  84. package/esm/ui/AboutDialogContents.js +34 -13
  85. package/esm/ui/AssemblyInfoPanel.d.ts +5 -0
  86. package/esm/ui/AssemblyInfoPanel.js +69 -0
  87. package/esm/ui/DisplayDefaultsSection.d.ts +36 -0
  88. package/esm/ui/DisplayDefaultsSection.js +65 -0
  89. package/esm/ui/Drawer.d.ts +21 -0
  90. package/esm/ui/Drawer.js +60 -0
  91. package/esm/ui/DrawerControls.d.ts +5 -0
  92. package/esm/ui/DrawerControls.js +28 -0
  93. package/esm/ui/DrawerHeader.d.ts +6 -0
  94. package/esm/ui/DrawerHeader.js +36 -0
  95. package/esm/ui/DrawerWidget.d.ts +5 -0
  96. package/esm/ui/DrawerWidget.js +12 -0
  97. package/esm/ui/DrawerWidgetSelector.d.ts +5 -0
  98. package/esm/ui/DrawerWidgetSelector.js +40 -0
  99. package/esm/ui/FileInfoPanel.d.ts +3 -6
  100. package/esm/ui/FileInfoPanel.js +35 -26
  101. package/esm/ui/HeaderButtons.d.ts +7 -1
  102. package/esm/ui/HeaderButtons.js +23 -18
  103. package/esm/ui/ModalWidget.d.ts +12 -0
  104. package/esm/ui/ModalWidget.js +33 -0
  105. package/esm/ui/ModalWidgetAppBar.d.ts +8 -0
  106. package/esm/ui/ModalWidgetAppBar.js +9 -0
  107. package/esm/ui/PreferencesDialog.d.ts +56 -0
  108. package/esm/ui/PreferencesDialog.js +145 -0
  109. package/esm/ui/PreferencesResetDialog.d.ts +8 -0
  110. package/esm/ui/PreferencesResetDialog.js +19 -0
  111. package/esm/ui/RefNameAliasesDialog.d.ts +9 -0
  112. package/esm/ui/RefNameAliasesDialog.js +65 -0
  113. package/esm/ui/RefNameInfoDialog.d.ts +3 -6
  114. package/esm/ui/RefNameInfoDialog.js +57 -63
  115. package/esm/ui/WidgetBody.d.ts +18 -0
  116. package/esm/ui/WidgetBody.js +22 -0
  117. package/esm/ui/WidgetHeading.d.ts +7 -0
  118. package/esm/ui/WidgetHeading.js +7 -0
  119. package/esm/ui/drawerLayout.d.ts +18 -0
  120. package/esm/ui/drawerLayout.js +22 -0
  121. package/esm/ui/index.d.ts +13 -1
  122. package/esm/ui/index.js +12 -1
  123. package/esm/ui/util.d.ts +37 -4
  124. package/esm/ui/util.js +35 -38
  125. package/esm/useEngineLifecycle.d.ts +13 -0
  126. package/esm/useEngineLifecycle.js +22 -0
  127. package/package.json +11 -15
  128. package/esm/RootModel/FormatAbout.d.ts +0 -12
  129. package/esm/RootModel/FormatAbout.js +0 -15
  130. package/esm/RootModel/FormatDetails.d.ts +0 -24
  131. package/esm/RootModel/FormatDetails.js +0 -27
  132. package/esm/Session/DialogQueue.d.ts +0 -17
  133. package/esm/Session/DialogQueue.js +0 -31
@@ -1,7 +1,4 @@
1
- import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
2
- import type { AbstractSessionModel } from '@jbrowse/core/util';
3
- export declare function AboutDialog({ config, session, handleClose, }: {
4
- config: AnyConfigurationModel | Record<string, unknown>;
5
- session: AbstractSessionModel;
1
+ import type { AboutPanelProps } from './util.ts';
2
+ export default function AboutDialog({ config, session, handleClose, }: AboutPanelProps & {
6
3
  handleClose: () => void;
7
- }): import("react/jsx-runtime").JSX.Element;
4
+ }): import("react").JSX.Element;
@@ -1,11 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { PluggableComponent } from '@jbrowse/core/ui';
2
3
  import Dialog from '@jbrowse/core/ui/Dialog';
3
4
  import { getEnv } from '@jbrowse/core/util';
4
5
  import { getTrackName } from '@jbrowse/core/util/tracks';
5
- import AboutContents from "./AboutDialogContents.js";
6
- export function AboutDialog({ config, session, handleClose, }) {
6
+ import AboutContents from './AboutDialogContents.js';
7
+ export default function AboutDialog({ config, session, handleClose, }) {
7
8
  const trackName = getTrackName(config, session);
8
9
  const { pluginManager } = getEnv(session);
9
- const AboutComponent = pluginManager.evaluateExtensionPoint('Core-replaceAbout', AboutContents, { session, config });
10
- return (_jsx(Dialog, { open: true, onClose: handleClose, title: trackName, maxWidth: "xl", children: _jsx(AboutComponent, { config: config, session: session }) }));
10
+ return (_jsx(Dialog, { open: true, onClose: handleClose, title: trackName, maxWidth: "xl", children: _jsx(PluggableComponent, { pluginManager: pluginManager, name: "Core-replaceAbout", component: AboutContents, props: { config, session } }) }));
11
11
  }
@@ -1,7 +1,3 @@
1
- import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
2
- import type { AbstractSessionModel } from '@jbrowse/core/util';
3
- declare const AboutDialogContents: ({ config, session, }: {
4
- config: AnyConfigurationModel | Record<string, unknown>;
5
- session: AbstractSessionModel;
6
- }) => import("react/jsx-runtime").JSX.Element;
1
+ import type { AboutPanelProps } from './util.ts';
2
+ declare const AboutDialogContents: ({ config, session, }: AboutPanelProps) => import("react").JSX.Element;
7
3
  export default AboutDialogContents;
@@ -2,35 +2,56 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
3
  import Attributes from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail/Attributes';
4
4
  import BaseCard from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail/BaseCard';
5
- import { getConf } from '@jbrowse/core/configuration';
5
+ import { getTrackConfigWithPromotables, hydrateTrackConfig, } from '@jbrowse/core/configuration';
6
+ import PluggableComponents from '@jbrowse/core/ui/PluggableComponents';
6
7
  import { getEnv } from '@jbrowse/core/util';
7
8
  import { makeStyles } from '@jbrowse/core/util/tss-react';
8
- import { getSnapshot, isStateTreeNode } from '@jbrowse/mobx-state-tree';
9
+ import { isStateTreeNode } from '@jbrowse/mobx-state-tree';
9
10
  import { observer } from 'mobx-react';
10
- import FileInfoPanel from "./FileInfoPanel.js";
11
- import HeaderButtons from "./HeaderButtons.js";
12
- import RefNameInfoDialog from "./RefNameInfoDialog.js";
13
- import { generateDisplayableConfig, readConf } from "./util.js";
11
+ import AssemblyInfoPanel from './AssemblyInfoPanel.js';
12
+ import FileInfoPanel from './FileInfoPanel.js';
13
+ import HeaderButtons from './HeaderButtons.js';
14
+ import RefNameInfoDialog from './RefNameInfoDialog.js';
15
+ import { getAboutDialogConfig } from './util.js';
14
16
  const useStyles = makeStyles()({
15
17
  content: {
16
18
  minWidth: 800,
17
19
  },
18
20
  });
21
+ const hideFields = ['displays', 'baseUri', 'refNames', 'formatAbout'];
19
22
  const AboutDialogContents = observer(function AboutDialogContents({ config, session, }) {
20
- const conf = isStateTreeNode(config) ? getSnapshot(config) : config;
21
23
  const { classes } = useStyles();
22
24
  const [showRefNames, setShowRefNames] = useState(false);
23
- const hideUris = getConf(session, ['formatAbout', 'hideUris']) ||
24
- readConf(config, ['formatAbout', 'hideUris']);
25
25
  const { pluginManager } = getEnv(session);
26
- const confPostExt = generateDisplayableConfig({
26
+ // "Copy config" output leaves the cascade for good (a user pastes it into a
27
+ // config.json), so promotable slots are resolved rather than left stripped —
28
+ // otherwise the copied config renders differently from the track it came from.
29
+ // `fromDisplayTypeDefaults` names what that folded in, so materializing a
30
+ // session-wide preference into a track config isn't silent.
31
+ //
32
+ // The two menus that open this dialog hand over different things: the in-view
33
+ // track label passes `track.configuration`, a live node, while the
34
+ // hierarchical selector passes a `session.tracks` entry, which is a
35
+ // `types.frozen` plain object until something references the track. Resolving
36
+ // only the first meant the same track copied a different config depending on
37
+ // which menu you came from — and the selector is the one you can reach
38
+ // without opening the track at all. Hydrating converges them; it returns
39
+ // undefined for a config no plugin can build, and that falls back to copying
40
+ // it as authored.
41
+ const live = isStateTreeNode(config)
42
+ ? config
43
+ : hydrateTrackConfig(pluginManager, config);
44
+ const { config: conf, fromDisplayTypeDefaults } = live
45
+ ? getTrackConfigWithPromotables(session, live)
46
+ : { config, fromDisplayTypeDefaults: [] };
47
+ const { config: shown, hideUris } = getAboutDialogConfig({
27
48
  config,
28
49
  session,
29
50
  pluginManager,
30
51
  });
31
- const ExtraPanel = pluginManager.evaluateExtensionPoint('Core-extraAboutPanel', null, { session, config });
32
- const hideFields = ['displays', 'baseUri', 'refNames', 'formatAbout'];
33
- return (_jsxs("div", { className: classes.content, children: [_jsxs(BaseCard, { title: "Configuration", children: [!hideUris ? (_jsx(HeaderButtons, { conf: conf, setShowRefNames: setShowRefNames })) : null, _jsx(Attributes, { attributes: confPostExt.config, omit: [...hideFields, 'metadata'], hideUris: hideUris })] }), confPostExt.config.metadata ? (_jsx(BaseCard, { title: "Metadata", children: _jsx(Attributes, { attributes: confPostExt.config.metadata, omit: hideFields, hideUris: hideUris }) })) : null, ExtraPanel ? (_jsx(BaseCard, { title: ExtraPanel.name, children: _jsx(ExtraPanel.Component, { config: config }) })) : null, _jsx(FileInfoPanel, { config: config, session: session }), showRefNames ? (_jsx(RefNameInfoDialog, { session: session, config: config, onClose: () => {
52
+ return (_jsxs("div", { className: classes.content, children: [_jsxs(BaseCard, { title: "Configuration", children: [_jsx(HeaderButtons, { conf: conf, hideUris: hideUris, fromDisplayTypeDefaults: fromDisplayTypeDefaults, setShowRefNames: setShowRefNames }), _jsx(Attributes, { attributes: shown, omit: [...hideFields, 'metadata'], hideUris: hideUris })] }), shown.metadata ? (_jsx(BaseCard, { title: "Metadata", children: _jsx(Attributes, { attributes: shown.metadata, hideUris: hideUris }) })) : null, _jsx(AssemblyInfoPanel, { config: config, session: session, hideUris: hideUris }), _jsx(PluggableComponents, { pluginManager: pluginManager,
53
+ /** #extensionPoint Core-extraAboutPanel | sync | Add extra panels to a track's About dialog */
54
+ name: "Core-extraAboutPanel", props: { session, config } }), hideUris ? null : _jsx(FileInfoPanel, { config: config, session: session }), showRefNames ? (_jsx(RefNameInfoDialog, { session: session, config: config, onClose: () => {
34
55
  setShowRefNames(false);
35
56
  } })) : null] }));
36
57
  });
@@ -0,0 +1,5 @@
1
+ import type { AboutPanelProps } from './util.ts';
2
+ declare const AssemblyInfoPanel: ({ config, session, hideUris, }: AboutPanelProps & {
3
+ hideUris?: boolean;
4
+ }) => import("react").JSX.Element | null;
5
+ export default AssemblyInfoPanel;
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import Attributes from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail/Attributes';
4
+ import BaseCard from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail/BaseCard';
5
+ import { readConfObject, readConfSlot } from '@jbrowse/core/configuration';
6
+ import { CopyToClipboardButton } from '@jbrowse/core/ui';
7
+ import { stripBaseUris } from '@jbrowse/core/util/addRelativeUris';
8
+ import { makeStyles } from '@jbrowse/core/util/tss-react';
9
+ import { getParent, hasParent, isStateTreeNode } from '@jbrowse/mobx-state-tree';
10
+ import { Button } from '@mui/material';
11
+ import { observer } from 'mobx-react';
12
+ import RefNameAliasesDialog from './RefNameAliasesDialog.js';
13
+ // `Attributes` omits these at every depth, which is what reaches the two that
14
+ // are nested:
15
+ // - `sequence` is the config of the very track this dialog is about, in the
16
+ // card above
17
+ // - `features` is an inline adapter's whole data payload. An assembly whose
18
+ // aliases or cytobands are written into the config rather than fetched from a
19
+ // file rendered them here as a spreadsheet of the raw rows, which buried the
20
+ // four lines anyone opened the card for — and it is the same data the alias
21
+ // listing shows resolved, one button over
22
+ // - `adapterId` is a generated hash, and `baseUri` is where the config was
23
+ // loaded from rather than anything about the assembly
24
+ //
25
+ // Nothing is hidden from "Copy assembly config", which copies the config whole.
26
+ const hideFields = ['sequence', 'features', 'adapterId', 'baseUri'];
27
+ const useStyles = makeStyles()(theme => ({
28
+ buttons: {
29
+ float: 'right',
30
+ display: 'flex',
31
+ gap: theme.spacing(1),
32
+ },
33
+ }));
34
+ /**
35
+ * The assembly config a `ReferenceSequenceTrack` config hangs off, and
36
+ * undefined for every other track.
37
+ *
38
+ * A reference sequence track declares no `assemblyNames` slot — the config node
39
+ * holding it *is* its assembly, the same walk `getConfAssemblyNames` makes.
40
+ * Compared by identity against that node's `sequence`, so the card is the
41
+ * assembly of the track the dialog is already showing.
42
+ *
43
+ * Both menus that open this dialog hand over a live node here: the hierarchical
44
+ * selector reads `assemblyManager.get(name).configuration.sequence`, and the
45
+ * in-view track label's `track.configuration` resolves to that same node
46
+ * through `getTrackById`. The frozen `session.tracks` entry the rest of the
47
+ * dialog has to cope with is never a reference sequence track.
48
+ */
49
+ function getAssemblyConf(config) {
50
+ const parent = isStateTreeNode(config) && hasParent(config)
51
+ ? getParent(config)
52
+ : undefined;
53
+ return parent?.sequence === config ? parent : undefined;
54
+ }
55
+ const AssemblyCard = observer(function AssemblyCard({ assemblyConf, session, hideUris, }) {
56
+ const { classes } = useStyles();
57
+ const [showAliases, setShowAliases] = useState(false);
58
+ const conf = readConfObject(assemblyConf);
59
+ return (_jsxs(BaseCard, { title: "Assembly", children: [_jsxs("span", { className: classes.buttons, children: [_jsx(Button, { variant: "contained", color: "secondary", onClick: () => {
60
+ setShowAliases(true);
61
+ }, children: "Show ref name aliases" }), hideUris ? null : (_jsx(CopyToClipboardButton, { variant: "contained", value: () => JSON.stringify(stripBaseUris(structuredClone(conf)), null, 2), children: "Copy assembly config" }))] }), _jsx(Attributes, { attributes: conf, omit: hideFields, hideUris: hideUris }), showAliases ? (_jsx(RefNameAliasesDialog, { assemblyName: readConfSlot(assemblyConf, 'name'), session: session, onClose: () => {
62
+ setShowAliases(false);
63
+ } })) : null] }));
64
+ });
65
+ const AssemblyInfoPanel = observer(function AssemblyInfoPanel({ config, session, hideUris, }) {
66
+ const assemblyConf = getAssemblyConf(config);
67
+ return assemblyConf ? (_jsx(AssemblyCard, { assemblyConf: assemblyConf, session: session, hideUris: hideUris })) : null;
68
+ });
69
+ export default AssemblyInfoPanel;
@@ -0,0 +1,36 @@
1
+ import type PluginManager from '@jbrowse/core/PluginManager';
2
+ export interface DisplayDefaultsSession {
3
+ getDisplayTypeDefaults: () => {
4
+ displayType: string;
5
+ slot: string;
6
+ value: unknown;
7
+ }[];
8
+ setDisplayTypeDefault: (displayType: string, slot: string, value: unknown) => void;
9
+ }
10
+ /**
11
+ * The inventory of session-wide display-type defaults: every value the user has
12
+ * pinned from a track menu, what it overrides, and a button to clear one.
13
+ *
14
+ * The pin is easy to set and, until this, hard to find again. A pinned default
15
+ * shows on the track-selector badge of any **open** track it moves — but one
16
+ * affecting nothing currently open, one promoted to a value equal to the slot's
17
+ * base, and one that no longer resolves at all (a stale value the cascade
18
+ * refuses, or a display type whose plugin is gone) appear nowhere. The only
19
+ * place that listed them was the "Reset preferences to defaults" confirmation,
20
+ * which is a destructive dialog to have to open in order to read something.
21
+ *
22
+ * The empty state is deliberate rather than rendering nothing: it is the one
23
+ * place the app says the capability exists to someone who has never used it.
24
+ *
25
+ * **The slot keeps its schema name**, while the display type gets its authored
26
+ * one. A slot has no menu label to borrow — which row a setting is offered on is
27
+ * a menu fact and declaring the slot promotable is a schema fact, and nothing
28
+ * joins them (that split is why `promotableSlotsWithoutPin` has to walk a built
29
+ * menu at all). A raw slot name is also what the config editor labels its fields
30
+ * with, so the two surfaces agree.
31
+ */
32
+ declare const DisplayDefaultsSection: ({ session, pluginManager, }: {
33
+ session: DisplayDefaultsSession;
34
+ pluginManager: PluginManager;
35
+ }) => import("react").JSX.Element;
36
+ export default DisplayDefaultsSection;
@@ -0,0 +1,65 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { getConfigurationSchemaDefinition, isSlotDefinitionEntry, } from '@jbrowse/core/configuration';
3
+ import { SettingsChangesTable } from '@jbrowse/core/ui';
4
+ import { Typography } from '@mui/material';
5
+ import { observer } from 'mobx-react';
6
+ // The registered display types, by name.
7
+ //
8
+ // **Built rather than reached through `getDisplayType`, which THROWS** on a name
9
+ // no plugin registers — an optional chain over it guards nothing. A default left
10
+ // behind by a plugin that has since been uninstalled is exactly a row this
11
+ // section has to render, so reaching for that getter would crash the whole
12
+ // Preferences dialog for the one user whose pins most need clearing.
13
+ function displayTypesByName(pluginManager) {
14
+ return new Map(pluginManager.getDisplayElements().map(d => [d.name, d]));
15
+ }
16
+ // What a track sitting at the bottom of the cascade shows for this slot — the
17
+ // value clearing the pin would restore. Read off the display type's schema
18
+ // rather than off the session, which holds only what was promoted. Undefined
19
+ // for a display type that is gone, whose row then reads "(default)" like any
20
+ // other unknown.
21
+ function promotedBaseOf(configSchema, slot) {
22
+ const def = configSchema
23
+ ? getConfigurationSchemaDefinition(configSchema)?.[slot]
24
+ : undefined;
25
+ return isSlotDefinitionEntry(def) ? def.promotedBase : undefined;
26
+ }
27
+ /**
28
+ * The inventory of session-wide display-type defaults: every value the user has
29
+ * pinned from a track menu, what it overrides, and a button to clear one.
30
+ *
31
+ * The pin is easy to set and, until this, hard to find again. A pinned default
32
+ * shows on the track-selector badge of any **open** track it moves — but one
33
+ * affecting nothing currently open, one promoted to a value equal to the slot's
34
+ * base, and one that no longer resolves at all (a stale value the cascade
35
+ * refuses, or a display type whose plugin is gone) appear nowhere. The only
36
+ * place that listed them was the "Reset preferences to defaults" confirmation,
37
+ * which is a destructive dialog to have to open in order to read something.
38
+ *
39
+ * The empty state is deliberate rather than rendering nothing: it is the one
40
+ * place the app says the capability exists to someone who has never used it.
41
+ *
42
+ * **The slot keeps its schema name**, while the display type gets its authored
43
+ * one. A slot has no menu label to borrow — which row a setting is offered on is
44
+ * a menu fact and declaring the slot promotable is a schema fact, and nothing
45
+ * joins them (that split is why `promotableSlotsWithoutPin` has to walk a built
46
+ * menu at all). A raw slot name is also what the config editor labels its fields
47
+ * with, so the two surfaces agree.
48
+ */
49
+ const DisplayDefaultsSection = observer(function DisplayDefaultsSection({ session, pluginManager, }) {
50
+ const byName = displayTypesByName(pluginManager);
51
+ const changes = session
52
+ .getDisplayTypeDefaults()
53
+ .map(({ displayType, slot, value }) => ({
54
+ // the address this section's own reset uses, so it depends on nothing the
55
+ // session's preference-key layout does
56
+ path: [displayType, slot],
57
+ label: `${byName.get(displayType)?.displayName ?? displayType} › ${slot}`,
58
+ from: promotedBaseOf(byName.get(displayType)?.configSchema, slot),
59
+ to: value,
60
+ }));
61
+ return changes.length ? (_jsxs(_Fragment, { children: [_jsx(Typography, { children: "These apply to every track of a display type that hasn't set its own value. Clear one to put those tracks back on the setting's default." }), _jsx(SettingsChangesTable, { changes: changes, onResetRow: change => {
62
+ session.setDisplayTypeDefault(change.path[0], change.path[1], undefined);
63
+ } })] })) : (_jsx(Typography, { children: "None set. The pin beside a setting in a track menu makes that value the default for every track of the same display type." }));
64
+ });
65
+ export default DisplayDefaultsSection;
@@ -0,0 +1,21 @@
1
+ import type { DrawerPosition } from '@jbrowse/core/util';
2
+ /**
3
+ * All the chrome needs of a session, narrowed the way
4
+ * `PreferencesDialogSession` is: the drawer is the one widget surface an
5
+ * embedder can mount against a session of their own shape.
6
+ */
7
+ export interface DrawerChromeSession {
8
+ drawerPosition: DrawerPosition;
9
+ resizeDrawer: (distance: number, availableWidth?: number) => number;
10
+ }
11
+ /**
12
+ * The drawer's chrome: the paper it sits on and the handle that resizes it.
13
+ * Focus tracking is left to the caller through `ref`, since only the app shell
14
+ * has a focused-view concept to wire it to.
15
+ */
16
+ declare const Drawer: ({ session, children, ref, }: {
17
+ session: DrawerChromeSession;
18
+ children: React.ReactNode;
19
+ ref?: React.Ref<HTMLDivElement>;
20
+ }) => import("react").JSX.Element;
21
+ export default Drawer;
@@ -0,0 +1,60 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef } from 'react';
3
+ import ResizeHandle from '@jbrowse/core/ui/ResizeHandle';
4
+ import { DRAWER_Z_INDEX } from '@jbrowse/core/ui/zIndexes';
5
+ import { makeStyles } from '@jbrowse/core/util/tss-react';
6
+ import { Paper } from '@mui/material';
7
+ import { observer } from 'mobx-react';
8
+ const useStyles = makeStyles()(theme => ({
9
+ // The grid item, and the handle's containing block. Its own box rather than
10
+ // the Paper's because the Paper is the thing that SCROLLS: a handle
11
+ // positioned against it starts at the top of the scrolled content and rides
12
+ // away with it, and one taken out of flow with `position: fixed` is measured
13
+ // against the window instead — which is the same box only in a full-window
14
+ // app, and put a page-tall col-resize strip down an embedded view's page.
15
+ root: {
16
+ // the drawer places itself, so a host renders it once wherever it likes
17
+ // rather than on the side matching `drawerPosition` (see
18
+ // `drawerGridTemplateColumns`)
19
+ gridColumn: 'drawer',
20
+ position: 'relative',
21
+ height: '100%',
22
+ minWidth: 0,
23
+ },
24
+ paper: {
25
+ overflowY: 'auto',
26
+ height: '100%',
27
+ // widgets position their own overlays against this
28
+ position: 'relative',
29
+ zIndex: DRAWER_Z_INDEX,
30
+ outline: 'none',
31
+ background: theme.palette.background.default,
32
+ },
33
+ resizeHandle: {
34
+ position: 'absolute',
35
+ top: 0,
36
+ zIndex: DRAWER_Z_INDEX + 1,
37
+ },
38
+ // whichever edge faces the main content
39
+ handleForLeftDrawer: { right: 0 },
40
+ handleForRightDrawer: { left: 0 },
41
+ }));
42
+ /**
43
+ * The drawer's chrome: the paper it sits on and the handle that resizes it.
44
+ * Focus tracking is left to the caller through `ref`, since only the app shell
45
+ * has a focused-view concept to wire it to.
46
+ */
47
+ const Drawer = observer(function Drawer({ session, children, ref, }) {
48
+ const { drawerPosition } = session;
49
+ const { classes, cx } = useStyles();
50
+ const rootRef = useRef(null);
51
+ return (_jsxs("div", { ref: rootRef, className: classes.root, children: [_jsx(Paper, { ref: ref, className: classes.paper, elevation: 16, square: true, "data-testid": "drawer-widget", children: children }), _jsx(ResizeHandle, { onDrag: distance => {
52
+ // the width the drawer and the main area share: both are columns of
53
+ // the grid this is an item of, and the clamp needs the container
54
+ // rather than the window
55
+ session.resizeDrawer(distance, rootRef.current?.parentElement?.clientWidth);
56
+ }, className: cx(classes.resizeHandle, drawerPosition === 'left'
57
+ ? classes.handleForLeftDrawer
58
+ : classes.handleForRightDrawer), vertical: true, bar: true })] }));
59
+ });
60
+ export default Drawer;
@@ -0,0 +1,5 @@
1
+ import type { SessionWithDrawerWidgets } from '@jbrowse/core/util';
2
+ declare const DrawerControls: ({ session, }: {
3
+ session: SessionWithDrawerWidgets;
4
+ }) => import("react").JSX.Element;
5
+ export default DrawerControls;
@@ -0,0 +1,28 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import CloseIcon from '@mui/icons-material/Close';
4
+ import MinimizeIcon from '@mui/icons-material/Minimize';
5
+ import MoreVertIcon from '@mui/icons-material/MoreVert';
6
+ import { IconButton, Menu, MenuItem, Tooltip } from '@mui/material';
7
+ import { observer } from 'mobx-react';
8
+ const drawerPositionOptions = ['left', 'right'];
9
+ const DrawerControls = observer(function DrawerControls({ session, }) {
10
+ const [anchorEl, setAnchorEl] = useState(null);
11
+ const { drawerPosition, visibleWidget } = session;
12
+ return (_jsxs(_Fragment, { children: [_jsx(Tooltip, { title: "Drawer position", children: _jsx(IconButton, { "data-testid": "drawer-position-button", color: "inherit", onClick: event => {
13
+ setAnchorEl(event.currentTarget);
14
+ }, children: _jsx(MoreVertIcon, {}) }) }), _jsx(Tooltip, { title: "Minimize drawer", children: _jsx(IconButton, { "data-testid": "drawer-minimize", color: "inherit", onClick: () => {
15
+ session.notify(`Drawer minimized, click button on ${drawerPosition} side of screen to re-open`, 'info');
16
+ session.minimizeWidgetDrawer();
17
+ }, children: _jsx(MinimizeIcon, {}) }) }), _jsx(Tooltip, { title: "Close drawer", children: _jsx(IconButton, { color: "inherit", onClick: () => {
18
+ if (visibleWidget) {
19
+ session.hideWidget(visibleWidget);
20
+ }
21
+ }, children: _jsx(CloseIcon, {}) }) }), _jsx(Menu, { anchorEl: anchorEl, open: Boolean(anchorEl), onClose: () => {
22
+ setAnchorEl(null);
23
+ }, children: drawerPositionOptions.map(option => (_jsx(MenuItem, { selected: drawerPosition === option, onClick: () => {
24
+ session.setDrawerPosition(option);
25
+ setAnchorEl(null);
26
+ }, children: option }, option))) })] }));
27
+ });
28
+ export default DrawerControls;
@@ -0,0 +1,6 @@
1
+ import type { SessionWithDrawerWidgets } from '@jbrowse/core/util';
2
+ declare const DrawerHeader: ({ session, setToolbarHeight, }: {
3
+ session: SessionWithDrawerWidgets;
4
+ setToolbarHeight: (arg: number) => void;
5
+ }) => import("react").JSX.Element;
6
+ export default DrawerHeader;
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback } from 'react';
3
+ import { makeStyles } from '@jbrowse/core/util/tss-react';
4
+ import { AppBar, Toolbar } from '@mui/material';
5
+ import { observer } from 'mobx-react';
6
+ import DrawerControls from './DrawerControls.js';
7
+ import DrawerWidgetSelector from './DrawerWidgetSelector.js';
8
+ const useStyles = makeStyles()(theme => ({
9
+ appBar: {
10
+ background: theme.palette.secondary.main,
11
+ },
12
+ spacer: {
13
+ flexGrow: 1,
14
+ },
15
+ }));
16
+ const DrawerHeader = observer(function DrawerHeader({ session, setToolbarHeight, }) {
17
+ const { classes } = useStyles();
18
+ // Measured, and re-measured: the toolbar's height is the room a virtualized
19
+ // widget subtracts from the box it is given, and it changes without this
20
+ // component re-rendering -- MUI's Toolbar is 56px under the `sm` breakpoint
21
+ // and 64 above it, and grows again with the root font size. Read once at
22
+ // mount, a widget's list was that much too tall for the rest of the session.
23
+ const appBarRef = useCallback((node) => {
24
+ const publish = () => {
25
+ setToolbarHeight(node.getBoundingClientRect().height);
26
+ };
27
+ publish();
28
+ const observer = new ResizeObserver(publish);
29
+ observer.observe(node);
30
+ return () => {
31
+ observer.disconnect();
32
+ };
33
+ }, [setToolbarHeight]);
34
+ return (_jsx(AppBar, { position: "sticky", className: classes.appBar, ref: appBarRef, children: _jsxs(Toolbar, { disableGutters: true, children: [_jsx(DrawerWidgetSelector, { session: session }), _jsx("div", { className: classes.spacer }), _jsx(DrawerControls, { session: session })] }) }));
35
+ });
36
+ export default DrawerHeader;
@@ -0,0 +1,5 @@
1
+ import type { SessionWithDrawerWidgets } from '@jbrowse/core/util';
2
+ declare const DrawerWidget: ({ session, }: {
3
+ session: SessionWithDrawerWidgets;
4
+ }) => import("react").JSX.Element;
5
+ export default DrawerWidget;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { observer } from 'mobx-react';
4
+ import Drawer from './Drawer.js';
5
+ import DrawerHeader from './DrawerHeader.js';
6
+ import WidgetBody from './WidgetBody.js';
7
+ const DrawerWidget = observer(function DrawerWidget({ session, }) {
8
+ const { visibleWidget } = session;
9
+ const [toolbarHeight, setToolbarHeight] = useState(0);
10
+ return (_jsxs(Drawer, { session: session, children: [_jsx(DrawerHeader, { session: session, setToolbarHeight: setToolbarHeight }), visibleWidget ? (_jsx(WidgetBody, { session: session, widget: visibleWidget, toolbarHeight: toolbarHeight })) : null] }));
11
+ });
12
+ export default DrawerWidget;
@@ -0,0 +1,5 @@
1
+ import type { SessionWithDrawerWidgets } from '@jbrowse/core/util';
2
+ declare const DrawerWidgetSelector: ({ session, }: {
3
+ session: SessionWithDrawerWidgets;
4
+ }) => import("react").JSX.Element;
5
+ export default DrawerWidgetSelector;
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { getEnv } from '@jbrowse/core/util';
3
+ import { makeStyles } from '@jbrowse/core/util/tss-react';
4
+ import DeleteIcon from '@mui/icons-material/Delete';
5
+ import { FormControl, IconButton, MenuItem, Select, Typography, } from '@mui/material';
6
+ import { observer } from 'mobx-react';
7
+ import WidgetHeading from './WidgetHeading.js';
8
+ const useStyles = makeStyles()(theme => ({
9
+ formControl: {
10
+ margin: 0,
11
+ },
12
+ drawerSelect: {
13
+ margin: 0,
14
+ color: theme.palette.secondary.contrastText,
15
+ },
16
+ dropDownIcon: {
17
+ color: theme.palette.secondary.contrastText,
18
+ },
19
+ }));
20
+ const DrawerWidgetSelector = observer(function DrawerWidgetSelector({ session, }) {
21
+ const { visibleWidget, activeWidgets } = session;
22
+ const { classes } = useStyles();
23
+ const { pluginManager } = getEnv(session);
24
+ return (_jsx(FormControl, { className: classes.formControl, children: _jsx(Select, { value: visibleWidget?.id ?? '', "data-testid": "widget-drawer-selects", className: classes.drawerSelect, classes: { icon: classes.dropDownIcon }, renderValue: widgetId => {
25
+ const widget = session.activeWidgets.get(widgetId);
26
+ return widget ? (_jsx(WidgetHeading, { pluginManager: pluginManager, widget: widget })) : (_jsx(Typography, { variant: "h6", color: "inherit", children: "Unknown widget" }));
27
+ }, onChange: e => {
28
+ const w = session.activeWidgets.get(e.target.value);
29
+ if (w) {
30
+ session.showWidget(w);
31
+ }
32
+ else {
33
+ session.notify(`Widget not found ${e.target.value}`, 'warning');
34
+ }
35
+ }, children: [...activeWidgets.values()].map(widget => (_jsxs(MenuItem, { "data-testid": `widget-drawer-selects-item-${widget.type}`, value: widget.id, children: [_jsx(WidgetHeading, { pluginManager: pluginManager, widget: widget }), _jsx(IconButton, { "data-testid": `${widget.type}-drawer-delete`, color: "inherit", "aria-label": "Delete", onClick: e => {
36
+ e.stopPropagation();
37
+ session.hideWidget(widget);
38
+ }, children: _jsx(DeleteIcon, {}) })] }, widget.id))) }) }));
39
+ });
40
+ export default DrawerWidgetSelector;
@@ -1,6 +1,3 @@
1
- import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
2
- import type { AbstractSessionModel } from '@jbrowse/core/util';
3
- export default function FileInfoPanel({ config, session, }: {
4
- config: AnyConfigurationModel | Record<string, unknown>;
5
- session: AbstractSessionModel;
6
- }): import("react/jsx-runtime").JSX.Element;
1
+ import type { AboutPanelProps } from './util.ts';
2
+ declare const FileInfoPanel: ({ config, session, }: AboutPanelProps) => import("react").JSX.Element | null;
3
+ export default FileInfoPanel;
@@ -1,36 +1,45 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useEffect, useState } from 'react';
3
2
  import Attributes from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail/Attributes';
4
3
  import BaseCard from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail/BaseCard';
5
- import { ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui';
6
- import { readConf } from "./util.js";
7
- export default function FileInfoPanel({ config, session, }) {
8
- const [error, setError] = useState();
9
- const [info, setInfo] = useState();
4
+ import { readConfSlot } from '@jbrowse/core/configuration';
5
+ import { ErrorBanner, LoadingEllipses } from '@jbrowse/core/ui';
6
+ import { statusProgressLabel } from '@jbrowse/core/util';
7
+ import { useFetch } from '@jbrowse/core/util/useFetch';
8
+ import { observer } from 'mobx-react';
9
+ // Inline `observer(function(){})`, not a bare declaration: this reads
10
+ // observables (session.rpcManager, and config slots off what is usually a live
11
+ // MST node), and babel-plugin-react-compiler compiles the bare form and can
12
+ // memoize such a read into staleness. RefNameInfoDialog beside it is written
13
+ // the same way; see the React Compiler x MobX note in CLAUDE.md
14
+ const FileInfoPanel = observer(function FileInfoPanel({ config, session, }) {
10
15
  const { rpcManager } = session;
11
- const trackId = readConf(config, 'trackId');
12
- useEffect(() => {
13
- ;
14
- (async () => {
15
- try {
16
- const adapterConfig = readConf(config, 'adapter');
17
- const result = await rpcManager.call(trackId, 'CoreGetInfo', {
18
- adapterConfig,
19
- });
20
- setInfo(result);
21
- }
22
- catch (e) {
23
- console.error(e);
24
- setError(e);
25
- }
26
- })();
27
- }, [config, rpcManager, trackId]);
16
+ const trackId = readConfSlot(config, 'trackId');
17
+ const { data: info, error, isLoading, status, } = useFetch(['CoreGetInfo', trackId],
18
+ // reading a header can mean walking a v8 .hic's norm-vector index or a
19
+ // multi-megabyte VCF header, so it gets both handles: closing the About
20
+ // dialog stops it, and the wait says what it is
21
+ async (_name, _trackId, stopToken, statusCallback) => (await rpcManager.call(trackId, 'CoreGetInfo', {
22
+ adapterConfig: readConfSlot(config, 'adapter'),
23
+ stopToken,
24
+ statusCallback,
25
+ })));
28
26
  const details = typeof info === 'string'
29
27
  ? {
30
28
  header: `<pre>${info
29
+ .replaceAll('&', '&amp;')
31
30
  .replaceAll('<', '&lt;')
32
31
  .replaceAll('>', '&gt;')}</pre>`,
33
32
  }
34
- : info || {};
35
- return (_jsx(BaseCard, { title: "File info", children: error ? (_jsx(ErrorMessage, { error: error })) : info === undefined ? (_jsx(LoadingEllipses, { message: "Loading file data" })) : (_jsx(Attributes, { attributes: details })) }));
36
- }
33
+ : (info ?? {});
34
+ // `BaseFeatureDataAdapter.getHeader` returns null unless an adapter overrides
35
+ // it, and `CoreGetInfo` returns null outright for anything that isn't a
36
+ // feature adapter — so most tracks reached this with nothing to say and got a
37
+ // FILE INFO heading with blank space under it. A panel with no content is not
38
+ // a panel
39
+ const empty = Object.values(details).every(v => v == null);
40
+ if (!error && !isLoading && empty) {
41
+ return null;
42
+ }
43
+ return (_jsx(BaseCard, { title: "File info", children: error ? (_jsx(ErrorBanner, { error: error })) : isLoading ? (_jsx(LoadingEllipses, { message: statusProgressLabel(status) || 'Loading file data' })) : (_jsx(Attributes, { attributes: details })) }));
44
+ });
45
+ export default FileInfoPanel;
@@ -1,6 +1,12 @@
1
1
  interface HeaderButtonsProps {
2
2
  conf: Record<string, unknown>;
3
+ hideUris?: boolean;
4
+ /**
5
+ * `<displayType>.<slot>` for each value the copied config inherited from a
6
+ * session-wide display-type default rather than from the config itself
7
+ */
8
+ fromDisplayTypeDefaults?: string[];
3
9
  setShowRefNames: (show: boolean) => void;
4
10
  }
5
- declare function HeaderButtons({ conf, setShowRefNames }: HeaderButtonsProps): import("react/jsx-runtime").JSX.Element;
11
+ declare function HeaderButtons({ conf, hideUris, fromDisplayTypeDefaults, setShowRefNames, }: HeaderButtonsProps): import("react").JSX.Element;
6
12
  export default HeaderButtons;