@jbrowse/plugin-config 5.0.0-beta.1 → 5.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/ConfigurationEditorWidget/components/AddNewField.d.ts +3 -1
- package/esm/ConfigurationEditorWidget/components/AddNewField.js +2 -1
- package/esm/ConfigurationEditorWidget/components/BooleanEditor.d.ts +0 -1
- package/esm/ConfigurationEditorWidget/components/BooleanEditor.js +1 -14
- package/esm/ConfigurationEditorWidget/components/ColorEditor.d.ts +2 -1
- package/esm/ConfigurationEditorWidget/components/ColorEditor.js +25 -8
- package/esm/ConfigurationEditorWidget/components/ConfigurationEditor.js +69 -21
- package/esm/ConfigurationEditorWidget/components/EntryListEditor.d.ts +17 -0
- package/esm/ConfigurationEditorWidget/components/EntryListEditor.js +35 -0
- package/esm/ConfigurationEditorWidget/components/JsonEditor.d.ts +0 -1
- package/esm/ConfigurationEditorWidget/components/JsonEditor.js +7 -11
- package/esm/ConfigurationEditorWidget/components/NumberEditor.d.ts +0 -1
- package/esm/ConfigurationEditorWidget/components/NumberEditor.js +1 -10
- package/esm/ConfigurationEditorWidget/components/SlotEditor.js +21 -17
- package/esm/ConfigurationEditorWidget/components/StringArrayEditor.d.ts +14 -7
- package/esm/ConfigurationEditorWidget/components/StringArrayEditor.js +15 -5
- package/esm/ConfigurationEditorWidget/components/StringMapEditor.d.ts +10 -0
- package/esm/ConfigurationEditorWidget/components/StringMapEditor.js +13 -0
- package/esm/ConfigurationEditorWidget/components/TypeSelector.d.ts +1 -1
- package/esm/ConfigurationEditorWidget/components/TypeSelector.js +1 -1
- package/esm/ConfigurationEditorWidget/index.js +2 -2
- package/esm/FromConfigAdapter/configSchema.d.ts +12 -17
- package/esm/FromConfigRegionsAdapter/configSchema.d.ts +12 -17
- package/esm/FromConfigSequenceAdapter/configSchema.d.ts +12 -17
- package/esm/NcbiSequenceReportAliasAdapter/configSchema.d.ts +11 -20
- package/esm/NcbiSequenceReportAliasAdapter/configSchema.js +5 -8
- package/esm/RefNameAliasAdapter/configSchema.d.ts +14 -31
- package/esm/RefNameAliasAdapter/configSchema.js +6 -9
- package/esm/aliasUtils.d.ts +1 -1
- package/esm/aliasUtils.js +1 -8
- package/package.json +6 -6
- package/esm/normalizeUriSnapshot.d.ts +0 -1
- package/esm/normalizeUriSnapshot.js +0 -13
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export default function AddNewField({ onAdd, testid, startAdornment, }: {
|
|
2
3
|
onAdd: (value: string) => void;
|
|
3
4
|
testid?: string;
|
|
5
|
+
startAdornment?: ReactNode;
|
|
4
6
|
}): import("react").JSX.Element;
|
|
@@ -4,7 +4,7 @@ import AddIcon from '@mui/icons-material/Add';
|
|
|
4
4
|
import { IconButton, InputAdornment, TextField } from '@mui/material';
|
|
5
5
|
// shared "add new" entry field used by the string-array and map slot editors:
|
|
6
6
|
// a text box that commits its contents via onAdd and clears itself
|
|
7
|
-
export default function AddNewField({ onAdd, testid, }) {
|
|
7
|
+
export default function AddNewField({ onAdd, testid, startAdornment, }) {
|
|
8
8
|
const [value, setValue] = useState('');
|
|
9
9
|
function commit() {
|
|
10
10
|
if (value !== '') {
|
|
@@ -20,6 +20,7 @@ export default function AddNewField({ onAdd, testid, }) {
|
|
|
20
20
|
}
|
|
21
21
|
}, slotProps: {
|
|
22
22
|
input: {
|
|
23
|
+
startAdornment,
|
|
23
24
|
endAdornment: (_jsx(InputAdornment, { position: "end", children: _jsx(IconButton, { "data-testid": testid, disabled: value === '', onClick: () => {
|
|
24
25
|
commit();
|
|
25
26
|
}, children: _jsx(AddIcon, {}) }) })),
|
|
@@ -4,20 +4,7 @@ import { FormControl, FormHelperText } from '@mui/material';
|
|
|
4
4
|
import { observer } from 'mobx-react';
|
|
5
5
|
/** #slotEditor checkbox */
|
|
6
6
|
const BooleanEditor = observer(function BooleanEditor({ slot, }) {
|
|
7
|
-
return (_jsxs(FormControl, { children: [_jsx(LabeledCheckbox, { label: slot.name,
|
|
8
|
-
// An unset promotable slot renders its `promotedBase`, not `false`.
|
|
9
|
-
// Coercing every unset slot to unchecked mis-stated the three whose
|
|
10
|
-
// base is `true` (displayDirectionalChevrons, showSashimiArcs,
|
|
11
|
-
// readConnectionsDown): the box read unchecked on a track that was
|
|
12
|
-
// drawing chevrons, and ticking it wrote the value the track already
|
|
13
|
-
// had — no visible change, but the slot silently left the cascade and
|
|
14
|
-
// stopped following later session-wide defaults. The reset button
|
|
15
|
-
// (SlotEditor's `modified`) is what still separates inherited from
|
|
16
|
-
// customized; this only fixes which state is *shown*. The session tier
|
|
17
|
-
// is out of reach here, so a value promoted this session isn't
|
|
18
|
-
// reflected — see agent-docs/ideas/promotable-slot-ui.md, which is
|
|
19
|
-
// where that raw-vs-resolved gap is worked out.
|
|
20
|
-
checked: slot.value ?? !!slot.promotedBase, onChange: val => {
|
|
7
|
+
return (_jsxs(FormControl, { children: [_jsx(LabeledCheckbox, { label: slot.name, checked: !!slot.value, onChange: val => {
|
|
21
8
|
slot.set(val);
|
|
22
9
|
} }), _jsx(FormHelperText, { children: slot.description })] }));
|
|
23
10
|
});
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/** #slotEditor text field beside a swatch that opens a color picker */
|
|
2
|
-
declare const ColorEditor: (
|
|
2
|
+
declare const ColorEditor: ({ slot, }: {
|
|
3
3
|
slot: {
|
|
4
4
|
name: string;
|
|
5
5
|
value: string | undefined;
|
|
6
6
|
description: string;
|
|
7
|
+
contextVariable?: string[];
|
|
7
8
|
set: (arg: string) => void;
|
|
8
9
|
};
|
|
9
10
|
}) => import("react").JSX.Element;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Suspense, lazy } from 'react';
|
|
2
|
+
import { Suspense, lazy, useState } from 'react';
|
|
3
|
+
import { isCssColor } from '@jbrowse/core/util/colorBits';
|
|
4
|
+
import { isJexl } from '@jbrowse/core/util/jexlStrings';
|
|
3
5
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
4
6
|
import { observer } from 'mobx-react';
|
|
5
7
|
import ConfigurationTextField from './ConfigurationTextField.js';
|
|
@@ -17,18 +19,33 @@ const useStyles = makeStyles()({
|
|
|
17
19
|
});
|
|
18
20
|
// Also drives `maybeColor`, whose value is `undefined` while unset. An empty
|
|
19
21
|
// string keeps the field controlled and the picker fed, the same way
|
|
20
|
-
// NumberEditor renders an unset `maybeNumber` as an empty field.
|
|
21
|
-
//
|
|
22
|
-
//
|
|
22
|
+
// NumberEditor renders an unset `maybeNumber` as an empty field. The slot
|
|
23
|
+
// refuses what is not a color, so text that does not parse yet stays a draft
|
|
24
|
+
// in the field and reaches the slot once it does; leaving the field drops an
|
|
25
|
+
// unparsed draft. "Unset" is reachable through the slot's reset button, which
|
|
26
|
+
// is where every other slot type puts it. Only a slot declaring a
|
|
27
|
+
// `contextVariable` takes a `jexl:` callback, which an entry of a colour list
|
|
28
|
+
// never does.
|
|
23
29
|
/** #slotEditor text field beside a swatch that opens a color picker */
|
|
24
|
-
const ColorEditor = observer(function ColorEditor(
|
|
25
|
-
const
|
|
30
|
+
const ColorEditor = observer(function ColorEditor({ slot, }) {
|
|
31
|
+
const admitsJexl = !!slot.contextVariable?.length;
|
|
26
32
|
const { classes } = useStyles();
|
|
33
|
+
const [draft, setDraft] = useState();
|
|
27
34
|
const value = slot.value ?? '';
|
|
28
|
-
return (_jsxs("div", { className: classes.root, children: [_jsx(ConfigurationTextField, { value: value, label: slot.name, helperText: slot.description
|
|
29
|
-
|
|
35
|
+
return (_jsxs("div", { className: classes.root, children: [_jsx(ConfigurationTextField, { value: draft ?? value, label: slot.name, error: draft !== undefined, helperText: draft === undefined ? slot.description : `"${draft}" is not a color`, className: classes.field, onChange: event => {
|
|
36
|
+
const text = event.target.value;
|
|
37
|
+
if (isCssColor(text) || (admitsJexl && isJexl(text))) {
|
|
38
|
+
slot.set(text);
|
|
39
|
+
setDraft(undefined);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
setDraft(text);
|
|
43
|
+
}
|
|
44
|
+
}, onBlur: () => {
|
|
45
|
+
setDraft(undefined);
|
|
30
46
|
} }), _jsx(Suspense, { fallback: null, children: _jsx(PopoverPicker, { color: value, onChange: color => {
|
|
31
47
|
slot.set(color);
|
|
48
|
+
setDraft(undefined);
|
|
32
49
|
} }) })] }));
|
|
33
50
|
});
|
|
34
51
|
export default ColorEditor;
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
-
import { getSlotDefinition, getTypeNamesFromExplicitlyTypedUnion, isConfigurationModel, isConfigurationSchemaType, isConfigurationSlot, makeSlotFacade, readConfObject, } from '@jbrowse/core/configuration';
|
|
3
|
+
import { arraySlotUnion, getSlotDefinition, getTypeNamesFromExplicitlyTypedUnion, isConfigurationModel, isConfigurationSchemaType, isConfigurationSlot, makeSlotFacade, readConfObject, setConf, } from '@jbrowse/core/configuration';
|
|
4
4
|
import SanitizedHTML from '@jbrowse/core/ui/SanitizedHTML';
|
|
5
5
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
6
|
-
import { getMembers } from '@jbrowse/mobx-state-tree';
|
|
6
|
+
import { getMembers, getSnapshot } from '@jbrowse/mobx-state-tree';
|
|
7
|
+
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
|
8
|
+
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
|
|
7
9
|
import ClearIcon from '@mui/icons-material/Clear';
|
|
10
|
+
import DeleteIcon from '@mui/icons-material/Delete';
|
|
8
11
|
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
|
9
12
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
10
13
|
import SearchIcon from '@mui/icons-material/Search';
|
|
11
|
-
import { Accordion, AccordionDetails, AccordionSummary, Button, IconButton, InputAdornment, TextField, Typography, } from '@mui/material';
|
|
14
|
+
import { Accordion, AccordionDetails, AccordionSummary, Button, IconButton, InputAdornment, MenuItem, TextField, Typography, } from '@mui/material';
|
|
12
15
|
import { observer } from 'mobx-react';
|
|
13
16
|
import SlotEditor from './SlotEditor.js';
|
|
14
17
|
import TypeSelector from './TypeSelector.js';
|
|
15
18
|
import { singularSlotName } from './singularSlotName.js';
|
|
16
19
|
const useStyles = makeStyles()(theme => ({
|
|
17
|
-
icon: {
|
|
18
|
-
color: theme.palette.tertiary.contrastText || '#fff',
|
|
19
|
-
},
|
|
20
20
|
expansionPanelDetails: {
|
|
21
21
|
display: 'block',
|
|
22
22
|
padding: theme.spacing(1),
|
|
@@ -39,7 +39,15 @@ const useStyles = makeStyles()(theme => ({
|
|
|
39
39
|
margin: theme.spacing(1, 0),
|
|
40
40
|
textTransform: 'none',
|
|
41
41
|
},
|
|
42
|
+
entryControls: {
|
|
43
|
+
display: 'flex',
|
|
44
|
+
justifyContent: 'flex-end',
|
|
45
|
+
},
|
|
42
46
|
}));
|
|
47
|
+
// The entries of an array of sub-schemas as snapshots, for a whole-list write.
|
|
48
|
+
function listSnapshot(schema, listName) {
|
|
49
|
+
return [...getSnapshot(schema[listName])];
|
|
50
|
+
}
|
|
43
51
|
// matches a slot or sub-schema against a lowercased filter string, recursing
|
|
44
52
|
// into sub-schemas so a nested slot name keeps its ancestors visible
|
|
45
53
|
function memberMatches(schema, slotName, slotSchema, query) {
|
|
@@ -53,7 +61,7 @@ function memberMatches(schema, slotName, slotSchema, query) {
|
|
|
53
61
|
return (slotName.toLowerCase().includes(query) ||
|
|
54
62
|
(Array.isArray(slot)
|
|
55
63
|
? slot.some(subslot => schemaMatches(subslot, query))
|
|
56
|
-
: schemaMatches(slot, query)));
|
|
64
|
+
: slot !== undefined && schemaMatches(slot, query)));
|
|
57
65
|
}
|
|
58
66
|
else {
|
|
59
67
|
return false;
|
|
@@ -77,9 +85,32 @@ function displayDefaultExpanded(slot, expandedDisplayId) {
|
|
|
77
85
|
: undefined;
|
|
78
86
|
return !displayId || !expandedDisplayId || displayId === expandedDisplayId;
|
|
79
87
|
}
|
|
88
|
+
// A union list entry's own controls: its type, and its place in the list. Each
|
|
89
|
+
// is one whole-list write, so every state it leaves loads again.
|
|
90
|
+
const EntryControls = observer(function EntryControls({ schema, title, slot, entry: { listName, choices, index, count }, }) {
|
|
91
|
+
const { classes } = useStyles();
|
|
92
|
+
const write = (edit) => {
|
|
93
|
+
setConf(schema, listName, edit(listSnapshot(schema, listName)));
|
|
94
|
+
};
|
|
95
|
+
const swap = (at) => {
|
|
96
|
+
write(list => list.toSpliced(at, 2, list[at + 1], list[at]));
|
|
97
|
+
};
|
|
98
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: classes.entryControls, children: [_jsx(IconButton, { "aria-label": `move ${title} up`, disabled: index === 0, onClick: () => {
|
|
99
|
+
swap(index - 1);
|
|
100
|
+
}, children: _jsx(ArrowUpwardIcon, { fontSize: "small" }) }), _jsx(IconButton, { "aria-label": `move ${title} down`, disabled: index === count - 1, onClick: () => {
|
|
101
|
+
swap(index);
|
|
102
|
+
}, children: _jsx(ArrowDownwardIcon, { fontSize: "small" }) }), _jsx(IconButton, { "aria-label": `remove ${title}`, onClick: () => {
|
|
103
|
+
write(list => list.toSpliced(index, 1));
|
|
104
|
+
}, children: _jsx(DeleteIcon, { fontSize: "small" }) })] }), _jsx(TypeSelector, { typeNameChoices: choices, slotName: title, slot: slot, onChange: evt => {
|
|
105
|
+
const type = evt.target.value;
|
|
106
|
+
if (type !== slot.type) {
|
|
107
|
+
write(list => list.with(index, { type }));
|
|
108
|
+
}
|
|
109
|
+
} })] }));
|
|
110
|
+
});
|
|
80
111
|
const Member = observer(function Member(props) {
|
|
81
112
|
const { classes } = useStyles();
|
|
82
|
-
const { slotName, slotSchema, schema, slot = schema[slotName], path = [], filter = '', expandedDisplayId, } = props;
|
|
113
|
+
const { slotName, slotSchema, schema, slot = schema[slotName], path = [], filter = '', expandedDisplayId, entry, } = props;
|
|
83
114
|
// when the sub-schema's own name matches, drop the filter for its children so
|
|
84
115
|
// the whole group stays visible; otherwise keep filtering descendants
|
|
85
116
|
const childFilter = slotName.toLowerCase().includes(filter.toLowerCase())
|
|
@@ -91,12 +122,7 @@ const Member = observer(function Member(props) {
|
|
|
91
122
|
// keyed on Array.isArray, not `.length` truthiness, so an empty array maps
|
|
92
123
|
// to nothing instead of being mis-handled as a single schema
|
|
93
124
|
if (Array.isArray(slot)) {
|
|
94
|
-
return
|
|
95
|
-
const key = subslot.type
|
|
96
|
-
? `${singularSlotName(slotName)} ${subslot.type}`
|
|
97
|
-
: `${singularSlotName(slotName)} ${slotIndex + 1}`;
|
|
98
|
-
return _jsx(Member, { ...props, slot: subslot, slotName: key }, key);
|
|
99
|
-
});
|
|
125
|
+
return _jsx(EntryList, { ...props, entries: slot });
|
|
100
126
|
}
|
|
101
127
|
// if this is an explicitly typed schema, make a type-selecting dropdown
|
|
102
128
|
// that can be used to change its type
|
|
@@ -107,13 +133,11 @@ const Member = observer(function Member(props) {
|
|
|
107
133
|
// visible, not just present in the collapsed DOM. The key flips the
|
|
108
134
|
// accordion between filtered/unfiltered mounts so the new
|
|
109
135
|
// defaultExpanded takes effect (Accordion reads it only on mount).
|
|
110
|
-
, { defaultExpanded: filter ? true : displayDefaultExpanded(slot, expandedDisplayId), className: classes.accordion, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ExpandMoreIcon, {
|
|
111
|
-
if (evt.target.value !== slot
|
|
112
|
-
schema
|
|
113
|
-
type: evt.target.value,
|
|
114
|
-
});
|
|
136
|
+
, { defaultExpanded: filter ? true : displayDefaultExpanded(slot, expandedDisplayId), className: classes.accordion, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ExpandMoreIcon, {}), children: _jsx(Typography, { children: [...path, slotName].join('➔') }) }), _jsxs(AccordionDetails, { className: classes.expansionPanelDetails, children: [entry && slot ? (_jsx(EntryControls, { schema: schema, title: slotName, slot: slot, entry: entry })) : null, typeNameChoices.length ? (_jsx(TypeSelector, { typeNameChoices: typeNameChoices, slotName: slotName, slot: slot, onChange: evt => {
|
|
137
|
+
if (evt.target.value !== slot?.type) {
|
|
138
|
+
setConf(schema, slotName, { type: evt.target.value });
|
|
115
139
|
}
|
|
116
|
-
} })) : null, _jsx("div", { className: classes.noOverflow, children: _jsx(Schema, { schema: slot, path: [...path, slotName], filter: childFilter, expandedDisplayId: expandedDisplayId }) })] })] }, filter ? 'filtered' : 'unfiltered'));
|
|
140
|
+
} })) : null, slot ? (_jsx("div", { className: classes.noOverflow, children: _jsx(Schema, { schema: slot, path: [...path, slotName], filter: childFilter, expandedDisplayId: expandedDisplayId }) })) : null] })] }, filter ? 'filtered' : 'unfiltered'));
|
|
117
141
|
}
|
|
118
142
|
else if (isConfigurationSlot(schema, slotName)) {
|
|
119
143
|
return _jsx(SlotEditor, { slot: makeSlotFacade(schema, slotName) }, slotName);
|
|
@@ -122,6 +146,30 @@ const Member = observer(function Member(props) {
|
|
|
122
146
|
return null;
|
|
123
147
|
}
|
|
124
148
|
});
|
|
149
|
+
// An array of sub-schemas, one accordion per entry. A list whose entries are a
|
|
150
|
+
// `ConfigurationSchemaUnion` also offers each entry's type and place, and a new
|
|
151
|
+
// entry of any member type; a pluggable union such as a track's `displays` is
|
|
152
|
+
// not one, since a display is added through the track.
|
|
153
|
+
const EntryList = observer(function EntryList(props) {
|
|
154
|
+
const { classes } = useStyles();
|
|
155
|
+
const { slotName, slotSchema, schema, entries } = props;
|
|
156
|
+
const singular = singularSlotName(slotName);
|
|
157
|
+
const union = arraySlotUnion(slotSchema);
|
|
158
|
+
const choices = union ? Object.keys(union.members) : [];
|
|
159
|
+
return (_jsxs(_Fragment, { children: [entries.map((subslot, index) => {
|
|
160
|
+
const title = `${singular} ${index + 1}`;
|
|
161
|
+
return (_jsx(Member
|
|
162
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key -- a config entry has no identity but its place
|
|
163
|
+
, { ...props, slot: subslot, slotName: union || !subslot.type ? title : `${title} ${subslot.type}`, entry: union
|
|
164
|
+
? { listName: slotName, choices, index, count: entries.length }
|
|
165
|
+
: undefined }, index));
|
|
166
|
+
}), union ? (_jsx(TextField, { className: classes.filter, select: true, size: "small", label: `Add ${singular}`, value: "", onChange: evt => {
|
|
167
|
+
setConf(schema, slotName, [
|
|
168
|
+
...listSnapshot(schema, slotName),
|
|
169
|
+
{ type: evt.target.value },
|
|
170
|
+
]);
|
|
171
|
+
}, fullWidth: true, children: choices.map(type => (_jsx(MenuItem, { value: type, children: type }, type))) })) : null] }));
|
|
172
|
+
});
|
|
125
173
|
const Schema = observer(function Schema({ schema, path = [], filter = '', expandedDisplayId, }) {
|
|
126
174
|
const { classes } = useStyles();
|
|
127
175
|
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
@@ -147,7 +195,7 @@ const ConfigurationEditor = observer(function ConfigurationEditor({ model, }) {
|
|
|
147
195
|
}
|
|
148
196
|
const key = readConfObject(target, 'trackId');
|
|
149
197
|
const name = readConfObject(target, 'name');
|
|
150
|
-
return (_jsxs(Accordion, { defaultExpanded: true, className: classes.accordion, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ExpandMoreIcon, {
|
|
198
|
+
return (_jsxs(Accordion, { defaultExpanded: true, className: classes.accordion, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ExpandMoreIcon, {}), children: _jsx(Typography, { children: _jsx(SanitizedHTML, { html: name ?? 'Configuration' }) }) }), _jsxs(AccordionDetails, { className: classes.expansionPanelDetails, "data-testid": "configEditor", children: [_jsx(TextField, { className: classes.filter, label: "Filter options", value: filter, onChange: evt => {
|
|
151
199
|
setFilter(evt.target.value);
|
|
152
200
|
}, size: "small", fullWidth: true, slotProps: {
|
|
153
201
|
input: {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface ListSlot {
|
|
2
|
+
name: string;
|
|
3
|
+
value: string[];
|
|
4
|
+
description: string;
|
|
5
|
+
set: (arg: string[]) => void;
|
|
6
|
+
}
|
|
7
|
+
/** #slotEditor a text field and color picker per entry, with add and delete */
|
|
8
|
+
declare const ColorArrayEditor: ({ slot, }: {
|
|
9
|
+
slot: ListSlot;
|
|
10
|
+
}) => import("react").JSX.Element;
|
|
11
|
+
/** #slotEditor a dropdown of the `model`'s members per entry, with add and delete */
|
|
12
|
+
declare const StringEnumArrayEditor: ({ slot, }: {
|
|
13
|
+
slot: ListSlot & {
|
|
14
|
+
choices?: string[];
|
|
15
|
+
};
|
|
16
|
+
}) => import("react").JSX.Element;
|
|
17
|
+
export { ColorArrayEditor, StringEnumArrayEditor };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import AddIcon from '@mui/icons-material/Add';
|
|
3
|
+
import DeleteIcon from '@mui/icons-material/Delete';
|
|
4
|
+
import { FormHelperText, IconButton, InputLabel, List, ListItem, MenuItem, } from '@mui/material';
|
|
5
|
+
import { observer } from 'mobx-react';
|
|
6
|
+
import ColorEditor from './ColorEditor.js';
|
|
7
|
+
import ConfigurationTextField from './ConfigurationTextField.js';
|
|
8
|
+
// A list slot whose entries each take one control: the list is written whole
|
|
9
|
+
// on every change, and "add" appends an entry the slot already admits, so no
|
|
10
|
+
// write can hold an entry the slot refuses.
|
|
11
|
+
const EntryList = observer(function EntryList({ slot, entry, added, }) {
|
|
12
|
+
const value = [...slot.value];
|
|
13
|
+
const addition = added(value);
|
|
14
|
+
return (_jsxs(_Fragment, { children: [slot.name ? _jsx(InputLabel, { children: slot.name }) : null, _jsxs(List, { disablePadding: true, children: [value.map((v, idx) => (_jsxs(ListItem, { disableGutters: true, children: [entry(v, next => {
|
|
15
|
+
slot.set(value.map((old, i) => (i === idx ? next : old)));
|
|
16
|
+
}), _jsx(IconButton, { "aria-label": "delete entry", onClick: () => {
|
|
17
|
+
slot.set(value.filter((_, i) => i !== idx));
|
|
18
|
+
}, children: _jsx(DeleteIcon, {}) })] }, idx))), _jsx(ListItem, { disableGutters: true, children: _jsx(IconButton, { "data-testid": `entryAdd-${slot.name}`, "aria-label": "add entry", disabled: addition === undefined, onClick: () => {
|
|
19
|
+
if (addition !== undefined) {
|
|
20
|
+
slot.set([...value, addition]);
|
|
21
|
+
}
|
|
22
|
+
}, children: _jsx(AddIcon, {}) }) })] }), _jsx(FormHelperText, { children: slot.description })] }));
|
|
23
|
+
});
|
|
24
|
+
/** #slotEditor a text field and color picker per entry, with add and delete */
|
|
25
|
+
const ColorArrayEditor = observer(function ColorArrayEditor({ slot, }) {
|
|
26
|
+
return (_jsx(EntryList, { slot: slot, added: value => value.at(-1) ?? 'black', entry: (color, set) => (_jsx(ColorEditor, { slot: { name: '', description: '', value: color, set } })) }));
|
|
27
|
+
});
|
|
28
|
+
/** #slotEditor a dropdown of the `model`'s members per entry, with add and delete */
|
|
29
|
+
const StringEnumArrayEditor = observer(function StringEnumArrayEditor({ slot, }) {
|
|
30
|
+
const choices = slot.choices ?? [];
|
|
31
|
+
return (_jsx(EntryList, { slot: slot, added: () => choices[0], entry: (member, set) => (_jsx(ConfigurationTextField, { select: true, value: member, onChange: evt => {
|
|
32
|
+
set(evt.target.value);
|
|
33
|
+
}, children: choices.map(choice => (_jsx(MenuItem, { value: choice, children: choice }, choice))) })) }));
|
|
34
|
+
});
|
|
35
|
+
export { ColorArrayEditor, StringEnumArrayEditor };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { MonospaceTextField } from '@jbrowse/core/ui';
|
|
4
|
+
import { alpha } from '@jbrowse/core/ui/palette';
|
|
4
5
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
5
6
|
import { observer } from 'mobx-react';
|
|
6
7
|
const useStyles = makeStyles()(theme => ({
|
|
@@ -9,22 +10,17 @@ const useStyles = makeStyles()(theme => ({
|
|
|
9
10
|
background: theme.palette.background.default,
|
|
10
11
|
width: '100%',
|
|
11
12
|
marginTop: '16px',
|
|
12
|
-
border:
|
|
13
|
+
border: `1px solid ${alpha(theme.palette.text.primary, 0.42)}`,
|
|
13
14
|
},
|
|
14
15
|
}));
|
|
15
16
|
/** #slotEditor monospace textarea holding arbitrary JSON */
|
|
16
17
|
const JsonEditor = observer(function JsonEditor({ slot, }) {
|
|
17
18
|
const { classes } = useStyles();
|
|
18
|
-
// A `maybeFrozen` slot is `undefined` when unset
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
// Show what unset resolves to instead, as BooleanEditor does.
|
|
24
|
-
const [contents, setContents] = useState(() => {
|
|
25
|
-
const shown = slot.value ?? slot.promotedBase;
|
|
26
|
-
return shown === undefined ? '' : JSON.stringify(shown, null, 2);
|
|
27
|
-
});
|
|
19
|
+
// A `maybeFrozen` slot is `undefined` when unset, and `JSON.stringify(undefined)`
|
|
20
|
+
// returns the *value* `undefined` rather than a string — which handed
|
|
21
|
+
// MonospaceTextField an undefined `value` and made the field uncontrolled
|
|
22
|
+
// until the first keystroke (React then warns on the switch).
|
|
23
|
+
const [contents, setContents] = useState(() => slot.value === undefined ? '' : JSON.stringify(slot.value, null, 2));
|
|
28
24
|
const [error, setError] = useState();
|
|
29
25
|
return (_jsx(MonospaceTextField, { className: classes.jsonEditor, label: slot.name, value: contents, error: error, helperText: slot.description, onChange: val => {
|
|
30
26
|
setContents(val);
|
|
@@ -5,16 +5,7 @@ import ConfigurationTextField from './ConfigurationTextField.js';
|
|
|
5
5
|
/** #slotEditor numeric text field */
|
|
6
6
|
const NumberEditor = observer(function NumberEditor({ slot, integer = false, }) {
|
|
7
7
|
const [val, setVal] = useState(slot.value === undefined ? '' : String(slot.value));
|
|
8
|
-
|
|
9
|
-
// empty box is the honest rendering of it — a number field, unlike a
|
|
10
|
-
// checkbox, has a state for "nothing". What it did not say is what the track
|
|
11
|
-
// is therefore drawing at, so `featureHeight` read as a blank with no clue
|
|
12
|
-
// that reads are 7px tall. The placeholder is that, in the affordance meant
|
|
13
|
-
// for it; the label has to float for MUI to draw one over a labelled field.
|
|
14
|
-
const placeholder = slot.value === undefined && slot.promotedBase !== undefined
|
|
15
|
-
? String(slot.promotedBase)
|
|
16
|
-
: undefined;
|
|
17
|
-
return (_jsx(ConfigurationTextField, { label: slot.name, helperText: slot.description, placeholder: placeholder, slotProps: placeholder ? { inputLabel: { shrink: true } } : undefined, value: val, onChange: evt => {
|
|
8
|
+
return (_jsx(ConfigurationTextField, { label: slot.name, helperText: slot.description, value: val, onChange: evt => {
|
|
18
9
|
const v = evt.target.value;
|
|
19
10
|
setVal(v);
|
|
20
11
|
const num = Number(v);
|
|
@@ -10,16 +10,21 @@ import BooleanEditor from './BooleanEditor.js';
|
|
|
10
10
|
import CallbackEditor from './CallbackEditor.js';
|
|
11
11
|
import ColorEditor from './ColorEditor.js';
|
|
12
12
|
import ConfigurationTextField from './ConfigurationTextField.js';
|
|
13
|
+
import { ColorArrayEditor, StringEnumArrayEditor } from './EntryListEditor.js';
|
|
13
14
|
import IntegerEditor from './IntegerEditor.js';
|
|
14
15
|
import JsonEditor from './JsonEditor.js';
|
|
15
16
|
import NumberEditor from './NumberEditor.js';
|
|
16
17
|
import NumberMapEditor from './NumberMapEditor.js';
|
|
17
|
-
import StringArrayEditor from './StringArrayEditor.js';
|
|
18
|
+
import StringArrayEditor, { ExpressionArrayEditor, } from './StringArrayEditor.js';
|
|
18
19
|
import StringArrayMapEditor from './StringArrayMapEditor.js';
|
|
20
|
+
import StringMapEditor from './StringMapEditor.js';
|
|
19
21
|
import { useSlotEditorStyles } from './useSlotEditorStyles.js';
|
|
22
|
+
// Also drives `maybeString`, whose value is `undefined` while unset: clearing
|
|
23
|
+
// the field leaves the empty string, which is a value, and the reset button
|
|
24
|
+
// returns the slot to unset.
|
|
20
25
|
/** #slotEditor single-line text field */
|
|
21
26
|
const StringEditor = observer(function StringEditor({ slot, }) {
|
|
22
|
-
return (_jsx(ConfigurationTextField, { label: slot.name, helperText: slot.description, value: slot.value, onChange: evt => {
|
|
27
|
+
return (_jsx(ConfigurationTextField, { label: slot.name, helperText: slot.description, value: slot.value ?? '', onChange: evt => {
|
|
23
28
|
slot.set(evt.target.value);
|
|
24
29
|
} }));
|
|
25
30
|
});
|
|
@@ -36,18 +41,10 @@ const UNSET_CHOICE = '';
|
|
|
36
41
|
/** #slotEditor dropdown of the `model`'s members */
|
|
37
42
|
const StringEnumEditor = observer(function StringEnumEditor({ slot, }) {
|
|
38
43
|
const nullable = slot.type === 'maybeStringEnum';
|
|
39
|
-
// Naming the member unset resolves to, rather than a bare "default": every
|
|
40
|
-
// other choice in the list is a value the user can read off the plot, and
|
|
41
|
-
// this one was the only one that said nothing about what picking it draws.
|
|
42
|
-
// `promotedBase` is the honest half to show — the editor's target is often a
|
|
43
|
-
// detached config node, so it cannot see a value promoted this session.
|
|
44
|
-
const unsetLabel = typeof slot.promotedBase === 'string'
|
|
45
|
-
? `default (${slot.promotedBase})`
|
|
46
|
-
: 'default';
|
|
47
44
|
return (_jsxs(ConfigurationTextField, { value: slot.value ?? UNSET_CHOICE, label: slot.name, select: true, helperText: slot.description, onChange: evt => {
|
|
48
45
|
const { value } = evt.target;
|
|
49
46
|
slot.set(value === UNSET_CHOICE ? undefined : value);
|
|
50
|
-
}, children: [nullable ? (_jsx(MenuItem, { value: UNSET_CHOICE, children: _jsx("em", { children:
|
|
47
|
+
}, children: [nullable ? (_jsx(MenuItem, { value: UNSET_CHOICE, children: _jsx("em", { children: "default" }) })) : null, (slot.choices ?? []).map(str => (_jsx(MenuItem, { value: str, children: str }, str)))] }));
|
|
51
48
|
});
|
|
52
49
|
/** #slotEditor URL, local file path (desktop) or file blob (browser) */
|
|
53
50
|
const FileSelectorWrapper = observer(function FileSelectorWrapper({ slot, }) {
|
|
@@ -62,11 +59,18 @@ const FileSelectorWrapper = observer(function FileSelectorWrapper({ slot, }) {
|
|
|
62
59
|
// The editors stay fully typed internally; only this lookup is untyped.
|
|
63
60
|
const valueComponents = {
|
|
64
61
|
string: StringEditor,
|
|
62
|
+
maybeString: StringEditor,
|
|
63
|
+
featureField: StringEditor,
|
|
65
64
|
text: TextEditor,
|
|
66
65
|
fileLocation: FileSelectorWrapper,
|
|
66
|
+
maybeFileLocation: FileSelectorWrapper,
|
|
67
67
|
stringArray: StringArrayEditor,
|
|
68
|
+
expressionArray: ExpressionArrayEditor,
|
|
69
|
+
colorArray: ColorArrayEditor,
|
|
70
|
+
stringEnumArray: StringEnumArrayEditor,
|
|
68
71
|
stringArrayMap: StringArrayMapEditor,
|
|
69
72
|
numberMap: NumberMapEditor,
|
|
73
|
+
stringMap: StringMapEditor,
|
|
70
74
|
number: NumberEditor,
|
|
71
75
|
maybeNumber: NumberEditor,
|
|
72
76
|
integer: IntegerEditor,
|
|
@@ -85,8 +89,10 @@ const SlotEditor = observer(function SlotEditor({ slot, }) {
|
|
|
85
89
|
// editor mode is UI-only state, derived once from whether the stored value is
|
|
86
90
|
// a jexl callback. Toggling rewrites the value; typing a "jexl:" prefix into a
|
|
87
91
|
// value field does not auto-swap the editor, since this state only changes on
|
|
88
|
-
// the explicit toggle.
|
|
89
|
-
|
|
92
|
+
// the explicit toggle. A featureField's `jexl:` expression is a field, typed
|
|
93
|
+
// in its own text field.
|
|
94
|
+
const callback = slot.contextVariable.length > 0;
|
|
95
|
+
const [callbackMode, setCallbackMode] = useState(() => callback && isCallbackValue(slot.value));
|
|
90
96
|
// bumped on reset to remount the value editor: the buffered editors (number,
|
|
91
97
|
// json, callback) seed internal state from slot.value only on mount, so an
|
|
92
98
|
// external set wouldn't otherwise show in the field
|
|
@@ -98,14 +104,12 @@ const SlotEditor = observer(function SlotEditor({ slot, }) {
|
|
|
98
104
|
const ValueComponent = callbackMode
|
|
99
105
|
? CallbackEditor
|
|
100
106
|
: (TypedComponent ?? StringEditor);
|
|
101
|
-
// a promotable slot's default is its inherit sentinel, so resetting it here
|
|
102
|
-
// doubles as "un-pin / follow the session-wide default"
|
|
103
107
|
const { modified } = slot;
|
|
104
108
|
return (_jsxs(Paper, { className: classes.paper, children: [_jsx("div", { className: classes.paperContent, children: _jsx(ValueComponent, { slot: slot }, resetNonce) }), modified ? (_jsx("div", { className: classes.resetButton, children: _jsx(Tooltip, { title: "Reset to default", children: _jsx(IconButton, { "aria-label": "reset to default", onClick: () => {
|
|
105
109
|
slot.set(slot.defaultValue);
|
|
106
|
-
setCallbackMode(isCallbackValue(slot.defaultValue));
|
|
110
|
+
setCallbackMode(callback && isCallbackValue(slot.defaultValue));
|
|
107
111
|
setResetNonce(nonce => nonce + 1);
|
|
108
|
-
}, children: _jsx(RestartAltIcon, {}) }) }) })) : null,
|
|
112
|
+
}, children: _jsx(RestartAltIcon, {}) }) }) })) : null, callback ? (_jsx("div", { className: classes.slotModeSwitch, children: _jsx(Tooltip, { title: callbackMode
|
|
109
113
|
? 'Editing as a callback (Jexl expression). Click to use a fixed value.'
|
|
110
114
|
: 'Editing as a fixed value. Click to use a callback (Jexl expression).', children: _jsx(IconButton, { color: callbackMode ? 'primary' : 'default', onClick: () => {
|
|
111
115
|
if (callbackMode) {
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
interface ArraySlot {
|
|
2
|
+
name: string;
|
|
3
|
+
value: string[];
|
|
4
|
+
set: (arg: string[]) => void;
|
|
5
|
+
description: string;
|
|
6
|
+
}
|
|
1
7
|
/** #slotEditor "todolist" of text fields, one per entry, with add and delete */
|
|
2
|
-
declare const StringArrayEditor: ({ slot, }: {
|
|
3
|
-
slot:
|
|
4
|
-
|
|
5
|
-
value: string[];
|
|
6
|
-
set: (arg: string[]) => void;
|
|
7
|
-
description: string;
|
|
8
|
-
};
|
|
8
|
+
declare const StringArrayEditor: ({ slot, prefix, }: {
|
|
9
|
+
slot: ArraySlot;
|
|
10
|
+
prefix?: string;
|
|
9
11
|
}) => import("react").JSX.Element;
|
|
12
|
+
/** #slotEditor "todolist" of text fields, each after a fixed `jexl:` */
|
|
13
|
+
declare const ExpressionArrayEditor: ({ slot, }: {
|
|
14
|
+
slot: ArraySlot;
|
|
15
|
+
}) => import("react").JSX.Element;
|
|
16
|
+
export { ExpressionArrayEditor };
|
|
10
17
|
export default StringArrayEditor;
|
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { JEXL_PREFIX } from '@jbrowse/core/util/jexlStrings';
|
|
2
3
|
import DeleteIcon from '@mui/icons-material/Delete';
|
|
3
4
|
import { FormHelperText, IconButton, InputAdornment, InputLabel, List, ListItem, TextField, } from '@mui/material';
|
|
4
5
|
import { observer } from 'mobx-react';
|
|
5
6
|
import AddNewField from './AddNewField.js';
|
|
6
7
|
/** #slotEditor "todolist" of text fields, one per entry, with add and delete */
|
|
7
|
-
const StringArrayEditor = observer(function StringArrayEditor({ slot, }) {
|
|
8
|
+
const StringArrayEditor = observer(function StringArrayEditor({ slot, prefix = '', }) {
|
|
8
9
|
const value = [...slot.value];
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const startAdornment = prefix ? (_jsx(InputAdornment, { position: "start", children: prefix })) : undefined;
|
|
11
|
+
return (_jsxs(_Fragment, { children: [slot.name ? _jsx(InputLabel, { children: slot.name }) : null, _jsxs(List, { disablePadding: true, children: [value.map((val, idx) => (_jsx(ListItem, { disableGutters: true, children: _jsx(TextField, { fullWidth: true, value: val.slice(prefix.length), onChange: evt => {
|
|
12
|
+
slot.set(value.map((v, i) => i === idx ? prefix + evt.target.value : v));
|
|
11
13
|
}, slotProps: {
|
|
12
14
|
input: {
|
|
15
|
+
startAdornment,
|
|
13
16
|
endAdornment: (_jsx(InputAdornment, { position: "end", children: _jsx(IconButton, { onClick: () => {
|
|
14
17
|
slot.set(value.filter((_, i) => i !== idx));
|
|
15
18
|
}, children: _jsx(DeleteIcon, {}) }) })),
|
|
16
19
|
},
|
|
17
|
-
} }) }, idx))), _jsx(ListItem, { disableGutters: true, children: _jsx(AddNewField, { testid: `stringArrayAdd-${slot.name}`, onAdd: val => {
|
|
18
|
-
slot.set([...value, val]);
|
|
20
|
+
} }) }, idx))), _jsx(ListItem, { disableGutters: true, children: _jsx(AddNewField, { testid: `stringArrayAdd-${slot.name}`, startAdornment: startAdornment, onAdd: val => {
|
|
21
|
+
slot.set([...value, prefix + val]);
|
|
19
22
|
} }) })] }), _jsx(FormHelperText, { children: slot.description })] }));
|
|
20
23
|
});
|
|
24
|
+
// `jexl:` sits fixed in front of each entry, so no keystroke writes one the
|
|
25
|
+
// slot refuses
|
|
26
|
+
/** #slotEditor "todolist" of text fields, each after a fixed `jexl:` */
|
|
27
|
+
const ExpressionArrayEditor = observer(function ExpressionArrayEditor({ slot, }) {
|
|
28
|
+
return _jsx(StringArrayEditor, { slot: slot, prefix: JEXL_PREFIX });
|
|
29
|
+
});
|
|
30
|
+
export { ExpressionArrayEditor };
|
|
21
31
|
export default StringArrayEditor;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** #slotEditor one card per key, each holding that key's text field */
|
|
2
|
+
declare const StringMapEditor: ({ slot, }: {
|
|
3
|
+
slot: {
|
|
4
|
+
name: string;
|
|
5
|
+
value: Map<string, string>;
|
|
6
|
+
set: (val: Record<string, string>) => void;
|
|
7
|
+
description: string;
|
|
8
|
+
};
|
|
9
|
+
}) => import("react").JSX.Element;
|
|
10
|
+
export default StringMapEditor;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { TextField } from '@mui/material';
|
|
3
|
+
import { observer } from 'mobx-react';
|
|
4
|
+
import MapSlotEditor from './MapSlotEditor.js';
|
|
5
|
+
/** #slotEditor one card per key, each holding that key's text field */
|
|
6
|
+
const StringMapEditor = observer(function StringMapEditor({ slot, }) {
|
|
7
|
+
return (_jsx(MapSlotEditor, { name: slot.name, description: slot.description, entries: [...slot.value], emptyValue: "", setMap: val => {
|
|
8
|
+
slot.set(val);
|
|
9
|
+
}, renderValue: (val, set) => (_jsx(TextField, { fullWidth: true, value: val, onChange: evt => {
|
|
10
|
+
set(evt.target.value);
|
|
11
|
+
} })) }));
|
|
12
|
+
});
|
|
13
|
+
export default StringMapEditor;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
|
|
2
2
|
declare const TypeSelector: ({ typeNameChoices, slot, slotName, onChange, }: {
|
|
3
3
|
typeNameChoices: string[];
|
|
4
|
-
slot: AnyConfigurationModel;
|
|
4
|
+
slot: AnyConfigurationModel | undefined;
|
|
5
5
|
slotName: string;
|
|
6
6
|
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
7
7
|
}) => import("react").JSX.Element;
|
|
@@ -4,6 +4,6 @@ import { observer } from 'mobx-react';
|
|
|
4
4
|
import { useSlotEditorStyles } from './useSlotEditorStyles.js';
|
|
5
5
|
const TypeSelector = observer(function TypeSelector({ typeNameChoices, slot, slotName, onChange, }) {
|
|
6
6
|
const { classes } = useSlotEditorStyles();
|
|
7
|
-
return (_jsx(Paper, { className: classes.paper, children: _jsx("div", { className: classes.paperContent, children: _jsx(TextField, { value: slot
|
|
7
|
+
return (_jsx(Paper, { className: classes.paper, children: _jsx("div", { className: classes.paperContent, children: _jsx(TextField, { value: slot?.type ?? '', label: `Type of ${slotName}`, select: true, variant: "outlined", fullWidth: true, onChange: onChange, children: typeNameChoices.map(str => (_jsx(MenuItem, { value: str, children: str }, str))) }) }) }));
|
|
8
8
|
});
|
|
9
9
|
export default TypeSelector;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { lazy } from 'react';
|
|
2
1
|
import { ConfigurationSchema } from '@jbrowse/core/configuration';
|
|
3
2
|
import { WidgetType } from '@jbrowse/core/pluggableElementTypes';
|
|
3
|
+
import { lazyWithPreload } from '@jbrowse/core/util/lazyWithPreload';
|
|
4
4
|
import HeadingComponent from './components/HeadingComponent.js';
|
|
5
5
|
import stateModelFactory from './model.js';
|
|
6
6
|
const configSchema = ConfigurationSchema('ConfigurationEditorWidget', {});
|
|
7
|
-
const LazyConfigurationEditorComponent =
|
|
7
|
+
const LazyConfigurationEditorComponent = lazyWithPreload(() => import('./components/ConfigurationEditor.js'));
|
|
8
8
|
export default function registerConfigurationEditorWidget(pluginManager) {
|
|
9
9
|
pluginManager.addWidgetType(() => {
|
|
10
10
|
return new WidgetType({
|
|
@@ -16,25 +16,20 @@
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
declare const configSchema: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
type: {
|
|
20
|
+
type: "string";
|
|
21
|
+
} & {
|
|
22
|
+
defaultValue: string;
|
|
23
|
+
};
|
|
24
24
|
readonly adapterId: {
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
type: "string";
|
|
26
|
+
} & {
|
|
27
|
+
defaultValue: string;
|
|
27
28
|
};
|
|
28
|
-
/**
|
|
29
|
-
* #slot
|
|
30
|
-
* the features themselves, as an array of plain objects. Each needs a
|
|
31
|
-
* `uniqueId`, `refName`, `start` and `end` (0-based half-open); anything
|
|
32
|
-
* else — `name`, `type`, `score`, `strand`, nested `subfeatures` — is
|
|
33
|
-
* carried through onto the feature as-is.
|
|
34
|
-
*/
|
|
35
29
|
readonly features: {
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
type: "frozen";
|
|
31
|
+
} & {
|
|
32
|
+
defaultValue: unknown;
|
|
38
33
|
};
|
|
39
|
-
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
34
|
+
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, true, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>, any>>;
|
|
40
35
|
export default configSchema;
|
|
@@ -22,25 +22,20 @@
|
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
24
|
declare const regionsConfigSchema: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
type: {
|
|
26
|
+
type: "string";
|
|
27
|
+
} & {
|
|
28
|
+
defaultValue: string;
|
|
29
|
+
};
|
|
30
30
|
readonly adapterId: {
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
type: "string";
|
|
32
|
+
} & {
|
|
33
|
+
defaultValue: string;
|
|
33
34
|
};
|
|
34
|
-
/**
|
|
35
|
-
* #slot
|
|
36
|
-
* one entry per reference sequence, each with a `uniqueId`, `refName`,
|
|
37
|
-
* `start: 0` and `end` set to that sequence's length. This is what defines
|
|
38
|
-
* the assembly's reference names and sizes; no bases are supplied, so
|
|
39
|
-
* base-level views are empty.
|
|
40
|
-
*/
|
|
41
35
|
readonly features: {
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
type: "frozen";
|
|
37
|
+
} & {
|
|
38
|
+
defaultValue: unknown;
|
|
44
39
|
};
|
|
45
|
-
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
40
|
+
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, true, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>, any>>;
|
|
46
41
|
export default regionsConfigSchema;
|
|
@@ -21,25 +21,20 @@
|
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
23
|
declare const sequenceConfigSchema: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
type: {
|
|
25
|
+
type: "string";
|
|
26
|
+
} & {
|
|
27
|
+
defaultValue: string;
|
|
28
|
+
};
|
|
29
29
|
readonly adapterId: {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
type: "string";
|
|
31
|
+
} & {
|
|
32
|
+
defaultValue: string;
|
|
32
33
|
};
|
|
33
|
-
/**
|
|
34
|
-
* #slot
|
|
35
|
-
* one entry per reference sequence, each with a `uniqueId`, `refName`,
|
|
36
|
-
* `start`, `end`, and a `seq` string holding the bases for that span. The
|
|
37
|
-
* bases live in the config, so this is for small sequences — a plasmid, a
|
|
38
|
-
* test contig — not a genome.
|
|
39
|
-
*/
|
|
40
34
|
readonly features: {
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
type: "frozen";
|
|
36
|
+
} & {
|
|
37
|
+
defaultValue: unknown;
|
|
43
38
|
};
|
|
44
|
-
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
39
|
+
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, true, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>, any>>;
|
|
45
40
|
export default sequenceConfigSchema;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { normalizeUriSnapshot } from '../normalizeUriSnapshot.ts';
|
|
2
1
|
import type { Instance } from '@jbrowse/mobx-state-tree';
|
|
3
|
-
export declare
|
|
2
|
+
export declare function normalizeSnapshot(snap: Record<string, unknown>): Record<string, unknown>;
|
|
4
3
|
/**
|
|
5
4
|
* #config NcbiSequenceReportAliasAdapter
|
|
6
5
|
* can read "sequence_report.tsv" type files from NCBI
|
|
@@ -57,27 +56,19 @@ export declare const normalizeSnapshot: typeof normalizeUriSnapshot;
|
|
|
57
56
|
* ```
|
|
58
57
|
*/
|
|
59
58
|
declare const NcbiSequenceReportAliasAdapterConfigSchema: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
*/
|
|
59
|
+
type: {
|
|
60
|
+
type: "string";
|
|
61
|
+
} & {
|
|
62
|
+
defaultValue: string;
|
|
63
|
+
};
|
|
66
64
|
readonly location: {
|
|
67
|
-
|
|
68
|
-
readonly defaultValue: {
|
|
69
|
-
readonly uri: "/path/to/my/sequence_report.tsv";
|
|
70
|
-
readonly locationType: "UriLocation";
|
|
71
|
-
};
|
|
65
|
+
type: "maybeFileLocation";
|
|
72
66
|
};
|
|
73
|
-
/**
|
|
74
|
-
* #slot
|
|
75
|
-
*/
|
|
76
67
|
readonly useNameOverride: {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
68
|
+
type: "boolean";
|
|
69
|
+
} & {
|
|
70
|
+
defaultValue: boolean;
|
|
80
71
|
};
|
|
81
|
-
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
72
|
+
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, true, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>, any>>;
|
|
82
73
|
export type NcbiSequenceReportAliasAdapterConfig = Instance<typeof NcbiSequenceReportAliasAdapterConfigSchema>;
|
|
83
74
|
export default NcbiSequenceReportAliasAdapterConfigSchema;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { ConfigurationSchema } from '@jbrowse/core/configuration';
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { ConfigurationSchema, expandUriShorthand, } from '@jbrowse/core/configuration';
|
|
2
|
+
export function normalizeSnapshot(snap) {
|
|
3
|
+
return expandUriShorthand(snap, 'location');
|
|
4
|
+
}
|
|
4
5
|
/**
|
|
5
6
|
* #config NcbiSequenceReportAliasAdapter
|
|
6
7
|
* can read "sequence_report.tsv" type files from NCBI
|
|
@@ -64,11 +65,7 @@ const NcbiSequenceReportAliasAdapterConfigSchema = ConfigurationSchema('NcbiSequ
|
|
|
64
65
|
* file aliases them all without hand-writing a chromAlias table.
|
|
65
66
|
*/
|
|
66
67
|
location: {
|
|
67
|
-
type: '
|
|
68
|
-
defaultValue: {
|
|
69
|
-
uri: '/path/to/my/sequence_report.tsv',
|
|
70
|
-
locationType: 'UriLocation',
|
|
71
|
-
},
|
|
68
|
+
type: 'maybeFileLocation',
|
|
72
69
|
},
|
|
73
70
|
/**
|
|
74
71
|
* #slot
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { normalizeUriSnapshot } from '../normalizeUriSnapshot.ts';
|
|
2
1
|
import type { Instance } from '@jbrowse/mobx-state-tree';
|
|
3
|
-
export declare
|
|
2
|
+
export declare function normalizeSnapshot(snap: Record<string, unknown>): Record<string, unknown>;
|
|
4
3
|
/**
|
|
5
4
|
* #config RefNameAliasAdapter
|
|
6
5
|
* can read "chromAliases" type files from UCSC or any tab separated file of
|
|
@@ -55,40 +54,24 @@ export declare const normalizeSnapshot: typeof normalizeUriSnapshot;
|
|
|
55
54
|
* ```
|
|
56
55
|
*/
|
|
57
56
|
declare const RefNameAliasAdapter: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
* `1`-named file load against a `chr1`-named assembly.
|
|
64
|
-
*/
|
|
57
|
+
type: {
|
|
58
|
+
type: "string";
|
|
59
|
+
} & {
|
|
60
|
+
defaultValue: string;
|
|
61
|
+
};
|
|
65
62
|
readonly location: {
|
|
66
|
-
|
|
67
|
-
readonly defaultValue: {
|
|
68
|
-
readonly uri: "/path/to/my/aliases.txt";
|
|
69
|
-
readonly locationType: "UriLocation";
|
|
70
|
-
};
|
|
63
|
+
type: "maybeFileLocation";
|
|
71
64
|
};
|
|
72
|
-
/**
|
|
73
|
-
* #slot
|
|
74
|
-
* by default, the "ref names that match the fasta" are assumed to be in the
|
|
75
|
-
* first column (0), change this variable if needed
|
|
76
|
-
*/
|
|
77
65
|
readonly refNameColumn: {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
66
|
+
type: "number";
|
|
67
|
+
} & {
|
|
68
|
+
defaultValue: number;
|
|
81
69
|
};
|
|
82
|
-
/**
|
|
83
|
-
* #slot
|
|
84
|
-
* refNameColumnHeaderName
|
|
85
|
-
*/
|
|
86
70
|
readonly refNameColumnHeaderName: {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
readonly advanced: true;
|
|
71
|
+
type: "string";
|
|
72
|
+
} & {
|
|
73
|
+
defaultValue: string;
|
|
91
74
|
};
|
|
92
|
-
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
75
|
+
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, true, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>, any>>;
|
|
93
76
|
export type RefNameAliasAdapterConfig = Instance<typeof RefNameAliasAdapter>;
|
|
94
77
|
export default RefNameAliasAdapter;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { ConfigurationSchema } from '@jbrowse/core/configuration';
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { ConfigurationSchema, expandUriShorthand, } from '@jbrowse/core/configuration';
|
|
2
|
+
export function normalizeSnapshot(snap) {
|
|
3
|
+
return expandUriShorthand(snap, 'location');
|
|
4
|
+
}
|
|
4
5
|
/**
|
|
5
6
|
* #config RefNameAliasAdapter
|
|
6
7
|
* can read "chromAliases" type files from UCSC or any tab separated file of
|
|
@@ -59,15 +60,11 @@ const RefNameAliasAdapter = ConfigurationSchema('RefNameAliasAdapter', {
|
|
|
59
60
|
* #slot
|
|
60
61
|
* location of the alias table: a UCSC `chromAlias.txt`, or any
|
|
61
62
|
* tab-separated file whose rows each list the alternate names of one
|
|
62
|
-
* reference sequence (`chr1<TAB>1<TAB>NC_000001.11`).
|
|
63
|
+
* reference sequence (`chr1<TAB>1<TAB>NC_000001.11`). The table lets a
|
|
63
64
|
* `1`-named file load against a `chr1`-named assembly.
|
|
64
65
|
*/
|
|
65
66
|
location: {
|
|
66
|
-
type: '
|
|
67
|
-
defaultValue: {
|
|
68
|
-
uri: '/path/to/my/aliases.txt',
|
|
69
|
-
locationType: 'UriLocation',
|
|
70
|
-
},
|
|
67
|
+
type: 'maybeFileLocation',
|
|
71
68
|
},
|
|
72
69
|
/**
|
|
73
70
|
* #slot
|
package/esm/aliasUtils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type PluginManager from '@jbrowse/core/PluginManager';
|
|
2
2
|
import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
3
3
|
import type { FileLocation } from '@jbrowse/core/util/types';
|
|
4
|
-
export declare function readAliasRows(loc: FileLocation, pluginManager?: PluginManager, opts?: BaseOptions): Promise<string[][]>;
|
|
4
|
+
export declare function readAliasRows(loc: FileLocation | undefined, pluginManager?: PluginManager, opts?: BaseOptions): Promise<string[][]>;
|
package/esm/aliasUtils.js
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
import { fetchAndMaybeUnzipText } from '@jbrowse/core/util';
|
|
2
2
|
import { openLocation } from '@jbrowse/core/util/io';
|
|
3
|
-
import { isUriLocation } from '@jbrowse/core/util/types';
|
|
4
|
-
// The alias adapters' `location` slots default to a "/path/to/my/..."
|
|
5
|
-
// placeholder; that (and a blank uri) means "no file configured", so the
|
|
6
|
-
// adapter yields no aliases rather than trying to fetch the placeholder path.
|
|
7
|
-
function isUnconfiguredLocation(loc) {
|
|
8
|
-
return isUriLocation(loc) && (!loc.uri || loc.uri.startsWith('/path/to/my/'));
|
|
9
|
-
}
|
|
10
3
|
// Reads a tab-separated alias file into rows of columns, dropping blank lines.
|
|
11
4
|
// Returns [] when no file is configured.
|
|
12
5
|
export async function readAliasRows(loc, pluginManager, opts) {
|
|
13
|
-
if (
|
|
6
|
+
if (!loc) {
|
|
14
7
|
return [];
|
|
15
8
|
}
|
|
16
9
|
// fetchAndMaybeUnzipText rather than readFile('utf8') so the read reports byte
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/plugin-config",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "JBrowse 2 config utilities",
|
|
6
6
|
"keywords": [
|
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
"esm"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@jbrowse/mobx-state-tree": "^6.
|
|
25
|
-
"@mui/icons-material": "^9.
|
|
26
|
-
"@mui/material": "^9.
|
|
27
|
-
"mobx": "^7.0.
|
|
24
|
+
"@jbrowse/mobx-state-tree": "^6.6.1",
|
|
25
|
+
"@mui/icons-material": "^9.4.0",
|
|
26
|
+
"@mui/material": "^9.4.0",
|
|
27
|
+
"mobx": "^7.0.5",
|
|
28
28
|
"mobx-react": "^10.0.2",
|
|
29
29
|
"rxjs": "^7.8.2",
|
|
30
|
-
"@jbrowse/core": "^5.0.0-beta.
|
|
30
|
+
"@jbrowse/core": "^5.0.0-beta.10"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"react": ">=19.0.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function normalizeUriSnapshot(snap: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// allows a minimal config of `{ uri: "..." }` by lifting `uri`/`baseUri` into
|
|
2
|
-
// a proper `location` fileLocation slot. Shared by the alias adapters.
|
|
3
|
-
export function normalizeUriSnapshot(snap) {
|
|
4
|
-
return snap.uri
|
|
5
|
-
? {
|
|
6
|
-
...snap,
|
|
7
|
-
location: {
|
|
8
|
-
uri: snap.uri,
|
|
9
|
-
baseUri: snap.baseUri,
|
|
10
|
-
},
|
|
11
|
-
}
|
|
12
|
-
: snap;
|
|
13
|
-
}
|