feat: fit-contain letterbox + smart queue refill

- Replace center-crop with fit-contain (no photo cropping)
- Letterbox bars filled with adjacent edge average color
- Queue fetches in batches until 50 usable assets
- Auto-refill queue on exhaustion, dedup via shown history
- Filter RAW files from asset selection

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-03 14:39:55 -04:00
parent 4b9a7f2fa7
commit b80f6e4bde
10 changed files with 985 additions and 411 deletions

View File

@@ -162,14 +162,53 @@ async function renderDisplay(el) {
}
async function renderDevice(el) {
const settings = await api('/api/settings');
el.innerHTML = `
<div class="card">
<h2>Device</h2>
<h2>Immich Connection</h2>
<div class="field">
<label>Immich URL</label>
<input type="url" id="immichUrl" value="${settings.immich_url || ''}" placeholder="https://photos.example.com">
</div>
<div class="field">
<label>API Key</label>
<input type="password" id="immichKey" value="${settings.immich_key || ''}" placeholder="Your Immich API key">
</div>
<div class="btn-group">
<button class="btn btn-primary" id="saveImmich">Save</button>
<button class="btn" id="testConn">Test Connection</button>
</div>
<div id="connStatus" style="margin-top:12px"></div>
</div>
<div class="card">
<h2>Device Actions</h2>
<div class="btn-group">
<button class="btn btn-danger" onclick="if(confirm('Enter deep sleep?'))api('/api/action/sleep',{method:'POST'})">Deep Sleep</button>
<button class="btn btn-danger" onclick="if(confirm('Reboot?'))fetch('/api/action/reboot',{method:'POST'})">Reboot</button>
<button class="btn btn-danger" onclick="if(confirm('Reboot device?'))fetch('/api/action/reboot',{method:'POST'})">Reboot</button>
</div>
</div>`;
document.getElementById('saveImmich').onclick = async () => {
const url = document.getElementById('immichUrl').value.trim();
const key = document.getElementById('immichKey').value.trim();
if (!url || !key) return toast('URL and API key are required');
await api('/api/settings', { method: 'POST', body: JSON.stringify({ immich_url: url, immich_key: key }) });
toast('Immich settings saved. Reboot to apply.');
};
document.getElementById('testConn').onclick = async () => {
const status = document.getElementById('connStatus');
status.textContent = 'Testing...';
status.style.color = '';
try {
const result = await api('/api/test-connection', { method: 'POST' });
status.textContent = result.msg;
status.style.color = result.ok ? '#2e7d32' : '#c62828';
} catch (e) {
status.textContent = 'Request failed: ' + e.message;
status.style.color = '#c62828';
}
};
}
async function renderFirmware(el) {