@teselagen/ove 0.8.29 → 0.8.31
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 +455 -256
- package/index.es.js +455 -256
- package/index.umd.js +455 -256
- 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/utils/getAnnotationNameAndStartStopString.js +1 -1
- 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.31",
|
|
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.19",
|
|
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
|
);
|
|
@@ -44,7 +44,7 @@ export default function getAnnotationNameAndStartStopString(
|
|
|
44
44
|
const interactionInstructions = readOnly
|
|
45
45
|
? ""
|
|
46
46
|
: annotationTypePlural === "cutsites"
|
|
47
|
-
? `\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n INTERACTIONS:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n click → top cut position\n alt/option+click → bottom cut position\n cmd/ctrl+click → recognition range`
|
|
47
|
+
? `\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n INTERACTIONS:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n click → top cut position\n alt/option+click → bottom cut position\n cmd/ctrl+click → recognition range\n double click → show info`
|
|
48
48
|
: `\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n INTERACTIONS:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n alt/option+click → jump row view to start/end\n double click → edit`;
|
|
49
49
|
|
|
50
50
|
return `${startText ? startText : ""} ${typeToUse ? typeToUse + " -" : ""} ${
|
|
@@ -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({
|