@jbrowse/product-core 4.3.0 → 5.0.0-beta.10

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 (157) hide show
  1. package/esm/RootModel/BaseRootModel.d.ts +514 -323
  2. package/esm/RootModel/BaseRootModel.js +141 -14
  3. package/esm/RootModel/HierarchicalConfig.d.ts +30 -20
  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 +37 -0
  8. package/esm/RootModel/PreferencesConfig.js +75 -0
  9. package/esm/RootModel/createConfigModel.d.ts +257 -0
  10. package/esm/RootModel/createConfigModel.js +77 -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 +141 -0
  15. package/esm/Session/BaseSession.d.ts +285 -11
  16. package/esm/Session/BaseSession.js +383 -15
  17. package/esm/Session/Connections.d.ts +115 -8
  18. package/esm/Session/Connections.js +196 -74
  19. package/esm/Session/DrawerWidgets.d.ts +145 -13
  20. package/esm/Session/DrawerWidgets.js +219 -49
  21. package/esm/Session/MultipleViews.d.ts +1067 -1177
  22. package/esm/Session/MultipleViews.js +395 -64
  23. package/esm/Session/Preferences.d.ts +1389 -0
  24. package/esm/Session/Preferences.js +58 -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 +1101 -1180
  28. package/esm/Session/SessionTracks.js +584 -34
  29. package/esm/Session/Themes.d.ts +136 -2
  30. package/esm/Session/Themes.js +267 -21
  31. package/esm/Session/TrackMenu.d.ts +88 -0
  32. package/esm/Session/TrackMenu.js +175 -0
  33. package/esm/Session/TrackMenuSessionMixin.d.ts +27 -0
  34. package/esm/Session/TrackMenuSessionMixin.js +42 -0
  35. package/esm/Session/Tracks.d.ts +945 -1159
  36. package/esm/Session/Tracks.js +229 -30
  37. package/esm/Session/assertSessionModel.d.ts +21 -0
  38. package/esm/Session/assertSessionModel.js +1 -0
  39. package/esm/Session/copyTrackSnapshot.d.ts +21 -0
  40. package/esm/Session/copyTrackSnapshot.js +20 -0
  41. package/esm/Session/developerMode.d.ts +7 -0
  42. package/esm/Session/developerMode.js +43 -0
  43. package/esm/Session/finalizeSession.d.ts +15 -0
  44. package/esm/Session/finalizeSession.js +28 -0
  45. package/esm/Session/hydratedForms.d.ts +16 -0
  46. package/esm/Session/hydratedForms.js +13 -0
  47. package/esm/Session/index.d.ts +27 -10
  48. package/esm/Session/index.js +16 -10
  49. package/esm/Session/pluginExtraTrackItems.d.ts +11 -0
  50. package/esm/Session/pluginExtraTrackItems.js +13 -0
  51. package/esm/Session/readdedTrackConf.d.ts +8 -0
  52. package/esm/Session/readdedTrackConf.js +24 -0
  53. package/esm/Session/shareableSnapshot.d.ts +23 -0
  54. package/esm/Session/shareableSnapshot.js +66 -0
  55. package/esm/Session/temporaryAssemblyTracks.d.ts +24 -0
  56. package/esm/Session/temporaryAssemblyTracks.js +37 -0
  57. package/esm/assertExtends.d.ts +55 -0
  58. package/esm/assertExtends.js +1 -0
  59. package/esm/controllerTracks.d.ts +76 -0
  60. package/esm/controllerTracks.js +125 -0
  61. package/esm/destroyViewState.d.ts +21 -0
  62. package/esm/destroyViewState.js +29 -0
  63. package/esm/embeddedController.d.ts +57 -0
  64. package/esm/embeddedController.js +100 -0
  65. package/esm/index.d.ts +32 -4
  66. package/esm/index.js +31 -4
  67. package/esm/loadPlugins.d.ts +38 -0
  68. package/esm/loadPlugins.js +45 -0
  69. package/esm/localFiles.d.ts +74 -0
  70. package/esm/localFiles.js +156 -0
  71. package/esm/observeSession.d.ts +55 -0
  72. package/esm/observeSession.js +90 -0
  73. package/esm/parallelChunkLoading.d.ts +30 -0
  74. package/esm/parallelChunkLoading.js +71 -0
  75. package/esm/pluginInput.d.ts +32 -0
  76. package/esm/pluginInput.js +19 -0
  77. package/esm/pruneUnbuildableNodes.d.ts +109 -0
  78. package/esm/pruneUnbuildableNodes.js +562 -0
  79. package/esm/resolveAssemblies.d.ts +68 -0
  80. package/esm/resolveAssemblies.js +77 -0
  81. package/esm/rpcWorker.d.ts +36 -4
  82. package/esm/rpcWorker.js +126 -20
  83. package/esm/sessionMigrations/index.d.ts +15 -0
  84. package/esm/sessionMigrations/index.js +178 -0
  85. package/esm/sessionUrl.d.ts +40 -0
  86. package/esm/sessionUrl.js +66 -0
  87. package/esm/sessionUtils.d.ts +74 -0
  88. package/esm/sessionUtils.js +599 -0
  89. package/esm/siblingCast.d.ts +14 -0
  90. package/esm/siblingCast.js +16 -0
  91. package/esm/snapshotUtils.d.ts +8 -0
  92. package/esm/snapshotUtils.js +12 -0
  93. package/esm/ui/AboutDialog.d.ts +8 -3
  94. package/esm/ui/AboutDialog.js +14 -5
  95. package/esm/ui/AboutDialogContents.d.ts +2 -6
  96. package/esm/ui/AboutDialogContents.js +13 -17
  97. package/esm/ui/AssemblyInfoPanel.d.ts +5 -0
  98. package/esm/ui/AssemblyInfoPanel.js +63 -0
  99. package/esm/ui/DescriptionPanel.d.ts +3 -0
  100. package/esm/ui/DescriptionPanel.js +25 -0
  101. package/esm/ui/Drawer.d.ts +21 -0
  102. package/esm/ui/Drawer.js +60 -0
  103. package/esm/ui/DrawerControls.d.ts +5 -0
  104. package/esm/ui/DrawerControls.js +28 -0
  105. package/esm/ui/DrawerHeader.d.ts +6 -0
  106. package/esm/ui/DrawerHeader.js +36 -0
  107. package/esm/ui/DrawerWidget.d.ts +5 -0
  108. package/esm/ui/DrawerWidget.js +12 -0
  109. package/esm/ui/DrawerWidgetSelector.d.ts +5 -0
  110. package/esm/ui/DrawerWidgetSelector.js +40 -0
  111. package/esm/ui/FileInfoPanel.d.ts +3 -6
  112. package/esm/ui/FileInfoPanel.js +36 -26
  113. package/esm/ui/GeneralPreferencesTab.d.ts +17 -0
  114. package/esm/ui/GeneralPreferencesTab.js +34 -0
  115. package/esm/ui/HeaderButtons.d.ts +2 -1
  116. package/esm/ui/HeaderButtons.js +15 -17
  117. package/esm/ui/ModalWidget.d.ts +13 -0
  118. package/esm/ui/ModalWidget.js +48 -0
  119. package/esm/ui/ModalWidgetAppBar.d.ts +8 -0
  120. package/esm/ui/ModalWidgetAppBar.js +9 -0
  121. package/esm/ui/PreferenceCheckbox.d.ts +6 -0
  122. package/esm/ui/PreferenceCheckbox.js +16 -0
  123. package/esm/ui/PreferencesDialog.d.ts +34 -0
  124. package/esm/ui/PreferencesDialog.js +55 -0
  125. package/esm/ui/PreferencesResetDialog.d.ts +8 -0
  126. package/esm/ui/PreferencesResetDialog.js +19 -0
  127. package/esm/ui/RefNameAliasesDialog.d.ts +9 -0
  128. package/esm/ui/RefNameAliasesDialog.js +65 -0
  129. package/esm/ui/RefNameInfoDialog.d.ts +3 -6
  130. package/esm/ui/RefNameInfoDialog.js +45 -63
  131. package/esm/ui/ViewPreferencesTab.d.ts +12 -0
  132. package/esm/ui/ViewPreferencesTab.js +15 -0
  133. package/esm/ui/WidgetBody.d.ts +18 -0
  134. package/esm/ui/WidgetBody.js +22 -0
  135. package/esm/ui/WidgetHeading.d.ts +7 -0
  136. package/esm/ui/WidgetHeading.js +7 -0
  137. package/esm/ui/aboutTestUtils.d.ts +140 -0
  138. package/esm/ui/aboutTestUtils.js +25 -0
  139. package/esm/ui/descriptionHtml.d.ts +25 -0
  140. package/esm/ui/descriptionHtml.js +64 -0
  141. package/esm/ui/drawerLayout.d.ts +18 -0
  142. package/esm/ui/drawerLayout.js +22 -0
  143. package/esm/ui/index.d.ts +15 -1
  144. package/esm/ui/index.js +13 -1
  145. package/esm/ui/preferencesReset.d.ts +18 -0
  146. package/esm/ui/preferencesReset.js +69 -0
  147. package/esm/ui/util.d.ts +22 -7
  148. package/esm/ui/util.js +19 -41
  149. package/esm/useEngineLifecycle.d.ts +19 -0
  150. package/esm/useEngineLifecycle.js +44 -0
  151. package/package.json +15 -18
  152. package/esm/RootModel/FormatAbout.d.ts +0 -12
  153. package/esm/RootModel/FormatAbout.js +0 -15
  154. package/esm/RootModel/FormatDetails.d.ts +0 -24
  155. package/esm/RootModel/FormatDetails.js +0 -27
  156. package/esm/Session/DialogQueue.d.ts +0 -17
  157. package/esm/Session/DialogQueue.js +0 -31
