@opencloning/ui 1.0.1 → 1.1.0-dev.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/package.json +12 -7
  3. package/scripts/inject-version.js +17 -0
  4. package/scripts/reset-version.js +16 -0
  5. package/src/components/DescriptionEditor.jsx +1 -2
  6. package/src/components/DragAndDropCloningHistoryWrapper.jsx +1 -1
  7. package/src/components/ExternalServicesStatusCheck.jsx +2 -1
  8. package/src/components/MainSequenceCheckBox.jsx +2 -5
  9. package/src/components/NetworkNode.jsx +1 -3
  10. package/src/components/OpenCloning.jsx +3 -3
  11. package/src/components/assembler/AssemblePartWidget.jsx +1 -1
  12. package/src/components/assembler/Assembler.jsx +1 -2
  13. package/src/components/dummy/DummyInterface.js +1 -2
  14. package/src/components/eLabFTW/eLabFTWInterface.js +1 -2
  15. package/src/components/form/EnzymeMultiSelect.cy.jsx +20 -9
  16. package/src/components/form/GetRequestMultiSelect.jsx +1 -3
  17. package/src/components/form/LabelWithTooltip.jsx +1 -1
  18. package/src/components/form/PostRequestSelect.jsx +1 -3
  19. package/src/components/index.js +2 -0
  20. package/src/components/navigation/ButtonWithMenu.jsx +1 -3
  21. package/src/components/navigation/MainAppBar.jsx +2 -9
  22. package/src/components/navigation/SelectTemplateDialog.jsx +1 -1
  23. package/src/components/primers/PrimerForm.jsx +2 -4
  24. package/src/components/primers/PrimerList.cy.jsx +86 -78
  25. package/src/components/primers/PrimerList.jsx +1 -1
  26. package/src/components/primers/PrimerTableRow.cy.jsx +31 -18
  27. package/src/components/primers/PrimerTableRow.jsx +1 -4
  28. package/src/components/primers/SelectPrimerForm.jsx +1 -1
  29. package/src/components/primers/import_primers/ImportPrimersButton.jsx +1 -4
  30. package/src/components/primers/import_primers/ImportPrimersTable.jsx +2 -5
  31. package/src/components/primers/import_primers/PrimerDatabaseImportForm.jsx +1 -2
  32. package/src/components/primers/primer_design/SequenceTabComponents/CollapsableLabel.jsx +1 -1
  33. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignForm.jsx +1 -1
  34. package/src/components/primers/primer_design/SequenceTabComponents/RestrictionSpacerForm.jsx +1 -1
  35. package/src/components/primers/primer_details/PrimerInfoIcon.jsx +1 -2
  36. package/src/components/settings/SettingsTab.jsx +2 -3
  37. package/src/components/sources/CollectionSource.jsx +1 -1
  38. package/src/components/sources/MultipleOutputsSelector.jsx +1 -2
  39. package/src/components/sources/NewSourceBox.jsx +2 -3
  40. package/src/components/sources/PCRUnitForm.jsx +1 -3
  41. package/src/components/sources/Source.jsx +0 -3
  42. package/src/components/sources/SourceBox.jsx +2 -2
  43. package/src/components/sources/SourceFile.jsx +1 -2
  44. package/src/components/sources/SourceGenomeRegion.cy.jsx +24 -9
  45. package/src/components/sources/SourceGenomeRegion.jsx +1 -6
  46. package/src/components/sources/SourceRepositoryId.jsx +2 -9
  47. package/src/components/sources/SourceTypeSelector.jsx +3 -6
  48. package/src/components/verification/SequencingFileRow.jsx +1 -2
  49. package/src/components/verification/VerificationFileDialog.cy.jsx +35 -8
  50. package/src/hooks/useBackendRoute.js +3 -2
  51. package/src/hooks/useConfig.js +2 -0
  52. package/src/hooks/useDatabase.js +2 -2
  53. package/src/hooks/useHttpClient.js +9 -3
  54. package/src/hooks/useInitializeApp.js +15 -0
  55. package/src/hooks/useUrlParamsLoader.js +149 -0
  56. package/src/index.css +314 -0
  57. package/src/index.js +8 -0
  58. package/src/providers/ConfigProvider.jsx +51 -0
  59. package/src/providers/index.js +3 -0
  60. package/src/version.js +2 -0
  61. package/src/components/sources/KnownSourceErrors.jsx +0 -50
