@jbrowse/core 2.3.4 → 2.4.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/BaseFeatureWidget/BaseFeatureDetail.d.ts +18 -19
- package/BaseFeatureWidget/BaseFeatureDetail.js +76 -69
- package/BaseFeatureWidget/SequenceBox.js +9 -3
- package/BaseFeatureWidget/SequenceFeatureDetails.js +70 -52
- package/BaseFeatureWidget/SequencePanel.d.ts +3 -3
- package/BaseFeatureWidget/SequencePanel.js +8 -5
- package/BaseFeatureWidget/index.js +2 -2
- package/CorePlugin.js +2 -7
- package/PluginLoader.d.ts +1 -1
- package/PluginLoader.js +20 -24
- package/PluginManager.d.ts +1 -1
- package/PluginManager.js +2 -3
- package/ReExports/Attributes.d.ts +1 -2
- package/ReExports/Attributes.js +4 -3
- package/ReExports/BaseCard.d.ts +1 -2
- package/ReExports/BaseCard.js +4 -3
- package/ReExports/DataGrid.d.ts +1 -2
- package/ReExports/DataGrid.js +2 -2
- package/ReExports/FeatureDetails.d.ts +1 -2
- package/ReExports/FeatureDetails.js +4 -3
- package/ReExports/index.d.ts +1 -2
- package/ReExports/index.js +3 -2
- package/ReExports/modules.d.ts +1 -1
- package/ReExports/modules.js +2 -2
- package/assemblyManager/assembly.js +5 -5
- package/assemblyManager/assemblyConfigSchema.js +2 -2
- package/configuration/configurationSchema.js +1 -1
- package/configuration/util.js +1 -1
- package/data_adapters/BaseAdapter.js +1 -1
- package/data_adapters/CytobandAdapter/CytobandAdapter.d.ts +8 -0
- package/data_adapters/CytobandAdapter/CytobandAdapter.js +40 -0
- package/data_adapters/CytobandAdapter/configSchema.d.ts +2 -0
- package/data_adapters/CytobandAdapter/configSchema.js +17 -0
- package/data_adapters/CytobandAdapter/index.d.ts +3 -0
- package/data_adapters/CytobandAdapter/index.js +37 -0
- package/data_adapters/dataAdapterCache.d.ts +3 -2
- package/data_adapters/dataAdapterCache.js +2 -3
- package/package.json +5 -4
- package/pluggableElementTypes/PluggableElementBase.d.ts +1 -1
- package/pluggableElementTypes/PluggableElementBase.js +1 -2
- package/pluggableElementTypes/RpcMethodType.d.ts +5 -8
- package/pluggableElementTypes/RpcMethodType.js +23 -34
- package/pluggableElementTypes/index.d.ts +11 -1
- package/pluggableElementTypes/index.js +23 -23
- package/pluggableElementTypes/models/BaseConnectionModelFactory.js +2 -2
- package/pluggableElementTypes/models/BaseTrackModel.js +8 -13
- package/pluggableElementTypes/renderers/CircularChordRendererType.d.ts +13 -2
- package/pluggableElementTypes/renderers/CircularChordRendererType.js +10 -2
- package/pluggableElementTypes/renderers/ComparativeServerSideRendererType.d.ts +8 -2
- package/pluggableElementTypes/renderers/ComparativeServerSideRendererType.js +10 -4
- package/pluggableElementTypes/renderers/ServerSideRendererType.d.ts +1 -1
- package/pluggableElementTypes/renderers/ServerSideRendererType.js +2 -34
- package/pluggableElementTypes/renderers/index.d.ts +7 -9
- package/pluggableElementTypes/renderers/index.js +15 -15
- package/pluggableElementTypes/renderers/util/serializableFilterChain.js +1 -1
- package/rpc/BaseRpcDriver.js +7 -8
- package/rpc/WebWorkerRpcDriver.js +18 -12
- package/rpc/coreRpcMethods.d.ts +9 -11
- package/rpc/coreRpcMethods.js +17 -17
- package/rpc/methods/CoreGetFeatureDetails.js +1 -1
- package/rpc/methods/util.d.ts +2 -2
- package/rpc/methods/util.js +2 -2
- package/rpc/remoteAbortSignals.js +0 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/ui/App.js +3 -18
- package/ui/AppLogo.js +1 -6
- package/ui/ColorPicker.js +1 -1
- package/ui/Dialog.js +1 -1
- package/ui/DrawerWidget.js +4 -4
- package/ui/EditableTypography.js +1 -1
- package/ui/FileSelector/FileSelector.d.ts +2 -2
- package/ui/FileSelector/FileSelector.js +24 -35
- package/ui/FileSelector/index.d.ts +1 -2
- package/ui/FileSelector/index.js +3 -2
- package/ui/LoadingEllipses.js +2 -2
- package/ui/Menu.js +45 -32
- package/ui/ResizeBar.js +10 -6
- package/ui/ResizeHandle.js +3 -6
- package/ui/SanitizedHTML.js +2 -0
- package/ui/ViewContainer.js +7 -44
- package/ui/ViewMenu.d.ts +9 -0
- package/ui/ViewMenu.js +69 -0
- package/ui/ViewPanel.d.ts +19 -0
- package/ui/ViewPanel.js +49 -0
- package/ui/theme.d.ts +10 -166
- package/ui/theme.js +260 -48
- package/util/Base1DUtils.js +16 -14
- package/util/Base1DViewModel.d.ts +1 -1
- package/util/Base1DViewModel.js +9 -8
- package/util/aborting.js +1 -1
- package/util/analytics.js +1 -1
- package/util/blockTypes.js +10 -10
- package/util/color/index.d.ts +1 -2
- package/util/color/index.js +4 -3
- package/util/idMaker.js +5 -8
- package/util/index.d.ts +9 -9
- package/util/index.js +35 -52
- package/util/io/RemoteFileWithRangeCache.js +2 -2
- package/util/io/index.d.ts +1 -2
- package/util/io/index.js +6 -6
- package/util/jexl.js +3 -1
- package/util/layouts/GranularRectLayout.js +10 -4
- package/util/layouts/MultiLayout.js +1 -1
- package/util/layouts/SceneGraph.js +3 -7
- package/util/map-obj.d.ts +3 -0
- package/util/map-obj.js +33 -0
- package/util/offscreenCanvasPonyfill.js +4 -3
- package/util/offscreenCanvasUtils.d.ts +18 -4
- package/util/offscreenCanvasUtils.js +49 -7
- package/util/tracks.d.ts +1 -1
- package/util/tracks.js +0 -1
- package/util/types/index.d.ts +1 -1
- package/util/types/index.js +3 -3
- package/util/types/mst.js +3 -3
- package/data_adapters/CytobandAdapter.d.ts +0 -8
- package/data_adapters/CytobandAdapter.js +0 -49
|
@@ -23,15 +23,21 @@ export declare const FieldName: ({ description, name, width, prefix, }: {
|
|
|
23
23
|
export declare const BasicValue: ({ value }: {
|
|
24
24
|
value: string | React.ReactNode;
|
|
25
25
|
}) => JSX.Element;
|
|
26
|
-
export declare
|
|
26
|
+
export declare function SimpleValue({ name, value, description, prefix, width, }: {
|
|
27
27
|
description?: React.ReactNode;
|
|
28
28
|
name: string;
|
|
29
29
|
value: any;
|
|
30
|
-
prefix?: string[]
|
|
31
|
-
width?: number
|
|
32
|
-
})
|
|
30
|
+
prefix?: string[];
|
|
31
|
+
width?: number;
|
|
32
|
+
}): JSX.Element | null;
|
|
33
33
|
export declare const BaseCoreDetails: (props: BaseProps) => JSX.Element;
|
|
34
|
-
|
|
34
|
+
export declare function UriLink({ value, }: {
|
|
35
|
+
value: {
|
|
36
|
+
uri: string;
|
|
37
|
+
baseUri?: string;
|
|
38
|
+
};
|
|
39
|
+
}): JSX.Element;
|
|
40
|
+
export declare function Attributes(props: {
|
|
35
41
|
attributes: Record<string, any>;
|
|
36
42
|
omit?: string[];
|
|
37
43
|
omitSingleLevel?: string[];
|
|
@@ -39,14 +45,7 @@ interface AttributeProps {
|
|
|
39
45
|
descriptions?: Record<string, React.ReactNode>;
|
|
40
46
|
prefix?: string[];
|
|
41
47
|
hideUris?: boolean;
|
|
42
|
-
}
|
|
43
|
-
export declare function UriLink({ value, }: {
|
|
44
|
-
value: {
|
|
45
|
-
uri: string;
|
|
46
|
-
baseUri?: string;
|
|
47
|
-
};
|
|
48
48
|
}): JSX.Element;
|
|
49
|
-
export declare function Attributes(props: AttributeProps): JSX.Element;
|
|
50
49
|
export declare const BaseAttributes: (props: BaseProps) => JSX.Element;
|
|
51
50
|
export interface BaseInputProps extends BaseCardProps {
|
|
52
51
|
omit?: string[];
|
|
@@ -54,12 +53,12 @@ export interface BaseInputProps extends BaseCardProps {
|
|
|
54
53
|
descriptions?: Record<string, React.ReactNode>;
|
|
55
54
|
formatter?: (val: unknown, key: string) => React.ReactNode;
|
|
56
55
|
}
|
|
57
|
-
export declare
|
|
56
|
+
export declare function FeatureDetails(props: {
|
|
58
57
|
model: IAnyStateTreeNode;
|
|
59
58
|
feature: SimpleFeatureSerializedNoId;
|
|
60
|
-
depth?: number
|
|
61
|
-
omit?: string[]
|
|
62
|
-
formatter?: (
|
|
63
|
-
})
|
|
64
|
-
declare const
|
|
65
|
-
export default
|
|
59
|
+
depth?: number;
|
|
60
|
+
omit?: string[];
|
|
61
|
+
formatter?: (val: unknown, key: string) => React.ReactNode;
|
|
62
|
+
}): JSX.Element;
|
|
63
|
+
declare const _default: ({ model }: BaseInputProps) => JSX.Element | null;
|
|
64
|
+
export default _default;
|
|
@@ -32,10 +32,11 @@ const react_1 = __importStar(require("react"));
|
|
|
32
32
|
const react_error_boundary_1 = require("react-error-boundary");
|
|
33
33
|
const material_1 = require("@mui/material");
|
|
34
34
|
const mui_1 = require("tss-react/mui");
|
|
35
|
-
const ExpandMore_1 = __importDefault(require("@mui/icons-material/ExpandMore"));
|
|
36
35
|
const x_data_grid_1 = require("@mui/x-data-grid");
|
|
37
36
|
const mobx_react_1 = require("mobx-react");
|
|
38
37
|
const is_object_1 = __importDefault(require("is-object"));
|
|
38
|
+
// icons
|
|
39
|
+
const ExpandMore_1 = __importDefault(require("@mui/icons-material/ExpandMore"));
|
|
39
40
|
// locals
|
|
40
41
|
const util_1 = require("../util");
|
|
41
42
|
const ui_1 = require("../ui");
|
|
@@ -64,53 +65,56 @@ const coreDetails = [
|
|
|
64
65
|
'description',
|
|
65
66
|
'type',
|
|
66
67
|
];
|
|
67
|
-
exports.useStyles = (0, mui_1.makeStyles)()(theme =>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
color: '#FFFFFF',
|
|
74
|
-
},
|
|
75
|
-
field: {
|
|
76
|
-
display: 'flex',
|
|
77
|
-
flexWrap: 'wrap',
|
|
78
|
-
},
|
|
79
|
-
fieldDescription: {
|
|
80
|
-
'&:hover': {
|
|
81
|
-
background: 'yellow',
|
|
68
|
+
exports.useStyles = (0, mui_1.makeStyles)()(theme => {
|
|
69
|
+
var _a;
|
|
70
|
+
return ({
|
|
71
|
+
expansionPanelDetails: {
|
|
72
|
+
display: 'block',
|
|
73
|
+
padding: theme.spacing(1),
|
|
82
74
|
},
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
75
|
+
expandIcon: {
|
|
76
|
+
color: ((_a = theme.palette.tertiary) === null || _a === void 0 ? void 0 : _a.contrastText) || '#fff',
|
|
77
|
+
},
|
|
78
|
+
field: {
|
|
79
|
+
display: 'flex',
|
|
80
|
+
flexWrap: 'wrap',
|
|
81
|
+
},
|
|
82
|
+
fieldDescription: {
|
|
83
|
+
'&:hover': {
|
|
84
|
+
background: theme.palette.mode === 'dark' ? '#e65100' : 'yellow',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
fieldName: {
|
|
88
|
+
wordBreak: 'break-all',
|
|
89
|
+
minWidth: 90,
|
|
90
|
+
borderBottom: '1px solid #0003',
|
|
91
|
+
fontSize: 12,
|
|
92
|
+
background: theme.palette.action.disabledBackground,
|
|
93
|
+
marginRight: theme.spacing(1),
|
|
94
|
+
padding: theme.spacing(0.5),
|
|
95
|
+
},
|
|
96
|
+
fieldValue: {
|
|
97
|
+
wordBreak: 'break-word',
|
|
98
|
+
maxHeight: 300,
|
|
99
|
+
fontSize: 12,
|
|
100
|
+
padding: theme.spacing(0.5),
|
|
101
|
+
overflow: 'auto',
|
|
102
|
+
},
|
|
103
|
+
fieldSubvalue: {
|
|
104
|
+
wordBreak: 'break-word',
|
|
105
|
+
maxHeight: 300,
|
|
106
|
+
padding: theme.spacing(0.5),
|
|
107
|
+
background: theme.palette.action.disabledBackground,
|
|
108
|
+
border: `1px solid ${theme.palette.action.disabledBackground}`,
|
|
109
|
+
boxSizing: 'border-box',
|
|
110
|
+
overflow: 'auto',
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
});
|
|
110
114
|
function BaseCard({ children, title, defaultExpanded = true, }) {
|
|
111
115
|
const { classes } = (0, exports.useStyles)();
|
|
112
116
|
const [expanded, setExpanded] = (0, react_1.useState)(defaultExpanded);
|
|
113
|
-
return (react_1.default.createElement(material_1.Accordion, { expanded: expanded, onChange: () => setExpanded(s => !s), TransitionProps: { unmountOnExit: true } },
|
|
117
|
+
return (react_1.default.createElement(material_1.Accordion, { expanded: expanded, onChange: () => setExpanded(s => !s), TransitionProps: { unmountOnExit: true, timeout: 150 } },
|
|
114
118
|
react_1.default.createElement(material_1.AccordionSummary, { expandIcon: react_1.default.createElement(ExpandMore_1.default, { className: classes.expandIcon }) },
|
|
115
119
|
react_1.default.createElement(material_1.Typography, { variant: "button" },
|
|
116
120
|
" ",
|
|
@@ -128,17 +132,17 @@ exports.FieldName = FieldName;
|
|
|
128
132
|
const BasicValue = ({ value }) => {
|
|
129
133
|
const { classes } = (0, exports.useStyles)();
|
|
130
134
|
const isLink = `${value}`.match(/^https?:\/\//);
|
|
131
|
-
return (react_1.default.createElement("div", { className: classes.fieldValue }, react_1.default.isValidElement(value) ? (value) : isLink ? (react_1.default.createElement(
|
|
135
|
+
return (react_1.default.createElement("div", { className: classes.fieldValue }, react_1.default.isValidElement(value) ? (value) : isLink ? (react_1.default.createElement(material_1.Link, { href: `${value}` }, `${value}`)) : (react_1.default.createElement(ui_1.SanitizedHTML, { html: (0, is_object_1.default)(value) ? JSON.stringify(value) : String(value) }))));
|
|
132
136
|
};
|
|
133
137
|
exports.BasicValue = BasicValue;
|
|
134
|
-
|
|
138
|
+
function SimpleValue({ name, value, description, prefix, width, }) {
|
|
135
139
|
const { classes } = (0, exports.useStyles)();
|
|
136
140
|
return value !== null && value !== undefined ? (react_1.default.createElement("div", { className: classes.field },
|
|
137
141
|
react_1.default.createElement(exports.FieldName, { prefix: prefix, description: description, name: name, width: width }),
|
|
138
142
|
react_1.default.createElement(exports.BasicValue, { value: value }))) : null;
|
|
139
|
-
}
|
|
143
|
+
}
|
|
140
144
|
exports.SimpleValue = SimpleValue;
|
|
141
|
-
|
|
145
|
+
function ArrayValue({ name, value, description, prefix = [], }) {
|
|
142
146
|
const { classes } = (0, exports.useStyles)();
|
|
143
147
|
if (value.length === 1) {
|
|
144
148
|
return (0, is_object_1.default)(value[0]) ? (react_1.default.createElement(Attributes, { attributes: value[0], prefix: [...prefix, name] })) : (react_1.default.createElement("div", { className: classes.field },
|
|
@@ -154,7 +158,7 @@ const ArrayValue = ({ name, value, description, prefix = [], }) => {
|
|
|
154
158
|
value.map((val, i) => (react_1.default.createElement("div", { key: JSON.stringify(val) + '-' + i, className: classes.fieldSubvalue },
|
|
155
159
|
react_1.default.createElement(exports.BasicValue, { value: val }))))));
|
|
156
160
|
}
|
|
157
|
-
}
|
|
161
|
+
}
|
|
158
162
|
const toLocale = (n) => n.toLocaleString('en-US');
|
|
159
163
|
function Position(props) {
|
|
160
164
|
const { feature } = props;
|
|
@@ -165,7 +169,7 @@ function Position(props) {
|
|
|
165
169
|
'1': '+',
|
|
166
170
|
};
|
|
167
171
|
const str = strandMap[strand] ? `(${strandMap[strand]})` : '';
|
|
168
|
-
// @ts-
|
|
172
|
+
// @ts-expect-error
|
|
169
173
|
const loc = (0, util_1.assembleLocString)(feature);
|
|
170
174
|
return react_1.default.createElement(react_1.default.Fragment, null, `${loc} ${str}`);
|
|
171
175
|
}
|
|
@@ -186,11 +190,11 @@ function CoreDetails(props) {
|
|
|
186
190
|
type: 'Type',
|
|
187
191
|
};
|
|
188
192
|
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
189
|
-
react_1.default.createElement(
|
|
193
|
+
react_1.default.createElement(SimpleValue, { name: "Position", value: react_1.default.createElement(Position, { ...props, feature: formattedFeat }) }),
|
|
190
194
|
Object.entries(coreRenderedDetails)
|
|
191
195
|
.map(([key, name]) => [name, displayedDetails[key]])
|
|
192
196
|
.filter(([, value]) => value != null)
|
|
193
|
-
.map(([name, value]) => (react_1.default.createElement(
|
|
197
|
+
.map(([name, value]) => (react_1.default.createElement(SimpleValue, { key: name, name: name, value: value })))));
|
|
194
198
|
}
|
|
195
199
|
const BaseCoreDetails = (props) => {
|
|
196
200
|
return (react_1.default.createElement(BaseCard, { ...props, title: "Primary data" },
|
|
@@ -202,10 +206,14 @@ function UriLink({ value, }) {
|
|
|
202
206
|
return react_1.default.createElement(ui_1.SanitizedHTML, { html: `<a href="${href}">${href}</a>` });
|
|
203
207
|
}
|
|
204
208
|
exports.UriLink = UriLink;
|
|
205
|
-
|
|
209
|
+
function DataGridDetails({ value, prefix, name, }) {
|
|
206
210
|
const keys = Object.keys(value[0]).sort();
|
|
207
211
|
const unionKeys = new Set(keys);
|
|
208
|
-
|
|
212
|
+
for (const val of value) {
|
|
213
|
+
for (const k of Object.keys(val)) {
|
|
214
|
+
unionKeys.add(k);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
209
217
|
if (unionKeys.size < keys.length + 5) {
|
|
210
218
|
// avoids key 'id' from being used in row data
|
|
211
219
|
const rows = Object.entries(value).map(([k, val]) => {
|
|
@@ -249,7 +257,7 @@ const DataGridDetails = ({ value, prefix, name, }) => {
|
|
|
249
257
|
react_1.default.createElement(x_data_grid_1.DataGrid, { disableSelectionOnClick: true, rowHeight: rowHeight, rows: rows, hideFooterSelectedRowCount: true, columns: columns, hideFooter: hideFoot }))));
|
|
250
258
|
}
|
|
251
259
|
return null;
|
|
252
|
-
}
|
|
260
|
+
}
|
|
253
261
|
// pick using a path from an object, similar to _.get from lodash with special logic
|
|
254
262
|
// for Descriptions from e.g. VCF headers
|
|
255
263
|
// @param arr example ['a','b'], obj = {a:{b:'hello}}
|
|
@@ -290,12 +298,12 @@ function UriAttribute({ value, prefix, name, }) {
|
|
|
290
298
|
}
|
|
291
299
|
function Attributes(props) {
|
|
292
300
|
const { attributes, omit = [], omitSingleLevel = [], descriptions, formatter = val => val, hideUris, prefix = [], } = props;
|
|
293
|
-
const omits = [...omit, ...globalOmit, ...omitSingleLevel];
|
|
301
|
+
const omits = new Set([...omit, ...globalOmit, ...omitSingleLevel]);
|
|
294
302
|
const { __jbrowsefmt, ...rest } = attributes;
|
|
295
303
|
const formattedAttributes = { ...rest, ...__jbrowsefmt };
|
|
296
|
-
const maxLabelWidth = generateMaxWidth(Object.entries(formattedAttributes).filter(([k, v]) => v !== undefined && !omits.
|
|
304
|
+
const maxLabelWidth = generateMaxWidth(Object.entries(formattedAttributes).filter(([k, v]) => v !== undefined && !omits.has(k)), prefix);
|
|
297
305
|
return (react_1.default.createElement(react_1.default.Fragment, null, Object.entries(formattedAttributes)
|
|
298
|
-
.filter(([k, v]) => v !== undefined && !omits.
|
|
306
|
+
.filter(([k, v]) => v !== undefined && !omits.has(k))
|
|
299
307
|
.map(([key, value]) => {
|
|
300
308
|
const description = accessNested([...prefix, key], descriptions);
|
|
301
309
|
if (Array.isArray(value)) {
|
|
@@ -308,7 +316,7 @@ function Attributes(props) {
|
|
|
308
316
|
return (0, util_1.isUriLocation)(value) ? (hideUris ? null : (react_1.default.createElement(UriAttribute, { key: key, name: key, prefix: prefix, value: value }))) : (react_1.default.createElement(Attributes, { ...rest, key: key, attributes: value, descriptions: descriptions, prefix: [...prefix, key] }));
|
|
309
317
|
}
|
|
310
318
|
else {
|
|
311
|
-
return (react_1.default.createElement(
|
|
319
|
+
return (react_1.default.createElement(SimpleValue, { key: key, name: key, value: formatter(value, key), description: description, prefix: prefix, width: Math.min(maxLabelWidth, MAX_FIELD_NAME_WIDTH) }));
|
|
312
320
|
}
|
|
313
321
|
})));
|
|
314
322
|
}
|
|
@@ -327,9 +335,9 @@ function generateTitle(name, id, type) {
|
|
|
327
335
|
.filter(f => !!f)
|
|
328
336
|
.join(' - ');
|
|
329
337
|
}
|
|
330
|
-
|
|
338
|
+
function FeatureDetails(props) {
|
|
331
339
|
const { omit = [], model, feature, depth = 0 } = props;
|
|
332
|
-
const { name = '', id = '', type = '', subfeatures } = feature;
|
|
340
|
+
const { mate, name = '', id = '', type = '', subfeatures } = feature;
|
|
333
341
|
const pm = (0, util_1.getEnv)(model).pluginManager;
|
|
334
342
|
const session = (0, util_1.getSession)(model);
|
|
335
343
|
const ExtraPanel = pm.evaluateExtensionPoint('Core-extraFeaturePanel', null, {
|
|
@@ -340,10 +348,10 @@ const FeatureDetails = (props) => {
|
|
|
340
348
|
return (react_1.default.createElement(BaseCard, { title: generateTitle(name, id, type) },
|
|
341
349
|
react_1.default.createElement(material_1.Typography, null, "Core details"),
|
|
342
350
|
react_1.default.createElement(CoreDetails, { ...props }),
|
|
343
|
-
|
|
351
|
+
mate ? (react_1.default.createElement(react_1.default.Fragment, null,
|
|
344
352
|
react_1.default.createElement(material_1.Divider, null),
|
|
345
353
|
react_1.default.createElement(material_1.Typography, null, "Mate details"),
|
|
346
|
-
react_1.default.createElement(CoreDetails, { ...props, feature:
|
|
354
|
+
react_1.default.createElement(CoreDetails, { ...props, feature: mate }))) : null,
|
|
347
355
|
react_1.default.createElement(material_1.Divider, null),
|
|
348
356
|
react_1.default.createElement(material_1.Typography, null, "Attributes"),
|
|
349
357
|
react_1.default.createElement(Attributes, { attributes: feature, ...props, omit: omit, omitSingleLevel: coreDetails }),
|
|
@@ -353,10 +361,10 @@ const FeatureDetails = (props) => {
|
|
|
353
361
|
react_1.default.createElement(material_1.Divider, null),
|
|
354
362
|
react_1.default.createElement(BaseCard, { title: ExtraPanel.name },
|
|
355
363
|
react_1.default.createElement(ExtraPanel.Component, { ...props })))) : null,
|
|
356
|
-
(subfeatures === null || subfeatures === void 0 ? void 0 : subfeatures.length) ? (react_1.default.createElement(BaseCard, { title: "Subfeatures", defaultExpanded: depth < 1 }, subfeatures.map(sub => (react_1.default.createElement(
|
|
357
|
-
}
|
|
364
|
+
(subfeatures === null || subfeatures === void 0 ? void 0 : subfeatures.length) ? (react_1.default.createElement(BaseCard, { title: "Subfeatures", defaultExpanded: depth < 1 }, subfeatures.map(sub => (react_1.default.createElement(FeatureDetails, { key: JSON.stringify(sub), feature: sub, model: model, depth: depth + 1 }))))) : null));
|
|
365
|
+
}
|
|
358
366
|
exports.FeatureDetails = FeatureDetails;
|
|
359
|
-
|
|
367
|
+
exports.default = (0, mobx_react_1.observer)(function ({ model }) {
|
|
360
368
|
const { featureData } = model;
|
|
361
369
|
if (!featureData) {
|
|
362
370
|
return null;
|
|
@@ -365,7 +373,6 @@ const BaseFeatureDetails = (0, mobx_react_1.observer)(({ model }) => {
|
|
|
365
373
|
// setting null is not allowed by jexl so we set it to undefined to hide. see
|
|
366
374
|
// config guide. this replacement happens both here and when snapshotting the
|
|
367
375
|
// featureData
|
|
368
|
-
const
|
|
369
|
-
return isEmpty(
|
|
376
|
+
const g = JSON.parse(JSON.stringify(featureData, (_, v) => (v === undefined ? null : v)));
|
|
377
|
+
return isEmpty(g) ? null : react_1.default.createElement(FeatureDetails, { model: model, feature: g });
|
|
370
378
|
});
|
|
371
|
-
exports.default = BaseFeatureDetails;
|
|
@@ -31,7 +31,9 @@ exports.GeneProtein = GeneProtein;
|
|
|
31
31
|
function GenecDNA({ utr, cds, exons, sequence, upstream, downstream, includeIntrons, collapseIntron, intronBp, }) {
|
|
32
32
|
const chunks = (cds.length ? [...cds, ...utr].sort((a, b) => a.start - b.start) : exons).filter(f => f.start !== f.end);
|
|
33
33
|
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
34
|
-
upstream ? (react_1.default.createElement("span", { style: {
|
|
34
|
+
upstream ? (react_1.default.createElement("span", { style: {
|
|
35
|
+
background: updownstreamColor,
|
|
36
|
+
} }, upstream)) : null,
|
|
35
37
|
chunks.map((chunk, idx) => {
|
|
36
38
|
var _a;
|
|
37
39
|
const intron = sequence.slice(chunk.end, (_a = chunks[idx + 1]) === null || _a === void 0 ? void 0 : _a.start);
|
|
@@ -48,10 +50,14 @@ function GenecDNA({ utr, cds, exons, sequence, upstream, downstream, includeIntr
|
|
|
48
50
|
exports.GenecDNA = GenecDNA;
|
|
49
51
|
function Genomic({ sequence, upstream, downstream, }) {
|
|
50
52
|
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
51
|
-
upstream ? (react_1.default.createElement("span", { style: {
|
|
53
|
+
upstream ? (react_1.default.createElement("span", { style: {
|
|
54
|
+
background: updownstreamColor,
|
|
55
|
+
} }, upstream)) : null,
|
|
52
56
|
react_1.default.createElement("span", { style: {
|
|
53
57
|
background: genomeColor,
|
|
54
58
|
} }, sequence),
|
|
55
|
-
downstream ? (react_1.default.createElement("span", { style: {
|
|
59
|
+
downstream ? (react_1.default.createElement("span", { style: {
|
|
60
|
+
background: updownstreamColor,
|
|
61
|
+
} }, downstream)) : null));
|
|
56
62
|
}
|
|
57
63
|
exports.Genomic = Genomic;
|
|
@@ -31,15 +31,16 @@ const material_1 = require("@mui/material");
|
|
|
31
31
|
const mui_1 = require("tss-react/mui");
|
|
32
32
|
const copy_to_clipboard_1 = __importDefault(require("copy-to-clipboard"));
|
|
33
33
|
// locals
|
|
34
|
-
const SequenceFeatureSettingsDialog_1 = __importDefault(require("./SequenceFeatureSettingsDialog"));
|
|
35
|
-
const SequenceHelpDialog_1 = __importDefault(require("./SequenceHelpDialog"));
|
|
36
|
-
const SequencePanel_1 = __importDefault(require("./SequencePanel"));
|
|
37
34
|
const util_1 = require("../util");
|
|
38
35
|
const configuration_1 = require("../configuration");
|
|
39
36
|
const ui_1 = require("../ui");
|
|
40
37
|
// icons
|
|
41
38
|
const Settings_1 = __importDefault(require("@mui/icons-material/Settings"));
|
|
42
39
|
const Help_1 = __importDefault(require("@mui/icons-material/Help"));
|
|
40
|
+
// lazies
|
|
41
|
+
const SettingsDlg = (0, react_1.lazy)(() => Promise.resolve().then(() => __importStar(require('./SequenceFeatureSettingsDialog'))));
|
|
42
|
+
const HelpDlg = (0, react_1.lazy)(() => Promise.resolve().then(() => __importStar(require('./SequenceHelpDialog'))));
|
|
43
|
+
const SequencePanel = (0, react_1.lazy)(() => Promise.resolve().then(() => __importStar(require('./SequencePanel'))));
|
|
43
44
|
const useStyles = (0, mui_1.makeStyles)()(theme => ({
|
|
44
45
|
button: {
|
|
45
46
|
margin: theme.spacing(1),
|
|
@@ -64,27 +65,46 @@ function SequenceFeatureDetails({ model, feature }) {
|
|
|
64
65
|
const parentFeature = feature;
|
|
65
66
|
const hasCDS = !!((_a = parentFeature.subfeatures) === null || _a === void 0 ? void 0 : _a.find(sub => sub.type === 'CDS'));
|
|
66
67
|
const isGene = feature.type === 'gene';
|
|
67
|
-
const seqPanelRef = (0, react_1.useRef)(null);
|
|
68
|
-
const [settingsDlgOpen, setSettingsDlgOpen] = (0, react_1.useState)(false);
|
|
69
68
|
const [shown, setShown] = (0, react_1.useState)(false);
|
|
70
69
|
const [helpShown, setHelpShown] = (0, react_1.useState)(false);
|
|
70
|
+
return (isGene && !hasCDS) || !model ? null : (react_1.default.createElement("div", { className: classes.container2 },
|
|
71
|
+
react_1.default.createElement(material_1.Button, { variant: "contained", onClick: () => setShown(!shown) }, shown ? 'Hide feature sequence' : 'Show feature sequence'),
|
|
72
|
+
react_1.default.createElement(material_1.FormControl, { className: classes.formControl },
|
|
73
|
+
react_1.default.createElement(material_1.IconButton, { onClick: () => setHelpShown(true) },
|
|
74
|
+
react_1.default.createElement(Help_1.default, null))),
|
|
75
|
+
react_1.default.createElement("br", null),
|
|
76
|
+
shown ? react_1.default.createElement(FeatureSequence, { model: model, feature: feature }) : null,
|
|
77
|
+
helpShown ? (react_1.default.createElement(react_1.Suspense, { fallback: react_1.default.createElement("div", null) },
|
|
78
|
+
react_1.default.createElement(HelpDlg, { handleClose: () => setHelpShown(false) }))) : null));
|
|
79
|
+
}
|
|
80
|
+
exports.default = SequenceFeatureDetails;
|
|
81
|
+
function FeatureSequence({ model, feature }) {
|
|
82
|
+
var _a;
|
|
83
|
+
const { classes } = useStyles();
|
|
84
|
+
const parentFeature = feature;
|
|
85
|
+
const hasCDS = !!((_a = parentFeature.subfeatures) === null || _a === void 0 ? void 0 : _a.find(sub => sub.type === 'CDS'));
|
|
86
|
+
const seqPanelRef = (0, react_1.useRef)(null);
|
|
87
|
+
const [settingsDlgOpen, setSettingsDlgOpen] = (0, react_1.useState)(false);
|
|
88
|
+
const [intronBp, setIntronBp] = (0, util_1.useLocalStorage)('intronBp', 10);
|
|
89
|
+
const [upDownBp, setUpDownBp] = (0, util_1.useLocalStorage)('upDownBp', 500);
|
|
71
90
|
const [sequence, setSequence] = (0, react_1.useState)();
|
|
72
91
|
const [error, setError] = (0, react_1.useState)();
|
|
73
92
|
const [copied, setCopied] = (0, react_1.useState)(false);
|
|
74
93
|
const [copiedHtml, setCopiedHtml] = (0, react_1.useState)(false);
|
|
75
|
-
const [intronBp, setIntronBp] = (0, util_1.useLocalStorage)('intronBp', 10);
|
|
76
|
-
const [upDownBp, setUpDownBp] = (0, util_1.useLocalStorage)('upDownBp', 500);
|
|
77
94
|
const [forceLoad, setForceLoad] = (0, react_1.useState)({
|
|
78
95
|
id: feature.uniqueId,
|
|
79
96
|
force: false,
|
|
80
97
|
});
|
|
81
98
|
(0, react_1.useEffect)(() => {
|
|
82
|
-
setForceLoad({
|
|
99
|
+
setForceLoad({
|
|
100
|
+
id: feature.uniqueId,
|
|
101
|
+
force: false,
|
|
102
|
+
});
|
|
83
103
|
}, [feature]);
|
|
84
104
|
(0, react_1.useEffect)(() => {
|
|
85
105
|
var _a;
|
|
86
106
|
let finished = false;
|
|
87
|
-
if (!model
|
|
107
|
+
if (!model) {
|
|
88
108
|
return () => { };
|
|
89
109
|
}
|
|
90
110
|
const { assemblyManager, rpcManager } = (0, util_1.getSession)(model);
|
|
@@ -103,6 +123,7 @@ function SequenceFeatureDetails({ model, feature }) {
|
|
|
103
123
|
start,
|
|
104
124
|
end,
|
|
105
125
|
refName: assembly.getCanonicalRefName(refName),
|
|
126
|
+
assemblyName,
|
|
106
127
|
},
|
|
107
128
|
],
|
|
108
129
|
});
|
|
@@ -137,7 +158,7 @@ function SequenceFeatureDetails({ model, feature }) {
|
|
|
137
158
|
return () => {
|
|
138
159
|
finished = true;
|
|
139
160
|
};
|
|
140
|
-
}, [feature,
|
|
161
|
+
}, [feature, model, upDownBp, forceLoad]);
|
|
141
162
|
const loading = !sequence;
|
|
142
163
|
const session = (0, util_1.getSession)(model);
|
|
143
164
|
const defaultSeqTypes = ['mRNA', 'transcript', 'gene'];
|
|
@@ -147,7 +168,10 @@ function SequenceFeatureDetails({ model, feature }) {
|
|
|
147
168
|
? sequenceTypes.includes('CDS') && !feature.parentId
|
|
148
169
|
: sequenceTypes.includes(feature.type);
|
|
149
170
|
const val = attemptGeneType ? (hasCDS ? 'cds' : 'cdna') : 'genomic';
|
|
150
|
-
// this useEffect is needed to reset the mode/setMode useState because the
|
|
171
|
+
// this useEffect is needed to reset the mode/setMode useState because the
|
|
172
|
+
// contents of the select box can completely change depending on whether we
|
|
173
|
+
// click on a gene feature or non-gene feature, so the current value in the
|
|
174
|
+
// select box must change accordingly
|
|
151
175
|
(0, react_1.useEffect)(() => {
|
|
152
176
|
setMode(val);
|
|
153
177
|
}, [attemptGeneType, val]);
|
|
@@ -171,49 +195,43 @@ function SequenceFeatureDetails({ model, feature }) {
|
|
|
171
195
|
genomic: 'Genomic seq',
|
|
172
196
|
genomic_sequence_updown: `Genomic seq w/ ${upDownBp}bp up+down stream`,
|
|
173
197
|
};
|
|
174
|
-
return (
|
|
175
|
-
react_1.default.createElement(material_1.Button, { variant: "contained", onClick: () => setShown(!shown) }, shown ? 'Hide feature sequence' : 'Show feature sequence'),
|
|
198
|
+
return (react_1.default.createElement("div", { className: classes.container2 },
|
|
176
199
|
react_1.default.createElement(material_1.FormControl, { className: classes.formControl },
|
|
177
|
-
react_1.default.createElement(material_1.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
200
|
+
react_1.default.createElement(material_1.Select, { value: mode, onChange: event => setMode(event.target.value) }, Object.entries(arg).map(([key, val]) => (react_1.default.createElement(material_1.MenuItem, { key: key, value: key }, val))))),
|
|
201
|
+
react_1.default.createElement(material_1.FormControl, { className: classes.formControl },
|
|
202
|
+
react_1.default.createElement(material_1.Button, { className: classes.button, variant: "contained", onClick: () => {
|
|
203
|
+
const ref = seqPanelRef.current;
|
|
204
|
+
if (ref) {
|
|
205
|
+
(0, copy_to_clipboard_1.default)(ref.textContent || '', { format: 'text/plain' });
|
|
206
|
+
setCopied(true);
|
|
207
|
+
setTimeout(() => setCopied(false), 1000);
|
|
208
|
+
}
|
|
209
|
+
} }, copied ? 'Copied to clipboard!' : 'Copy plaintext')),
|
|
210
|
+
react_1.default.createElement(material_1.FormControl, { className: classes.formControl },
|
|
211
|
+
react_1.default.createElement(material_1.Tooltip, { title: "The 'Copy HTML' function retains the colors from the sequence panel but cannot be pasted into some programs like notepad that only expect plain text" },
|
|
212
|
+
react_1.default.createElement(material_1.Button, { className: classes.button, variant: "contained", onClick: () => {
|
|
185
213
|
const ref = seqPanelRef.current;
|
|
186
214
|
if (ref) {
|
|
187
|
-
(0, copy_to_clipboard_1.default)(ref.
|
|
188
|
-
|
|
189
|
-
setTimeout(() =>
|
|
215
|
+
(0, copy_to_clipboard_1.default)(ref.innerHTML, { format: 'text/html' });
|
|
216
|
+
setCopiedHtml(true);
|
|
217
|
+
setTimeout(() => setCopiedHtml(false), 1000);
|
|
190
218
|
}
|
|
191
|
-
} },
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
settingsDlgOpen ? (react_1.default.createElement(SequenceFeatureSettingsDialog_1.default, { handleClose: arg => {
|
|
210
|
-
if (arg) {
|
|
211
|
-
const { upDownBp, intronBp } = arg;
|
|
212
|
-
setIntronBp(intronBp);
|
|
213
|
-
setUpDownBp(upDownBp);
|
|
214
|
-
}
|
|
215
|
-
setSettingsDlgOpen(false);
|
|
216
|
-
}, upDownBp: upDownBp, intronBp: intronBp })) : null,
|
|
217
|
-
helpShown ? react_1.default.createElement(SequenceHelpDialog_1.default, { handleClose: () => setHelpShown(false) }) : null));
|
|
219
|
+
} }, copiedHtml ? 'Copied to clipboard!' : 'Copy HTML'))),
|
|
220
|
+
react_1.default.createElement(material_1.FormControl, { className: classes.formControl },
|
|
221
|
+
react_1.default.createElement(material_1.IconButton, { onClick: () => setSettingsDlgOpen(true) },
|
|
222
|
+
react_1.default.createElement(Settings_1.default, null))),
|
|
223
|
+
react_1.default.createElement("br", null),
|
|
224
|
+
error ? (react_1.default.createElement(material_1.Typography, { color: "error" }, `${error}`)) : loading ? (react_1.default.createElement(ui_1.LoadingEllipses, null)) : sequence ? ('error' in sequence ? (react_1.default.createElement(react_1.default.Fragment, null,
|
|
225
|
+
react_1.default.createElement(material_1.Typography, { color: "error" }, sequence.error),
|
|
226
|
+
react_1.default.createElement(material_1.Button, { variant: "contained", color: "inherit", onClick: () => setForceLoad({ ...forceLoad, force: true }) }, "Force load"))) : (react_1.default.createElement(react_1.Suspense, { fallback: react_1.default.createElement("div", null, "Loading") },
|
|
227
|
+
react_1.default.createElement(SequencePanel, { ref: seqPanelRef, feature: parentFeature, mode: mode, sequence: sequence, intronBp: intronBp })))) : (react_1.default.createElement(material_1.Typography, null, "No sequence found")),
|
|
228
|
+
settingsDlgOpen ? (react_1.default.createElement(react_1.Suspense, { fallback: react_1.default.createElement("div", null) },
|
|
229
|
+
react_1.default.createElement(SettingsDlg, { handleClose: arg => {
|
|
230
|
+
if (arg) {
|
|
231
|
+
const { upDownBp, intronBp } = arg;
|
|
232
|
+
setIntronBp(intronBp);
|
|
233
|
+
setUpDownBp(upDownBp);
|
|
234
|
+
}
|
|
235
|
+
setSettingsDlgOpen(false);
|
|
236
|
+
}, upDownBp: upDownBp, intronBp: intronBp }))) : null));
|
|
218
237
|
}
|
|
219
|
-
exports.default = SequenceFeatureDetails;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { ParentFeat, SeqState } from './util';
|
|
3
|
-
interface
|
|
3
|
+
interface SeqPanelProps {
|
|
4
4
|
sequence: SeqState;
|
|
5
5
|
feature: ParentFeat;
|
|
6
6
|
mode: string;
|
|
7
7
|
intronBp?: number;
|
|
8
8
|
}
|
|
9
|
-
declare const
|
|
10
|
-
export default
|
|
9
|
+
declare const SeqPanel: React.ForwardRefExoticComponent<SeqPanelProps & React.RefAttributes<HTMLDivElement>>;
|
|
10
|
+
export default SeqPanel;
|
|
@@ -7,7 +7,7 @@ const react_1 = __importDefault(require("react"));
|
|
|
7
7
|
const util_1 = require("../util");
|
|
8
8
|
const util_2 = require("./util");
|
|
9
9
|
const SequenceBox_1 = require("./SequenceBox");
|
|
10
|
-
const
|
|
10
|
+
const SeqPanel = react_1.default.forwardRef(function (props, ref) {
|
|
11
11
|
const { feature, mode, intronBp = 10 } = props;
|
|
12
12
|
let { sequence: { seq, upstream = '', downstream = '' }, } = props;
|
|
13
13
|
const { subfeatures = [] } = feature;
|
|
@@ -55,18 +55,21 @@ const SequencePanel = react_1.default.forwardRef((props, ref) => {
|
|
|
55
55
|
}
|
|
56
56
|
const codonTable = (0, util_1.generateCodonTable)(util_1.defaultCodonTable);
|
|
57
57
|
return (react_1.default.createElement("div", { ref: ref, "data-testid": "sequence_panel" },
|
|
58
|
-
react_1.default.createElement("div", { style:
|
|
58
|
+
react_1.default.createElement("div", { style:
|
|
59
|
+
/* raw styles so that html copy works */
|
|
60
|
+
{
|
|
59
61
|
fontFamily: 'monospace',
|
|
60
62
|
wordWrap: 'break-word',
|
|
61
63
|
overflow: 'auto',
|
|
64
|
+
color: 'black',
|
|
62
65
|
fontSize: 12,
|
|
63
66
|
maxWidth: 600,
|
|
64
67
|
maxHeight: 500,
|
|
65
68
|
} },
|
|
66
|
-
`>${feature.name ||
|
|
69
|
+
react_1.default.createElement("span", { style: { background: 'white' } }, `>${feature.name ||
|
|
67
70
|
feature.id ||
|
|
68
|
-
feature.refName
|
|
71
|
+
`${feature.refName}:${feature.start + 1}-${feature.end}`}-${mode}\n`),
|
|
69
72
|
react_1.default.createElement("br", null),
|
|
70
73
|
mode === 'genomic' ? (react_1.default.createElement(SequenceBox_1.Genomic, { sequence: seq })) : mode === 'genomic_sequence_updown' ? (react_1.default.createElement(SequenceBox_1.Genomic, { sequence: seq, upstream: upstream, downstream: downstream })) : mode === 'cds' ? (react_1.default.createElement(SequenceBox_1.GeneCDS, { cds: cds, sequence: seq })) : mode === 'cdna' ? (react_1.default.createElement(SequenceBox_1.GenecDNA, { exons: exons, cds: cds, utr: utr, sequence: seq, intronBp: intronBp })) : mode === 'protein' ? (react_1.default.createElement(SequenceBox_1.GeneProtein, { cds: cds, codonTable: codonTable, sequence: seq })) : mode === 'gene' ? (react_1.default.createElement(SequenceBox_1.GenecDNA, { exons: exons, cds: cds, utr: utr, sequence: seq, includeIntrons: true, intronBp: intronBp })) : mode === 'gene_collapsed_intron' ? (react_1.default.createElement(SequenceBox_1.GenecDNA, { exons: exons, cds: cds, sequence: seq, utr: utr, includeIntrons: true, collapseIntron: true, intronBp: intronBp })) : mode === 'gene_updownstream' ? (react_1.default.createElement(SequenceBox_1.GenecDNA, { exons: exons, cds: cds, sequence: seq, utr: utr, upstream: upstream, downstream: downstream, includeIntrons: true, intronBp: intronBp })) : mode === 'gene_updownstream_collapsed_intron' ? (react_1.default.createElement(SequenceBox_1.GenecDNA, { exons: exons, cds: cds, sequence: seq, utr: utr, upstream: upstream, downstream: downstream, includeIntrons: true, collapseIntron: true, intronBp: intronBp })) : (react_1.default.createElement("div", null, "Unknown type")))));
|
|
71
74
|
});
|
|
72
|
-
exports.default =
|
|
75
|
+
exports.default = SeqPanel;
|
|
@@ -82,7 +82,7 @@ function stateModelFactory(pluginManager) {
|
|
|
82
82
|
},
|
|
83
83
|
}))
|
|
84
84
|
.preProcessSnapshot(snap => {
|
|
85
|
-
// @ts-
|
|
85
|
+
// @ts-expect-error
|
|
86
86
|
const { featureData, finalizedFeatureData, ...rest } = snap;
|
|
87
87
|
return {
|
|
88
88
|
unformattedFeatureData: featureData,
|
|
@@ -100,7 +100,7 @@ function stateModelFactory(pluginManager) {
|
|
|
100
100
|
// hidden, setting null is not allowed by jexl so we set it to
|
|
101
101
|
// undefined to hide. see config guide. this replacement happens both
|
|
102
102
|
// here and when displaying the featureData in base feature widget
|
|
103
|
-
finalizedFeatureData: JSON.parse(JSON.stringify(featureData, (_, v) =>
|
|
103
|
+
finalizedFeatureData: JSON.parse(JSON.stringify(featureData, (_, v) => (v === undefined ? null : v))),
|
|
104
104
|
...rest,
|
|
105
105
|
};
|
|
106
106
|
});
|