@jbrowse/plugin-config 4.3.0 → 5.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/esm/ConfigurationEditorWidget/components/AddNewField.d.ts +6 -0
  2. package/esm/ConfigurationEditorWidget/components/AddNewField.js +29 -0
  3. package/esm/ConfigurationEditorWidget/components/BooleanEditor.d.ts +3 -2
  4. package/esm/ConfigurationEditorWidget/components/BooleanEditor.js +6 -4
  5. package/esm/ConfigurationEditorWidget/components/CallbackEditor.d.ts +4 -2
  6. package/esm/ConfigurationEditorWidget/components/CallbackEditor.js +24 -42
  7. package/esm/ConfigurationEditorWidget/components/ColorEditor.d.ts +5 -12
  8. package/esm/ConfigurationEditorWidget/components/ColorEditor.js +47 -22
  9. package/esm/ConfigurationEditorWidget/components/ConfigurationEditor.d.ts +2 -3
  10. package/esm/ConfigurationEditorWidget/components/ConfigurationEditor.js +168 -29
  11. package/esm/ConfigurationEditorWidget/components/ConfigurationTextField.d.ts +1 -1
  12. package/esm/ConfigurationEditorWidget/components/ConfigurationTextField.js +10 -2
  13. package/esm/ConfigurationEditorWidget/components/EntryListEditor.d.ts +17 -0
  14. package/esm/ConfigurationEditorWidget/components/EntryListEditor.js +35 -0
  15. package/esm/ConfigurationEditorWidget/components/HeadingComponent.d.ts +1 -1
  16. package/esm/ConfigurationEditorWidget/components/IntegerEditor.d.ts +10 -0
  17. package/esm/ConfigurationEditorWidget/components/IntegerEditor.js +9 -0
  18. package/esm/ConfigurationEditorWidget/components/JsonEditor.d.ts +2 -1
  19. package/esm/ConfigurationEditorWidget/components/JsonEditor.js +24 -41
  20. package/esm/ConfigurationEditorWidget/components/MapEntryCard.d.ts +8 -0
  21. package/esm/ConfigurationEditorWidget/components/MapEntryCard.js +23 -0
  22. package/esm/ConfigurationEditorWidget/components/MapSlotEditor.d.ts +8 -0
  23. package/esm/ConfigurationEditorWidget/components/MapSlotEditor.js +18 -0
  24. package/esm/ConfigurationEditorWidget/components/NumberEditor.d.ts +5 -4
  25. package/esm/ConfigurationEditorWidget/components/NumberEditor.js +15 -15
  26. package/esm/ConfigurationEditorWidget/components/NumberMapEditor.d.ts +4 -4
  27. package/esm/ConfigurationEditorWidget/components/NumberMapEditor.js +7 -31
  28. package/esm/ConfigurationEditorWidget/components/SlotEditor.d.ts +4 -5
  29. package/esm/ConfigurationEditorWidget/components/SlotEditor.js +88 -49
  30. package/esm/ConfigurationEditorWidget/components/StringArrayEditor.d.ts +16 -10
  31. package/esm/ConfigurationEditorWidget/components/StringArrayEditor.js +22 -25
  32. package/esm/ConfigurationEditorWidget/components/StringArrayMapEditor.d.ts +3 -6
  33. package/esm/ConfigurationEditorWidget/components/StringArrayMapEditor.js +14 -39
  34. package/esm/ConfigurationEditorWidget/components/StringMapEditor.d.ts +10 -0
  35. package/esm/ConfigurationEditorWidget/components/StringMapEditor.js +13 -0
  36. package/esm/ConfigurationEditorWidget/components/TypeSelector.d.ts +2 -2
  37. package/esm/ConfigurationEditorWidget/components/TypeSelector.js +2 -2
  38. package/esm/ConfigurationEditorWidget/components/singularSlotName.d.ts +13 -0
  39. package/esm/ConfigurationEditorWidget/components/singularSlotName.js +23 -0
  40. package/esm/ConfigurationEditorWidget/components/useSlotEditorStyles.d.ts +2 -2
  41. package/esm/ConfigurationEditorWidget/components/useSlotEditorStyles.js +6 -1
  42. package/esm/ConfigurationEditorWidget/index.js +4 -4
  43. package/esm/ConfigurationEditorWidget/model.d.ts +8 -1
  44. package/esm/ConfigurationEditorWidget/model.js +40 -1
  45. package/esm/FromConfigAdapter/FromConfigAdapter.d.ts +6 -1
  46. package/esm/FromConfigAdapter/FromConfigAdapter.js +31 -1
  47. package/esm/FromConfigAdapter/configSchema.d.ts +32 -4
  48. package/esm/FromConfigAdapter/configSchema.js +34 -2
  49. package/esm/FromConfigAdapter/index.js +2 -2
  50. package/esm/FromConfigRegionsAdapter/FromConfigRegionsAdapter.d.ts +8 -0
  51. package/esm/FromConfigRegionsAdapter/FromConfigRegionsAdapter.js +10 -25
  52. package/esm/FromConfigRegionsAdapter/configSchema.d.ts +38 -4
  53. package/esm/FromConfigRegionsAdapter/configSchema.js +39 -2
  54. package/esm/FromConfigRegionsAdapter/index.js +2 -2
  55. package/esm/FromConfigSequenceAdapter/FromConfigSequenceAdapter.d.ts +15 -2
  56. package/esm/FromConfigSequenceAdapter/FromConfigSequenceAdapter.js +32 -40
  57. package/esm/FromConfigSequenceAdapter/configSchema.d.ts +37 -4
  58. package/esm/FromConfigSequenceAdapter/configSchema.js +38 -2
  59. package/esm/FromConfigSequenceAdapter/index.js +2 -2
  60. package/esm/NcbiSequenceReportAliasAdapter/NcbiSequenceReportAliasAdapter.d.ts +5 -4
  61. package/esm/NcbiSequenceReportAliasAdapter/NcbiSequenceReportAliasAdapter.js +28 -24
  62. package/esm/NcbiSequenceReportAliasAdapter/configSchema.d.ts +69 -10
  63. package/esm/NcbiSequenceReportAliasAdapter/configSchema.js +82 -18
  64. package/esm/NcbiSequenceReportAliasAdapter/index.js +3 -2
  65. package/esm/RefNameAliasAdapter/RefNameAliasAdapter.d.ts +4 -3
  66. package/esm/RefNameAliasAdapter/RefNameAliasAdapter.js +22 -28
  67. package/esm/RefNameAliasAdapter/configSchema.d.ts +70 -12
  68. package/esm/RefNameAliasAdapter/configSchema.js +89 -18
  69. package/esm/RefNameAliasAdapter/index.js +3 -2
  70. package/esm/aliasUtils.d.ts +4 -0
  71. package/esm/aliasUtils.js +19 -0
  72. package/esm/index.d.ts +2 -2
  73. package/esm/index.js +7 -7
  74. package/package.json +11 -15
