@teselagen/ove 0.7.29 → 0.7.30-beta.1
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 +251 -126
- package/index.es.js +251 -126
- package/index.umd.js +251 -126
- package/ove.css +16 -3
- package/package.json +5 -5
- package/redux/findTool.d.ts +1 -0
- package/selectors/searchLayersSelector.d.ts +1 -1
- package/src/AutoAnnotate.js +1 -1
- package/src/CreateAnnotationsPage.js +1 -2
- package/src/Editor/style.css +8 -3
- package/src/FindBar/index.js +32 -1
- package/src/RowItem/SelectionLayer/index.js +42 -4
- package/src/RowItem/SelectionLayer/style.css +8 -0
- package/src/fileUtils.js +103 -0
- package/src/redux/findTool.js +9 -0
- package/src/selectors/searchLayersSelector.js +40 -2
- package/style.css +12107 -0
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.1",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
"./*": "./*"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@teselagen/sequence-utils": "0.3.
|
|
15
|
-
"@teselagen/range-utils": "0.3.
|
|
16
|
-
"@teselagen/ui": "0.8.6-beta.
|
|
14
|
+
"@teselagen/sequence-utils": "0.3.32-beta.1",
|
|
15
|
+
"@teselagen/range-utils": "0.3.14-beta.1",
|
|
16
|
+
"@teselagen/ui": "0.8.6-beta.24",
|
|
17
17
|
"@teselagen/file-utils": "0.3.20",
|
|
18
|
-
"@teselagen/bio-parsers": "0.4.
|
|
18
|
+
"@teselagen/bio-parsers": "0.4.29-beta.1",
|
|
19
19
|
"@blueprintjs/core": "3.54.0",
|
|
20
20
|
"@hello-pangea/dnd": "16.2.0",
|
|
21
21
|
"@risingstack/react-easy-state": "^6.3.0",
|
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;
|
|
@@ -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;
|
package/src/AutoAnnotate.js
CHANGED
|
@@ -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
|
|
|
@@ -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
|
: [
|
|
@@ -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 {
|
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
|
+
};
|
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,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
|
);
|