feat(settings): expandable Icon sets panel (view/upload/delete)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
70
public/views/icon_sets_panel.js
Normal file
70
public/views/icon_sets_panel.js
Normal file
@@ -0,0 +1,70 @@
|
||||
// Icon sets management panel — list, upload, delete custom icon sets.
|
||||
import { el, mount, clear } from '../dom.js';
|
||||
import { api } from '../api.js';
|
||||
|
||||
export function iconSetsPanel() {
|
||||
const root = el('div', { class: 'icon-sets-panel' });
|
||||
|
||||
async function refresh() {
|
||||
clear(root);
|
||||
let list = [];
|
||||
try { list = await api.get('/api/icon-sets'); } catch {
|
||||
root.appendChild(el('div', { class: 'muted' }, 'unavailable'));
|
||||
return;
|
||||
}
|
||||
for (const s of list) {
|
||||
const grid = el('div', { class: 'ip-grid' },
|
||||
s.icons.map(f =>
|
||||
el('div', { class: 'ip-icon' },
|
||||
el('img', { src: `/api/icon-sets/${s.set}/${f}`, title: f })
|
||||
)
|
||||
)
|
||||
);
|
||||
const head = el('div', { class: 'isp-hd' },
|
||||
el('b', {}, s.set),
|
||||
el('span', { class: 'muted' }, ` ${s.icons.length}`)
|
||||
);
|
||||
if (!s.readonly) {
|
||||
const del = el('button', { class: 'ghost' }, 'Delete');
|
||||
del.addEventListener('click', async () => {
|
||||
await api.del('/api/icon-sets/' + s.set);
|
||||
refresh();
|
||||
});
|
||||
head.appendChild(del);
|
||||
}
|
||||
root.appendChild(el('div', { class: 'isp-set' }, head, grid));
|
||||
}
|
||||
root.appendChild(uploadForm(refresh));
|
||||
}
|
||||
|
||||
refresh();
|
||||
return root;
|
||||
}
|
||||
|
||||
function uploadForm(onDone) {
|
||||
const setI = el('input', { class: 'dv-edit-name', placeholder: 'new set name (a-z0-9-)' });
|
||||
const fileI = el('input', { type: 'file', accept: '.svg,.png,.jpg,.jpeg,.zip', multiple: true });
|
||||
const urlI = el('input', { class: 'dv-edit-name', placeholder: 'or ingest from URL (image or .zip)' });
|
||||
const err = el('span', { class: 'muted', style: { fontSize: '11px' } }, '');
|
||||
const up = el('button', { class: 'dv-add' }, 'Upload');
|
||||
|
||||
up.addEventListener('click', async () => {
|
||||
const set = setI.value.trim().toLowerCase();
|
||||
if (!/^[a-z0-9-]+$/.test(set)) { err.textContent = 'set name: a-z 0-9 - only'; return; }
|
||||
if (!fileI.files.length && !urlI.value.trim()) { err.textContent = 'pick files or a URL'; return; }
|
||||
const fd = new FormData();
|
||||
for (const f of fileI.files) fd.append('files', f);
|
||||
if (urlI.value.trim()) fd.append('url', urlI.value.trim());
|
||||
up.textContent = 'Uploading…'; up.disabled = true;
|
||||
try {
|
||||
await api.postForm('/api/icon-sets/' + set, fd);
|
||||
onDone();
|
||||
} catch {
|
||||
err.textContent = 'upload failed';
|
||||
up.textContent = 'Upload';
|
||||
up.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
return el('div', { class: 'isp-upload' }, setI, fileI, urlI, up, err);
|
||||
}
|
||||
Reference in New Issue
Block a user