@opencloning/ui 1.3.0 → 1.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @opencloning/ui
2
2
 
3
+ ## 1.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#602](https://github.com/manulera/OpenCloning_frontend/pull/602) [`b9b821d`](https://github.com/manulera/OpenCloning_frontend/commit/b9b821d562417b85b69dbf53ddaac324474d4e6b) Thanks [@manulera](https://github.com/manulera)! - Allow users to submit their own syntax from JSON file. Not validated yet so wrong syntaxes will trigger an error.
8
+
9
+ - Updated dependencies []:
10
+ - @opencloning/store@1.3.1
11
+ - @opencloning/utils@1.3.1
12
+
3
13
  ## 1.3.0
4
14
 
5
15
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencloning/ui",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "scripts": {
@@ -25,8 +25,8 @@
25
25
  "@emotion/styled": "^11.14.0",
26
26
  "@mui/icons-material": "^5.15.17",
27
27
  "@mui/material": "^5.15.17",
28
- "@opencloning/store": "1.3.0",
29
- "@opencloning/utils": "1.3.0",
28
+ "@opencloning/store": "1.3.1",
29
+ "@opencloning/utils": "1.3.1",
30
30
  "@teselagen/bio-parsers": "^0.4.32",
31
31
  "@teselagen/ove": "^0.8.30",
32
32
  "@teselagen/range-utils": "^0.3.13",
@@ -191,4 +191,61 @@ describe('<ExistingSyntaxDialog />', () => {
191
191
  cy.get('@onSyntaxSelectSpy').should('have.been.calledWith', mockSyntaxData, mockPlasmidsData);
192
192
  cy.get('@onCloseSpy').should('have.been.called');
193
193
  });
194
+
195
+ it('successfully uploads syntax from JSON file', () => {
196
+ const onCloseSpy = cy.spy().as('onCloseSpy');
197
+ const onSyntaxSelectSpy = cy.spy().as('onSyntaxSelectSpy');
198
+
199
+ const uploadedSyntaxData = {
200
+ name: 'Uploaded Syntax',
201
+ parts: [
202
+ { id: 1, name: 'Part 1' },
203
+ { id: 2, name: 'Part 2' },
204
+ ],
205
+ };
206
+
207
+ // Create a temporary JSON file
208
+ cy.writeFile('cypress/temp/syntax.json', uploadedSyntaxData);
209
+
210
+ cy.mount(
211
+ <ExistingSyntaxDialog
212
+ onClose={onCloseSpy}
213
+ onSyntaxSelect={onSyntaxSelectSpy}
214
+ />,
215
+ );
216
+
217
+ cy.wait('@getSyntaxes');
218
+ cy.contains('Upload syntax from JSON file').should('exist');
219
+
220
+ // Upload the JSON file
221
+ cy.get('input[type="file"]').selectFile('cypress/temp/syntax.json', { force: true });
222
+
223
+ cy.get('@onSyntaxSelectSpy').should('have.been.calledWith', uploadedSyntaxData, []);
224
+ cy.get('@onCloseSpy').should('have.been.called');
225
+ });
226
+
227
+ it('displays error message when uploaded JSON file is invalid', () => {
228
+ const onCloseSpy = cy.spy().as('onCloseSpy');
229
+ const onSyntaxSelectSpy = cy.spy().as('onSyntaxSelectSpy');
230
+
231
+ // Create a file with invalid JSON
232
+ cy.writeFile('cypress/temp/invalid.json', '{ invalid json }', { encoding: 'utf8' });
233
+
234
+ cy.mount(
235
+ <ExistingSyntaxDialog
236
+ onClose={onCloseSpy}
237
+ onSyntaxSelect={onSyntaxSelectSpy}
238
+ />,
239
+ );
240
+
241
+ cy.wait('@getSyntaxes');
242
+ cy.contains('Upload syntax from JSON file').should('exist');
243
+
244
+ // Upload invalid JSON
245
+ cy.get('input[type="file"]').selectFile('cypress/temp/invalid.json', { force: true });
246
+
247
+ cy.contains(/Failed to parse JSON file/).should('exist');
248
+ cy.get('@onSyntaxSelectSpy').should('not.have.been.called');
249
+ cy.get('@onCloseSpy').should('not.have.been.called');
250
+ });
194
251
  });
