@jbrowse/plugin-config 4.3.0 → 5.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/ConfigurationEditorWidget/components/AddNewField.d.ts +4 -0
- package/esm/ConfigurationEditorWidget/components/AddNewField.js +28 -0
- package/esm/ConfigurationEditorWidget/components/BooleanEditor.d.ts +4 -2
- package/esm/ConfigurationEditorWidget/components/BooleanEditor.js +19 -4
- package/esm/ConfigurationEditorWidget/components/CallbackEditor.d.ts +4 -2
- package/esm/ConfigurationEditorWidget/components/CallbackEditor.js +24 -42
- package/esm/ConfigurationEditorWidget/components/ColorEditor.d.ts +3 -11
- package/esm/ConfigurationEditorWidget/components/ColorEditor.js +28 -20
- package/esm/ConfigurationEditorWidget/components/ConfigurationEditor.d.ts +2 -3
- package/esm/ConfigurationEditorWidget/components/ConfigurationEditor.js +108 -17
- package/esm/ConfigurationEditorWidget/components/ConfigurationTextField.d.ts +1 -1
- package/esm/ConfigurationEditorWidget/components/ConfigurationTextField.js +10 -2
- package/esm/ConfigurationEditorWidget/components/HeadingComponent.d.ts +1 -1
- package/esm/ConfigurationEditorWidget/components/IntegerEditor.d.ts +10 -0
- package/esm/ConfigurationEditorWidget/components/IntegerEditor.js +9 -0
- package/esm/ConfigurationEditorWidget/components/JsonEditor.d.ts +3 -1
- package/esm/ConfigurationEditorWidget/components/JsonEditor.js +27 -40
- package/esm/ConfigurationEditorWidget/components/MapEntryCard.d.ts +8 -0
- package/esm/ConfigurationEditorWidget/components/MapEntryCard.js +23 -0
- package/esm/ConfigurationEditorWidget/components/MapSlotEditor.d.ts +8 -0
- package/esm/ConfigurationEditorWidget/components/MapSlotEditor.js +18 -0
- package/esm/ConfigurationEditorWidget/components/NumberEditor.d.ts +6 -4
- package/esm/ConfigurationEditorWidget/components/NumberEditor.js +24 -15
- package/esm/ConfigurationEditorWidget/components/NumberMapEditor.d.ts +4 -4
- package/esm/ConfigurationEditorWidget/components/NumberMapEditor.js +7 -31
- package/esm/ConfigurationEditorWidget/components/SlotEditor.d.ts +4 -5
- package/esm/ConfigurationEditorWidget/components/SlotEditor.js +83 -48
- package/esm/ConfigurationEditorWidget/components/StringArrayEditor.d.ts +3 -4
- package/esm/ConfigurationEditorWidget/components/StringArrayEditor.js +11 -24
- package/esm/ConfigurationEditorWidget/components/StringArrayMapEditor.d.ts +3 -6
- package/esm/ConfigurationEditorWidget/components/StringArrayMapEditor.js +14 -39
- package/esm/ConfigurationEditorWidget/components/TypeSelector.d.ts +1 -1
- package/esm/ConfigurationEditorWidget/components/TypeSelector.js +2 -2
- package/esm/ConfigurationEditorWidget/components/singularSlotName.d.ts +13 -0
- package/esm/ConfigurationEditorWidget/components/singularSlotName.js +23 -0
- package/esm/ConfigurationEditorWidget/components/useSlotEditorStyles.d.ts +2 -2
- package/esm/ConfigurationEditorWidget/components/useSlotEditorStyles.js +6 -1
- package/esm/ConfigurationEditorWidget/index.js +3 -3
- package/esm/ConfigurationEditorWidget/model.d.ts +8 -1
- package/esm/ConfigurationEditorWidget/model.js +40 -1
- package/esm/FromConfigAdapter/FromConfigAdapter.d.ts +6 -1
- package/esm/FromConfigAdapter/FromConfigAdapter.js +31 -1
- package/esm/FromConfigAdapter/configSchema.d.ts +36 -3
- package/esm/FromConfigAdapter/configSchema.js +34 -2
- package/esm/FromConfigAdapter/index.js +2 -2
- package/esm/FromConfigRegionsAdapter/FromConfigRegionsAdapter.d.ts +8 -0
- package/esm/FromConfigRegionsAdapter/FromConfigRegionsAdapter.js +10 -25
- package/esm/FromConfigRegionsAdapter/configSchema.d.ts +42 -3
- package/esm/FromConfigRegionsAdapter/configSchema.js +39 -2
- package/esm/FromConfigRegionsAdapter/index.js +2 -2
- package/esm/FromConfigSequenceAdapter/FromConfigSequenceAdapter.d.ts +15 -2
- package/esm/FromConfigSequenceAdapter/FromConfigSequenceAdapter.js +32 -40
- package/esm/FromConfigSequenceAdapter/configSchema.d.ts +41 -3
- package/esm/FromConfigSequenceAdapter/configSchema.js +38 -2
- package/esm/FromConfigSequenceAdapter/index.js +2 -2
- package/esm/NcbiSequenceReportAliasAdapter/NcbiSequenceReportAliasAdapter.d.ts +5 -4
- package/esm/NcbiSequenceReportAliasAdapter/NcbiSequenceReportAliasAdapter.js +28 -24
- package/esm/NcbiSequenceReportAliasAdapter/configSchema.d.ts +77 -9
- package/esm/NcbiSequenceReportAliasAdapter/configSchema.js +79 -12
- package/esm/NcbiSequenceReportAliasAdapter/index.js +3 -2
- package/esm/RefNameAliasAdapter/RefNameAliasAdapter.d.ts +4 -3
- package/esm/RefNameAliasAdapter/RefNameAliasAdapter.js +22 -28
- package/esm/RefNameAliasAdapter/configSchema.d.ts +87 -12
- package/esm/RefNameAliasAdapter/configSchema.js +86 -12
- package/esm/RefNameAliasAdapter/index.js +3 -2
- package/esm/aliasUtils.d.ts +4 -0
- package/esm/aliasUtils.js +26 -0
- package/esm/index.d.ts +2 -2
- package/esm/index.js +7 -7
- package/esm/normalizeUriSnapshot.d.ts +1 -0
- package/esm/normalizeUriSnapshot.js +13 -0
- package/package.json +11 -15
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import AddIcon from '@mui/icons-material/Add';
|
|
4
|
+
import { IconButton, InputAdornment, TextField } from '@mui/material';
|
|
5
|
+
// shared "add new" entry field used by the string-array and map slot editors:
|
|
6
|
+
// a text box that commits its contents via onAdd and clears itself
|
|
7
|
+
export default function AddNewField({ onAdd, testid, }) {
|
|
8
|
+
const [value, setValue] = useState('');
|
|
9
|
+
function commit() {
|
|
10
|
+
if (value !== '') {
|
|
11
|
+
onAdd(value);
|
|
12
|
+
setValue('');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return (_jsx(TextField, { fullWidth: true, value: value, placeholder: "add new", onChange: event => {
|
|
16
|
+
setValue(event.target.value);
|
|
17
|
+
}, onKeyDown: event => {
|
|
18
|
+
if (event.key === 'Enter') {
|
|
19
|
+
commit();
|
|
20
|
+
}
|
|
21
|
+
}, slotProps: {
|
|
22
|
+
input: {
|
|
23
|
+
endAdornment: (_jsx(InputAdornment, { position: "end", children: _jsx(IconButton, { "data-testid": testid, disabled: value === '', onClick: () => {
|
|
24
|
+
commit();
|
|
25
|
+
}, children: _jsx(AddIcon, {}) }) })),
|
|
26
|
+
},
|
|
27
|
+
} }));
|
|
28
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
/** #slotEditor checkbox */
|
|
1
2
|
declare const BooleanEditor: ({ slot, }: {
|
|
2
3
|
slot: {
|
|
3
4
|
name: string;
|
|
4
|
-
value: boolean;
|
|
5
|
+
value: boolean | undefined;
|
|
6
|
+
promotedBase?: unknown;
|
|
5
7
|
set: (arg: boolean) => void;
|
|
6
8
|
description: string;
|
|
7
9
|
};
|
|
8
|
-
}) => import("react
|
|
10
|
+
}) => import("react").JSX.Element;
|
|
9
11
|
export default BooleanEditor;
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { LabeledCheckbox } from '@jbrowse/core/ui';
|
|
3
|
+
import { FormControl, FormHelperText } from '@mui/material';
|
|
3
4
|
import { observer } from 'mobx-react';
|
|
5
|
+
/** #slotEditor checkbox */
|
|
4
6
|
const BooleanEditor = observer(function BooleanEditor({ slot, }) {
|
|
5
|
-
return (_jsxs(FormControl, { children: [_jsx(
|
|
6
|
-
|
|
7
|
-
|
|
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 => {
|
|
21
|
+
slot.set(val);
|
|
22
|
+
} }), _jsx(FormHelperText, { children: slot.description })] }));
|
|
8
23
|
});
|
|
9
24
|
export default BooleanEditor;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import type PluginManager from '@jbrowse/core/PluginManager';
|
|
1
2
|
declare const CallbackEditor: ({ slot, }: {
|
|
2
3
|
slot: {
|
|
3
4
|
set: (arg: string) => void;
|
|
4
5
|
description: string;
|
|
5
6
|
name: string;
|
|
6
7
|
value: string;
|
|
7
|
-
contextVariable: string;
|
|
8
|
+
contextVariable: string[];
|
|
9
|
+
pluginManager: PluginManager;
|
|
8
10
|
};
|
|
9
|
-
}) => import("react
|
|
11
|
+
}) => import("react").JSX.Element;
|
|
10
12
|
export default CallbackEditor;
|
|
@@ -1,68 +1,50 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useTransition } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { MonospaceTextField } from '@jbrowse/core/ui';
|
|
4
|
+
import { ensureJexlPrefix, stringToJexlExpression, } from '@jbrowse/core/util/jexlStrings';
|
|
4
5
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
5
|
-
import { getEnv } from '@jbrowse/mobx-state-tree';
|
|
6
6
|
import HelpIcon from '@mui/icons-material/Help';
|
|
7
|
-
import { IconButton,
|
|
7
|
+
import { IconButton, Tooltip } from '@mui/material';
|
|
8
8
|
import { observer } from 'mobx-react';
|
|
9
|
-
const fontFamily = 'Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, monospace';
|
|
10
9
|
const useStyles = makeStyles()(theme => ({
|
|
11
10
|
callbackEditor: {
|
|
12
11
|
marginTop: '16px',
|
|
13
12
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
|
14
13
|
width: '100%',
|
|
15
|
-
fontFamily,
|
|
16
|
-
},
|
|
17
|
-
textAreaFont: {
|
|
18
|
-
fontFamily,
|
|
19
|
-
},
|
|
20
|
-
callbackContainer: {
|
|
21
|
-
width: '100%',
|
|
22
|
-
overflowX: 'auto',
|
|
23
|
-
},
|
|
24
|
-
error: {
|
|
25
|
-
color: 'red',
|
|
26
|
-
fontSize: '0.8em',
|
|
27
14
|
},
|
|
28
15
|
}));
|
|
29
16
|
function validateAndSetCode(code, slot, setCodeError) {
|
|
17
|
+
// empty buffer is "in progress", not invalid — don't commit and don't warn
|
|
18
|
+
const trimmed = code.trim();
|
|
19
|
+
if (trimmed === '' || trimmed === 'jexl:') {
|
|
20
|
+
setCodeError(undefined);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
30
23
|
try {
|
|
31
|
-
const jexlCode = code
|
|
32
|
-
|
|
33
|
-
throw new Error('Empty jexl expression is not valid');
|
|
34
|
-
}
|
|
35
|
-
stringToJexlExpression(jexlCode, getEnv(slot).pluginManager?.jexl);
|
|
24
|
+
const jexlCode = ensureJexlPrefix(code);
|
|
25
|
+
stringToJexlExpression(jexlCode, slot.pluginManager.jexl);
|
|
36
26
|
slot.set(jexlCode);
|
|
37
27
|
setCodeError(undefined);
|
|
38
28
|
}
|
|
39
29
|
catch (e) {
|
|
40
|
-
console.error(
|
|
30
|
+
console.error(e);
|
|
41
31
|
setCodeError(e);
|
|
42
32
|
}
|
|
43
33
|
}
|
|
44
34
|
const CallbackEditor = observer(function CallbackEditor({ slot, }) {
|
|
45
35
|
const { classes } = useStyles();
|
|
46
36
|
const [code, setCode] = useState(slot.value);
|
|
47
|
-
const [
|
|
37
|
+
const [codeError, setCodeError] = useState();
|
|
48
38
|
const [, startTransition] = useTransition();
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
} }), _jsx("p", { children: slot.description }), _jsx(Tooltip, { title: _jsxs("div", { children: ["Callbacks are written in Jexl format. Click to learn more.", _jsx("br", {}), " Names of available context items: ", slot.contextVariable] }), arrow: true, children: _jsx(IconButton, { color: "primary", onClick: () => {
|
|
62
|
-
const newWindow = window.open('https://github.com/TomFrost/Jexl', '_blank', 'noopener,noreferrer');
|
|
63
|
-
if (newWindow) {
|
|
64
|
-
newWindow.opener = null;
|
|
65
|
-
}
|
|
66
|
-
}, children: _jsx(HelpIcon, {}) }) })] })] }));
|
|
39
|
+
// if default value is a callback, will have to remove jexl:
|
|
40
|
+
// do this last
|
|
41
|
+
return (_jsxs(MonospaceTextField, { className: classes.callbackEditor, value: code, error: codeError, onChange: value => {
|
|
42
|
+
setCode(value);
|
|
43
|
+
startTransition(() => {
|
|
44
|
+
validateAndSetCode(value, slot, setCodeError);
|
|
45
|
+
});
|
|
46
|
+
}, children: [_jsx("p", { children: slot.description }), _jsx(Tooltip, { title: _jsxs("div", { children: ["Callbacks are written in Jexl format. Click to learn more.", _jsx("br", {}), " Names of available context items:", ' ', slot.contextVariable.join(', ')] }), arrow: true, children: _jsx(IconButton, { color: "primary", onClick: () => {
|
|
47
|
+
window.open('https://jbrowse.org/jb2/docs/config_guides/jexl/', '_blank', 'noopener,noreferrer');
|
|
48
|
+
}, children: _jsx(HelpIcon, {}) }) })] }));
|
|
67
49
|
});
|
|
68
50
|
export default CallbackEditor;
|
|
@@ -1,18 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
value: string;
|
|
3
|
-
label?: string;
|
|
4
|
-
TextFieldProps?: {
|
|
5
|
-
helperText: string;
|
|
6
|
-
fullWidth: boolean;
|
|
7
|
-
};
|
|
8
|
-
onChange: (arg: string) => void;
|
|
9
|
-
}) => import("react/jsx-runtime").JSX.Element;
|
|
1
|
+
/** #slotEditor text field beside a swatch that opens a color picker */
|
|
10
2
|
declare const ColorEditor: (props: {
|
|
11
3
|
slot: {
|
|
12
4
|
name: string;
|
|
13
|
-
value: string;
|
|
5
|
+
value: string | undefined;
|
|
14
6
|
description: string;
|
|
15
7
|
set: (arg: string) => void;
|
|
16
8
|
};
|
|
17
|
-
}) => import("react
|
|
9
|
+
}) => import("react").JSX.Element;
|
|
18
10
|
export default ColorEditor;
|
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Suspense,
|
|
3
|
-
import
|
|
4
|
-
import { TextField } from '@mui/material';
|
|
2
|
+
import { Suspense, lazy } from 'react';
|
|
3
|
+
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
5
4
|
import { observer } from 'mobx-react';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
5
|
+
import ConfigurationTextField from './ConfigurationTextField.js';
|
|
6
|
+
const PopoverPicker = lazy(() => import('@jbrowse/core/ui/PopoverPicker'));
|
|
7
|
+
const useStyles = makeStyles()({
|
|
8
|
+
root: {
|
|
9
|
+
display: 'flex',
|
|
10
|
+
alignItems: 'center',
|
|
11
|
+
gap: 4,
|
|
12
|
+
},
|
|
13
|
+
field: {
|
|
14
|
+
flex: 1,
|
|
15
|
+
minWidth: 0,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
// Also drives `maybeColor`, whose value is `undefined` while unset. An empty
|
|
19
|
+
// string keeps the field controlled and the picker fed, the same way
|
|
20
|
+
// NumberEditor renders an unset `maybeNumber` as an empty field. Clearing the
|
|
21
|
+
// field writes `''` rather than restoring `undefined` — "unset" is reachable
|
|
22
|
+
// through the slot's reset button, which is where every other slot type puts it.
|
|
23
|
+
/** #slotEditor text field beside a swatch that opens a color picker */
|
|
17
24
|
const ColorEditor = observer(function ColorEditor(props) {
|
|
18
25
|
const { slot } = props;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
const { classes } = useStyles();
|
|
27
|
+
const value = slot.value ?? '';
|
|
28
|
+
return (_jsxs("div", { className: classes.root, children: [_jsx(ConfigurationTextField, { value: value, label: slot.name, helperText: slot.description, className: classes.field, onChange: event => {
|
|
29
|
+
slot.set(event.target.value);
|
|
30
|
+
} }), _jsx(Suspense, { fallback: null, children: _jsx(PopoverPicker, { color: value, onChange: color => {
|
|
31
|
+
slot.set(color);
|
|
32
|
+
} }) })] }));
|
|
25
33
|
});
|
|
26
34
|
export default ColorEditor;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
|
|
2
|
-
import type { AbstractSessionModel } from '@jbrowse/core/util';
|
|
3
2
|
declare const ConfigurationEditor: ({ model, }: {
|
|
4
3
|
model: {
|
|
5
4
|
target?: AnyConfigurationModel;
|
|
5
|
+
expandedDisplayId?: string;
|
|
6
6
|
};
|
|
7
|
-
|
|
8
|
-
}) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
}) => import("react").JSX.Element;
|
|
9
8
|
export default ConfigurationEditor;
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { getSlotDefinition, getTypeNamesFromExplicitlyTypedUnion, isConfigurationModel, isConfigurationSchemaType, isConfigurationSlot, makeSlotFacade, readConfObject, } from '@jbrowse/core/configuration';
|
|
3
4
|
import SanitizedHTML from '@jbrowse/core/ui/SanitizedHTML';
|
|
4
5
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
5
6
|
import { getMembers } from '@jbrowse/mobx-state-tree';
|
|
7
|
+
import ClearIcon from '@mui/icons-material/Clear';
|
|
8
|
+
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
|
6
9
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
7
|
-
import
|
|
10
|
+
import SearchIcon from '@mui/icons-material/Search';
|
|
11
|
+
import { Accordion, AccordionDetails, AccordionSummary, Button, IconButton, InputAdornment, TextField, Typography, } from '@mui/material';
|
|
8
12
|
import { observer } from 'mobx-react';
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
13
|
+
import SlotEditor from './SlotEditor.js';
|
|
14
|
+
import TypeSelector from './TypeSelector.js';
|
|
15
|
+
import { singularSlotName } from './singularSlotName.js';
|
|
12
16
|
const useStyles = makeStyles()(theme => ({
|
|
13
17
|
icon: {
|
|
14
18
|
color: theme.palette.tertiary.contrastText || '#fff',
|
|
@@ -24,47 +28,134 @@ const useStyles = makeStyles()(theme => ({
|
|
|
24
28
|
width: '100%',
|
|
25
29
|
overflowX: 'auto',
|
|
26
30
|
},
|
|
31
|
+
filter: {
|
|
32
|
+
margin: theme.spacing(1),
|
|
33
|
+
},
|
|
34
|
+
noResults: {
|
|
35
|
+
margin: theme.spacing(2),
|
|
36
|
+
fontStyle: 'italic',
|
|
37
|
+
},
|
|
38
|
+
advancedToggle: {
|
|
39
|
+
margin: theme.spacing(1, 0),
|
|
40
|
+
textTransform: 'none',
|
|
41
|
+
},
|
|
27
42
|
}));
|
|
43
|
+
// matches a slot or sub-schema against a lowercased filter string, recursing
|
|
44
|
+
// into sub-schemas so a nested slot name keeps its ancestors visible
|
|
45
|
+
function memberMatches(schema, slotName, slotSchema, query) {
|
|
46
|
+
if (isConfigurationSlot(schema, slotName)) {
|
|
47
|
+
const { description } = getSlotDefinition(schema, slotName);
|
|
48
|
+
return (slotName.toLowerCase().includes(query) ||
|
|
49
|
+
!!description?.toLowerCase().includes(query));
|
|
50
|
+
}
|
|
51
|
+
else if (isConfigurationSchemaType(slotSchema)) {
|
|
52
|
+
const slot = schema[slotName];
|
|
53
|
+
return (slotName.toLowerCase().includes(query) ||
|
|
54
|
+
(Array.isArray(slot)
|
|
55
|
+
? slot.some(subslot => schemaMatches(subslot, query))
|
|
56
|
+
: schemaMatches(slot, query)));
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function schemaMatches(schema, query) {
|
|
63
|
+
const { properties } = getMembers(schema);
|
|
64
|
+
return Object.entries(properties).some(([slotName, slotSchema]) => memberMatches(schema, slotName, slotSchema, query));
|
|
65
|
+
}
|
|
66
|
+
// a slot flagged `advanced` in its schema definition, hidden behind a toggle
|
|
67
|
+
function isAdvancedSlot(schema, slotName) {
|
|
68
|
+
return (isConfigurationSlot(schema, slotName) &&
|
|
69
|
+
!!getSlotDefinition(schema, slotName).advanced);
|
|
70
|
+
}
|
|
71
|
+
// a display config is the only sub-schema carrying a displayId; when the editor
|
|
72
|
+
// was opened from a view, only the active display expands by default so the
|
|
73
|
+
// track's other (incompatible/inactive) displays don't crowd the panel
|
|
74
|
+
function displayDefaultExpanded(slot, expandedDisplayId) {
|
|
75
|
+
const displayId = isConfigurationModel(slot)
|
|
76
|
+
? readConfObject(slot, 'displayId')
|
|
77
|
+
: undefined;
|
|
78
|
+
return !displayId || !expandedDisplayId || displayId === expandedDisplayId;
|
|
79
|
+
}
|
|
28
80
|
const Member = observer(function Member(props) {
|
|
29
81
|
const { classes } = useStyles();
|
|
30
|
-
const { slotName, slotSchema, schema, slot = schema[slotName], path = [], } = props;
|
|
82
|
+
const { slotName, slotSchema, schema, slot = schema[slotName], path = [], filter = '', expandedDisplayId, } = props;
|
|
83
|
+
// when the sub-schema's own name matches, drop the filter for its children so
|
|
84
|
+
// the whole group stays visible; otherwise keep filtering descendants
|
|
85
|
+
const childFilter = slotName.toLowerCase().includes(filter.toLowerCase())
|
|
86
|
+
? ''
|
|
87
|
+
: filter;
|
|
31
88
|
if (isConfigurationSchemaType(slotSchema)) {
|
|
32
|
-
|
|
89
|
+
// an array-typed sub-schema (e.g. `displays`) renders one Member per entry;
|
|
90
|
+
// a single sub-schema (not an array) falls through to the accordion below.
|
|
91
|
+
// keyed on Array.isArray, not `.length` truthiness, so an empty array maps
|
|
92
|
+
// to nothing instead of being mis-handled as a single schema
|
|
93
|
+
if (Array.isArray(slot)) {
|
|
33
94
|
return slot.map((subslot, slotIndex) => {
|
|
34
95
|
const key = subslot.type
|
|
35
|
-
? `${
|
|
36
|
-
: `${
|
|
96
|
+
? `${singularSlotName(slotName)} ${subslot.type}`
|
|
97
|
+
: `${singularSlotName(slotName)} ${slotIndex + 1}`;
|
|
37
98
|
return _jsx(Member, { ...props, slot: subslot, slotName: key }, key);
|
|
38
99
|
});
|
|
39
100
|
}
|
|
101
|
+
// if this is an explicitly typed schema, make a type-selecting dropdown
|
|
102
|
+
// that can be used to change its type
|
|
40
103
|
const typeNameChoices = getTypeNamesFromExplicitlyTypedUnion(slotSchema);
|
|
41
|
-
return (_jsxs(Accordion
|
|
104
|
+
return (_jsxs(Accordion
|
|
105
|
+
// an active filter force-expands the group so a match inside an
|
|
106
|
+
// otherwise-collapsed (inactive display) sub-schema is actually
|
|
107
|
+
// visible, not just present in the collapsed DOM. The key flips the
|
|
108
|
+
// accordion between filtered/unfiltered mounts so the new
|
|
109
|
+
// 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, { className: classes.icon }), children: _jsx(Typography, { children: [...path, slotName].join('➔') }) }), _jsxs(AccordionDetails, { className: classes.expansionPanelDetails, children: [typeNameChoices.length ? (_jsx(TypeSelector, { typeNameChoices: typeNameChoices, slotName: slotName, slot: slot, onChange: evt => {
|
|
42
111
|
if (evt.target.value !== slot.type) {
|
|
43
112
|
schema.setSubschema(slotName, {
|
|
44
113
|
type: evt.target.value,
|
|
45
114
|
});
|
|
46
115
|
}
|
|
47
|
-
} })) : null, _jsx(
|
|
116
|
+
} })) : null, _jsx("div", { className: classes.noOverflow, children: _jsx(Schema, { schema: slot, path: [...path, slotName], filter: childFilter, expandedDisplayId: expandedDisplayId }) })] })] }, filter ? 'filtered' : 'unfiltered'));
|
|
48
117
|
}
|
|
49
|
-
else if (
|
|
50
|
-
return _jsx(SlotEditor, { slot:
|
|
118
|
+
else if (isConfigurationSlot(schema, slotName)) {
|
|
119
|
+
return _jsx(SlotEditor, { slot: makeSlotFacade(schema, slotName) }, slotName);
|
|
51
120
|
}
|
|
52
121
|
else {
|
|
53
122
|
return null;
|
|
54
123
|
}
|
|
55
124
|
});
|
|
56
|
-
const Schema = observer(function Schema({ schema, path = [], }) {
|
|
125
|
+
const Schema = observer(function Schema({ schema, path = [], filter = '', expandedDisplayId, }) {
|
|
126
|
+
const { classes } = useStyles();
|
|
127
|
+
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
128
|
+
const query = filter.toLowerCase();
|
|
57
129
|
const properties = getMembers(schema).properties;
|
|
58
|
-
|
|
130
|
+
const visible = Object.entries(properties).filter(([slotName, slotSchema]) => !query || memberMatches(schema, slotName, slotSchema, query));
|
|
131
|
+
const normal = visible.filter(([slotName]) => !isAdvancedSlot(schema, slotName));
|
|
132
|
+
const advanced = visible.filter(([slotName]) => isAdvancedSlot(schema, slotName));
|
|
133
|
+
const renderMember = ([slotName, slotSchema]) => (_jsx(Member, { slotName: slotName, slotSchema: slotSchema, path: path, schema: schema, filter: filter, expandedDisplayId: expandedDisplayId }, slotName));
|
|
134
|
+
return (_jsxs(_Fragment, { children: [normal.map(renderMember), advanced.length && !query ? (_jsxs(Button, { className: classes.advancedToggle, size: "small", startIcon: showAdvanced ? _jsx(ExpandLessIcon, {}) : _jsx(ExpandMoreIcon, {}), onClick: () => {
|
|
135
|
+
setShowAdvanced(!showAdvanced);
|
|
136
|
+
}, children: [showAdvanced ? 'Hide' : 'Show', " advanced settings (", advanced.length, ")"] })) : null, showAdvanced || query ? advanced.map(renderMember) : null] }));
|
|
59
137
|
});
|
|
60
138
|
const ConfigurationEditor = observer(function ConfigurationEditor({ model, }) {
|
|
61
139
|
const { classes } = useStyles();
|
|
140
|
+
const [filter, setFilter] = useState('');
|
|
141
|
+
// key forces a re-render, otherwise the same field can end up being used for
|
|
142
|
+
// different tracks since only the backing model changes for example see pr
|
|
143
|
+
// #804
|
|
62
144
|
const target = model.target;
|
|
63
145
|
if (!target) {
|
|
64
146
|
return _jsx(Typography, { children: "No configuration target" });
|
|
65
147
|
}
|
|
66
148
|
const key = readConfObject(target, 'trackId');
|
|
67
149
|
const name = readConfObject(target, 'name');
|
|
68
|
-
return (_jsxs(Accordion, { defaultExpanded: true, className: classes.accordion, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ExpandMoreIcon, { className: classes.icon }), children: _jsx(Typography, { children: _jsx(SanitizedHTML, { html: name ?? 'Configuration' }) }) }),
|
|
150
|
+
return (_jsxs(Accordion, { defaultExpanded: true, className: classes.accordion, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ExpandMoreIcon, { className: classes.icon }), 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
|
+
setFilter(evt.target.value);
|
|
152
|
+
}, size: "small", fullWidth: true, slotProps: {
|
|
153
|
+
input: {
|
|
154
|
+
startAdornment: (_jsx(InputAdornment, { position: "start", children: _jsx(SearchIcon, { fontSize: "small" }) })),
|
|
155
|
+
endAdornment: filter ? (_jsx(InputAdornment, { position: "end", children: _jsx(IconButton, { size: "small", "aria-label": "clear filter", onClick: () => {
|
|
156
|
+
setFilter('');
|
|
157
|
+
}, children: _jsx(ClearIcon, { fontSize: "small" }) }) })) : null,
|
|
158
|
+
},
|
|
159
|
+
} }), filter && !schemaMatches(target, filter.toLowerCase()) ? (_jsxs(Typography, { className: classes.noResults, children: ["No options match \u201C", filter, "\u201D"] })) : (_jsx(Schema, { schema: target, filter: filter, expandedDisplayId: model.expandedDisplayId }))] })] }, key));
|
|
69
160
|
});
|
|
70
161
|
export default ConfigurationEditor;
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { SanitizedHTML } from '@jbrowse/core/ui';
|
|
3
3
|
import { TextField } from '@mui/material';
|
|
4
|
+
// adds ability to have html in helperText. note that FormHelperTextProps is
|
|
5
|
+
// div because the default is p which does not like div children
|
|
4
6
|
export default function ConfigurationTextField(props) {
|
|
5
|
-
const { helperText } = props;
|
|
6
|
-
return (_jsx(TextField, { ...
|
|
7
|
+
const { helperText, slotProps, ...rest } = props;
|
|
8
|
+
return (_jsx(TextField, { ...rest, helperText: _jsx(SanitizedHTML, { html: helperText || '' }), fullWidth: true,
|
|
9
|
+
// merged, not replaced: this component owns `formHelperText` (the div
|
|
10
|
+
// above needs it) while a caller owns the rest, and setting the whole bag
|
|
11
|
+
// here dropped a caller's `inputLabel` silently — the shrink that lets a
|
|
12
|
+
// labelled field draw its placeholder
|
|
13
|
+
slotProps: {
|
|
14
|
+
...slotProps,
|
|
7
15
|
formHelperText: {
|
|
8
16
|
component: 'div',
|
|
9
17
|
},
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** #slotEditor numeric text field that rounds to an integer */
|
|
2
|
+
declare const IntegerEditor: ({ slot, }: {
|
|
3
|
+
slot: {
|
|
4
|
+
name?: string;
|
|
5
|
+
value: number;
|
|
6
|
+
description?: string;
|
|
7
|
+
set: (num: number) => void;
|
|
8
|
+
};
|
|
9
|
+
}) => import("react").JSX.Element;
|
|
10
|
+
export default IntegerEditor;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { observer } from 'mobx-react';
|
|
3
|
+
import NumberEditor from './NumberEditor.js';
|
|
4
|
+
// thin wrapper: an integer slot is a number slot that only commits whole values
|
|
5
|
+
/** #slotEditor numeric text field that rounds to an integer */
|
|
6
|
+
const IntegerEditor = observer(function IntegerEditor({ slot, }) {
|
|
7
|
+
return _jsx(NumberEditor, { slot: slot, integer: true });
|
|
8
|
+
});
|
|
9
|
+
export default IntegerEditor;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
/** #slotEditor monospace textarea holding arbitrary JSON */
|
|
1
2
|
declare const JsonEditor: ({ slot, }: {
|
|
2
3
|
slot: {
|
|
3
4
|
name: string;
|
|
4
5
|
description: string;
|
|
5
6
|
value: unknown;
|
|
7
|
+
promotedBase?: unknown;
|
|
6
8
|
set: (arg: unknown) => void;
|
|
7
9
|
};
|
|
8
|
-
}) => import("react
|
|
10
|
+
}) => import("react").JSX.Element;
|
|
9
11
|
export default JsonEditor;
|
|
@@ -1,53 +1,40 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
2
|
-
import {
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { MonospaceTextField } from '@jbrowse/core/ui';
|
|
3
4
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
4
|
-
import { InputLabel, TextField } from '@mui/material';
|
|
5
5
|
import { observer } from 'mobx-react';
|
|
6
|
-
const fontSize = '12px';
|
|
7
|
-
const fontFamily = 'Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, monospace';
|
|
8
6
|
const useStyles = makeStyles()(theme => ({
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
fontSize: '0.8em',
|
|
12
|
-
},
|
|
13
|
-
callbackEditor: {
|
|
14
|
-
fontFamily,
|
|
15
|
-
fontSize,
|
|
7
|
+
jsonEditor: {
|
|
8
|
+
fontSize: '12px',
|
|
16
9
|
background: theme.palette.background.default,
|
|
17
|
-
width:
|
|
10
|
+
width: '100%',
|
|
18
11
|
marginTop: '16px',
|
|
19
12
|
border: '1px solid rgba(0,0,0,0.42)',
|
|
20
13
|
},
|
|
21
|
-
callbackContainer: {
|
|
22
|
-
width: '100%',
|
|
23
|
-
overflowX: 'auto',
|
|
24
|
-
},
|
|
25
|
-
textAreaFont: {
|
|
26
|
-
fontFamily,
|
|
27
|
-
},
|
|
28
14
|
}));
|
|
15
|
+
/** #slotEditor monospace textarea holding arbitrary JSON */
|
|
29
16
|
const JsonEditor = observer(function JsonEditor({ slot, }) {
|
|
30
17
|
const { classes } = useStyles();
|
|
31
|
-
|
|
18
|
+
// A `maybeFrozen` slot is `undefined` when unset — the promotable inherit
|
|
19
|
+
// state (alignments/synteny `colorBy`). `JSON.stringify(undefined)` returns
|
|
20
|
+
// the *value* `undefined`, not a string, so seeding straight off `slot.value`
|
|
21
|
+
// handed MonospaceTextField an undefined `value` and made the field
|
|
22
|
+
// uncontrolled until the first keystroke (React then warns on the switch).
|
|
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
|
+
});
|
|
32
28
|
const [error, setError] = useState();
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return (_jsxs(_Fragment, { children: [error ? _jsx("p", { className: classes.error, children: `${error}` }) : null, _jsxs("div", { className: classes.callbackContainer, children: [_jsx(InputLabel, { shrink: true, htmlFor: "json-editor", children: slot.name }), _jsx(TextField, { id: "json-editor", className: classes.callbackEditor, value: contents, helperText: slot.description, multiline: true, onChange: event => {
|
|
44
|
-
setContents(event.target.value);
|
|
45
|
-
}, style: { background: error ? '#fdd' : undefined }, slotProps: {
|
|
46
|
-
input: {
|
|
47
|
-
classes: {
|
|
48
|
-
input: classes.textAreaFont,
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
} })] })] }));
|
|
29
|
+
return (_jsx(MonospaceTextField, { className: classes.jsonEditor, label: slot.name, value: contents, error: error, helperText: slot.description, onChange: val => {
|
|
30
|
+
setContents(val);
|
|
31
|
+
try {
|
|
32
|
+
slot.set(JSON.parse(val));
|
|
33
|
+
setError(undefined);
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
setError(e);
|
|
37
|
+
}
|
|
38
|
+
} }));
|
|
52
39
|
});
|
|
53
40
|
export default JsonEditor;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export default function MapEntryCard({ title, onDelete, children, }: {
|
|
2
|
+
title: string;
|
|
3
|
+
onDelete: () => void;
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
}): import("react").JSX.Element;
|
|
6
|
+
export declare function MapAddCard({ onAdd }: {
|
|
7
|
+
onAdd: (key: string) => void;
|
|
8
|
+
}): import("react").JSX.Element;
|