@teselagen/ove 0.7.3-beta.7 → 0.7.4
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/AlignmentView/index.d.ts +4 -0
- package/DigestTool/DigestTool.d.ts +1 -1
- package/helperComponents/PropertiesDialog/index.d.ts +2 -7
- package/index.cjs.js +7219 -581
- package/index.es.js +7208 -570
- package/index.umd.js +7216 -560
- package/package.json +3 -2
- package/src/AlignmentView/index.js +45 -83
- package/src/CreateAnnotationsPage.js +1 -1
- package/src/DigestTool/DigestTool.js +78 -75
- package/src/GlobalDialogUtils.js +1 -0
- package/src/helperComponents/PropertiesDialog/CutsiteProperties.js +126 -135
- package/src/helperComponents/PropertiesDialog/SingleEnzymeCutsiteInfo.js +59 -51
- package/src/helperComponents/PropertiesDialog/index.js +115 -114
- package/src/helperComponents/RemoveDuplicates/index.js +144 -160
- package/src/utils/useFormValue.js +7 -0
- package/src/withEditorInteractions/index.js +18 -20
- package/utils/useFormValue.d.ts +1 -0
|
@@ -18,11 +18,11 @@ import { pick } from "lodash-es";
|
|
|
18
18
|
const PropertiesContainer = Comp => props => {
|
|
19
19
|
const { additionalFooterEls, additionalHeaderEls, ...rest } = props;
|
|
20
20
|
return (
|
|
21
|
-
|
|
21
|
+
<>
|
|
22
22
|
{additionalHeaderEls}
|
|
23
23
|
<Comp {...rest} />
|
|
24
24
|
{additionalFooterEls}
|
|
25
|
-
|
|
25
|
+
</>
|
|
26
26
|
);
|
|
27
27
|
};
|
|
28
28
|
const allTabs = {
|
|
@@ -35,129 +35,130 @@ const allTabs = {
|
|
|
35
35
|
orfs: PropertiesContainer(OrfProperties),
|
|
36
36
|
genbank: PropertiesContainer(GenbankView)
|
|
37
37
|
};
|
|
38
|
-
export class PropertiesDialog extends React.Component {
|
|
39
|
-
render() {
|
|
40
|
-
const {
|
|
41
|
-
propertiesTool = {},
|
|
42
|
-
propertiesViewTabUpdate,
|
|
43
|
-
dimensions = {},
|
|
44
|
-
height,
|
|
45
|
-
editorName,
|
|
46
|
-
onSave,
|
|
47
|
-
showReadOnly,
|
|
48
|
-
showAvailability,
|
|
49
|
-
isProtein,
|
|
50
|
-
annotationsToSupport = {},
|
|
51
|
-
disableSetReadOnly,
|
|
52
|
-
propertiesList = [
|
|
53
|
-
"general",
|
|
54
|
-
"features",
|
|
55
|
-
"parts",
|
|
56
|
-
"primers",
|
|
57
|
-
"translations",
|
|
58
|
-
"cutsites",
|
|
59
|
-
"orfs",
|
|
60
|
-
"genbank"
|
|
61
|
-
],
|
|
62
|
-
closePanelButton
|
|
63
|
-
} = { ...this.props, ...this.props.PropertiesProps };
|
|
64
38
|
|
|
65
|
-
|
|
39
|
+
export const PropertiesDialog = props => {
|
|
40
|
+
const {
|
|
41
|
+
propertiesTool = {},
|
|
42
|
+
propertiesViewTabUpdate,
|
|
43
|
+
dimensions = {},
|
|
44
|
+
height,
|
|
45
|
+
editorName,
|
|
46
|
+
onSave,
|
|
47
|
+
showReadOnly,
|
|
48
|
+
showAvailability,
|
|
49
|
+
isProtein,
|
|
50
|
+
annotationsToSupport = {},
|
|
51
|
+
disableSetReadOnly,
|
|
52
|
+
propertiesList = [
|
|
53
|
+
"general",
|
|
54
|
+
"features",
|
|
55
|
+
"parts",
|
|
56
|
+
"primers",
|
|
57
|
+
"translations",
|
|
58
|
+
"cutsites",
|
|
59
|
+
"orfs",
|
|
60
|
+
"genbank"
|
|
61
|
+
],
|
|
62
|
+
closePanelButton
|
|
63
|
+
} = { ...props, ...props.PropertiesProps };
|
|
66
64
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
tabId
|
|
65
|
+
const { width, height: heightFromDim } = dimensions;
|
|
66
|
+
|
|
67
|
+
let { tabId, selectedAnnotationId } = propertiesTool;
|
|
68
|
+
if (
|
|
69
|
+
propertiesList
|
|
70
|
+
.map(nameOrOverride => nameOrOverride.name || nameOrOverride)
|
|
71
|
+
.indexOf(tabId) === -1
|
|
72
|
+
) {
|
|
73
|
+
tabId = propertiesList[0].name || propertiesList[0];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const propertiesTabs = flatMap(propertiesList, nameOrOverride => {
|
|
77
|
+
if (annotationsToSupport[nameOrOverride] === false) {
|
|
78
|
+
return [];
|
|
74
79
|
}
|
|
75
|
-
const propertiesTabs = flatMap(propertiesList, nameOrOverride => {
|
|
76
|
-
if (annotationsToSupport[nameOrOverride] === false) {
|
|
77
|
-
return [];
|
|
78
|
-
}
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
81
|
+
const name = nameOrOverride.name || nameOrOverride;
|
|
82
|
+
const Comp = nameOrOverride.Comp || allTabs[name];
|
|
83
|
+
if (isProtein) {
|
|
84
|
+
if (
|
|
85
|
+
name === "translations" ||
|
|
86
|
+
name === "orfs" ||
|
|
87
|
+
name === "primers" ||
|
|
88
|
+
name === "cutsites"
|
|
89
|
+
) {
|
|
90
|
+
return null;
|
|
91
91
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
key={name}
|
|
101
|
-
title={title}
|
|
102
|
-
id={name}
|
|
103
|
-
panel={
|
|
104
|
-
<Comp
|
|
105
|
-
{...{
|
|
106
|
-
...pick(this.props, userDefinedHandlersAndOpts),
|
|
107
|
-
editorName,
|
|
108
|
-
onSave,
|
|
109
|
-
isProtein,
|
|
110
|
-
showReadOnly,
|
|
111
|
-
showAvailability,
|
|
112
|
-
disableSetReadOnly,
|
|
113
|
-
selectedAnnotationId,
|
|
114
|
-
...(nameOrOverride.name && nameOrOverride)
|
|
115
|
-
}}
|
|
116
|
-
/>
|
|
117
|
-
}
|
|
118
|
-
/>
|
|
119
|
-
);
|
|
120
|
-
});
|
|
121
|
-
const heightToUse = Math.max(0, Number((heightFromDim || height) - 30));
|
|
92
|
+
}
|
|
93
|
+
const title = (() => {
|
|
94
|
+
if (nameOrOverride.Comp) return name; //just use the user supplied name because this is a custom panel
|
|
95
|
+
if (name === "orfs") return "ORFs";
|
|
96
|
+
if (name === "cutsites") return "Cut Sites";
|
|
97
|
+
return startCase(name);
|
|
98
|
+
})();
|
|
99
|
+
|
|
122
100
|
return (
|
|
101
|
+
<Tab
|
|
102
|
+
key={name}
|
|
103
|
+
title={title}
|
|
104
|
+
id={name}
|
|
105
|
+
panel={
|
|
106
|
+
<Comp
|
|
107
|
+
{...{
|
|
108
|
+
...pick(props, userDefinedHandlersAndOpts),
|
|
109
|
+
editorName,
|
|
110
|
+
onSave,
|
|
111
|
+
isProtein,
|
|
112
|
+
showReadOnly,
|
|
113
|
+
showAvailability,
|
|
114
|
+
disableSetReadOnly,
|
|
115
|
+
selectedAnnotationId,
|
|
116
|
+
...(nameOrOverride.name && nameOrOverride)
|
|
117
|
+
}}
|
|
118
|
+
/>
|
|
119
|
+
}
|
|
120
|
+
/>
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
const heightToUse = Math.max(0, Number((heightFromDim || height) - 30));
|
|
124
|
+
return (
|
|
125
|
+
<div
|
|
126
|
+
style={{
|
|
127
|
+
position: "relative"
|
|
128
|
+
}}
|
|
129
|
+
>
|
|
130
|
+
{closePanelButton}
|
|
123
131
|
<div
|
|
132
|
+
className="ve-propertiesPanel"
|
|
124
133
|
style={{
|
|
125
|
-
|
|
134
|
+
display: "flex",
|
|
135
|
+
width,
|
|
136
|
+
height: heightToUse || 300,
|
|
137
|
+
zIndex: 10,
|
|
138
|
+
padding: 10
|
|
139
|
+
// paddingBottom: '31px',
|
|
126
140
|
}}
|
|
127
141
|
>
|
|
128
|
-
{
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
{
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
selectedTabId={tabId}
|
|
145
|
-
onChange={propertiesViewTabUpdate}
|
|
146
|
-
>
|
|
147
|
-
<Tabs.Expander />
|
|
148
|
-
{propertiesTabs}
|
|
149
|
-
<Tabs.Expander />
|
|
150
|
-
</Tabs>
|
|
151
|
-
) : (
|
|
152
|
-
<div style={{ margin: 20, fontSize: 20 }}>
|
|
153
|
-
No Properties to display
|
|
154
|
-
</div>
|
|
155
|
-
)}
|
|
156
|
-
</div>
|
|
142
|
+
{propertiesTabs.length ? (
|
|
143
|
+
<Tabs
|
|
144
|
+
style={{ width }}
|
|
145
|
+
renderActiveTabPanelOnly
|
|
146
|
+
selectedTabId={tabId}
|
|
147
|
+
onChange={propertiesViewTabUpdate}
|
|
148
|
+
>
|
|
149
|
+
<Tabs.Expander />
|
|
150
|
+
{propertiesTabs}
|
|
151
|
+
<Tabs.Expander />
|
|
152
|
+
</Tabs>
|
|
153
|
+
) : (
|
|
154
|
+
<div style={{ margin: 20, fontSize: 20 }}>
|
|
155
|
+
No Properties to display
|
|
156
|
+
</div>
|
|
157
|
+
)}
|
|
157
158
|
</div>
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
161
162
|
|
|
162
163
|
export default compose(
|
|
163
164
|
connectToEditor(({ propertiesTool, annotationsToSupport }) => {
|
|
@@ -1,194 +1,178 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useCallback, useMemo, useState } from "react";
|
|
2
2
|
import { reduxForm } from "redux-form";
|
|
3
|
-
|
|
4
3
|
import {
|
|
5
4
|
wrapDialog,
|
|
6
5
|
DataTable,
|
|
7
|
-
withSelectedEntities,
|
|
8
6
|
SwitchField,
|
|
9
|
-
|
|
7
|
+
useTableEntities
|
|
10
8
|
} from "@teselagen/ui";
|
|
11
9
|
import { compose } from "redux";
|
|
12
10
|
import { Button, Classes, Popover } from "@blueprintjs/core";
|
|
13
11
|
import classNames from "classnames";
|
|
14
|
-
|
|
15
12
|
import withEditorProps from "../../withEditorProps";
|
|
16
13
|
import { forEach, camelCase, startCase } from "lodash-es";
|
|
17
14
|
import { sizeSchema } from "../PropertiesDialog/utils";
|
|
18
15
|
import { getRangeLength } from "@teselagen/range-utils";
|
|
16
|
+
import { useFormValue } from "../../utils/useFormValue";
|
|
17
|
+
|
|
18
|
+
const dialogFormName = "RemoveDuplicatesDialog";
|
|
19
|
+
const dataTableFormName = "duplicatesToRemove";
|
|
20
|
+
const checkboxStyle = { marginTop: 0, marginBottom: 0 };
|
|
21
|
+
|
|
22
|
+
const RemoveDuplicatesDialog = props => {
|
|
23
|
+
const {
|
|
24
|
+
type,
|
|
25
|
+
sequenceData = { sequence: "" },
|
|
26
|
+
sequenceLength,
|
|
27
|
+
isProtein,
|
|
28
|
+
hideModal
|
|
29
|
+
} = props;
|
|
19
30
|
|
|
20
|
-
|
|
21
|
-
state = {
|
|
22
|
-
dups: []
|
|
23
|
-
};
|
|
24
|
-
componentDidMount() {
|
|
25
|
-
this.recomputeDups();
|
|
26
|
-
}
|
|
31
|
+
const { selectedEntities } = useTableEntities(dataTableFormName);
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
const ignoreName = useFormValue(dialogFormName, "ignoreName");
|
|
34
|
+
const ignoreStartAndEnd = useFormValue(dialogFormName, "ignoreStartAndEnd");
|
|
35
|
+
const ignoreStrand = useFormValue(dialogFormName, "ignoreStrand");
|
|
29
36
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
37
|
+
const recomputeDups = useCallback(
|
|
38
|
+
values => {
|
|
39
|
+
const ignoreName = values?.ignoreName;
|
|
40
|
+
const ignoreStartAndEnd = values?.ignoreStartAndEnd;
|
|
41
|
+
const ignoreStrand = values?.ignoreStrand;
|
|
42
|
+
const annotations = sequenceData[type];
|
|
43
|
+
const newDups = [];
|
|
44
|
+
const seqsHashByStartEndStrandName = {};
|
|
45
|
+
forEach(annotations, a => {
|
|
46
|
+
const hash = `${ignoreStartAndEnd ? "" : a.start}&${
|
|
47
|
+
ignoreStartAndEnd ? "" : a.end
|
|
48
|
+
}&${ignoreStrand ? "" : a.strand}&${ignoreName ? "" : a.name}`;
|
|
49
|
+
if (seqsHashByStartEndStrandName[hash]) {
|
|
50
|
+
newDups.push({ ...a, size: getRangeLength(a, sequenceLength) });
|
|
51
|
+
} else {
|
|
52
|
+
seqsHashByStartEndStrandName[hash] = true;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return newDups;
|
|
56
|
+
},
|
|
57
|
+
[sequenceData, sequenceLength, type]
|
|
58
|
+
);
|
|
48
59
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const seqsHashByStartEndStrandName = {};
|
|
52
|
-
forEach(annotations, a => {
|
|
53
|
-
const hash = `${ignoreStartAndEnd ? "" : a.start}&${
|
|
54
|
-
ignoreStartAndEnd ? "" : a.end
|
|
55
|
-
}&${ignoreStrand ? "" : a.strand}&${ignoreName ? "" : a.name}`;
|
|
56
|
-
if (seqsHashByStartEndStrandName[hash]) {
|
|
57
|
-
dups.push({ ...a, size: getRangeLength(a, sequenceLength) });
|
|
58
|
-
} else {
|
|
59
|
-
seqsHashByStartEndStrandName[hash] = true;
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
this.setState({ dups });
|
|
63
|
-
};
|
|
64
|
-
render() {
|
|
65
|
-
const { duplicatesToRemoveSelectedEntities, hideModal, type } = this.props;
|
|
60
|
+
const [dups, setDups] = useState(recomputeDups);
|
|
61
|
+
const selectedIds = useMemo(() => dups.map(d => d.id), [dups]);
|
|
66
62
|
|
|
67
|
-
|
|
63
|
+
const fieldSubmit = useCallback(
|
|
64
|
+
(newVal, field) => {
|
|
65
|
+
const values = {
|
|
66
|
+
ignoreName,
|
|
67
|
+
ignoreStartAndEnd,
|
|
68
|
+
ignoreStrand,
|
|
69
|
+
[field]: newVal
|
|
70
|
+
};
|
|
71
|
+
const newDups = recomputeDups(values);
|
|
72
|
+
setDups(newDups);
|
|
73
|
+
},
|
|
74
|
+
[ignoreName, ignoreStartAndEnd, ignoreStrand, recomputeDups]
|
|
75
|
+
);
|
|
68
76
|
|
|
69
|
-
|
|
77
|
+
const schema = useMemo(
|
|
78
|
+
() => ({
|
|
70
79
|
fields: [
|
|
71
|
-
// ...(noColor
|
|
72
|
-
// ? []
|
|
73
|
-
// : [
|
|
74
|
-
// {
|
|
75
|
-
// path: "color",
|
|
76
|
-
// type: "string",
|
|
77
|
-
// render: color => {
|
|
78
|
-
// return (
|
|
79
|
-
// <ColorPickerPopover>
|
|
80
|
-
// <div style={{ height: 20, width: 20, background: color }} />
|
|
81
|
-
// </ColorPickerPopover>
|
|
82
|
-
// );
|
|
83
|
-
// }
|
|
84
|
-
// }
|
|
85
|
-
// ]),
|
|
86
80
|
{ path: "name", type: "string" },
|
|
87
81
|
// ...(noType ? [] : [{ path: "type", type: "string" }]),
|
|
88
|
-
sizeSchema(
|
|
82
|
+
sizeSchema(isProtein),
|
|
89
83
|
{ path: "strand", type: "string" }
|
|
90
84
|
]
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return (
|
|
95
|
-
<div className={classNames(Classes.DIALOG_BODY, "tg-min-width-dialog")}>
|
|
96
|
-
{/* {dups.map((d) => {
|
|
97
|
-
return <div>
|
|
85
|
+
}),
|
|
86
|
+
[isProtein]
|
|
87
|
+
);
|
|
98
88
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
89
|
+
return (
|
|
90
|
+
<div className={classNames(Classes.DIALOG_BODY, "tg-min-width-dialog")}>
|
|
91
|
+
<DataTable
|
|
92
|
+
noPadding
|
|
93
|
+
withCheckboxes
|
|
94
|
+
noFullscreenButton
|
|
95
|
+
maxHeight={400}
|
|
96
|
+
selectedIds={selectedIds}
|
|
97
|
+
formName={dataTableFormName}
|
|
98
|
+
noRouter
|
|
99
|
+
noRowsFoundMessage="No duplicates found"
|
|
100
|
+
compact
|
|
101
|
+
noHeader
|
|
102
|
+
noFooter
|
|
103
|
+
withSearch={false}
|
|
104
|
+
hideSelectedCount
|
|
105
|
+
isInfinite
|
|
106
|
+
schema={schema}
|
|
107
|
+
entities={dups}
|
|
108
|
+
/>
|
|
109
|
+
<div
|
|
110
|
+
style={{
|
|
111
|
+
marginTop: 10,
|
|
112
|
+
display: "flex",
|
|
113
|
+
justifyContent: "space-between"
|
|
114
|
+
}}
|
|
115
|
+
>
|
|
116
|
+
<Popover
|
|
117
|
+
target={<Button icon="settings" />}
|
|
118
|
+
content={
|
|
119
|
+
<div style={{ padding: 20, maxWidth: 250 }}>
|
|
120
|
+
<div>Ignore These Fields While Finding Duplicates:</div>
|
|
121
|
+
<br />
|
|
122
|
+
<SwitchField
|
|
123
|
+
containerStyle={{ marginBottom: 2 }}
|
|
124
|
+
//delay the call to recompute dups until redux has had time to update
|
|
125
|
+
onFieldSubmit={newVal => fieldSubmit(newVal, "ignoreName")}
|
|
126
|
+
style={checkboxStyle}
|
|
127
|
+
name="ignoreName"
|
|
128
|
+
label="Name"
|
|
129
|
+
/>
|
|
130
|
+
<SwitchField
|
|
131
|
+
containerStyle={{ marginBottom: 2 }}
|
|
132
|
+
//delay the call to recompute dups until redux has had time to update
|
|
133
|
+
onFieldSubmit={newVal => fieldSubmit(newVal, "ignoreStrand")}
|
|
134
|
+
style={checkboxStyle}
|
|
135
|
+
name="ignoreStrand"
|
|
136
|
+
label="Strand"
|
|
137
|
+
/>
|
|
138
|
+
<SwitchField
|
|
139
|
+
containerStyle={{ marginBottom: 2 }}
|
|
140
|
+
//delay the call to recompute dups until redux has had time to update
|
|
141
|
+
onFieldSubmit={newVal =>
|
|
142
|
+
fieldSubmit(newVal, "ignoreStartAndEnd")
|
|
143
|
+
}
|
|
144
|
+
style={checkboxStyle}
|
|
145
|
+
name="ignoreStartAndEnd"
|
|
146
|
+
label="Start and End"
|
|
147
|
+
/>
|
|
148
|
+
</div>
|
|
149
|
+
}
|
|
119
150
|
/>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
151
|
+
|
|
152
|
+
<Button
|
|
153
|
+
intent="primary"
|
|
154
|
+
onClick={() => {
|
|
155
|
+
props[camelCase(`delete_${type}`).slice(0, -1)](
|
|
156
|
+
Object.keys(selectedEntities || {})
|
|
157
|
+
);
|
|
158
|
+
window.toastr.success(
|
|
159
|
+
`Successfully Deleted ${
|
|
160
|
+
Object.keys(selectedEntities || {}).length
|
|
161
|
+
} ${startCase(type)}`
|
|
162
|
+
);
|
|
163
|
+
hideModal();
|
|
125
164
|
}}
|
|
165
|
+
disabled={!Object.keys(selectedEntities || {}).length}
|
|
126
166
|
>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
content={
|
|
130
|
-
<div style={{ padding: 20, maxWidth: 250 }}>
|
|
131
|
-
<div>Ignore These Fields While Finding Duplicates:</div>
|
|
132
|
-
<br></br>
|
|
133
|
-
<SwitchField
|
|
134
|
-
containerStyle={{ marginBottom: 2 }}
|
|
135
|
-
//delay the call to recompute dups until redux has had time to update
|
|
136
|
-
onFieldSubmit={this.delayedRecomputeDups}
|
|
137
|
-
style={this.checkboxStyle}
|
|
138
|
-
name="ignoreName"
|
|
139
|
-
label="Name"
|
|
140
|
-
></SwitchField>
|
|
141
|
-
<SwitchField
|
|
142
|
-
containerStyle={{ marginBottom: 2 }}
|
|
143
|
-
//delay the call to recompute dups until redux has had time to update
|
|
144
|
-
onFieldSubmit={this.delayedRecomputeDups}
|
|
145
|
-
style={this.checkboxStyle}
|
|
146
|
-
name="ignoreStrand"
|
|
147
|
-
label="Strand"
|
|
148
|
-
></SwitchField>
|
|
149
|
-
<SwitchField
|
|
150
|
-
containerStyle={{ marginBottom: 2 }}
|
|
151
|
-
//delay the call to recompute dups until redux has had time to update
|
|
152
|
-
onFieldSubmit={this.delayedRecomputeDups}
|
|
153
|
-
style={this.checkboxStyle}
|
|
154
|
-
name="ignoreStartAndEnd"
|
|
155
|
-
label="Start and End"
|
|
156
|
-
></SwitchField>
|
|
157
|
-
</div>
|
|
158
|
-
}
|
|
159
|
-
></Popover>
|
|
160
|
-
|
|
161
|
-
<Button
|
|
162
|
-
intent="primary"
|
|
163
|
-
onClick={() => {
|
|
164
|
-
this.props[camelCase(`delete_${type}`).slice(0, -1)](
|
|
165
|
-
duplicatesToRemoveSelectedEntities.map(d => d.id)
|
|
166
|
-
);
|
|
167
|
-
window.toastr.success(
|
|
168
|
-
`Successfully Deleted ${
|
|
169
|
-
duplicatesToRemoveSelectedEntities.length
|
|
170
|
-
} ${startCase(type)}`
|
|
171
|
-
);
|
|
172
|
-
hideModal();
|
|
173
|
-
}}
|
|
174
|
-
disabled={!(duplicatesToRemoveSelectedEntities || []).length}
|
|
175
|
-
>
|
|
176
|
-
Remove {duplicatesToRemoveSelectedEntities.length} Duplicates
|
|
177
|
-
</Button>
|
|
178
|
-
</div>
|
|
167
|
+
Remove {Object.keys(selectedEntities || {}).length} Duplicates
|
|
168
|
+
</Button>
|
|
179
169
|
</div>
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
};
|
|
183
173
|
|
|
184
174
|
export default compose(
|
|
185
175
|
wrapDialog(),
|
|
186
176
|
withEditorProps,
|
|
187
|
-
|
|
188
|
-
withSelectedEntities("duplicatesToRemove"),
|
|
189
|
-
|
|
190
|
-
reduxForm({
|
|
191
|
-
form: "RemoveDuplicatesDialog"
|
|
192
|
-
}),
|
|
193
|
-
tgFormValues("ignoreName", "ignoreStrand", "ignoreStartAndEnd")
|
|
177
|
+
reduxForm({ form: dialogFormName })
|
|
194
178
|
)(RemoveDuplicatesDialog);
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
getAminoAcidStringFromSequenceString
|
|
5
5
|
} from "@teselagen/sequence-utils";
|
|
6
6
|
import { getSequenceWithinRange } from "@teselagen/range-utils";
|
|
7
|
+
import Clipboard from "clipboard";
|
|
7
8
|
import { compose } from "redux";
|
|
8
9
|
import {
|
|
9
10
|
getReverseComplementSequenceAndAnnotations,
|
|
@@ -598,10 +599,16 @@ function VectorInteractionHOC(Component /* options */) {
|
|
|
598
599
|
this.props;
|
|
599
600
|
const { isProtein } = sequenceData;
|
|
600
601
|
const makeTextCopyable = (transformFunc, className, action = "copy") => {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
602
|
+
return new Clipboard(`.${className}`, {
|
|
603
|
+
action: () => action,
|
|
604
|
+
text: () => {
|
|
605
|
+
if (action === "copy") {
|
|
606
|
+
document.body.addEventListener("copy", this.handleCopy);
|
|
607
|
+
} else {
|
|
608
|
+
document.body.addEventListener("cut", this.handleCut);
|
|
609
|
+
}
|
|
610
|
+
const { editorName, store } = this.props;
|
|
611
|
+
const { sequenceData, copyOptions, selectionLayer } =
|
|
605
612
|
store.getState().VectorEditor[editorName];
|
|
606
613
|
|
|
607
614
|
const selectedSeqData = getSequenceDataBetweenRange(
|
|
@@ -627,23 +634,12 @@ function VectorInteractionHOC(Component /* options */) {
|
|
|
627
634
|
);
|
|
628
635
|
this.sequenceDataToCopy = sequenceDataToCopy;
|
|
629
636
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
try {
|
|
634
|
-
await navigator.clipboard.writeText(textToCopy);
|
|
635
|
-
if (action === "cut") {
|
|
636
|
-
// Optionally clear the original text or perform any "cut" related logic
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
if (window.Cypress) {
|
|
640
|
-
window.Cypress.textToCopy = sequenceDataToCopy.textToCopy;
|
|
641
|
-
window.Cypress.seqDataToCopy = sequenceDataToCopy;
|
|
642
|
-
}
|
|
643
|
-
} catch (err) {
|
|
644
|
-
console.error(`${action} failed:`, err);
|
|
637
|
+
if (window.Cypress) {
|
|
638
|
+
window.Cypress.textToCopy = sequenceDataToCopy.textToCopy;
|
|
639
|
+
window.Cypress.seqDataToCopy = sequenceDataToCopy;
|
|
645
640
|
}
|
|
646
|
-
|
|
641
|
+
return sequenceDataToCopy.textToCopy || sequenceDataToCopy.sequence;
|
|
642
|
+
}
|
|
647
643
|
});
|
|
648
644
|
};
|
|
649
645
|
const aaCopy = {
|
|
@@ -664,6 +660,8 @@ function VectorInteractionHOC(Component /* options */) {
|
|
|
664
660
|
}, className);
|
|
665
661
|
}
|
|
666
662
|
};
|
|
663
|
+
// TODO: maybe stop using Clipboard.js and unify clipboard handling with
|
|
664
|
+
// a more versatile approach
|
|
667
665
|
return [
|
|
668
666
|
...(readOnly || disableBpEditing
|
|
669
667
|
? []
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function useFormValue(formName: any, field: any): any;
|