@@ -1,18 +1,152 @@
1
1
  import type PluginManager from '@jbrowse/core/PluginManager';
2
+ import type { PaletteMode } from '@jbrowse/core/ui/palette';
3
+ import type { SerializableThemeArgs, ThemeMap } from '@jbrowse/core/ui/theme';
2
4
  import type { IAnyStateTreeNode, Instance } from '@jbrowse/mobx-state-tree';
3
5
  import type { ThemeOptions } from '@mui/material';
4
- type ThemeMap = Record<string, ThemeOptions>;
6
+ /** The three the mode picker offers; `system` follows the OS preference. */
7
+ export type ThemeModeSelection = PaletteMode | 'system';
8
+ /**
9
+ * The theme the stored selection resolves to, for UI drawn before a session
10
+ * exists, such as Desktop's start screen.
11
+ */
12
+ export declare function storedThemeArgs(): SerializableThemeArgs;
13
+ /**
14
+ * #stateModel ThemeManagerSessionMixin
15
+ */
5
16
  export declare function ThemeManagerSessionMixin(_pluginManager: PluginManager): import("@jbrowse/mobx-state-tree").IModelType<{}, {
6
17
  sessionThemeName: string;
18
+ sessionThemeMode: ThemeModeSelection;
19
+ systemThemeOverride: PaletteMode | undefined;
20
+ systemPrefersDark: boolean;
7
21
  } & {
22
+ /**
23
+ * #method
24
+ */
8
25
  allThemes(): ThemeMap;
26
+ /**
27
+ * #getter
28
+ * Which palette is in effect. A stored name whose theme an admin has
29
+ * since dropped reads as `default` without the stored value being
30
+ * touched, so it comes back if the plugin supplying it loads again.
31
+ */
9
32
  readonly themeName: string;
33
+ /**
34
+ * #getter
35
+ * Light, dark, or following the OS — the axis the palette is drawn
36
+ * along, and what the mode picker shows. `effectiveThemeMode` is the
37
+ * one to read for a colour decision.
38
+ */
39
+ readonly themeMode: ThemeModeSelection;
40
+ /**
41
+ * #getter
42
+ * Light or dark, with `system` resolved against the OS preference or
43
+ * the toolbar's hold on the other mode.
44
+ */
45
+ readonly effectiveThemeMode: PaletteMode;
46
+ /**
47
+ * #getter
48
+ * Whether what is drawn right now is dark. Read off the resolved
49
+ * palette rather than the mode, so a palette pinned to one mode — an
50
+ * `extraThemes` entry declaring `mode: 'dark'` — answers for itself.
51
+ */
52
+ readonly themeIsDark: boolean;
53
+ /**
54
+ * #getter
55
+ */
56
+ readonly themeOptions: SerializableThemeArgs;
57
+ /**
58
+ * #getter
59
+ * Every color JBrowse renders, resolved to plain strings. This is what
60
+ * rendering reads: it needs no React context, it crosses the RPC worker
61
+ * boundary as itself, and it costs no UI toolkit. Prefer it over
62
+ * `theme` anywhere the answer wanted is a color rather than a Material
63
+ * UI component style.
64
+ */
65
+ readonly palette: import("@jbrowse/core/ui/palette").JBrowsePalette;
66
+ /**
67
+ * #getter
68
+ * The palette plus the sizing tokens `makeStyles` reads — spacing,
69
+ * corner radius, type scale. This is what a product mounts on
70
+ * `StyleThemeProvider`; it costs no UI toolkit, and it is derived from
71
+ * the same `themeOptions` as `theme`, so a config `theme` that sets
72
+ * `spacing` moves JBrowse's own styles and its Material components
73
+ * together.
74
+ */
75
+ readonly styleTheme: import("@jbrowse/core/ui/styleTheme").JBrowseStyleTheme;
76
+ /**
77
+ * #getter
78
+ * The Material UI theme, for the components that are Material UI. Its
79
+ * palette is spliced from the same `resolvePalette` call as `palette`
80
+ * above, so the two cannot disagree.
81
+ */
10
82
  readonly theme: import("@mui/material").Theme;
83
+ /**
84
+ * #method
85
+ * Raw `ThemeOptions` for the active theme, or a named override (used by
86
+ * the SVG-export theme picker). Unlike `theme` (a built,
87
+ * non-serializable MUI theme), this is the plain options object every
88
+ * view's SVG export threads into each display's `renderSvg`, which
89
+ * rebuilds the theme via `createJBrowseTheme` outside React context.
90
+ *
91
+ * The `default` entry is merged with the config `theme` slot, because
92
+ * the picker labels it "Default (from config)" and `resolvePalette`
93
+ * merges the preset with the slot for every other consumer. The bare
94
+ * preset would make `view.exportSvg()` drop a host's configured
95
+ * palette: a config setting `primary.main` would draw `#123456` on
96
+ * screen and export the stock `#0D233F`, while the export dialog named
97
+ * the default theme. Every other named theme is a fixed preset that
98
+ * ignores config.
99
+ *
100
+ * **The mode is spliced in here**, because a palette no longer carries
101
+ * one and this is the last point before the export leaves the session:
102
+ * every caller hands `renderSvg` these options and nothing else, so a
103
+ * figure exported from a dark session comes out dark. A palette pinned
104
+ * to its own mode, or a `name` from before the axis, states the mode
105
+ * itself and keeps it.
106
+ */
107
+ getActiveThemeOptions(name?: string): ThemeOptions;
11
108
  } & {
109
+ /**
110
+ * #action
111
+ * Pick a palette. A name from before light and dark were an axis sets
112
+ * the mode it spelled as well, so an old share link, a saved figure spec
113
+ * and `jbrowse-img --theme darkStock` all still mean what they said.
114
+ * Either that or a palette pinned to its own mode drops a held mode,
115
+ * which would otherwise resurface on the next palette change.
116
+ */
12
117
  setThemeName(name: string): void;
118
+ /**
119
+ * #action
120
+ */
121
+ setSystemPrefersDark(dark: boolean): void;
122
+ /**
123
+ * #action
124
+ * Hold a session that follows the system on the mode the OS is not
125
+ * asking for, or `undefined` to follow it again. The toolbar's sun/moon
126
+ * writes this, so it stays in the toolbar after a click. The OS flipping
127
+ * clears it, since by then the OS has come round to the held mode. The
128
+ * palette is untouched: a reader on Minimal who does this keeps Minimal.
129
+ */
130
+ setSystemThemeOverride(mode?: PaletteMode): void;
131
+ /**
132
+ * #action
133
+ * Draw the session light or dark, leaving the palette alone. `system`
134
+ * follows the OS preference. Satisfies `ThemeModeSession`, so
135
+ * `useSessionPalette` works against an app session and an embedded one
136
+ * alike, and a host that follows its own dark-mode state calls this.
137
+ *
138
+ * `themeOptions` carries the mode to the RPC worker, so the labels baked
139
+ * into a rendered image follow it along with what React draws. That used
140
+ * to take a write into the config `theme` slot, because mode lived
141
+ * inside a palette and there was nowhere else to put it.
142
+ */
143
+ setThemeMode(mode: ThemeModeSelection): void;
144
+ } & {
13
145
  afterAttach(): void;
14
146
  }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>;
