feat: added visibility toggle with autoHide option

ref: N25B-450
This commit is contained in:
JGerla
2026-01-23 17:10:48 +01:00
parent 820884f8aa
commit 85b84c2281
3 changed files with 134 additions and 30 deletions

View File

@@ -7,6 +7,7 @@ import {
MarkerType, getOutgoers MarkerType, getOutgoers
} from '@xyflow/react'; } from '@xyflow/react';
import '@xyflow/react/dist/style.css'; import '@xyflow/react/dist/style.css';
import warningStyles from './visualProgrammingUI/components/WarningSidebar.module.css'
import {type CSSProperties, useEffect, useState} from "react"; import {type CSSProperties, useEffect, useState} from "react";
import {useShallow} from 'zustand/react/shallow'; import {useShallow} from 'zustand/react/shallow';
import orderPhaseNodeArray from "../../utils/orderPhaseNodes.ts"; import orderPhaseNodeArray from "../../utils/orderPhaseNodes.ts";
@@ -114,8 +115,6 @@ const VisProgUI = () => {
} }
},[edges, registerWarning, unregisterWarning]) },[edges, registerWarning, unregisterWarning])
return ( return (
<div className={`${styles.innerEditorContainer} round-lg border-lg flex-row`} style={({'--flow-zoom': zoom} as CSSProperties)}> <div className={`${styles.innerEditorContainer} round-lg border-lg flex-row`} style={({'--flow-zoom': zoom} as CSSProperties)}>
<ReactFlow <ReactFlow
@@ -152,10 +151,13 @@ const VisProgUI = () => {
<button onClick={() => redo()}>Redo</button> <button onClick={() => redo()}>Redo</button>
</Panel> </Panel>
<Panel position="center-right" className={warningStyles.warningsSidebar}>
<WarningsSidebar/>
</Panel>
<Controls/> <Controls/>
<Background/> <Background/>
</ReactFlow> </ReactFlow>
<WarningsSidebar/>
</div> </div>
); );
}; };
@@ -221,7 +223,6 @@ const checkPhaseChain = (): boolean => {
const next = outgoingPhases.map(node => checkForCompleteChain(node.id)) const next = outgoingPhases.map(node => checkForCompleteChain(node.id))
.find(result => result); .find(result => result);
console.log(next);
return !!next; return !!next;
} }
@@ -246,7 +247,6 @@ function VisProgPage() {
// however this would cause unneeded updates // however this would cause unneeded updates
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [severityIndex]); }, [severityIndex]);
console.log(severityIndex);
return ( return (
<> <>
<VisualProgrammingUI/> <VisualProgrammingUI/>

View File

@@ -1,12 +1,51 @@
.warnings-sidebar { .warnings-sidebar {
min-width: 320px; min-width: auto;
max-width: 320px; max-width: 340px;
width: 320px; margin-right: 0;
height: 100%; height: 100%;
background: canvas; background: canvas;
border-left: 2px solid CanvasText;
display: flex; display: flex;
flex-direction: row;
}
.warnings-toggle-bar {
background-color: ButtonFace;
justify-items: center;
align-content: center;
width: 1rem;
cursor: pointer;
}
.warnings-toggle-bar.error:first-child:has(.arrow-right){
background-color: hsl(from red h s 75%);
}
.warnings-toggle-bar.warning:first-child:has(.arrow-right) {
background-color: hsl(from orange h s 75%);
}
.warnings-toggle-bar.info:first-child:has(.arrow-right) {
background-color: hsl(from steelblue h s 75%);
}
.warnings-toggle-bar:hover {
background-color: GrayText !important ;
.arrow-left {
border-right-color: ButtonFace;
transition: transform 0.15s ease-in-out;
transform: rotateY(180deg);
}
.arrow-right {
border-left-color: ButtonFace;
transition: transform 0.15s ease-in-out;
transform: rotateY(180deg);
}
}
.warnings-content {
width: 320px;
flex: 1;
flex-direction: column; flex-direction: column;
border-left: 2px solid CanvasText;
} }
.warnings-header { .warnings-header {
@@ -45,12 +84,14 @@
.warning-group-header { .warning-group-header {
background: ButtonFace; background: ButtonFace;
padding: 4px; padding: 6px;
font-weight: bold;
} }
.warnings-list { .warnings-list {
flex: 1; flex: 1;
overflow-y: auto; min-height: 0;
overflow-y: scroll;
} }
.warnings-empty { .warnings-empty {
@@ -131,4 +172,32 @@
.warning-item .description { .warning-item .description {
padding: 5px 10px; padding: 5px 10px;
font-size: 0.8rem; font-size: 0.8rem;
} }
.auto-hide {
background-color: Canvas;
border-top: 2px solid CanvasText;
margin-top: auto;
width: 100%;
height: 2.5rem;
display: flex;
align-items: center;
padding: 0 12px;
}
/* arrows for toggleBar */
.arrow-right {
width: 0;
height: 0;
border-top: 0.5rem solid transparent;
border-bottom: 0.5rem solid transparent;
border-left: 0.6rem solid GrayText;
}
.arrow-left {
width: 0;
height: 0;
border-top: 0.5rem solid transparent;
border-bottom: 0.5rem solid transparent;
border-right: 0.6rem solid GrayText;
}

View File

@@ -11,23 +11,64 @@ import styles from "./WarningSidebar.module.css";
export function WarningsSidebar() { export function WarningsSidebar() {
const warnings = useFlowStore.getState().getWarnings(); const warnings = useFlowStore.getState().getWarnings();
const [hide, setHide] = useState(false);
const [severityFilter, setSeverityFilter] = useState<WarningSeverity | 'ALL'>('ALL'); const [severityFilter, setSeverityFilter] = useState<WarningSeverity | 'ALL'>('ALL');
const [autoHide, setAutoHide] = useState(false);
// let autohide change hide status only when autohide is toggled
// and allow for user to change the hide state even if autohide is enabled
const hasWarnings = warnings.length > 0;
useEffect(() => {
if (autoHide) {
setHide(!hasWarnings);
}
}, [autoHide, hasWarnings]);
useEffect(() => {}, [warnings]);
const filtered = severityFilter === 'ALL' const filtered = severityFilter === 'ALL'
? warnings ? warnings
: warnings.filter(w => w.severity === severityFilter); : warnings.filter(w => w.severity === severityFilter);
return (
<aside id="warningSidebar" className={styles.warningsSidebar}>
<WarningsHeader
severityFilter={severityFilter}
onChange={setSeverityFilter}
/>
<WarningsList warnings={filtered} /> const summary = warningSummary();
// Finds the first key where the count > 0
const getHighestSeverity = () => {
if (summary.error > 0) return styles.error;
if (summary.warning > 0) return styles.warning;
if (summary.info > 0) return styles.info;
return '';
};
return (
<aside className={`flex-row`} >
<div
className={`${styles.warningsToggleBar} ${getHighestSeverity()}`}
onClick={() => setHide(!hide)}
title={"toggle warnings"}
>
<div className={`${hide ? styles.arrowRight : styles.arrowLeft}`}></div>
</div>
<div
id="warningSidebar"
className={styles.warningsContent}
style={hide ? {display: "none"} : {display: "flex"}}
>
<WarningsHeader
severityFilter={severityFilter}
onChange={setSeverityFilter}
/>
<WarningsList warnings={filtered} />
<div className={styles.autoHide}>
<input
id="autoHideSwitch"
type={"checkbox"}
checked={autoHide}
onChange={(e) => setAutoHide(e.target.checked)}
/><label>Hide if there are no warnings</label>
</div>
</div>
</aside> </aside>
); );
} }
@@ -43,7 +84,6 @@ function WarningsHeader({
return ( return (
<div className={styles.warningsHeader}> <div className={styles.warningsHeader}>
<h3>Warnings</h3> <h3>Warnings</h3>
<div className={styles.severityTabs}> <div className={styles.severityTabs}>
{(['ALL', 'ERROR', 'WARNING', 'INFO'] as const).map(severity => ( {(['ALL', 'ERROR', 'WARNING', 'INFO'] as const).map(severity => (
<button <button
@@ -71,7 +111,6 @@ function WarningsList(props: { warnings: EditorWarning[] }) {
global: props.warnings.filter(w => w.scope.id === globalWarning), global: props.warnings.filter(w => w.scope.id === globalWarning),
other: props.warnings.filter(w => w.scope.id !== globalWarning), other: props.warnings.filter(w => w.scope.id !== globalWarning),
} }
console.log(splitWarnings);
if (props.warnings.length === 0) { if (props.warnings.length === 0) {
return ( return (
<div className={styles.warningsEmpty}> <div className={styles.warningsEmpty}>
@@ -80,10 +119,9 @@ function WarningsList(props: { warnings: EditorWarning[] }) {
) )
} }
return ( return (
<div style={{overflowY: 'auto'}}> <div className={styles.warningsList}>
<div className={"warningGroup"}>
<div className={styles.warningGroupHeader}>global:</div> <div className={styles.warningGroupHeader}>global:</div>
<div className={styles.warningsList}> <div className={styles.warningsGroup}>
{splitWarnings.global.map((warning) => ( {splitWarnings.global.map((warning) => (
<WarningListItem warning={warning} key={`${warning.scope.id}|${warning.type}` + (warning.scope.handleId <WarningListItem warning={warning} key={`${warning.scope.id}|${warning.type}` + (warning.scope.handleId
? `:${warning.scope.handleId}` ? `:${warning.scope.handleId}`
@@ -92,10 +130,8 @@ function WarningsList(props: { warnings: EditorWarning[] }) {
))} ))}
{splitWarnings.global.length === 0 && "No global warnings!"} {splitWarnings.global.length === 0 && "No global warnings!"}
</div> </div>
</div>
<div className={"warningGroup"}>
<div className={styles.warningGroupHeader}>other:</div> <div className={styles.warningGroupHeader}>other:</div>
<div className={styles.warningsList}> <div className={styles.warningsGroup}>
{splitWarnings.other.map((warning) => ( {splitWarnings.other.map((warning) => (
<WarningListItem warning={warning} key={`${warning.scope.id}|${warning.type}` + (warning.scope.handleId <WarningListItem warning={warning} key={`${warning.scope.id}|${warning.type}` + (warning.scope.handleId
? `:${warning.scope.handleId}` ? `:${warning.scope.handleId}`
@@ -104,7 +140,6 @@ function WarningsList(props: { warnings: EditorWarning[] }) {
))} ))}
{splitWarnings.other.length === 0 && "No other warnings!"} {splitWarnings.other.length === 0 && "No other warnings!"}
</div> </div>
</div>
</div> </div>
); );
} }