@@ -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,3 +1,4 @@
1
+ /** #slotEditor monospace textarea holding arbitrary JSON */
1
2
  declare const JsonEditor: ({ slot, }: {
2
3
  slot: {
3
4
  name: string;
@@ -5,5 +6,5 @@ declare const JsonEditor: ({ slot, }: {
5
6
  value: unknown;
6
7
  set: (arg: unknown) => void;
7
8
  };
8
- }) => import("react/jsx-runtime").JSX.Element;
9
+ }) => import("react").JSX.Element;
9
10
  export default JsonEditor;
@@ -1,53 +1,36 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useEffect, useState } from 'react';
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { MonospaceTextField } from '@jbrowse/core/ui';
4
+ import { alpha } from '@jbrowse/core/ui/palette';
3
5
  import { makeStyles } from '@jbrowse/core/util/tss-react';
4
- import { InputLabel, TextField } from '@mui/material';
5
6
  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
7
  const useStyles = makeStyles()(theme => ({
9
- error: {
10
- color: 'red',
11
- fontSize: '0.8em',
12
- },
13
- callbackEditor: {
14
- fontFamily,
15
- fontSize,
8
+ jsonEditor: {
9
+ fontSize: '12px',
16
10
  background: theme.palette.background.default,
17
- width: 800,
18
- marginTop: '16px',
19
- border: '1px solid rgba(0,0,0,0.42)',
20
- },
21
- callbackContainer: {
22
11
  width: '100%',
23
- overflowX: 'auto',
24
- },
25
- textAreaFont: {
26
- fontFamily,
12
+ marginTop: '16px',
13
+ border: `1px solid ${alpha(theme.palette.text.primary, 0.42)}`,
27
14
  },
28
15
  }));
16
+ /** #slotEditor monospace textarea holding arbitrary JSON */
29
17
  const JsonEditor = observer(function JsonEditor({ slot, }) {
30
18
  const { classes } = useStyles();
31
- const [contents, setContents] = useState(JSON.stringify(slot.value, null, 2));
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));
32
24
  const [error, setError] = useState();
33
- useEffect(() => {
34
- try {
35
- setError(undefined);
36
- slot.set(JSON.parse(contents));
37
- }
38
- catch (e) {
39
- console.error({ e });
40
- setError(e);
41
- }
42
- }, [contents, slot]);
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
- } })] })] }));
25
+ return (_jsx(MonospaceTextField, { className: classes.jsonEditor, label: slot.name, value: contents, error: error, helperText: slot.description, onChange: val => {
26
+ setContents(val);
27
+ try {
28
+ slot.set(JSON.parse(val));
29
+ setError(undefined);
30
+ }
31
+ catch (e) {
32
+ setError(e);
33
+ }
34
+ } }));
52
35
  });
