@teselagen/ove 0.7.29 → 0.7.30-beta.2
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/CreateAnnotationsPage.d.ts +4 -3
- package/README.md +1 -1
- package/fileUtils.d.ts +12 -0
- package/html2canvas.esm--JN4fLQL.js +7891 -0
- package/html2canvas.esm-B7d7VJmQ.cjs +7891 -0
- package/index.cjs.js +1989 -2328
- package/index.es.js +1989 -2328
- package/index.umd.js +2059 -2398
- package/ove.css +16 -3
- package/package.json +15 -12
- package/redux/findTool.d.ts +1 -0
- package/selectors/orfsSelector.d.ts +2 -2
- package/selectors/searchLayersSelector.d.ts +1 -1
- package/selectors/translationsSelector.d.ts +1 -1
- package/src/AutoAnnotate.js +4 -4
- package/src/CreateAnnotationsPage.js +1 -2
- package/src/Editor/style.css +8 -3
- package/src/FindBar/index.js +32 -1
- package/src/GlobalDialogUtils.js +2 -2
- package/src/RowItem/SelectionLayer/index.js +42 -4
- package/src/RowItem/SelectionLayer/style.css +8 -0
- package/src/ToolBar/alignmentTool.js +2 -2
- package/src/fileUtils.js +103 -0
- package/src/helperComponents/MergeFeaturesDialog/index.js +2 -2
- package/src/redux/alignments.js +2 -2
- package/src/redux/findTool.js +9 -0
- package/src/redux/sequenceData/index.js +2 -2
- package/src/redux/sequenceData/upsertDeleteActionGenerator.js +2 -2
- package/src/selectors/cutsitesSelector.js +2 -2
- package/src/selectors/searchLayersSelector.js +40 -2
- package/src/selectors/translationsSelector.js +3 -3
- package/src/utils/cleanSequenceData_DEPRECATED/arrayToObjWithIds.js +2 -2
- package/src/withEditorProps/index.js +2 -2
- package/style.css +12107 -0
package/ove.css
CHANGED
|
@@ -10689,6 +10689,14 @@ li.bp3-menu-divider:last-child {
|
|
|
10689
10689
|
top: -2px;
|
|
10690
10690
|
position: absolute;
|
|
10691
10691
|
z-index: 10;
|
|
10692
|
+
display: flex;
|
|
10693
|
+
align-items: center;
|
|
10694
|
+
justify-content: center;
|
|
10695
|
+
font-family: monospace;
|
|
10696
|
+
}
|
|
10697
|
+
|
|
10698
|
+
.veMismatchLayer {
|
|
10699
|
+
z-index: 11; /* Ensure mismatch layers appear on top of normal selection layer */
|
|
10692
10700
|
}
|
|
10693
10701
|
|
|
10694
10702
|
.veCaret {
|
|
@@ -11847,9 +11855,14 @@ path.partWithSelectedTag {
|
|
|
11847
11855
|
border-right-color: yellow !important;
|
|
11848
11856
|
}
|
|
11849
11857
|
.veSearchLayerActive {
|
|
11850
|
-
stroke:
|
|
11851
|
-
fill:
|
|
11852
|
-
background:
|
|
11858
|
+
stroke: green !important;
|
|
11859
|
+
fill: green !important;
|
|
11860
|
+
background: green !important;
|
|
11861
|
+
}
|
|
11862
|
+
.veMismatchedBase {
|
|
11863
|
+
color: red !important;
|
|
11864
|
+
font-weight: bold;
|
|
11865
|
+
background-color: rgba(255, 0, 0, 0.3);
|
|
11853
11866
|
}
|
|
11854
11867
|
.notCaret.veSearchLayerBottomStrand:after {
|
|
11855
11868
|
content: "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teselagen/ove",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.30-beta.2",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -11,19 +11,22 @@
|
|
|
11
11
|
"./*": "./*"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@teselagen/sequence-utils": "0.3.31",
|
|
15
|
-
"@teselagen/range-utils": "0.3.13",
|
|
16
|
-
"@teselagen/ui": "0.8.6-beta.23",
|
|
17
|
-
"@teselagen/file-utils": "0.3.20",
|
|
18
|
-
"@teselagen/bio-parsers": "0.4.28",
|
|
19
14
|
"@blueprintjs/core": "3.54.0",
|
|
20
15
|
"@hello-pangea/dnd": "16.2.0",
|
|
21
16
|
"@risingstack/react-easy-state": "^6.3.0",
|
|
17
|
+
"@teselagen/bio-parsers": "0.4.29-beta.1",
|
|
18
|
+
"@teselagen/file-utils": "0.3.20",
|
|
19
|
+
"@teselagen/range-utils": "0.3.14-beta.1",
|
|
22
20
|
"@teselagen/react-list": "0.8.18",
|
|
21
|
+
"@teselagen/sequence-utils": "0.3.32-beta.2",
|
|
22
|
+
"@teselagen/ui": "0.8.6-beta.27",
|
|
23
|
+
"@use-gesture/react": "10.3.0",
|
|
24
|
+
"biomsa": "^0.2.4",
|
|
23
25
|
"classnames": "^2.3.2",
|
|
24
|
-
"color": "^
|
|
26
|
+
"color": "^5.0.0",
|
|
25
27
|
"combokeys": "^3.0.1",
|
|
26
28
|
"copy-to-clipboard": "^3.3.1",
|
|
29
|
+
"cypress-real-events": "^1.13.0",
|
|
27
30
|
"deep-equal": "^1.1.1",
|
|
28
31
|
"dom-to-image": "^2.6.0",
|
|
29
32
|
"downloadjs": "^1.4.7",
|
|
@@ -58,11 +61,11 @@
|
|
|
58
61
|
"to-regex-range": "5.0.1",
|
|
59
62
|
"use-debounce": "^8.0.4",
|
|
60
63
|
"validate.io-nonnegative-integer-array": "^1.0.1",
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"
|
|
65
|
-
"
|
|
64
|
+
"nanoid": "5.1.5"
|
|
65
|
+
},
|
|
66
|
+
"volta": {
|
|
67
|
+
"node": "18.18.0",
|
|
68
|
+
"yarn": "1.22.21"
|
|
66
69
|
},
|
|
67
70
|
"license": "MIT"
|
|
68
71
|
}
|
package/redux/findTool.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export const toggleIsInline: import('redux-act').ComplexActionCreator1<any, any,
|
|
|
4
4
|
export const updateSearchText: import('redux-act').ComplexActionCreator1<any, any, any>;
|
|
5
5
|
export const updateAmbiguousOrLiteral: import('redux-act').ComplexActionCreator1<any, any, any>;
|
|
6
6
|
export const updateDnaOrAA: import('redux-act').ComplexActionCreator1<any, any, any>;
|
|
7
|
+
export const updateMismatchesAllowed: import('redux-act').ComplexActionCreator1<any, any, any>;
|
|
7
8
|
export const updateMatchNumber: import('redux-act').ComplexActionCreator1<any, any, any>;
|
|
8
9
|
declare const _default: {
|
|
9
10
|
(newState: {} | undefined, action: any): any;
|
|
@@ -7,7 +7,7 @@ declare const _default: ((state: any) => {
|
|
|
7
7
|
forward: any;
|
|
8
8
|
annotationTypePlural: string;
|
|
9
9
|
isOrf: boolean;
|
|
10
|
-
id:
|
|
10
|
+
id: string;
|
|
11
11
|
}[]) & import('reselect').OutputSelectorFields<(args_0: any, args_1: any, args_2: any, args_3: any) => {
|
|
12
12
|
start: number;
|
|
13
13
|
end: number;
|
|
@@ -17,7 +17,7 @@ declare const _default: ((state: any) => {
|
|
|
17
17
|
forward: any;
|
|
18
18
|
annotationTypePlural: string;
|
|
19
19
|
isOrf: boolean;
|
|
20
|
-
id:
|
|
20
|
+
id: string;
|
|
21
21
|
}[], {
|
|
22
22
|
clearCache: () => void;
|
|
23
23
|
}> & {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const _default: ((state: any) => any[]) & import('reselect').OutputSelectorFields<(args_0: any, args_1: any, args_2: any, args_3: any, args_4: any, args_5: any, args_6: any, args_7: any) => any[], {
|
|
1
|
+
declare const _default: ((state: any) => any[]) & import('reselect').OutputSelectorFields<(args_0: any, args_1: any, args_2: any, args_3: any, args_4: any, args_5: any, args_6: any, args_7: any, args_8: any) => any[], {
|
|
2
2
|
clearCache: () => void;
|
|
3
3
|
}> & {
|
|
4
4
|
clearCache: () => void;
|
|
@@ -7,7 +7,7 @@ declare const _default: ((state: any) => any) & import('reselect').OutputSelecto
|
|
|
7
7
|
forward: any;
|
|
8
8
|
annotationTypePlural: string;
|
|
9
9
|
isOrf: boolean;
|
|
10
|
-
id:
|
|
10
|
+
id: string;
|
|
11
11
|
}[], args_4: any, args_5: any, args_6: any[], args_7: any, args_8: any, args_9: any, args_10: any, args_11: any, args_12: any) => any, {
|
|
12
12
|
clearCache: () => void;
|
|
13
13
|
}> & {
|
package/src/AutoAnnotate.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { unparse } from "papaparse";
|
|
5
5
|
import pluralize from "pluralize";
|
|
6
6
|
import { SubmissionError, reduxForm } from "redux-form";
|
|
7
|
-
import
|
|
7
|
+
import { nanoid } from "nanoid";
|
|
8
8
|
import CreateAnnotationsPage from "./CreateAnnotationsPage";
|
|
9
9
|
import { formName } from "./constants";
|
|
10
10
|
import { AutoAnnotateBpMatchingDialog } from "./AutoAnnotateBpMatchingDialog";
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
} from "@teselagen/ui";
|
|
34
34
|
import { startCase } from "lodash-es";
|
|
35
35
|
import withEditorProps from "./withEditorProps";
|
|
36
|
-
import { useEffect, useState } from "react";
|
|
36
|
+
import React, { useEffect, useState } from "react";
|
|
37
37
|
import { Colors, Tab, Tabs } from "@blueprintjs/core";
|
|
38
38
|
import { typeField } from "./helperComponents/PropertiesDialog/typeField";
|
|
39
39
|
|
|
@@ -403,7 +403,7 @@ FRT GAAGTTCCTATTCTCTAGAAAGTATAGGAACTTC misc_recomb orchid pink 0 0`,
|
|
|
403
403
|
if (ann.matchType === "protein") {
|
|
404
404
|
ann.sequence = convertProteinSeqToDNAIupac(ann.sequence);
|
|
405
405
|
}
|
|
406
|
-
const id =
|
|
406
|
+
const id = nanoid();
|
|
407
407
|
annotationsToCheckById[id] = {
|
|
408
408
|
...ann,
|
|
409
409
|
sequence: ann.isRegex
|
|
@@ -428,7 +428,7 @@ FRT GAAGTTCCTATTCTCTAGAAAGTATAGGAACTTC misc_recomb orchid pink 0 0`,
|
|
|
428
428
|
...annotationsToCheckById[a.id],
|
|
429
429
|
...a,
|
|
430
430
|
forward: a.strand !== -1,
|
|
431
|
-
id:
|
|
431
|
+
id: nanoid()
|
|
432
432
|
};
|
|
433
433
|
toRet.color =
|
|
434
434
|
toRet.color || getFeatureToColorMap()[toRet.type];
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { compose } from "redux";
|
|
2
2
|
import pluralize from "pluralize";
|
|
3
|
-
|
|
4
3
|
import { formName } from "./constants";
|
|
5
4
|
import { typeField } from "./helperComponents/PropertiesDialog/typeField";
|
|
6
5
|
import {
|
|
@@ -9,7 +8,7 @@ import {
|
|
|
9
8
|
withSelectTableRecords,
|
|
10
9
|
withSelectedEntities
|
|
11
10
|
} from "@teselagen/ui";
|
|
12
|
-
import { useEffect } from "react";
|
|
11
|
+
import React, { useEffect } from "react";
|
|
13
12
|
import { hideDialog } from "./GlobalDialogUtils";
|
|
14
13
|
import { startCase } from "lodash-es";
|
|
15
14
|
import { tidyUpAnnotation } from "@teselagen/sequence-utils";
|
package/src/Editor/style.css
CHANGED
|
@@ -89,9 +89,14 @@
|
|
|
89
89
|
border-right-color: yellow !important;
|
|
90
90
|
}
|
|
91
91
|
.veSearchLayerActive {
|
|
92
|
-
stroke:
|
|
93
|
-
fill:
|
|
94
|
-
background:
|
|
92
|
+
stroke: green !important;
|
|
93
|
+
fill: green !important;
|
|
94
|
+
background: green !important;
|
|
95
|
+
}
|
|
96
|
+
.veMismatchedBase {
|
|
97
|
+
color: red !important;
|
|
98
|
+
font-weight: bold;
|
|
99
|
+
background-color: rgba(255, 0, 0, 0.3);
|
|
95
100
|
}
|
|
96
101
|
.notCaret.veSearchLayerBottomStrand:after {
|
|
97
102
|
content: "";
|
package/src/FindBar/index.js
CHANGED
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
Popover,
|
|
7
7
|
Position,
|
|
8
8
|
TextArea,
|
|
9
|
-
Tooltip
|
|
9
|
+
Tooltip,
|
|
10
|
+
NumericInput
|
|
10
11
|
} from "@blueprintjs/core";
|
|
11
12
|
import withEditorProps from "../withEditorProps";
|
|
12
13
|
import { MAX_MATCHES_DISPLAYED } from "../constants/findToolConstants";
|
|
@@ -50,6 +51,7 @@ export class FindBar extends React.Component {
|
|
|
50
51
|
annotationVisibilityShow,
|
|
51
52
|
updateAmbiguousOrLiteral,
|
|
52
53
|
updateDnaOrAA,
|
|
54
|
+
updateMismatchesAllowed,
|
|
53
55
|
updateMatchNumber: _updateMatchNumber,
|
|
54
56
|
selectionLayerUpdate,
|
|
55
57
|
annotationSearchMatches,
|
|
@@ -63,6 +65,7 @@ export class FindBar extends React.Component {
|
|
|
63
65
|
ambiguousOrLiteral,
|
|
64
66
|
matchesTotal = 0,
|
|
65
67
|
matchNumber = 0,
|
|
68
|
+
mismatchesAllowed = 0,
|
|
66
69
|
isInline
|
|
67
70
|
} = findTool;
|
|
68
71
|
const updateMatchNumber = (...args) => {
|
|
@@ -139,6 +142,33 @@ export class FindBar extends React.Component {
|
|
|
139
142
|
</div>
|
|
140
143
|
</InfoHelper>
|
|
141
144
|
</div>,
|
|
145
|
+
<div
|
|
146
|
+
key="mismatchesAllowed"
|
|
147
|
+
style={{ marginTop: "8px", display: "flex", alignItems: "center" }}
|
|
148
|
+
>
|
|
149
|
+
<label style={{ marginRight: "8px" }}>Mismatches Allowed:</label>
|
|
150
|
+
<NumericInput
|
|
151
|
+
min={0}
|
|
152
|
+
max={10}
|
|
153
|
+
className={"tg-mismatches-allowed-input"}
|
|
154
|
+
style={{ width: "60px" }}
|
|
155
|
+
value={mismatchesAllowed}
|
|
156
|
+
disabled={dnaOrAA !== "DNA" || ambiguousOrLiteral !== "LITERAL"}
|
|
157
|
+
onValueChange={value => {
|
|
158
|
+
const newValue = Math.max(0, parseInt(value, 10) || 0);
|
|
159
|
+
updateMismatchesAllowed(newValue);
|
|
160
|
+
}}
|
|
161
|
+
/>
|
|
162
|
+
<InfoHelper style={{ marginLeft: 10 }}>
|
|
163
|
+
<div>
|
|
164
|
+
Number of mismatches allowed when searching DNA sequences with
|
|
165
|
+
literal matching.
|
|
166
|
+
<br />
|
|
167
|
+
<br />
|
|
168
|
+
Higher values may slow down search performance.
|
|
169
|
+
</div>
|
|
170
|
+
</InfoHelper>
|
|
171
|
+
</div>,
|
|
142
172
|
<Switch
|
|
143
173
|
key="highlightall"
|
|
144
174
|
checked={highlightAll}
|
|
@@ -152,6 +182,7 @@ export class FindBar extends React.Component {
|
|
|
152
182
|
Highlight All
|
|
153
183
|
</Tooltip>
|
|
154
184
|
</Switch>,
|
|
185
|
+
|
|
155
186
|
...(isMobile()
|
|
156
187
|
? []
|
|
157
188
|
: [
|
package/src/GlobalDialogUtils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { nanoid } from "nanoid";
|
|
2
2
|
|
|
3
3
|
import { cloneDeep, startCase } from "lodash-es";
|
|
4
4
|
import { convertRangeTo1Based } from "@teselagen/range-utils";
|
|
@@ -36,7 +36,7 @@ export function showDialog({
|
|
|
36
36
|
dialogHolder.CustomModalComponent = ModalComponent;
|
|
37
37
|
dialogHolder.props = props;
|
|
38
38
|
dialogHolder.overrideName = overrideName;
|
|
39
|
-
dialogHolder.setUniqKeyToForceRerender(
|
|
39
|
+
dialogHolder.setUniqKeyToForceRerender(nanoid());
|
|
40
40
|
}
|
|
41
41
|
export function hideDialog() {
|
|
42
42
|
delete dialogHolder.dialogType;
|
|
@@ -149,7 +149,8 @@ function SelectionLayer(props) {
|
|
|
149
149
|
)
|
|
150
150
|
];
|
|
151
151
|
}
|
|
152
|
-
|
|
152
|
+
// Create base selection layer element
|
|
153
|
+
const selectionElement = (
|
|
153
154
|
<div
|
|
154
155
|
onClick={_onClick}
|
|
155
156
|
title={selectionMessage}
|
|
@@ -168,9 +169,46 @@ function SelectionLayer(props) {
|
|
|
168
169
|
background: color || topLevelColor,
|
|
169
170
|
height
|
|
170
171
|
}}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
></div>
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// Generate mismatch sub-regions if mismatchPositions exist
|
|
176
|
+
let mismatchElements = [];
|
|
177
|
+
if (
|
|
178
|
+
selectionLayer.mismatchPositions &&
|
|
179
|
+
selectionLayer.mismatchPositions.length > 0
|
|
180
|
+
) {
|
|
181
|
+
// Calculate relative mismatch positions within this overlap
|
|
182
|
+
const relativeToOverlap = selectionLayer.mismatchPositions
|
|
183
|
+
.filter(pos => {
|
|
184
|
+
// Adjust position based on overlap's relation to the original selection layer
|
|
185
|
+
const absPos = pos + selectionLayer.start;
|
|
186
|
+
return absPos >= overlap.start && absPos <= overlap.end;
|
|
187
|
+
})
|
|
188
|
+
.map(pos => {
|
|
189
|
+
// Convert to position relative to this overlap segment
|
|
190
|
+
return pos - (overlap.start - selectionLayer.start);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Create a red highlight for each mismatch position
|
|
194
|
+
mismatchElements = relativeToOverlap.map((pos, i) => (
|
|
195
|
+
<div
|
|
196
|
+
key={`${key}-mismatch-${i}`}
|
|
197
|
+
className="veSelectionLayer veMismatchLayer notCaret"
|
|
198
|
+
style={{
|
|
199
|
+
width: charWidth,
|
|
200
|
+
left: leftMargin + xStart + pos * charWidth,
|
|
201
|
+
top: 0,
|
|
202
|
+
height: height || "100%",
|
|
203
|
+
background: "red",
|
|
204
|
+
position: "absolute",
|
|
205
|
+
opacity: 0.5
|
|
206
|
+
}}
|
|
207
|
+
></div>
|
|
208
|
+
));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return [selectionElement, ...mismatchElements, ...caretSvgs];
|
|
174
212
|
});
|
|
175
213
|
} else {
|
|
176
214
|
return null;
|
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
top: -2px;
|
|
7
7
|
position: absolute;
|
|
8
8
|
z-index: 10;
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
justify-content: center;
|
|
12
|
+
font-family: monospace;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.veMismatchLayer {
|
|
16
|
+
z-index: 11; /* Ensure mismatch layers appear on top of normal selection layer */
|
|
9
17
|
}
|
|
10
18
|
|
|
11
19
|
.veCaret {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import { reduxForm, FieldArray } from "redux-form";
|
|
11
11
|
import { anyToJson } from "@teselagen/bio-parsers";
|
|
12
12
|
import { flatMap } from "lodash-es";
|
|
13
|
-
import
|
|
13
|
+
import { nanoid } from "nanoid";
|
|
14
14
|
import { cloneDeep } from "lodash-es";
|
|
15
15
|
import classNames from "classnames";
|
|
16
16
|
|
|
@@ -155,7 +155,7 @@ class AlignmentTool extends React.Component {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
hideModal();
|
|
158
|
-
const alignmentId =
|
|
158
|
+
const alignmentId = nanoid();
|
|
159
159
|
// const alignmentIdMismatches = uniqid();
|
|
160
160
|
createNewAlignment({
|
|
161
161
|
id: alignmentId,
|
package/src/fileUtils.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/* Copyright (C) 2018 TeselaGen Biotechnology, Inc. */
|
|
2
|
+
import { parse } from "papaparse";
|
|
3
|
+
|
|
4
|
+
export const allowedCsvFileTypes = [".csv", ".txt", ".xlsx"];
|
|
5
|
+
|
|
6
|
+
export const isZipFile = file => {
|
|
7
|
+
const type = file.mimetype || file.type;
|
|
8
|
+
return type === "application/zip" || type === "application/x-zip-compressed";
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const getExt = file => file.name.split(".").pop();
|
|
12
|
+
export const isExcelFile = file => getExt(file) === "xlsx";
|
|
13
|
+
export const isCsvFile = file => getExt(file) === "csv";
|
|
14
|
+
export const isTextFile = file => ["text", "txt"].includes(getExt(file));
|
|
15
|
+
|
|
16
|
+
const defaultCsvParserOptions = {
|
|
17
|
+
header: true,
|
|
18
|
+
skipEmptyLines: "greedy",
|
|
19
|
+
trimHeaders: true
|
|
20
|
+
// delimiter: ","
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const parseCsvFile = (csvFile, parserOptions = {}) => {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
parse(csvFile.originFileObj, {
|
|
26
|
+
...defaultCsvParserOptions,
|
|
27
|
+
complete: results => {
|
|
28
|
+
if (results && results.errors && results.errors.length) {
|
|
29
|
+
return reject("Error in csv: " + JSON.stringify(results.errors));
|
|
30
|
+
}
|
|
31
|
+
resolve(results);
|
|
32
|
+
},
|
|
33
|
+
error: error => {
|
|
34
|
+
reject(error);
|
|
35
|
+
},
|
|
36
|
+
...parserOptions
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const parseCsvString = (csvString, parserOptions = {}) => {
|
|
42
|
+
return parse(csvString, { ...defaultCsvParserOptions, ...parserOptions });
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const cleanCommaSeparatedCell = cellData =>
|
|
46
|
+
(cellData || "")
|
|
47
|
+
.split(",")
|
|
48
|
+
.map(n => n.trim())
|
|
49
|
+
.filter(n => n);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Because the csv rows might not have the same header keys in some cases (extended properties)
|
|
53
|
+
* this function will make sure that each row will have all headers so that the export
|
|
54
|
+
* does not drop fields
|
|
55
|
+
* @param {*} rows
|
|
56
|
+
*/
|
|
57
|
+
export const cleanCsvExport = rows => {
|
|
58
|
+
const allHeaders = [];
|
|
59
|
+
rows.forEach(row => {
|
|
60
|
+
Object.keys(row).forEach(header => {
|
|
61
|
+
if (!allHeaders.includes(header)) {
|
|
62
|
+
allHeaders.push(header);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
rows.forEach(row => {
|
|
67
|
+
allHeaders.forEach(header => {
|
|
68
|
+
row[header] = row[header] || "";
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
return rows;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const validateCSVRequiredHeaders = (
|
|
75
|
+
fields,
|
|
76
|
+
requiredHeaders,
|
|
77
|
+
filename
|
|
78
|
+
) => {
|
|
79
|
+
const missingRequiredHeaders = requiredHeaders.filter(field => {
|
|
80
|
+
return !fields.includes(field);
|
|
81
|
+
});
|
|
82
|
+
if (missingRequiredHeaders.length) {
|
|
83
|
+
const name = filename ? `The file ${filename}` : "CSV file";
|
|
84
|
+
return `${name} is missing required headers. (${missingRequiredHeaders.join(
|
|
85
|
+
", "
|
|
86
|
+
)})`;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const validateCSVRow = (row, requiredHeaders, index) => {
|
|
91
|
+
const missingRequiredFields = requiredHeaders.filter(field => !row[field]);
|
|
92
|
+
if (missingRequiredFields.length) {
|
|
93
|
+
if (missingRequiredFields.length === 1) {
|
|
94
|
+
return `Row ${index + 1} is missing the required field "${
|
|
95
|
+
missingRequiredFields[0]
|
|
96
|
+
}"`;
|
|
97
|
+
} else {
|
|
98
|
+
return `Row ${
|
|
99
|
+
index + 1
|
|
100
|
+
} is missing these required fields: ${missingRequiredFields.join(", ")}`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
3
|
|
|
4
4
|
import { reduxForm } from "redux-form";
|
|
5
5
|
|
|
@@ -58,7 +58,7 @@ class MergeFeaturesDialog extends React.Component {
|
|
|
58
58
|
upsertFeature(
|
|
59
59
|
{
|
|
60
60
|
...feat1,
|
|
61
|
-
id:
|
|
61
|
+
id: nanoid(),
|
|
62
62
|
start: start - 1,
|
|
63
63
|
end: end - 1,
|
|
64
64
|
name
|
package/src/redux/alignments.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
condensePairwiseAlignmentDifferences
|
|
4
4
|
} from "@teselagen/sequence-utils";
|
|
5
5
|
import { convertBasePosTraceToPerBpTrace } from "@teselagen/bio-parsers";
|
|
6
|
-
import
|
|
6
|
+
import { nanoid } from "nanoid";
|
|
7
7
|
|
|
8
8
|
import addDashesForMatchStartAndEndForTracks from "./utils/addDashesForMatchStartAndEndForTracks";
|
|
9
9
|
|
|
@@ -149,7 +149,7 @@ export default (state = {}, { payload = {}, type }) => {
|
|
|
149
149
|
if (type === "UPSERT_ALIGNMENT_RUN") {
|
|
150
150
|
const { id } = payload;
|
|
151
151
|
const payloadToUse = {
|
|
152
|
-
stateTrackingId: state[id]?.stateTrackingId ?
|
|
152
|
+
stateTrackingId: state[id]?.stateTrackingId ? nanoid() : "initialLoadId",
|
|
153
153
|
alignmentType: state[id]?.alignmentType,
|
|
154
154
|
...payload,
|
|
155
155
|
//assign default visibilities
|
package/src/redux/findTool.js
CHANGED
|
@@ -13,6 +13,7 @@ export const updateAmbiguousOrLiteral = createAction(
|
|
|
13
13
|
"updateAmbiguousOrLiteral"
|
|
14
14
|
);
|
|
15
15
|
export const updateDnaOrAA = createAction("updateDnaOrAA");
|
|
16
|
+
export const updateMismatchesAllowed = createAction("updateMismatchesAllowed");
|
|
16
17
|
export const updateMatchNumber = createAction("updateMatchNumber");
|
|
17
18
|
|
|
18
19
|
// ------------------------------------
|
|
@@ -53,6 +54,13 @@ export default createMergedDefaultStateReducer(
|
|
|
53
54
|
dnaOrAA: payload
|
|
54
55
|
};
|
|
55
56
|
},
|
|
57
|
+
[updateMismatchesAllowed]: (state, payload) => {
|
|
58
|
+
return {
|
|
59
|
+
...state,
|
|
60
|
+
matchNumber: 0,
|
|
61
|
+
mismatchesAllowed: payload
|
|
62
|
+
};
|
|
63
|
+
},
|
|
56
64
|
[updateSearchText]: (state, payload) => {
|
|
57
65
|
return {
|
|
58
66
|
...state,
|
|
@@ -74,6 +82,7 @@ export default createMergedDefaultStateReducer(
|
|
|
74
82
|
dnaOrAA: "DNA",
|
|
75
83
|
ambiguousOrLiteral: "LITERAL",
|
|
76
84
|
highlightAll: false,
|
|
85
|
+
mismatchesAllowed: 0,
|
|
77
86
|
matchNumber: 0
|
|
78
87
|
}
|
|
79
88
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import deepEqual from "deep-equal";
|
|
2
2
|
import { tidyUpSequenceData } from "@teselagen/sequence-utils";
|
|
3
|
-
import
|
|
3
|
+
import { nanoid } from "nanoid";
|
|
4
4
|
|
|
5
5
|
import createAction from "../utils/createMetaAction";
|
|
6
6
|
import features from "./features";
|
|
@@ -75,7 +75,7 @@ export default function (state, action) {
|
|
|
75
75
|
return {
|
|
76
76
|
// ...cloneDeep(newState),
|
|
77
77
|
...newState,
|
|
78
|
-
stateTrackingId: newState.stateTrackingId ?
|
|
78
|
+
stateTrackingId: newState.stateTrackingId ? nanoid() : "initialLoadId"
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
81
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import omit from "lodash/omit";
|
|
2
|
-
import
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
3
|
|
|
4
4
|
// ------------------------------------
|
|
5
5
|
// Reducer
|
|
@@ -10,7 +10,7 @@ export default function upsertDeleteActionGenerator(
|
|
|
10
10
|
) {
|
|
11
11
|
return {
|
|
12
12
|
[upsertAction]: (state, payload) => {
|
|
13
|
-
const idToUse = payload.id ||
|
|
13
|
+
const idToUse = payload.id || nanoid();
|
|
14
14
|
return {
|
|
15
15
|
...state,
|
|
16
16
|
[idToUse]: { ...(state[idToUse] || {}), ...payload, id: idToUse }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { nanoid } from "nanoid";
|
|
2
2
|
import circularSelector from "./circularSelector";
|
|
3
3
|
import sequenceSelector from "./sequenceSelector";
|
|
4
4
|
import restrictionEnzymesSelector from "./restrictionEnzymesSelector";
|
|
@@ -20,7 +20,7 @@ function cutsitesSelector(sequence, circular, enzymeList, cutsiteLabelColors) {
|
|
|
20
20
|
const cutsitesForEnzyme = cutsitesByName[enzymeName];
|
|
21
21
|
cutsitesForEnzyme.forEach(function (cutsite) {
|
|
22
22
|
const numberOfCuts = cutsitesByName[enzymeName].length;
|
|
23
|
-
const uniqueId =
|
|
23
|
+
const uniqueId = nanoid();
|
|
24
24
|
cutsite.id = uniqueId;
|
|
25
25
|
cutsite.numberOfCuts = numberOfCuts;
|
|
26
26
|
cutsite.annotationType = "cutsite";
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
findSequenceMatches,
|
|
3
|
+
findApproxMatches
|
|
4
|
+
} from "@teselagen/sequence-utils";
|
|
2
5
|
import sequenceSelector from "./sequenceSelector";
|
|
3
6
|
import { createSelector } from "reselect";
|
|
4
7
|
import circularSelector from "./circularSelector";
|
|
@@ -11,7 +14,8 @@ function searchLayersSelector(
|
|
|
11
14
|
ambiguousOrLiteral,
|
|
12
15
|
dnaOrAA,
|
|
13
16
|
isProtein,
|
|
14
|
-
proteinSequence
|
|
17
|
+
proteinSequence,
|
|
18
|
+
mismatchesAllowed
|
|
15
19
|
) {
|
|
16
20
|
if (!searchString || !isOpen) {
|
|
17
21
|
return [];
|
|
@@ -40,6 +44,39 @@ function searchLayersSelector(
|
|
|
40
44
|
end: end * 3 + 2
|
|
41
45
|
}));
|
|
42
46
|
}
|
|
47
|
+
|
|
48
|
+
// Use findApproxMatches when literal matching DNA with mismatches allowed
|
|
49
|
+
if (
|
|
50
|
+
dnaOrAA === "DNA" &&
|
|
51
|
+
ambiguousOrLiteral === "LITERAL" &&
|
|
52
|
+
mismatchesAllowed > 0
|
|
53
|
+
) {
|
|
54
|
+
const approxMatches = findApproxMatches(
|
|
55
|
+
searchString,
|
|
56
|
+
sequence,
|
|
57
|
+
mismatchesAllowed,
|
|
58
|
+
isCircular
|
|
59
|
+
);
|
|
60
|
+
// Convert approximate matches to the format expected by the application
|
|
61
|
+
const matches = approxMatches
|
|
62
|
+
.map(match => ({
|
|
63
|
+
start: match.index,
|
|
64
|
+
end: match.index + match.match.length - 1,
|
|
65
|
+
matchedString: match.match,
|
|
66
|
+
mismatchPositions: match.mismatchPositions,
|
|
67
|
+
numMismatches: match.numMismatches,
|
|
68
|
+
isSearchLayer: true,
|
|
69
|
+
forward: true
|
|
70
|
+
}))
|
|
71
|
+
.sort((a, b) => a.start - b.start);
|
|
72
|
+
|
|
73
|
+
return matches.map(match => ({
|
|
74
|
+
...match,
|
|
75
|
+
className: "veSearchLayer"
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Use regular findSequenceMatches for all other cases
|
|
43
80
|
const matches = findSequenceMatches(sequence, searchString, {
|
|
44
81
|
isCircular,
|
|
45
82
|
isAmbiguous: ambiguousOrLiteral === "AMBIGUOUS",
|
|
@@ -67,5 +104,6 @@ export default createSelector(
|
|
|
67
104
|
state => state.findTool && state.findTool.dnaOrAA,
|
|
68
105
|
state => state.sequenceData.isProtein,
|
|
69
106
|
state => state.sequenceData.proteinSequence,
|
|
107
|
+
state => state.findTool && state.findTool.mismatchesAllowed,
|
|
70
108
|
searchLayersSelector
|
|
71
109
|
);
|