bfa-dryer-design/templates/editor.html

290 lines
14 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>BFA Banana Dryer — HMI Design Tool</title>
<link rel="stylesheet" href="static/vendor/grapes.min.css">
<style>
:root{--bg:#0a0e17;--card:#131a2b;--border:#1e2a45;--text:#e8ecf4;--text2:#7a8baa;--dim:#4a5670;--blue:#2d7ff9;--green:#00c853;--amber:#ffab00;--red:#ff1744}
body{margin:0;font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(--text);height:100vh;overflow:hidden}
/* GrapesJS dark theme overrides */
.gjs-one-bg{background:var(--bg) !important}
.gjs-two-color{color:var(--text) !important}
.gjs-three-bg{background:var(--card) !important}
.gjs-four-color,.gjs-four-color-h:hover{color:var(--blue) !important}
.gjs-pn-panel{background:var(--card) !important;border-color:var(--border) !important}
.gjs-pn-views-container,.gjs-pn-views{background:var(--card) !important}
.gjs-block{background:var(--bg) !important;border:1px solid var(--border) !important;color:var(--text2) !important;border-radius:8px !important;min-height:60px !important}
.gjs-block:hover{border-color:var(--blue) !important;color:var(--text) !important}
.gjs-block__media{color:var(--text2) !important}
.gjs-blocks-cs{background:var(--card) !important}
.gjs-category-title,.gjs-layer-title,.gjs-sm-sector-title{background:var(--bg) !important;color:var(--text2) !important;border-color:var(--border) !important}
.gjs-field{background:var(--bg) !important;color:var(--text) !important;border-color:var(--border) !important}
.gjs-field input,.gjs-field select,.gjs-field textarea{color:var(--text) !important}
.gjs-sm-property,.gjs-trt-trait{color:var(--text2) !important}
.gjs-clm-tags .gjs-sm-composite{background:var(--bg) !important}
.gjs-layer{background:var(--card) !important;color:var(--text2) !important}
.gjs-layer.gjs-selected{background:var(--border) !important}
.gjs-input-holder,.gjs-sm-input-holder{background:var(--bg) !important}
.gjs-pn-btn{color:var(--text2) !important}
.gjs-pn-btn.gjs-pn-active{color:var(--blue) !important}
.gjs-cv-canvas{background:#080c14 !important}
.gjs-frame-wrapper{background:var(--bg) !important}
.gjs-toolbar{background:var(--card) !important}
.gjs-toolbar-item{color:var(--text) !important}
.gjs-resizer-h{border-color:var(--blue) !important}
.gjs-highlighter{outline-color:var(--blue) !important}
.gjs-badge{background:var(--blue) !important}
.gjs-ghost{background:rgba(45,127,249,0.15) !important;border:2px dashed var(--blue) !important}
/* Top bar */
.top-bar{display:flex;align-items:center;justify-content:space-between;height:48px;padding:0 16px;background:#111827;border-bottom:1px solid var(--border);flex-shrink:0;z-index:10}
.top-bar .title{font-size:18px;font-weight:700}
.top-bar .sub{font-size:13px;color:var(--text2);margin-left:16px}
.top-bar-right{display:flex;align-items:center;gap:12px}
.top-bar select,.top-bar button{padding:5px 12px;border:1px solid var(--border);border-radius:5px;background:var(--card);color:var(--text);font-size:12px;cursor:pointer}
.top-bar button:hover{background:var(--border)}
.top-bar .save-btn{background:var(--green);border-color:var(--green);color:#000;font-weight:600}
/* Editor container */
#gjs{height:calc(100vh - 48px);width:100%}
/* Right panel width */
.gjs-pn-views-container{width:280px !important}
/* Simulation toggle panel */
.sim-panel{position:fixed;right:0;top:48px;width:50%;height:calc(100vh - 48px);background:#080c14;border-left:2px solid var(--border);z-index:20;display:none;overflow:auto}
.sim-panel.open{display:block}
.sim-panel-header{padding:8px 12px;background:var(--card);border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;font-size:13px;font-weight:600;color:var(--text2)}
.sim-panel-header button{padding:4px 10px;border:1px solid var(--border);border-radius:4px;background:var(--card);color:var(--text2);font-size:11px;cursor:pointer}
</style>
</head>
<body>
<!-- TOP BAR -->
<div class="top-bar">
<div style="display:flex;align-items:center">
<span class="title">BFA Banana Dryer</span>
<span class="sub">HMI Design Tool v2</span>
</div>
<div class="top-bar-right">
<span style="color:var(--blue);font-weight:600">{{ user }}</span>
<a href="logout" style="color:var(--dim);text-decoration:none;font-size:12px">Logout</a>
<span style="color:var(--dim)">|</span>
<select id="theme-select" onchange="switchTheme(this.value)">
<option value="dark-industrial">Dark Industrial</option>
<option value="light-industrial">Light Industrial</option>
<option value="high-contrast">High Contrast</option>
<option value="classic-scada">Classic SCADA</option>
</select>
<button onclick="toggleSim()">Simulation</button>
<button class="save-btn" onclick="saveProject()">Save</button>
<button onclick="exportProject()">Export</button>
</div>
</div>
<!-- GRAPESJS EDITOR -->
<div id="gjs"></div>
<!-- SIMULATION PANEL (toggle) -->
<div class="sim-panel" id="sim-panel">
<div class="sim-panel-header">
MACHINE SIMULATION
<button onclick="toggleSim()">Close</button>
</div>
<div id="sim-container" style="padding:16px;display:flex;align-items:center;justify-content:center;min-height:400px"></div>
</div>
<script src="static/vendor/grapes.min.js"></script>
<script src="static/hmi-blocks.js"></script>
<script src="static/hmi-themes.js"></script>
<script>
// ══════════════════════════════════════════════════════
// GrapesJS Init
// ══════════════════════════════════════════════════════
const editor = grapesjs.init({
container: '#gjs',
fromElement: false,
height: '100%',
width: 'auto',
storageManager: {
type: 'remote',
stepsBeforeSave: 3,
options: {
remote: {
urlLoad: 'api/layout',
urlStore: 'api/layout',
fetchOptions: opts => ({...opts, method: opts.method || 'POST', headers: {'Content-Type':'application/json'}}),
onStore: data => data,
onLoad: result => result,
}
}
},
deviceManager: {
devices: [
{id: 'tab5', name: 'Tab5 (1280×720)', width: '1280px', height: '720px'},
{id: 'schneider', name: 'Schneider HMIDT651 (1280×800)', width: '1280px', height: '800px'},
{id: 'desktop', name: 'Desktop (Full)', width: ''},
]
},
styleManager: {
sectors: [
{
name: 'Typography',
open: true,
properties: [
{property: 'font-family', type: 'select', defaults: 'Segoe UI',
list: [{value:'Segoe UI',name:'Segoe UI'},{value:'Arial',name:'Arial'},{value:'Helvetica',name:'Helvetica'},{value:'Roboto',name:'Roboto'},{value:'monospace',name:'Monospace'},{value:'Courier New',name:'Courier New'}]},
{property: 'font-size', type: 'number', units: ['px','em','rem'], defaults: '18px', min: 8, max: 72},
{property: 'font-weight', type: 'select', defaults: '400',
list: [{value:'400',name:'Normal'},{value:'600',name:'Semi Bold'},{value:'700',name:'Bold'},{value:'900',name:'Black'}]},
{property: 'color', type: 'color'},
{property: 'text-align', type: 'select', defaults: 'center',
list: [{value:'left',name:'Left'},{value:'center',name:'Center'},{value:'right',name:'Right'}]},
{property: 'line-height', type: 'number', units: ['px',''], defaults: '1.4'},
]
},
{
name: 'Background & Colors',
open: true,
properties: [
{property: 'background-color', type: 'color'},
{property: 'border-color', type: 'color'},
{property: 'border-width', type: 'number', units: ['px'], defaults: '1px', min: 0, max: 10},
{property: 'border-radius', type: 'number', units: ['px'], defaults: '10px', min: 0, max: 50},
{property: 'border-style', type: 'select', defaults: 'solid',
list: [{value:'none'},{value:'solid'},{value:'dashed'},{value:'dotted'}]},
{property: 'opacity', type: 'number', defaults: 1, min: 0, max: 1, step: 0.1},
]
},
{
name: 'Layout',
open: false,
properties: [
{property: 'width', type: 'number', units: ['px','%','vw']},
{property: 'height', type: 'number', units: ['px','%','vh']},
{property: 'min-height', type: 'number', units: ['px']},
{property: 'padding', type: 'composite',
properties: [{property:'padding-top',type:'number',units:['px']},{property:'padding-right',type:'number',units:['px']},{property:'padding-bottom',type:'number',units:['px']},{property:'padding-left',type:'number',units:['px']}]},
{property: 'margin', type: 'composite',
properties: [{property:'margin-top',type:'number',units:['px']},{property:'margin-right',type:'number',units:['px']},{property:'margin-bottom',type:'number',units:['px']},{property:'margin-left',type:'number',units:['px']}]},
{property: 'display', type: 'select', defaults: 'flex',
list: [{value:'block'},{value:'flex'},{value:'grid'},{value:'inline-block'},{value:'none'}]},
{property: 'flex-direction', type: 'select',
list: [{value:'row'},{value:'column'},{value:'row-reverse'},{value:'column-reverse'}]},
{property: 'justify-content', type: 'select',
list: [{value:'flex-start'},{value:'center'},{value:'flex-end'},{value:'space-between'},{value:'space-around'}]},
{property: 'align-items', type: 'select',
list: [{value:'flex-start'},{value:'center'},{value:'flex-end'},{value:'stretch'}]},
{property: 'gap', type: 'number', units: ['px'], defaults: '8px'},
{property: 'z-index', type: 'number', defaults: 'auto', min: -10, max: 100},
{property: 'overflow', type: 'select', list: [{value:'visible'},{value:'hidden'},{value:'auto'},{value:'scroll'}]},
]
},
{
name: 'Effects',
open: false,
properties: [
{property: 'box-shadow', type: 'text'},
{property: 'transition', type: 'text'},
{property: 'cursor', type: 'select', list: [{value:'default'},{value:'pointer'},{value:'grab'},{value:'not-allowed'}]},
]
}
]
},
blockManager: {blocks: []},
canvas: {
styles: ['static/hmi-canvas.css'],
}
});
// Use GrapesJS default panels — they include blocks, styles, layers, traits
// Just add device switching buttons
editor.Panels.addButton('options', {
id: 'device-tab5', command: e => e.setDevice('tab5'), label: 'Tab5', attributes: {title: 'Tab5 1280x720'}
});
editor.Panels.addButton('options', {
id: 'device-schneider', command: e => e.setDevice('schneider'), label: 'HMIDT', attributes: {title: 'Schneider 1280x800'}
});
editor.Panels.addButton('options', {
id: 'device-full', command: e => e.setDevice('desktop'), label: 'Full', attributes: {title: 'Full width'}
});
// ══════════════════════════════════════════════════════
// Register HMI blocks
// ══════════════════════════════════════════════════════
registerHMIBlocks(editor);
2026-04-08 17:19:11 +10:00
// Open blocks panel by default
editor.on('load', () => {
const pn = editor.Panels;
const openBl = pn.getButton('views', 'open-blocks');
if (openBl) openBl.set('active', true);
});
// ══════════════════════════════════════════════════════
// Save / Load / Export
// ══════════════════════════════════════════════════════
async function saveProject() {
try {
const data = editor.getProjectData();
await fetch('api/layout', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
});
alert('Saved!');
} catch(e) { alert('Save failed: ' + e.message); }
}
function exportProject() {
const data = editor.getProjectData();
const blob = new Blob([JSON.stringify(data, null, 2)], {type: 'application/json'});
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'bfa-hmi-design.json';
a.click();
}
// Load on init
(async function() {
try {
const resp = await fetch('api/layout');
const data = await resp.json();
if (data.assets || data.styles || (data.pages && data.pages.length && data.pages[0].frames)) {
// V2 GrapesJS project — load it
editor.loadProjectData(data);
} else {
// No V2 yet — try loading converted V1 HTML
try {
const htmlResp = await fetch('api/v1html');
const html = await htmlResp.text();
if (html && html.length > 10) {
editor.setComponents(html);
console.log('Loaded V1 converted layout');
}
} catch(e2) { console.log('No V1 HTML either, starting blank'); }
}
} catch(e) { console.log('No saved layout, starting fresh'); }
})();
// ══════════════════════════════════════════════════════
// Theme switching
// ══════════════════════════════════════════════════════
function switchTheme(theme) {
applyHMITheme(editor, theme);
}
// ══════════════════════════════════════════════════════
// Simulation panel toggle
// ══════════════════════════════════════════════════════
function toggleSim() {
document.getElementById('sim-panel').classList.toggle('open');
}
</script>
</body>
</html>