53
36
  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;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { makeStyles } from '@jbrowse/core/util/tss-react';
3
+ import DeleteIcon from '@mui/icons-material/Delete';
4
+ import { Card, CardContent, CardHeader, IconButton } from '@mui/material';
5
+ import AddNewField from './AddNewField.js';
6
+ const useStyles = makeStyles()(theme => ({
7
+ card: {
8
+ marginTop: theme.spacing(1),
9
+ },
10
+ }));
11
+ // shared card for a single key in a map slot editor: a titled card with a
12
+ // delete button wrapping the per-value editor passed as children
13
+ export default function MapEntryCard({ title, onDelete, children, }) {
14
+ const { classes } = useStyles();
15
+ return (_jsxs(Card, { raised: true, className: classes.card, children: [_jsx(CardHeader, { title: title, action: _jsx(IconButton, { onClick: () => {
16
+ onDelete();
17
+ }, children: _jsx(DeleteIcon, {}) }) }), _jsx(CardContent, { children: children })] }));
18
+ }
19
+ // trailing card in a map slot editor holding the "add new key" field
20
+ export function MapAddCard({ onAdd }) {
21
+ const { classes } = useStyles();
22
+ return (_jsx(Card, { raised: true, className: classes.card, children: _jsx(CardHeader, { disableTypography: true, title: _jsx(AddNewField, { onAdd: onAdd }) }) }));
23
+ }
@@ -0,0 +1,8 @@
1
+ export default function MapSlotEditor<V>({ name, description, entries, emptyValue, setMap, renderValue, }: {
2
+ name: string;
3
+ description: string;
4
+ entries: [string, V][];
5
+ emptyValue: V;
6
+ setMap: (val: Record<string, V>) => void;
7
+ renderValue: (val: V, set: (val: V) => void, key: string) => React.ReactNode;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { FormHelperText, InputLabel } from '@mui/material';
3
+ import MapEntryCard, { MapAddCard } from './MapEntryCard.js';
4
+ // shared shell for map slot editors (numberMap, stringArrayMap): a labelled
5
+ // list of per-key cards plus an "add key" card. The owning wrapper stays the
6
+ // observer and passes plain entries in, so this stays a non-observer generic
7
+ // (which keeps JSX type args working at the call sites).
8
+ export default function MapSlotEditor({ name, description, entries, emptyValue, setMap, renderValue, }) {
9
+ const obj = Object.fromEntries(entries);
10
+ return (_jsxs(_Fragment, { children: [_jsx(InputLabel, { children: name }), entries.map(([key, val]) => (_jsx(MapEntryCard, { title: key, onDelete: () => {
11
+ const { [key]: _omit, ...rest } = obj;
12
+ setMap(rest);
13
+ }, children: renderValue(val, newVal => {
14
+ setMap({ ...obj, [key]: newVal });
15
+ }, key) }, key))), _jsx(MapAddCard, { onAdd: key => {
16
+ setMap({ ...obj, [key]: emptyValue });
17
+ } }), _jsx(FormHelperText, { children: description })] }));
18
+ }
@@ -1,10 +1,11 @@
1
- declare const NumberEditor: ({ slot, }: {
1
+ /** #slotEditor numeric text field */
2
+ declare const NumberEditor: ({ slot, integer, }: {
2
3
  slot: {
3
4
  name?: string;
4
- value: string;
5
+ value: number | undefined;
5
6
  description?: string;
6
7
  set: (val: number) => void;
7
- reset?: () => void;
8
8
  };
9
- }) => import("react/jsx-runtime").JSX.Element;
9
+ integer?: boolean;
10
+ }) => import("react").JSX.Element;
10
11
  export default NumberEditor;
@@ -1,20 +1,20 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useEffect, useState } from 'react';
2
+ import { useState } from 'react';
3
3
  import { observer } from 'mobx-react';
4
- import ConfigurationTextField from "./ConfigurationTextField.js";
5
- const NumberEditor = observer(function NumberEditor({ slot, }) {
6
- const [val, setVal] = useState(slot.value);
7
- useEffect(() => {
8
- const num = Number.parseFloat(val);
9
- if (Number.isNaN(num)) {
10
- slot.reset?.();
11
- }
12
- else {
13
- slot.set(num);
14
- }
15
- }, [slot, val]);
16
- return (_jsx(ConfigurationTextField, { label: slot.name, helperText: slot.description, value: val, type: "number", onChange: evt => {
17
- setVal(evt.target.value);
4
+ import ConfigurationTextField from './ConfigurationTextField.js';
5
+ /** #slotEditor numeric text field */
6
+ const NumberEditor = observer(function NumberEditor({ slot, integer = false, }) {
7
+ const [val, setVal] = useState(slot.value === undefined ? '' : String(slot.value));
8
+ return (_jsx(ConfigurationTextField, { label: slot.name, helperText: slot.description, value: val, onChange: evt => {
9
+ const v = evt.target.value;
10
+ setVal(v);
11
+ const num = Number(v);
12
+ // commit only valid values; the text buffer preserves in-progress
13
+ // entries like "-" or "1." without writing garbage to the config
14
+ const valid = integer ? Number.isInteger(num) : Number.isFinite(num);
15
+ if (v !== '' && valid) {
16
+ slot.set(num);
17
+ }
18
18
  } }));
19
19
  });
20
20
  export default NumberEditor;
@@ -1,10 +1,10 @@
1
+ /** #slotEditor one card per key, each holding that key's numeric field */
1
2
  declare const NumberMapEditor: ({ slot, }: {
2
3
  slot: {
3
4
  name: string;
4
- value: Map<string, string>;
5
- remove: (key: string) => void;
6
- add: (key: string, val: number) => void;
5
+ value: Map<string, number>;
6
+ set: (val: Record<string, number>) => void;
7
7
  description: string;
8
8
  };
9
- }) => import("react/jsx-runtime").JSX.Element;
9
+ }) => import("react").JSX.Element;
10
10
  export default NumberMapEditor;
@@ -1,35 +1,11 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState } from 'react';
3
- import { makeStyles } from '@jbrowse/core/util/tss-react';
4
- import AddIcon from '@mui/icons-material/Add';
5
- import DeleteIcon from '@mui/icons-material/Delete';
6
- import { Card, CardContent, CardHeader, FormHelperText, IconButton, InputAdornment, InputLabel, TextField, } from '@mui/material';
1
+ import { jsx as _jsx } from "react/jsx-runtime";
7
2
  import { observer } from 'mobx-react';
8
- import NumberEditor from "./NumberEditor.js";
9
- const useStyles = makeStyles()(theme => ({
10
- card: {
11
- marginTop: theme.spacing(1),
12
- },
13
- }));
3
+ import MapSlotEditor from './MapSlotEditor.js';
4
+ import NumberEditor from './NumberEditor.js';
5
+ /** #slotEditor one card per key, each holding that key's numeric field */
14
6
  const NumberMapEditor = observer(function NumberMapEditor({ slot, }) {
15
- const { classes } = useStyles();
16
- const [value, setValue] = useState('');
17
- return (_jsxs(_Fragment, { children: [_jsx(InputLabel, { children: slot.name }), [...slot.value].map(([key, val]) => (_jsxs(Card, { raised: true, className: classes.card, children: [_jsx(CardHeader, { title: key, action: _jsx(IconButton, { onClick: () => {
18
- slot.remove(key);
19
- }, children: _jsx(DeleteIcon, {}) }) }), _jsx(CardContent, { children: _jsx(NumberEditor, { slot: {
20
- value: val,
21
- set: (val) => {
22
- slot.add(key, val);
23
- },
24
- } }) })] }, key))), _jsx(Card, { raised: true, className: classes.card, children: _jsx(CardHeader, { disableTypography: true, title: _jsx(TextField, { fullWidth: true, value: value, placeholder: "add new", onChange: event => {
25
- setValue(event.target.value);
26
- }, slotProps: {
27
- input: {
28
- endAdornment: (_jsx(InputAdornment, { position: "end", children: _jsx(IconButton, { disabled: value === '', onClick: () => {
29
- slot.add(value, 0);
30
- setValue('');
31
- }, children: _jsx(AddIcon, {}) }) })),
32
- },
33
- } }) }) }), _jsx(FormHelperText, { children: slot.description })] }));
7
+ return (_jsx(MapSlotEditor, { name: slot.name, description: slot.description, entries: [...slot.value], emptyValue: 0, setMap: val => {
8
+ slot.set(val);
9
+ }, renderValue: (val, set) => _jsx(NumberEditor, { slot: { value: val, set } }) }));
34
10
  });
35
11
  export default NumberMapEditor;
@@ -1,6 +1,5 @@
1
- import type { IAnyType } from '@jbrowse/mobx-state-tree';
2
- declare const SlotEditor: ({ slot, slotSchema, }: {
3
- slot: any;
4
- slotSchema: IAnyType;
5
- }) => import("react/jsx-runtime").JSX.Element;
1
+ import type { SlotFacade } from '@jbrowse/core/configuration';
2
+ declare const SlotEditor: ({ slot, }: {
3
+ slot: SlotFacade;
4
+ }) => import("react").JSX.Element;
6
5
  export default SlotEditor;
@@ -1,86 +1,125 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState } from 'react';
2
+ import { useState } from 'react';
3
+ import { isCallbackValue, toCallbackValue, toFixedValue, } from '@jbrowse/core/configuration';
3
4
  import { FileSelector } from '@jbrowse/core/ui';
4
- import { getEnv } from '@jbrowse/core/util';
5
- import { getSubType, getUnionSubTypes } from '@jbrowse/core/util/mst-reflection';
6
- import { getPropertyMembers } from '@jbrowse/mobx-state-tree';
7
- import RadioButtonUncheckedIcon from '@mui/icons-material/RadioButtonUnchecked';
8
- import { IconButton, MenuItem, Paper, SvgIcon, TextField } from '@mui/material';
5
+ import CodeIcon from '@mui/icons-material/Code';
6
+ import RestartAltIcon from '@mui/icons-material/RestartAlt';
7
+ import { IconButton, MenuItem, Paper, TextField, Tooltip } from '@mui/material';
9
8
  import { observer } from 'mobx-react';
10
- import BooleanEditor from "./BooleanEditor.js";
11
- import CallbackEditor from "./CallbackEditor.js";
12
- import ColorEditor from "./ColorEditor.js";
13
- import ConfigurationTextField from "./ConfigurationTextField.js";
14
- import JsonEditor from "./JsonEditor.js";
15
- import NumberEditor from "./NumberEditor.js";
16
- import NumberMapEditor from "./NumberMapEditor.js";
17
- import StringArrayEditor from "./StringArrayEditor.js";
18
- import StringArrayMapEditor from "./StringArrayMapEditor.js";
19
- import { useSlotEditorStyles } from "./useSlotEditorStyles.js";
9
+ import BooleanEditor from './BooleanEditor.js';
10
+ import CallbackEditor from './CallbackEditor.js';
11
+ import ColorEditor from './ColorEditor.js';
12
+ import ConfigurationTextField from './ConfigurationTextField.js';
13
+ import { ColorArrayEditor, StringEnumArrayEditor } from './EntryListEditor.js';
14
+ import IntegerEditor from './IntegerEditor.js';
15
+ import JsonEditor from './JsonEditor.js';
16
+ import NumberEditor from './NumberEditor.js';
17
+ import NumberMapEditor from './NumberMapEditor.js';
18
+ import StringArrayEditor, { ExpressionArrayEditor, } from './StringArrayEditor.js';
19
+ import StringArrayMapEditor from './StringArrayMapEditor.js';
20
+ import StringMapEditor from './StringMapEditor.js';
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.
25
+ /** #slotEditor single-line text field */
20
26
  const StringEditor = observer(function StringEditor({ slot, }) {
21
- 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 => {
22
28
  slot.set(evt.target.value);
23
29
  } }));
24
30
  });
