@jbrowse/product-core 5.0.0-beta.3 → 5.0.0-beta.7

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.
@@ -39,9 +39,9 @@ export function exportSessionMenuItem() {
39
39
  const { saveAs } = await import('@jbrowse/core/util/FileSaver');
40
40
  saveAs(new Blob([
41
41
  JSON.stringify({
42
- // an exported file is read by someone who doesn't have this
43
- // browser's promoted display-type defaults, so flatten the
44
- // cascade into it (same rule as ShareDialog)
42
+ // an exported file is read by someone whose own preferences
43
+ // differ, so stamp what the live session resolves at read
44
+ // time (same rule as ShareDialog)
45
45
  session: getShareableSessionSnapshot(session),
46
46
  }, null, 2),
47
47
  ], { type: 'text/plain;charset=utf-8' }), 'session.json');
@@ -75,23 +75,15 @@ export declare function BaseSessionModel<ROOT_MODEL_TYPE extends BaseRootModelTy
75
75
  *
76
76
  * An `observable.map` (not a plain object reassigned wholesale) so each
77
77
  * preference is its own tracked key: writing one (`setScrollZoom`) can't
78
- * invalidate a reader of another (`getDisplayTypeDefault` in a track's
79
- * `rpcProps`). A single spread-replaced object made every setter wake
80
- * every reader, so toggling scroll-to-zoom re-fetched every track. For the
81
- * same reason each promoted per-display-type default is a flat composite
82
- * key (see `displayTypeDefaultKey`), not a single nested `displayTypeDefaults`
83
- * object — promoting one default can't wake readers of a different one.
78
+ * invalidate a reader of another. A single spread-replaced object made
79
+ * every setter wake every reader, so toggling scroll-to-zoom re-fetched
80
+ * every track.
84
81
  *
85
- * `deep: false` is load-bearing, not a micro-optimization. The default
86
- * enhancer wraps an object/array value in a MobX Proxy on `set`, and a
87
- * promoted default is handed straight back out by `getConf` — so an
88
- * object-valued promotable slot (alignments `colorBy`) put a Proxy into
89
- * `rpcProps()`, and V8's structured-clone serializer rejects a Proxy:
90
- * `worker.postMessage` threw `DataCloneError` on the next fetch of any
91
- * track following that default (electron IPC and `structuredClone` in the
92
- * share bake likewise). The map still notifies per key on `set`, so shallow
93
- * values lose no reactivity — and nothing can mutate a preference in place,
94
- * because `setPreferenceOverride` freezes what it stores.
82
+ * `deep: false` keeps an object-valued preference a plain object rather
83
+ * than a MobX Proxy, which V8's structured-clone serializer rejects. The
84
+ * map still notifies per key on `set`, so shallow values lose no
85
+ * reactivity — and nothing can mutate a preference in place, because
86
+ * `setPreferenceOverride` freezes what it stores.
95
87
  */
96
88
  preferencesOverrides: import("mobx").ObservableMap<string, unknown>;
97
89
  } & {
@@ -150,35 +142,12 @@ export declare function BaseSessionModel<ROOT_MODEL_TYPE extends BaseRootModelTy
150
142
  * The override map is empty unless the product loads it (web/desktop).
151
143
  */
152
144
  getPreference(key: string): unknown;
153
- /**
154
- * #method
155
- * resolved value of a per-display-type slot default the user promoted
156
- * (see `setDisplayTypeDefault`); undefined when nothing was promoted.
157
- */
158
- getDisplayTypeDefault(displayType: string, slot: string): unknown;
159
- /**
160
- * #method
161
- * every per-display-type default the user has promoted, as
162
- * `{ displayType, slot, value }` — the inventory the Preferences dialog
163
- * lists and clears one at a time (`setDisplayTypeDefault(…, undefined)`).
164
- *
165
- * Parsed here rather than by the dialog, because the composite-key
166
- * layout is this file's and nothing outside it should depend on it.
167
- */
168
- getDisplayTypeDefaults(): {
169
- displayType: string;
170
- slot: string;
171
- value: unknown;
172
- }[];
173
145
  /**
174
146
  * #method
175
147
  * every scalar preference override that currently differs from its
176
148
  * config/admin default, as `{ path, from, to }` rows whose path is the
177
149
  * override's own key. A scalar pref (animationMode, scrollZoom) whose
178
150
  * override equals the default is omitted, since reverting it is a no-op.
179
- * Promoted display-type defaults share the map but are not here: they
180
- * are `getDisplayTypeDefaults`, and the Preferences dialog lists them
181
- * through one builder on both of its surfaces.
182
151
  */
183
152
  getPreferenceChanges(): TrackConfigChange[];
184
153
  } & {
@@ -244,10 +213,9 @@ export declare function BaseSessionModel<ROOT_MODEL_TYPE extends BaseRootModelTy
244
213
  setPreferenceOverride(key: string, value: unknown): void;
245
214
  /**
246
215
  * #action
247
- * clear every runtime preference override at once — scrollZoom,
248
- * animationMode, and every promoted per-display-type default (see
249
- * `setDisplayTypeDefault`) — so each falls back to its config/admin
250
- * default. Backs the Preferences dialog "Reset to defaults" button.
216
+ * clear every runtime preference override at once, so each falls back to
217
+ * its config/admin default. Backs the Preferences dialog "Reset to
218
+ * defaults" button.
251
219
  */
252
220
  clearPreferenceOverrides(): void;
253
221
  /**
@@ -262,14 +230,6 @@ export declare function BaseSessionModel<ROOT_MODEL_TYPE extends BaseRootModelTy
262
230
  * set the global scroll-to-zoom preference (see the `scrollZoom` getter)
263
231
  */
264
232
  setScrollZoom(flag: boolean): void;
265
- /**
266
- * #action
267
- * promote (or, with `value` undefined, clear) a per-display-type slot
268
- * default. Just a preference override under one flat composite key (see
269
- * `displayTypeDefaultKey`), so it persists and independently tracks like
270
- * any other pref, and clearing deletes only that key.
271
- */
272
- setDisplayTypeDefault(displayType: string, slot: string, value: unknown): void;
273
233
  /**
274
234
  * #action
275
235
  */
@@ -10,35 +10,6 @@ import { applyDeveloperMode } from './developerMode.js';
10
10
  function isAnimationMode(val) {
11
11
  return val === 'system' || val === 'enabled' || val === 'disabled';
12
12
  }
13
- // Promoted per-display-type slot defaults live flat in `preferencesOverrides`
14
- // under one composite key each (`displayTypeDefault\0<type>\0<slot>`), not under
15
- // a single nested `displayTypeDefaults` object. Flat keys make each promoted
16
- // default its own tracked observable-map entry — promoting one can't invalidate
17
- // a reader of another (every promotable display's `rpcProps` reads one via
18
- // `getDisplayTypeDefault`) — and collapse the get/set/diff logic to one lookup.
19
- // The `\0` delimiter can't appear in a display type or slot name.
20
- // The literal is a **persisted localStorage key prefix**, so treat it as data
21
- // rather than as an identifier a rename should sweep — a blanket
22
- // `displayTypeDefault` → `pin` pass did sweep it, and orphans every promoted
23
- // default already in a browser's storage. Nothing released stores these yet, so
24
- // the reason it keeps this spelling is naming, not compatibility: the key names
25
- // *what is stored* (a display-type default, reached through
26
- // `get/setDisplayTypeDefault`), while `Pin` names the menu control the write
27
- // is offered from.
28
- const DISPLAY_TYPE_DEFAULT_PREFIX = 'displayTypeDefault\0';
29
- // Module-private on purpose: the composite-key spelling is a storage detail of
30
- // this file's get/set/diff methods, and nothing outside it should be able to
31
- // depend on the layout.
32
- function displayTypeDefaultKey(displayType, slot) {
33
- return `${DISPLAY_TYPE_DEFAULT_PREFIX}${displayType}\0${slot}`;
34
- }
35
- function parseDisplayTypeDefaultKey(key) {
36
- const rest = key.startsWith(DISPLAY_TYPE_DEFAULT_PREFIX)
37
- ? key.slice(DISPLAY_TYPE_DEFAULT_PREFIX.length)
38
- : '';
39
- const [displayType, slot] = rest.split('\0');
40
- return displayType && slot ? { displayType, slot } : undefined;
41
- }
42
13
  /**
43
14
  * #stateModel BaseSessionModel
44
15
  *
@@ -113,23 +84,15 @@ export function BaseSessionModel(_pluginManager) {
113
84
  *
114
85
  * An `observable.map` (not a plain object reassigned wholesale) so each
115
86
  * preference is its own tracked key: writing one (`setScrollZoom`) can't
116
- * invalidate a reader of another (`getDisplayTypeDefault` in a track's
117
- * `rpcProps`). A single spread-replaced object made every setter wake
118
- * every reader, so toggling scroll-to-zoom re-fetched every track. For the
119
- * same reason each promoted per-display-type default is a flat composite
120
- * key (see `displayTypeDefaultKey`), not a single nested `displayTypeDefaults`
121
- * object — promoting one default can't wake readers of a different one.
87
+ * invalidate a reader of another. A single spread-replaced object made
88
+ * every setter wake every reader, so toggling scroll-to-zoom re-fetched
89
+ * every track.
122
90
  *
123
- * `deep: false` is load-bearing, not a micro-optimization. The default
124
- * enhancer wraps an object/array value in a MobX Proxy on `set`, and a
125
- * promoted default is handed straight back out by `getConf` — so an
126
- * object-valued promotable slot (alignments `colorBy`) put a Proxy into
127
- * `rpcProps()`, and V8's structured-clone serializer rejects a Proxy:
128
- * `worker.postMessage` threw `DataCloneError` on the next fetch of any
129
- * track following that default (electron IPC and `structuredClone` in the
130
- * share bake likewise). The map still notifies per key on `set`, so shallow
131
- * values lose no reactivity — and nothing can mutate a preference in place,
132
- * because `setPreferenceOverride` freezes what it stores.
91
+ * `deep: false` keeps an object-valued preference a plain object rather
92
+ * than a MobX Proxy, which V8's structured-clone serializer rejects. The
93
+ * map still notifies per key on `set`, so shallow values lose no
94
+ * reactivity — and nothing can mutate a preference in place, because
95
+ * `setPreferenceOverride` freezes what it stores.
133
96
  */
134
97
  preferencesOverrides: observable.map(undefined, {
135
98
  deep: false,
@@ -219,55 +182,23 @@ export function BaseSessionModel(_pluginManager) {
219
182
  ? this.getPreferenceDefault(key)
220
183
  : override;
221
184
  },
222
- /**
223
- * #method
224
- * resolved value of a per-display-type slot default the user promoted
225
- * (see `setDisplayTypeDefault`); undefined when nothing was promoted.
226
- */
227
- getDisplayTypeDefault(displayType, slot) {
228
- return self.preferencesOverrides.get(displayTypeDefaultKey(displayType, slot));
229
- },
230
- /**
231
- * #method
232
- * every per-display-type default the user has promoted, as
233
- * `{ displayType, slot, value }` — the inventory the Preferences dialog
234
- * lists and clears one at a time (`setDisplayTypeDefault(…, undefined)`).
235
- *
236
- * Parsed here rather than by the dialog, because the composite-key
237
- * layout is this file's and nothing outside it should depend on it.
238
- */
239
- getDisplayTypeDefaults() {
240
- const found = [];
241
- for (const [key, value] of self.preferencesOverrides.entries()) {
242
- const parsed = parseDisplayTypeDefaultKey(key);
243
- if (parsed) {
244
- found.push({ ...parsed, value });
245
- }
246
- }
247
- return found;
248
- },
249
185
  /**
250
186
  * #method
251
187
  * every scalar preference override that currently differs from its
252
188
  * config/admin default, as `{ path, from, to }` rows whose path is the
253
189
  * override's own key. A scalar pref (animationMode, scrollZoom) whose
254
190
  * override equals the default is omitted, since reverting it is a no-op.
255
- * Promoted display-type defaults share the map but are not here: they
256
- * are `getDisplayTypeDefaults`, and the Preferences dialog lists them
257
- * through one builder on both of its surfaces.
258
191
  */
259
192
  getPreferenceChanges() {
260
193
  const changes = [];
261
194
  for (const [key, value] of self.preferencesOverrides.entries()) {
262
- if (!key.startsWith(DISPLAY_TYPE_DEFAULT_PREFIX)) {
263
- const dflt = this.getPreferenceDefault(key);
264
- if (value !== dflt) {
265
- changes.push({
266
- path: [key],
267
- from: dflt,
268
- to: value,
269
- });
270
- }
195
+ const dflt = this.getPreferenceDefault(key);
196
+ if (value !== dflt) {
197
+ changes.push({
198
+ path: [key],
199
+ from: dflt,
200
+ to: value,
201
+ });
271
202
  }
272
203
  }
273
204
  return changes;
@@ -364,19 +295,18 @@ export function BaseSessionModel(_pluginManager) {
364
295
  self.preferencesOverrides.delete(key);
365
296
  }
366
297
  else {
367
- // frozen because `deep: false` hands an object-valued preference (a
368
- // promoted `colorBy`) straight back out by reference to every display
369
- // following it, so the "nothing mutates a preference in place" this
370
- // store relies on is enforced rather than assumed
298
+ // frozen because `deep: false` hands an object-valued preference
299
+ // straight back out by reference, so the "nothing mutates a
300
+ // preference in place" this store relies on is enforced rather than
301
+ // assumed
371
302
  self.preferencesOverrides.set(key, freezeDeep(value));
372
303
  }
373
304
  },
374
305
  /**
375
306
  * #action
376
- * clear every runtime preference override at once — scrollZoom,
377
- * animationMode, and every promoted per-display-type default (see
378
- * `setDisplayTypeDefault`) — so each falls back to its config/admin
379
- * default. Backs the Preferences dialog "Reset to defaults" button.
307
+ * clear every runtime preference override at once, so each falls back to
308
+ * its config/admin default. Backs the Preferences dialog "Reset to
309
+ * defaults" button.
380
310
  */
381
311
  clearPreferenceOverrides() {
382
312
  self.preferencesOverrides.clear();
@@ -397,16 +327,6 @@ export function BaseSessionModel(_pluginManager) {
397
327
  setScrollZoom(flag) {
398
328
  this.setPreferenceOverride('scrollZoom', flag);
399
329
  },
400
- /**
401
- * #action
402
- * promote (or, with `value` undefined, clear) a per-display-type slot
403
- * default. Just a preference override under one flat composite key (see
404
- * `displayTypeDefaultKey`), so it persists and independently tracks like
405
- * any other pref, and clearing deletes only that key.
406
- */
407
- setDisplayTypeDefault(displayType, slot, value) {
408
- this.setPreferenceOverride(displayTypeDefaultKey(displayType, slot), value);
409
- },
410
330
  /**
411
331
  * #action
412
332
  */
@@ -1639,12 +1639,6 @@ export declare function MultipleViewsSessionMixin(pluginManager: PluginManager):
1639
1639
  } & {
1640
1640
  getPreferenceDefault(key: string): unknown;
1641
1641
  getPreference(key: string): unknown;
1642
- getDisplayTypeDefault(displayType: string, slot: string): unknown;
1643
- getDisplayTypeDefaults(): {
1644
- displayType: string;
1645
- slot: string;
1646
- value: unknown;
1647
- }[];
1648
1642
  getPreferenceChanges(): import("@jbrowse/core/util").TrackConfigChange[];
1649
1643
  } & {
1650
1644
  readonly animationMode: import("@jbrowse/core/util").AnimationMode;
@@ -1661,7 +1655,6 @@ export declare function MultipleViewsSessionMixin(pluginManager: PluginManager):
1661
1655
  clearPreferenceOverrides(): void;
1662
1656
  clearPreferenceOverride(key: string): void;
1663
1657
  setScrollZoom(flag: boolean): void;
1664
- setDisplayTypeDefault(displayType: string, slot: string, value: unknown): void;
1665
1658
  setName(str: string): void;
1666
1659
  setFocusedViewId(viewId: string | undefined): void;
1667
1660
  removeActiveDialog(): void;
@@ -1620,12 +1620,6 @@ export declare function PreferencesSessionMixin(pluginManager: PluginManager): i
1620
1620
  } & {
1621
1621
  getPreferenceDefault(key: string): unknown;
1622
1622
  getPreference(key: string): unknown;
1623
- getDisplayTypeDefault(displayType: string, slot: string): unknown;
1624
- getDisplayTypeDefaults(): {
1625
- displayType: string;
1626
- slot: string;
1627
- value: unknown;
1628
- }[];
1629
1623
  getPreferenceChanges(): import("@jbrowse/core/util").TrackConfigChange[];
1630
1624
  } & {
1631
1625
  readonly animationMode: import("@jbrowse/core/util").AnimationMode;
@@ -1642,7 +1636,6 @@ export declare function PreferencesSessionMixin(pluginManager: PluginManager): i
1642
1636
  clearPreferenceOverrides(): void;
1643
1637
  clearPreferenceOverride(key: string): void;
1644
1638
  setScrollZoom(flag: boolean): void;
1645
- setDisplayTypeDefault(displayType: string, slot: string, value: unknown): void;
1646
1639
  setName(str: string): void;
1647
1640
  setFocusedViewId(viewId: string | undefined): void;
1648
1641
  removeActiveDialog(): void;
@@ -5,6 +5,11 @@ import { autorun } from 'mobx';
5
5
  import { BaseSessionModel } from './BaseSession.js';
6
6
  import { applyDeveloperMode } from './developerMode.js';
7
7
  const PREFS_KEY = 'jbrowsePreferences';
8
+ // v5.0.0-beta.4 shipped session-wide display-type defaults, which persisted here
9
+ // under composite keys of this shape. They resolve to nothing now, and left in
10
+ // place they would show up as unnamed rows in the Preferences reset diff, so a
11
+ // beta user's stored ones are dropped on load.
12
+ const RETIRED_DISPLAY_TYPE_DEFAULT_PREFIX = 'displayTypeDefault\0';
8
13
  function loadStoredPreferences() {
9
14
  const stored = localStorageGetItem(PREFS_KEY);
10
15
  let result = {};
@@ -12,7 +17,7 @@ function loadStoredPreferences() {
12
17
  try {
13
18
  const parsed = JSON.parse(stored);
14
19
  if (typeof parsed === 'object' && parsed !== null) {
15
- result = parsed;
20
+ result = Object.fromEntries(Object.entries(parsed).filter(([key]) => !key.startsWith(RETIRED_DISPLAY_TYPE_DEFAULT_PREFIX)));
16
21
  }
17
22
  }
18
23
  catch {
@@ -35,8 +40,7 @@ export function PreferencesSessionMixin(pluginManager) {
35
40
  .actions(self => ({
36
41
  afterAttach() {
37
42
  // the restore path bypasses `setPreferenceOverride`, so it freezes here
38
- // too — a promoted default read back from localStorage is shared by
39
- // reference exactly like a freshly set one
43
+ // too
40
44
  self.preferencesOverrides.replace(freezeDeep(loadStoredPreferences()));
41
45
  // Applied once, here, rather than reactively: the same setting has to
42
46
  // hold in the RPC workers (which format tooltip strings from jexl
@@ -1649,12 +1649,6 @@ export declare function SessionTracksManagerSessionMixin(pluginManager: PluginMa
1649
1649
  } & {
1650
1650
  getPreferenceDefault(key: string): unknown;
1651
1651
  getPreference(key: string): unknown;
1652
- getDisplayTypeDefault(displayType: string, slot: string): unknown;
1653
- getDisplayTypeDefaults(): {
1654
- displayType: string;
1655
- slot: string;
1656
- value: unknown;
1657
- }[];
1658
1652
  getPreferenceChanges(): import("@jbrowse/core/util").TrackConfigChange[];
1659
1653
  } & {
1660
1654
  readonly animationMode: import("@jbrowse/core/util").AnimationMode;
@@ -1671,7 +1665,6 @@ export declare function SessionTracksManagerSessionMixin(pluginManager: PluginMa
1671
1665
  clearPreferenceOverrides(): void;
1672
1666
  clearPreferenceOverride(key: string): void;
1673
1667
  setScrollZoom(flag: boolean): void;
1674
- setDisplayTypeDefault(displayType: string, slot: string, value: unknown): void;
1675
1668
  setName(str: string): void;
1676
1669
  setFocusedViewId(viewId: string | undefined): void;
1677
1670
  removeActiveDialog(): void;
@@ -486,8 +486,8 @@ export function SessionTracksManagerSessionMixin(pluginManager) {
486
486
  // base sets diffs to nothing exactly as netting back to the base
487
487
  // does — and reverting the working copy then undid the edit ~400ms
488
488
  // after the user watched it land. The reachable case is a size
489
- // row's reset, whose whole job is to unset a slot, over a
490
- // promotable slot an admin `config.json` declares.
489
+ // row's reset, whose whole job is to unset a slot an admin
490
+ // `config.json` declares.
491
491
  //
492
492
  // The removal still doesn't survive a reload, which is the
493
493
  // tombstone-free delta's documented limitation; what it no longer
@@ -1617,12 +1617,6 @@ export declare function TracksManagerSessionMixin(pluginManager: PluginManager):
1617
1617
  } & {
1618
1618
  getPreferenceDefault(key: string): unknown;
1619
1619
  getPreference(key: string): unknown;
1620
- getDisplayTypeDefault(displayType: string, slot: string): unknown;
1621
- getDisplayTypeDefaults(): {
1622
- displayType: string;
1623
- slot: string;
1624
- value: unknown;
1625
- }[];
1626
1620
  getPreferenceChanges(): import("@jbrowse/core/util").TrackConfigChange[];
1627
1621
  } & {
1628
1622
  readonly animationMode: import("@jbrowse/core/util").AnimationMode;
@@ -1639,7 +1633,6 @@ export declare function TracksManagerSessionMixin(pluginManager: PluginManager):
1639
1633
  clearPreferenceOverrides(): void;
1640
1634
  clearPreferenceOverride(key: string): void;
1641
1635
  setScrollZoom(flag: boolean): void;
1642
- setDisplayTypeDefault(displayType: string, slot: string, value: unknown): void;
1643
1636
  setName(str: string): void;
1644
1637
  setFocusedViewId(viewId: string | undefined): void;
1645
1638
  removeActiveDialog(): void;
@@ -15,7 +15,7 @@ export { BaseSessionModel, isBaseSession, isSession } from './BaseSession.ts';
15
15
  export type { BaseSession, BaseSessionType } from './BaseSession.ts';
16
16
  export { SessionTracksManagerSessionMixin } from './SessionTracks.ts';
17
17
  export type { EditableTrackConfig, PlainTrackConfig, SessionWithSessionTracks, SessionWithSessionTracksType, } from './SessionTracks.ts';
18
- export { bakePromotedDefaultsIntoSnapshot, bakeSessionCascades, getShareableSessionSnapshot, } from './shareableSnapshot.ts';
18
+ export { bakeSessionCascades, getShareableSessionSnapshot, } from './shareableSnapshot.ts';
19
19
  export { TrackMenuItemsSessionMixin, aboutTrackMenuItem, copyTrackSnapshot, pluginExtraTrackItems, trackActionItems, trackActionMenuItems, trackListMenuItems, } from './TrackMenu.ts';
20
20
  export { TrackMenuSessionMixin } from './TrackMenuSessionMixin.ts';
21
21
  export { finalizeSession } from './finalizeSession.ts';
@@ -7,7 +7,7 @@ export { MultipleViewsSessionMixin, isSessionWithMultipleViews, } from './Multip
7
7
  export { PreferencesSessionMixin } from './Preferences.js';
8
8
  export { BaseSessionModel, isBaseSession, isSession } from './BaseSession.js';
9
9
  export { SessionTracksManagerSessionMixin } from './SessionTracks.js';
10
- export { bakePromotedDefaultsIntoSnapshot, bakeSessionCascades, getShareableSessionSnapshot, } from './shareableSnapshot.js';
10
+ export { bakeSessionCascades, getShareableSessionSnapshot, } from './shareableSnapshot.js';
11
11
  export { TrackMenuItemsSessionMixin, aboutTrackMenuItem, copyTrackSnapshot, pluginExtraTrackItems, trackActionItems, trackActionMenuItems, trackListMenuItems, } from './TrackMenu.js';
12
12
  export { TrackMenuSessionMixin } from './TrackMenuSessionMixin.js';
13
13
  export { finalizeSession } from './finalizeSession.js';
@@ -12,66 +12,12 @@ import type { AbstractSessionModel } from '@jbrowse/core/util';
12
12
  export declare function getShareableSessionSnapshot(session: AbstractSessionModel): Record<string, unknown>;
13
13
  /**
14
14
  * Everything an outgoing snapshot has to resolve because the live session
15
- * resolves it at read time against state that is staying behind.
15
+ * resolves it at read time against state that is staying behind — currently the
16
+ * workspaces intent alone.
16
17
  *
17
- * Two such cascades, and they have nothing to do with each other — promoted
18
- * display-type defaults are per-track rendering, workspaces intent is the
19
- * session's layout mode. What they share is only this: both live partly in the
20
- * sender's own browser, so a raw `getSnapshot` records neither, and the
21
- * recipient silently resolves their own.
22
- *
23
- * They get one entry point rather than a call each because the failure of a
24
- * boundary that performs half of them is invisible at that boundary — the
25
- * export succeeds, and what is missing only shows up on someone else's screen.
26
- * Adding a third cascade should mean editing this function, not auditing four
27
- * call sites.
18
+ * It keeps its own entry point rather than being inlined into the one caller
19
+ * because the failure of a boundary that performs half of them is invisible at
20
+ * that boundary: the export succeeds, and what is missing only shows up on
21
+ * someone else's screen.
28
22
  */
29
23
  export declare function bakeSessionCascades(session: AbstractSessionModel, snapshot: Record<string, unknown>): Record<string, unknown>;
30
- /**
31
- * `getComputedStyle` for the promotable-default cascade.
32
- *
33
- * A track that "follows" a session-wide promoted display-type default holds no
34
- * value of its own — its rendered value is resolved at read time from the
35
- * promoted display-type defaults in `preferencesOverrides`, which are personal,
36
- * localStorage-backed, and deliberately never serialized into a shared session.
37
- * So a raw `getSnapshot(session)` records a track as at-default even when the
38
- * sender is looking at a promoted value, and the recipient (who lacks the sender's
39
- * preferences) sees something different. This flattens the live cascade into a
40
- * self-contained snapshot:
41
- *
42
- * Every slot an open display *inherits* from a promoted default (i.e.
43
- * `getDisplayTypeDefaultChanges` — non-customized, differs from base) is baked
44
- * into that track's config layer: its own config for a user-added or connection
45
- * track, else a `trackConfigDeltas` entry against the admin base (see
46
- * `ownTrackConfig`). So the concrete value travels with the document, and
47
- * because it lands in the track's config the recipient reads it as *customized*
48
- * — a customized value is the top of the cascade, so their own promoted defaults
49
- * never get consulted for it.
50
- *
51
- * **What this deliberately does not cover**, and the reason there is no flag
52
- * here: a slot the sender was viewing at its *base* value. Nothing is baked for
53
- * it (the value equals base, and `stripDefault` drops it from the snapshot
54
- * regardless), so a recipient who has promoted something else resolves it from
55
- * their own cascade and sees their value. There is no value that can express "I
56
- * deliberately saw the default" — at-base and unset are byte-identical once
57
- * stripped. That case used to be covered by stamping a per-display
58
- * `ignorePromotedDefaults` on every open display, which cost a second
59
- * shape-aware walk that had to stay in step with `openPromotableDisplays` by
60
- * hand, and permanently detached received tracks from the recipient's own pins
61
- * until they clicked "use this default" on each one. A promoted default is
62
- * personal and local — the same as the theme a session is viewed in, which this
63
- * function has never baked either.
64
- *
65
- * The live session is untouched; a modified deep copy of `snapshot` is returned.
66
- * Tracks the sender never opened carry no display state to resolve, so they're
67
- * left to pick up the recipient's defaults when opened — matching "export the
68
- * actual state of the (open) tracks".
69
- *
70
- * Reach is `openPromotableDisplays` — literally the same walk the cascade's own
71
- * "apply to open tracks" uses, so the two can't drift. It recurses into a
72
- * composite view's `views` array (breakpoint-split, the linear-comparative /
73
- * synteny family), which holds child views rather than tracks of its own —
74
- * `LGVSyntenyDisplay` is only reachable that way, and before the recursion a
75
- * shared session containing one rendered differently for the recipient.
76
- */
77
- export declare function bakePromotedDefaultsIntoSnapshot(session: AbstractSessionModel, snapshot: Record<string, unknown>): Record<string, unknown>;
@@ -1,7 +1,4 @@
1
- import { getConf, getDisplayTypeDefaultChanges, } from '@jbrowse/core/configuration';
2
- import { getContainingTrack, mergeTrackConfig, openPromotableDisplays, } from '@jbrowse/core/util';
3
1
  import { getSnapshot } from '@jbrowse/mobx-state-tree';
4
- import { asArray, isRecord } from '../snapshotUtils.js';
5
2
  /**
6
3
  * The session snapshot to hand to anyone else — a share link, an exported
7
4
  * `session.json`, a desktop→web export. Snapshotting and baking are one call so
@@ -17,22 +14,16 @@ export function getShareableSessionSnapshot(session) {
17
14
  }
18
15
  /**
19
16
  * Everything an outgoing snapshot has to resolve because the live session
20
- * resolves it at read time against state that is staying behind.
17
+ * resolves it at read time against state that is staying behind — currently the
18
+ * workspaces intent alone.
21
19
  *
22
- * Two such cascades, and they have nothing to do with each other — promoted
23
- * display-type defaults are per-track rendering, workspaces intent is the
24
- * session's layout mode. What they share is only this: both live partly in the
25
- * sender's own browser, so a raw `getSnapshot` records neither, and the
26
- * recipient silently resolves their own.
27
- *
28
- * They get one entry point rather than a call each because the failure of a
29
- * boundary that performs half of them is invisible at that boundary — the
30
- * export succeeds, and what is missing only shows up on someone else's screen.
31
- * Adding a third cascade should mean editing this function, not auditing four
32
- * call sites.
20
+ * It keeps its own entry point rather than being inlined into the one caller
21
+ * because the failure of a boundary that performs half of them is invisible at
22
+ * that boundary: the export succeeds, and what is missing only shows up on
23
+ * someone else's screen.
33
24
  */
34
25
  export function bakeSessionCascades(session, snapshot) {
35
- const snap = bakePromotedDefaultsIntoSnapshot(session, snapshot);
26
+ const snap = structuredClone(snapshot);
36
27
  bakeWorkspacesIntent(session, snap);
37
28
  return snap;
38
29
  }
@@ -49,8 +40,7 @@ export function bakeSessionCascades(session, snapshot) {
49
40
  *
50
41
  * Only the on case is baked. At `false` there is no arrangement to carry, so
51
42
  * stamping it would be a sender with nothing to say overriding a recipient who
52
- * prefers workspaces — the same line the promoted-default bake draws at a slot
53
- * the sender was viewing at its base value.
43
+ * prefers workspaces.
54
44
  *
55
45
  * The paths that build a layout through an explicit user intent already set the
56
46
  * property themselves (`setUseWorkspacesPreference`, ViewMenu's move-view-out,
@@ -74,123 +64,3 @@ function bakeWorkspacesIntent(session, snap) {
74
64
  snap.useWorkspaces = true;
75
65
  }
76
66
  }
77
- /**
78
- * `getComputedStyle` for the promotable-default cascade.
79
- *
80
- * A track that "follows" a session-wide promoted display-type default holds no
81
- * value of its own — its rendered value is resolved at read time from the
82
- * promoted display-type defaults in `preferencesOverrides`, which are personal,
83
- * localStorage-backed, and deliberately never serialized into a shared session.
84
- * So a raw `getSnapshot(session)` records a track as at-default even when the
85
- * sender is looking at a promoted value, and the recipient (who lacks the sender's
86
- * preferences) sees something different. This flattens the live cascade into a
87
- * self-contained snapshot:
88
- *
89
- * Every slot an open display *inherits* from a promoted default (i.e.
90
- * `getDisplayTypeDefaultChanges` — non-customized, differs from base) is baked
91
- * into that track's config layer: its own config for a user-added or connection
92
- * track, else a `trackConfigDeltas` entry against the admin base (see
93
- * `ownTrackConfig`). So the concrete value travels with the document, and
94
- * because it lands in the track's config the recipient reads it as *customized*
95
- * — a customized value is the top of the cascade, so their own promoted defaults
96
- * never get consulted for it.
97
- *
98
- * **What this deliberately does not cover**, and the reason there is no flag
99
- * here: a slot the sender was viewing at its *base* value. Nothing is baked for
100
- * it (the value equals base, and `stripDefault` drops it from the snapshot
101
- * regardless), so a recipient who has promoted something else resolves it from
102
- * their own cascade and sees their value. There is no value that can express "I
103
- * deliberately saw the default" — at-base and unset are byte-identical once
104
- * stripped. That case used to be covered by stamping a per-display
105
- * `ignorePromotedDefaults` on every open display, which cost a second
106
- * shape-aware walk that had to stay in step with `openPromotableDisplays` by
107
- * hand, and permanently detached received tracks from the recipient's own pins
108
- * until they clicked "use this default" on each one. A promoted default is
109
- * personal and local — the same as the theme a session is viewed in, which this
110
- * function has never baked either.
111
- *
112
- * The live session is untouched; a modified deep copy of `snapshot` is returned.
113
- * Tracks the sender never opened carry no display state to resolve, so they're
114
- * left to pick up the recipient's defaults when opened — matching "export the
115
- * actual state of the (open) tracks".
116
- *
117
- * Reach is `openPromotableDisplays` — literally the same walk the cascade's own
118
- * "apply to open tracks" uses, so the two can't drift. It recurses into a
119
- * composite view's `views` array (breakpoint-split, the linear-comparative /
120
- * synteny family), which holds child views rather than tracks of its own —
121
- * `LGVSyntenyDisplay` is only reachable that way, and before the recursion a
122
- * shared session containing one rendered differently for the recipient.
123
- */
124
- export function bakePromotedDefaultsIntoSnapshot(session, snapshot) {
125
- const snap = structuredClone(snapshot);
126
- // `openPromotableDisplays` is the one walk that decides reach — shared with
127
- // the cascade's own "apply to open tracks", so the set this bakes and the set
128
- // that acts on a promoted default can't drift
129
- for (const display of openPromotableDisplays(session)) {
130
- const changes = getDisplayTypeDefaultChanges(display);
131
- if (changes.length > 0) {
132
- bakeValues(snap, {
133
- trackId: getConf(getContainingTrack(display), 'trackId'),
134
- displayId: getConf(display, 'displayId'),
135
- displayType: display.type,
136
- values: Object.fromEntries(changes.map(c => [c.path[0], c.to])),
137
- });
138
- }
139
- }
140
- return snap;
141
- }
142
- // The track's own full config in the snapshot, when it has one: a user-added
143
- // track's `sessionTracks` entry, or an opened connection track's persisted
144
- // `connectionTrackConfigs` config. Everything else is an admin-base track,
145
- // which carries only a delta.
146
- //
147
- // The connection case is not optional. `trackConfigDeltas` is merged over
148
- // `jbrowse.tracks` alone, so a delta written for a connection track resolves
149
- // nowhere — the recipient would render the base value, which is the exact
150
- // failure the bake exists to prevent. `updateTrackConfiguration` splits on the
151
- // same line.
152
- function ownTrackConfig(snap, trackId) {
153
- const sessionTrack = asArray(snap.sessionTracks).find(t => isRecord(t) && t.trackId === trackId);
154
- const connectionEntry = isRecord(snap.connectionTrackConfigs)
155
- ? snap.connectionTrackConfigs[trackId]
156
- : undefined;
157
- return isRecord(sessionTrack)
158
- ? sessionTrack
159
- : isRecord(connectionEntry) && isRecord(connectionEntry.config)
160
- ? connectionEntry.config
161
- : undefined;
162
- }
163
- // Bake one display's inherited values into the config layer: into the matching
164
- // display of the track's own config when it has one, else merged as a
165
- // `trackConfigDeltas` entry against the admin base.
166
- function bakeValues(snap, bake) {
167
- const partialDisplay = {
168
- type: bake.displayType,
169
- displayId: bake.displayId,
170
- ...bake.values,
171
- };
172
- const ownConfig = ownTrackConfig(snap, bake.trackId);
173
- if (ownConfig) {
174
- const displays = asArray(ownConfig.displays);
175
- const target = displays.find(d => isRecord(d) && d.displayId === bake.displayId);
176
- if (isRecord(target)) {
177
- Object.assign(target, bake.values);
178
- }
179
- else {
180
- ownConfig.displays = [...displays, partialDisplay];
181
- }
182
- }
183
- else {
184
- const deltas = isRecord(snap.trackConfigDeltas)
185
- ? { ...snap.trackConfigDeltas }
186
- : {};
187
- const existing = isRecord(deltas[bake.trackId])
188
- ? deltas[bake.trackId]
189
- : { trackId: bake.trackId };
190
- deltas[bake.trackId] = mergeTrackConfig(existing, {
191
- trackId: bake.trackId,
192
- displays: [partialDisplay],
193
- });
194
- snap.trackConfigDeltas = deltas;
195
- }
196
- }
package/esm/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export { asRoot, asSession } from './siblingCast.ts';
3
3
  export { scheduleDetachedDestroy } from '@jbrowse/core/util';
4
4
  export { BaseRootModelFactory, FormatAboutConfigSchemaFactory, FormatDetailsConfigSchemaFactory, HierarchicalConfigSchemaFactory, InternetAccountsRootModelMixin, PreferencesConfigSchemaFactory, createConfigModel, exportSessionMenuItem, importSessionMenuItem, isRootModel, newSessionMenuItem, openConnectionMenuItem, openTrackMenuItem, pluginStoreMenuItem, preferencesMenuItem, redoMenuItem, undoMenuItem, workspacesMenuItem, } from './RootModel/index.ts';
5
5
  export type { BaseRootModel, BaseRootModelType, RootConfigurationSnapshot, RootModelWithInternetAccounts, RootModelWithInternetAccountsType, } from './RootModel/index.ts';
6
- export { BaseSessionModel, ConnectionManagementSessionMixin, DrawerWidgetSessionMixin, MultipleViewsSessionMixin, PreferencesSessionMixin, ReferenceManagementSessionMixin, SessionTracksManagerSessionMixin, ThemeManagerSessionMixin, TrackMenuItemsSessionMixin, TrackMenuSessionMixin, TracksManagerSessionMixin, aboutTrackMenuItem, bakePromotedDefaultsIntoSnapshot, bakeSessionCascades, getShareableSessionSnapshot, copyTrackSnapshot, finalizeSession, isBaseSession, isSession, isSessionWithConnections, isSessionWithDrawerWidgets, isSessionWithMultipleViews, isSessionWithReferenceManagement, isSessionWithThemes, isSessionWithTracks, pluginExtraTrackItems, trackActionItems, trackActionMenuItems, trackListMenuItems, } from './Session/index.ts';
6
+ export { BaseSessionModel, ConnectionManagementSessionMixin, DrawerWidgetSessionMixin, MultipleViewsSessionMixin, PreferencesSessionMixin, ReferenceManagementSessionMixin, SessionTracksManagerSessionMixin, ThemeManagerSessionMixin, TrackMenuItemsSessionMixin, TrackMenuSessionMixin, TracksManagerSessionMixin, aboutTrackMenuItem, bakeSessionCascades, getShareableSessionSnapshot, copyTrackSnapshot, finalizeSession, isBaseSession, isSession, isSessionWithConnections, isSessionWithDrawerWidgets, isSessionWithMultipleViews, isSessionWithReferenceManagement, isSessionWithThemes, isSessionWithTracks, pluginExtraTrackItems, trackActionItems, trackActionMenuItems, trackListMenuItems, } from './Session/index.ts';
7
7
  export type { AssertSessionModel, BaseSession, BaseSessionType, ConnectionTrackConfigEntry, EditableTrackConfig, PlainTrackConfig, ReferringNode, SessionWithConnections, SessionWithConnectionsType, SessionWithDrawerWidgets, SessionWithDrawerWidgetsType, SessionWithMultipleViews, SessionWithMultipleViewsType, SessionWithReferenceManagement, SessionWithReferenceManagementType, SessionWithSessionTracks, SessionWithSessionTracksType, SessionWithThemes, SessionWithThemesType, SessionWithTracks, SessionWithTracksType, } from './Session/index.ts';
8
8
  export { AboutDialog, Drawer, DrawerControls, DrawerHeader, DrawerWidget, DrawerWidgetSelector, ModalWidget, ModalWidgetAppBar, PreferencesDialog, WidgetBody, WidgetHeading, drawerGridTemplateColumns, } from './ui/index.ts';
9
9
  export type { PreferencesDialogSession, PreferencesPanelDescriptor, } from './ui/index.ts';
package/esm/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export { asRoot, asSession } from './siblingCast.js';
2
2
  export { scheduleDetachedDestroy } from '@jbrowse/core/util';
3
3
  export { BaseRootModelFactory, FormatAboutConfigSchemaFactory, FormatDetailsConfigSchemaFactory, HierarchicalConfigSchemaFactory, InternetAccountsRootModelMixin, PreferencesConfigSchemaFactory, createConfigModel, exportSessionMenuItem, importSessionMenuItem, isRootModel, newSessionMenuItem, openConnectionMenuItem, openTrackMenuItem, pluginStoreMenuItem, preferencesMenuItem, redoMenuItem, undoMenuItem, workspacesMenuItem, } from './RootModel/index.js';
4
- export { BaseSessionModel, ConnectionManagementSessionMixin, DrawerWidgetSessionMixin, MultipleViewsSessionMixin, PreferencesSessionMixin, ReferenceManagementSessionMixin, SessionTracksManagerSessionMixin, ThemeManagerSessionMixin, TrackMenuItemsSessionMixin, TrackMenuSessionMixin, TracksManagerSessionMixin, aboutTrackMenuItem, bakePromotedDefaultsIntoSnapshot, bakeSessionCascades, getShareableSessionSnapshot, copyTrackSnapshot, finalizeSession, isBaseSession, isSession, isSessionWithConnections, isSessionWithDrawerWidgets, isSessionWithMultipleViews, isSessionWithReferenceManagement, isSessionWithThemes, isSessionWithTracks, pluginExtraTrackItems, trackActionItems, trackActionMenuItems, trackListMenuItems, } from './Session/index.js';
4
+ export { BaseSessionModel, ConnectionManagementSessionMixin, DrawerWidgetSessionMixin, MultipleViewsSessionMixin, PreferencesSessionMixin, ReferenceManagementSessionMixin, SessionTracksManagerSessionMixin, ThemeManagerSessionMixin, TrackMenuItemsSessionMixin, TrackMenuSessionMixin, TracksManagerSessionMixin, aboutTrackMenuItem, bakeSessionCascades, getShareableSessionSnapshot, copyTrackSnapshot, finalizeSession, isBaseSession, isSession, isSessionWithConnections, isSessionWithDrawerWidgets, isSessionWithMultipleViews, isSessionWithReferenceManagement, isSessionWithThemes, isSessionWithTracks, pluginExtraTrackItems, trackActionItems, trackActionMenuItems, trackListMenuItems, } from './Session/index.js';
5
5
  export { AboutDialog, Drawer, DrawerControls, DrawerHeader, DrawerWidget, DrawerWidgetSelector, ModalWidget, ModalWidgetAppBar, PreferencesDialog, WidgetBody, WidgetHeading, drawerGridTemplateColumns, } from './ui/index.js';
6
6
  export { initializeWorker } from './rpcWorker.js';
7
7
  export { toPluginLoadRecord } from './pluginInput.js';
@@ -43,10 +43,9 @@ export interface SessionObservers {
43
43
  * uncompressed twin of `encodeSession`, for hosts that move JSON rather than
44
44
  * URLs — a notebook kernel, an R session, a "save this layout" button.
45
45
  *
46
- * Not a plain `getSnapshot`, and for the same reason `encodeSession` isn't:
47
- * display settings a user is *inheriting* from a promoted display-type default
48
- * live in their own browser, never in the session, so a raw snapshot replays
49
- * differently for whoever opens it.
46
+ * Not a plain `getSnapshot`, and for the same reason `encodeSession` isn't: a
47
+ * workspace arrangement turned on by the sender's preference is not in the
48
+ * snapshot, so a raw one replays as a classic stack for whoever opens it.
50
49
  */
51
50
  export declare function getSessionSnapshot(session: ObservedSession): SessionSnapshot;
52
51
  /**
@@ -7,10 +7,9 @@ import { getShareableSessionSnapshot } from './Session/shareableSnapshot.js';
7
7
  * uncompressed twin of `encodeSession`, for hosts that move JSON rather than
8
8
  * URLs — a notebook kernel, an R session, a "save this layout" button.
9
9
  *
10
- * Not a plain `getSnapshot`, and for the same reason `encodeSession` isn't:
11
- * display settings a user is *inheriting* from a promoted display-type default
12
- * live in their own browser, never in the session, so a raw snapshot replays
13
- * differently for whoever opens it.
10
+ * Not a plain `getSnapshot`, and for the same reason `encodeSession` isn't: a
11
+ * workspace arrangement turned on by the sender's preference is not in the
12
+ * snapshot, so a raw one replays as a classic stack for whoever opens it.
14
13
  */
15
14
  export function getSessionSnapshot(session) {
16
15
  // `name` is restated rather than the whole thing cast: the bake returns an
@@ -136,9 +136,9 @@ function isObject(v) {
136
136
  // The standalone `methylation` colorBy scheme was removed; it is now the
137
137
  // `modifications` scheme with `fillUnmarked` set. It is NOT rewritten here: the
138
138
  // alignments model normalizes it at read time (`normalizeColorBy`, applied in
139
- // its `colorBy` getter), which covers every persistence path — instance slot,
140
- // session-wide promoted default, and config-file display default — so session
141
- // migration deliberately leaves a `methylation` colorBy untouched.
139
+ // its `colorBy` getter), which covers every persistence path — instance slot and
140
+ // config-file display default alike — so session migration deliberately leaves a
141
+ // `methylation` colorBy untouched.
142
142
  function migrateDisplayType(display, trackConfigId, collected) {
143
143
  const entry = displayTypeMap[display.type];
144
144
  if (!entry) {
@@ -12,10 +12,9 @@ export interface SessionSnapshot {
12
12
  * hash fragment. Deflated then base64url-encoded, so it survives a URL intact
13
13
  * and stays far smaller than raw JSON.
14
14
  *
15
- * Not a plain `getSnapshot`: display settings a user is *inheriting* from a
16
- * promoted display-type default live in their own browser, never in the
17
- * session, so a raw snapshot renders differently for whoever opens the link.
18
- * `getShareableSessionSnapshot` flattens that cascade into the snapshot first.
15
+ * Not a plain `getSnapshot`: a workspace arrangement turned on by the sender's
16
+ * preference is not in the snapshot, so a raw one renders as a classic stack
17
+ * for whoever opens the link. `getShareableSessionSnapshot` stamps it first.
19
18
  *
20
19
  * The session travels but the config does not — the receiving app supplies its
21
20
  * own assembly/tracks. Put the result somewhere the server never sees (the hash
package/esm/sessionUrl.js CHANGED
@@ -5,10 +5,9 @@ import { getShareableSessionSnapshot } from './Session/index.js';
5
5
  * hash fragment. Deflated then base64url-encoded, so it survives a URL intact
6
6
  * and stays far smaller than raw JSON.
7
7
  *
8
- * Not a plain `getSnapshot`: display settings a user is *inheriting* from a
9
- * promoted display-type default live in their own browser, never in the
10
- * session, so a raw snapshot renders differently for whoever opens the link.
11
- * `getShareableSessionSnapshot` flattens that cascade into the snapshot first.
8
+ * Not a plain `getSnapshot`: a workspace arrangement turned on by the sender's
9
+ * preference is not in the snapshot, so a raw one renders as a classic stack
10
+ * for whoever opens the link. `getShareableSessionSnapshot` stamps it first.
12
11
  *
13
12
  * The session travels but the config does not — the receiving app supplies its
14
13
  * own assembly/tracks. Put the result somewhere the server never sees (the hash
@@ -2,11 +2,11 @@ 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 { getTrackConfigWithPromotables, hydrateTrackConfig, } from '@jbrowse/core/configuration';
5
+ import { hydrateTrackConfig } from '@jbrowse/core/configuration';
6
6
  import PluggableComponents from '@jbrowse/core/ui/PluggableComponents';
7
7
  import { getEnv } from '@jbrowse/core/util';
8
8
  import { makeStyles } from '@jbrowse/core/util/tss-react';
9
- import { isStateTreeNode } from '@jbrowse/mobx-state-tree';
9
+ import { getSnapshot, isStateTreeNode } from '@jbrowse/mobx-state-tree';
10
10
  import { observer } from 'mobx-react';
11
11
  import AssemblyInfoPanel from './AssemblyInfoPanel.js';
12
12
  import DescriptionPanel from './DescriptionPanel.js';
@@ -24,33 +24,25 @@ const AboutDialogContents = observer(function AboutDialogContents({ config, sess
24
24
  const { classes } = useStyles();
25
25
  const [showRefNames, setShowRefNames] = useState(false);
26
26
  const { pluginManager } = getEnv(session);
27
- // "Copy config" output leaves the cascade for good (a user pastes it into a
28
- // config.json), so promotable slots are resolved rather than left stripped —
29
- // otherwise the copied config renders differently from the track it came from.
30
- // `fromDisplayTypeDefaults` names what that folded in, so materializing a
31
- // session-wide preference into a track config isn't silent.
32
- //
33
27
  // The two menus that open this dialog hand over different things: the in-view
34
28
  // track label passes `track.configuration`, a live node, while the
35
29
  // hierarchical selector passes a `session.tracks` entry, which is a
36
- // `types.frozen` plain object until something references the track. Resolving
37
- // only the first meant the same track copied a different config depending on
38
- // which menu you came from — and the selector is the one you can reach
39
- // without opening the track at all. Hydrating converges them; it returns
40
- // undefined for a config no plugin can build, and that falls back to copying
41
- // it as authored.
30
+ // `types.frozen` plain object until something references the track. Hydrating
31
+ // converges them, so "Copy config" gives the same JSON whichever menu you came
32
+ // from; it returns undefined for a config no plugin can build, and that falls
33
+ // back to copying it as authored.
42
34
  const live = isStateTreeNode(config)
43
35
  ? config
44
36
  : hydrateTrackConfig(pluginManager, config);
45
- const { config: conf, fromDisplayTypeDefaults } = live
46
- ? getTrackConfigWithPromotables(session, live)
47
- : { config, fromDisplayTypeDefaults: [] };
37
+ const conf = live
38
+ ? getSnapshot(live)
39
+ : config;
48
40
  const { config: shown, hideUris } = getAboutDialogConfig({
49
41
  config,
50
42
  session,
51
43
  pluginManager,
52
44
  });
53
- 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(DescriptionPanel, { config: config, session: session }), _jsx(AssemblyInfoPanel, { config: config, session: session, hideUris: hideUris }), _jsx(PluggableComponents, { pluginManager: pluginManager,
45
+ return (_jsxs("div", { className: classes.content, children: [_jsxs(BaseCard, { title: "Configuration", children: [_jsx(HeaderButtons, { conf: conf, hideUris: hideUris, 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(DescriptionPanel, { config: config, session: session }), _jsx(AssemblyInfoPanel, { config: config, session: session, hideUris: hideUris }), _jsx(PluggableComponents, { pluginManager: pluginManager,
54
46
  /** #extensionPoint Core-extraAboutPanel | sync | Add extra panels to a track's About dialog */
55
47
  name: "Core-extraAboutPanel", props: { session, config } }), hideUris ? null : _jsx(FileInfoPanel, { config: config, session: session }), showRefNames ? (_jsx(RefNameInfoDialog, { session: session, config: config, onClose: () => {
56
48
  setShowRefNames(false);
@@ -1,12 +1,7 @@
1
1
  interface HeaderButtonsProps {
2
2
  conf: Record<string, unknown>;
3
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[];
9
4
  setShowRefNames: (show: boolean) => void;
10
5
  }
11
- declare function HeaderButtons({ conf, hideUris, fromDisplayTypeDefaults, setShowRefNames, }: HeaderButtonsProps): import("react").JSX.Element;
6
+ declare function HeaderButtons({ conf, hideUris, setShowRefNames, }: HeaderButtonsProps): import("react").JSX.Element;
12
7
  export default HeaderButtons;
@@ -1,9 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { CopyToClipboardButton } from '@jbrowse/core/ui';
3
- import { pluralize } from '@jbrowse/core/util';
4
3
  import { stripBaseUris } from '@jbrowse/core/util/addRelativeUris';
5
4
  import { makeStyles } from '@jbrowse/core/util/tss-react';
6
- import { Button, Tooltip, Typography } from '@mui/material';
5
+ import { Button } from '@mui/material';
7
6
  const useStyles = makeStyles()(theme => ({
8
7
  button: {
9
8
  float: 'right',
@@ -15,17 +14,11 @@ const useStyles = makeStyles()(theme => ({
15
14
  gap: theme.spacing(1),
16
15
  justifyContent: 'flex-end',
17
16
  },
18
- note: {
19
- clear: 'both',
20
- display: 'block',
21
- textAlign: 'right',
22
- },
23
17
  }));
24
- function HeaderButtons({ conf, hideUris, fromDisplayTypeDefaults = [], setShowRefNames, }) {
18
+ function HeaderButtons({ conf, hideUris, setShowRefNames, }) {
25
19
  const { classes } = useStyles();
26
- const n = fromDisplayTypeDefaults.length;
27
- return (_jsxs("span", { className: classes.button, children: [_jsxs("span", { className: classes.buttons, children: [_jsx(Button, { variant: "contained", color: "secondary", onClick: () => {
28
- setShowRefNames(true);
29
- }, children: "Show ref names" }), hideUris ? null : (_jsx(CopyToClipboardButton, { variant: "contained", value: () => JSON.stringify(stripBaseUris(structuredClone(conf)), null, 2), children: "Copy config" }))] }), hideUris || !n ? null : (_jsx(Tooltip, { title: fromDisplayTypeDefaults.join(', '), children: _jsxs(Typography, { variant: "caption", color: "textSecondary", className: classes.note, children: ["includes ", n, " ", pluralize(n, 'setting'), " from your session-wide defaults"] }) }))] }));
20
+ return (_jsx("span", { className: classes.button, children: _jsxs("span", { className: classes.buttons, children: [_jsx(Button, { variant: "contained", color: "secondary", onClick: () => {
21
+ setShowRefNames(true);
22
+ }, children: "Show ref names" }), hideUris ? null : (_jsx(CopyToClipboardButton, { variant: "contained", value: () => JSON.stringify(stripBaseUris(structuredClone(conf)), null, 2), children: "Copy config" }))] }) }));
30
23
  }
31
24
  export default HeaderButtons;
@@ -1,10 +1,9 @@
1
- import type { DisplayDefaultsSession } from './DisplayDefaultsSection.tsx';
2
1
  import type { GeneralPreferencesSession } from './GeneralPreferencesTab.tsx';
3
2
  import type { ViewPreferencesSession } from './ViewPreferencesTab.tsx';
4
3
  import type { ResettablePreferencesSession } from './preferencesReset.ts';
5
4
  import type PluginManager from '@jbrowse/core/PluginManager';
6
5
  import type React from 'react';
7
- export interface PreferencesDialogSession extends GeneralPreferencesSession, ViewPreferencesSession, DisplayDefaultsSession, ResettablePreferencesSession {
6
+ export interface PreferencesDialogSession extends GeneralPreferencesSession, ViewPreferencesSession, ResettablePreferencesSession {
8
7
  }
9
8
  /**
10
9
  * Descriptor returned from the `Core-preferencesDialogPanels` extension point.
@@ -4,7 +4,6 @@ import { Dialog } from '@jbrowse/core/ui';
4
4
  import { makeStyles } from '@jbrowse/core/util/tss-react';
5
5
  import { Button, DialogActions, DialogContent, Tab, Tabs } from '@mui/material';
6
6
  import { observer } from 'mobx-react';
7
- import DisplayDefaultsSection from './DisplayDefaultsSection.js';
8
7
  import GeneralPreferencesTab from './GeneralPreferencesTab.js';
9
8
  import PreferencesResetDialog from './PreferencesResetDialog.js';
10
9
  import ViewPreferencesTab from './ViewPreferencesTab.js';
@@ -22,7 +21,6 @@ const PreferencesDialog = observer(function PreferencesDialog({ handleClose, ses
22
21
  const pluginPanels = pluginManager.evaluateExtensionPoint(
23
22
  /** #extensionPoint Core-preferencesDialogPanels | sync | Add tabs to the preferences dialog */
24
23
  'Core-preferencesDialogPanels', [], { session });
25
- const displayDefaultCount = session.getDisplayTypeDefaults().length;
26
24
  const tabs = [
27
25
  {
28
26
  value: 'general',
@@ -34,13 +32,6 @@ const PreferencesDialog = observer(function PreferencesDialog({ handleClose, ses
34
32
  label: 'Views',
35
33
  content: _jsx(ViewPreferencesTab, { session: session }),
36
34
  },
37
- {
38
- value: 'displayDefaults',
39
- label: displayDefaultCount
40
- ? `Display defaults (${displayDefaultCount})`
41
- : 'Display defaults',
42
- content: (_jsx(DisplayDefaultsSection, { session: session, pluginManager: pluginManager })),
43
- },
44
35
  ...pluginPanels.map(({ name, Component }) => ({
45
36
  value: `plugin:${name}`,
46
37
  label: name,
@@ -53,7 +44,7 @@ const PreferencesDialog = observer(function PreferencesDialog({ handleClose, ses
53
44
  setResetDialogOpen(true);
54
45
  }, children: "Reset to defaults\u2026" }), _jsx(Button, { variant: "contained", onClick: () => {
55
46
  handleClose();
56
- }, children: "Close" })] }), resetDialogOpen ? (_jsx(PreferencesResetDialog, { changes: collectPreferenceChanges(session, pluginManager), onReset: () => {
47
+ }, children: "Close" })] }), resetDialogOpen ? (_jsx(PreferencesResetDialog, { changes: collectPreferenceChanges(session), onReset: () => {
57
48
  resetAllPreferences(session);
58
49
  }, onResetRow: change => {
59
50
  resetPreferenceChange(session, change);
@@ -1,7 +1,5 @@
1
- import type { DisplayDefaultsSession } from './displayDefaultChanges.ts';
2
- import type PluginManager from '@jbrowse/core/PluginManager';
3
1
  import type { TrackConfigChange } from '@jbrowse/core/util';
4
- export interface ResettablePreferencesSession extends DisplayDefaultsSession {
2
+ export interface ResettablePreferencesSession {
5
3
  themeName?: string;
6
4
  setThemeName: (arg: string) => void;
7
5
  stickyViewHeaders: boolean;
@@ -13,6 +11,6 @@ export interface ResettablePreferencesSession extends DisplayDefaultsSession {
13
11
  clearPreferenceOverride: (key: string) => void;
14
12
  getPreferenceChanges: () => TrackConfigChange[];
15
13
  }
16
- export declare function collectPreferenceChanges(session: ResettablePreferencesSession, pluginManager: PluginManager): TrackConfigChange[];
14
+ export declare function collectPreferenceChanges(session: ResettablePreferencesSession): TrackConfigChange[];
17
15
  export declare function resetAllPreferences(session: ResettablePreferencesSession): void;
18
16
  export declare function resetPreferenceChange(session: ResettablePreferencesSession, change: TrackConfigChange): void;
@@ -1,4 +1,3 @@
1
- import { displayDefaultChanges, resetDisplayDefault, } from './displayDefaultChanges.js';
2
1
  const NON_MAP_PREFERENCES = [
3
2
  {
4
3
  head: 'theme',
@@ -34,15 +33,12 @@ const NON_MAP_PREFERENCES = [
34
33
  ];
35
34
  const NON_MAP_HEADS = new Set(NON_MAP_PREFERENCES.map(p => p.head));
36
35
  // Everything "Reset to defaults" reverts, as rows: the scalar overrides in the
37
- // preference map, the promoted display-type defaults (same map, listed through
38
- // the builder the Display defaults section uses so both surfaces read alike),
39
- // and the preferences held outside the map.
40
- export function collectPreferenceChanges(session, pluginManager) {
36
+ // preference map, and the preferences held outside it.
37
+ export function collectPreferenceChanges(session) {
41
38
  return [
42
39
  ...session
43
40
  .getPreferenceChanges()
44
41
  .filter(c => !NON_MAP_HEADS.has(c.path[0])),
45
- ...displayDefaultChanges(session, pluginManager),
46
42
  ...NON_MAP_PREFERENCES.map(p => p.change(session)).filter(c => c !== undefined),
47
43
  ];
48
44
  }
@@ -58,7 +54,7 @@ export function resetPreferenceChange(session, change) {
58
54
  if (pref) {
59
55
  pref.reset(session);
60
56
  }
61
- else if (!resetDisplayDefault(session, change) && head) {
57
+ else if (head) {
62
58
  session.clearPreferenceOverride(head);
63
59
  }
64
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/product-core",
3
- "version": "5.0.0-beta.3",
3
+ "version": "5.0.0-beta.7",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "JBrowse 2 code shared between products but not used by plugins",
@@ -30,8 +30,8 @@
30
30
  "@mui/material": "^9.4.0",
31
31
  "mobx": "^7.0.3",
32
32
  "mobx-react": "^10.0.2",
33
- "@jbrowse/core": "^5.0.0-beta.3",
34
- "@jbrowse/render-core": "^5.0.0-beta.3"
33
+ "@jbrowse/core": "^5.0.0-beta.7",
34
+ "@jbrowse/render-core": "^5.0.0-beta.7"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "react": ">=19.0.0",
@@ -1,23 +0,0 @@
1
- import type { DisplayDefaultsSession } from './displayDefaultChanges.ts';
2
- import type PluginManager from '@jbrowse/core/PluginManager';
3
- export type { DisplayDefaultsSession } from './displayDefaultChanges.ts';
4
- /**
5
- * The inventory of session-wide display-type defaults: every value the user has
6
- * pinned from a track menu, what it overrides, and a button to clear one.
7
- *
8
- * The pin is easy to set and, until this, hard to find again. A pinned default
9
- * shows on the track-selector badge of any **open** track it moves — but one
10
- * affecting nothing currently open, and one that no longer resolves at all (a
11
- * stale value the cascade refuses, or a display type whose plugin is gone)
12
- * appear nowhere. The only place that listed them was the "Reset preferences to
13
- * defaults" confirmation, which is a destructive dialog to have to open in
14
- * order to read something.
15
- *
16
- * The empty state is deliberate rather than rendering nothing: it is the one
17
- * place the app says the capability exists to someone who has never used it.
18
- */
19
- declare const DisplayDefaultsSection: ({ session, pluginManager, }: {
20
- session: DisplayDefaultsSession;
21
- pluginManager: PluginManager;
22
- }) => import("react").JSX.Element;
23
- export default DisplayDefaultsSection;
@@ -1,27 +0,0 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { SettingsChangesTable } from '@jbrowse/core/ui';
3
- import { Typography } from '@mui/material';
4
- import { observer } from 'mobx-react';
5
- import { displayDefaultChanges, resetDisplayDefault, } from './displayDefaultChanges.js';
6
- /**
7
- * The inventory of session-wide display-type defaults: every value the user has
8
- * pinned from a track menu, what it overrides, and a button to clear one.
9
- *
10
- * The pin is easy to set and, until this, hard to find again. A pinned default
11
- * shows on the track-selector badge of any **open** track it moves — but one
12
- * affecting nothing currently open, and one that no longer resolves at all (a
13
- * stale value the cascade refuses, or a display type whose plugin is gone)
14
- * appear nowhere. The only place that listed them was the "Reset preferences to
15
- * defaults" confirmation, which is a destructive dialog to have to open in
16
- * order to read something.
17
- *
18
- * The empty state is deliberate rather than rendering nothing: it is the one
19
- * place the app says the capability exists to someone who has never used it.
20
- */
21
- const DisplayDefaultsSection = observer(function DisplayDefaultsSection({ session, pluginManager, }) {
22
- const changes = displayDefaultChanges(session, pluginManager);
23
- 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 => {
24
- resetDisplayDefault(session, change);
25
- } })] })) : (_jsx(Typography, { children: "None set. The pin beside a setting in a track menu applies that value to every open track of the same display type, and then offers to keep it as the default for the ones you open later." }));
26
- });
27
- export default DisplayDefaultsSection;
@@ -1,31 +0,0 @@
1
- import type PluginManager from '@jbrowse/core/PluginManager';
2
- import type { TrackConfigChange } from '@jbrowse/core/util';
3
- export interface DisplayDefaultsSession {
4
- getDisplayTypeDefaults: () => {
5
- displayType: string;
6
- slot: string;
7
- value: unknown;
8
- }[];
9
- setDisplayTypeDefault: (displayType: string, slot: string, value: unknown) => void;
10
- }
11
- /**
12
- * Every promoted display-type default as a settings row: what it overrides
13
- * (the slot's `promotedBase`), the value, and a label naming the display type
14
- * the way the user knows it. One builder for the two surfaces that list them,
15
- * the Display defaults section and the "Reset to defaults" confirmation, which
16
- * used to render the same default two ways.
17
- *
18
- * **The slot keeps its schema name**, while the display type gets its authored
19
- * one. A slot has no menu label to borrow — which row a setting is offered on is
20
- * a menu fact and declaring the slot promotable is a schema fact, and nothing
21
- * joins them (that split is why `promotableSlotsWithoutPin` has to walk a built
22
- * menu at all). A raw slot name is also what the config editor labels its fields
23
- * with, so the two surfaces agree.
24
- */
25
- export declare function displayDefaultChanges(session: DisplayDefaultsSession, pluginManager: PluginManager): TrackConfigChange[];
26
- /**
27
- * Clear the promoted default a row built by {@link displayDefaultChanges}
28
- * stands for. Returns false for any other row, so a table mixing both kinds
29
- * can hand every reset here first.
30
- */
31
- export declare function resetDisplayDefault(session: DisplayDefaultsSession, change: TrackConfigChange): boolean;
@@ -1,66 +0,0 @@
1
- import { getConfigurationSchemaDefinition, isSlotDefinitionEntry, } from '@jbrowse/core/configuration';
2
- // Head of the path a promoted default's row carries, so `resetDisplayDefault`
3
- // can tell it from a scalar preference row in the same table. Module-private:
4
- // the one producer and the one consumer of the shape are both here, which is
5
- // what lets the session know nothing about how the Preferences dialog
6
- // addresses a row.
7
- const DISPLAY_DEFAULT_PATH_HEAD = 'displayTypeDefaults';
8
- // The registered display types, by name.
9
- //
10
- // **Built rather than reached through `getDisplayType`, which THROWS** on a name
11
- // no plugin registers — an optional chain over it guards nothing. A default left
12
- // behind by a plugin that has since been uninstalled is exactly a row this
13
- // section has to render, so reaching for that getter would crash the whole
14
- // Preferences dialog for the one user whose pins most need clearing.
15
- function displayTypesByName(pluginManager) {
16
- return new Map(pluginManager.getDisplayElements().map(d => [d.name, d]));
17
- }
18
- // What a track sitting at the bottom of the cascade shows for this slot — the
19
- // value clearing the pin would restore. Read off the display type's schema
20
- // rather than off the session, which holds only what was promoted. Undefined
21
- // for a display type that is gone, whose row then reads "(default)" like any
22
- // other unknown.
23
- function promotedBaseOf(configSchema, slot) {
24
- const def = configSchema
25
- ? getConfigurationSchemaDefinition(configSchema)?.[slot]
26
- : undefined;
27
- return isSlotDefinitionEntry(def) ? def.promotedBase : undefined;
28
- }
29
- /**
30
- * Every promoted display-type default as a settings row: what it overrides
31
- * (the slot's `promotedBase`), the value, and a label naming the display type
32
- * the way the user knows it. One builder for the two surfaces that list them,
33
- * the Display defaults section and the "Reset to defaults" confirmation, which
34
- * used to render the same default two ways.
35
- *
36
- * **The slot keeps its schema name**, while the display type gets its authored
37
- * one. A slot has no menu label to borrow — which row a setting is offered on is
38
- * a menu fact and declaring the slot promotable is a schema fact, and nothing
39
- * joins them (that split is why `promotableSlotsWithoutPin` has to walk a built
40
- * menu at all). A raw slot name is also what the config editor labels its fields
41
- * with, so the two surfaces agree.
42
- */
43
- export function displayDefaultChanges(session, pluginManager) {
44
- const byName = displayTypesByName(pluginManager);
45
- return session
46
- .getDisplayTypeDefaults()
47
- .map(({ displayType, slot, value }) => ({
48
- path: [DISPLAY_DEFAULT_PATH_HEAD, displayType, slot],
49
- label: `${byName.get(displayType)?.displayName ?? displayType} › ${slot}`,
50
- from: promotedBaseOf(byName.get(displayType)?.configSchema, slot),
51
- to: value,
52
- }));
53
- }
54
- /**
55
- * Clear the promoted default a row built by {@link displayDefaultChanges}
56
- * stands for. Returns false for any other row, so a table mixing both kinds
57
- * can hand every reset here first.
58
- */
59
- export function resetDisplayDefault(session, change) {
60
- const [head, displayType, slot] = change.path;
61
- const isDisplayDefault = head === DISPLAY_DEFAULT_PATH_HEAD && !!displayType && !!slot;
62
- if (isDisplayDefault) {
63
- session.setDisplayTypeDefault(displayType, slot, undefined);
64
- }
65
- return isDisplayDefault;
66
- }