Skip to content
This repository was archived by the owner on Jan 13, 2026. It is now read-only.

Commit 041f84c

Browse files
authored
Merge pull request #133 from openUC2/ui_fixes_bd_3
UI fixes bd 3
2 parents f1bd57f + a2bd1c6 commit 041f84c

22 files changed

+4596
-204
lines changed

src/axon/ParameterEditorWrapper.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
11
import ParameterEditorComponent from "./ParameterEditorComponent";
22
import ExperimentComponent from "./ExperimentComponent";
3-
4-
5-
3+
import { ExperimentDesigner } from "./experiment-designer";
4+
5+
// Feature flag for new experiment designer UI
6+
const USE_NEW_EXPERIMENT_DESIGNER = true;
7+
68
const ParameterEditorWrapper = () => {
9+
// Use new dimension-based experiment designer when feature flag is enabled
10+
if (USE_NEW_EXPERIMENT_DESIGNER) {
11+
return (
12+
<div style={{ position: "relative", width: "100%", height: "100%" }}>
13+
<ExperimentDesigner />
14+
</div>
15+
);
16+
}
17+
18+
// Legacy layout
719
return (
820
<div style={{ position: "relative", width: "400", height: "300" }}>
921
<div style={{ position: "relative", top: "0px", left: "0px", zIndex: 1, }}>

src/axon/WellSelectorComponent.js

Lines changed: 126 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,78 @@ const WellSelectorComponent = () => {
218218
dispatch(wellSelectorSlice.setCupSelectOverlap(value));
219219
};
220220

221+
//##################################################################################
222+
// Save positions to JSON file
223+
const handleSavePositions = () => {
224+
try {
225+
const positionsData = {
226+
pointList: experimentState.pointList,
227+
wellLayout: experimentState.wellLayout,
228+
savedAt: new Date().toISOString(),
229+
};
230+
231+
const dataStr = JSON.stringify(positionsData, null, 2);
232+
const dataBlob = new Blob([dataStr], { type: "application/json" });
233+
const url = URL.createObjectURL(dataBlob);
234+
235+
const link = document.createElement("a");
236+
link.href = url;
237+
link.download = `wellplate_positions_${new Date().toISOString().slice(0, 10)}.json`;
238+
link.click();
239+
240+
URL.revokeObjectURL(url);
241+
242+
infoPopupRef.current?.showInfo("Positions saved successfully!");
243+
} catch (error) {
244+
console.error("Error saving positions:", error);
245+
infoPopupRef.current?.showInfo("Error saving positions!");
246+
}
247+
};
248+
249+
//##################################################################################
250+
// Load positions from JSON file
251+
const handleLoadPositions = () => {
252+
const input = document.createElement("input");
253+
input.type = "file";
254+
input.accept = "application/json";
255+
256+
input.onchange = (e) => {
257+
const file = e.target.files[0];
258+
if (!file) return;
259+
260+
const reader = new FileReader();
261+
reader.onload = (event) => {
262+
try {
263+
const positionsData = JSON.parse(event.target.result);
264+
265+
// Validate data structure
266+
if (!positionsData.pointList || !Array.isArray(positionsData.pointList)) {
267+
throw new Error("Invalid positions file format");
268+
}
269+
270+
// Load positions into Redux state
271+
dispatch(experimentSlice.setPointList(positionsData.pointList));
272+
273+
// Optionally restore layout if included
274+
if (positionsData.wellLayout) {
275+
dispatch(experimentSlice.setWellLayout(positionsData.wellLayout));
276+
}
277+
278+
infoPopupRef.current?.showInfo(
279+
`Loaded ${positionsData.pointList.length} position(s) successfully!`
280+
);
281+
} catch (error) {
282+
console.error("Error loading positions:", error);
283+
infoPopupRef.current?.showInfo("Error loading positions file!");
284+
}
285+
};
286+
287+
reader.readAsText(file);
288+
};
289+
290+
input.click();
291+
};
292+
221293
//##################################################################################
222294
return (
223295
<div style={{ border: "0px solid #eee", padding: "10px" }}>
@@ -263,115 +335,63 @@ const WellSelectorComponent = () => {
263335
</Select>
264336
</FormControl>
265337

266-
{/* TILE OVERLAP SLIDER */}
267-
<Box sx={{ width: 250, marginLeft: "20px", marginRight: "20px" }}>
268-
<Typography variant="body2" gutterBottom>
269-
Tile Overlap: {Math.round((experimentState.parameterValue.overlapWidth || 0) * 100)}%
270-
</Typography>
271-
<Slider
272-
value={(experimentState.parameterValue.overlapWidth || 0) * 100}
273-
onChange={(e, newValue) => {
274-
const overlapValue = newValue / 100;
275-
// Update both states to keep them in sync
276-
dispatch(wellSelectorSlice.setOverlapWidth(overlapValue));
277-
dispatch(wellSelectorSlice.setOverlapHeight(overlapValue));
278-
dispatch(experimentSlice.setOverlapWidth(overlapValue));
279-
dispatch(experimentSlice.setOverlapHeight(overlapValue));
280-
}}
281-
min={0}
282-
max={50}
283-
step={5}
284-
marks={[
285-
{ value: 0, label: '0%' },
286-
{ value: 25, label: '25%' },
287-
{ value: 50, label: '50%' },
288-
]}
289-
valueLabelDisplay="auto"
290-
valueLabelFormat={(value) => `${value}%`}
291-
/>
338+
{/* VIEW - All controls in one row */}
339+
<Box sx={{ display: "flex", gap: 1, alignItems: "center", flexWrap: "wrap" }}>
340+
<Button
341+
variant="contained"
342+
size="small"
343+
onClick={() => handleResetView()}
344+
>
345+
Reset View
346+
</Button>
347+
348+
<Button
349+
variant="contained"
350+
size="small"
351+
onClick={() => handleResetHistory()}
352+
>
353+
Reset History
354+
</Button>
355+
356+
<label style={{ fontSize: "14px", display: "flex", alignItems: "center", gap: "4px" }}>
357+
<input
358+
type="checkbox"
359+
checked={wellSelectorState.showOverlap}
360+
onChange={handleShowOverlapChange}
361+
/>
362+
Show Overlap
363+
</label>
364+
365+
<label style={{ fontSize: "14px", display: "flex", alignItems: "center", gap: "4px" }}>
366+
<input
367+
type="checkbox"
368+
checked={wellSelectorState.showShape}
369+
onChange={handleShowShapeChange}
370+
/>
371+
Show Shape
372+
</label>
292373
</Box>
293374

294-
{/* VIEW */}
295-
296-
<Button variant="contained" onClick={() => handleResetView()}>
297-
reset view
298-
</Button>
299-
300-
<Button
301-
variant="contained"
302-
onClick={() => handleResetHistory()}
303-
style={{ marginLeft: "10px" }}
304-
>
305-
reset history
306-
</Button>
307-
308-
<label style={{ fontSize: "14px" }}>
309-
<input
310-
type="checkbox"
311-
checked={wellSelectorState.showOverlap}
312-
onChange={handleShowOverlapChange}
313-
/>
314-
show overlap
315-
</label>
316-
317-
<label style={{ fontSize: "14px" }}>
318-
<input
319-
type="checkbox"
320-
checked={wellSelectorState.showShape}
321-
onChange={handleShowShapeChange}
322-
/>
323-
show shape
324-
</label>
325-
</div>
375+
{/* Save/Load Positions */}
376+
<Box sx={{ display: "flex", gap: 1, mt: 1 }}>
377+
<Button
378+
variant="outlined"
379+
size="small"
380+
onClick={handleSavePositions}
381+
disabled={!experimentState.pointList || experimentState.pointList.length === 0}
382+
>
383+
💾 Save Positions
384+
</Button>
326385

327-
{/* ADVANCED SETTINGS */}
328-
<Accordion style={{ marginBottom: "10px" }}>
329-
<AccordionSummary
330-
expandIcon={<ExpandMoreIcon />}
331-
aria-controls="advanced-settings-content"
332-
id="advanced-settings-header"
333-
>
334-
<Typography>Advanced Layout Settings</Typography>
335-
</AccordionSummary>
336-
<AccordionDetails>
337-
<Box display="flex" flexDirection="column" gap={2}>
338-
<Typography variant="body2" color="textSecondary">
339-
Global layout offsets (applied to all wells in micrometers):
340-
</Typography>
341-
342-
<FormControl fullWidth>
343-
<TextField
344-
label="Layout Offset X (μm)"
345-
type="number"
346-
value={wellSelectorState.layoutOffsetX || 0}
347-
onChange={handleLayoutOffsetXChange}
348-
inputProps={{
349-
step: 100,
350-
}}
351-
helperText="Horizontal offset for all wells (default: 0)"
352-
/>
353-
</FormControl>
354-
355-
<FormControl fullWidth>
356-
<TextField
357-
label="Layout Offset Y (μm)"
358-
type="number"
359-
value={wellSelectorState.layoutOffsetY || 0}
360-
onChange={handleLayoutOffsetYChange}
361-
inputProps={{
362-
step: 100,
363-
}}
364-
helperText="Vertical offset for all wells (default: 0)"
365-
/>
366-
</FormControl>
367-
368-
<Typography variant="caption" color="textSecondary" style={{ marginTop: '8px' }}>
369-
Note: Changes are applied immediately to the current layout.
370-
For Wellplate 384, default startX=29490.625μm, startY=30688.125μm
371-
</Typography>
372-
</Box>
373-
</AccordionDetails>
374-
</Accordion>
386+
<Button
387+
variant="outlined"
388+
size="small"
389+
onClick={handleLoadPositions}
390+
>
391+
📂 Load Positions
392+
</Button>
393+
</Box>
394+
</div>
375395

376396
{/* MODE */}
377397
<div
@@ -437,69 +457,6 @@ const WellSelectorComponent = () => {
437457
</ButtonGroup>
438458
</div>
439459

440-
{/* MODE-SPECIFIC SETTINGS */}
441-
{/* Area Select Settings */}
442-
{wellSelectorState.mode === Mode.AREA_SELECT && (
443-
<Box
444-
sx={{
445-
marginBottom: "15px",
446-
padding: "12px",
447-
border: (theme) => `1px solid ${theme.palette.divider}`,
448-
borderRadius: "4px",
449-
backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.05)' : '#f9f9f9',
450-
}}
451-
>
452-
<Typography variant="subtitle2" gutterBottom>
453-
Area Select Settings
454-
</Typography>
455-
<Box display="flex" flexDirection="column" gap={2}>
456-
{/* Snakescan checkbox */}
457-
<FormControlLabel
458-
control={
459-
<input
460-
type="checkbox"
461-
checked={wellSelectorState.areaSelectSnakescan}
462-
onChange={handleAreaSelectSnakescanChange}
463-
/>
464-
}
465-
label="Enable Snakescan Pattern"
466-
/>
467-
</Box>
468-
</Box>
469-
)}
470-
471-
{/* Cup/Well Select Settings */}
472-
{wellSelectorState.mode === Mode.CUP_SELECT && (
473-
<Box
474-
sx={{
475-
marginBottom: "15px",
476-
padding: "12px",
477-
border: (theme) => `1px solid ${theme.palette.divider}`,
478-
borderRadius: "4px",
479-
backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.05)' : '#f9f9f9',
480-
}}
481-
>
482-
<Typography variant="subtitle2" gutterBottom>
483-
Well Select Settings
484-
</Typography>
485-
<Box display="flex" flexDirection="column" gap={2}>
486-
{/* Shape selector */}
487-
<FormControl size="small" sx={{ width: "200px" }}>
488-
<InputLabel>Well Shape</InputLabel>
489-
<Select
490-
value={wellSelectorState.cupSelectShape}
491-
onChange={handleCupSelectShapeChange}
492-
label="Well Shape"
493-
>
494-
<MenuItem value="circle">Circle</MenuItem>
495-
<MenuItem value="rectangle">Rectangle</MenuItem>
496-
</Select>
497-
<FormHelperText>Shape pattern for well scanning</FormHelperText>
498-
</FormControl>
499-
</Box>
500-
</Box>
501-
)}
502-
503460
<InfoPopup ref={infoPopupRef}/>
504461
</div>
505462
);

0 commit comments

Comments
 (0)