31
+ /** #slotEditor multi-line textarea */
25
32
  const TextEditor = observer(function TextEditor({ slot, }) {
26
33
  return (_jsx(TextField, { label: slot.name, helperText: slot.description, multiline: true, value: slot.value, onChange: evt => {
27
34
  slot.set(evt.target.value);
28
35
  } }));
29
36
  });
30
- const SvgCheckbox = () => (_jsx(SvgIcon, { children: _jsx("path", { d: "M20.41,3C21.8,5.71 22.35,8.84 22,12C21.8,15.16 20.7,18.29 18.83,21L17.3,20C18.91,17.57 19.85,14.8 20,12C20.34,9.2 19.89,6.43 18.7,4L20.41,3M5.17,3L6.7,4C5.09,6.43 4.15,9.2 4,12C3.66,14.8 4.12,17.57 5.3,20L3.61,21C2.21,18.29 1.65,15.17 2,12C2.2,8.84 3.3,5.71 5.17,3M12.08,10.68L14.4,7.45H16.93L13.15,12.45L15.35,17.37H13.09L11.71,14L9.28,17.33H6.76L10.66,12.21L8.53,7.45H10.8L12.08,10.68Z" }) }));
31
- const IntegerEditor = observer(function IntegerEditor({ slot, }) {
32
- const [val, setVal] = useState(slot.value);
33
- useEffect(() => {
34
- const num = Number.parseInt(val, 10);
35
- if (!Number.isNaN(num)) {
36
- slot.set(num);
37
- }
38
- }, [slot, val]);
39
- return (_jsx(ConfigurationTextField, { label: slot.name, helperText: slot.description, value: val, type: "number", onChange: evt => {
40
- setVal(evt.target.value);
41
- } }));
42
- });
43
- const StringEnumEditor = observer(function StringEnumEditor({ slot, slotSchema, }) {
44
- const p = getPropertyMembers(getSubType(slotSchema));
45
- const choices = getUnionSubTypes(getUnionSubTypes(getSubType(p.properties.value))[1]).map(t => t.value);
46
- return (_jsx(ConfigurationTextField, { value: slot.value, label: slot.name, select: true, helperText: slot.description, onChange: evt => {
47
- slot.set(evt.target.value);
48
- }, children: choices.map(str => (_jsx(MenuItem, { value: str, children: str }, str))) }));
37
+ // Sentinel for "leave the slot unset" in the maybe-enum dropdown. MUI needs a
38
+ // concrete option value, and '' can't collide with an enum member (an empty
39
+ // enumeration member would be unnameable in a menu anyway).
40
+ const UNSET_CHOICE = '';
41
+ /** #slotEditor dropdown of the `model`'s members */
42
+ const StringEnumEditor = observer(function StringEnumEditor({ slot, }) {
43
+ const nullable = slot.type === 'maybeStringEnum';
44
+ return (_jsxs(ConfigurationTextField, { value: slot.value ?? UNSET_CHOICE, label: slot.name, select: true, helperText: slot.description, onChange: evt => {
45
+ const { value } = evt.target;
46
+ slot.set(value === UNSET_CHOICE ? undefined : value);
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)))] }));
49
48
  });
