bfa-dryer-design/static/hmi-blocks.js
Richard Sauer 1831759563 V2: GrapesJS-based HMI design editor
Complete rewrite of the editor frontend using GrapesJS:
- Full drag/drop visual editor with style manager
- Custom HMI blocks: Temperature, Motor, Output, Burner, Automation, Gauge
- Layout blocks: Page Container, Tab Bar, Top Bar, Label, Divider, Spacer
- Style panel: font family/size/weight/color, background, border, layout, effects
- Traits panel: active/inactive colors, animation, linked control, setpoints
- Device manager: Tab5 (1280x720), Schneider HMIDT651 (1280x800), Desktop
- Theme switcher: Dark Industrial, Light Industrial, High Contrast, Classic SCADA
- Layers panel with z-index reorder and show/hide
- Undo/redo, preview mode, canvas zoom
- V1 layout preserved (saved as current.json), V2 uses current_v2.json
- Login/users/comments backend unchanged

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 17:06:01 +10:00

278 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* BFA Banana Dryer — Custom HMI Blocks for GrapesJS */
function registerHMIBlocks(editor) {
const bm = editor.BlockManager;
const dm = editor.DomComponents;
// ── Shared styles ──
const cardBase = {
'display': 'flex', 'flex-direction': 'column', 'align-items': 'center', 'justify-content': 'center',
'gap': '6px', 'padding': '16px', 'border-radius': '10px', 'cursor': 'pointer',
'min-height': '120px', 'width': '48%', 'font-family': 'Segoe UI, system-ui, sans-serif',
'border': '1px solid #1e2a45', 'transition': 'background 0.15s', 'box-sizing': 'border-box'
};
// ══════════════════════════════════════════
// TEMPERATURE CARD
// ══════════════════════════════════════════
dm.addType('temp-card', {
model: {
defaults: {
tagName: 'div',
draggable: true,
droppable: false,
attributes: {'data-type': 'temp'},
traits: [
{type: 'text', name: 'label', label: 'Name', changeProp: true},
{type: 'color', name: 'temp_color', label: 'Temp Color', changeProp: true},
{type: 'number', name: 'setpoint', label: 'Setpoint (°C)', changeProp: true, min: 0, max: 200},
{type: 'text', name: 'linked_id', label: 'Linked Control ID', changeProp: true},
],
label: 'Heat Input',
temp_color: '#ff4444',
setpoint: 130,
linked_id: '',
components: [
{tagName: 'div', attributes: {'data-role': 'name'}, content: 'Heat Input',
style: {'font-size': '20px', 'color': '#7a8baa'}},
{tagName: 'div', attributes: {'data-role': 'value'}, content: '-- °C',
style: {'font-size': '36px', 'font-weight': '700', 'color': '#ff4444'}},
{tagName: 'div', attributes: {'data-role': 'bar'},
style: {'width': '80%', 'height': '6px', 'background': '#1a2240', 'border-radius': '3px', 'overflow': 'hidden'},
components: [{tagName: 'div', style: {'width': '50%', 'height': '100%', 'background': '#ff4444', 'border-radius': '3px'}}]},
{tagName: 'div', attributes: {'data-role': 'sp'}, content: 'SP: 130 °C',
style: {'font-size': '14px', 'color': '#4a5670'}},
],
style: {...cardBase, 'background': '#131a2b'},
}
}
});
bm.add('temp-card', {
label: '🌡 Temperature',
category: 'HMI Controls',
content: {type: 'temp-card'},
attributes: {class: 'gjs-fonts gjs-f-b1'}
});
// ══════════════════════════════════════════
// MOTOR CARD
// ══════════════════════════════════════════
dm.addType('motor-card', {
model: {
defaults: {
tagName: 'div',
draggable: true,
droppable: false,
attributes: {'data-type': 'motor'},
traits: [
{type: 'text', name: 'label', label: 'Name', changeProp: true},
{type: 'number', name: 'speed_sp', label: 'Speed SP (%)', changeProp: true, min: 0, max: 100, step: 5},
{type: 'color', name: 'active_color', label: 'Active Color', changeProp: true},
{type: 'color', name: 'inactive_color', label: 'Inactive Color', changeProp: true},
{type: 'select', name: 'animation', label: 'Animation', changeProp: true,
options: [{value:'none'},{value:'fan'},{value:'agitator'},{value:'conveyor'},{value:'pulse'},{value:'vibrate'},{value:'glow'},{value:'blink'},{value:'flow'}]},
{type: 'text', name: 'linked_id', label: 'Linked Control ID', changeProp: true},
],
label: 'Motor',
speed_sp: 50,
active_color: '#00c853',
inactive_color: '#ff1744',
animation: 'none',
linked_id: '',
components: [
{tagName: 'div', style: {'display': 'flex', 'gap': '10px', 'align-items': 'center'},
components: [
{tagName: 'span', attributes: {'data-role': 'name'}, content: 'Motor',
style: {'font-size': '24px', 'font-weight': '600', 'color': '#e8ecf4'}},
{tagName: 'span', attributes: {'data-role': 'speed'}, content: '50%',
style: {'font-size': '24px', 'color': '#7a8baa'}},
]},
{tagName: 'div', attributes: {'data-role': 'state'}, content: 'OFF',
style: {'font-size': '36px', 'font-weight': '700', 'color': '#e8ecf4'}},
],
style: {...cardBase, 'background': '#ff1744'},
}
}
});
bm.add('motor-card', {
label: '⚙ Motor / Speed',
category: 'HMI Controls',
content: {type: 'motor-card'},
});
// ══════════════════════════════════════════
// OUTPUT CARD
// ══════════════════════════════════════════
dm.addType('output-card', {
model: {
defaults: {
tagName: 'div',
draggable: true,
droppable: false,
attributes: {'data-type': 'output'},
traits: [
{type: 'text', name: 'label', label: 'Name', changeProp: true},
{type: 'color', name: 'active_color', label: 'Active Color', changeProp: true},
{type: 'color', name: 'inactive_color', label: 'Inactive Color', changeProp: true},
{type: 'text', name: 'linked_id', label: 'Linked Control ID', changeProp: true},
],
label: 'Output',
active_color: '#00c853',
inactive_color: '#ff1744',
linked_id: '',
components: [
{tagName: 'div', attributes: {'data-role': 'name'}, content: 'Output',
style: {'font-size': '20px', 'text-align': 'center', 'color': '#e8ecf4'}},
{tagName: 'div', attributes: {'data-role': 'state'}, content: 'OFF',
style: {'font-size': '28px', 'font-weight': '700', 'color': '#e8ecf4'}},
],
style: {...cardBase, 'background': '#ff1744'},
}
}
});
bm.add('output-card', {
label: '⚡ Output / Switch',
category: 'HMI Controls',
content: {type: 'output-card'},
});
// ══════════════════════════════════════════
// BURNER CARD
// ══════════════════════════════════════════
dm.addType('burner-card', {
model: {
defaults: {
tagName: 'div',
draggable: true,
droppable: false,
attributes: {'data-type': 'burner'},
traits: [
{type: 'text', name: 'label', label: 'Name', changeProp: true},
{type: 'color', name: 'active_color', label: 'Active Color', changeProp: true},
{type: 'color', name: 'inactive_color', label: 'Inactive Color', changeProp: true},
],
label: 'Burner',
active_color: '#ffab00',
inactive_color: '#ff1744',
components: [
{tagName: 'div', attributes: {'data-role': 'name'}, content: 'Burner',
style: {'font-size': '24px', 'color': '#e8ecf4'}},
{tagName: 'div', attributes: {'data-role': 'state'}, content: 'OFF',
style: {'font-size': '32px', 'font-weight': '700', 'color': '#e8ecf4'}},
],
style: {...cardBase, 'background': '#ff1744'},
}
}
});
bm.add('burner-card', {
label: '🔥 Burner',
category: 'HMI Controls',
content: {type: 'burner-card'},
});
// ══════════════════════════════════════════
// AUTOMATION CARD
// ══════════════════════════════════════════
dm.addType('automation-card', {
model: {
defaults: {
tagName: 'div',
draggable: true,
droppable: false,
attributes: {'data-type': 'automation'},
traits: [
{type: 'text', name: 'label', label: 'Name', changeProp: true},
{type: 'color', name: 'active_color', label: 'Active Color', changeProp: true},
{type: 'color', name: 'inactive_color', label: 'Inactive Color', changeProp: true},
],
label: 'Automation',
active_color: '#00c853',
inactive_color: '#ff1744',
components: [
{tagName: 'div', attributes: {'data-role': 'name'}, content: 'Automation',
style: {'font-size': '24px', 'font-weight': '700', 'color': '#e8ecf4'}},
{tagName: 'div', attributes: {'data-role': 'rules'}, content: 'Click to configure',
style: {'font-size': '12px', 'color': '#7a8baa'}},
],
style: {...cardBase, 'background': '#ff1744', 'width': '100%'},
}
}
});
bm.add('automation-card', {
label: '🤖 Automation',
category: 'HMI Controls',
content: {type: 'automation-card'},
});
// ══════════════════════════════════════════
// GAUGE (arc)
// ══════════════════════════════════════════
bm.add('gauge', {
label: '📊 Gauge',
category: 'HMI Controls',
content: `<div style="width:150px;height:150px;display:flex;align-items:center;justify-content:center;position:relative" data-type="gauge">
<svg viewBox="0 0 100 100" width="120" height="120">
<circle cx="50" cy="50" r="42" fill="none" stroke="#1a2240" stroke-width="6"/>
<circle cx="50" cy="50" r="42" fill="none" stroke="#2d7ff9" stroke-width="6"
stroke-dasharray="264" stroke-dashoffset="132" transform="rotate(-90 50 50)" stroke-linecap="round"/>
<text x="50" y="50" text-anchor="middle" dominant-baseline="central" fill="#e8ecf4" font-size="18" font-weight="700">50%</text>
</svg>
</div>`,
});
// ══════════════════════════════════════════
// LAYOUT HELPERS
// ══════════════════════════════════════════
bm.add('page-container', {
label: '📄 Page Container',
category: 'Layout',
content: `<div style="display:flex;flex-wrap:wrap;gap:8px;padding:8px;width:100%;height:100%;align-content:flex-start;background:#0a0e17" data-type="page">
</div>`,
});
bm.add('tab-bar', {
label: '📑 Tab Bar',
category: 'Layout',
content: `<div style="display:flex;height:50px;background:#0d1220;border-bottom:1px solid #1e2a45;width:100%">
<div style="flex:1;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:600;color:#2d7ff9;border-bottom:3px solid #2d7ff9;cursor:pointer">PAGE 1</div>
<div style="flex:1;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:600;color:#7a8baa;cursor:pointer">PAGE 2</div>
<div style="flex:1;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:600;color:#7a8baa;cursor:pointer">PAGE 3</div>
</div>`,
});
bm.add('top-bar', {
label: '📌 Top Bar',
category: 'Layout',
content: `<div style="display:flex;align-items:center;justify-content:space-between;height:48px;padding:0 20px;background:#111827;border-bottom:1px solid #1e2a45;width:100%">
<div style="display:flex;align-items:center;gap:16px">
<span style="font-size:20px;font-weight:700;color:#e8ecf4">BFA BANANA DRYER</span>
<span style="font-size:14px;color:#7a8baa">SAE Engineering</span>
</div>
<span style="font-size:16px;font-weight:600;color:#ff1744">RS485 OFFLINE</span>
</div>`,
});
bm.add('text-label', {
label: '📝 Text Label',
category: 'Layout',
content: `<div style="font-size:18px;color:#e8ecf4;padding:8px;text-align:center">Label Text</div>`,
});
bm.add('divider', {
label: ' Divider',
category: 'Layout',
content: `<div style="width:100%;height:1px;background:#1e2a45;margin:8px 0"></div>`,
});
bm.add('spacer', {
label: '⬜ Spacer',
category: 'Layout',
content: `<div style="width:100%;height:20px"></div>`,
});
}