@@ -0,0 +1,149 @@
1
+ import { useEffect } from 'react';
2
+ import { useDispatch } from 'react-redux';
3
+ import { cloningActions } from '@opencloning/store/cloning';
4
+ import useDatabase from './useDatabase';
5
+ import useLoadDatabaseFile from './useLoadDatabaseFile';
6
+ import useAlerts from './useAlerts';
7
+ import useHttpClient from './useHttpClient';
8
+ import useValidateState from './useValidateState';
9
+ import { formatSequenceLocationString, getUrlParameters } from '@opencloning/utils/other';
10
+ import { formatTemplate, loadHistoryFile, loadFilesToSessionStorage } from '@opencloning/utils/readNwrite';
11
+
12
+ const { setState: setCloningState, updateSource } = cloningActions;
13
+
14
+ /**
15
+ * Hook to load sequences from URL parameters
16
+ * Handles various source types: database, example, template, genome_coordinates, locus_tag
17
+ */
18
+ export default function useUrlParamsLoader() {
19
+ const dispatch = useDispatch();
20
+ const database = useDatabase();
21
+ const { addAlert } = useAlerts();
22
+ const setHistoryFileError = (e) => addAlert({ message: e, severity: 'error' });
23
+ const { loadDatabaseFile } = useLoadDatabaseFile({ source: { id: 1 }, sendPostRequest: null, setHistoryFileError });
24
+ const validateState = useValidateState();
25
+ const httpClient = useHttpClient();
26
+
27
+ useEffect(() => {
28
+ async function loadSequenceFromUrlParams() {
29
+ const urlParams = getUrlParameters();
30
+
31
+ if (urlParams.source === 'database') {
32
+ try {
33
+ if (!database) {
34
+ return;
35
+ }
36
+ const { file, databaseId } = await database.loadSequenceFromUrlParams(urlParams);
37
+ loadDatabaseFile(file, databaseId);
38
+ } catch (error) {
39
+ addAlert({
40
+ message: 'Error loading sequence from URL parameters',
41
+ severity: 'error',
42
+ });
43
+ console.error(error);
44
+ }
45
+ } else if (urlParams.source === 'example' && urlParams.example) {
46
+ try {
47
+ const url = `${import.meta.env.BASE_URL}examples/${urlParams.example}`;
48
+ let data;
49
+ if (urlParams.example.endsWith('.zip')) {
50
+ // For zip files, get as blob and process with loadHistoryFile
51
+ const { data: blob } = await httpClient.get(url, { responseType: 'blob' });
52
+ const fileName = urlParams.example;
53
+ // eslint-disable-next-line no-undef
54
+ const file = new File([blob], fileName);
55
+ const { cloningStrategy, verificationFiles } = await loadHistoryFile(file);
56
+ data = await validateState(cloningStrategy);
57
+ await loadFilesToSessionStorage(verificationFiles, 0);
58
+ } else {
59
+ // For JSON files, get as JSON
60
+ const { data: jsonData } = await httpClient.get(url);
61
+ data = await validateState(jsonData);
62
+ }
63
+ dispatch(setCloningState(data));
64
+ } catch (error) {
65
+ addAlert({
66
+ message: 'Error loading example',
67
+ severity: 'error',
68
+ });
69
+ console.error(error);
70
+ }
71
+ } else if (urlParams.source === 'template' && urlParams.template && urlParams.key) {
72
+ try {
73
+ const baseUrl = 'https://assets.opencloning.org/OpenCloning-submission';
74
+ const url = `${baseUrl}/processed/${urlParams.key}/templates/${urlParams.template}`;
75
+ const { data } = await httpClient.get(url);
76
+ const validatedData = await validateState(data);
77
+ const newState = formatTemplate(validatedData, url);
78
+
79
+ dispatch(setCloningState(newState));
80
+ } catch (error) {
81
+ addAlert({
82
+ message: 'Error loading template',
83
+ severity: 'error',
84
+ });
85
+ console.error(error);
86
+ }
87
+ } else if (urlParams.source === 'genome_coordinates') {
88
+ const { sequence_accession, start, end, strand, assembly_accession } = urlParams;
89
+ if (!sequence_accession || !start || !end || !strand) {
90
+ addAlert({
91
+ message: 'Error loading genome sequence from URL parameters',
92
+ severity: 'error',
93
+ });
94
+ return;
95
+ }
96
+ const startNum = Number(start);
97
+ const endNum = Number(end);
98
+ const strandNum = Number(strand);
99
+ let error = '';
100
+ if (isNaN(startNum) || isNaN(endNum)) {
101
+ error = 'Start and end must be numbers';
102
+ }
103
+ else if (![1, -1].includes(strandNum)) {
104
+ error = 'Strand must be 1 or -1';
105
+ }
106
+ else if (startNum < 1) {
107
+ error = 'Start must be greater than zero';
108
+ }
109
+ else if (startNum >= endNum) {
110
+ error = 'End must be greater than start';
111
+ }
112
+ if (error) {
113
+ addAlert({ message: `Error loading genome coordinates from URL parameters: ${error}`, severity: 'error' });
114
+ return;
115
+ }
116
+
117
+ const source = {
118
+ id: 1,
119
+ type: 'KnownGenomeCoordinatesSource',
120
+ assembly_accession,
121
+ repository_id: sequence_accession,
122
+ coordinates: formatSequenceLocationString(start, end, strand),
123
+ }
124
+ dispatch(updateSource(source));
125
+ } else if (urlParams.source === 'locus_tag') {
126
+ const { locus_tag, assembly_accession, padding } = urlParams;
127
+ if (!locus_tag || !assembly_accession) {
128
+ addAlert({
129
+ message: 'Error loading locus tag from URL parameters',
130
+ severity: 'error',
131
+ });
132
+ return;
133
+ }
134
+
135
+ const source = {
136
+ id: 1,
137
+ type: 'KnownGenomeCoordinatesSource',
138
+ assembly_accession,
139
+ locus_tag,
140
+ padding: padding ? Number(padding) : 1000,
141
+ }
142
+ dispatch(updateSource(source));
143
+ }
144
+ }
145
+ loadSequenceFromUrlParams();
146
+ // eslint-disable-next-line react-hooks/exhaustive-deps
147
+ }, []);
148
+ }
149
+
package/src/index.css ADDED
@@ -0,0 +1,314 @@
1
+ button {
2
+ cursor: pointer;
3
+ }
4
+
5
+ .tf-tree .tf-nc, .tf-tree .tf-node-content {
6
+ border: 3px solid;
7
+ border-color: rgb(25, 118, 210);
8
+ }
9
+
10
+ ul.hidden-ancestors {
11
+ display: none;
12
+ }
13
+
14
+ /* So that the eye icon is visible */
15
+ li.hidden-ancestors {
16
+ margin-bottom: 30px;
17
+ }
18
+
19
+ .app-container {
20
+ display: flex;
21
+ flex-direction: column;
22
+ /* height: calc(100vh - 114px - 10px); - moved to component to depend on presence of appbar */
23
+ background-color: white;
24
+ }
25
+
26
+ .tab-panels-container {
27
+ height: 100%;
28
+ overflow: auto;
29
+ }
30
+
31
+ .main-sequence-editor {
32
+ width: 70%;
33
+ margin: auto;
34
+ }
35
+
36
+
37
+ .node-corner {
38
+ position: absolute;
39
+ right: -1px;
40
+ top: 1px;
41
+ display: flex;
42
+ gap: .2em;
43
+ }
44
+
45
+ .overhang-representation {
46
+ display: inline-block;
47
+ margin-top: 10px;
48
+ font-family: monospace;
49
+ font-size: small;
50
+ /* Prevent the removal of spaces that make sequences align correctly */
51
+ white-space: pre;
52
+ max-width: 300px;
53
+ overflow-x: auto;
54
+ }
55
+
56
+ div.before-node {
57
+ position: absolute;
58
+ top: 0;
59
+ left: 50%;
60
+ transform: translate(-50%, -38px);
61
+ z-index: 1;
62
+ }
63
+
64
+ div.before-node svg {
65
+ background-color: white;
66
+ cursor: pointer;
67
+ }
68
+
69
+ div.hang-from-node {
70
+ box-sizing: content-box;
71
+ height: 50px;
72
+ width: 50px;
73
+ position: absolute;
74
+ top: 100%;
75
+ left: 50%;
76
+ transform: translate(-50%, 40px);
77
+ border: 3px solid;
78
+ border-color: rgb(25, 118, 210);
79
+ border-radius: 100%;
80
+ }
81
+
82
+ /* To center the icon vertically */
83
+ div.hang-from-node div {
84
+ margin: 0;
85
+ position: absolute;
86
+ top: 50%;
87
+ left: 50%;
88
+ transform: translate(-50%, -50%);
89
+ }
90
+
91
+ div.hang-from-node::before {
92
+ position: absolute;
93
+ border: 1px solid;
94
+ width: 0;
95
+ height: 30px;
96
+ display: block;
97
+ content: '';
98
+ top: -3%;
99
+ left: 50%;
100
+ margin-left: -1px;
101
+ transform: translate(0, -100%);
102
+ }
103
+
104
+
105
+ /* TODO: handle these */
106
+ div.tf-tree.tf-ancestor-tree {
107
+ padding-bottom: 150px;
108
+ }
109
+
110
+ div.corner-id {
111
+ position: absolute;
112
+ left: 0px;
113
+ top: 0px;
114
+ font-family: sans-serif;
115
+ font-size: large;
116
+ color: rgb(25, 118, 210);
117
+ font-weight: bold;
118
+ }
119
+
120
+ button.github-icon svg {
121
+ padding: 0;
122
+ color: white;
123
+ font-size: 30px;
124
+ }
125
+
126
+ div.collapsed div {
127
+ margin: auto
128
+ }
129
+
130
+ div.collapsed button {
131
+ margin-bottom: 0px;
132
+ margin-top: 0px;
133
+ }
134
+
135
+ /* Prevents scollbar from showing on small editors,
136
+ that was caused by overflowing numbers at the edges
137
+ of the x axis */
138
+
139
+ span.node-text svg.veAxis {
140
+ width: 100%;
141
+ }
142
+
143
+ span.node-text > div:first-child {
144
+ margin: 20px
145
+ }
146
+
147
+ /* Cannot overwrite styles that are hard-coded, but we may want this */
148
+ /* span.node-text div.tg-simple-dna-view {
149
+ overflow: visible;
150
+
151
+ } */
152
+
153
+ body {
154
+ margin: 0px;
155
+ padding: 0px;
156
+ height: 100%;
157
+ overflow: hidden;
158
+ display: flex;
159
+ flex-direction: column;
160
+ }
161
+
162
+ html {
163
+ overflow: hidden;
164
+ }
165
+
166
+ div.select-source div.MuiFormControl-root {
167
+ margin-top: 5px;
168
+ margin-bottom: 5px;
169
+ }
170
+
171
+ div.select-source h2 {
172
+ margin-bottom: 5px;
173
+ font-size: x-large;
174
+ }
175
+
176
+
177
+ div.select-source {
178
+ width: 275px;
179
+ }
180
+
181
+ .fragment-picker {
182
+ margin-top: 5px;
183
+ margin-bottom: 10px;
184
+ text-align: center;
185
+ }
186
+
187
+ div.description-container {
188
+ margin: auto;
189
+ margin-bottom: 2em;
190
+ max-width: 600px;
191
+ }
192
+
193
+ div.description-container p {
194
+ /* Show line breaks */
195
+ white-space: pre-line;
196
+ text-align: left;
197
+ }
198
+
199
+ code {
200
+ background-color: lightgrey;
201
+ padding: 15px;
202
+ border-radius: 5px;
203
+ min-width: 40%;
204
+ box-shadow: inset 0px 0px 0px 3px gray;
205
+ margin-bottom: 30px;
206
+ white-space: pre-wrap;
207
+ display: inline-block;
208
+ text-align: left;
209
+ max-width: 50%;
210
+ }
211
+
212
+ .data-model-displayer {
213
+ text-align: center;
214
+ }
215
+
216
+ .data-model-displayer p {
217
+ font-size: large;
218
+ }
219
+
220
+ a.button-hyperlink {
221
+ text-decoration: none;
222
+ color: white;
223
+ }
224
+
225
+ div.dragging-file {
226
+ text-align: center;
227
+ }
228
+
229
+ .drag-file-wrapper {
230
+ border: 5px dashed;
231
+ border-color: darkgray;
232
+ width: 40%;
233
+ height: 400px;
234
+ padding: 10px;
235
+ margin: auto;
236
+ text-align: center;
237
+ border-radius: 40px;
238
+ }
239
+
240
+ .drag-file-container {
241
+ padding: 10px;
242
+ }
243
+
244
+ .drag-file-container .drag-file-close {
245
+ text-align: right;
246
+ color: red;
247
+ cursor: pointer;
248
+ }
249
+
250
+ .drag-file-container .drag-file-close svg {
251
+ font-size: 40px;
252
+ }
253
+
254
+ .drag-file-container .drag-file-close:hover {
255
+ /* 70% brightness */
256
+ filter: brightness(70%);
257
+ }
258
+
259
+ div.primer-designer {
260
+ margin: auto;
261
+ width: 60%;
262
+ }
263
+
264
+
265
+ div.pcr-unit.MuiAccordion-root:first-of-type {
266
+ border-radius: 1.2em;
267
+ overflow: clip;
268
+ }
269
+
270
+ div.pcr-unit.MuiAccordion-root {
271
+ border-radius: 1.2em;
272
+ overflow: clip;
273
+ margin-bottom: .5em;
274
+ }
275
+
276
+ .pcr-unit div.MuiAccordionSummary-root.MuiAccordionSummary-root {
277
+ min-height: 0;
278
+ }
279
+
280
+ .pcr-unit div.MuiAccordionSummary-content {
281
+ margin-top: .35em;
282
+ margin-bottom: .35em;
283
+ }
284
+
285
+ /* Vertical align the content and icon in alert banners */
286
+ div.MuiPaper-root[role=alert] {
287
+ align-items: center;
288
+ }
289
+
290
+ div#global-error-message-wrapper {
291
+ position: fixed;
292
+ top: 0;
293
+ z-index: 999;
294
+ max-width: 25em;
295
+ }
296
+
297
+ div#global-error-message-wrapper > div {
298
+ margin: 10px;
299
+ /* Allow line breaks in very long strings only when necessary */
300
+ word-break: break-word;
301
+ }
302
+
303
+ .finished-source {
304
+ /* Allow line breaks in very long strings only when necessary */
305
+ word-break: break-word;
306
+ }
307
+
308
+ .tab-panels-container {
309
+ text-align: center;
310
+ }
311
+
312
+ div[role="tooltip"] div.MuiTooltip-tooltip {
313
+ font-size: 1em;
314
+ }
package/src/index.js ADDED
@@ -0,0 +1,8 @@
1
+ // Import styles - these will be applied when the package is used
2
+ import './index.css';
3
+
4
+ // Re-export components
5
+ export * from './components/index.js';
6
+ // Export version - replaced at publish time via prepack script
7
+ export { version } from './version.js';
8
+
@@ -0,0 +1,51 @@
1
+ import React, { createContext, useContext, useMemo } from 'react';
2
+
3
+ const ConfigContext = createContext(null);
4
+
5
+ /**
6
+ * ConfigProvider - Provides application configuration via React Context
7
+ *
8
+ * @param {Object} props
9
+ * @param {Object} props.config - Config object
10
+ * @param {React.ReactNode} props.children - Child components
11
+ */
12
+ export function ConfigProvider({ config, children }) {
13
+ if (!config) {
14
+ throw new Error('ConfigProvider requires a config prop');
15
+ }
16
+
17
+ const value = useMemo(() => ({
18
+ config,
19
+ }), [config]);
20
+
21
+ return (
22
+ <ConfigContext.Provider value={value}>
23
+ {children}
24
+ </ConfigContext.Provider>
25
+ );
26
+ }
27
+
28
+ /**
29
+ * useConfig - Hook to access configuration from ConfigProvider
30
+ *
31
+ * @returns {Object} Configuration object with properties:
32
+ * - backendUrl: string
33
+ * - showAppBar: boolean
34
+ * - enableAssembler: boolean
35
+ * - enablePlannotate: boolean
36
+ * - noExternalRequests: boolean
37
+ * - database: string | null
38
+ *
39
+ * @throws {Error} If used outside of ConfigProvider
40
+ */
41
+ export function useConfig() {
42
+ const context = useContext(ConfigContext);
43
+
44
+ if (context === null) {
45
+ throw new Error('useConfig must be used within a ConfigProvider');
46
+ }
47
+
48
+ return context.config;
49
+ }
50
+
51
+ export default ConfigProvider;
@@ -0,0 +1,3 @@
1
+ // Re-export from ConfigProvider.jsx to ensure single source of truth
2
+ // This file is used by the "./providers/ConfigProvider" export path
3
+ export { ConfigProvider, useConfig, default } from './ConfigProvider.jsx';
package/src/version.js ADDED
@@ -0,0 +1,2 @@
1
+ // Version placeholder - replaced at publish time via prepack script
2
+ export const version = "1.1.0-dev.4";
@@ -1,50 +0,0 @@
1
- import { Alert, Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material';
2
- import React from 'react';
3
-
4
- function KnownSourceErrors({ errors }) {
5
- const [dialogOpen, setDialogOpen] = React.useState(false);
6
- return (
7
- <>
8
- <Alert
9
- severity="error"
10
- action={(
11
- <Button color="inherit" size="small" onClick={() => setDialogOpen(true)}>
12
- See how
13
- </Button>
14
- )}
15
- sx={{ alignItems: 'center', mb: 1 }}
16
- >
17
- Affected by external errors
18
- </Alert>
19
- <Dialog
20
- open={dialogOpen}
21
- onClose={() => setDialogOpen(false)}
22
- >
23
- <DialogTitle>Known external errors</DialogTitle>
24
- <DialogContent>
25
- <ul>
26
- {errors.map((error, i) => (
27
- <li style={{ marginBottom: '1em' }} key={i} component="li">
28
- {error}
29
- </li>
30
- ))}
31
- </ul>
32
-
33
- </DialogContent>
34
- <DialogActions>
35
- <Button
36
- onClick={() => {
37
- setDialogOpen(false);
38
- }}
39
- >
40
- Close
41
- </Button>
42
-
43
- </DialogActions>
44
- </Dialog>
45
- </>
46
-
47
- );
48
- }
49
-
50
- export default KnownSourceErrors;