@jbrowse/core 2.1.4 → 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 (65) hide show
  1. package/BaseFeatureWidget/BaseFeatureDetail.d.ts +4 -3
  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 +2 -8
  6. package/BaseFeatureWidget/SequenceFeatureDetails.js +123 -135
  7. package/BaseFeatureWidget/SequenceFeatureSettingsDialog.d.ts +9 -0
  8. package/BaseFeatureWidget/SequenceFeatureSettingsDialog.js +71 -0
  9. package/BaseFeatureWidget/SequenceHelpDialog.d.ts +4 -0
  10. package/BaseFeatureWidget/SequenceHelpDialog.js +51 -0
  11. package/BaseFeatureWidget/SequencePanel.d.ts +10 -0
  12. package/BaseFeatureWidget/SequencePanel.js +72 -0
  13. package/BaseFeatureWidget/index.d.ts +7 -0
  14. package/BaseFeatureWidget/index.js +21 -5
  15. package/BaseFeatureWidget/test_data/DLGAP3.d.ts +80 -0
  16. package/BaseFeatureWidget/test_data/DLGAP3.js +585 -0
  17. package/BaseFeatureWidget/test_data/NCDN.d.ts +141 -0
  18. package/BaseFeatureWidget/test_data/NCDN.js +1118 -0
  19. package/BaseFeatureWidget/types.d.ts +2 -2
  20. package/BaseFeatureWidget/util.d.ts +13 -2
  21. package/BaseFeatureWidget/util.js +13 -1
  22. package/PluginLoader.d.ts +29 -13
  23. package/PluginLoader.js +47 -77
  24. package/PluginManager.d.ts +4 -3
  25. package/PluginManager.js +16 -8
  26. package/assemblyManager/assembly.d.ts +8 -15
  27. package/assemblyManager/assembly.js +14 -17
  28. package/assemblyManager/assemblyManager.d.ts +37 -86
  29. package/assemblyManager/assemblyManager.js +2 -1
  30. package/configuration/configurationSlot.js +2 -1
  31. package/data_adapters/CytobandAdapter.js +1 -1
  32. package/package.json +3 -3
  33. package/pluggableElementTypes/ViewType.d.ts +2 -0
  34. package/pluggableElementTypes/ViewType.js +1 -0
  35. package/pluggableElementTypes/models/BaseConnectionModelFactory.d.ts +7 -5
  36. package/pluggableElementTypes/models/BaseConnectionModelFactory.js +3 -2
  37. package/pluggableElementTypes/models/BaseDisplayModel.d.ts +1 -1
  38. package/pluggableElementTypes/models/BaseDisplayModel.js +3 -2
  39. package/pluggableElementTypes/models/BaseTrackModel.d.ts +1 -1
  40. package/pluggableElementTypes/models/BaseTrackModel.js +1 -1
  41. package/pluggableElementTypes/models/baseTrackConfig.js +16 -4
  42. package/rpc/RpcManager.d.ts +1 -1
  43. package/rpc/RpcManager.js +4 -1
  44. package/rpc/WebWorkerRpcDriver.d.ts +2 -0
  45. package/rpc/WebWorkerRpcDriver.js +21 -11
  46. package/rpc/configSchema.js +4 -1
  47. package/tsconfig.build.tsbuildinfo +1 -1
  48. package/ui/AboutDialog.d.ts +6 -0
  49. package/ui/AboutDialog.js +42 -20
  50. package/ui/App.d.ts +1 -1
  51. package/ui/App.js +49 -42
  52. package/ui/DrawerWidget.js +11 -6
  53. package/ui/FatalErrorDialog.d.ts +6 -22
  54. package/ui/FatalErrorDialog.js +10 -29
  55. package/ui/theme.d.ts +18 -171
  56. package/ui/theme.js +17 -20
  57. package/util/calculateStaticBlocks.js +3 -8
  58. package/util/index.d.ts +5 -1
  59. package/util/index.js +34 -11
  60. package/util/simpleFeature.d.ts +1 -2
  61. package/util/stats.js +7 -5
  62. package/util/tracks.d.ts +5 -2
  63. package/util/tracks.js +15 -5
  64. package/util/types/index.d.ts +1 -1
  65. package/util/types/index.js +3 -1
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { IAnyStateTreeNode } from 'mobx-state-tree';
3
3
  import { BaseCardProps, BaseProps } from './types';
