@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.
Files changed (46) hide show
  1. package/BaseFeatureWidget/BaseFeatureDetail.d.ts +2 -1
  2. package/BaseFeatureWidget/BaseFeatureDetail.js +16 -17
  3. package/BaseFeatureWidget/SequenceBox.d.ts +29 -0
  4. package/BaseFeatureWidget/SequenceBox.js +61 -0
  5. package/BaseFeatureWidget/SequenceFeatureDetails.d.ts +1 -10
  6. package/BaseFeatureWidget/SequenceFeatureDetails.js +100 -152
  7. package/BaseFeatureWidget/SequenceHelpDialog.d.ts +4 -0
  8. package/BaseFeatureWidget/SequenceHelpDialog.js +51 -0
  9. package/BaseFeatureWidget/SequencePanel.d.ts +10 -0
  10. package/BaseFeatureWidget/SequencePanel.js +72 -0
  11. package/BaseFeatureWidget/index.d.ts +7 -0
  12. package/BaseFeatureWidget/index.js +21 -5
  13. package/BaseFeatureWidget/util.d.ts +11 -0
  14. package/BaseFeatureWidget/util.js +13 -1
  15. package/PluginLoader.d.ts +29 -13
  16. package/PluginLoader.js +47 -77
  17. package/PluginManager.d.ts +1 -0
  18. package/PluginManager.js +9 -3
  19. package/assemblyManager/assembly.d.ts +8 -15
  20. package/assemblyManager/assembly.js +14 -17
  21. package/assemblyManager/assemblyManager.d.ts +37 -86
  22. package/configuration/configurationSlot.js +2 -1
  23. package/data_adapters/CytobandAdapter.js +1 -1
  24. package/package.json +3 -3
  25. package/pluggableElementTypes/models/BaseDisplayModel.d.ts +1 -1
  26. package/pluggableElementTypes/models/BaseDisplayModel.js +3 -2
  27. package/pluggableElementTypes/models/BaseTrackModel.d.ts +1 -1
  28. package/pluggableElementTypes/models/BaseTrackModel.js +1 -1
  29. package/pluggableElementTypes/models/baseTrackConfig.js +16 -4
  30. package/rpc/RpcManager.d.ts +1 -1
  31. package/rpc/RpcManager.js +4 -1
  32. package/rpc/WebWorkerRpcDriver.d.ts +2 -0
  33. package/rpc/WebWorkerRpcDriver.js +21 -11
  34. package/rpc/configSchema.js +4 -1
  35. package/tsconfig.build.tsbuildinfo +1 -1
  36. package/ui/AboutDialog.d.ts +6 -0
  37. package/ui/AboutDialog.js +42 -20
  38. package/ui/App.js +2 -2
  39. package/ui/DrawerWidget.js +9 -7
  40. package/ui/FatalErrorDialog.d.ts +6 -22
  41. package/ui/FatalErrorDialog.js +10 -29
  42. package/util/index.d.ts +4 -0
  43. package/util/index.js +5 -1
  44. package/util/stats.js +7 -5
  45. package/util/tracks.d.ts +5 -2
  46. 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: (props: BaseInputProps) => JSX.Element | null;
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 SanitizedHTML_1 = __importDefault(require("../ui/SanitizedHTML"));
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(SanitizedHTML_1.default, { html: `<a href="${value}">${value}</a>` })) : (react_1.default.createElement(SanitizedHTML_1.default, { html: (0, is_object_1.default)(value) ? JSON.stringify(value) : String(value) }))));
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(SanitizedHTML_1.default, { html: `<a href="${href}">${href}</a>` });
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 defaultSeqTypes = ['mRNA', 'transcript', 'gene'];
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
- sequenceTypes.includes(feature.type) ? (react_1.default.createElement(react_error_boundary_1.ErrorBoundary, { FallbackComponent: ({ error }) => (react_1.default.createElement(material_1.Typography, { color: "error" }, `${error}`)) },
337
- react_1.default.createElement(SequenceFeatureDetails_1.default, { ...props }))) : null,
338
- (subfeatures === null || subfeatures === void 0 ? void 0 : subfeatures.length) ? (react_1.default.createElement(BaseCard, { title: "Subfeatures", defaultExpanded: !sequenceTypes.includes(feature.type) }, subfeatures.map(sub => (react_1.default.createElement(exports.FeatureDetails, { key: JSON.stringify(sub), feature: sub, model: model, depth: depth + 1 }))))) : null));
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)((props) => {
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
- if (isEmpty(feature)) {
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
- import React from 'react';
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
- // note that these are currently put into the style section instead of being
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 { ref, inView } = (0, react_intersection_observer_1.useInView)();
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 || !inView) {
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
- if (!feat) {
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
- const seq = await fetchSeq(start, end, refName);
200
- const up = await fetchSeq(Math.max(0, start - upDownBp), start, refName);
201
- const down = await fetchSeq(end, end + upDownBp, refName);
202
- if (!finished) {
203
- setSequence({ seq, upstream: up, downstream: down });
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, inView, model, upDownBp]);
138
+ }, [feature, shown, model, upDownBp, forceLoad]);
214
139
  const loading = !sequence;
215
- return !model ? null : (react_1.default.createElement("div", { ref: ref, className: classes.container },
216
- react_1.default.createElement(material_1.FormControl, { className: classes.formControl },
217
- react_1.default.createElement(material_1.Select, { style: { margin: 0 }, value: mode, onChange: event => setMode(event.target.value) },
218
- hasCDS ? react_1.default.createElement(material_1.MenuItem, { value: "cds" }, "CDS") : null,
219
- hasCDS ? react_1.default.createElement(material_1.MenuItem, { value: "protein" }, "Protein") : null,
220
- react_1.default.createElement(material_1.MenuItem, { value: "gene" }, "Gene w/ introns"),
221
- react_1.default.createElement(material_1.MenuItem, { value: "gene_collapsed_intron" },
222
- "Gene w/ ",
223
- intronBp,
224
- "bp of intron"),
225
- react_1.default.createElement(material_1.MenuItem, { value: "gene_updownstream" },
226
- "Gene w/ ",
227
- upDownBp,
228
- "bp up+down stream"),
229
- react_1.default.createElement(material_1.MenuItem, { value: "gene_updownstream_collapsed_intron" },
230
- "Gene w/ ",
231
- upDownBp,
232
- "bp up+down stream w/ ",
233
- intronBp,
234
- "bp intron"),
235
- react_1.default.createElement(material_1.MenuItem, { value: "cdna" }, "cDNA"))),
236
- react_1.default.createElement(material_1.FormControl, { className: classes.formControl },
237
- react_1.default.createElement(material_1.Button, { className: classes.button, type: "button", variant: "contained", onClick: () => {
238
- const ref = seqPanelRef.current;
239
- if (ref) {
240
- (0, copy_to_clipboard_1.default)(ref.textContent || '', { format: 'text/plain' });
241
- setCopied(true);
242
- setTimeout(() => setCopied(false), 1000);
243
- }
244
- } }, copied ? 'Copied to clipboard!' : 'Copy as plaintext')),
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.Tooltip, { title: "Note that 'Copy as HTML' can retain the colors but cannot be pasted into some programs like notepad that only expect plain text" },
247
- react_1.default.createElement(material_1.Button, { className: classes.button, type: "button", variant: "contained", onClick: () => {
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.innerHTML, { format: 'text/html' });
251
- setCopiedHtml(true);
252
- setTimeout(() => setCopiedHtml(false), 1000);
185
+ (0, copy_to_clipboard_1.default)(ref.textContent || '', { format: 'text/plain' });
186
+ setCopied(true);
187
+ setTimeout(() => setCopied(false), 1000);
253
188
  }
254
- } }, copiedHtml ? 'Copied to clipboard!' : 'Copy as HTML'))),
255
- react_1.default.createElement(material_1.FormControl, { className: classes.formControl },
256
- react_1.default.createElement(material_1.IconButton, { onClick: () => setSettingsDlgOpen(true), style: { margin: 0, padding: 0 } },
257
- react_1.default.createElement(Settings_1.default, null))),
258
- react_1.default.createElement("br", null),
259
- 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 ? (react_1.default.createElement(exports.SequencePanel, { ref: seqPanelRef, feature: parentFeature, mode: mode, sequence: sequence, intronBp: intronBp })) : (react_1.default.createElement(material_1.Typography, null, "No sequence found"))),
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,4 @@
1
+ /// <reference types="react" />
2
+ export default function HelpDialog({ handleClose, }: {
3
+ handleClose: () => void;
4
+ }): JSX.Element;
@@ -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;