Implemented admin pane + user permission

This commit is contained in:
Martino Ferrari
2026-06-22 17:49:14 +02:00
parent 73fcbe7b28
commit ac24011487
67 changed files with 5925 additions and 411 deletions
+52 -1
View File
@@ -18,10 +18,13 @@ import PlotWidget from './widgets/PlotWidget';
import ImageWidget from './widgets/ImageWidget';
import LinkWidget from './widgets/LinkWidget';
import ConfigSelect from './widgets/ConfigSelect';
import Container from './widgets/Container';
import TableWidget from './widgets/TableWidget';
import ContextMenu from './ContextMenu';
import InfoPanel from './InfoPanel';
import SplitLayout from './SplitLayout';
import LogicDialogs from './LogicDialogs';
import { centreInside, widgetTab, tabPanes } from './lib/containers';
const COMPONENTS: Record<string, any> = {
textview: TextView,
@@ -38,8 +41,18 @@ const COMPONENTS: Record<string, any> = {
image: ImageWidget,
link: LinkWidget,
configselect: ConfigSelect,
container: Container,
table: TableWidget,
};
// Container panes are decorative frames placed behind other widgets; render them
// first so they paint underneath.
function renderOrder(widgets: Widget[]): Widget[] {
const containers = widgets.filter(w => w.type === 'container');
const rest = widgets.filter(w => w.type !== 'container');
return [...containers, ...rest];
}
interface CtxState {
visible: boolean;
x: number;
@@ -84,6 +97,26 @@ export default function Canvas({ iface, onNavigate, timeRange }: Props) {
visible: false, x: 0, y: 0, signal: null,
});
const [infoSignal, setInfoSignal] = useState<SignalRef | null>(null);
// View-mode collapse state for collapsible container panes (ephemeral, by id).
const [collapsed, setCollapsed] = useState<Set<string>>(() => new Set());
// View-mode active-tab state for tabbed container panes (ephemeral, by id).
const [activeTabs, setActiveTabs] = useState<Map<string, number>>(() => new Map());
function toggleCollapse(id: string) {
setCollapsed(prev => {
const next = new Set(prev);
next.has(id) ? next.delete(id) : next.add(id);
return next;
});
}
function selectTab(id: string, i: number) {
setActiveTabs(prev => {
const next = new Map(prev);
next.set(id, i);
return next;
});
}
// Instantiate this panel's local state variables from their initial values.
useEffect(() => {
@@ -157,10 +190,24 @@ export default function Canvas({ iface, onNavigate, timeRange }: Props) {
);
}
// Collapsible panes currently in the collapsed state — their contents are hidden.
const collapsedPanes = iface.widgets.filter(
w => w.type === 'container' && w.options['collapsible'] === 'true' && collapsed.has(w.id),
);
// Tabbed panes — widgets inside that aren't on the active tab are hidden.
const tPanes = tabPanes(iface.widgets);
return (
<div class="canvas-container">
<div class="canvas-area canvas-view-bare" style={`width:${iface.w}px;height:${iface.h}px;`}>
{iface.widgets.map(widget => {
{renderOrder(iface.widgets).map(widget => {
// Hide widgets that fall inside a currently-collapsed container pane.
const hidden = collapsedPanes.some(c => c.id !== widget.id && centreInside(widget, c));
// Hide widgets inside a tabbed pane that aren't on its active tab.
const tabHidden = tPanes.some(c =>
c.id !== widget.id && centreInside(widget, c) && widgetTab(widget) !== (activeTabs.get(c.id) ?? 0),
);
if (hidden || tabHidden) return null;
const Comp = COMPONENTS[widget.type];
const inner = Comp
? <Comp
@@ -168,6 +215,10 @@ export default function Canvas({ iface, onNavigate, timeRange }: Props) {
onContextMenu={(e: MouseEvent) => onCtxMenu(e, widget)}
onNavigate={onNavigate}
timeRange={timeRange}
collapsed={collapsed.has(widget.id)}
onToggleCollapse={() => toggleCollapse(widget.id)}
activeTab={activeTabs.get(widget.id) ?? 0}
onSelectTab={(i: number) => selectTab(widget.id, i)}
/>
: (
<div