Compare commits
2 Commits
eb7f87dfbc
...
9402b6c40f
| Author | SHA1 | Date |
|---|---|---|
|
|
9402b6c40f | |
|
|
87be2526ba |
|
|
@ -75,6 +75,10 @@ server {
|
||||||
gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml application/rss+xml;
|
gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml application/rss+xml;
|
||||||
|
|
||||||
# Frontend SPA routing — must be last so specific locations above take precedence
|
# Frontend SPA routing — must be last so specific locations above take precedence
|
||||||
|
location = / {
|
||||||
|
return 301 /velxio/editor;
|
||||||
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ interface ComponentPropertyDialogProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onRotate: (componentId: string) => void;
|
onRotate: (componentId: string) => void;
|
||||||
onDelete: (componentId: string) => void;
|
onDelete: (componentId: string) => void;
|
||||||
|
lockComponents?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ComponentPropertyDialog: React.FC<ComponentPropertyDialogProps> = ({
|
export const ComponentPropertyDialog: React.FC<ComponentPropertyDialogProps> = ({
|
||||||
|
|
@ -29,6 +30,7 @@ export const ComponentPropertyDialog: React.FC<ComponentPropertyDialogProps> = (
|
||||||
onClose,
|
onClose,
|
||||||
onRotate,
|
onRotate,
|
||||||
onDelete,
|
onDelete,
|
||||||
|
lockComponents = false,
|
||||||
}) => {
|
}) => {
|
||||||
const dialogRef = useRef<HTMLDivElement>(null);
|
const dialogRef = useRef<HTMLDivElement>(null);
|
||||||
const [dialogPosition, setDialogPosition] = useState({ x: 0, y: 0 });
|
const [dialogPosition, setDialogPosition] = useState({ x: 0, y: 0 });
|
||||||
|
|
@ -151,6 +153,7 @@ export const ComponentPropertyDialog: React.FC<ComponentPropertyDialogProps> = (
|
||||||
>
|
>
|
||||||
Rotate
|
Rotate
|
||||||
</button>
|
</button>
|
||||||
|
{!lockComponents && (
|
||||||
<button
|
<button
|
||||||
className="property-action-button delete-button"
|
className="property-action-button delete-button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -162,6 +165,7 @@ export const ComponentPropertyDialog: React.FC<ComponentPropertyDialogProps> = (
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ export const SimulatorCanvas = () => {
|
||||||
updateComponent,
|
updateComponent,
|
||||||
serialMonitorOpen,
|
serialMonitorOpen,
|
||||||
toggleSerialMonitor,
|
toggleSerialMonitor,
|
||||||
|
lockComponents,
|
||||||
} = useSimulatorStore();
|
} = useSimulatorStore();
|
||||||
|
|
||||||
// Active board (for WiFi/BLE status display)
|
// Active board (for WiFi/BLE status display)
|
||||||
|
|
@ -992,6 +993,8 @@ export const SimulatorCanvas = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (e.key === 'Delete' || e.key === 'Backspace') {
|
if (e.key === 'Delete' || e.key === 'Backspace') {
|
||||||
|
if (lockComponents) return; // Prevent deletion if locked
|
||||||
|
|
||||||
if (selectedComponentId) {
|
if (selectedComponentId) {
|
||||||
removeComponent(selectedComponentId);
|
removeComponent(selectedComponentId);
|
||||||
setSelectedComponentId(null);
|
setSelectedComponentId(null);
|
||||||
|
|
@ -1004,7 +1007,7 @@ export const SimulatorCanvas = () => {
|
||||||
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
}, [selectedComponentId, removeComponent, activeBoardId, boards.length]);
|
}, [selectedComponentId, removeComponent, activeBoardId, boards.length, lockComponents]);
|
||||||
|
|
||||||
// Handle component selection from modal
|
// Handle component selection from modal
|
||||||
const handleSelectComponent = (metadata: ComponentMetadata) => {
|
const handleSelectComponent = (metadata: ComponentMetadata) => {
|
||||||
|
|
@ -1607,6 +1610,7 @@ export const SimulatorCanvas = () => {
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* Add Component */}
|
{/* Add Component */}
|
||||||
|
{!lockComponents && (
|
||||||
<button
|
<button
|
||||||
className="add-component-btn"
|
className="add-component-btn"
|
||||||
onClick={() => setShowComponentPicker(true)}
|
onClick={() => setShowComponentPicker(true)}
|
||||||
|
|
@ -1619,6 +1623,7 @@ export const SimulatorCanvas = () => {
|
||||||
</svg>
|
</svg>
|
||||||
Add
|
Add
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1831,6 +1836,7 @@ export const SimulatorCanvas = () => {
|
||||||
removeComponent(id);
|
removeComponent(id);
|
||||||
setShowPropertyDialog(false);
|
setShowPropertyDialog(false);
|
||||||
}}
|
}}
|
||||||
|
lockComponents={lockComponents}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|
@ -1884,6 +1890,7 @@ export const SimulatorCanvas = () => {
|
||||||
<div style={{ padding: '6px 14px', color: '#888', fontSize: 11, borderBottom: '1px solid #3c3c3c', marginBottom: 2 }}>
|
<div style={{ padding: '6px 14px', color: '#888', fontSize: 11, borderBottom: '1px solid #3c3c3c', marginBottom: 2 }}>
|
||||||
{label}
|
{label}
|
||||||
</div>
|
</div>
|
||||||
|
{!lockComponents && (
|
||||||
<button
|
<button
|
||||||
style={{
|
style={{
|
||||||
display: 'flex', alignItems: 'center', gap: 8,
|
display: 'flex', alignItems: 'center', gap: 8,
|
||||||
|
|
@ -1907,6 +1914,7 @@ export const SimulatorCanvas = () => {
|
||||||
Remove board
|
Remove board
|
||||||
{connectedWires > 0 && <span style={{ color: '#888', fontSize: 11 }}>({connectedWires} wire{connectedWires > 1 ? 's' : ''})</span>}
|
{connectedWires > 0 && <span style={{ color: '#888', fontSize: 11 }}>({connectedWires} wire{connectedWires > 1 ? 's' : ''})</span>}
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,18 @@ export const EditorPage: React.FC = () => {
|
||||||
return {
|
return {
|
||||||
enabled: params.get('embed') === 'true',
|
enabled: params.get('embed') === 'true',
|
||||||
hideEditor: params.get('hideEditor') === 'true',
|
hideEditor: params.get('hideEditor') === 'true',
|
||||||
|
lockComponents: params.get('lockComponents') === 'true',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const [embedHideComponentPicker, setEmbedHideComponentPicker] = useState(false);
|
const [embedHideComponentPicker, setEmbedHideComponentPicker] = useState(false);
|
||||||
|
const setLockComponents = useSimulatorStore((s) => s.setLockComponents);
|
||||||
|
|
||||||
|
// Initial URL params sync
|
||||||
|
useEffect(() => {
|
||||||
|
if (embedMode.lockComponents) {
|
||||||
|
setLockComponents(true);
|
||||||
|
}
|
||||||
|
}, [embedMode.lockComponents, setLockComponents]);
|
||||||
|
|
||||||
// Listen for embed mode commands from parent
|
// Listen for embed mode commands from parent
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -71,6 +80,7 @@ export const EditorPage: React.FC = () => {
|
||||||
const handler = (e: Event) => {
|
const handler = (e: Event) => {
|
||||||
const detail = (e as CustomEvent).detail || {};
|
const detail = (e as CustomEvent).detail || {};
|
||||||
if (detail.hideComponentPicker) setEmbedHideComponentPicker(true);
|
if (detail.hideComponentPicker) setEmbedHideComponentPicker(true);
|
||||||
|
if (detail.lockComponents !== undefined) setLockComponents(detail.lockComponents);
|
||||||
};
|
};
|
||||||
window.addEventListener('velxio-embed-mode', handler);
|
window.addEventListener('velxio-embed-mode', handler);
|
||||||
// Notify parent that Velxio is ready
|
// Notify parent that Velxio is ready
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,10 @@ interface SimulatorState {
|
||||||
esp32CrashBoardId: string | null;
|
esp32CrashBoardId: string | null;
|
||||||
dismissEsp32Crash: () => void;
|
dismissEsp32Crash: () => void;
|
||||||
|
|
||||||
|
// ── Lock components ─────────────────────────────────────────────────────
|
||||||
|
lockComponents: boolean;
|
||||||
|
setLockComponents: (locked: boolean) => void;
|
||||||
|
|
||||||
// ── Components ──────────────────────────────────────────────────────────
|
// ── Components ──────────────────────────────────────────────────────────
|
||||||
components: Component[];
|
components: Component[];
|
||||||
addComponent: (component: Component) => void;
|
addComponent: (component: Component) => void;
|
||||||
|
|
@ -786,6 +790,9 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||||
esp32CrashBoardId: null,
|
esp32CrashBoardId: null,
|
||||||
dismissEsp32Crash: () => set({ esp32CrashBoardId: null }),
|
dismissEsp32Crash: () => set({ esp32CrashBoardId: null }),
|
||||||
|
|
||||||
|
lockComponents: false,
|
||||||
|
setLockComponents: (locked: boolean) => set({ lockComponents: locked }),
|
||||||
|
|
||||||
setBoardType: (type: BoardType) => {
|
setBoardType: (type: BoardType) => {
|
||||||
const { activeBoardId, running, stopSimulation } = get();
|
const { activeBoardId, running, stopSimulation } = get();
|
||||||
if (running) stopSimulation();
|
if (running) stopSimulation();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue