diff --git a/templates/editor.html b/templates/editor.html index 2c6d003..316015c 100644 --- a/templates/editor.html +++ b/templates/editor.html @@ -52,6 +52,8 @@ body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var( .hmi-card.type-motor,.hmi-card.type-output,.hmi-card.type-burner{background:var(--red)} .hmi-card.on.type-motor,.hmi-card.on.type-output{background:var(--green)} .hmi-card.on.type-burner{background:var(--amber)} +.hmi-card.type-automation{background:var(--card);border:2px solid var(--blue)} +.hmi-card.on.type-automation{background:rgba(45,127,249,0.15);border-color:var(--blue)} .hmi-card .card-label{font-size:18px;color:var(--text2);text-align:center} .hmi-card .card-value{font-size:28px;font-weight:700} @@ -72,10 +74,17 @@ body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var( .sortable-ghost{opacity:0.4;border:2px dashed var(--blue) !important} /* Add card palette */ -.add-palette{display:none;padding:8px;gap:8px;border-top:1px solid var(--border);flex-shrink:0} -.edit-mode .add-palette{display:flex} -.add-palette button{flex:1;padding:10px;border:2px dashed var(--dim);border-radius:8px;background:none;color:var(--text2);font-size:13px;cursor:pointer} -.add-palette button:hover{border-color:var(--blue);color:var(--blue)} +/* Add card placeholder */ +.add-placeholder{width:calc(50% - 4px);height:calc(33.3% - 6px);border:2px dashed var(--dim);border-radius:10px;display:none;align-items:center;justify-content:center;cursor:pointer;transition:all 0.15s;position:relative} +.edit-mode .add-placeholder{display:flex} +.add-placeholder:hover{border-color:var(--blue);background:rgba(45,127,249,0.05)} +.add-placeholder .plus{font-size:36px;color:var(--dim)} +.add-placeholder:hover .plus{color:var(--blue)} +/* Add menu dropdown */ +.add-menu{display:none;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--card);border:1px solid var(--border);border-radius:10px;padding:6px;z-index:20;min-width:160px;box-shadow:0 8px 24px rgba(0,0,0,0.5)} +.add-menu.open{display:flex;flex-direction:column;gap:2px} +.add-menu button{padding:10px 16px;border:none;border-radius:6px;background:none;color:var(--text);font-size:14px;text-align:left;cursor:pointer} +.add-menu button:hover{background:var(--border)} /* Comment badge on cards */ .hmi-card .card-comment-badge{position:absolute;bottom:4px;right:4px;width:20px;height:20px;border-radius:50%;background:var(--amber);color:#000;font-size:10px;font-weight:700;display:none;align-items:center;justify-content:center} @@ -193,12 +202,7 @@ body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(
-
- - - - -
+ @@ -326,7 +330,7 @@ function setMode(m) { document.getElementById('card-grid').classList.toggle('edit-mode', m === 'edit'); document.getElementById('preview-tag').style.display = m === 'preview' ? 'inline' : 'none'; document.querySelectorAll('.edit-overlay').forEach(e => e.style.display = m === 'edit' ? 'flex' : 'none'); - document.getElementById('add-palette').style.display = m === 'edit' ? 'flex' : 'none'; + // add-palette removed — inline + buttons handle this now if (m === 'edit') initSortable(); else if (sortableInstance) { sortableInstance.destroy(); sortableInstance = null; } } @@ -398,7 +402,35 @@ function renderCards() { const el = createCardEl(c); grid.appendChild(el); }); - if (mode === 'edit') initSortable(); + // In edit mode, fill remaining slots with + placeholders (up to 6 slots per page) + if (mode === 'edit') { + const halfCards = page.cards.filter(c => c.width !== 'full').length; + const fullCards = page.cards.filter(c => c.width === 'full').length; + const slotsUsed = halfCards + fullCards * 2; + const emptySlots = Math.max(1, 6 - slotsUsed); // always show at least 1 + for (let i = 0; i < emptySlots; i++) { + const ph = document.createElement('div'); + ph.className = 'add-placeholder'; + ph.innerHTML = `+ +
+ + + + + +
`; + ph.addEventListener('click', (e) => { + if (e.target.tagName === 'BUTTON') return; + // Toggle the menu + const menu = ph.querySelector('.add-menu'); + document.querySelectorAll('.add-menu.open').forEach(m => { if(m!==menu) m.classList.remove('open'); }); + menu.classList.toggle('open'); + e.stopPropagation(); + }); + grid.appendChild(ph); + } + initSortable(); + } } function createCardEl(c) { @@ -439,6 +471,26 @@ function createCardEl(c) { el.innerHTML += `
${c.label}
`; el.innerHTML += `
${s.on ? 'ON' : 'OFF'}
`; el.onclick = () => { if (mode === 'preview') toggleSim(c.id); }; + } else if (c.type === 'automation') { + // Automation card — combines outputs, speeds, temp setpoints + el.innerHTML += `
${c.label}
`; + const rules = c.rules || []; + if (rules.length === 0) { + el.innerHTML += `
No rules configured
Click to edit in edit mode
`; + } else { + let rulesHtml = '
'; + rules.forEach(r => { + const icon = r.type === 'temp' ? '🌡' : r.type === 'motor' ? '⚙' : r.type === 'output' ? '⚡' : '●'; + rulesHtml += `
${icon} ${r.label || r.target}: ${r.action || ''} ${r.value !== undefined ? r.value : ''}
`; + }); + rulesHtml += '
'; + el.innerHTML += rulesHtml; + } + el.innerHTML += `
${s.on ? 'ACTIVE' : 'IDLE'}
`; + el.onclick = () => { + if (mode === 'edit') { event.stopPropagation(); editAutomation(c); } + else if (mode === 'preview') toggleSim(c.id); + }; } else { // output el.innerHTML += `
${c.label}
`; el.innerHTML += `
${s.on ? 'ON' : 'OFF'}
`; @@ -447,6 +499,30 @@ function createCardEl(c) { return el; } +function editAutomation(c) { + const rulesStr = (c.rules || []).map(r => `${r.type}:${r.target}:${r.action}:${r.value||''}`).join('\n'); + const help = `Edit automation rules (one per line): +Format: type:target:action:value + +Types: temp, motor, output +Actions: set, on, off +Examples: + temp:Heat Input:set:130 + motor:Hot Fan:set:65 + motor:Conveyor:on + output:Brush:on + output:Mill:off + +Current rules:`; + const input = prompt(help, rulesStr); + if (input === null) return; + c.rules = input.split('\n').filter(l => l.trim()).map(l => { + const [type, target, action, value] = l.split(':'); + return {type: type||'output', target: target||'', label: target||'', action: action||'on', value: value ? parseInt(value) : undefined}; + }); + renderCards(); +} + // ══════════════════════════════════════════════════════ // DRAG & DROP // ══════════════════════════════════════════════════════ @@ -469,14 +545,17 @@ function initSortable() { // CARD ACTIONS // ══════════════════════════════════════════════════════ function addCard(type) { + // Close any open add menus + document.querySelectorAll('.add-menu.open').forEach(m => m.classList.remove('open')); const id = type + '_' + Date.now(); const defaults = { temp: {label: 'New Temp', color: '#ff8844', sp_default: 70}, motor: {label: 'New Motor', sp_default: 50}, output: {label: 'New Output'}, - burner: {label: 'Burner'} + burner: {label: 'Burner'}, + automation: {label: 'New Automation', width: 'full', rules: []} }; - const card = {id, type, width: 'half', ...defaults[type]}; + const card = {id, type, width: defaults[type]?.width || 'half', ...defaults[type]}; layout.pages[currentPage].cards.push(card); simState[id] = {on: false, value: card.sp_default || 0}; renderCards(); @@ -654,7 +733,10 @@ function hideCtx() { document.getElementById('eq-context').style.display = 'none'; ctxEquip = null; } -document.addEventListener('click', e => { if (!e.target.closest('#eq-context')) hideCtx(); }); +document.addEventListener('click', e => { + if (!e.target.closest('#eq-context')) hideCtx(); + if (!e.target.closest('.add-placeholder')) document.querySelectorAll('.add-menu.open').forEach(m => m.classList.remove('open')); +}); function ctxAction(action) { if (!ctxEquip) return;