@@ -1,5 +1,5 @@
1
1
  import React from 'react'
2
- import { Dialog, DialogTitle, DialogContent, List, ListItem, ListItemText, ListItemButton, Alert } from '@mui/material'
2
+ import { Dialog, DialogTitle, DialogContent, List, ListItem, ListItemText, ListItemButton, Alert, Button, Box } from '@mui/material'
3
3
  import getHttpClient from '@opencloning/utils/getHttpClient';
4
4
  import RequestStatusWrapper from '../form/RequestStatusWrapper';
5
5
 
@@ -12,6 +12,7 @@ function ExistingSyntaxDialog({ onClose, onSyntaxSelect }) {
12
12
  const [connectAttempt, setConnectAttempt] = React.useState(0);
13
13
  const [requestStatus, setRequestStatus] = React.useState({ status: 'loading' });
14
14
  const [loadError, setLoadError] = React.useState(null);
15
+ const fileInputRef = React.useRef(null);
15
16
 
16
17
  React.useEffect(() => {
17
18
  setRequestStatus({ status: 'loading' });
@@ -41,12 +42,34 @@ function ExistingSyntaxDialog({ onClose, onSyntaxSelect }) {
41
42
  }
42
43
  }, [onSyntaxSelect, onClose]);
43
44
 
45
+ const handleFileUpload = React.useCallback(async (event) => {
46
+ setLoadError(null);
47
+ const file = event.target.files[0];
48
+ if (!file) return;
49
+
50
+ try {
51
+ const text = await file.text();
52
+ const syntaxData = JSON.parse(text);
53
+
54
+ // Uploaded JSON files contain only syntax data, no plasmids
55
+ onSyntaxSelect(syntaxData, []);
56
+ onClose();
57
+ } catch (error) {
58
+ setLoadError(`Failed to parse JSON file: ${error.message}`);
59
+ } finally {
60
+ // Reset file input so the same file can be selected again
61
+ if (fileInputRef.current) {
62
+ fileInputRef.current.value = '';
63
+ }
64
+ }
65
+ }, [onSyntaxSelect, onClose]);
66
+
44
67
  return (
45
68
  <Dialog open onClose={onClose}>
46
69
  <DialogTitle>Load an existing syntax</DialogTitle>
47
70
  <DialogContent>
71
+ {loadError && <Alert severity="error" sx={{ mb: 2 }}>{loadError}</Alert>}
48
72
  <RequestStatusWrapper requestStatus={requestStatus} retry={() => setConnectAttempt((prev) => prev + 1)}>
49
- {loadError && <Alert severity="error" sx={{ mb: 2 }}>{loadError}</Alert>}
50
73
  <List>
51
74
  {syntaxes.map((syntax) => (
52
75
  <ListItem key={syntax.path}>
@@ -57,6 +80,22 @@ function ExistingSyntaxDialog({ onClose, onSyntaxSelect }) {
57
80
  ))}
58
81
  </List>
59
82
  </RequestStatusWrapper>
83
+ <Box sx={{ mb: 2 }}>
84
+ <input
85
+ type="file"
86
+ ref={fileInputRef}
87
+ accept=".json"
88
+ onChange={handleFileUpload}
89
+ style={{ display: 'none' }}
90
+ />
91
+ <Button
92
+ variant="outlined"
93
+ sx={{ display: 'block', mx: 'auto' }}
94
+ onClick={() => fileInputRef.current?.click()}
95
+ >
96
+ Upload syntax from JSON file
97
+ </Button>
98
+ </Box>
60
99
  </DialogContent>
61
100
  </Dialog>
62
101
  )
package/src/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Version placeholder - replaced at publish time via prepack script
2
- export const version = "1.3.0";
2
+ export const version = "1.3.1";