mirror of
http://10.0.2.1:3031/sauer/bfa-dryer-design.git
synced 2026-06-30 13:06:42 +10:00
Replace bottom palette with inline + buttons, add automation card type
- Empty grid slots show + placeholder buttons in edit mode - Click + to see menu: Temperature, Motor, Output, Burner, Automation - Automation card: combines temp setpoints, motor speeds, and outputs into a single rule-based card (edit rules via click in edit mode) - Automation cards show rule summary and ACTIVE/IDLE state - Bottom add-palette removed for cleaner layout Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f756de43c5
commit
c6e3b0c0eb
@ -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(
|
||||
</div>
|
||||
<div class="hmi-tabs" id="hmi-tabs"></div>
|
||||
<div class="card-grid edit-mode" id="card-grid"></div>
|
||||
<div class="add-palette" id="add-palette">
|
||||
<button onclick="addCard('temp')">+ Temperature</button>
|
||||
<button onclick="addCard('motor')">+ Motor</button>
|
||||
<button onclick="addCard('output')">+ Output</button>
|
||||
<button onclick="addCard('burner')">+ Burner</button>
|
||||
</div>
|
||||
<!-- add palette removed — inline + buttons in grid instead -->
|
||||
</div>
|
||||
|
||||
<!-- RIGHT: MACHINE SIM -->
|
||||
@ -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 = `<span class="plus">+</span>
|
||||
<div class="add-menu" id="addmenu-${i}">
|
||||
<button onclick="addCard('temp')">Temperature</button>
|
||||
<button onclick="addCard('motor')">Motor / Speed</button>
|
||||
<button onclick="addCard('output')">Output / Switch</button>
|
||||
<button onclick="addCard('burner')">Burner</button>
|
||||
<button onclick="addCard('automation')">Automation</button>
|
||||
</div>`;
|
||||
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 += `<div class="card-label" onclick="if(mode==='edit'){event.stopPropagation();renameCard('${c.id}')}">${c.label}</div>`;
|
||||
el.innerHTML += `<div class="card-value">${s.on ? 'ON' : 'OFF'}</div>`;
|
||||
el.onclick = () => { if (mode === 'preview') toggleSim(c.id); };
|
||||
} else if (c.type === 'automation') {
|
||||
// Automation card — combines outputs, speeds, temp setpoints
|
||||
el.innerHTML += `<div class="card-label" onclick="if(mode==='edit'){event.stopPropagation();renameCard('${c.id}')}" style="font-size:16px">${c.label}</div>`;
|
||||
const rules = c.rules || [];
|
||||
if (rules.length === 0) {
|
||||
el.innerHTML += `<div class="card-sub" style="text-align:center">No rules configured<br>Click to edit in edit mode</div>`;
|
||||
} else {
|
||||
let rulesHtml = '<div style="width:100%;font-size:11px;color:var(--text2);text-align:left;padding:0 4px;overflow:hidden">';
|
||||
rules.forEach(r => {
|
||||
const icon = r.type === 'temp' ? '🌡' : r.type === 'motor' ? '⚙' : r.type === 'output' ? '⚡' : '●';
|
||||
rulesHtml += `<div style="padding:2px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${icon} ${r.label || r.target}: ${r.action || ''} ${r.value !== undefined ? r.value : ''}</div>`;
|
||||
});
|
||||
rulesHtml += '</div>';
|
||||
el.innerHTML += rulesHtml;
|
||||
}
|
||||
el.innerHTML += `<div class="card-value" style="font-size:20px">${s.on ? 'ACTIVE' : 'IDLE'}</div>`;
|
||||
el.onclick = () => {
|
||||
if (mode === 'edit') { event.stopPropagation(); editAutomation(c); }
|
||||
else if (mode === 'preview') toggleSim(c.id);
|
||||
};
|
||||
} else { // output
|
||||
el.innerHTML += `<div class="card-label" onclick="if(mode==='edit'){event.stopPropagation();renameCard('${c.id}')}" style="text-align:center">${c.label}</div>`;
|
||||
el.innerHTML += `<div class="card-value">${s.on ? 'ON' : 'OFF'}</div>`;
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user