@teselagen/ove 0.7.3-beta.7 → 0.7.3

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.
@@ -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
- <React.Fragment>
21
+ <>
22
22
  {additionalHeaderEls}
23
23
  <Comp {...rest} />
24
24
  {additionalFooterEls}
25
- </React.Fragment>
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
- const { width, height: heightFromDim } = dimensions;
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
- 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];
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
- const name = nameOrOverride.name || nameOrOverride;
81
- const Comp = nameOrOverride.Comp || allTabs[name];
82
- if (isProtein) {
83
- if (
84
- name === "translations" ||
85
- name === "orfs" ||
86
- name === "primers" ||
87
- name === "cutsites"
88
- ) {
89
- return null;
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
- const title = (() => {
93
- if (nameOrOverride.Comp) return name; //just use the user supplied name because this is a custom panel
94
- if (name === "orfs") return "ORFs";
95
- if (name === "cutsites") return "Cut Sites";
96
- return startCase(name);
97
- })();
98
- return (
99
- <Tab
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
- position: "relative"
134
+ display: "flex",
135
+ width,
136
+ height: heightToUse || 300,
137
+ zIndex: 10,
138
+ padding: 10
139
+ // paddingBottom: '31px',
126
140
  }}
127
141
  >
128
- {closePanelButton}
129
- <div
130
- className="ve-propertiesPanel"
131
- style={{
132
- display: "flex",
133
- width,
134
- height: heightToUse || 300,
135
- zIndex: 10,
136
- padding: 10
137
- // paddingBottom: '31px',
138
- }}
139
- >
140
- {propertiesTabs.length ? (
141
- <Tabs
142
- style={{ width }}
143
- renderActiveTabPanelOnly
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
- tgFormValues
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
- class RemoveDuplicatesDialog extends React.Component {
21
- state = {
22
- dups: []
23
- };
24
- componentDidMount() {
25
- this.recomputeDups();
26
- }
31
+ const { selectedEntities } = useTableEntities(dataTableFormName);
27
32
 
28
- checkboxStyle = { marginTop: 0, marginBottom: 0 };
33
+ const ignoreName = useFormValue(dialogFormName, "ignoreName");
34
+ const ignoreStartAndEnd = useFormValue(dialogFormName, "ignoreStartAndEnd");
35
+ const ignoreStrand = useFormValue(dialogFormName, "ignoreStrand");
29
36
 
30
- delayedRecomputeDups = () => {
31
- setTimeout(() => {
32
- this.recomputeDups();
33
- });
34
- };
35
- recomputeDups = () => {
36
- const {
37
- // hideModal,
38
- type,
39
- sequenceData = { sequence: "" },
40
- // handleSubmit,
41
- sequenceLength,
42
- ignoreName,
43
- ignoreStrand,
44
- ignoreStartAndEnd
45
- // circular,
46
- // upsertFeature
47
- } = this.props;
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
- const annotations = sequenceData[type];
50
- const dups = [];
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
- const selectedIds = this.state.dups.map(d => d.id);
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
- const schema = {
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(this.props.isProtein),
82
+ sizeSchema(isProtein),
89
83
  { path: "strand", type: "string" }
90
84
  ]
91
- };
92
- // const sequenceLength = sequenceData.sequence.length;
93
- // const isCirc = (this.state || {}).circular;
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
- </div>
100
- })} */}
101
- <DataTable
102
- noPadding
103
- withCheckboxes
104
- noFullscreenButton
105
- // onRowSelect={this.onRowSelect}
106
- maxHeight={400}
107
- selectedIds={selectedIds}
108
- formName="duplicatesToRemove"
109
- noRouter
110
- noRowsFoundMessage="No duplicates found"
111
- compact
112
- noHeader
113
- noFooter
114
- withSearch={false}
115
- hideSelectedCount
116
- isInfinite
117
- schema={schema}
118
- entities={this.state.dups}
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
- <div
121
- style={{
122
- marginTop: 10,
123
- display: "flex",
124
- justifyContent: "space-between"
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
- <Popover
128
- target={<Button icon="settings" />}
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);
@@ -0,0 +1,7 @@
1
+ /* Copyright (C) 2018 TeselaGen Biotechnology, Inc. */
2
+ import { useSelector } from "react-redux";
3
+ import { get } from "lodash";
4
+
5
+ export const useFormValue = (formName, field) => {
6
+ return useSelector(state => get(state.form?.[formName]?.values, field));
7
+ };
@@ -600,8 +600,8 @@ function VectorInteractionHOC(Component /* options */) {
600
600
  const makeTextCopyable = (transformFunc, className, action = "copy") => {
601
601
  document.querySelectorAll(`.${className}`).forEach(element => {
602
602
  element.addEventListener("click", async () => {
603
- const { selectionLayer, editorName, store } = this.props;
604
- const { sequenceData, copyOptions } =
603
+ const { editorName, store } = this.props;
604
+ const { sequenceData, copyOptions, selectionLayer } =
605
605
  store.getState().VectorEditor[editorName];
606
606
 
607
607
  const selectedSeqData = getSequenceDataBetweenRange(
@@ -621,6 +621,7 @@ function VectorInteractionHOC(Component /* options */) {
621
621
  }
622
622
  }
623
623
  );
624
+
624
625
  const sequenceDataToCopy = transformFunc(
625
626
  selectedSeqData,
626
627
  sequenceData
@@ -632,20 +633,24 @@ function VectorInteractionHOC(Component /* options */) {
632
633
 
633
634
  try {
634
635
  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
636
  } catch (err) {
644
- console.error(`${action} failed:`, err);
637
+ console.error("Failed to copy text:", err);
638
+ }
639
+
640
+ if (action === "copy") {
641
+ document.body.addEventListener("copy", this.handleCopy);
642
+ } else {
643
+ document.body.addEventListener("cut", this.handleCut);
644
+ }
645
+
646
+ if (window.Cypress) {
647
+ window.Cypress.textToCopy = sequenceDataToCopy.textToCopy;
648
+ window.Cypress.seqDataToCopy = sequenceDataToCopy;
645
649
  }
646
650
  });
647
651
  });
648
652
  };
653
+
649
654
  const aaCopy = {
650
655
  text: "Copy AA Sequence",
651
656
  className: "openVeCopyAA",
@@ -0,0 +1 @@
1
+ export function useFormValue(formName: any, field: any): any;