4
- import { SimpleFeatureSerialized } from '../util/simpleFeature';
4
+ import { SimpleFeatureSerializedNoId } from '../util/simpleFeature';
5
5
  export declare const useStyles: (params: void, styleOverrides?: {
6
6
  props: {
7
7
  classes?: Record<string, string> | undefined;
@@ -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: {
@@ -54,10 +55,10 @@ export interface BaseInputProps extends BaseCardProps {
54
55
  }
55
56
  export declare const FeatureDetails: (props: {
56
57
  model: IAnyStateTreeNode;
57
- feature: SimpleFeatureSerialized;
58
+ feature: SimpleFeatureSerializedNoId;
58
59
  depth?: number | undefined;
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'];
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,9 +1,3 @@
1
- import React from 'react';
1
+ /// <reference types="react" />
2
2
  import { BaseProps } from './types';
3
- import { ParentFeat, SeqState } from './util';
4
- export declare const SequencePanel: React.ForwardRefExoticComponent<{
5
- sequence: SeqState;
6
- feature: ParentFeat;
7
- mode: string;
8
- } & React.RefAttributes<HTMLDivElement>>;
9
- export default function SequenceFeatureDetails({ model, feature }: BaseProps): JSX.Element;
3
+ export default function SequenceFeatureDetails({ model, feature }: BaseProps): JSX.Element | null;
@@ -26,111 +26,34 @@ 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"));
33
+ // locals
34
+ const SequenceFeatureSettingsDialog_1 = __importDefault(require("./SequenceFeatureSettingsDialog"));
35
+ const SequenceHelpDialog_1 = __importDefault(require("./SequenceHelpDialog"));
36
+ const SequencePanel_1 = __importDefault(require("./SequencePanel"));
35
37
  const util_1 = require("../util");
36
38
  const configuration_1 = require("../configuration");
37
- const util_2 = require("./util");
39
+ // icons
40
+ const Settings_1 = __importDefault(require("@mui/icons-material/Settings"));
41
+ const Help_1 = __importDefault(require("@mui/icons-material/Help"));
38
42
  const useStyles = (0, mui_1.makeStyles)()(theme => ({
39
43
  button: {
40
44
  margin: theme.spacing(1),
41
45
  },
46
+ formControl: {
47
+ margin: 0,
48
+ },
49
+ container: {
50
+ margin: theme.spacing(1),
51
+ },
52
+ container2: {
53
+ marginTop: theme.spacing(1),
54
+ },
42
55
  }));
43
- // note that these are currently put into the style section instead of being
44
- // defined in classes to aid copy and paste to an external document e.g. word
45
- const proteinColor = 'rgb(220,160,220)';
46
- const intronColor = undefined;
47
- const cdsColor = 'rgb(220,220,180)';
48
- const updownstreamColor = 'rgba(250,200,200)';
49
- const utrColor = 'rgb(200,240,240)';
50
- function GeneCDS({ cds, sequence }) {
51
- return react_1.default.createElement("span", { style: { background: cdsColor } }, (0, util_2.stitch)(cds, sequence));
52
- }
53
- function GeneProtein({ cds, sequence, codonTable, }) {
54
- const str = (0, util_2.stitch)(cds, sequence);
55
- let protein = '';
56
- for (let i = 0; i < str.length; i += 3) {
57
- // use & symbol for undefined codon, or partial slice
58
- protein += codonTable[str.slice(i, i + 3)] || '&';
59
- }
60
- return react_1.default.createElement("span", { style: { background: proteinColor } }, protein);
61
- }
62
- function GenecDNA({ utr, cds, exons, sequence, upstream, downstream, includeIntrons, collapseIntron, }) {
63
- const chunks = cds.length
64
- ? [...cds, ...utr].sort((a, b) => a.start - b.start)
65
- : exons;
66
- return (react_1.default.createElement(react_1.default.Fragment, null,
67
- upstream ? (react_1.default.createElement("span", { style: { background: updownstreamColor } }, upstream)) : null,
68
- chunks
69
- .filter(f => f.start !== f.end)
70
- .map((chunk, index) => {
71
- var _a;
72
- const intron = sequence.slice(chunk.end, (_a = chunks[index + 1]) === null || _a === void 0 ? void 0 : _a.start);
73
- return (react_1.default.createElement(react_1.default.Fragment, { key: JSON.stringify(chunk) },
74
- react_1.default.createElement("span", { style: {
75
- background: chunk.type === 'CDS' ? cdsColor : utrColor,
76
- } }, sequence.slice(chunk.start, chunk.end)),
77
- includeIntrons && index < chunks.length - 1 ? (react_1.default.createElement("span", { style: { background: intronColor } }, collapseIntron && intron.length > 20
78
- ? `${intron.slice(0, 10)}...${intron.slice(-10)}`
79
- : intron)) : null));
80
- }),
81
- downstream ? (react_1.default.createElement("span", { style: { background: updownstreamColor } }, downstream)) : null));
82
- }
83
- exports.SequencePanel = react_1.default.forwardRef(({ feature, mode, sequence: { seq: sequence, upstream = '', downstream = '' }, }, ref) => {
84
- const { subfeatures } = feature;
85
- const codonTable = (0, util_1.generateCodonTable)(util_1.defaultCodonTable);
86
- if (!subfeatures) {
87
- return null;
88
- }
89
- const children = subfeatures
90
- .sort((a, b) => a.start - b.start)
91
- .map(sub => ({
92
- ...sub,
93
- start: sub.start - feature.start,
94
- end: sub.end - feature.start,
95
- }));
96
- // we filter duplicate entries in cds and exon lists duplicate entries may be
97
- // rare but was seen in Gencode v36 track NCList, likely a bug on GFF3 or
98
- // probably worth ignoring here (produces broken protein translations if
99
- // included)
100
- //
101
- // position 1:224,800,006..225,203,064 gene ENSG00000185842.15 first
102
- // transcript ENST00000445597.6
103
- //
104
- // http://localhost:3000/?config=test_data%2Fconfig.json&session=share-FUl7G1isvF&password=HXh5Y
105
- let cds = (0, util_2.dedupe)(children.filter(sub => sub.type === 'CDS'));
106
- let utr = (0, util_2.dedupe)(children.filter(sub => sub.type.match(/utr/i)));
107
- let exons = (0, util_2.dedupe)(children.filter(sub => sub.type === 'exon'));
108
- if (!utr.length && cds.length && exons.length) {
109
- utr = (0, util_2.calculateUTRs)(cds, exons);
110
- }
111
- if (feature.strand === -1) {
112
- // doing this in a single assignment is needed because downstream and
113
- // upstream are swapped so this avoids a temp variable
114
- ;
115
- [sequence, upstream, downstream] = [
116
- (0, util_1.revcom)(sequence),
117
- (0, util_1.revcom)(downstream),
118
- (0, util_1.revcom)(upstream),
119
- ];
120
- cds = (0, util_2.revlist)(cds, sequence.length);
121
- exons = (0, util_2.revlist)(exons, sequence.length);
122
- utr = (0, util_2.revlist)(utr, sequence.length);
123
- }
124
- return (react_1.default.createElement("div", { ref: ref, "data-testid": "sequence_panel" },
125
- react_1.default.createElement("div", { style: {
126
- fontFamily: 'monospace',
127
- wordWrap: 'break-word',
128
- fontSize: 12,
129
- maxWidth: 600,
130
- } },
131
- `>${feature.name || feature.id || 'unknown'}-${mode}\n`,
132
- mode === 'cds' ? (react_1.default.createElement(GeneCDS, { cds: cds, sequence: sequence })) : mode === 'cdna' ? (react_1.default.createElement(GenecDNA, { exons: exons, cds: cds, utr: utr, sequence: sequence })) : mode === 'protein' ? (react_1.default.createElement(GeneProtein, { cds: cds, codonTable: codonTable, sequence: sequence })) : mode === 'gene' ? (react_1.default.createElement(GenecDNA, { exons: exons, cds: cds, utr: utr, sequence: sequence, includeIntrons: true })) : mode === 'gene_collapsed_intron' ? (react_1.default.createElement(GenecDNA, { exons: exons, cds: cds, sequence: sequence, utr: utr, includeIntrons: true, collapseIntron: true })) : mode === 'gene_updownstream' ? (react_1.default.createElement(GenecDNA, { exons: exons, cds: cds, sequence: sequence, utr: utr, upstream: upstream, downstream: downstream, includeIntrons: true })) : mode === 'gene_updownstream_collapsed_intron' ? (react_1.default.createElement(GenecDNA, { exons: exons, cds: cds, sequence: sequence, utr: utr, upstream: upstream, downstream: downstream, includeIntrons: true, collapseIntron: true })) : (react_1.default.createElement("div", null, "Unknown type")))));
133
- });
56
+ const BPLIMIT = 500000;
134
57
  // display the stitched-together sequence of a gene's CDS, cDNA, or protein
135
58
  // sequence. this is a best effort and weird genomic phenomena could lead these
136
59
  // to not be 100% accurate
@@ -138,18 +61,29 @@ function SequenceFeatureDetails({ model, feature }) {
138
61
  var _a;
139
62
  const { classes } = useStyles();
140
63
  const parentFeature = feature;
141
- 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';
142
66
  const seqPanelRef = (0, react_1.useRef)(null);
143
- const { ref, inView } = (0, react_intersection_observer_1.useInView)();
67
+ const [settingsDlgOpen, setSettingsDlgOpen] = (0, react_1.useState)(false);
68
+ const [shown, setShown] = (0, react_1.useState)(false);
69
+ const [helpShown, setHelpShown] = (0, react_1.useState)(false);
144
70
  const [sequence, setSequence] = (0, react_1.useState)();
145
71
  const [error, setError] = (0, react_1.useState)();
146
- const [mode, setMode] = (0, react_1.useState)(hasCDS ? 'cds' : 'cdna');
147
72
  const [copied, setCopied] = (0, react_1.useState)(false);
148
73
  const [copiedHtml, setCopiedHtml] = (0, react_1.useState)(false);
74
+ const [intronBp, setIntronBp] = (0, util_1.useLocalStorage)('intronBp', 10);
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]);
149
83
  (0, react_1.useEffect)(() => {
150
84
  var _a;
151
85
  let finished = false;
152
- if (!model || !inView) {
86
+ if (!model || !shown) {
153
87
  return () => { };
154
88
  }
155
89
  const { assemblyManager, rpcManager } = (0, util_1.getSession)(model);
@@ -172,58 +106,112 @@ function SequenceFeatureDetails({ model, feature }) {
172
106
  ],
173
107
  });
174
108
  const [feat] = feats;
175
- if (!feat) {
176
- throw new Error(`sequence not found for feature with refName:${refName}`);
177
- }
178
- return feat.get('seq');
109
+ return (feat === null || feat === void 0 ? void 0 : feat.get('seq')) || '';
179
110
  }
180
111
  ;
181
112
  (async () => {
182
113
  try {
114
+ setError(undefined);
183
115
  const { start, end, refName } = feature;
184
- const seq = await fetchSeq(start, end, refName);
185
- const up = await fetchSeq(Math.max(0, start - 500), start, refName);
186
- const down = await fetchSeq(end, end + 500, refName);
187
- if (!finished) {
188
- 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
+ }
189
128
  }
190
129
  }
191
130
  catch (e) {
131
+ console.error(e);
192
132
  setError(e);
193
133
  }
194
134
  })();
195
135
  return () => {
196
136
  finished = true;
197
137
  };
198
- }, [feature, inView, model]);
138
+ }, [feature, shown, model, upDownBp, forceLoad]);
199
139
  const loading = !sequence;
