Major changes: logic add to panel, local variables, panel histor, users management...

This commit is contained in:
Martino Ferrari
2026-06-18 17:37:04 +02:00
parent 71430bc3b0
commit aba394b84d
54 changed files with 6104 additions and 1166 deletions
+677 -249
View File
@@ -164,6 +164,42 @@ body {
background: #374151;
}
/* ── User identity chip ──────────────────────────────────────────────────── */
.user-chip {
display: flex;
align-items: center;
font-size: 0.75rem;
padding: 0.2rem 0.6rem;
border-radius: 9999px;
background: #1e293b;
color: #cbd5e1;
border: 1px solid #334155;
white-space: nowrap;
}
/* ── Access denied screen ────────────────────────────────────────────────── */
.access-denied {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
text-align: center;
color: #e2e8f0;
padding: 2rem;
}
.access-denied h1 {
font-size: 1.5rem;
margin-bottom: 0.5rem;
color: #fca5a5;
}
.access-denied p {
max-width: 32rem;
color: #94a3b8;
}
.content {
display: flex;
flex: 1;
@@ -257,8 +293,17 @@ body {
color: #e2e8f0;
}
.panel-body {
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
min-height: 0;
}
.panel-actions {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
padding: 0.5rem 0.6rem;
border-bottom: 1px solid #2d3748;
@@ -834,6 +879,15 @@ body {
background: #1e40af;
}
/* Read-only: user lacks permission to write this signal (.sv-select shares
.sv-input). */
.sv-input:disabled,
.sv-btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.sv-btn:disabled:hover { background: #2563eb; }
/* ── Button widget ─────────────────────────────────────────────────────── */
.button-widget {
@@ -868,6 +922,19 @@ body {
transform: scale(0.97);
}
/* Read-only: user lacks permission to write this signal. */
.button-widget .btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.button-widget .btn:disabled:hover {
background: #1e3a5f;
border-color: #2d5a9e;
}
.button-widget .btn:disabled:active {
transform: none;
}
/* ── ImageWidget ───────────────────────────────────────────────────────── */
.image-widget {
@@ -1001,6 +1068,7 @@ body {
border-radius: 4px;
cursor: pointer;
font-family: system-ui, sans-serif;
white-space: nowrap;
}
.toolbar-btn:hover { background: #374151; }
@@ -1042,6 +1110,384 @@ body {
min-height: 0;
}
/* ── Center column tabs (Layout / Logic) ────────────────────────────────── */
.edit-center {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
.center-tabs {
display: flex;
gap: 0;
background: #0f1117;
border-bottom: 1px solid #1e2433;
flex: 0 0 auto;
}
.center-tab {
padding: 0.4rem 1rem;
font-size: 0.8rem;
color: #94a3b8;
background: transparent;
border: none;
border-right: 1px solid #1e2433;
border-bottom: 2px solid transparent;
cursor: pointer;
}
.center-tab:hover { color: #e2e8f0; background: #161b27; }
.center-tab-active {
color: #e2e8f0;
border-bottom-color: #3b82f6;
background: #161b27;
}
/* ── Logic flow editor (Node-RED style) ─────────────────────────────────── */
.flow-editor {
flex: 1;
display: flex;
min-height: 0;
overflow: hidden;
}
/* Palette of node types to add */
.flow-palette {
width: 10rem;
flex: 0 0 auto;
border-right: 1px solid #1e2433;
background: #0f1117;
padding: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.4rem;
overflow-y: auto;
}
.flow-palette-toolbar {
display: flex;
gap: 0.35rem;
margin-bottom: 0.15rem;
}
.flow-palette-toolbar .toolbar-btn { flex: 1; }
.flow-palette-title {
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #64748b;
}
.flow-palette-btn {
text-align: left;
padding: 0.4rem 0.6rem;
font-size: 0.8rem;
color: #e2e8f0;
background: #161b27;
border: 1px solid #1e2433;
border-left-width: 3px;
border-radius: 4px;
cursor: pointer;
}
.flow-palette-btn:hover { background: #1c2230; }
.flow-palette-trigger { border-left-color: #f59e0b; }
.flow-palette-gate { border-left-color: #a855f7; }
.flow-palette-flow { border-left-color: #10b981; }
.flow-palette-action { border-left-color: #3b82f6; }
.flow-palette-hint { margin-top: 0.5rem; font-size: 0.7rem; line-height: 1.4; }
.flow-palette-hint code { background: #1e2433; padding: 0 0.2em; border-radius: 3px; }
/* Scrollable node canvas */
.flow-canvas {
flex: 1;
min-width: 0;
position: relative;
overflow: auto;
background:
repeating-linear-gradient(0deg, transparent 0 19px, #161b2733 19px 20px),
repeating-linear-gradient(90deg, transparent 0 19px, #161b2733 19px 20px),
#0b0e14;
}
.flow-canvas-inner {
position: relative;
width: 125rem;
height: 87.5rem;
}
.flow-wires {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
pointer-events: none;
overflow: visible;
}
.flow-wire {
fill: none;
stroke: #475569;
stroke-width: 2;
pointer-events: stroke;
cursor: pointer;
}
.flow-wire:hover { stroke: #64748b; }
.flow-wire-selected { stroke: #ef4444; stroke-width: 2.5; }
.flow-wire-pending { stroke: #3b82f6; stroke-dasharray: 5 4; }
/* Node block */
.flow-node {
position: absolute;
background: #161b27;
border: 1px solid #1e2433;
border-radius: 6px;
box-shadow: 0 2px 6px #0006;
cursor: move;
user-select: none;
}
.flow-node-trigger { border-top: 3px solid #f59e0b; }
.flow-node-gate { border-top: 3px solid #a855f7; }
.flow-node-flow { border-top: 3px solid #10b981; }
.flow-node-action { border-top: 3px solid #3b82f6; }
.flow-node-selected { border-color: #3b82f6; box-shadow: 0 0 0 2px #3b82f680; }
.flow-node-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.3rem 0.5rem;
}
.flow-node-title { font-size: 0.78rem; font-weight: 600; color: #e2e8f0; }
.flow-node-del {
background: none; border: none; color: #64748b;
cursor: pointer; font-size: 0.75rem; line-height: 1; padding: 0;
}
.flow-node-del:hover { color: #ef4444; }
.flow-node-body {
padding: 0 0.5rem 0.4rem;
font-size: 0.7rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Ports — input/output positions are set inline (in rem-scaled px) so they
stay aligned with the node geometry on any root font size. */
.flow-port {
position: absolute;
width: 0.75rem;
height: 0.75rem;
border-radius: 50%;
background: #0b0e14;
border: 2px solid #64748b;
cursor: crosshair;
}
.flow-port:hover { border-color: #3b82f6; background: #3b82f6; }
.flow-port-then { border-color: #10b981; }
.flow-port-body { border-color: #10b981; }
.flow-port-else { border-color: #ef4444; }
.flow-port-label {
position: absolute;
right: 10px;
font-size: 0.6rem;
color: #64748b;
pointer-events: none;
}
/* Expression field: signal-insert helper row */
.flow-expr-insert {
display: flex;
gap: 0.35rem;
margin-top: 0.35rem;
}
.flow-expr-insert > * { flex: 1; min-width: 0; }
.prop-input-error { border-color: #ef4444; }
/* Inspector */
.flow-inspector {
width: 15rem;
flex: 0 0 auto;
border-left: 1px solid #1e2433;
background: #0f1117;
padding: 0.75rem;
overflow-y: auto;
}
/* Local-variable editor in the palette */
.flow-localvars {
margin-top: 0.5rem;
border-top: 1px solid #1e2433;
padding-top: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.flow-localvars-empty { padding: 0; }
.flow-localvar-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.35rem;
padding: 0.2rem 0.4rem;
background: #161b27;
border: 1px solid #1e2433;
border-left: 3px solid #14b8a6;
border-radius: 4px;
}
.flow-localvar-name {
font-size: 0.78rem;
color: #e2e8f0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.flow-localvar-add { text-align: center; border-left-color: #14b8a6; }
.flow-localvar-form {
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.flow-localvar-actions {
display: flex;
gap: 0.35rem;
}
.flow-localvar-actions .panel-btn { flex: 1; }
/* ── History pane ───────────────────────────────────────────────────────── */
.history-pane {
width: 280px;
flex-shrink: 0;
display: flex;
flex-direction: column;
background: #11151f;
border-left: 1px solid #1f2733;
overflow: hidden;
}
.history-pane-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
font-weight: 600;
font-size: 13px;
border-bottom: 1px solid #1f2733;
color: #cbd5e1;
}
.history-save-row {
display: flex;
gap: 6px;
padding: 10px 12px;
border-bottom: 1px solid #1f2733;
}
.history-tag-input {
flex: 1;
min-width: 0;
background: #0a0d14;
border: 1px solid #2a3543;
border-radius: 4px;
color: #e2e8f0;
padding: 4px 8px;
font-size: 12px;
}
.history-list {
flex: 1;
overflow-y: auto;
padding: 6px;
}
.history-empty {
padding: 16px 12px;
color: #64748b;
font-size: 12px;
text-align: center;
}
.history-item {
padding: 8px 10px;
border: 1px solid #1f2733;
border-radius: 6px;
margin-bottom: 6px;
background: #0e1219;
}
.history-item-current {
border-color: #2563eb;
background: #0f1830;
}
.history-item-main {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.history-version {
font-weight: 600;
font-size: 12px;
color: #93c5fd;
}
.history-tag {
font-size: 11px;
padding: 1px 6px;
border-radius: 10px;
background: #1e293b;
color: #cbd5e1;
}
.history-current-badge {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.04em;
padding: 1px 6px;
border-radius: 10px;
background: #2563eb;
color: #fff;
}
.history-item-meta {
font-size: 11px;
color: #64748b;
margin-top: 3px;
}
.history-item-actions {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: 6px;
}
.history-action-btn {
flex: 1 1 auto;
font-size: 11px;
padding: 3px 6px;
background: #1e293b;
border: 1px solid #2a3543;
border-radius: 4px;
color: #cbd5e1;
cursor: pointer;
white-space: nowrap;
}
.history-action-btn:hover {
background: #2a3543;
}
/* ── Edit Canvas ────────────────────────────────────────────────────────── */
.edit-canvas-container {
@@ -1792,6 +2238,47 @@ body {
.iface-delete { color: #ef4444 !important; }
.iface-delete:hover { background: rgba(239, 68, 68, 0.15) !important; }
/* ── Panel sharing / folders (access control) ───────────────────────────── */
.iface-badge {
display: inline-block;
margin-left: 6px;
padding: 0 4px;
font-size: 0.6rem;
line-height: 1.3;
color: #94a3b8;
background: #1e293b;
border: 1px solid #334155;
border-radius: 3px;
vertical-align: middle;
}
.iface-folder { list-style: none; }
.iface-folder-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.iface-folder-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
color: #cbd5e1;
font-weight: 600;
}
.iface-folder:hover .iface-item-actions { display: flex; }
.iface-sublist {
margin-left: 0.75rem;
border-left: 1px solid #2d3748;
padding-left: 0.4rem;
}
/* ── Phase 7 additions ───────────────────────────────────────────────────── */
/* Rubber-band selection rectangle */
@@ -2395,39 +2882,6 @@ kbd {
overflow: hidden;
}
.view-tabs {
display: flex;
align-items: center;
gap: 2px;
padding: 0 0.5rem;
height: 2.25rem;
background: #0f1117;
border-bottom: 1px solid #1e293b;
flex-shrink: 0;
}
.view-tab {
background: none;
border: none;
border-bottom: 2px solid transparent;
color: #64748b;
font-size: 0.82rem;
padding: 0 0.75rem;
height: 100%;
cursor: pointer;
transition: color 0.15s, border-color 0.15s;
white-space: nowrap;
}
.view-tab:hover {
color: #cbd5e1;
}
.view-tab-active {
color: #e2e8f0;
border-bottom-color: #4a9eff;
}
/* ── InfoPanel ─────────────────────────────────────────────────────────── */
.info-panel {
@@ -2527,220 +2981,6 @@ kbd {
line-height: 1.4;
}
/* ── PlotPanel ─────────────────────────────────────────────────────────── */
.plot-panel {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
background: #0f1117;
}
.plot-panel-toolbar {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0 0.75rem;
height: 2.5rem;
background: #0f1117;
border-bottom: 1px solid #1e293b;
flex-shrink: 0;
}
.plot-panel-title {
font-size: 0.8rem;
font-weight: 600;
color: #94a3b8;
margin-right: 0.5rem;
white-space: nowrap;
}
.plot-window-btns {
display: flex;
gap: 2px;
}
.plot-panel-body {
display: flex;
flex: 1;
min-height: 0;
overflow: hidden;
}
.plot-empty-hint {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
color: #475569;
font-size: 0.88rem;
text-align: center;
padding: 2rem;
}
.plot-panel-content {
display: flex;
flex: 1;
min-width: 0;
min-height: 0;
overflow: hidden;
}
.plot-panel-legend {
width: 160px;
flex-shrink: 0;
overflow-y: auto;
border-right: 1px solid #1e293b;
padding: 0.5rem 0;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.plot-legend-item {
padding: 0.4rem 0.6rem;
border-radius: 4px;
}
.plot-legend-item:hover { background: #1a1f2e; }
.plot-legend-hdr {
display: flex;
align-items: center;
gap: 0.35rem;
margin-bottom: 0.3rem;
}
.plot-legend-swatch {
width: 10px;
height: 10px;
border-radius: 2px;
flex-shrink: 0;
}
.plot-legend-name {
flex: 1;
font-size: 0.75rem;
color: #cbd5e1;
font-family: ui-monospace, monospace;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.plot-legend-icon-btn {
background: none;
border: none;
color: #475569;
cursor: pointer;
font-size: 0.75rem;
padding: 0 0.15rem;
line-height: 1;
opacity: 0.6;
border-radius: 3px;
}
.plot-legend-icon-btn:hover { opacity: 1; color: #94a3b8; }
.plot-legend-icon-btn.active { opacity: 1; color: #4a9eff; }
/* kept for back-compat — remove button is now .plot-legend-icon-btn */
.plot-legend-rm {
background: none;
border: none;
color: #475569;
cursor: pointer;
font-size: 0.7rem;
padding: 0 0.15rem;
opacity: 0.6;
}
.plot-legend-rm:hover { opacity: 1; color: #f87171; }
/* ── Per-signal style editor ────────────────────────────────────────────── */
.plot-style-editor {
margin-top: 0.4rem;
padding-top: 0.4rem;
border-top: 1px solid #1e293b;
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.plot-style-row {
display: flex;
align-items: center;
gap: 0.4rem;
}
.plot-style-label {
font-size: 0.68rem;
color: #64748b;
width: 2.8rem;
flex-shrink: 0;
}
.plot-style-color {
width: 28px;
height: 20px;
border: 1px solid #2d3748;
border-radius: 3px;
padding: 0;
cursor: pointer;
background: none;
}
.plot-style-btns {
display: flex;
gap: 2px;
}
.plot-style-btn {
background: #1e293b;
border: 1px solid #2d3748;
color: #64748b;
cursor: pointer;
font-size: 0.68rem;
padding: 1px 5px;
border-radius: 3px;
line-height: 1.4;
min-width: 1.6rem;
text-align: center;
}
.plot-style-btn:hover { color: #cbd5e1; border-color: #475569; }
.plot-style-btn.active { background: #1e3a5f; border-color: #4a9eff; color: #e2e8f0; }
.plot-stats-tbl {
width: 100%;
border-collapse: collapse;
font-size: 0.72rem;
}
.plot-stats-tbl td {
padding: 0.05rem 0;
color: #475569;
}
.plot-stats-tbl td:first-child {
width: 44px;
color: #334155;
font-size: 0.68rem;
}
.plot-stats-tbl td:last-child {
color: #94a3b8;
font-family: ui-monospace, monospace;
}
.plot-panel-chart {
flex: 1;
min-width: 0;
min-height: 0;
overflow: hidden;
}
/* uPlot inside plot-panel needs full dimensions */
.plot-panel-chart .uplot,
.plot-panel-chart .u-wrap {
width: 100% !important;
height: 100% !important;
}
/* ── Zoom control ─────────────────────────────────────────────────────────── */
.zoom-control {
@@ -2800,6 +3040,15 @@ kbd {
margin-left: 0.25rem;
}
/* Muted placeholder text inside the trigger. Unlike .hint it adds no padding,
so an empty select is exactly as tall as one showing a selected value. */
.search-select-placeholder {
color: #475569;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.search-select-dropdown {
position: absolute;
top: 100%;
@@ -2893,11 +3142,190 @@ kbd {
}
.panel-btn.icon-only {
padding: 0;
width: 1.6rem;
padding: 0 0.35rem;
min-width: 1.6rem;
width: auto;
height: 1.6rem;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.9rem;
}
.app-root {
display: flex;
flex-direction: column;
height: 100vh;
width: 100%;
overflow: hidden;
}
.view-panel-container {
display: none;
flex: 1;
min-height: 0;
position: relative;
}
.view-panel-container.active {
display: flex;
flex-direction: column;
}
/* ── Plot panels (split layout) ─────────────────────────────────────────── */
/* Edit surface: fills the editor body flex slot. */
.plot-panel-edit {
flex: 1;
min-width: 0;
min-height: 0;
position: relative;
overflow: hidden;
display: flex;
background: #0a0d14;
}
/* View/fullscreen surface: fills the (relative) canvas container. */
.plot-panel-view {
position: absolute;
inset: 0;
display: flex;
}
.split-root {
flex: 1;
display: flex;
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
}
.split-row { display: flex; flex-direction: row; flex: 1; min-width: 0; min-height: 0; }
.split-col { display: flex; flex-direction: column; flex: 1; min-width: 0; min-height: 0; }
.split-child {
display: flex;
min-width: 0;
min-height: 0;
overflow: hidden;
}
.split-leaf {
flex: 1;
display: flex;
position: relative;
min-width: 0;
min-height: 0;
}
.split-divider {
flex: 0 0 auto;
background: #1e293b;
transition: background 0.12s;
}
.split-divider-h { width: 6px; cursor: col-resize; }
.split-divider-v { height: 6px; cursor: row-resize; }
.split-divider:hover { background: #4a9eff; }
.split-divider-static { pointer-events: none; }
/* Neutralize the free-form absolute positioning of plot widgets so they fill
their split pane responsively (the chart uses a ResizeObserver). */
.split-leaf .plot-widget {
position: relative !important;
left: auto !important;
top: auto !important;
width: 100% !important;
height: 100% !important;
border-radius: 0;
}
.split-leaf .chart-area {
width: 100% !important;
height: 100% !important;
}
/* Editable pane chrome. */
.plot-pane {
position: relative;
flex: 1;
display: flex;
min-width: 0;
min-height: 0;
overflow: hidden;
background: #0f1117;
outline: 1px solid transparent;
}
/* Dim panes that are not the one currently being edited. */
.plot-panel-edit .plot-pane:not(.plot-pane-selected)::after {
content: '';
position: absolute;
inset: 0;
background: rgba(10, 14, 22, 0.45);
z-index: 4;
pointer-events: none;
}
.plot-pane-selected {
outline: 3px solid #4a9eff;
outline-offset: -3px;
box-shadow: inset 0 0 0 1px rgba(74, 158, 255, 0.6),
inset 0 0 18px rgba(74, 158, 255, 0.35);
}
.plot-pane-badge {
position: absolute;
top: 4px;
left: 4px;
z-index: 6;
padding: 1px 7px;
font-size: 10px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: #0b1018;
background: #4a9eff;
border-radius: 3px;
pointer-events: none;
}
.plot-pane-empty,
.plot-pane-hint {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
color: #475569;
font-size: 0.82rem;
text-align: center;
padding: 1rem;
pointer-events: none;
}
.plot-pane-hint { top: auto; bottom: 0.6rem; transform: translateX(-50%); }
.plot-pane-overlay {
position: absolute;
top: 4px;
right: 4px;
display: flex;
gap: 2px;
z-index: 5;
opacity: 0;
transition: opacity 0.12s;
}
.plot-pane:hover .plot-pane-overlay,
.plot-pane-selected .plot-pane-overlay {
opacity: 1;
}
.plot-pane-btn {
background: #1e293b;
border: 1px solid #2d3748;
color: #94a3b8;
cursor: pointer;
font-size: 0.8rem;
line-height: 1;
padding: 3px 6px;
border-radius: 3px;
}
.plot-pane-btn:hover { color: #e2e8f0; border-color: #4a9eff; }
.plot-pane-btn:disabled { opacity: 0.35; cursor: default; }