Card properties editor: name, type, color, font size, width, setpoints

This commit is contained in:
Richard Sauer 2026-04-08 16:40:47 +10:00
parent b4033f210d
commit 4686458926

View File

@ -184,6 +184,16 @@ body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(
.auto-btns button{padding:10px 24px;border:none;border-radius:8px;font-size:16px;font-weight:600;cursor:pointer} .auto-btns button{padding:10px 24px;border:none;border-radius:8px;font-size:16px;font-weight:600;cursor:pointer}
.auto-btns .save-btn{background:var(--green);color:#000} .auto-btns .save-btn{background:var(--green);color:#000}
.auto-btns .cancel-btn{background:var(--card);color:var(--text);border:1px solid var(--border)} .auto-btns .cancel-btn{background:var(--card);color:var(--text);border:1px solid var(--border)}
/* Card properties editor */
.props-editor{width:420px;max-width:95vw}
.props-editor h3{font-size:20px;margin-bottom:16px}
.prop-row{display:flex;align-items:center;gap:12px;margin-bottom:12px}
.prop-row label{width:100px;font-size:13px;color:var(--text2);flex-shrink:0;text-align:right}
.prop-row input,.prop-row select{flex:1;padding:10px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-size:14px}
.prop-row input[type=color]{width:50px;height:38px;padding:2px;cursor:pointer;flex:0}
.prop-row input[type=range]{flex:1}
.prop-preview{display:inline-block;width:24px;height:24px;border-radius:4px;border:1px solid var(--border);vertical-align:middle;margin-left:8px}
</style> </style>
</head> </head>
<body> <body>
@ -470,27 +480,28 @@ function createCardEl(c) {
const commentCount = (layout.comments || []).filter(x => x.target === c.id).length; const commentCount = (layout.comments || []).filter(x => x.target === c.id).length;
el.innerHTML += `<div class="card-comment-badge ${commentCount > 0 ? 'has-comments' : ''}">${commentCount}</div>`; el.innerHTML += `<div class="card-comment-badge ${commentCount > 0 ? 'has-comments' : ''}">${commentCount}</div>`;
const cfs = c.fontSize || 18;
if (c.type === 'temp') { if (c.type === 'temp') {
const color = c.color || '#ff8844'; const color = c.color || '#ff8844';
el.innerHTML += `<div class="card-label" onclick="if(mode==='edit'){event.stopPropagation();renameCard('${c.id}')}">${c.label}</div>`; el.innerHTML += `<div class="card-label" style="font-size:${cfs}px" onclick="if(mode==='edit'){event.stopPropagation();editCardProps('${c.id}')}">${c.label}</div>`;
el.innerHTML += `<div class="card-value" style="color:${color}">${Math.round(s.value)} &deg;C</div>`; el.innerHTML += `<div class="card-value" style="color:${color}">${Math.round(s.value)} &deg;C</div>`;
el.innerHTML += `<div class="card-bar"><div class="card-bar-fill" style="width:${Math.min(100,s.value/2)}%;background:${color}"></div></div>`; el.innerHTML += `<div class="card-bar"><div class="card-bar-fill" style="width:${Math.min(100,s.value/2)}%;background:${color}"></div></div>`;
el.innerHTML += `<div class="card-sub">SP: ${c.sp_default || 70} &deg;C</div>`; el.innerHTML += `<div class="card-sub">SP: ${c.sp_default || 70} &deg;C</div>`;
el.onclick = () => { if (mode === 'preview') openTempPopup(c); }; el.onclick = () => { if (mode === 'preview') openTempPopup(c); };
} else if (c.type === 'motor') { } else if (c.type === 'motor') {
el.innerHTML += `<div style="display:flex;gap:10px;align-items:center"> el.innerHTML += `<div style="display:flex;gap:10px;align-items:center">
<span class="card-label" onclick="if(mode==='edit'){event.stopPropagation();renameCard('${c.id}')}">${c.label}</span> <span class="card-label" style="font-size:${cfs}px" onclick="if(mode==='edit'){event.stopPropagation();editCardProps('${c.id}')}">${c.label}</span>
<span style="font-size:18px;color:var(--text2)">${c.sp_default || 50}%</span> <span style="font-size:18px;color:var(--text2)">${c.sp_default || 50}%</span>
</div>`; </div>`;
el.innerHTML += `<div class="card-value">${s.on ? 'ON' : 'OFF'}</div>`; el.innerHTML += `<div class="card-value">${s.on ? 'ON' : 'OFF'}</div>`;
el.onclick = () => { if (mode === 'preview') toggleSim(c.id); }; el.onclick = () => { if (mode === 'preview') toggleSim(c.id); };
} else if (c.type === 'burner') { } else if (c.type === 'burner') {
el.innerHTML += `<div class="card-label" onclick="if(mode==='edit'){event.stopPropagation();renameCard('${c.id}')}">${c.label}</div>`; el.innerHTML += `<div class="card-label" style="font-size:${cfs}px" onclick="if(mode==='edit'){event.stopPropagation();editCardProps('${c.id}')}">${c.label}</div>`;
el.innerHTML += `<div class="card-value">${s.on ? 'ON' : 'OFF'}</div>`; el.innerHTML += `<div class="card-value">${s.on ? 'ON' : 'OFF'}</div>`;
el.onclick = () => { if (mode === 'preview') toggleSim(c.id); }; el.onclick = () => { if (mode === 'preview') toggleSim(c.id); };
} else if (c.type === 'automation') { } else if (c.type === 'automation') {
// Automation card — combines outputs, speeds, temp setpoints // 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:24px;font-weight:700">${c.label}</div>`; el.innerHTML += `<div class="card-label" onclick="if(mode==='edit'){event.stopPropagation();editCardProps('${c.id}')}" style="font-size:${Math.max(cfs,20)}px;font-weight:700">${c.label}</div>`;
const rules = c.rules || []; const rules = c.rules || [];
if (rules.length === 0) { 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>`; el.innerHTML += `<div class="card-sub" style="text-align:center">No rules configured<br>Click to edit in edit mode</div>`;
@ -508,7 +519,7 @@ function createCardEl(c) {
else if (mode === 'preview') toggleSim(c.id); else if (mode === 'preview') toggleSim(c.id);
}; };
} else { // output } 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-label" onclick="if(mode==='edit'){event.stopPropagation();editCardProps('${c.id}')}" style="text-align:center;font-size:${cfs}px">${c.label}</div>`;
el.innerHTML += `<div class="card-value">${s.on ? 'ON' : 'OFF'}</div>`; el.innerHTML += `<div class="card-value">${s.on ? 'ON' : 'OFF'}</div>`;
el.onclick = () => { if (mode === 'preview') toggleSim(c.id); }; el.onclick = () => { if (mode === 'preview') toggleSim(c.id); };
} }
@ -728,12 +739,89 @@ function deleteCard(id) {
renderCards(); renderCards();
} }
function renameCard(id) { function editCardProps(id) {
const page = layout.pages[currentPage]; const card = findCard(id);
const card = page.cards.find(c => c.id === id);
if (!card) return; if (!card) return;
const name = prompt('New name:', card.label);
if (name) { card.label = name; renderCards(); } const p = document.getElementById('popup');
p.style.borderColor = 'transparent';
let typeOpts = ['temp','motor','output','burner','automation'].map(t =>
`<option value="${t}" ${card.type===t?'selected':''}>${t.charAt(0).toUpperCase()+t.slice(1)}</option>`
).join('');
let extraFields = '';
if (card.type === 'temp') {
extraFields = `
<div class="prop-row"><label>Setpoint</label><input type="number" id="prop-sp" value="${card.sp_default||70}" min="0" max="200"> <span style="color:var(--text2)">&deg;C</span></div>`;
} else if (card.type === 'motor') {
extraFields = `
<div class="prop-row"><label>Speed SP</label><input type="number" id="prop-sp" value="${card.sp_default||50}" min="0" max="100" step="5"> <span style="color:var(--text2)">%</span></div>`;
}
p.innerHTML = `
<div class="props-editor">
<h3>Edit Card</h3>
<div class="prop-row">
<label>Name</label>
<input type="text" id="prop-name" value="${card.label}">
</div>
<div class="prop-row">
<label>Type</label>
<select id="prop-type">${typeOpts}</select>
</div>
<div class="prop-row">
<label>Size</label>
<select id="prop-width">
<option value="half" ${card.width!=='full'?'selected':''}>Half width</option>
<option value="full" ${card.width==='full'?'selected':''}>Full width</option>
</select>
</div>
<div class="prop-row">
<label>Color</label>
<input type="color" id="prop-color" value="${card.color||'#ff8844'}">
<span class="prop-preview" id="prop-color-preview" style="background:${card.color||'#ff8844'}"></span>
<button onclick="document.getElementById('prop-color').value='';document.getElementById('prop-color-preview').style.background='var(--card)'" style="padding:6px 10px;border:1px solid var(--border);border-radius:4px;background:var(--card);color:var(--text2);font-size:11px;cursor:pointer">Reset</button>
</div>
<div class="prop-row">
<label>Font Size</label>
<input type="range" id="prop-fontsize" min="12" max="36" value="${card.fontSize||18}">
<span id="prop-fontsize-val" style="color:var(--text2);min-width:30px">${card.fontSize||18}px</span>
</div>
${extraFields}
<div class="auto-btns">
<button class="cancel-btn" onclick="closePopup()">Cancel</button>
<button class="save-btn" onclick="saveCardProps('${id}')">Save</button>
</div>
</div>`;
// Live preview for color picker
document.getElementById('prop-color').addEventListener('input', e => {
document.getElementById('prop-color-preview').style.background = e.target.value;
});
document.getElementById('prop-fontsize').addEventListener('input', e => {
document.getElementById('prop-fontsize-val').textContent = e.target.value + 'px';
});
document.getElementById('popup-overlay').classList.add('active');
}
function saveCardProps(id) {
const card = findCard(id);
if (!card) return;
card.label = document.getElementById('prop-name').value || card.label;
card.type = document.getElementById('prop-type').value;
card.width = document.getElementById('prop-width').value;
const color = document.getElementById('prop-color').value;
card.color = color || null;
card.fontSize = parseInt(document.getElementById('prop-fontsize').value) || 18;
const spEl = document.getElementById('prop-sp');
if (spEl) card.sp_default = parseInt(spEl.value) || 0;
closePopup();
renderCards();
} }
function toggleCardSize(id) { function toggleCardSize(id) {