49
+ /** #slotEditor URL, local file path (desktop) or file blob (browser) */
50
50
  const FileSelectorWrapper = observer(function FileSelectorWrapper({ slot, }) {
51
51
  return (_jsx(FileSelector, { location: slot.value, setLocation: location => {
52
52
  slot.set(location);
53
- }, name: slot.name, description: slot.description, rootModel: getEnv(slot).pluginManager.rootModel }));
53
+ }, name: slot.name, description: slot.description, rootModel: slot.pluginManager.rootModel }));
54
54
  });
55
+ // dynamic dispatch from a runtime slot `type` string to a typed editor. The
56
+ // `any` is irreducible here: each editor declares a narrow `slot.value` type
57
+ // (number, boolean, FileLocation, string[], ...) but makeSlotFacade can only
58
+ // type value as `unknown`, so no single registry prop type satisfies them all.
59
+ // The editors stay fully typed internally; only this lookup is untyped.
55
60
  const valueComponents = {
56
61
  string: StringEditor,
62
+ maybeString: StringEditor,
63
+ featureField: StringEditor,
57
64
  text: TextEditor,
58
65
  fileLocation: FileSelectorWrapper,
66
+ maybeFileLocation: FileSelectorWrapper,
59
67
  stringArray: StringArrayEditor,
68
+ expressionArray: ExpressionArrayEditor,
69
+ colorArray: ColorArrayEditor,
70
+ stringEnumArray: StringEnumArrayEditor,
60
71
  stringArrayMap: StringArrayMapEditor,
61
72
  numberMap: NumberMapEditor,
73
+ stringMap: StringMapEditor,
62
74
  number: NumberEditor,
75
+ maybeNumber: NumberEditor,
63
76
  integer: IntegerEditor,
64
77
  color: ColorEditor,
78
+ maybeColor: ColorEditor,
65
79
  stringEnum: StringEnumEditor,
80
+ maybeStringEnum: StringEnumEditor,
66
81
  boolean: BooleanEditor,
82
+ maybeBoolean: BooleanEditor,
67
83
  frozen: JsonEditor,
68
- configRelationships: JsonEditor,
84
+ maybeFrozen: JsonEditor,
69
85
  };
