feat(devices): icon picker (Type sets + Brand search)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
57
public/views/icon_picker.js
Normal file
57
public/views/icon_picker.js
Normal file
@@ -0,0 +1,57 @@
|
||||
// public/views/icon_picker.js — inline picker with Type + Brand tabs.
|
||||
import { el, mount, clear } from '../dom.js';
|
||||
import { api } from '../api.js';
|
||||
import { resolveIcon } from './icon_util.js';
|
||||
|
||||
// onPick(ref) called with 'set:<set>:<name>' or 'brand:<slug>'. Returns an element.
|
||||
export function iconPicker(currentRef, onPick) {
|
||||
const box = el('div', { class: 'icon-picker' });
|
||||
const tabs = el('div', { class: 'ip-tabs' });
|
||||
const body = el('div', { class: 'ip-body' });
|
||||
const typeTab = el('button', { class: 'ip-tab active' }, 'Type');
|
||||
const brandTab = el('button', { class: 'ip-tab' }, 'Brand');
|
||||
typeTab.onclick = () => { typeTab.classList.add('active'); brandTab.classList.remove('active'); showType(); };
|
||||
brandTab.onclick = () => { brandTab.classList.add('active'); typeTab.classList.remove('active'); showBrand(); };
|
||||
|
||||
async function showType() {
|
||||
clear(body);
|
||||
body.append(el('div', { class: 'muted' }, 'Loading…'));
|
||||
let list = [];
|
||||
try { list = await api.get('/api/icon-sets'); } catch { /* ignore */ }
|
||||
clear(body);
|
||||
for (const s of list) {
|
||||
const grid = el('div', { class: 'ip-grid' }, s.icons.map(file => {
|
||||
const name = file.replace(/\.[a-z]+$/, '');
|
||||
const ref = `set:${s.set}:${name}`;
|
||||
const b = el('button', { class: 'ip-icon', title: name },
|
||||
el('img', { src: `/api/icon-sets/${s.set}/${file}` }));
|
||||
b.onclick = () => onPick(ref);
|
||||
return b;
|
||||
}));
|
||||
body.append(el('div', { class: 'ip-set' },
|
||||
el('div', { class: 'ip-set-hd' }, s.set + (s.readonly ? '' : ' ·')),
|
||||
grid));
|
||||
}
|
||||
}
|
||||
|
||||
function showBrand() {
|
||||
clear(body);
|
||||
const inp = el('input', { class: 'dv-edit-name', placeholder: 'brand slug e.g. apple, google-nest' });
|
||||
const prev = el('div', { class: 'ip-grid' });
|
||||
inp.oninput = () => {
|
||||
const slug = inp.value.trim().toLowerCase().replace(/[^a-z0-9-]/g, '');
|
||||
clear(prev);
|
||||
if (!slug) return;
|
||||
const b = el('button', { class: 'ip-icon' },
|
||||
el('img', { src: `/api/icons/${slug}.png` }));
|
||||
b.onclick = () => onPick(`brand:${slug}`);
|
||||
prev.append(b);
|
||||
};
|
||||
body.append(inp, prev);
|
||||
}
|
||||
|
||||
mount(tabs, typeTab, brandTab);
|
||||
mount(box, tabs, body);
|
||||
showType();
|
||||
return box;
|
||||
}
|
||||
Reference in New Issue
Block a user