mirror of
http://10.0.2.1:3031/sauer/bfa-dryer-design.git
synced 2026-06-30 12:46:43 +10:00
276 lines
14 KiB
HTML
276 lines
14 KiB
HTML
<!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>
|