bfa-dryer-design/templates/editor.html

276 lines
14 KiB
HTML
Raw 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.

<!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);
// ══════════════════════════════════════════════════════
// 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();
// Check if it's V2 (GrapesJS) or V1 (old format)
if (data.assets || data.styles || data.pages) {
editor.loadProjectData(data);
} else {
// V1 layout — start fresh with default canvas content
console.log('V1 layout detected — starting fresh GrapesJS project');
}
} 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>