70
- const SlotEditor = observer(function SlotEditor({ slot, slotSchema, }) {
86
+ const SlotEditor = observer(function SlotEditor({ slot, }) {
71
87
  const { classes } = useSlotEditorStyles();
72
88
  const { type } = slot;
73
- let ValueComponent = slot.isCallback
74
- ? CallbackEditor
75
- :
76
- valueComponents[type];
77
- if (!ValueComponent) {
89
+ // editor mode is UI-only state, derived once from whether the stored value is
90
+ // a jexl callback. Toggling rewrites the value; typing a "jexl:" prefix into a
91
+ // value field does not auto-swap the editor, since this state only changes on
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));
96
+ // bumped on reset to remount the value editor: the buffered editors (number,
97
+ // json, callback) seed internal state from slot.value only on mount, so an
98
+ // external set wouldn't otherwise show in the field
99
+ const [resetNonce, setResetNonce] = useState(0);
100
+ const TypedComponent = valueComponents[type];
101
+ if (!callbackMode && !TypedComponent) {
78
102
  console.warn(`no slot editor defined for ${type}, editing as string`);
79
- ValueComponent = StringEditor;
80
- }
81
- if (!(type in valueComponents)) {
82
- console.warn(`SlotEditor needs to implement ${type}`);
83
103
  }
84
- return (_jsxs(Paper, { className: classes.paper, children: [_jsx("div", { className: classes.paperContent, children: _jsx(ValueComponent, { slot: slot, slotSchema: slotSchema }) }), _jsx("div", { className: classes.slotModeSwitch, children: slot.contextVariable.length ? (_jsx(IconButton, { onClick: () => slot.isCallback ? slot.convertToValue() : slot.convertToCallback(), title: `convert to ${slot.isCallback ? 'regular value' : 'callback'}`, children: slot.isCallback ? _jsx(SvgCheckbox, {}) : _jsx(RadioButtonUncheckedIcon, {}) })) : null })] }));
104
+ const ValueComponent = callbackMode
105
+ ? CallbackEditor
106
+ : (TypedComponent ?? StringEditor);
107
+ const { modified } = slot;
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: () => {
109
+ slot.set(slot.defaultValue);
110
+ setCallbackMode(callback && isCallbackValue(slot.defaultValue));
111
+ setResetNonce(nonce => nonce + 1);
112
+ }, children: _jsx(RestartAltIcon, {}) }) }) })) : null, callback ? (_jsx("div", { className: classes.slotModeSwitch, children: _jsx(Tooltip, { title: callbackMode
113
+ ? 'Editing as a callback (Jexl expression). Click to use a fixed value.'
114
+ : 'Editing as a fixed value. Click to use a callback (Jexl expression).', children: _jsx(IconButton, { color: callbackMode ? 'primary' : 'default', onClick: () => {
115
+ if (callbackMode) {
116
+ slot.set(toFixedValue(slot.value, type, slot.defaultValue, slot.pluginManager.jexl));
117
+ setCallbackMode(false);
118
+ }
119
+ else {
120
+ slot.set(toCallbackValue(slot.value));
121
+ setCallbackMode(true);
122
+ }
123
+ }, children: _jsx(CodeIcon, {}) }) }) })) : null] }));
85
124
  });
