@jbrowse/product-core 5.0.0-beta.2 → 5.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/RootModel/BaseRootModel.js +1 -1
- package/esm/Session/BaseSession.d.ts +9 -26
- package/esm/Session/BaseSession.js +10 -59
- package/esm/Session/MultipleViews.d.ts +0 -1
- package/esm/Session/MultipleViews.js +1 -2
- package/esm/Session/Preferences.d.ts +0 -1
- package/esm/Session/SessionTracks.d.ts +2 -1
- package/esm/Session/Tracks.d.ts +2 -5
- package/esm/Session/Tracks.js +103 -103
- package/esm/index.d.ts +1 -1
- package/esm/index.js +1 -1
- package/esm/pruneUnbuildableNodes.js +5 -2
- package/esm/ui/AboutDialogContents.js +2 -1
- package/esm/ui/DescriptionPanel.d.ts +3 -0
- package/esm/ui/DescriptionPanel.js +24 -0
- package/esm/ui/DisplayDefaultsSection.d.ts +7 -20
- package/esm/ui/DisplayDefaultsSection.js +8 -46
- package/esm/ui/GeneralPreferencesTab.d.ts +14 -0
- package/esm/ui/GeneralPreferencesTab.js +27 -0
- package/esm/ui/PreferenceCheckbox.d.ts +6 -0
- package/esm/ui/PreferenceCheckbox.js +16 -0
- package/esm/ui/PreferencesDialog.d.ts +6 -27
- package/esm/ui/PreferencesDialog.js +38 -119
- package/esm/ui/ViewPreferencesTab.d.ts +12 -0
- package/esm/ui/ViewPreferencesTab.js +15 -0
- package/esm/ui/descriptionHtml.d.ts +26 -0
- package/esm/ui/descriptionHtml.js +66 -0
- package/esm/ui/displayDefaultChanges.d.ts +31 -0
- package/esm/ui/displayDefaultChanges.js +66 -0
- package/esm/ui/preferencesReset.d.ts +18 -0
- package/esm/ui/preferencesReset.js +64 -0
- package/package.json +3 -3
- package/esm/scheduleDetachedDestroy.d.ts +0 -18
- package/esm/scheduleDetachedDestroy.js +0 -24
|
@@ -1,65 +1,27 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { getConfigurationSchemaDefinition, isSlotDefinitionEntry, } from '@jbrowse/core/configuration';
|
|
3
2
|
import { SettingsChangesTable } from '@jbrowse/core/ui';
|
|
4
3
|
import { Typography } from '@mui/material';
|
|
5
4
|
import { observer } from 'mobx-react';
|
|
6
|
-
|
|
7
|
-
//
|
|
8
|
-
// **Built rather than reached through `getDisplayType`, which THROWS** on a name
|
|
9
|
-
// no plugin registers — an optional chain over it guards nothing. A default left
|
|
10
|
-
// behind by a plugin that has since been uninstalled is exactly a row this
|
|
11
|
-
// section has to render, so reaching for that getter would crash the whole
|
|
12
|
-
// Preferences dialog for the one user whose pins most need clearing.
|
|
13
|
-
function displayTypesByName(pluginManager) {
|
|
14
|
-
return new Map(pluginManager.getDisplayElements().map(d => [d.name, d]));
|
|
15
|
-
}
|
|
16
|
-
// What a track sitting at the bottom of the cascade shows for this slot — the
|
|
17
|
-
// value clearing the pin would restore. Read off the display type's schema
|
|
18
|
-
// rather than off the session, which holds only what was promoted. Undefined
|
|
19
|
-
// for a display type that is gone, whose row then reads "(default)" like any
|
|
20
|
-
// other unknown.
|
|
21
|
-
function promotedBaseOf(configSchema, slot) {
|
|
22
|
-
const def = configSchema
|
|
23
|
-
? getConfigurationSchemaDefinition(configSchema)?.[slot]
|
|
24
|
-
: undefined;
|
|
25
|
-
return isSlotDefinitionEntry(def) ? def.promotedBase : undefined;
|
|
26
|
-
}
|
|
5
|
+
import { displayDefaultChanges, resetDisplayDefault, } from './displayDefaultChanges.js';
|
|
27
6
|
/**
|
|
28
7
|
* The inventory of session-wide display-type defaults: every value the user has
|
|
29
8
|
* pinned from a track menu, what it overrides, and a button to clear one.
|
|
30
9
|
*
|
|
31
10
|
* The pin is easy to set and, until this, hard to find again. A pinned default
|
|
32
11
|
* shows on the track-selector badge of any **open** track it moves — but one
|
|
33
|
-
* affecting nothing currently open, one
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
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.
|
|
38
17
|
*
|
|
39
18
|
* The empty state is deliberate rather than rendering nothing: it is the one
|
|
40
19
|
* place the app says the capability exists to someone who has never used it.
|
|
41
|
-
*
|
|
42
|
-
* **The slot keeps its schema name**, while the display type gets its authored
|
|
43
|
-
* one. A slot has no menu label to borrow — which row a setting is offered on is
|
|
44
|
-
* a menu fact and declaring the slot promotable is a schema fact, and nothing
|
|
45
|
-
* joins them (that split is why `promotableSlotsWithoutPin` has to walk a built
|
|
46
|
-
* menu at all). A raw slot name is also what the config editor labels its fields
|
|
47
|
-
* with, so the two surfaces agree.
|
|
48
20
|
*/
|
|
49
21
|
const DisplayDefaultsSection = observer(function DisplayDefaultsSection({ session, pluginManager, }) {
|
|
50
|
-
const
|
|
51
|
-
const changes = session
|
|
52
|
-
.getDisplayTypeDefaults()
|
|
53
|
-
.map(({ displayType, slot, value }) => ({
|
|
54
|
-
// the address this section's own reset uses, so it depends on nothing the
|
|
55
|
-
// session's preference-key layout does
|
|
56
|
-
path: [displayType, slot],
|
|
57
|
-
label: `${byName.get(displayType)?.displayName ?? displayType} › ${slot}`,
|
|
58
|
-
from: promotedBaseOf(byName.get(displayType)?.configSchema, slot),
|
|
59
|
-
to: value,
|
|
60
|
-
}));
|
|
22
|
+
const changes = displayDefaultChanges(session, pluginManager);
|
|
61
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 => {
|
|
62
|
-
session
|
|
24
|
+
resetDisplayDefault(session, change);
|
|
63
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." }));
|
|
64
26
|
});
|
|
65
27
|
export default DisplayDefaultsSection;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ThemeMap } from '@jbrowse/core/ui';
|
|
2
|
+
import type { AnimationMode } from '@jbrowse/core/util';
|
|
3
|
+
export interface GeneralPreferencesSession {
|
|
4
|
+
allThemes: () => ThemeMap;
|
|
5
|
+
themeName?: string;
|
|
6
|
+
setThemeName: (arg: string) => void;
|
|
7
|
+
animationMode: AnimationMode;
|
|
8
|
+
numberGrouping: boolean;
|
|
9
|
+
setPreferenceOverride: (key: string, value: unknown) => void;
|
|
10
|
+
}
|
|
11
|
+
declare const GeneralPreferencesTab: ({ session, }: {
|
|
12
|
+
session: GeneralPreferencesSession;
|
|
13
|
+
}) => import("react").JSX.Element;
|
|
14
|
+
export default GeneralPreferencesTab;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
3
|
+
import { FormGroup, MenuItem, TextField } from '@mui/material';
|
|
4
|
+
import { observer } from 'mobx-react';
|
|
5
|
+
import PreferenceCheckbox from './PreferenceCheckbox.js';
|
|
6
|
+
const useStyles = makeStyles()({
|
|
7
|
+
field: {
|
|
8
|
+
marginTop: 16,
|
|
9
|
+
display: 'block',
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
const ANIMATION_MODES = [
|
|
13
|
+
{ value: 'system', label: 'Follow system (reduced motion)' },
|
|
14
|
+
{ value: 'enabled', label: 'Always on' },
|
|
15
|
+
{ value: 'disabled', label: 'Off' },
|
|
16
|
+
];
|
|
17
|
+
const GeneralPreferencesTab = observer(function GeneralPreferencesTab({ session, }) {
|
|
18
|
+
const { classes } = useStyles();
|
|
19
|
+
return (_jsxs(_Fragment, { children: [_jsx(TextField, { select: true, variant: "outlined", className: classes.field, label: "Theme", value: session.themeName, onChange: event => {
|
|
20
|
+
session.setThemeName(event.target.value);
|
|
21
|
+
}, children: Object.entries(session.allThemes()).map(([key, val]) => (_jsx(MenuItem, { value: key, children: val.name || '(Unknown name)' }, key))) }), _jsx(TextField, { select: true, variant: "outlined", className: classes.field, label: "Animations", value: session.animationMode, onChange: event => {
|
|
22
|
+
session.setPreferenceOverride('animationMode', event.target.value);
|
|
23
|
+
}, children: ANIMATION_MODES.map(opt => (_jsx(MenuItem, { value: opt.value, children: opt.label }, opt.value))) }), _jsx(FormGroup, { children: _jsx(PreferenceCheckbox, { checked: session.numberGrouping, label: "Thousand separators in numbers", help: "Shows chr1:1,234,567 rather than chr1:1234567. Takes effect after reloading the page.", onChange: checked => {
|
|
24
|
+
session.setPreferenceOverride('numberGrouping', checked);
|
|
25
|
+
} }) })] }));
|
|
26
|
+
});
|
|
27
|
+
export default GeneralPreferencesTab;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { LabeledCheckbox } from '@jbrowse/core/ui';
|
|
3
|
+
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
4
|
+
import { Typography } from '@mui/material';
|
|
5
|
+
const useStyles = makeStyles()({
|
|
6
|
+
row: {
|
|
7
|
+
alignItems: 'flex-start',
|
|
8
|
+
marginTop: 8,
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
export default function PreferenceCheckbox({ checked, label, help, onChange, }) {
|
|
12
|
+
const { classes } = useStyles();
|
|
13
|
+
return (_jsx(LabeledCheckbox, { className: classes.row, checked: checked, onChange: checked => {
|
|
14
|
+
onChange(checked);
|
|
15
|
+
}, label: _jsxs(_Fragment, { children: [_jsx(Typography, { children: label }), help ? (_jsx(Typography, { variant: "body2", color: "text.secondary", children: help })) : null] }) }));
|
|
16
|
+
}
|
|
@@ -1,35 +1,14 @@
|
|
|
1
|
+
import type { DisplayDefaultsSession } from './DisplayDefaultsSection.tsx';
|
|
2
|
+
import type { GeneralPreferencesSession } from './GeneralPreferencesTab.tsx';
|
|
3
|
+
import type { ViewPreferencesSession } from './ViewPreferencesTab.tsx';
|
|
4
|
+
import type { ResettablePreferencesSession } from './preferencesReset.ts';
|
|
1
5
|
import type PluginManager from '@jbrowse/core/PluginManager';
|
|
2
|
-
import type { ThemeMap } from '@jbrowse/core/ui';
|
|
3
|
-
import type { AnimationMode, TrackConfigChange } from '@jbrowse/core/util';
|
|
4
6
|
import type React from 'react';
|
|
5
|
-
export interface PreferencesDialogSession {
|
|
6
|
-
allThemes: () => ThemeMap;
|
|
7
|
-
themeName?: string;
|
|
8
|
-
setThemeName: (arg: string) => void;
|
|
9
|
-
stickyViewHeaders: boolean;
|
|
10
|
-
setStickyViewHeaders: (sticky: boolean) => void;
|
|
11
|
-
effectiveUseWorkspaces: boolean;
|
|
12
|
-
defaultUseWorkspaces: boolean;
|
|
13
|
-
setUseWorkspacesPreference: (useWorkspaces: boolean) => void;
|
|
14
|
-
resetUseWorkspaces: () => void;
|
|
15
|
-
animationMode: AnimationMode;
|
|
16
|
-
numberGrouping: boolean;
|
|
17
|
-
scrollZoom: boolean;
|
|
18
|
-
setScrollZoom: (flag: boolean) => void;
|
|
19
|
-
setPreferenceOverride: (key: string, value: unknown) => void;
|
|
20
|
-
clearPreferenceOverrides: () => void;
|
|
21
|
-
getPreferenceChanges: () => TrackConfigChange[];
|
|
22
|
-
resetPreferenceChange: (path: string[]) => void;
|
|
23
|
-
getDisplayTypeDefaults: () => {
|
|
24
|
-
displayType: string;
|
|
25
|
-
slot: string;
|
|
26
|
-
value: unknown;
|
|
27
|
-
}[];
|
|
28
|
-
setDisplayTypeDefault: (displayType: string, slot: string, value: unknown) => void;
|
|
7
|
+
export interface PreferencesDialogSession extends GeneralPreferencesSession, ViewPreferencesSession, DisplayDefaultsSession, ResettablePreferencesSession {
|
|
29
8
|
}
|
|
30
9
|
/**
|
|
31
10
|
* Descriptor returned from the `Core-preferencesDialogPanels` extension point.
|
|
32
|
-
* Each panel renders as its own
|
|
11
|
+
* Each panel renders as its own tab in the dialog, labeled by `name`.
|
|
33
12
|
*/
|
|
34
13
|
export interface PreferencesPanelDescriptor {
|
|
35
14
|
name: string;
|
|
@@ -1,140 +1,59 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Suspense, useState } from 'react';
|
|
3
|
-
import { Dialog
|
|
4
|
-
import { SCROLL_ZOOM_HELP, SCROLL_ZOOM_LABEL, } from '@jbrowse/core/ui/scrollZoomLabels';
|
|
3
|
+
import { Dialog } from '@jbrowse/core/ui';
|
|
5
4
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
6
|
-
import { Button, DialogActions, DialogContent,
|
|
5
|
+
import { Button, DialogActions, DialogContent, Tab, Tabs } from '@mui/material';
|
|
7
6
|
import { observer } from 'mobx-react';
|
|
8
7
|
import DisplayDefaultsSection from './DisplayDefaultsSection.js';
|
|
8
|
+
import GeneralPreferencesTab from './GeneralPreferencesTab.js';
|
|
9
9
|
import PreferencesResetDialog from './PreferencesResetDialog.js';
|
|
10
|
+
import ViewPreferencesTab from './ViewPreferencesTab.js';
|
|
11
|
+
import { collectPreferenceChanges, resetAllPreferences, resetPreferenceChange, } from './preferencesReset.js';
|
|
10
12
|
const useStyles = makeStyles()({
|
|
11
13
|
container: {
|
|
12
14
|
width: 800,
|
|
13
|
-
|
|
14
|
-
panelHeading: {
|
|
15
|
-
marginTop: 16,
|
|
16
|
-
},
|
|
17
|
-
field: {
|
|
18
|
-
marginTop: 16,
|
|
19
|
-
display: 'block',
|
|
15
|
+
minHeight: 300,
|
|
20
16
|
},
|
|
21
17
|
});
|
|
22
|
-
const PREFERENCE_SUBSYSTEMS = [
|
|
23
|
-
{
|
|
24
|
-
head: 'theme',
|
|
25
|
-
change: s => s.themeName && s.themeName !== 'default'
|
|
26
|
-
? { path: ['theme'], from: 'default', to: s.themeName }
|
|
27
|
-
: undefined,
|
|
28
|
-
reset: s => {
|
|
29
|
-
s.setThemeName('default');
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
head: 'stickyViewHeaders',
|
|
34
|
-
change: s => s.stickyViewHeaders
|
|
35
|
-
? undefined
|
|
36
|
-
: { path: ['stickyViewHeaders'], from: true, to: false },
|
|
37
|
-
reset: s => {
|
|
38
|
-
s.setStickyViewHeaders(true);
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
head: 'useWorkspaces',
|
|
43
|
-
// the resolved flag against the admin default, not the override map's view
|
|
44
|
-
// of it: a session-scoped value (a spec `layout`, "move view to a tab")
|
|
45
|
-
// writes no override, so the map reported nothing to reset while
|
|
46
|
-
// `resetUseWorkspaces` would plainly have turned workspaces back off —
|
|
47
|
-
// leaving the confirmation empty and its Reset button disabled
|
|
48
|
-
change: s => s.effectiveUseWorkspaces === s.defaultUseWorkspaces
|
|
49
|
-
? undefined
|
|
50
|
-
: {
|
|
51
|
-
path: ['useWorkspaces'],
|
|
52
|
-
from: s.defaultUseWorkspaces,
|
|
53
|
-
to: s.effectiveUseWorkspaces,
|
|
54
|
-
},
|
|
55
|
-
reset: s => {
|
|
56
|
-
s.resetUseWorkspaces();
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
];
|
|
60
|
-
// heads the subsystems above own outright: their row states the resolved value,
|
|
61
|
-
// so the session's override-map row for the same key would double-report it
|
|
62
|
-
// (and disagree with it whenever the two layers differ)
|
|
63
|
-
const SUBSYSTEM_HEADS = new Set(PREFERENCE_SUBSYSTEMS.map(p => p.head));
|
|
64
|
-
// every preference that currently differs from its default, so the confirmation
|
|
65
|
-
// dialog shows the full effect of a reset: the session override map plus each
|
|
66
|
-
// non-map subsystem above.
|
|
67
|
-
function collectPreferenceChanges(session) {
|
|
68
|
-
return [
|
|
69
|
-
...session
|
|
70
|
-
.getPreferenceChanges()
|
|
71
|
-
.filter(c => !SUBSYSTEM_HEADS.has(c.path[0])),
|
|
72
|
-
...PREFERENCE_SUBSYSTEMS.map(p => p.change(session)).filter(c => c !== undefined),
|
|
73
|
-
];
|
|
74
|
-
}
|
|
75
|
-
// Reset every preference this dialog exposes back to its default: clear the
|
|
76
|
-
// whole override map (scrollZoom, animationMode, all promoted display-type
|
|
77
|
-
// defaults) at once, then reset each non-map subsystem through its own setter.
|
|
78
|
-
function resetAllPreferences(session) {
|
|
79
|
-
session.clearPreferenceOverrides();
|
|
80
|
-
for (const p of PREFERENCE_SUBSYSTEMS) {
|
|
81
|
-
p.reset(session);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
// Revert a single change row (see `collectPreferenceChanges`) to its default:
|
|
85
|
-
// a non-map subsystem by matching `head`, everything else back through the
|
|
86
|
-
// session that produced the row. The override map's own path shapes are the
|
|
87
|
-
// session's business — it owns the composite-key layout a promoted
|
|
88
|
-
// display-type default is stored under, so it also owns undoing one.
|
|
89
|
-
function resetPreferenceChange(session, change) {
|
|
90
|
-
const subsystem = PREFERENCE_SUBSYSTEMS.find(p => p.head === change.path[0]);
|
|
91
|
-
if (subsystem) {
|
|
92
|
-
subsystem.reset(session);
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
session.resetPreferenceChange(change.path);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
// declarative user-preference rows backed by the session preferences-override
|
|
99
|
-
// system (BaseSession getPreference/setPreferenceOverride). Add a row here to
|
|
100
|
-
// surface a new preference; resolution + persistence are already handled.
|
|
101
|
-
const PREFERENCE_SELECTS = [
|
|
102
|
-
{
|
|
103
|
-
key: 'animationMode',
|
|
104
|
-
label: 'Animations',
|
|
105
|
-
options: [
|
|
106
|
-
{ value: 'system', label: 'Follow system (reduced motion)' },
|
|
107
|
-
{ value: 'enabled', label: 'Always on' },
|
|
108
|
-
{ value: 'disabled', label: 'Off' },
|
|
109
|
-
],
|
|
110
|
-
get: session => session.animationMode,
|
|
111
|
-
},
|
|
112
|
-
];
|
|
113
18
|
const PreferencesDialog = observer(function PreferencesDialog({ handleClose, session, pluginManager, }) {
|
|
114
19
|
const { classes } = useStyles();
|
|
115
|
-
|
|
116
|
-
// instead of an accidental single click wiping every preference
|
|
20
|
+
const [tab, setTab] = useState('general');
|
|
117
21
|
const [resetDialogOpen, setResetDialogOpen] = useState(false);
|
|
118
|
-
const
|
|
119
|
-
/** #extensionPoint Core-preferencesDialogPanels | sync | Add
|
|
22
|
+
const pluginPanels = pluginManager.evaluateExtensionPoint(
|
|
23
|
+
/** #extensionPoint Core-preferencesDialogPanels | sync | Add tabs to the preferences dialog */
|
|
120
24
|
'Core-preferencesDialogPanels', [], { session });
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
25
|
+
const displayDefaultCount = session.getDisplayTypeDefaults().length;
|
|
26
|
+
const tabs = [
|
|
27
|
+
{
|
|
28
|
+
value: 'general',
|
|
29
|
+
label: 'General',
|
|
30
|
+
content: _jsx(GeneralPreferencesTab, { session: session }),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
value: 'views',
|
|
34
|
+
label: 'Views',
|
|
35
|
+
content: _jsx(ViewPreferencesTab, { session: session }),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
value: 'displayDefaults',
|
|
39
|
+
label: displayDefaultCount
|
|
40
|
+
? `Display defaults (${displayDefaultCount})`
|
|
41
|
+
: 'Display defaults',
|
|
42
|
+
content: (_jsx(DisplayDefaultsSection, { session: session, pluginManager: pluginManager })),
|
|
43
|
+
},
|
|
44
|
+
...pluginPanels.map(({ name, Component }) => ({
|
|
45
|
+
value: `plugin:${name}`,
|
|
46
|
+
label: name,
|
|
47
|
+
content: (_jsx(Suspense, { fallback: null, children: _jsx(Component, { session: session }) })),
|
|
48
|
+
})),
|
|
49
|
+
];
|
|
50
|
+
return (_jsxs(Dialog, { title: "Preferences", open: true, onClose: handleClose, maxWidth: "xl", children: [_jsx(Tabs, { value: tab, variant: "scrollable", scrollButtons: "auto", onChange: (_, value) => {
|
|
51
|
+
setTab(value);
|
|
52
|
+
}, children: tabs.map(t => (_jsx(Tab, { value: t.value, label: t.label }, t.value))) }), _jsx(DialogContent, { className: classes.container, children: tabs.find(t => t.value === tab)?.content }), _jsxs(DialogActions, { children: [_jsx(Button, { variant: "contained", color: "secondary", onClick: () => {
|
|
134
53
|
setResetDialogOpen(true);
|
|
135
54
|
}, children: "Reset to defaults\u2026" }), _jsx(Button, { variant: "contained", onClick: () => {
|
|
136
55
|
handleClose();
|
|
137
|
-
}, children: "Close" })] }), resetDialogOpen ? (_jsx(PreferencesResetDialog, { changes: collectPreferenceChanges(session), onReset: () => {
|
|
56
|
+
}, children: "Close" })] }), resetDialogOpen ? (_jsx(PreferencesResetDialog, { changes: collectPreferenceChanges(session, pluginManager), onReset: () => {
|
|
138
57
|
resetAllPreferences(session);
|
|
139
58
|
}, onResetRow: change => {
|
|
140
59
|
resetPreferenceChange(session, change);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ViewPreferencesSession {
|
|
2
|
+
scrollZoom: boolean;
|
|
3
|
+
setScrollZoom: (flag: boolean) => void;
|
|
4
|
+
stickyViewHeaders: boolean;
|
|
5
|
+
setStickyViewHeaders: (sticky: boolean) => void;
|
|
6
|
+
effectiveUseWorkspaces: boolean;
|
|
7
|
+
setUseWorkspacesPreference: (useWorkspaces: boolean) => void;
|
|
8
|
+
}
|
|
9
|
+
declare const ViewPreferencesTab: ({ session, }: {
|
|
10
|
+
session: ViewPreferencesSession;
|
|
11
|
+
}) => import("react").JSX.Element;
|
|
12
|
+
export default ViewPreferencesTab;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { SCROLL_ZOOM_HELP, SCROLL_ZOOM_LABEL, } from '@jbrowse/core/ui/scrollZoomLabels';
|
|
3
|
+
import { FormGroup } from '@mui/material';
|
|
4
|
+
import { observer } from 'mobx-react';
|
|
5
|
+
import PreferenceCheckbox from './PreferenceCheckbox.js';
|
|
6
|
+
const ViewPreferencesTab = observer(function ViewPreferencesTab({ session, }) {
|
|
7
|
+
return (_jsxs(FormGroup, { children: [_jsx(PreferenceCheckbox, { checked: session.scrollZoom, label: SCROLL_ZOOM_LABEL, help: SCROLL_ZOOM_HELP, onChange: checked => {
|
|
8
|
+
session.setScrollZoom(checked);
|
|
9
|
+
} }), _jsx(PreferenceCheckbox, { checked: session.stickyViewHeaders, label: "Keep view header visible", help: "The view header stays at the top of the page while its tracks scroll under it.", onChange: checked => {
|
|
10
|
+
session.setStickyViewHeaders(checked);
|
|
11
|
+
} }), _jsx(PreferenceCheckbox, { checked: session.effectiveUseWorkspaces, label: "Use workspaces", help: "Views open as tabs and tiles instead of stacking down the page.", onChange: checked => {
|
|
12
|
+
session.setUseWorkspacesPreference(checked);
|
|
13
|
+
} })] }));
|
|
14
|
+
});
|
|
15
|
+
export default ViewPreferencesTab;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AboutConfig } from './util.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The URL of a track's prose description page, as a UCSC track hub supplies it,
|
|
4
|
+
* and undefined for anything that is not an http(s) url — the value reaches
|
|
5
|
+
* `openLocation` and an anchor's href, so a `javascript:` or `file://` one does
|
|
6
|
+
* not become one here.
|
|
7
|
+
*
|
|
8
|
+
* Four spellings: `UCSCTrackHubConnection` spreads the trackDb stanza across
|
|
9
|
+
* `metadata`, the bulk converter behind genomes.jbrowse.org nests it under
|
|
10
|
+
* `metadata.ucsc` (~50,700 configs at permanent URLs), and the stanza key is
|
|
11
|
+
* `html` for a track and `htmlPath` for an assembly. Both producers write an
|
|
12
|
+
* escaped anchor rather than a bare url, so the href comes back through a parse
|
|
13
|
+
* rather than a regex.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getDescriptionHtmlUrl(config: AboutConfig): string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* A fetched description page as markup for `SanitizedHTML`: links and images
|
|
18
|
+
* resolved against the page they came from rather than the JBrowse origin, and
|
|
19
|
+
* `<form>` dropped, which the sanitizer keeps and which in a document off a
|
|
20
|
+
* third-party origin is a credential prompt wearing the app's own chrome.
|
|
21
|
+
*
|
|
22
|
+
* Parsing rather than string-rewriting is what makes taking `body` handle the
|
|
23
|
+
* pages that are whole documents in the same pass. The parse is inert — no
|
|
24
|
+
* scripts run, no images load — and the sanitizer still sees everything after.
|
|
25
|
+
*/
|
|
26
|
+
export declare function rebaseDescriptionHtml(html: string, baseUrl: string): string;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { readConfSlot } from '@jbrowse/core/configuration';
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return typeof value === 'object' && value !== null;
|
|
4
|
+
}
|
|
5
|
+
function descriptionUrl(value) {
|
|
6
|
+
const href = new DOMParser()
|
|
7
|
+
.parseFromString(value, 'text/html')
|
|
8
|
+
.querySelector('a[href]')
|
|
9
|
+
?.getAttribute('href');
|
|
10
|
+
const candidate = typeof href === 'string' ? href : value.trim();
|
|
11
|
+
return /^https?:\/\//i.test(candidate) ? candidate : undefined;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The URL of a track's prose description page, as a UCSC track hub supplies it,
|
|
15
|
+
* and undefined for anything that is not an http(s) url — the value reaches
|
|
16
|
+
* `openLocation` and an anchor's href, so a `javascript:` or `file://` one does
|
|
17
|
+
* not become one here.
|
|
18
|
+
*
|
|
19
|
+
* Four spellings: `UCSCTrackHubConnection` spreads the trackDb stanza across
|
|
20
|
+
* `metadata`, the bulk converter behind genomes.jbrowse.org nests it under
|
|
21
|
+
* `metadata.ucsc` (~50,700 configs at permanent URLs), and the stanza key is
|
|
22
|
+
* `html` for a track and `htmlPath` for an assembly. Both producers write an
|
|
23
|
+
* escaped anchor rather than a bare url, so the href comes back through a parse
|
|
24
|
+
* rather than a regex.
|
|
25
|
+
*/
|
|
26
|
+
export function getDescriptionHtmlUrl(config) {
|
|
27
|
+
const metadata = readConfSlot(config, 'metadata');
|
|
28
|
+
return [metadata, isRecord(metadata) ? metadata.ucsc : undefined]
|
|
29
|
+
.flatMap(record => (isRecord(record) ? [record.html, record.htmlPath] : []))
|
|
30
|
+
.filter(candidate => typeof candidate === 'string')
|
|
31
|
+
.map(candidate => descriptionUrl(candidate))
|
|
32
|
+
.find(url => url !== undefined);
|
|
33
|
+
}
|
|
34
|
+
function absolute(value, baseUrl) {
|
|
35
|
+
try {
|
|
36
|
+
return new URL(value, baseUrl).href;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A fetched description page as markup for `SanitizedHTML`: links and images
|
|
44
|
+
* resolved against the page they came from rather than the JBrowse origin, and
|
|
45
|
+
* `<form>` dropped, which the sanitizer keeps and which in a document off a
|
|
46
|
+
* third-party origin is a credential prompt wearing the app's own chrome.
|
|
47
|
+
*
|
|
48
|
+
* Parsing rather than string-rewriting is what makes taking `body` handle the
|
|
49
|
+
* pages that are whole documents in the same pass. The parse is inert — no
|
|
50
|
+
* scripts run, no images load — and the sanitizer still sees everything after.
|
|
51
|
+
*/
|
|
52
|
+
export function rebaseDescriptionHtml(html, baseUrl) {
|
|
53
|
+
const doc = new DOMParser().parseFromString(html, 'text/html');
|
|
54
|
+
for (const form of doc.querySelectorAll('form')) {
|
|
55
|
+
form.remove();
|
|
56
|
+
}
|
|
57
|
+
for (const el of doc.querySelectorAll('[href], [src]')) {
|
|
58
|
+
for (const attribute of ['href', 'src']) {
|
|
59
|
+
const value = el.getAttribute(attribute);
|
|
60
|
+
if (value) {
|
|
61
|
+
el.setAttribute(attribute, absolute(value, baseUrl));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return doc.body.innerHTML;
|
|
66
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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;
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DisplayDefaultsSession } from './displayDefaultChanges.ts';
|
|
2
|
+
import type PluginManager from '@jbrowse/core/PluginManager';
|
|
3
|
+
import type { TrackConfigChange } from '@jbrowse/core/util';
|
|
4
|
+
export interface ResettablePreferencesSession extends DisplayDefaultsSession {
|
|
5
|
+
themeName?: string;
|
|
6
|
+
setThemeName: (arg: string) => void;
|
|
7
|
+
stickyViewHeaders: boolean;
|
|
8
|
+
setStickyViewHeaders: (sticky: boolean) => void;
|
|
9
|
+
effectiveUseWorkspaces: boolean;
|
|
10
|
+
defaultUseWorkspaces: boolean;
|
|
11
|
+
resetUseWorkspaces: () => void;
|
|
12
|
+
clearPreferenceOverrides: () => void;
|
|
13
|
+
clearPreferenceOverride: (key: string) => void;
|
|
14
|
+
getPreferenceChanges: () => TrackConfigChange[];
|
|
15
|
+
}
|
|
16
|
+
export declare function collectPreferenceChanges(session: ResettablePreferencesSession, pluginManager: PluginManager): TrackConfigChange[];
|
|
17
|
+
export declare function resetAllPreferences(session: ResettablePreferencesSession): void;
|
|
18
|
+
export declare function resetPreferenceChange(session: ResettablePreferencesSession, change: TrackConfigChange): void;
|