200
- return (react_1.default.createElement("div", { ref: ref },
201
- react_1.default.createElement(material_1.FormControl, null,
202
- react_1.default.createElement(material_1.Select, { value: mode, onChange: event => setMode(event.target.value) },
203
- hasCDS ? react_1.default.createElement(material_1.MenuItem, { value: "cds" }, "CDS") : null,
204
- hasCDS ? react_1.default.createElement(material_1.MenuItem, { value: "protein" }, "Protein") : null,
205
- react_1.default.createElement(material_1.MenuItem, { value: "gene" }, "Gene w/ introns"),
206
- react_1.default.createElement(material_1.MenuItem, { value: "gene_collapsed_intron" }, "Gene w/ 10bp of intron"),
207
- react_1.default.createElement(material_1.MenuItem, { value: "gene_updownstream" }, "Gene w/ 500bp up+down stream"),
208
- react_1.default.createElement(material_1.MenuItem, { value: "gene_updownstream_collapsed_intron" }, "Gene w/ 500bp up+down stream w/ 10bp intron"),
209
- react_1.default.createElement(material_1.MenuItem, { value: "cdna" }, "cDNA"))),
210
- react_1.default.createElement(material_1.Button, { className: classes.button, type: "button", variant: "contained", onClick: () => {
211
- const ref = seqPanelRef.current;
212
- if (ref) {
213
- (0, copy_to_clipboard_1.default)(ref.textContent || '', { format: 'text/plain' });
214
- setCopied(true);
215
- setTimeout(() => setCopied(false), 1000);
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'),
174
+ react_1.default.createElement(material_1.FormControl, { className: classes.formControl },
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: () => {
183
+ const ref = seqPanelRef.current;
184
+ if (ref) {
185
+ (0, copy_to_clipboard_1.default)(ref.textContent || '', { format: 'text/plain' });
186
+ setCopied(true);
187
+ setTimeout(() => setCopied(false), 1000);
188
+ }
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,
207
+ settingsDlgOpen ? (react_1.default.createElement(SequenceFeatureSettingsDialog_1.default, { handleClose: arg => {
208
+ if (arg) {
209
+ const { upDownBp, intronBp } = arg;
210
+ setIntronBp(intronBp);
211
+ setUpDownBp(upDownBp);
216
212
  }
217
- } }, copied ? 'Copied to clipboard!' : 'Copy as plaintext'),
218
- 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" },
219
- react_1.default.createElement(material_1.Button, { className: classes.button, type: "button", variant: "contained", onClick: () => {
220
- const ref = seqPanelRef.current;
221
- if (ref) {
222
- (0, copy_to_clipboard_1.default)(ref.innerHTML, { format: 'text/html' });
223
- setCopiedHtml(true);
224
- setTimeout(() => setCopiedHtml(false), 1000);
225
- }
226
- } }, copiedHtml ? 'Copied to clipboard!' : 'Copy as HTML')),
227
- react_1.default.createElement("div", { "data-testid": "feature_sequence" }, error ? (react_1.default.createElement(material_1.Typography, { color: "error" }, `${error}`)) : loading ? (react_1.default.createElement("div", null, "Loading gene sequence...")) : sequence ? (react_1.default.createElement(exports.SequencePanel, { ref: seqPanelRef, feature: parentFeature, mode: mode, sequence: sequence })) : (react_1.default.createElement("div", null, "No sequence found")))));
213
+ setSettingsDlgOpen(false);
214
+ }, upDownBp: upDownBp, intronBp: intronBp })) : null,
215
+ helpShown ? react_1.default.createElement(SequenceHelpDialog_1.default, { handleClose: () => setHelpShown(false) }) : null));
228
216
  }