147
+ /** Session mixin MST type for a session that supports theming */
15
148
  export type SessionWithThemesType = ReturnType<typeof ThemeManagerSessionMixin>;
149
+ /** Instance of a session that has theming support */
16
150
  export type SessionWithThemes = Instance<SessionWithThemesType>;
151
+ /** Type guard for SessionWithThemes */
17
152
  export declare function isSessionWithThemes(session: IAnyStateTreeNode): session is SessionWithThemes;
18
- export {};
@@ -1,43 +1,289 @@
1
1
  import { getConf } from '@jbrowse/core/configuration';
2
- import { createJBrowseTheme, defaultThemes } from '@jbrowse/core/ui';
2
+ import { resolvePalette, resolveThemeSelection } from '@jbrowse/core/ui/palette';
3
+ import { resolveStyleTheme } from '@jbrowse/core/ui/styleTheme';
4
+ import { createJBrowseThemeFromArgs, defaultThemes, } from '@jbrowse/core/ui/theme';
3
5
  import { localStorageGetItem, localStorageSetItem } from '@jbrowse/core/util';
6
+ import { onColorSchemeChange, prefersDarkColorScheme, } from '@jbrowse/core/util/systemColorScheme';
4
7
  import { addDisposer, types } from '@jbrowse/mobx-state-tree';
