diff --git a/uiserver/scripts/migrate.js b/uiserver/scripts/migrate.js index af44c72..9cc0677 100644 --- a/uiserver/scripts/migrate.js +++ b/uiserver/scripts/migrate.js @@ -12,7 +12,7 @@ console.log(`[Migrate] Connected to ${dbPath}`); console.log('[Migrate] Applying schema migrations...'); try { - db.exec(` + db.exec(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, @@ -30,9 +30,20 @@ try { FOREIGN KEY(created_by) REFERENCES users(id) ); `); - console.log('[Migrate] Schema applied successfully.'); + + // Add position column if not exists + try { + db.exec("ALTER TABLE views ADD COLUMN position INTEGER DEFAULT 0"); + console.log('[Migrate] Added position column to views table.'); + } catch (e) { + if (!e.message.includes("duplicate column name")) { + console.log('[Migrate] NOTE: ' + e.message); + } + } + + console.log('[Migrate] Schema applied successfully.'); } catch (err) { - console.error('[Migrate] Error applying schema:', err.message); + console.error('[Migrate] Error applying schema:', err.message); } finally { - db.close(); + db.close(); } diff --git a/uiserver/src/components/ViewManager.js b/uiserver/src/components/ViewManager.js index f94a3aa..fe89796 100644 --- a/uiserver/src/components/ViewManager.js +++ b/uiserver/src/components/ViewManager.js @@ -101,7 +101,6 @@ class ViewManager extends Component { }; parseViewData(view) { - // Flatten config for display/editing let channels = []; let axes = { left: { min: '', max: '' }, right: { min: '', max: '' } }; @@ -109,28 +108,20 @@ class ViewManager extends Component { if (!config) return { channels, axes }; if (Array.isArray(config)) { - // Very old legacy (array of channels) channels = config; } else if (config.groups) { - // Group format (Recent) - Flatten it! config.groups.forEach(g => { - if (g.channels) { - channels = [...channels, ...g.channels]; - } - // Merge axes? Just take first group's axes or default? - // Let's defer to default or first group if present + if (g.channels) channels = [...channels, ...g.channels]; if (g.axes) { if (g.axes.left) axes.left = { ...axes.left, ...g.axes.left }; if (g.axes.right) axes.right = { ...axes.right, ...g.axes.right }; } }); } else if (config.channels) { - // Standard Legacy channels = config.channels; if (config.axes) axes = config.axes; } - // Normalize channels channels = channels.map((c, i) => ({ ...c, color: c.color || this.getNextColor(i) @@ -180,6 +171,31 @@ class ViewManager extends Component { this.refreshViews(); }; + moveView = async (idx, dir) => { + const newViews = [...this.state.views]; + const target = idx + dir; + if (target < 0 || target >= newViews.length) return; + + [newViews[idx], newViews[target]] = [newViews[target], newViews[idx]]; + this.setState({ views: newViews }); + + const order = newViews.map((v, i) => ({ id: v.id, position: i })); + const { user } = this.props; + + try { + await fetch('/api/views/reorder', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${user.token}` + }, + body: JSON.stringify({ order }) + }); + } catch (err) { + console.error("Failed to save order", err); + } + }; + handleSave = async () => { const { viewName, viewConfig, axisConfig, editingId } = this.state; const { user } = this.props; @@ -189,7 +205,6 @@ class ViewManager extends Component { const url = editingId ? `/api/views/${editingId}` : '/api/views'; const method = editingId ? 'PUT' : 'POST'; - // Save as flat format again const finalConfig = { channels: viewConfig, axes: axisConfig @@ -274,7 +289,6 @@ class ViewManager extends Component { } }; - // --- Navigation --- handleRangeChange = (e, newVal) => { if (newVal) this.setState({ rangeLabel: newVal }); }; @@ -315,7 +329,6 @@ class ViewManager extends Component { return ( - {/* Global Time Controls */} @@ -330,14 +343,21 @@ class ViewManager extends Component { {isAdmin && } - {/* View List */} - {views.map(view => { + {views.map((view, vIdx) => { const { channels, axes } = this.parseViewData(view); return ( - {view.name} + + {view.name} + {isAdmin && ( + <> + this.moveView(vIdx, -1)} disabled={vIdx === 0}> + this.moveView(vIdx, 1)} disabled={vIdx === views.length - 1}> + + )} + {isAdmin && ( this.handleOpenEdit(view, e)}> @@ -364,7 +384,6 @@ class ViewManager extends Component { {views.length === 0 && No views available.} - {/* Edit Dialog */} this.setState({ open: false })} maxWidth="md" fullWidth> {editingId ? 'Edit View' : 'Create New View'} @@ -373,7 +392,6 @@ class ViewManager extends Component { onChange={(e) => this.setState({ viewName: e.target.value })} sx={{ mb: 2 }} /> - {/* Axis Config */} Axis Configuration @@ -410,7 +428,6 @@ class ViewManager extends Component { ))} - {/* Add Channel */}