@teselagen/ove 0.8.29 → 0.8.30
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/RowItem/utils.d.ts +1 -0
- package/helperComponents/PropertiesDialog/utils.d.ts +1 -0
- package/index.cjs.js +443 -255
- package/index.es.js +443 -255
- package/index.umd.js +443 -255
- package/package.json +3 -3
- package/selectors/cutsitesSelector.d.ts +1 -9
- package/selectors/filteredCutsitesSelector.d.ts +5 -1
- package/src/Editor/index.js +2 -0
- package/src/Editor/userDefinedHandlersAndOpts.js +2 -0
- package/src/GlobalDialog.js +11 -1
- package/src/GlobalDialogUtils.js +14 -2
- package/src/RowItem/StackedAnnotations/PointedAnnotation.js +101 -34
- package/src/RowItem/utils.js +19 -3
- package/src/commands/index.js +1 -1
- package/src/helperComponents/PropertiesDialog/OrfProperties.js +2 -3
- package/src/helperComponents/PropertiesDialog/TranslationProperties.js +2 -1
- package/src/helperComponents/PropertiesDialog/utils.js +29 -0
- package/src/selectors/cutsitesSelector.js +57 -2
- package/src/withEditorInteractions/createSequenceInputPopup.js +4 -2
- package/src/withEditorInteractions/index.js +12 -5
- package/src/withEditorProps/index.js +21 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teselagen/ove",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.30",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": "https://github.com/TeselaGen/tg-oss",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"@teselagen/file-utils": "0.3.23",
|
|
20
20
|
"@teselagen/range-utils": "0.3.20",
|
|
21
21
|
"@teselagen/react-list": "0.8.18",
|
|
22
|
-
"@teselagen/sequence-utils": "0.3.
|
|
23
|
-
"@teselagen/ui": "0.10.
|
|
22
|
+
"@teselagen/sequence-utils": "0.3.42",
|
|
23
|
+
"@teselagen/ui": "0.10.18",
|
|
24
24
|
"@use-gesture/react": "10.3.0",
|
|
25
25
|
"biomsa": "^0.2.4",
|
|
26
26
|
"classnames": "^2.3.2",
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
declare const _default: ((state: any) => {
|
|
2
|
-
cutsitesByName: {};
|
|
3
|
-
cutsitesById: {};
|
|
4
|
-
cutsitesArray: never[];
|
|
5
|
-
}) & import('reselect').OutputSelectorFields<(...args: any) => {
|
|
6
|
-
cutsitesByName: {};
|
|
7
|
-
cutsitesById: {};
|
|
8
|
-
cutsitesArray: never[];
|
|
9
|
-
}, {
|
|
1
|
+
declare const _default: ((state: any, ...params: any[]) => any) & import('reselect').OutputSelectorFields<(...args: readonly unknown[]) => any, {
|
|
10
2
|
clearCache: () => void;
|
|
11
3
|
}> & {
|
|
12
4
|
clearCache: () => void;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
declare const _default: ((state: any
|
|
1
|
+
declare const _default: ((state: any) => {
|
|
2
|
+
cutsitesByName: {};
|
|
3
|
+
}) & import('reselect').OutputSelectorFields<(args_0: any, args_1: any, args_2: any, args_3: any) => {
|
|
4
|
+
cutsitesByName: {};
|
|
5
|
+
}, {
|
|
2
6
|
clearCache: () => void;
|
|
3
7
|
}> & {
|
|
4
8
|
clearCache: () => void;
|
package/src/Editor/index.js
CHANGED
|
@@ -361,6 +361,7 @@ export class Editor extends React.Component {
|
|
|
361
361
|
hoveredId,
|
|
362
362
|
isFullscreen,
|
|
363
363
|
maxInsertSize,
|
|
364
|
+
getAcceptedInsertChars,
|
|
364
365
|
showAminoAcidUnitAsCodon,
|
|
365
366
|
maxAnnotationsToDisplay,
|
|
366
367
|
minHeight = 400,
|
|
@@ -595,6 +596,7 @@ export class Editor extends React.Component {
|
|
|
595
596
|
{...panelPropsToSpread}
|
|
596
597
|
editorName={editorName}
|
|
597
598
|
maxInsertSize={maxInsertSize}
|
|
599
|
+
getAcceptedInsertChars={getAcceptedInsertChars}
|
|
598
600
|
showAminoAcidUnitAsCodon={showAminoAcidUnitAsCodon}
|
|
599
601
|
isProtein={sequenceData.isProtein}
|
|
600
602
|
onlyShowLabelsThatDoNotFit={onlyShowLabelsThatDoNotFit}
|
package/src/GlobalDialog.js
CHANGED
|
@@ -41,12 +41,23 @@ const Dialogs = {
|
|
|
41
41
|
export function GlobalDialog(props) {
|
|
42
42
|
const { dialogOverrides = {}, editorName } = props;
|
|
43
43
|
const [uniqKey, setUniqKeyToForceRerender] = useState();
|
|
44
|
+
|
|
44
45
|
useEffect(() => {
|
|
45
46
|
//on unmount, clear the global dialog state..
|
|
46
47
|
return () => {
|
|
47
48
|
hideDialog();
|
|
48
49
|
};
|
|
49
50
|
}, []);
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
dialogHolder.setUniqKeyToForceRerender = setUniqKeyToForceRerender;
|
|
54
|
+
|
|
55
|
+
if (editorName) {
|
|
56
|
+
const slot = (dialogHolder[editorName] = dialogHolder[editorName] || {});
|
|
57
|
+
slot.setUniqKeyToForceRerender = setUniqKeyToForceRerender;
|
|
58
|
+
}
|
|
59
|
+
}, [editorName]);
|
|
60
|
+
|
|
50
61
|
if (
|
|
51
62
|
dialogHolder.editorName &&
|
|
52
63
|
editorName &&
|
|
@@ -54,7 +65,6 @@ export function GlobalDialog(props) {
|
|
|
54
65
|
) {
|
|
55
66
|
return null;
|
|
56
67
|
}
|
|
57
|
-
dialogHolder.setUniqKeyToForceRerender = setUniqKeyToForceRerender;
|
|
58
68
|
const Comp =
|
|
59
69
|
dialogHolder.CustomModalComponent ||
|
|
60
70
|
dialogOverrides[dialogHolder.overrideName] ||
|
package/src/GlobalDialogUtils.js
CHANGED
|
@@ -15,6 +15,8 @@ export function showDialog({
|
|
|
15
15
|
if (!dialogHolder.dialogType && ModalComponent) {
|
|
16
16
|
dialogHolder.dialogType = "TGCustomModal";
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
dialogHolder.editorName = props?.editorName;
|
|
18
20
|
// check if focused element in the dom is within a given editor and add an editor prop to the dialog
|
|
19
21
|
if (document.activeElement && document.activeElement.closest(".veEditor")) {
|
|
20
22
|
let editorName;
|
|
@@ -36,15 +38,25 @@ export function showDialog({
|
|
|
36
38
|
dialogHolder.CustomModalComponent = ModalComponent;
|
|
37
39
|
dialogHolder.props = props;
|
|
38
40
|
dialogHolder.overrideName = overrideName;
|
|
39
|
-
dialogHolder.
|
|
41
|
+
if (dialogHolder.editorName && dialogHolder?.[dialogHolder.editorName]) {
|
|
42
|
+
dialogHolder?.[dialogHolder.editorName]?.setUniqKeyToForceRerender?.(
|
|
43
|
+
shortid()
|
|
44
|
+
);
|
|
45
|
+
} else {
|
|
46
|
+
dialogHolder?.setUniqKeyToForceRerender?.(shortid());
|
|
47
|
+
}
|
|
40
48
|
}
|
|
41
49
|
export function hideDialog() {
|
|
42
50
|
delete dialogHolder.dialogType;
|
|
43
51
|
delete dialogHolder.CustomModalComponent;
|
|
44
52
|
delete dialogHolder.props;
|
|
45
53
|
delete dialogHolder.overrideName;
|
|
54
|
+
if (dialogHolder.editorName && dialogHolder?.[dialogHolder.editorName]) {
|
|
55
|
+
dialogHolder?.[dialogHolder.editorName]?.setUniqKeyToForceRerender?.();
|
|
56
|
+
} else {
|
|
57
|
+
dialogHolder?.setUniqKeyToForceRerender?.();
|
|
58
|
+
}
|
|
46
59
|
delete dialogHolder.editorName;
|
|
47
|
-
dialogHolder.setUniqKeyToForceRerender();
|
|
48
60
|
}
|
|
49
61
|
|
|
50
62
|
const typeToDialogType = {
|
|
@@ -4,14 +4,101 @@ import withHover from "../../helperComponents/withHover";
|
|
|
4
4
|
import getAnnotationNameAndStartStopString from "../../utils/getAnnotationNameAndStartStopString";
|
|
5
5
|
|
|
6
6
|
import React, { useState } from "react";
|
|
7
|
-
import { doesLabelFitInAnnotation } from "../utils";
|
|
7
|
+
import { doesLabelFitInAnnotation, getAnnotationTextWidth } from "../utils";
|
|
8
8
|
import { noop } from "lodash-es";
|
|
9
9
|
import getAnnotationClassnames from "../../utils/getAnnotationClassnames";
|
|
10
|
-
import { getStripedPattern } from "../../utils/editorUtils";
|
|
11
10
|
import { ANNOTATION_LABEL_FONT_WIDTH } from "../constants";
|
|
11
|
+
import { getStripedPattern } from "../../utils/editorUtils";
|
|
12
12
|
import { partOverhangs } from "../partOverhangs";
|
|
13
13
|
import { Tooltip } from "@blueprintjs/core";
|
|
14
14
|
|
|
15
|
+
function getAnnotationTextOffset({
|
|
16
|
+
width,
|
|
17
|
+
nameToDisplay,
|
|
18
|
+
hasAPoint,
|
|
19
|
+
pointiness,
|
|
20
|
+
forward
|
|
21
|
+
}) {
|
|
22
|
+
return (
|
|
23
|
+
width / 2 -
|
|
24
|
+
getAnnotationTextWidth(nameToDisplay) / 2 -
|
|
25
|
+
(hasAPoint
|
|
26
|
+
? (pointiness / 2 + ANNOTATION_LABEL_FONT_WIDTH / 2) * (forward ? 1 : -1)
|
|
27
|
+
: 0)
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getAnnotationNameInfo({
|
|
32
|
+
name,
|
|
33
|
+
width,
|
|
34
|
+
hasAPoint,
|
|
35
|
+
pointiness,
|
|
36
|
+
forward,
|
|
37
|
+
charWidth,
|
|
38
|
+
truncateLabelsThatDoNotFit,
|
|
39
|
+
onlyShowLabelsThatDoNotFit,
|
|
40
|
+
annotation
|
|
41
|
+
}) {
|
|
42
|
+
let nameToDisplay = name;
|
|
43
|
+
let textOffset = getAnnotationTextOffset({
|
|
44
|
+
width,
|
|
45
|
+
nameToDisplay,
|
|
46
|
+
hasAPoint,
|
|
47
|
+
pointiness,
|
|
48
|
+
forward
|
|
49
|
+
});
|
|
50
|
+
const widthAvailableForText = width - ANNOTATION_LABEL_FONT_WIDTH * 2;
|
|
51
|
+
if (
|
|
52
|
+
!doesLabelFitInAnnotation(name, { width }, charWidth) ||
|
|
53
|
+
(!onlyShowLabelsThatDoNotFit &&
|
|
54
|
+
["parts", "features"].includes(annotation.annotationTypePlural))
|
|
55
|
+
) {
|
|
56
|
+
if (truncateLabelsThatDoNotFit) {
|
|
57
|
+
// Binary search for max fitting substring
|
|
58
|
+
let left = 0;
|
|
59
|
+
let right = name.length;
|
|
60
|
+
let bestFit = "";
|
|
61
|
+
|
|
62
|
+
while (left <= right) {
|
|
63
|
+
const mid = Math.floor((left + right) / 2);
|
|
64
|
+
const candidate = name.slice(0, mid);
|
|
65
|
+
const candidateWidth = getAnnotationTextWidth(candidate);
|
|
66
|
+
|
|
67
|
+
if (candidateWidth <= widthAvailableForText) {
|
|
68
|
+
if (candidate.length > bestFit.length) {
|
|
69
|
+
bestFit = candidate;
|
|
70
|
+
}
|
|
71
|
+
left = mid + 1;
|
|
72
|
+
} else {
|
|
73
|
+
right = mid - 1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (bestFit.length < name.length) {
|
|
77
|
+
bestFit = bestFit.slice(0, -2) + "..";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
nameToDisplay = bestFit;
|
|
81
|
+
|
|
82
|
+
if (nameToDisplay.length <= 3) {
|
|
83
|
+
textOffset = 0;
|
|
84
|
+
nameToDisplay = "";
|
|
85
|
+
} else {
|
|
86
|
+
textOffset = getAnnotationTextOffset({
|
|
87
|
+
width,
|
|
88
|
+
nameToDisplay,
|
|
89
|
+
hasAPoint,
|
|
90
|
+
pointiness,
|
|
91
|
+
forward
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
textOffset = 0;
|
|
96
|
+
nameToDisplay = "";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { textOffset, nameToDisplay };
|
|
100
|
+
}
|
|
101
|
+
|
|
15
102
|
function PointedAnnotation(props) {
|
|
16
103
|
const {
|
|
17
104
|
className,
|
|
@@ -165,39 +252,19 @@ function PointedAnnotation(props) {
|
|
|
165
252
|
Q ${pointiness},${height / 2} ${0},${0}
|
|
166
253
|
z`;
|
|
167
254
|
}
|
|
168
|
-
let nameToDisplay = name;
|
|
169
|
-
let textOffset =
|
|
170
|
-
width / 2 -
|
|
171
|
-
(name.length * 5) / 2 -
|
|
172
|
-
(hasAPoint ? (pointiness / 2) * (forward ? 1 : -1) : 0);
|
|
173
|
-
if (
|
|
174
|
-
!doesLabelFitInAnnotation(name, { width }, charWidth) ||
|
|
175
|
-
(!onlyShowLabelsThatDoNotFit &&
|
|
176
|
-
["parts", "features"].includes(annotation.annotationTypePlural))
|
|
177
|
-
) {
|
|
178
|
-
if (truncateLabelsThatDoNotFit) {
|
|
179
|
-
const fractionToDisplay =
|
|
180
|
-
width / (name.length * ANNOTATION_LABEL_FONT_WIDTH);
|
|
181
|
-
const numLetters = Math.floor(fractionToDisplay * name.length);
|
|
182
|
-
nameToDisplay = name.slice(0, numLetters);
|
|
183
|
-
if (nameToDisplay.length > 3) {
|
|
184
|
-
if (nameToDisplay.length !== name.length) {
|
|
185
|
-
nameToDisplay += "..";
|
|
186
|
-
}
|
|
187
255
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
256
|
+
const { textOffset, nameToDisplay } = getAnnotationNameInfo({
|
|
257
|
+
name,
|
|
258
|
+
width,
|
|
259
|
+
hasAPoint,
|
|
260
|
+
pointiness,
|
|
261
|
+
forward,
|
|
262
|
+
charWidth,
|
|
263
|
+
truncateLabelsThatDoNotFit,
|
|
264
|
+
onlyShowLabelsThatDoNotFit,
|
|
265
|
+
annotation
|
|
266
|
+
});
|
|
267
|
+
|
|
201
268
|
let _textColor = textColor;
|
|
202
269
|
if (!textColor) {
|
|
203
270
|
try {
|
package/src/RowItem/utils.js
CHANGED
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
import { ANNOTATION_LABEL_FONT_WIDTH } from "./constants";
|
|
2
2
|
import { getWidth } from "./getXStartAndWidthOfRowAnnotation";
|
|
3
3
|
|
|
4
|
+
// Cache canvas context for text measurement
|
|
5
|
+
let measureCanvas;
|
|
6
|
+
export function getAnnotationTextWidth(
|
|
7
|
+
text,
|
|
8
|
+
fontSize = ANNOTATION_LABEL_FONT_WIDTH,
|
|
9
|
+
fontFamily = "monospace"
|
|
10
|
+
) {
|
|
11
|
+
if (!measureCanvas) {
|
|
12
|
+
measureCanvas = document.createElement("canvas");
|
|
13
|
+
}
|
|
14
|
+
const ctx = measureCanvas.getContext("2d");
|
|
15
|
+
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
16
|
+
return ctx.measureText(text).width;
|
|
17
|
+
}
|
|
18
|
+
|
|
4
19
|
export const doesLabelFitInAnnotation = (
|
|
5
20
|
text = "",
|
|
6
21
|
{ range, width },
|
|
7
22
|
charWidth
|
|
8
23
|
) => {
|
|
9
|
-
const textLength = text
|
|
10
|
-
const widthMinusOne =
|
|
11
|
-
|
|
24
|
+
const textLength = getAnnotationTextWidth(text);
|
|
25
|
+
const widthMinusOne = range
|
|
26
|
+
? getWidth(range, charWidth, 0) - ANNOTATION_LABEL_FONT_WIDTH * 2
|
|
27
|
+
: width - ANNOTATION_LABEL_FONT_WIDTH * 2;
|
|
12
28
|
return widthMinusOne > textLength;
|
|
13
29
|
};
|
|
14
30
|
|
package/src/commands/index.js
CHANGED
|
@@ -284,7 +284,7 @@ const fileCommandDefs = {
|
|
|
284
284
|
},
|
|
285
285
|
exportSequenceAsTeselagenJson: {
|
|
286
286
|
name: "Download Teselagen JSON File",
|
|
287
|
-
handler: props => props.exportSequenceToFile("teselagenJson")
|
|
287
|
+
handler: props => props.exportSequenceToFile("teselagenJson", { getAcceptedInsertChars: props.getAcceptedInsertChars})
|
|
288
288
|
},
|
|
289
289
|
|
|
290
290
|
viewProperties: {
|
|
@@ -10,10 +10,9 @@ import { getRangeLength } from "@teselagen/range-utils";
|
|
|
10
10
|
import { getOrfColor } from "../../constants/orfFrameToColorMap";
|
|
11
11
|
import { connectToEditor } from "../../withEditorProps";
|
|
12
12
|
import { compose } from "recompose";
|
|
13
|
-
import selectors from "../../selectors";
|
|
14
13
|
|
|
15
14
|
import getCommands from "../../commands";
|
|
16
|
-
import { sizeSchema } from "./utils";
|
|
15
|
+
import { sizeSchema, getMemoOrfs } from "./utils";
|
|
17
16
|
import { orfsSubmenu } from "../../MenuBar/viewSubmenu";
|
|
18
17
|
import { getVisFilter } from "./GenericAnnotationProperties";
|
|
19
18
|
|
|
@@ -107,7 +106,7 @@ export default compose(
|
|
|
107
106
|
readOnly,
|
|
108
107
|
annotationVisibility,
|
|
109
108
|
useAdditionalOrfStartCodons,
|
|
110
|
-
orfs:
|
|
109
|
+
orfs: getMemoOrfs(editorState),
|
|
111
110
|
sequenceLength: sequence.length,
|
|
112
111
|
sequenceData,
|
|
113
112
|
minimumOrfSize
|
|
@@ -14,6 +14,7 @@ import selectors from "../../selectors";
|
|
|
14
14
|
import { getMassOfAaString } from "@teselagen/sequence-utils";
|
|
15
15
|
import { translationsSubmenu } from "../../MenuBar/viewSubmenu";
|
|
16
16
|
import { getVisFilter } from "./GenericAnnotationProperties";
|
|
17
|
+
import { getMemoOrfs } from "./utils";
|
|
17
18
|
|
|
18
19
|
class TranslationProperties extends React.Component {
|
|
19
20
|
constructor(props) {
|
|
@@ -140,7 +141,7 @@ export default compose(
|
|
|
140
141
|
return {
|
|
141
142
|
readOnly,
|
|
142
143
|
translations: selectors.translationsSelector(editorState),
|
|
143
|
-
orfs:
|
|
144
|
+
orfs: getMemoOrfs(editorState),
|
|
144
145
|
annotationVisibility,
|
|
145
146
|
sequenceLength: (sequenceData.sequence || "").length,
|
|
146
147
|
sequenceData
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { isEqual } from "lodash-es";
|
|
2
3
|
import { convertDnaCaretPositionOrRangeToAA } from "@teselagen/sequence-utils";
|
|
3
4
|
import { convertRangeTo1Based } from "@teselagen/range-utils";
|
|
5
|
+
import selectors from "../../selectors";
|
|
4
6
|
|
|
5
7
|
export const sizeSchema = isProtein => ({
|
|
6
8
|
path: "size",
|
|
@@ -35,3 +37,30 @@ export const sizeSchema = isProtein => ({
|
|
|
35
37
|
);
|
|
36
38
|
}
|
|
37
39
|
});
|
|
40
|
+
|
|
41
|
+
export const getMemoOrfs = (() => {
|
|
42
|
+
let lastDeps;
|
|
43
|
+
let lastResult;
|
|
44
|
+
return (editorState) => {
|
|
45
|
+
const {
|
|
46
|
+
sequenceData,
|
|
47
|
+
minimumOrfSize,
|
|
48
|
+
useAdditionalOrfStartCodons
|
|
49
|
+
} = editorState;
|
|
50
|
+
|
|
51
|
+
const { sequence, circular } = sequenceData;
|
|
52
|
+
|
|
53
|
+
const deps = {
|
|
54
|
+
sequence,
|
|
55
|
+
circular,
|
|
56
|
+
minimumOrfSize,
|
|
57
|
+
useAdditionalOrfStartCodons
|
|
58
|
+
};
|
|
59
|
+
if (lastResult && isEqual(deps, lastDeps)) {
|
|
60
|
+
return lastResult;
|
|
61
|
+
}
|
|
62
|
+
lastResult = selectors.orfsSelector(editorState);
|
|
63
|
+
lastDeps = deps;
|
|
64
|
+
return lastResult;
|
|
65
|
+
};
|
|
66
|
+
})();
|
|
@@ -4,12 +4,55 @@ import sequenceSelector from "./sequenceSelector";
|
|
|
4
4
|
import restrictionEnzymesSelector from "./restrictionEnzymesSelector";
|
|
5
5
|
import cutsiteLabelColorSelector from "./cutsiteLabelColorSelector";
|
|
6
6
|
import { createSelector } from "reselect";
|
|
7
|
+
import { isEqual } from "lodash-es";
|
|
7
8
|
|
|
8
9
|
import { flatMap as flatmap, map } from "lodash-es";
|
|
9
10
|
import { getCutsitesFromSequence } from "@teselagen/sequence-utils";
|
|
10
11
|
import { getLowerCaseObj } from "../utils/arrayUtils";
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
// [{ args: {sequence,circular,enzymeList,cutsiteLabelColors}, result }]
|
|
14
|
+
const cutsitesCache = [];
|
|
15
|
+
|
|
16
|
+
function getCachedResult(argsObj) {
|
|
17
|
+
const idx = cutsitesCache.findIndex(
|
|
18
|
+
entry =>
|
|
19
|
+
entry &&
|
|
20
|
+
isEqual(entry.args, argsObj)
|
|
21
|
+
);
|
|
22
|
+
if (idx === -1) return;
|
|
23
|
+
const hit = cutsitesCache[idx];
|
|
24
|
+
return hit.result;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function setCachedResult(
|
|
28
|
+
argsObj,
|
|
29
|
+
result,
|
|
30
|
+
cacheSize = 1
|
|
31
|
+
) {
|
|
32
|
+
cutsitesCache.push({
|
|
33
|
+
args: argsObj,
|
|
34
|
+
result
|
|
35
|
+
});
|
|
36
|
+
//keep cache size manageable
|
|
37
|
+
if (cutsitesCache.length > cacheSize) cutsitesCache.shift();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function cutsitesSelector(
|
|
41
|
+
sequence,
|
|
42
|
+
circular,
|
|
43
|
+
enzymeList,
|
|
44
|
+
cutsiteLabelColors,
|
|
45
|
+
editorSize = 1
|
|
46
|
+
) {
|
|
47
|
+
const cachedResult = getCachedResult({
|
|
48
|
+
sequence,
|
|
49
|
+
circular,
|
|
50
|
+
enzymeList,
|
|
51
|
+
cutsiteLabelColors
|
|
52
|
+
});
|
|
53
|
+
if (cachedResult) {
|
|
54
|
+
return cachedResult;
|
|
55
|
+
}
|
|
13
56
|
//get the cutsites grouped by enzyme
|
|
14
57
|
const cutsitesByName = getLowerCaseObj(
|
|
15
58
|
getCutsitesFromSequence(sequence, circular, map(enzymeList))
|
|
@@ -45,11 +88,22 @@ function cutsitesSelector(sequence, circular, enzymeList, cutsiteLabelColors) {
|
|
|
45
88
|
const cutsitesArray = flatmap(cutsitesByName, function (cutsitesForEnzyme) {
|
|
46
89
|
return cutsitesForEnzyme;
|
|
47
90
|
});
|
|
48
|
-
|
|
91
|
+
const result = {
|
|
49
92
|
cutsitesByName,
|
|
50
93
|
cutsitesById,
|
|
51
94
|
cutsitesArray
|
|
52
95
|
};
|
|
96
|
+
setCachedResult(
|
|
97
|
+
{
|
|
98
|
+
sequence,
|
|
99
|
+
circular,
|
|
100
|
+
enzymeList,
|
|
101
|
+
cutsiteLabelColors
|
|
102
|
+
},
|
|
103
|
+
result,
|
|
104
|
+
editorSize
|
|
105
|
+
);
|
|
106
|
+
return result;
|
|
53
107
|
}
|
|
54
108
|
|
|
55
109
|
export default createSelector(
|
|
@@ -57,5 +111,6 @@ export default createSelector(
|
|
|
57
111
|
circularSelector,
|
|
58
112
|
restrictionEnzymesSelector,
|
|
59
113
|
cutsiteLabelColorSelector,
|
|
114
|
+
editorState => editorState.editorSize,
|
|
60
115
|
cutsitesSelector
|
|
61
116
|
);
|
|
@@ -82,6 +82,7 @@ class SequenceInputNoHotkeys extends React.Component {
|
|
|
82
82
|
caretPosition,
|
|
83
83
|
sequenceData,
|
|
84
84
|
maxInsertSize,
|
|
85
|
+
getAcceptedInsertChars,
|
|
85
86
|
showAminoAcidUnitAsCodon
|
|
86
87
|
} = this.props;
|
|
87
88
|
const { charsToInsert, hasTempError } = this.state;
|
|
@@ -151,8 +152,9 @@ class SequenceInputNoHotkeys extends React.Component {
|
|
|
151
152
|
e.target.value,
|
|
152
153
|
{
|
|
153
154
|
...sequenceData,
|
|
154
|
-
name: undefined
|
|
155
|
-
|
|
155
|
+
name: undefined,
|
|
156
|
+
getAcceptedInsertChars
|
|
157
|
+
},
|
|
156
158
|
);
|
|
157
159
|
if (warnings.length) {
|
|
158
160
|
this.setState({
|
|
@@ -212,7 +212,8 @@ function VectorInteractionHOC(Component /* options */) {
|
|
|
212
212
|
onPaste,
|
|
213
213
|
disableBpEditing,
|
|
214
214
|
sequenceData,
|
|
215
|
-
maxInsertSize
|
|
215
|
+
maxInsertSize,
|
|
216
|
+
getAcceptedInsertChars
|
|
216
217
|
} = this.props;
|
|
217
218
|
|
|
218
219
|
if (disableBpEditing) {
|
|
@@ -260,7 +261,8 @@ function VectorInteractionHOC(Component /* options */) {
|
|
|
260
261
|
topLevelSeqData: sequenceData,
|
|
261
262
|
provideNewIdsForAnnotations: true,
|
|
262
263
|
annotationsAsObjects: true,
|
|
263
|
-
noCdsTranslations: true
|
|
264
|
+
noCdsTranslations: true,
|
|
265
|
+
getAcceptedInsertChars
|
|
264
266
|
});
|
|
265
267
|
if (!seqDataToInsert.sequence.length)
|
|
266
268
|
return window.toastr.warning("Sorry no valid base pairs to paste");
|
|
@@ -283,7 +285,8 @@ function VectorInteractionHOC(Component /* options */) {
|
|
|
283
285
|
selectionLayer,
|
|
284
286
|
copyOptions,
|
|
285
287
|
disableBpEditing,
|
|
286
|
-
readOnly
|
|
288
|
+
readOnly,
|
|
289
|
+
getAcceptedInsertChars,
|
|
287
290
|
} = this.props;
|
|
288
291
|
const onCut = this.props.onCut || this.props.onCopy || noop;
|
|
289
292
|
const seqData = tidyUpSequenceData(
|
|
@@ -308,7 +311,8 @@ function VectorInteractionHOC(Component /* options */) {
|
|
|
308
311
|
{
|
|
309
312
|
doNotRemoveInvalidChars: true,
|
|
310
313
|
annotationsAsObjects: true,
|
|
311
|
-
includeProteinSequence: true
|
|
314
|
+
includeProteinSequence: true,
|
|
315
|
+
getAcceptedInsertChars
|
|
312
316
|
}
|
|
313
317
|
);
|
|
314
318
|
|
|
@@ -340,7 +344,8 @@ function VectorInteractionHOC(Component /* options */) {
|
|
|
340
344
|
e,
|
|
341
345
|
tidyUpSequenceData(seqData, {
|
|
342
346
|
doNotRemoveInvalidChars: true,
|
|
343
|
-
annotationsAsObjects: true
|
|
347
|
+
annotationsAsObjects: true,
|
|
348
|
+
getAcceptedInsertChars
|
|
344
349
|
}),
|
|
345
350
|
this.props
|
|
346
351
|
);
|
|
@@ -404,6 +409,7 @@ function VectorInteractionHOC(Component /* options */) {
|
|
|
404
409
|
readOnly,
|
|
405
410
|
disableBpEditing,
|
|
406
411
|
maxInsertSize,
|
|
412
|
+
getAcceptedInsertChars,
|
|
407
413
|
showAminoAcidUnitAsCodon
|
|
408
414
|
} = this.props;
|
|
409
415
|
const sequenceLength = sequenceData.sequence.length;
|
|
@@ -423,6 +429,7 @@ function VectorInteractionHOC(Component /* options */) {
|
|
|
423
429
|
sequenceLength,
|
|
424
430
|
caretPosition,
|
|
425
431
|
maxInsertSize,
|
|
432
|
+
getAcceptedInsertChars,
|
|
426
433
|
showAminoAcidUnitAsCodon,
|
|
427
434
|
handleInsert: async seqDataToInsert => {
|
|
428
435
|
await insertAndSelectHelper({
|
|
@@ -39,6 +39,7 @@ import { createSelector, defaultMemoize } from "reselect";
|
|
|
39
39
|
import domtoimage from "dom-to-image";
|
|
40
40
|
import {
|
|
41
41
|
hideDialog,
|
|
42
|
+
dialogHolder,
|
|
42
43
|
showAddOrEditAnnotationDialog,
|
|
43
44
|
showDialog
|
|
44
45
|
} from "../GlobalDialogUtils";
|
|
@@ -141,7 +142,8 @@ export const handleSave =
|
|
|
141
142
|
readOnly,
|
|
142
143
|
alwaysAllowSave,
|
|
143
144
|
sequenceData,
|
|
144
|
-
lastSavedIdUpdate
|
|
145
|
+
lastSavedIdUpdate,
|
|
146
|
+
getAcceptedInsertChars
|
|
145
147
|
} = props;
|
|
146
148
|
const saveHandler = opts.isSaveAs ? onSaveAs || onSave : onSave;
|
|
147
149
|
|
|
@@ -162,7 +164,8 @@ export const handleSave =
|
|
|
162
164
|
opts,
|
|
163
165
|
tidyUpSequenceData(sequenceData, {
|
|
164
166
|
doNotRemoveInvalidChars: true,
|
|
165
|
-
annotationsAsObjects: true
|
|
167
|
+
annotationsAsObjects: true,
|
|
168
|
+
getAcceptedInsertChars
|
|
166
169
|
}),
|
|
167
170
|
props,
|
|
168
171
|
updateLastSavedIdToCurrent
|
|
@@ -367,8 +370,8 @@ export default compose(
|
|
|
367
370
|
options
|
|
368
371
|
} = props.beforeSequenceInsertOrDelete
|
|
369
372
|
? (await props.beforeSequenceInsertOrDelete(
|
|
370
|
-
tidyUpSequenceData(_sequenceDataToInsert),
|
|
371
|
-
tidyUpSequenceData(_existingSequenceData),
|
|
373
|
+
tidyUpSequenceData(_sequenceDataToInsert, { getAcceptedInsertChars: props.getAcceptedInsertChars }),
|
|
374
|
+
tidyUpSequenceData(_existingSequenceData, { getAcceptedInsertChars: props.getAcceptedInsertChars }),
|
|
372
375
|
_caretPositionOrRange,
|
|
373
376
|
_options
|
|
374
377
|
)) || {}
|
|
@@ -555,7 +558,11 @@ const getEditorState = createSelector(
|
|
|
555
558
|
state => state.VectorEditor,
|
|
556
559
|
(state, editorName) => editorName,
|
|
557
560
|
(VectorEditor, editorName) => {
|
|
558
|
-
|
|
561
|
+
const editorState = VectorEditor[editorName];
|
|
562
|
+
editorState && (editorState.editorSize = Object.values(VectorEditor).filter(
|
|
563
|
+
editorItem => editorItem?.sequenceData
|
|
564
|
+
).length);
|
|
565
|
+
return editorState;
|
|
559
566
|
}
|
|
560
567
|
);
|
|
561
568
|
|
|
@@ -595,6 +602,10 @@ function mapStateToProps(state, ownProps) {
|
|
|
595
602
|
annotationTypePlural,
|
|
596
603
|
sequenceLength
|
|
597
604
|
);
|
|
605
|
+
if (dialogHolder.editorName) {
|
|
606
|
+
annotationToAdd =
|
|
607
|
+
dialogHolder.editorName === editorName ? annotationToAdd : undefined;
|
|
608
|
+
}
|
|
598
609
|
}
|
|
599
610
|
});
|
|
600
611
|
|
|
@@ -623,7 +634,7 @@ function mapStateToProps(state, ownProps) {
|
|
|
623
634
|
const selectedCutsites = s.selectedCutsitesSelector(editorState);
|
|
624
635
|
const allCutsites = s.cutsitesSelector(
|
|
625
636
|
editorState,
|
|
626
|
-
ownProps.additionalEnzymes
|
|
637
|
+
ownProps.additionalEnzymes,
|
|
627
638
|
);
|
|
628
639
|
|
|
629
640
|
const { matchedSearchLayer, searchLayers, matchesTotal } =
|
|
@@ -887,13 +898,15 @@ export function getShowGCContent(state, ownProps) {
|
|
|
887
898
|
return toRet;
|
|
888
899
|
}
|
|
889
900
|
|
|
890
|
-
function jsonToJson(incomingJson) {
|
|
901
|
+
function jsonToJson(incomingJson, options) {
|
|
902
|
+
const {getAcceptedInsertChars} = options || {};
|
|
891
903
|
return JSON.stringify(
|
|
892
904
|
omit(
|
|
893
905
|
cleanUpTeselagenJsonForExport(
|
|
894
906
|
tidyUpSequenceData(incomingJson, {
|
|
895
907
|
doNotRemoveInvalidChars: true,
|
|
896
|
-
annotationsAsObjects: false
|
|
908
|
+
annotationsAsObjects: false,
|
|
909
|
+
getAcceptedInsertChars
|
|
897
910
|
})
|
|
898
911
|
),
|
|
899
912
|
[
|