5
8
  import { autorun } from 'mobx';
9
+ import { asSession } from '../siblingCast.js';
10
+ import { isBaseSession } from './BaseSession.js';
11
+ /**
12
+ * What `name` actually resolves to — `default` for one `themes` no longer
13
+ * holds. Every theme name is either stored or handed in from outside, and both
14
+ * go stale the same way: `sessionThemeName` outlives an `extraThemes` entry an
15
+ * admin drops, and so does a name an export dialog persisted or a saved figure
16
+ * spec carries.
17
+ */
18
+ function resolveThemeName(themes, name) {
19
+ return themes[name] ? name : 'default';
20
+ }
21
+ /**
22
+ * A session starts light, and follows the OS only when asked to. A dark OS is
23
+ * not a request for a dark genome browser.
24
+ */
25
+ const DEFAULT_MODE = 'light';
26
+ function systemMode() {
27
+ return prefersDarkColorScheme() ? 'dark' : 'light';
28
+ }
29
+ // An override outlives a reload only while the OS still disagrees with it; one
30
+ // the OS has since come round to means following again.
31
+ function storedOverride() {
32
+ const stored = localStorageGetItem('themeModeOverride');
33
+ return (stored === 'light' || stored === 'dark') && stored !== systemMode()
34
+ ? stored
35
+ : undefined;
36
+ }
37
+ // A `themeName` stored before light and dark were an axis spells a mode into
38
+ // the name. Split it once, on the way in, so the session persists the pair from
39
+ // then on and the retired name goes away; a name this does not know — an
40
+ // `extraThemes` entry whose plugin is absent — passes through untouched.
41
+ function storedSelection() {
42
+ const stored = localStorageGetItem('themeName') ?? 'default';
43
+ const { themeName, mode } = resolveThemeSelection(stored);
44
+ const storedMode = localStorageGetItem('themeMode');
45
+ return {
46
+ sessionThemeName: themeName,
47
+ sessionThemeMode: storedMode ?? mode ?? DEFAULT_MODE,
48
+ systemThemeOverride: storedOverride(),
49
+ };
50
+ }
51
+ /**
52
+ * The theme the stored selection resolves to, for UI drawn before a session
53
+ * exists, such as Desktop's start screen.
54
+ */
55
+ export function storedThemeArgs() {
56
+ const { sessionThemeName, sessionThemeMode, systemThemeOverride } = storedSelection();
57
+ return {
58
+ themeName: sessionThemeName,
59
+ mode: sessionThemeMode === 'system'
60
+ ? (systemThemeOverride ?? systemMode())
61
+ : sessionThemeMode,
62
+ };
63
+ }
64
+ /**
65
+ * #stateModel ThemeManagerSessionMixin
66
+ */
6
67
  export function ThemeManagerSessionMixin(_pluginManager) {
7
68
  return types
8
69
  .model({})
9
70
  .volatile(() => ({
10
- sessionThemeName: localStorageGetItem('themeName') || 'default',
71
+ ...storedSelection(),
72
+ systemPrefersDark: prefersDarkColorScheme(),
11
73
  }))
12
- .views(s => ({
13
- allThemes() {
14
- const self = s;
15
- const extraThemes = getConf(self.jbrowse, 'extraThemes');
16
- return { ...defaultThemes, ...extraThemes };
74
+ .views(s => {
75
+ const self = asSession(s);
76
+ return {
77
+ /**
78
+ * #method
79
+ */
80
+ allThemes() {
81
+ const extraThemes = getConf(self.jbrowse, 'extraThemes');
82
+ return { ...defaultThemes, ...extraThemes };
83
+ },
84
+ /**
85
+ * #getter
86
+ * Which palette is in effect. A stored name whose theme an admin has
87
+ * since dropped reads as `default` without the stored value being
88
+ * touched, so it comes back if the plugin supplying it loads again.
89
+ */
90
+ get themeName() {
91
+ return resolveThemeName(this.allThemes(), self.sessionThemeName);
92
+ },
93
+ /**
94
+ * #getter
95
+ * Light, dark, or following the OS — the axis the palette is drawn
96
+ * along, and what the mode picker shows. `effectiveThemeMode` is the
97
+ * one to read for a colour decision.
98
+ */
99
+ get themeMode() {
100
+ return self.sessionThemeMode;
101
+ },
102
+ /**
103
+ * #getter
104
+ * Light or dark, with `system` resolved against the OS preference or
105
+ * the toolbar's hold on the other mode.
106
+ */
107
+ get effectiveThemeMode() {
108
+ return this.themeMode === 'system'
109
+ ? (self.systemThemeOverride ??
110
+ (self.systemPrefersDark ? 'dark' : 'light'))
111
+ : this.themeMode;
112
+ },
113
+ /**
114
+ * #getter
115
+ * Whether what is drawn right now is dark. Read off the resolved
116
+ * palette rather than the mode, so a palette pinned to one mode — an
117
+ * `extraThemes` entry declaring `mode: 'dark'` — answers for itself.
118
+ */
119
+ get themeIsDark() {
120
+ return this.palette.mode === 'dark';
121
+ },
122
+ /**
123
+ * #getter
124
+ */
125
+ // Structurally-serializable description of the active theme, safe to
126
+ // send across the RPC worker boundary (the created `theme` carries
127
+ // functions and cannot be cloned). The worker rebuilds via
128
+ // createJBrowseThemeFromArgs.
129
+ get themeOptions() {
130
+ return {
131
+ configTheme: getConf(self.jbrowse, 'theme'),
132
+ extraThemes: getConf(self.jbrowse, 'extraThemes'),
133
+ themeName: this.themeName,
134
+ mode: this.effectiveThemeMode,
135
+ };
136
+ },
137
+ /**
138
+ * #getter
139
+ * Every color JBrowse renders, resolved to plain strings. This is what
140
+ * rendering reads: it needs no React context, it crosses the RPC worker
141
+ * boundary as itself, and it costs no UI toolkit. Prefer it over
142
+ * `theme` anywhere the answer wanted is a color rather than a Material
143
+ * UI component style.
144
+ */
145
+ get palette() {
146
+ return resolvePalette(this.themeOptions);
147
+ },
148
+ /**
149
+ * #getter
150
+ * The palette plus the sizing tokens `makeStyles` reads — spacing,
151
+ * corner radius, type scale. This is what a product mounts on
152
+ * `StyleThemeProvider`; it costs no UI toolkit, and it is derived from
153
+ * the same `themeOptions` as `theme`, so a config `theme` that sets
154
+ * `spacing` moves JBrowse's own styles and its Material components
155
+ * together.
156
+ */
157
+ get styleTheme() {
158
+ return resolveStyleTheme(this.themeOptions);
159
+ },
160
+ /**
161
+ * #getter
162
+ * The Material UI theme, for the components that are Material UI. Its
163
+ * palette is spliced from the same `resolvePalette` call as `palette`
164
+ * above, so the two cannot disagree.
165
+ */
166
+ get theme() {
167
+ return createJBrowseThemeFromArgs(this.themeOptions);
168
+ },
169
+ /**
170
+ * #method
171
+ * Raw `ThemeOptions` for the active theme, or a named override (used by
172
+ * the SVG-export theme picker). Unlike `theme` (a built,
173
+ * non-serializable MUI theme), this is the plain options object every
174
+ * view's SVG export threads into each display's `renderSvg`, which
175
+ * rebuilds the theme via `createJBrowseTheme` outside React context.
176
+ *
177
+ * The `default` entry is merged with the config `theme` slot, because
178
+ * the picker labels it "Default (from config)" and `resolvePalette`
179
+ * merges the preset with the slot for every other consumer. The bare
180
+ * preset would make `view.exportSvg()` drop a host's configured
181
+ * palette: a config setting `primary.main` would draw `#123456` on
182
+ * screen and export the stock `#0D233F`, while the export dialog named
183
+ * the default theme. Every other named theme is a fixed preset that
184
+ * ignores config.
185
+ *
186
+ * **The mode is spliced in here**, because a palette no longer carries
187
+ * one and this is the last point before the export leaves the session:
188
+ * every caller hands `renderSvg` these options and nothing else, so a
189
+ * figure exported from a dark session comes out dark. A palette pinned
190
+ * to its own mode, or a `name` from before the axis, states the mode
191
+ * itself and keeps it.
192
+ */
193
+ getActiveThemeOptions(name) {
194
+ const all = this.allThemes();
195
+ const selection = resolveThemeSelection(name ?? this.themeName, undefined, all);
196
+ const themeName = resolveThemeName(all, selection.themeName);
197
+ const theme = all[themeName];
198
+ const mode = theme?.palette?.mode ?? selection.mode ?? this.effectiveThemeMode;
199
+ if (themeName !== 'default') {
200
+ return { ...theme, palette: { ...theme?.palette, mode } };
201
+ }
202
+ // shallow over the palette, which is how `resolvePalette` spreads the
203
+ // same two — `mode` and `primary` are siblings there, so both levels
204
+ // have to survive
205
+ const configTheme = getConf(self.jbrowse, 'theme');
206
+ return {
207
+ ...theme,
208
+ ...configTheme,
209
+ palette: { ...theme?.palette, ...configTheme.palette, mode },
210
+ };
211
+ },
212
+ };
213
+ })
214
+ .actions(self => ({
215
+ /**
216
+ * #action
217
+ * Pick a palette. A name from before light and dark were an axis sets
218
+ * the mode it spelled as well, so an old share link, a saved figure spec
219
+ * and `jbrowse-img --theme darkStock` all still mean what they said.
220
+ * Either that or a palette pinned to its own mode drops a held mode,
221
+ * which would otherwise resurface on the next palette change.
222
+ */
223
+ setThemeName(name) {
224
+ const { themeName, mode } = resolveThemeSelection(name);
225
+ self.sessionThemeName = themeName;
226
+ if (mode) {
227
+ self.sessionThemeMode = mode;
228
+ }
229
+ const drawnMode = self.themeIsDark ? 'dark' : 'light';
230
+ if (mode || drawnMode !== self.effectiveThemeMode) {
231
+ self.systemThemeOverride = undefined;
232
+ }
233
+ },
234
+ /**
235
+ * #action
236
+ */
237
+ setSystemPrefersDark(dark) {
238
+ if (dark !== self.systemPrefersDark) {
239
+ self.systemThemeOverride = undefined;
240
+ }
241
+ self.systemPrefersDark = dark;
17
242
  },
18
- get themeName() {
19
- const { sessionThemeName } = s;
20
- const all = this.allThemes();
21
- return all[sessionThemeName] ? sessionThemeName : 'default';
243
+ /**
244
+ * #action
245
+ * Hold a session that follows the system on the mode the OS is not
246
+ * asking for, or `undefined` to follow it again. The toolbar's sun/moon
247
+ * writes this, so it stays in the toolbar after a click. The OS flipping
248
+ * clears it, since by then the OS has come round to the held mode. The
249
+ * palette is untouched: a reader on Minimal who does this keeps Minimal.
250
+ */
251
+ setSystemThemeOverride(mode) {
252
+ self.systemThemeOverride = mode;
22
253
  },
23
- get theme() {
24
- const self = s;
25
- const configTheme = getConf(self.jbrowse, 'theme');
26
- const all = this.allThemes();
27
- return createJBrowseTheme(configTheme, all, this.themeName);
254
+ /**
255
+ * #action
256
+ * Draw the session light or dark, leaving the palette alone. `system`
257
+ * follows the OS preference. Satisfies `ThemeModeSession`, so
258
+ * `useSessionPalette` works against an app session and an embedded one
259
+ * alike, and a host that follows its own dark-mode state calls this.
260
+ *
261
+ * `themeOptions` carries the mode to the RPC worker, so the labels baked
262
+ * into a rendered image follow it along with what React draws. That used
263
+ * to take a write into the config `theme` slot, because mode lived
264
+ * inside a palette and there was nowhere else to put it.
265
+ */
266
+ setThemeMode(mode) {
267
+ self.sessionThemeMode = mode;
268
+ self.systemThemeOverride = undefined;
28
269
  },
29
270
  }))
30
271
  .actions(self => ({
31
- setThemeName(name) {
32
- self.sessionThemeName = name;
33
- },
34
272
  afterAttach() {
273
+ addDisposer(self, onColorSchemeChange(() => {
274
+ self.setSystemPrefersDark(prefersDarkColorScheme());
275
+ }));
35
276
  addDisposer(self, autorun(function themeNameAutorun() {
36
- localStorageSetItem('themeName', self.themeName);
277
+ // persist the raw selection, not the coerced themeName, so a
278
+ // theme registered later isn't clobbered with 'default'
279
+ localStorageSetItem('themeName', self.sessionThemeName);
280
+ localStorageSetItem('themeMode', self.sessionThemeMode);
281
+ localStorageSetItem('themeModeOverride', self.systemThemeOverride ?? '');
37
282
  }, { name: 'ThemeName' }));
38
283
  },
39
284
  }));
40
285
  }
286
+ /** Type guard for SessionWithThemes */
41
287
  export function isSessionWithThemes(session) {
42
- return 'theme' in session;
288
+ return isBaseSession(session) && 'theme' in session;
43
289
  }
@@ -0,0 +1,88 @@
1
+ import type PluginManager from '@jbrowse/core/PluginManager';
2
+ import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
3
+ import type { BaseTrackConfig } from '@jbrowse/core/pluggableElementTypes';
4
+ import type { MenuItem } from '@jbrowse/core/ui';
5
+ import type { DialogComponentType } from '@jbrowse/core/util';
6
+ import type { TrackActionView } from '@jbrowse/core/util/types';
7
+ interface TrackActionSession<C> {
8
+ editConfiguration: (config: AnyConfigurationModel | {
9
+ trackId: string;
10
+ }, opts?: {
11
+ expandedDisplayId?: string;
12
+ }) => void;
13
+ publishTrackConf: (conf: C) => unknown;
14
+ deleteTrackConf: (conf: AnyConfigurationModel) => void;
15
+ resetTrackConfiguration?: (trackId: string) => void;
16
+ /**
17
+ * Declared here, though only the copy guard below reads it, so a session that
18
+ * loses the list loses the guard *loudly*. `namesTemporaryAssembly` takes an
19
+ * `unknown` session and answers false for one that holds no such list, which
20
+ * is right for a caller that genuinely has none and wrong for one that let it
21
+ * drop — and that second case is a copy action silently offered again.
22
+ *
23
+ * `unknown[]`, the spelling `AbstractSessionModel` already uses, and not the
24
+ * `{ name?: string }[]` the guard narrows to: that one is a weak type, so a
25
+ * real MST array of assembly configs — whose element type is an index
26
+ * signature that declares no `name` — shares no property with it and TS
27
+ * rejects the assignment. The shape belongs in the guard, which is the only
28
+ * thing that reads it.
29
+ */
30
+ temporaryAssemblies?: unknown[];
31
+ }
32
+ /**
33
+ * The shared Settings / Copy / Copy-and-open / Delete track actions. Each
34
+ * product supplies `makeCopy` (its own session-track/category rules) and
35
+ * `canEdit`; reference sequence tracks can't be copied or deleted. When
36
+ * `isSessionOverride` is set the track carries session edits over its base
37
+ * config, so a Reset is offered, and it replaces Delete for a user who cannot
38
+ * delete the base.
39
+ */
40
+ export declare function trackActionItems<C extends {
41
+ trackId: string;
42
+ }>({ session, config, view, canEdit, isSessionOverride, makeCopy, }: {
43
+ session: TrackActionSession<C>;
44
+ config: BaseTrackConfig;
45
+ view?: TrackActionView;
46
+ canEdit: boolean;
47
+ isSessionOverride?: boolean;
48
+ makeCopy: () => C;
49
+ }): MenuItem[];
50
+ export interface SessionWithDialog {
51
+ queueDialog: (cb: (done: () => void) => [DialogComponentType, Record<string, unknown>]) => void;
52
+ }
53
+ type TrackConfig = AnyConfigurationModel | Record<string, unknown>;
54
+ /** "About track" menu item, shared by every product's track menu */
55
+ export declare function aboutTrackMenuItem(session: SessionWithDialog, config: TrackConfig): MenuItem;
56
+ /**
57
+ * flattened track menu (About + raw actions) for the hierarchical track selector
58
+ */
59
+ export declare function trackListMenuItems(session: SessionWithDialog, config: TrackConfig, actions: MenuItem[]): MenuItem[];
60
+ /**
61
+ * track menu with an "About track" item and a "Track actions" submenu, for the
62
+ * in-view track label menu
63
+ */
64
+ export declare function trackActionMenuItems(session: SessionWithDialog, config: TrackConfig, actions: MenuItem[]): MenuItem[];
65
+ /**
66
+ * #stateModel TrackMenuItemsSessionMixin
67
+ *
68
+ * The two track-menu wrappers (`getTrackListMenuItems` for the hierarchical
69
+ * selector, `getTrackActionMenuItems` for the in-view label menu) shared by the
70
+ * full web and desktop sessions. Both are pure functions of `getTrackActions`,
71
+ * which each session supplies (web gates on edit rights; desktop adds indexing).
72
+ */
73
+ export declare function TrackMenuItemsSessionMixin(pluginManager: PluginManager): import("@jbrowse/mobx-state-tree").IModelType<{}, {
74
+ /**
75
+ * #method
76
+ * flattened menu items for use in hierarchical track selector
77
+ */
78
+ getTrackListMenuItems(config: BaseTrackConfig, view?: TrackActionView): MenuItem[];
79
+ /**
80
+ * #method
81
+ * track menu with About + "Track actions" submenu for the in-view label
82
+ */
83
+ getTrackActionMenuItems({ config, view, }: {
84
+ config: BaseTrackConfig;
85
+ view?: TrackActionView;
86
+ }): MenuItem[];
87
+ }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>;
88
+ export {};