fix: auto-refresh preview after element edits, improved rendering
Preview now reloads display data whenever elements change. Card style renders with proper label/value/unit layout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
19497fe335
commit
4964a2e8ab
@ -9,37 +9,35 @@ interface LayoutElement {
|
||||
width: number | null;
|
||||
height: number | null;
|
||||
font_size: number;
|
||||
font_weight: string;
|
||||
text_align: string;
|
||||
style: string;
|
||||
static_text: string | null;
|
||||
datapoint_id: number | null;
|
||||
icon: string | null;
|
||||
}
|
||||
|
||||
interface DisplayJson {
|
||||
display_id: number;
|
||||
layout: {
|
||||
id: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
elements: Array<{
|
||||
type: string;
|
||||
x: number;
|
||||
y: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
font_size: number;
|
||||
style: string;
|
||||
static_text?: string;
|
||||
label?: string;
|
||||
value?: string;
|
||||
unit?: string;
|
||||
}>;
|
||||
interface DisplayApiElement {
|
||||
type: string;
|
||||
x: number;
|
||||
y: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
font_size?: number;
|
||||
font_weight?: string;
|
||||
text_align?: string;
|
||||
style?: string;
|
||||
icon?: string;
|
||||
text?: string;
|
||||
label?: string;
|
||||
value?: string | null;
|
||||
unit?: string | null;
|
||||
}
|
||||
|
||||
interface Display {
|
||||
id: number;
|
||||
name: string;
|
||||
layout_id: number | null;
|
||||
interface DisplayApiResponse {
|
||||
display: { id: number; name: string; width: number; height: number; refresh_sec: number };
|
||||
layout: { type: string };
|
||||
elements: DisplayApiElement[];
|
||||
}
|
||||
|
||||
interface EpaperPreviewProps {
|
||||
@ -49,119 +47,146 @@ interface EpaperPreviewProps {
|
||||
layoutHeight?: number;
|
||||
}
|
||||
|
||||
const SCALE = 0.5;
|
||||
const SCALE = 0.6;
|
||||
|
||||
export default function EpaperPreview({ layoutId, elements, layoutWidth = 800, layoutHeight = 480 }: EpaperPreviewProps) {
|
||||
const [displayJson, setDisplayJson] = useState<DisplayJson | null>(null);
|
||||
const [liveElements, setLiveElements] = useState<DisplayApiElement[] | null>(null);
|
||||
const [displayId, setDisplayId] = useState<number | null>(null);
|
||||
|
||||
// Find a display that uses this layout
|
||||
useEffect(() => {
|
||||
// Find a display using this layout and fetch its JSON
|
||||
api.get<Display[]>('/api/displays')
|
||||
api.get<any[]>('/api/displays')
|
||||
.then((displays) => {
|
||||
const d = displays.find((disp) => disp.layout_id === layoutId);
|
||||
if (!d) return;
|
||||
return api.get<DisplayJson>(`/api/display/${d.id}`).then(setDisplayJson).catch(() => {});
|
||||
const d = displays.find((disp: any) => disp.layout_id === layoutId);
|
||||
setDisplayId(d ? d.id : null);
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(() => setDisplayId(null));
|
||||
}, [layoutId]);
|
||||
|
||||
// Reload display data whenever elements change OR displayId is found
|
||||
useEffect(() => {
|
||||
if (!displayId) { setLiveElements(null); return; }
|
||||
api.get<DisplayApiResponse>(`/api/display/${displayId}`)
|
||||
.then((data) => setLiveElements(data.elements))
|
||||
.catch(() => setLiveElements(null));
|
||||
}, [displayId, elements]); // elements is a new array ref after each selectLayout()
|
||||
|
||||
const w = layoutWidth * SCALE;
|
||||
const h = layoutHeight * SCALE;
|
||||
|
||||
const renderEl = (el: typeof elements[0], _idx?: number) => {
|
||||
const left = el.x * SCALE;
|
||||
const top = el.y * SCALE;
|
||||
const fs = Math.max(8, el.font_size * SCALE);
|
||||
|
||||
if (el.type === 'line') {
|
||||
return (
|
||||
<div
|
||||
key={el.id}
|
||||
style={{ position: 'absolute', left, top, width: (el.width ?? 100) * SCALE, height: 1, backgroundColor: '#000' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (el.type === 'rect') {
|
||||
return (
|
||||
<div
|
||||
key={el.id}
|
||||
style={{
|
||||
position: 'absolute', left, top,
|
||||
width: (el.width ?? 80) * SCALE,
|
||||
height: (el.height ?? 40) * SCALE,
|
||||
border: '1px solid #000',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// label or value elements
|
||||
const text = el.static_text ?? (el.type === 'label' ? `[DP ${el.datapoint_id ?? '?'}]` : `[Wert]`);
|
||||
const isCard = el.style === 'card';
|
||||
const renderElements = liveElements || [];
|
||||
const isLive = liveElements !== null;
|
||||
|
||||
// If no display linked and no elements, show hint
|
||||
if (!isLive && elements.length === 0) {
|
||||
return (
|
||||
<div
|
||||
key={el.id}
|
||||
style={{
|
||||
position: 'absolute', left, top,
|
||||
fontSize: fs,
|
||||
fontFamily: 'monospace',
|
||||
color: '#000',
|
||||
background: isCard ? '#e8e8e8' : 'transparent',
|
||||
border: isCard ? '1px solid #aaa' : 'none',
|
||||
padding: isCard ? '2px 4px' : 0,
|
||||
whiteSpace: 'nowrap',
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
<div className="mt-4 p-4 bg-gray-50 rounded border text-sm text-gray-400 text-center">
|
||||
Fuege Elemente hinzu um die Vorschau zu sehen.
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
// Use live display JSON if available, else render from elements prop
|
||||
const liveEls = displayJson?.elements;
|
||||
// Build render list: use live data if available, otherwise use raw elements
|
||||
const items: DisplayApiElement[] = isLive
|
||||
? renderElements
|
||||
: elements.map((el) => ({
|
||||
type: el.type,
|
||||
x: el.x,
|
||||
y: el.y,
|
||||
width: el.width ?? undefined,
|
||||
height: el.height ?? undefined,
|
||||
font_size: el.font_size,
|
||||
font_weight: el.font_weight,
|
||||
text_align: el.text_align,
|
||||
style: el.style,
|
||||
icon: el.icon ?? undefined,
|
||||
text: el.type === 'label' ? (el.static_text || '') : undefined,
|
||||
label: el.type === 'value' ? `DP #${el.datapoint_id || '?'}` : undefined,
|
||||
value: el.type === 'value' ? '—' : undefined,
|
||||
unit: '',
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<p className="text-xs text-gray-500 mb-2">
|
||||
E-Paper Vorschau ({layoutWidth}x{layoutHeight}px){liveEls ? ' — Live-Daten' : ' — Design-Modus'}
|
||||
Vorschau ({layoutWidth}x{layoutHeight})
|
||||
{isLive ? ' — Live-Daten' : ' — Layout-Struktur (kein Display zugewiesen)'}
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: w,
|
||||
height: h,
|
||||
backgroundColor: '#f5f0e8',
|
||||
border: '2px solid #1a1a1a',
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
boxShadow: '2px 2px 8px rgba(0,0,0,0.2)',
|
||||
}}
|
||||
>
|
||||
{liveEls
|
||||
? liveEls.map((el, idx) => {
|
||||
const left = el.x * SCALE;
|
||||
const top = el.y * SCALE;
|
||||
const fs = Math.max(8, el.font_size * SCALE);
|
||||
<div style={{
|
||||
position: 'relative', width: w, height: h,
|
||||
backgroundColor: '#f0f0e8', border: '3px solid #222',
|
||||
borderRadius: 4, overflow: 'hidden',
|
||||
boxShadow: '2px 2px 8px rgba(0,0,0,0.25)',
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
}}>
|
||||
{items.map((el, idx) => {
|
||||
const left = el.x * SCALE;
|
||||
const top = el.y * SCALE;
|
||||
const fontSize = Math.max(7, (el.font_size || 24) * SCALE);
|
||||
const isBold = el.font_weight === 'bold';
|
||||
|
||||
if (el.type === 'line') {
|
||||
return <div key={idx} style={{ position: 'absolute', left, top, width: (el.width ?? 100) * SCALE, height: 1, backgroundColor: '#000' }} />;
|
||||
}
|
||||
if (el.type === 'rect') {
|
||||
return <div key={idx} style={{ position: 'absolute', left, top, width: (el.width ?? 80) * SCALE, height: (el.height ?? 40) * SCALE, border: '1px solid #000' }} />;
|
||||
}
|
||||
if (el.type === 'line') {
|
||||
return <div key={idx} style={{
|
||||
position: 'absolute', left, top,
|
||||
width: (el.width || 800) * SCALE,
|
||||
height: Math.max(1, (el.height || 2) * SCALE),
|
||||
backgroundColor: '#000',
|
||||
}} />;
|
||||
}
|
||||
|
||||
const text = el.static_text ?? (el.type === 'value' ? `${el.value ?? '?'} ${el.unit ?? ''}` : (el.label ?? ''));
|
||||
const isCard = el.style === 'card';
|
||||
if (el.type === 'rect') {
|
||||
return <div key={idx} style={{
|
||||
position: 'absolute', left, top,
|
||||
width: (el.width || 100) * SCALE,
|
||||
height: (el.height || 50) * SCALE,
|
||||
border: '1px solid #000',
|
||||
}} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={idx} style={{ position: 'absolute', left, top, fontSize: fs, fontFamily: 'monospace', color: '#000', background: isCard ? '#e8e8e8' : 'transparent', border: isCard ? '1px solid #aaa' : 'none', padding: isCard ? '2px 4px' : 0, whiteSpace: 'nowrap', lineHeight: 1.2 }}>
|
||||
{text}
|
||||
if (el.type === 'label') {
|
||||
const align = el.text_align || 'left';
|
||||
return <div key={idx} style={{
|
||||
position: 'absolute', left, top,
|
||||
fontSize, fontWeight: isBold ? 'bold' : 'normal',
|
||||
color: '#000', whiteSpace: 'nowrap',
|
||||
...(align === 'center' ? { width: w, left: 0, textAlign: 'center' } : {}),
|
||||
...(align === 'right' ? { right: 0, textAlign: 'right' } : {}),
|
||||
}}>
|
||||
{el.text || ''}
|
||||
</div>;
|
||||
}
|
||||
|
||||
if (el.type === 'value') {
|
||||
const isCard = el.style === 'card';
|
||||
const elW = (el.width || 370) * SCALE;
|
||||
const elH = (el.height || 90) * SCALE;
|
||||
|
||||
if (isCard) {
|
||||
return <div key={idx} style={{
|
||||
position: 'absolute', left, top,
|
||||
width: elW, height: elH,
|
||||
border: '1px solid #aaa', borderRadius: 4 * SCALE,
|
||||
background: '#e8e8e0', padding: 4 * SCALE,
|
||||
display: 'flex', flexDirection: 'column', justifyContent: 'space-between',
|
||||
}}>
|
||||
<div style={{ fontSize: fontSize * 0.45, color: '#555', textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
||||
{el.icon ? `[${el.icon}] ` : ''}{el.label || ''}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: elements.map((el) => renderEl(el, el.id))}
|
||||
<div style={{ fontSize, fontWeight: 'bold', textAlign: 'right' }}>
|
||||
{el.value ?? '—'} <span style={{ fontSize: fontSize * 0.5, color: '#555' }}>{el.unit || ''}</span>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <div key={idx} style={{
|
||||
position: 'absolute', left, top, fontSize, color: '#000', whiteSpace: 'nowrap',
|
||||
}}>
|
||||
{el.label}: <strong>{el.value ?? '—'}</strong> {el.unit || ''}
|
||||
</div>;
|
||||
}
|
||||
|
||||
return null;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user