229
217
  exports.default = SequenceFeatureDetails;
@@ -0,0 +1,9 @@
1
+ /// <reference types="react" />
2
+ export default function SequenceFeatureSettingsDialog({ handleClose, intronBp: intronBpArg, upDownBp: upDownBpArg, }: {
3
+ handleClose: (arg?: {
4
+ intronBp: number;
5
+ upDownBp: number;
6
+ }) => void;
7
+ intronBp: number;
8
+ upDownBp: number;
9
+ }): JSX.Element;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ const react_1 = __importStar(require("react"));
30
+ const material_1 = require("@mui/material");
31
+ const mui_1 = require("tss-react/mui");
32
+ // icons
33
+ const Close_1 = __importDefault(require("@mui/icons-material/Close"));
34
+ const useStyles = (0, mui_1.makeStyles)()(theme => ({
35
+ formElt: {
36
+ margin: theme.spacing(3),
37
+ width: 400,
38
+ },
39
+ closeButton: {
40
+ position: 'absolute',
41
+ right: theme.spacing(1),
42
+ top: theme.spacing(1),
43
+ color: theme.palette.grey[500],
44
+ },
45
+ dialogContent: {
46
+ width: '80em',
47
+ },
48
+ }));
49
+ function SequenceFeatureSettingsDialog({ handleClose, intronBp: intronBpArg, upDownBp: upDownBpArg, }) {
50
+ const { classes } = useStyles();
51
+ const [intronBp, setIntronBp] = (0, react_1.useState)(`${intronBpArg}`);
52
+ const [upDownBp, setUpDownBp] = (0, react_1.useState)(`${upDownBpArg}`);
53
+ const intronBpValid = !Number.isNaN(+intronBp);
54
+ const upDownBpValid = !Number.isNaN(+upDownBp);
55
+ return (react_1.default.createElement(material_1.Dialog, { maxWidth: "xl", open: true, onClose: () => handleClose() },
56
+ react_1.default.createElement(material_1.DialogTitle, null,
57
+ "Feature sequence settings",
58
+ handleClose ? (react_1.default.createElement(material_1.IconButton, { className: classes.closeButton, onClick: () => handleClose() },
59
+ react_1.default.createElement(Close_1.default, null))) : null),
60
+ react_1.default.createElement(material_1.Divider, null),
61
+ react_1.default.createElement(material_1.DialogContent, { className: classes.dialogContent },
62
+ react_1.default.createElement(material_1.TextField, { label: "Number of intronic bases around splice site to display", className: classes.formElt, value: intronBp, helperText: !intronBpValid ? 'Not a number' : '', error: !intronBpValid, onChange: event => setIntronBp(event.target.value) }),
63
+ react_1.default.createElement(material_1.TextField, { label: "Number of bases up/down stream of feature to display", className: classes.formElt, value: upDownBp, helperText: !upDownBpValid ? 'Not a number' : '', error: !upDownBpValid, onChange: event => setUpDownBp(event.target.value) })),
64
+ react_1.default.createElement(material_1.DialogActions, null,
65
+ react_1.default.createElement(material_1.Button, { onClick: () => handleClose({
66
+ upDownBp: +upDownBp,
67
+ intronBp: +intronBp,
68
+ }), disabled: !intronBpValid || !upDownBpValid, color: "primary", variant: "contained" }, "Submit"),
69
+ react_1.default.createElement(material_1.Button, { onClick: () => handleClose(), color: "secondary", autoFocus: true, variant: "contained" }, "Cancel"))));
70
+ }
71
+ exports.default = SequenceFeatureSettingsDialog;
@@ -0,0 +1,4 @@
1
+ /// <reference types="react" />
2
+ export default function HelpDialog({ handleClose, }: {
3
+ handleClose: () => void;
4
+ }): JSX.Element;