@jbrowse/core 2.1.5 → 2.1.6
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 +2 -1
- package/BaseFeatureWidget/BaseFeatureDetail.js +16 -17
- package/BaseFeatureWidget/SequenceBox.d.ts +29 -0
- package/BaseFeatureWidget/SequenceBox.js +61 -0
- package/BaseFeatureWidget/SequenceFeatureDetails.d.ts +1 -10
- package/BaseFeatureWidget/SequenceFeatureDetails.js +100 -152
- package/BaseFeatureWidget/SequenceHelpDialog.d.ts +4 -0
- package/BaseFeatureWidget/SequenceHelpDialog.js +51 -0
- package/BaseFeatureWidget/SequencePanel.d.ts +10 -0
- package/BaseFeatureWidget/SequencePanel.js +72 -0
- package/BaseFeatureWidget/index.d.ts +7 -0
- package/BaseFeatureWidget/index.js +21 -5
- package/BaseFeatureWidget/util.d.ts +11 -0
- package/BaseFeatureWidget/util.js +13 -1
- package/PluginLoader.d.ts +29 -13
- package/PluginLoader.js +47 -77
- package/PluginManager.d.ts +1 -0
- package/PluginManager.js +9 -3
- package/assemblyManager/assembly.d.ts +8 -15
- package/assemblyManager/assembly.js +14 -17
- package/assemblyManager/assemblyManager.d.ts +37 -86
- package/configuration/configurationSlot.js +2 -1
- package/data_adapters/CytobandAdapter.js +1 -1
- package/package.json +3 -3
- package/pluggableElementTypes/models/BaseDisplayModel.d.ts +1 -1
- package/pluggableElementTypes/models/BaseDisplayModel.js +3 -2
- package/pluggableElementTypes/models/BaseTrackModel.d.ts +1 -1
- package/pluggableElementTypes/models/BaseTrackModel.js +1 -1
- package/pluggableElementTypes/models/baseTrackConfig.js +16 -4
- package/rpc/RpcManager.d.ts +1 -1
- package/rpc/RpcManager.js +4 -1
- package/rpc/WebWorkerRpcDriver.d.ts +2 -0
- package/rpc/WebWorkerRpcDriver.js +21 -11
- package/rpc/configSchema.js +4 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/ui/AboutDialog.d.ts +6 -0
- package/ui/AboutDialog.js +42 -20
- package/ui/App.js +2 -2
- package/ui/DrawerWidget.js +9 -7
- package/ui/FatalErrorDialog.d.ts +6 -22
- package/ui/FatalErrorDialog.js +10 -29
- package/util/index.d.ts +4 -0
- package/util/index.js +5 -1
- package/util/stats.js +7 -5
- package/util/tracks.d.ts +5 -2
- package/util/tracks.js +15 -5
|
@@ -37,6 +37,7 @@ interface AttributeProps {
|
|
|
37
37
|
formatter?: (val: unknown, key: string) => React.ReactNode;
|
|
38
38
|
descriptions?: Record<string, React.ReactNode>;
|
|
39
39
|
prefix?: string[];
|
|
40
|
+
hideUris?: boolean;
|
|
40
41
|
}
|
|
41
42
|
export declare function UriLink({ value, }: {
|
|
42
43
|
value: {
|
|
@@ -59,5 +60,5 @@ export declare const FeatureDetails: (props: {
|
|
|
59
60
|
omit?: string[] | undefined;
|
|
60
61
|
formatter?: ((val: unknown, key: string) => React.ReactNode) | undefined;
|
|
61
62
|
}) => JSX.Element;
|
|
62
|
-
declare const BaseFeatureDetails: (
|
|
63
|
+
declare const BaseFeatureDetails: ({ model }: BaseInputProps) => JSX.Element | null;
|
|
63
64
|
export default BaseFeatureDetails;
|
|
@@ -37,9 +37,8 @@ const x_data_grid_1 = require("@mui/x-data-grid");
|
|
|
37
37
|
const mobx_react_1 = require("mobx-react");
|
|
38
38
|
const is_object_1 = __importDefault(require("is-object"));
|
|
39
39
|
// locals
|
|
40
|
-
const configuration_1 = require("../configuration");
|
|
41
40
|
const util_1 = require("../util");
|
|
42
|
-
const
|
|
41
|
+
const ui_1 = require("../ui");
|
|
43
42
|
const SequenceFeatureDetails_1 = __importDefault(require("./SequenceFeatureDetails"));
|
|
44
43
|
const util_2 = require("./util");
|
|
45
44
|
const MAX_FIELD_NAME_WIDTH = 170;
|
|
@@ -129,7 +128,7 @@ exports.FieldName = FieldName;
|
|
|
129
128
|
const BasicValue = ({ value }) => {
|
|
130
129
|
const { classes } = (0, exports.useStyles)();
|
|
131
130
|
const isLink = `${value}`.match(/^https?:\/\//);
|
|
132
|
-
return (react_1.default.createElement("div", { className: classes.fieldValue }, react_1.default.isValidElement(value) ? (value) : isLink ? (react_1.default.createElement(
|
|
131
|
+
return (react_1.default.createElement("div", { className: classes.fieldValue }, react_1.default.isValidElement(value) ? (value) : isLink ? (react_1.default.createElement(ui_1.SanitizedHTML, { html: `<a href="${value}">${value}</a>` })) : (react_1.default.createElement(ui_1.SanitizedHTML, { html: (0, is_object_1.default)(value) ? JSON.stringify(value) : String(value) }))));
|
|
133
132
|
};
|
|
134
133
|
exports.BasicValue = BasicValue;
|
|
135
134
|
const SimpleValue = ({ name, value, description, prefix, width, }) => {
|
|
@@ -194,7 +193,7 @@ const BaseCoreDetails = (props) => {
|
|
|
194
193
|
exports.BaseCoreDetails = BaseCoreDetails;
|
|
195
194
|
function UriLink({ value, }) {
|
|
196
195
|
const href = (0, util_1.getUriLink)(value);
|
|
197
|
-
return react_1.default.createElement(
|
|
196
|
+
return react_1.default.createElement(ui_1.SanitizedHTML, { html: `<a href="${href}">${href}</a>` });
|
|
198
197
|
}
|
|
199
198
|
exports.UriLink = UriLink;
|
|
200
199
|
const DataGridDetails = ({ value, prefix, name, }) => {
|
|
@@ -284,7 +283,7 @@ function UriAttribute({ value, prefix, name, }) {
|
|
|
284
283
|
react_1.default.createElement(exports.BasicValue, { value: href })));
|
|
285
284
|
}
|
|
286
285
|
function Attributes(props) {
|
|
287
|
-
const { attributes, omit = [], descriptions, formatter = val => val, prefix = [], } = props;
|
|
286
|
+
const { attributes, omit = [], descriptions, formatter = val => val, hideUris, prefix = [], } = props;
|
|
288
287
|
const omits = [...omit, ...globalOmit];
|
|
289
288
|
const { __jbrowsefmt, ...rest } = attributes;
|
|
290
289
|
const formattedAttributes = { ...rest, ...__jbrowsefmt };
|
|
@@ -299,7 +298,7 @@ function Attributes(props) {
|
|
|
299
298
|
return value.length > 1 && value.every(val => (0, is_object_1.default)(val)) ? (react_1.default.createElement(DataGridDetails, { key: key, name: key, prefix: prefix, value: value })) : (react_1.default.createElement(ArrayValue, { key: key, name: key, value: value, description: description, prefix: prefix }));
|
|
300
299
|
}
|
|
301
300
|
else if ((0, is_object_1.default)(value)) {
|
|
302
|
-
return (0, util_1.isUriLocation)(value) ? (react_1.default.createElement(UriAttribute, { key: key, name: key, prefix: prefix, value: value })) : (react_1.default.createElement(Attributes, { ...props, omit: omits, key: key, attributes: value, descriptions: descriptions, prefix: [...prefix, key] }));
|
|
301
|
+
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, { ...props, omit: omits, key: key, attributes: value, descriptions: descriptions, prefix: [...prefix, key] }));
|
|
303
302
|
}
|
|
304
303
|
else {
|
|
305
304
|
return (react_1.default.createElement(exports.SimpleValue, { key: key, name: key, value: formatter(value, key), description: description, prefix: prefix, width: Math.min(maxLabelWidth, MAX_FIELD_NAME_WIDTH) }));
|
|
@@ -324,22 +323,25 @@ function generateTitle(name, id, type) {
|
|
|
324
323
|
const FeatureDetails = (props) => {
|
|
325
324
|
const { omit = [], model, feature, depth = 0 } = props;
|
|
326
325
|
const { name = '', id = '', type = '', subfeatures } = feature;
|
|
326
|
+
const { pluginManager } = (0, util_1.getEnv)(model);
|
|
327
327
|
const session = (0, util_1.getSession)(model);
|
|
328
|
-
const
|
|
329
|
-
const sequenceTypes = (0, configuration_1.getConf)(session, ['featureDetails', 'sequenceTypes']) || defaultSeqTypes;
|
|
328
|
+
const ExtraPanel = pluginManager === null || pluginManager === void 0 ? void 0 : pluginManager.evaluateExtensionPoint('Core-extraFeaturePanel', null, { session, feature, model });
|
|
330
329
|
return (react_1.default.createElement(BaseCard, { title: generateTitle(name, id, type) },
|
|
331
330
|
react_1.default.createElement(material_1.Typography, null, "Core details"),
|
|
332
331
|
react_1.default.createElement(CoreDetails, { ...props }),
|
|
333
332
|
react_1.default.createElement(material_1.Divider, null),
|
|
334
333
|
react_1.default.createElement(material_1.Typography, null, "Attributes"),
|
|
335
334
|
react_1.default.createElement(Attributes, { attributes: feature, ...props, omit: [...omit, ...coreDetails] }),
|
|
336
|
-
|
|
337
|
-
react_1.default.createElement(SequenceFeatureDetails_1.default, { ...props }))
|
|
338
|
-
|
|
335
|
+
react_1.default.createElement(react_error_boundary_1.ErrorBoundary, { FallbackComponent: ({ error }) => react_1.default.createElement(ui_1.ErrorMessage, { error: error }) },
|
|
336
|
+
react_1.default.createElement(SequenceFeatureDetails_1.default, { ...props })),
|
|
337
|
+
ExtraPanel ? (react_1.default.createElement(react_1.default.Fragment, null,
|
|
338
|
+
react_1.default.createElement(material_1.Divider, null),
|
|
339
|
+
react_1.default.createElement(BaseCard, { title: ExtraPanel.name },
|
|
340
|
+
react_1.default.createElement(ExtraPanel.Component, { ...props })))) : null,
|
|
341
|
+
(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(exports.FeatureDetails, { key: JSON.stringify(sub), feature: sub, model: model, depth: depth + 1 }))))) : null));
|
|
339
342
|
};
|
|
340
343
|
exports.FeatureDetails = FeatureDetails;
|
|
341
|
-
const BaseFeatureDetails = (0, mobx_react_1.observer)((
|
|
342
|
-
const { model } = props;
|
|
344
|
+
const BaseFeatureDetails = (0, mobx_react_1.observer)(({ model }) => {
|
|
343
345
|
const { featureData } = model;
|
|
344
346
|
if (!featureData) {
|
|
345
347
|
return null;
|
|
@@ -349,9 +351,6 @@ const BaseFeatureDetails = (0, mobx_react_1.observer)((props) => {
|
|
|
349
351
|
// config guide. this replacement happens both here and when snapshotting the
|
|
350
352
|
// featureData
|
|
351
353
|
const feature = JSON.parse(JSON.stringify(featureData, (_, v) => typeof v === 'undefined' ? null : v));
|
|
352
|
-
|
|
353
|
-
return null;
|
|
354
|
-
}
|
|
355
|
-
return react_1.default.createElement(exports.FeatureDetails, { model: model, feature: feature });
|
|
354
|
+
return isEmpty(feature) ? null : (react_1.default.createElement(exports.FeatureDetails, { model: model, feature: feature }));
|
|
356
355
|
});
|
|
357
356
|
exports.default = BaseFeatureDetails;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { Feat } from './util';
|
|
3
|
+
export declare function GeneCDS({ cds, sequence }: {
|
|
4
|
+
cds: Feat[];
|
|
5
|
+
sequence: string;
|
|
6
|
+
}): JSX.Element;
|
|
7
|
+
export declare function GeneProtein({ cds, sequence, codonTable, }: {
|
|
8
|
+
cds: Feat[];
|
|
9
|
+
sequence: string;
|
|
10
|
+
codonTable: {
|
|
11
|
+
[key: string]: string;
|
|
12
|
+
};
|
|
13
|
+
}): JSX.Element;
|
|
14
|
+
export declare function GenecDNA({ utr, cds, exons, sequence, upstream, downstream, includeIntrons, collapseIntron, intronBp, }: {
|
|
15
|
+
utr: Feat[];
|
|
16
|
+
cds: Feat[];
|
|
17
|
+
exons: Feat[];
|
|
18
|
+
sequence: string;
|
|
19
|
+
upstream?: string;
|
|
20
|
+
downstream?: string;
|
|
21
|
+
includeIntrons?: boolean;
|
|
22
|
+
collapseIntron?: boolean;
|
|
23
|
+
intronBp: number;
|
|
24
|
+
}): JSX.Element;
|
|
25
|
+
export declare function Genomic({ sequence, upstream, downstream, }: {
|
|
26
|
+
sequence: string;
|
|
27
|
+
upstream?: string;
|
|
28
|
+
downstream?: string;
|
|
29
|
+
}): JSX.Element;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Genomic = exports.GenecDNA = exports.GeneProtein = exports.GeneCDS = void 0;
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
8
|
+
const util_1 = require("./util");
|
|
9
|
+
// note that these are currently put into the style section instead of being
|
|
10
|
+
// defined in classes to aid copy and paste to an external document e.g. word
|
|
11
|
+
const proteinColor = 'rgb(220,160,220)';
|
|
12
|
+
const intronColor = undefined;
|
|
13
|
+
const cdsColor = 'rgb(220,220,180)';
|
|
14
|
+
const updownstreamColor = 'rgba(250,200,200)';
|
|
15
|
+
const utrColor = 'rgb(200,240,240)';
|
|
16
|
+
const genomeColor = 'rgb(200,280,200)';
|
|
17
|
+
function GeneCDS({ cds, sequence }) {
|
|
18
|
+
return react_1.default.createElement("span", { style: { background: cdsColor } }, (0, util_1.stitch)(cds, sequence));
|
|
19
|
+
}
|
|
20
|
+
exports.GeneCDS = GeneCDS;
|
|
21
|
+
function GeneProtein({ cds, sequence, codonTable, }) {
|
|
22
|
+
const str = (0, util_1.stitch)(cds, sequence);
|
|
23
|
+
let protein = '';
|
|
24
|
+
for (let i = 0; i < str.length; i += 3) {
|
|
25
|
+
// use & symbol for undefined codon, or partial slice
|
|
26
|
+
protein += codonTable[str.slice(i, i + 3)] || '&';
|
|
27
|
+
}
|
|
28
|
+
return react_1.default.createElement("span", { style: { background: proteinColor } }, protein);
|
|
29
|
+
}
|
|
30
|
+
exports.GeneProtein = GeneProtein;
|
|
31
|
+
function GenecDNA({ utr, cds, exons, sequence, upstream, downstream, includeIntrons, collapseIntron, intronBp, }) {
|
|
32
|
+
const chunks = cds.length
|
|
33
|
+
? [...cds, ...utr].sort((a, b) => a.start - b.start)
|
|
34
|
+
: exons;
|
|
35
|
+
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
36
|
+
upstream ? (react_1.default.createElement("span", { style: { background: updownstreamColor } }, upstream)) : null,
|
|
37
|
+
chunks
|
|
38
|
+
.filter(f => f.start !== f.end)
|
|
39
|
+
.map((chunk, index) => {
|
|
40
|
+
var _a;
|
|
41
|
+
const intron = sequence.slice(chunk.end, (_a = chunks[index + 1]) === null || _a === void 0 ? void 0 : _a.start);
|
|
42
|
+
return (react_1.default.createElement(react_1.default.Fragment, { key: JSON.stringify(chunk) },
|
|
43
|
+
react_1.default.createElement("span", { style: {
|
|
44
|
+
background: chunk.type === 'CDS' ? cdsColor : utrColor,
|
|
45
|
+
} }, sequence.slice(chunk.start, chunk.end)),
|
|
46
|
+
includeIntrons && index < chunks.length - 1 ? (react_1.default.createElement("span", { style: { background: intronColor } }, collapseIntron && intron.length > intronBp * 2
|
|
47
|
+
? `${intron.slice(0, intronBp)}...${intron.slice(-intronBp)}`
|
|
48
|
+
: intron)) : null));
|
|
49
|
+
}),
|
|
50
|
+
downstream ? (react_1.default.createElement("span", { style: { background: updownstreamColor } }, downstream)) : null));
|
|
51
|
+
}
|
|
52
|
+
exports.GenecDNA = GenecDNA;
|
|
53
|
+
function Genomic({ sequence, upstream, downstream, }) {
|
|
54
|
+
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
55
|
+
upstream ? (react_1.default.createElement("span", { style: { background: updownstreamColor } }, upstream)) : null,
|
|
56
|
+
react_1.default.createElement("span", { style: {
|
|
57
|
+
background: genomeColor,
|
|
58
|
+
} }, sequence),
|
|
59
|
+
downstream ? (react_1.default.createElement("span", { style: { background: updownstreamColor } }, downstream)) : null));
|
|
60
|
+
}
|
|
61
|
+
exports.Genomic = Genomic;
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
/// <reference types="react" />
|
|
2
2
|
import { BaseProps } from './types';
|
|
3
|
-
import { ParentFeat, SeqState } from './util';
|
|
4
|
-
interface SequencePanelProps {
|
|
5
|
-
sequence: SeqState;
|
|
6
|
-
feature: ParentFeat;
|
|
7
|
-
mode: string;
|
|
8
|
-
intronBp?: number;
|
|
9
|
-
}
|
|
10
|
-
export declare const SequencePanel: React.ForwardRefExoticComponent<SequencePanelProps & React.RefAttributes<HTMLDivElement>>;
|
|
11
3
|
export default function SequenceFeatureDetails({ model, feature }: BaseProps): JSX.Element | null;
|
|
12
|
-
export {};
|
|
@@ -26,19 +26,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
26
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
27
|
};
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.SequencePanel = void 0;
|
|
30
29
|
const react_1 = __importStar(require("react"));
|
|
31
30
|
const material_1 = require("@mui/material");
|
|
32
31
|
const mui_1 = require("tss-react/mui");
|
|
33
|
-
const react_intersection_observer_1 = require("react-intersection-observer");
|
|
34
32
|
const copy_to_clipboard_1 = __importDefault(require("copy-to-clipboard"));
|
|
35
33
|
// locals
|
|
36
34
|
const SequenceFeatureSettingsDialog_1 = __importDefault(require("./SequenceFeatureSettingsDialog"));
|
|
35
|
+
const SequenceHelpDialog_1 = __importDefault(require("./SequenceHelpDialog"));
|
|
36
|
+
const SequencePanel_1 = __importDefault(require("./SequencePanel"));
|
|
37
37
|
const util_1 = require("../util");
|
|
38
38
|
const configuration_1 = require("../configuration");
|
|
39
|
-
const util_2 = require("./util");
|
|
40
39
|
// icons
|
|
41
40
|
const Settings_1 = __importDefault(require("@mui/icons-material/Settings"));
|
|
41
|
+
const Help_1 = __importDefault(require("@mui/icons-material/Help"));
|
|
42
42
|
const useStyles = (0, mui_1.makeStyles)()(theme => ({
|
|
43
43
|
button: {
|
|
44
44
|
margin: theme.spacing(1),
|
|
@@ -49,100 +49,11 @@ const useStyles = (0, mui_1.makeStyles)()(theme => ({
|
|
|
49
49
|
container: {
|
|
50
50
|
margin: theme.spacing(1),
|
|
51
51
|
},
|
|
52
|
+
container2: {
|
|
53
|
+
marginTop: theme.spacing(1),
|
|
54
|
+
},
|
|
52
55
|
}));
|
|
53
|
-
|
|
54
|
-
// defined in classes to aid copy and paste to an external document e.g. word
|
|
55
|
-
const proteinColor = 'rgb(220,160,220)';
|
|
56
|
-
const intronColor = undefined;
|
|
57
|
-
const cdsColor = 'rgb(220,220,180)';
|
|
58
|
-
const updownstreamColor = 'rgba(250,200,200)';
|
|
59
|
-
const utrColor = 'rgb(200,240,240)';
|
|
60
|
-
function GeneCDS({ cds, sequence }) {
|
|
61
|
-
return react_1.default.createElement("span", { style: { background: cdsColor } }, (0, util_2.stitch)(cds, sequence));
|
|
62
|
-
}
|
|
63
|
-
function GeneProtein({ cds, sequence, codonTable, }) {
|
|
64
|
-
const str = (0, util_2.stitch)(cds, sequence);
|
|
65
|
-
let protein = '';
|
|
66
|
-
for (let i = 0; i < str.length; i += 3) {
|
|
67
|
-
// use & symbol for undefined codon, or partial slice
|
|
68
|
-
protein += codonTable[str.slice(i, i + 3)] || '&';
|
|
69
|
-
}
|
|
70
|
-
return react_1.default.createElement("span", { style: { background: proteinColor } }, protein);
|
|
71
|
-
}
|
|
72
|
-
function GenecDNA({ utr, cds, exons, sequence, upstream, downstream, includeIntrons, collapseIntron, intronBp, }) {
|
|
73
|
-
const chunks = cds.length
|
|
74
|
-
? [...cds, ...utr].sort((a, b) => a.start - b.start)
|
|
75
|
-
: exons;
|
|
76
|
-
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
77
|
-
upstream ? (react_1.default.createElement("span", { style: { background: updownstreamColor } }, upstream)) : null,
|
|
78
|
-
chunks
|
|
79
|
-
.filter(f => f.start !== f.end)
|
|
80
|
-
.map((chunk, index) => {
|
|
81
|
-
var _a;
|
|
82
|
-
const intron = sequence.slice(chunk.end, (_a = chunks[index + 1]) === null || _a === void 0 ? void 0 : _a.start);
|
|
83
|
-
return (react_1.default.createElement(react_1.default.Fragment, { key: JSON.stringify(chunk) },
|
|
84
|
-
react_1.default.createElement("span", { style: {
|
|
85
|
-
background: chunk.type === 'CDS' ? cdsColor : utrColor,
|
|
86
|
-
} }, sequence.slice(chunk.start, chunk.end)),
|
|
87
|
-
includeIntrons && index < chunks.length - 1 ? (react_1.default.createElement("span", { style: { background: intronColor } }, collapseIntron && intron.length > intronBp * 2
|
|
88
|
-
? `${intron.slice(0, intronBp)}...${intron.slice(-intronBp)}`
|
|
89
|
-
: intron)) : null));
|
|
90
|
-
}),
|
|
91
|
-
downstream ? (react_1.default.createElement("span", { style: { background: updownstreamColor } }, downstream)) : null));
|
|
92
|
-
}
|
|
93
|
-
exports.SequencePanel = react_1.default.forwardRef((props, ref) => {
|
|
94
|
-
const { feature, mode, intronBp = 10 } = props;
|
|
95
|
-
let { sequence: { seq, upstream = '', downstream = '' }, } = props;
|
|
96
|
-
const { subfeatures } = feature;
|
|
97
|
-
if (!subfeatures) {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
const children = subfeatures
|
|
101
|
-
.sort((a, b) => a.start - b.start)
|
|
102
|
-
.map(sub => ({
|
|
103
|
-
...sub,
|
|
104
|
-
start: sub.start - feature.start,
|
|
105
|
-
end: sub.end - feature.start,
|
|
106
|
-
}));
|
|
107
|
-
// we filter duplicate entries in cds and exon lists duplicate entries may be
|
|
108
|
-
// rare but was seen in Gencode v36 track NCList, likely a bug on GFF3 or
|
|
109
|
-
// probably worth ignoring here (produces broken protein translations if
|
|
110
|
-
// included)
|
|
111
|
-
//
|
|
112
|
-
// position 1:224,800,006..225,203,064 gene ENSG00000185842.15 first
|
|
113
|
-
// transcript ENST00000445597.6
|
|
114
|
-
//
|
|
115
|
-
// http://localhost:3000/?config=test_data%2Fconfig.json&session=share-FUl7G1isvF&password=HXh5Y
|
|
116
|
-
let cds = (0, util_2.dedupe)(children.filter(sub => sub.type === 'CDS'));
|
|
117
|
-
let utr = (0, util_2.dedupe)(children.filter(sub => sub.type.match(/utr/i)));
|
|
118
|
-
let exons = (0, util_2.dedupe)(children.filter(sub => sub.type === 'exon'));
|
|
119
|
-
if (!utr.length && cds.length && exons.length) {
|
|
120
|
-
utr = (0, util_2.calculateUTRs)(cds, exons);
|
|
121
|
-
}
|
|
122
|
-
if (feature.strand === -1) {
|
|
123
|
-
// doing this in a single assignment is needed because downstream and
|
|
124
|
-
// upstream are swapped so this avoids a temp variable
|
|
125
|
-
;
|
|
126
|
-
[seq, upstream, downstream] = [
|
|
127
|
-
(0, util_1.revcom)(seq),
|
|
128
|
-
(0, util_1.revcom)(downstream),
|
|
129
|
-
(0, util_1.revcom)(upstream),
|
|
130
|
-
];
|
|
131
|
-
cds = (0, util_2.revlist)(cds, seq.length);
|
|
132
|
-
exons = (0, util_2.revlist)(exons, seq.length);
|
|
133
|
-
utr = (0, util_2.revlist)(utr, seq.length);
|
|
134
|
-
}
|
|
135
|
-
const codonTable = (0, util_1.generateCodonTable)(util_1.defaultCodonTable);
|
|
136
|
-
return (react_1.default.createElement("div", { ref: ref, "data-testid": "sequence_panel" },
|
|
137
|
-
react_1.default.createElement("div", { style: {
|
|
138
|
-
fontFamily: 'monospace',
|
|
139
|
-
wordWrap: 'break-word',
|
|
140
|
-
fontSize: 12,
|
|
141
|
-
maxWidth: 600,
|
|
142
|
-
} },
|
|
143
|
-
`>${feature.name || feature.id || 'unknown'}-${mode}\n`,
|
|
144
|
-
mode === 'cds' ? (react_1.default.createElement(GeneCDS, { cds: cds, sequence: seq })) : mode === 'cdna' ? (react_1.default.createElement(GenecDNA, { exons: exons, cds: cds, utr: utr, sequence: seq, intronBp: intronBp })) : mode === 'protein' ? (react_1.default.createElement(GeneProtein, { cds: cds, codonTable: codonTable, sequence: seq })) : mode === 'gene' ? (react_1.default.createElement(GenecDNA, { exons: exons, cds: cds, utr: utr, sequence: seq, includeIntrons: true, intronBp: intronBp })) : mode === 'gene_collapsed_intron' ? (react_1.default.createElement(GenecDNA, { exons: exons, cds: cds, sequence: seq, utr: utr, includeIntrons: true, collapseIntron: true, intronBp: intronBp })) : mode === 'gene_updownstream' ? (react_1.default.createElement(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(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")))));
|
|
145
|
-
});
|
|
56
|
+
const BPLIMIT = 500000;
|
|
146
57
|
// display the stitched-together sequence of a gene's CDS, cDNA, or protein
|
|
147
58
|
// sequence. this is a best effort and weird genomic phenomena could lead these
|
|
148
59
|
// to not be 100% accurate
|
|
@@ -150,21 +61,29 @@ function SequenceFeatureDetails({ model, feature }) {
|
|
|
150
61
|
var _a;
|
|
151
62
|
const { classes } = useStyles();
|
|
152
63
|
const parentFeature = feature;
|
|
153
|
-
const hasCDS = (_a = parentFeature.subfeatures) === null || _a === void 0 ? void 0 : _a.find(sub => sub.type === 'CDS');
|
|
64
|
+
const hasCDS = !!((_a = parentFeature.subfeatures) === null || _a === void 0 ? void 0 : _a.find(sub => sub.type === 'CDS'));
|
|
65
|
+
const isGene = feature.type === 'gene';
|
|
154
66
|
const seqPanelRef = (0, react_1.useRef)(null);
|
|
155
67
|
const [settingsDlgOpen, setSettingsDlgOpen] = (0, react_1.useState)(false);
|
|
156
|
-
const
|
|
68
|
+
const [shown, setShown] = (0, react_1.useState)(false);
|
|
69
|
+
const [helpShown, setHelpShown] = (0, react_1.useState)(false);
|
|
157
70
|
const [sequence, setSequence] = (0, react_1.useState)();
|
|
158
71
|
const [error, setError] = (0, react_1.useState)();
|
|
159
|
-
const [mode, setMode] = (0, react_1.useState)(hasCDS ? 'cds' : 'cdna');
|
|
160
72
|
const [copied, setCopied] = (0, react_1.useState)(false);
|
|
161
73
|
const [copiedHtml, setCopiedHtml] = (0, react_1.useState)(false);
|
|
162
74
|
const [intronBp, setIntronBp] = (0, util_1.useLocalStorage)('intronBp', 10);
|
|
163
75
|
const [upDownBp, setUpDownBp] = (0, util_1.useLocalStorage)('upDownBp', 500);
|
|
76
|
+
const [forceLoad, setForceLoad] = (0, react_1.useState)({
|
|
77
|
+
id: feature.uniqueId,
|
|
78
|
+
force: false,
|
|
79
|
+
});
|
|
80
|
+
(0, react_1.useEffect)(() => {
|
|
81
|
+
setForceLoad({ id: feature.uniqueId, force: false });
|
|
82
|
+
}, [feature]);
|
|
164
83
|
(0, react_1.useEffect)(() => {
|
|
165
84
|
var _a;
|
|
166
85
|
let finished = false;
|
|
167
|
-
if (!model || !
|
|
86
|
+
if (!model || !shown) {
|
|
168
87
|
return () => { };
|
|
169
88
|
}
|
|
170
89
|
const { assemblyManager, rpcManager } = (0, util_1.getSession)(model);
|
|
@@ -187,76 +106,104 @@ function SequenceFeatureDetails({ model, feature }) {
|
|
|
187
106
|
],
|
|
188
107
|
});
|
|
189
108
|
const [feat] = feats;
|
|
190
|
-
|
|
191
|
-
throw new Error(`sequence not found for feature with refName:${refName}`);
|
|
192
|
-
}
|
|
193
|
-
return feat.get('seq');
|
|
109
|
+
return (feat === null || feat === void 0 ? void 0 : feat.get('seq')) || '';
|
|
194
110
|
}
|
|
195
111
|
;
|
|
196
112
|
(async () => {
|
|
197
113
|
try {
|
|
114
|
+
setError(undefined);
|
|
198
115
|
const { start, end, refName } = feature;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
116
|
+
if (!forceLoad.force && end - start > BPLIMIT) {
|
|
117
|
+
setSequence({
|
|
118
|
+
error: `Genomic sequence larger than ${BPLIMIT}bp, use "force load" button to display`,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
const seq = await fetchSeq(start, end, refName);
|
|
123
|
+
const up = await fetchSeq(Math.max(0, start - upDownBp), start, refName);
|
|
124
|
+
const down = await fetchSeq(end, end + upDownBp, refName);
|
|
125
|
+
if (!finished) {
|
|
126
|
+
setSequence({ seq, upstream: up, downstream: down });
|
|
127
|
+
}
|
|
204
128
|
}
|
|
205
129
|
}
|
|
206
130
|
catch (e) {
|
|
131
|
+
console.error(e);
|
|
207
132
|
setError(e);
|
|
208
133
|
}
|
|
209
134
|
})();
|
|
210
135
|
return () => {
|
|
211
136
|
finished = true;
|
|
212
137
|
};
|
|
213
|
-
}, [feature,
|
|
138
|
+
}, [feature, shown, model, upDownBp, forceLoad]);
|
|
214
139
|
const loading = !sequence;
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
140
|
+
const session = (0, util_1.getSession)(model);
|
|
141
|
+
const defaultSeqTypes = ['mRNA', 'transcript', 'gene'];
|
|
142
|
+
const sequenceTypes = (0, configuration_1.getConf)(session, ['featureDetails', 'sequenceTypes']) || defaultSeqTypes;
|
|
143
|
+
// only attempt fetching gene type sequence on a bare CDS if it has no parent
|
|
144
|
+
const attemptGeneType = feature.type === 'CDS'
|
|
145
|
+
? sequenceTypes.includes('CDS') && !feature.parentId
|
|
146
|
+
: sequenceTypes.includes(feature.type);
|
|
147
|
+
const val = attemptGeneType ? (hasCDS ? 'cds' : 'cdna') : 'genomic';
|
|
148
|
+
// this useEffect is needed to reset the mode/setMode useState because the contents of the select box can completely change depending on whether we click on a gene feature or non-gene feature, so the current value in the select box must change accordingly
|
|
149
|
+
(0, react_1.useEffect)(() => {
|
|
150
|
+
setMode(val);
|
|
151
|
+
}, [attemptGeneType, val]);
|
|
152
|
+
const [mode, setMode] = (0, react_1.useState)(attemptGeneType ? (hasCDS ? 'cds' : 'cdna') : 'genomic');
|
|
153
|
+
const rest = {
|
|
154
|
+
gene: 'Gene w/ introns',
|
|
155
|
+
gene_collapsed_intron: `Gene w/ ${intronBp}bp of intron`,
|
|
156
|
+
gene_updownstream: `Gene w/ ${upDownBp}bp up+down stream`,
|
|
157
|
+
gene_updownstream_collapsed_intron: `Gene w/ ${upDownBp}bp up+down stream w/ ${intronBp}bp intron`,
|
|
158
|
+
cdna: 'cDNA',
|
|
159
|
+
};
|
|
160
|
+
const arg = attemptGeneType
|
|
161
|
+
? hasCDS
|
|
162
|
+
? {
|
|
163
|
+
cds: 'CDS',
|
|
164
|
+
protein: 'Protein',
|
|
165
|
+
...rest,
|
|
166
|
+
}
|
|
167
|
+
: rest
|
|
168
|
+
: {
|
|
169
|
+
genomic: 'Genomic seq',
|
|
170
|
+
genomic_sequence_updown: `Genomic seq w/ ${upDownBp}bp up+down stream`,
|
|
171
|
+
};
|
|
172
|
+
return (isGene && !hasCDS) || !model ? null : (react_1.default.createElement("div", { className: classes.container2 },
|
|
173
|
+
react_1.default.createElement(material_1.Button, { variant: "contained", onClick: () => setShown(!shown) }, shown ? 'Hide feature sequence' : 'Show feature sequence'),
|
|
245
174
|
react_1.default.createElement(material_1.FormControl, { className: classes.formControl },
|
|
246
|
-
react_1.default.createElement(material_1.
|
|
247
|
-
react_1.default.createElement(
|
|
175
|
+
react_1.default.createElement(material_1.IconButton, { onClick: () => setHelpShown(true) },
|
|
176
|
+
react_1.default.createElement(Help_1.default, null))),
|
|
177
|
+
react_1.default.createElement("br", null),
|
|
178
|
+
shown ? (react_1.default.createElement("div", { className: classes.container2 },
|
|
179
|
+
react_1.default.createElement(material_1.FormControl, { className: classes.formControl },
|
|
180
|
+
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))))),
|
|
181
|
+
react_1.default.createElement(material_1.FormControl, { className: classes.formControl },
|
|
182
|
+
react_1.default.createElement(material_1.Button, { className: classes.button, variant: "contained", color: "inherit", onClick: () => {
|
|
248
183
|
const ref = seqPanelRef.current;
|
|
249
184
|
if (ref) {
|
|
250
|
-
(0, copy_to_clipboard_1.default)(ref.
|
|
251
|
-
|
|
252
|
-
setTimeout(() =>
|
|
185
|
+
(0, copy_to_clipboard_1.default)(ref.textContent || '', { format: 'text/plain' });
|
|
186
|
+
setCopied(true);
|
|
187
|
+
setTimeout(() => setCopied(false), 1000);
|
|
253
188
|
}
|
|
254
|
-
} },
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
189
|
+
} }, copied ? 'Copied to clipboard!' : 'Copy plaintext')),
|
|
190
|
+
react_1.default.createElement(material_1.FormControl, { className: classes.formControl },
|
|
191
|
+
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" },
|
|
192
|
+
react_1.default.createElement(material_1.Button, { className: classes.button, variant: "contained", color: "inherit", onClick: () => {
|
|
193
|
+
const ref = seqPanelRef.current;
|
|
194
|
+
if (ref) {
|
|
195
|
+
(0, copy_to_clipboard_1.default)(ref.innerHTML, { format: 'text/html' });
|
|
196
|
+
setCopiedHtml(true);
|
|
197
|
+
setTimeout(() => setCopiedHtml(false), 1000);
|
|
198
|
+
}
|
|
199
|
+
} }, copiedHtml ? 'Copied to clipboard!' : 'Copy HTML'))),
|
|
200
|
+
react_1.default.createElement(material_1.FormControl, { className: classes.formControl },
|
|
201
|
+
react_1.default.createElement(material_1.IconButton, { onClick: () => setSettingsDlgOpen(true) },
|
|
202
|
+
react_1.default.createElement(Settings_1.default, null))),
|
|
203
|
+
react_1.default.createElement("br", null),
|
|
204
|
+
react_1.default.createElement(react_1.default.Fragment, null, error ? (react_1.default.createElement(material_1.Typography, { color: "error" }, `${error}`)) : loading ? (react_1.default.createElement(material_1.Typography, null, "Loading gene sequence...")) : sequence ? ('error' in sequence ? (react_1.default.createElement(react_1.default.Fragment, null,
|
|
205
|
+
react_1.default.createElement(material_1.Typography, { color: "error" }, sequence.error),
|
|
206
|
+
react_1.default.createElement(material_1.Button, { variant: "contained", color: "inherit", onClick: () => setForceLoad({ ...forceLoad, force: true }) }, "Force load"))) : (react_1.default.createElement(SequencePanel_1.default, { ref: seqPanelRef, feature: parentFeature, mode: mode, sequence: sequence, intronBp: intronBp }))) : (react_1.default.createElement(material_1.Typography, null, "No sequence found"))))) : null,
|
|
260
207
|
settingsDlgOpen ? (react_1.default.createElement(SequenceFeatureSettingsDialog_1.default, { handleClose: arg => {
|
|
261
208
|
if (arg) {
|
|
262
209
|
const { upDownBp, intronBp } = arg;
|
|
@@ -264,6 +211,7 @@ function SequenceFeatureDetails({ model, feature }) {
|
|
|
264
211
|
setUpDownBp(upDownBp);
|
|
265
212
|
}
|
|
266
213
|
setSettingsDlgOpen(false);
|
|
267
|
-
}, upDownBp: upDownBp, intronBp: intronBp })) : null
|
|
214
|
+
}, upDownBp: upDownBp, intronBp: intronBp })) : null,
|
|
215
|
+
helpShown ? react_1.default.createElement(SequenceHelpDialog_1.default, { handleClose: () => setHelpShown(false) }) : null));
|
|
268
216
|
}
|
|
269
217
|
exports.default = SequenceFeatureDetails;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const react_1 = __importDefault(require("react"));
|
|
7
|
+
const material_1 = require("@mui/material");
|
|
8
|
+
const mui_1 = require("tss-react/mui");
|
|
9
|
+
// icons
|
|
10
|
+
const Close_1 = __importDefault(require("@mui/icons-material/Close"));
|
|
11
|
+
const Settings_1 = __importDefault(require("@mui/icons-material/Settings"));
|
|
12
|
+
const useStyles = (0, mui_1.makeStyles)()(theme => ({
|
|
13
|
+
closeButton: {
|
|
14
|
+
position: 'absolute',
|
|
15
|
+
right: theme.spacing(1),
|
|
16
|
+
top: theme.spacing(1),
|
|
17
|
+
color: theme.palette.grey[500],
|
|
18
|
+
},
|
|
19
|
+
dialogContent: {},
|
|
20
|
+
}));
|
|
21
|
+
function HelpDialog({ handleClose, }) {
|
|
22
|
+
const { classes } = useStyles();
|
|
23
|
+
return (react_1.default.createElement(material_1.Dialog, { maxWidth: "xl", open: true, onClose: () => handleClose() },
|
|
24
|
+
react_1.default.createElement(material_1.DialogTitle, null,
|
|
25
|
+
"Feature sequence panel",
|
|
26
|
+
handleClose ? (react_1.default.createElement(material_1.IconButton, { className: classes.closeButton, onClick: () => handleClose() },
|
|
27
|
+
react_1.default.createElement(Close_1.default, null))) : null),
|
|
28
|
+
react_1.default.createElement(material_1.Divider, null),
|
|
29
|
+
react_1.default.createElement(material_1.DialogContent, { className: classes.dialogContent },
|
|
30
|
+
react_1.default.createElement(material_1.Typography, { paragraph: true }, "The \"Feature sequence\" panel shows the underlying genomic sequence for a given feature, fetched from the reference genome."),
|
|
31
|
+
react_1.default.createElement(material_1.Typography, null, "For gene features, this panel does special calculations to e.g. stitch together the coding sequence, the options are:"),
|
|
32
|
+
react_1.default.createElement("ul", null,
|
|
33
|
+
react_1.default.createElement("li", null, "CDS - shows the stitched together CDS sequences"),
|
|
34
|
+
react_1.default.createElement("li", null, "Protein - the translated coding sequence, with the \"standard\" genetic code"),
|
|
35
|
+
react_1.default.createElement("li", null, "cDNA - shows the UTRs and stitched together CDS sequences"),
|
|
36
|
+
react_1.default.createElement("li", null, "Gene w/ introns - the sequence underlying the entire gene including including introns, with UTR and CDS highlighted"),
|
|
37
|
+
react_1.default.createElement("li", null, "Gene w/ Nbp introns - same \"Gene w/ introns\", but limiting to a subset of the intron sequence displayed"),
|
|
38
|
+
react_1.default.createElement("li", null, "Gene +/- Nbp up+down stream - same as \"Gene w/ introns\" but with up and downstream sequence displayed"),
|
|
39
|
+
react_1.default.createElement("li", null, "Gene +/- Nbp up+down stream, Nbp introns - same as \"Gene w/ introns\", but with limited intron sequence displayed and up and downstream sequence")),
|
|
40
|
+
react_1.default.createElement(material_1.Typography, { paragraph: true }, "For other feature types, the options are:"),
|
|
41
|
+
react_1.default.createElement("ul", null,
|
|
42
|
+
react_1.default.createElement("li", null, "Feature sequence - the reference genome sequence underlying the feature"),
|
|
43
|
+
react_1.default.createElement("li", null, "Feature sequence +/- Nbp up+down stream - the reference genome sequence underlying the feature, with the up and downstream sequence")),
|
|
44
|
+
react_1.default.createElement(material_1.Typography, null,
|
|
45
|
+
"Note: you can use the \"gear icon\" ",
|
|
46
|
+
react_1.default.createElement(Settings_1.default, null),
|
|
47
|
+
" to edit the number of bp displayed up/downstream and in the intron region")),
|
|
48
|
+
react_1.default.createElement(material_1.DialogActions, null,
|
|
49
|
+
react_1.default.createElement(material_1.Button, { onClick: () => handleClose(), autoFocus: true, variant: "contained" }, "Close"))));
|
|
50
|
+
}
|
|
51
|
+
exports.default = HelpDialog;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ParentFeat, SeqState } from './util';
|
|
3
|
+
interface SequencePanelProps {
|
|
4
|
+
sequence: SeqState;
|
|
5
|
+
feature: ParentFeat;
|
|
6
|
+
mode: string;
|
|
7
|
+
intronBp?: number;
|
|
8
|
+
}
|
|
9
|
+
declare const SequencePanel: React.ForwardRefExoticComponent<SequencePanelProps & React.RefAttributes<HTMLDivElement>>;
|
|
10
|
+
export default SequencePanel;
|