mirror of
http://10.0.2.1:3031/sauer/bfa-dryer-design.git
synced 2026-06-30 18:06:42 +10:00
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>
278 lines
12 KiB
JavaScript
278 lines
12 KiB
JavaScript
/* 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>`,
|
||
});
|
||
}
|