@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teselagen/ove",
3
- "version": "0.7.29",
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.31",
15
- "@teselagen/range-utils": "0.3.13",
16
- "@teselagen/ui": "0.8.6-beta.23",
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.28",
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",
@@ -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;
@@ -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";
@@ -89,9 +89,14 @@
89
89
  border-right-color: yellow !important;
90
90
  }
91
91
  .veSearchLayerActive {
92
- stroke: red !important;
93
- fill: red !important;
94
- background: red !important;
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: "";
@@ -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
- return [
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
- ...caretSvgs
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 {
@@ -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
+ };
@@ -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 { findSequenceMatches } from "@teselagen/sequence-utils";
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
  );