86
125
  export default SlotEditor;
@@ -1,11 +1,17 @@
1
- declare const StringArrayEditor: ({ slot, }: {
2
- slot: {
3
- name: string;
4
- value: string[];
5
- setAtIndex: (arg: number, arg2: string) => void;
6
- removeAtIndex: (arg: number) => void;
7
- add: (arg: string) => void;
8
- description: string;
9
- };
10
- }) => import("react/jsx-runtime").JSX.Element;
1
+ interface ArraySlot {
2
+ name: string;
3
+ value: string[];
4
+ set: (arg: string[]) => void;
5
+ description: string;
6
+ }
7
+ /** #slotEditor "todolist" of text fields, one per entry, with add and delete */
8
+ declare const StringArrayEditor: ({ slot, prefix, }: {
9
+ slot: ArraySlot;
10
+ prefix?: string;
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 };
11
17
  export default StringArrayEditor;
@@ -1,34 +1,31 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from 'react';
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { JEXL_PREFIX } from '@jbrowse/core/util/jexlStrings';
3
3
  import DeleteIcon from '@mui/icons-material/Delete';
4
- import { Button, FormHelperText, IconButton, InputAdornment, InputLabel, List, ListItem, TextField, } from '@mui/material';
4
+ import { FormHelperText, IconButton, InputAdornment, InputLabel, List, ListItem, TextField, } from '@mui/material';
5
5
  import { observer } from 'mobx-react';
6
- const StringArrayEditor = observer(function StringArrayEditor({ slot, }) {
7
- const [value, setValue] = useState('');
8
- const [addNew, setAddNew] = useState(false);
9
- return (_jsxs(_Fragment, { children: [slot.name ? _jsx(InputLabel, { children: slot.name }) : null, _jsxs(List, { disablePadding: true, children: [slot.value.map((val, idx) => (_jsx(ListItem, { disableGutters: true, children: _jsx(TextField, { value: val, onChange: evt => {
10
- slot.setAtIndex(idx, evt.target.value);
6
+ import AddNewField from './AddNewField.js';
7
+ /** #slotEditor "todolist" of text fields, one per entry, with add and delete */
8
+ const StringArrayEditor = observer(function StringArrayEditor({ slot, prefix = '', }) {
9
+ const value = [...slot.value];
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
- slot.removeAtIndex(idx);
17
+ slot.set(value.filter((_, i) => i !== idx));
15
18
  }, children: _jsx(DeleteIcon, {}) }) })),
16
19
  },
17
- } }) }, `${JSON.stringify(val)}-${idx}`))), addNew ? (_jsx(ListItem, { disableGutters: true, children: _jsx(TextField, { value: value, placeholder: "add new", onChange: event => {
18
- setValue(event.target.value);
19
- }, slotProps: {
20
- input: {
21
- endAdornment: (_jsx(InputAdornment, { position: "end", children: _jsxs(_Fragment, { children: [_jsx(Button, { color: "primary", variant: "contained", style: { margin: 2 }, "data-testid": `stringArrayAdd-${slot.name}`, onClick: () => {
22
- setAddNew(false);
23
- slot.add(value);
24
- setValue('');
25
- }, children: "OK" }), _jsx(Button, { color: "primary", variant: "contained", style: { margin: 2 }, onClick: () => {
26
- setAddNew(false);
27
- setValue('');
28
- }, children: "Cancel" })] }) })),
29
- },
30
- } }) })) : null, _jsx(Button, { color: "primary", variant: "contained", style: { margin: 4 }, disabled: addNew, onClick: () => {
31
- setAddNew(true);
32
- }, children: "Add item" })] }), _jsx(FormHelperText, { children: slot.description })] }));
20
+ } }) }, idx))), _jsx(ListItem, { disableGutters: true, children: _jsx(AddNewField, { testid: `stringArrayAdd-${slot.name}`, startAdornment: startAdornment, onAdd: val => {
21
+ slot.set([...value, prefix + val]);
22
+ } }) })] }), _jsx(FormHelperText, { children: slot.description })] }));
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 });
33
29
  });
30
+ export { ExpressionArrayEditor };
34
31
  export default StringArrayEditor;
@@ -1,13 +1,10 @@
1
+ /** #slotEditor one card per key, each holding that key's "todolist" of strings */
1
2
  declare const StringArrayMapEditor: ({ slot, }: {
2
3
  slot: {
3
4
  name: string;
4
5
  value: Map<string, string[]>;
5
- remove: (key: string) => void;
6
- add: (key: string, val: string[]) => void;
6
+ set: (val: Record<string, string[]>) => void;
7
7
  description: string;
8
- setAtKeyIndex: (key: string, idx: number, val: string) => void;
9
- removeAtKeyIndex: (key: string, idx: number) => void;
10
- addToKey: (key: string, val: string) => void;
11
8
  };
12
- }) => import("react/jsx-runtime").JSX.Element;
9
+ }) => import("react").JSX.Element;
13
10
  export default StringArrayMapEditor;