@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.
- package/CHANGELOG.md +51 -0
- package/package.json +12 -7
- package/scripts/inject-version.js +17 -0
- package/scripts/reset-version.js +16 -0
- package/src/components/DescriptionEditor.jsx +1 -2
- package/src/components/DragAndDropCloningHistoryWrapper.jsx +1 -1
- package/src/components/ExternalServicesStatusCheck.jsx +2 -1
- package/src/components/MainSequenceCheckBox.jsx +2 -5
- package/src/components/NetworkNode.jsx +1 -3
- package/src/components/OpenCloning.jsx +3 -3
- package/src/components/assembler/AssemblePartWidget.jsx +1 -1
- package/src/components/assembler/Assembler.jsx +1 -2
- package/src/components/dummy/DummyInterface.js +1 -2
- package/src/components/eLabFTW/eLabFTWInterface.js +1 -2
- package/src/components/form/EnzymeMultiSelect.cy.jsx +20 -9
- package/src/components/form/GetRequestMultiSelect.jsx +1 -3
- package/src/components/form/LabelWithTooltip.jsx +1 -1
- package/src/components/form/PostRequestSelect.jsx +1 -3
- package/src/components/index.js +2 -0
- package/src/components/navigation/ButtonWithMenu.jsx +1 -3
- package/src/components/navigation/MainAppBar.jsx +2 -9
- package/src/components/navigation/SelectTemplateDialog.jsx +1 -1
- package/src/components/primers/PrimerForm.jsx +2 -4
- package/src/components/primers/PrimerList.cy.jsx +86 -78
- package/src/components/primers/PrimerList.jsx +1 -1
- package/src/components/primers/PrimerTableRow.cy.jsx +31 -18
- package/src/components/primers/PrimerTableRow.jsx +1 -4
- package/src/components/primers/SelectPrimerForm.jsx +1 -1
- package/src/components/primers/import_primers/ImportPrimersButton.jsx +1 -4
- package/src/components/primers/import_primers/ImportPrimersTable.jsx +2 -5
- package/src/components/primers/import_primers/PrimerDatabaseImportForm.jsx +1 -2
- package/src/components/primers/primer_design/SequenceTabComponents/CollapsableLabel.jsx +1 -1
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignForm.jsx +1 -1
- package/src/components/primers/primer_design/SequenceTabComponents/RestrictionSpacerForm.jsx +1 -1
- package/src/components/primers/primer_details/PrimerInfoIcon.jsx +1 -2
- package/src/components/settings/SettingsTab.jsx +2 -3
- package/src/components/sources/CollectionSource.jsx +1 -1
- package/src/components/sources/MultipleOutputsSelector.jsx +1 -2
- package/src/components/sources/NewSourceBox.jsx +2 -3
- package/src/components/sources/PCRUnitForm.jsx +1 -3
- package/src/components/sources/Source.jsx +0 -3
- package/src/components/sources/SourceBox.jsx +2 -2
- package/src/components/sources/SourceFile.jsx +1 -2
- package/src/components/sources/SourceGenomeRegion.cy.jsx +24 -9
- package/src/components/sources/SourceGenomeRegion.jsx +1 -6
- package/src/components/sources/SourceRepositoryId.jsx +2 -9
- package/src/components/sources/SourceTypeSelector.jsx +3 -6
- package/src/components/verification/SequencingFileRow.jsx +1 -2
- package/src/components/verification/VerificationFileDialog.cy.jsx +35 -8
- package/src/hooks/useBackendRoute.js +3 -2
- package/src/hooks/useConfig.js +2 -0
- package/src/hooks/useDatabase.js +2 -2
- package/src/hooks/useHttpClient.js +9 -3
- package/src/hooks/useInitializeApp.js +15 -0
- package/src/hooks/useUrlParamsLoader.js +149 -0
- package/src/index.css +314 -0
- package/src/index.js +8 -0
- package/src/providers/ConfigProvider.jsx +51 -0
- package/src/providers/index.js +3 -0
- package/src/version.js +2 -0
- 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;
|
package/src/version.js
ADDED
|
@@ -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;
|