fix: leerer select in node im config panel leading to white screen

This commit is contained in:
Ida 2026-05-13 15:48:30 +02:00
parent 9e36075f0e
commit 600e0c87dc
3 changed files with 49 additions and 13 deletions

View file

@ -246,6 +246,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-width: 0; min-width: 0;
min-height: 0;
background: var(--canvas-bg, #fafafa); background: var(--canvas-bg, #fafafa);
} }
@ -594,7 +595,7 @@
.canvasArea { .canvasArea {
flex: 1; flex: 1;
padding: 0; padding: 0;
min-height: 400px; min-height: 0;
overflow-x: visible; overflow-x: visible;
overflow-y: hidden; overflow-y: hidden;
} }
@ -996,6 +997,16 @@
cursor: copy; cursor: copy;
} }
/* Shell: stretches to full canvas-area height so inner `.nodeConfigPanel` can scroll. */
.nodeConfigPanelWrap {
flex-shrink: 0;
align-self: stretch;
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
/* Node Config Panel /* Node Config Panel
* Fixed-width side panel. The `box-sizing: border-box` + `overflow-x: hidden` * Fixed-width side panel. The `box-sizing: border-box` + `overflow-x: hidden`
* pair acts as a safety net so long unbreakable strings (type names like * pair acts as a safety net so long unbreakable strings (type names like
@ -1005,11 +1016,12 @@
* a long label rather than escaping to the right. * a long label rather than escaping to the right.
*/ */
.nodeConfigPanel { .nodeConfigPanel {
flex: 1;
min-height: 0;
padding: 1rem; padding: 1rem;
background: var(--bg-primary, #fff); background: var(--bg-primary, #fff);
border-left: 1px solid var(--border-color, #e0e0e0); border-left: 1px solid var(--border-color, #e0e0e0);
width: 280px; width: 280px;
flex-shrink: 0;
box-sizing: border-box; box-sizing: border-box;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;

View file

@ -957,8 +957,8 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
onVerboseSchemaChange={setVerboseSchema} onVerboseSchemaChange={setVerboseSchema}
canvasEdit={canvasHeaderEdit} canvasEdit={canvasHeaderEdit}
/> />
<div className={styles.canvasArea} style={{ display: 'flex', flex: 1, minWidth: 0 }}> <div className={styles.canvasArea} style={{ display: 'flex', flex: 1, minWidth: 0, alignItems: 'stretch' }}>
<div style={{ flex: 1, minWidth: 0 }}> <div style={{ flex: 1, minWidth: 0, minHeight: 0 }}>
<FlowCanvas <FlowCanvas
ref={flowCanvasRef} ref={flowCanvasRef}
nodes={canvasNodes} nodes={canvasNodes}
@ -995,7 +995,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
/> />
</div> </div>
{configurableSelected && selectedNode && ( {configurableSelected && selectedNode && (
<div style={{ flexShrink: 0, display: 'flex', flexDirection: 'column' }}> <div className={styles.nodeConfigPanelWrap}>
<Automation2DataFlowProvider <Automation2DataFlowProvider
node={selectedNode} node={selectedNode}
nodes={canvasNodes} nodes={canvasNodes}

View file

@ -100,9 +100,30 @@ const DateInput: React.FC<FieldRendererProps> = ({ param, value, onChange }) =>
</div> </div>
); );
/** Backend may send `options: ["a","b"]` or `options: [{ value, label }, ...]` (e.g. context.extractContent). */
function _normalizedSelectOptions(raw: unknown): Array<{ value: string; label: string }> {
if (!Array.isArray(raw)) return [];
const out: Array<{ value: string; label: string }> = [];
for (const item of raw) {
if (typeof item === 'string') {
out.push({ value: item, label: item });
continue;
}
if (item && typeof item === 'object' && 'value' in item) {
const rec = item as { value?: unknown; label?: unknown };
if (typeof rec.value === 'string') {
const label = typeof rec.label === 'string' && rec.label.length > 0 ? rec.label : rec.value;
out.push({ value: rec.value, label });
}
}
}
return out;
}
const SelectInput: React.FC<FieldRendererProps> = ({ param, value, onChange }) => { const SelectInput: React.FC<FieldRendererProps> = ({ param, value, onChange }) => {
const options: string[] = const options = _normalizedSelectOptions(
(param.frontendOptions?.options as string[]) || (param.options as string[]) || []; param.frontendOptions?.options ?? param.options ?? []
);
return ( return (
<div style={{ marginBottom: 8 }}> <div style={{ marginBottom: 8 }}>
<label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>{param.description || param.name}</label> <label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>{param.description || param.name}</label>
@ -113,7 +134,9 @@ const SelectInput: React.FC<FieldRendererProps> = ({ param, value, onChange }) =
> >
<option value=""></option> <option value=""></option>
{options.map((opt) => ( {options.map((opt) => (
<option key={opt} value={opt}>{opt}</option> <option key={opt.value} value={opt.value}>
{opt.label}
</option>
))} ))}
</select> </select>
</div> </div>
@ -121,8 +144,9 @@ const SelectInput: React.FC<FieldRendererProps> = ({ param, value, onChange }) =
}; };
const MultiSelectInput: React.FC<FieldRendererProps> = ({ param, value, onChange }) => { const MultiSelectInput: React.FC<FieldRendererProps> = ({ param, value, onChange }) => {
const options: string[] = const options = _normalizedSelectOptions(
(param.frontendOptions?.options as string[]) || (param.options as string[]) || []; param.frontendOptions?.options ?? param.options ?? []
);
const selected = Array.isArray(value) ? value : []; const selected = Array.isArray(value) ? value : [];
const toggle = (opt: string) => { const toggle = (opt: string) => {
const next = selected.includes(opt) ? selected.filter((v: string) => v !== opt) : [...selected, opt]; const next = selected.includes(opt) ? selected.filter((v: string) => v !== opt) : [...selected, opt];
@ -133,9 +157,9 @@ const MultiSelectInput: React.FC<FieldRendererProps> = ({ param, value, onChange
<label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>{param.description || param.name}</label> <label style={{ display: 'block', fontSize: 12, marginBottom: 2 }}>{param.description || param.name}</label>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}> <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
{options.map((opt) => ( {options.map((opt) => (
<label key={opt} style={{ fontSize: 12, display: 'flex', alignItems: 'center', gap: 2 }}> <label key={opt.value} style={{ fontSize: 12, display: 'flex', alignItems: 'center', gap: 2 }}>
<input type="checkbox" checked={selected.includes(opt)} onChange={() => toggle(opt)} /> <input type="checkbox" checked={selected.includes(opt.value)} onChange={() => toggle(opt.value)} />
{opt} {opt.label}
</label> </label>
))} ))}
</div> </div>