Files

188 lines
7.0 KiB
JavaScript

// public/js/icon-picker.js
(function () {
let ALL_ICONS = [];
let _targetInput = null;
// ── Init: inject modal + fetch icons ──────────────────────────────────────
function init() {
_injectModal();
_fetchIcons();
_bindSearch();
}
function _injectModal() {
if (document.getElementById('iconPickerModal')) return; // tránh inject 2 lần
document.body.insertAdjacentHTML('beforeend', `
<div class="modal fade" id="iconPickerModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-icons me-2"></i>Choose an Icon</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="text" class="form-control mb-3" id="iconSearchInput" placeholder="Search icons... (e.g. phone, mail)">
<div id="iconPickerStatus" class="text-muted small mb-2"></div>
<div id="iconGrid" class="d-flex flex-wrap gap-2"></div>
</div>
</div>
</div>
</div>
`);
}
async function _fetchIcons() {
try {
const res = await fetch('/js/fa-icons.json');
if (!res.ok) throw new Error('HTTP ' + res.status);
const json = await res.json();
const POPULAR_ORDER = [
// Education
'fa-solid fa-graduation-cap', 'fa-solid fa-book', 'fa-solid fa-book-open',
'fa-solid fa-chalkboard-teacher', 'fa-solid fa-school', 'fa-solid fa-university',
'fa-solid fa-pencil', 'fa-solid fa-certificate', 'fa-solid fa-award',
'fa-solid fa-medal',
// Finance
'fa-solid fa-piggy-bank', 'fa-solid fa-dollar-sign', 'fa-solid fa-credit-card',
'fa-solid fa-wallet', 'fa-solid fa-hand-holding-dollar', 'fa-solid fa-coins',
'fa-solid fa-chart-line', 'fa-solid fa-receipt', 'fa-solid fa-building-columns',
'fa-solid fa-landmark',
// Social / Contact
'fa-solid fa-users', 'fa-solid fa-user-group', 'fa-solid fa-handshake',
'fa-solid fa-comments', 'fa-solid fa-phone', 'fa-solid fa-envelope',
'fa-solid fa-globe', 'fa-solid fa-location-dot', 'fa-solid fa-building',
'fa-solid fa-headset',
// Social Media
'fa-brands fa-facebook', 'fa-brands fa-instagram', 'fa-brands fa-youtube',
'fa-brands fa-linkedin', 'fa-brands fa-twitter', 'fa-brands fa-tiktok',
'fa-brands fa-whatsapp', 'fa-brands fa-telegram',
];
ALL_ICONS = Object.entries(json).flatMap(([name, meta]) =>
(meta.styles || [])
.filter(s => ['solid', 'regular', 'brands'].includes(s))
.map(style => ({
value: `fa-${style} fa-${name}`,
label: meta.label || name,
}))
);
ALL_ICONS.sort((a, b) => {
const ai = POPULAR_ORDER.indexOf(a.value);
const bi = POPULAR_ORDER.indexOf(b.value);
if (ai === -1 && bi === -1) return 0;
if (ai === -1) return 1;
if (bi === -1) return -1;
return ai - bi;
});
console.log('[IconPicker] loaded:', ALL_ICONS.length, 'icons');
} catch (err) {
console.error('[IconPicker] fetch failed:', err);
}
}
function _bindSearch() {
document.addEventListener('input', function (e) {
if (e.target.id !== 'iconSearchInput') return;
const q = e.target.value.toLowerCase().trim();
const filtered = q
? ALL_ICONS.filter(i => i.label.toLowerCase().includes(q) || i.value.includes(q))
: ALL_ICONS;
console.log('[IconPicker] search:', q, '→', filtered.length);
_renderGrid(filtered);
});
}
// ── Public: open picker ───────────────────────────────────────────────────
function open(inputEl) {
_targetInput = inputEl;
document.getElementById('iconSearchInput').value = '';
if (!ALL_ICONS.length) {
_setStatus('Loading icons...');
_renderGrid([]);
} else {
_setStatus('');
_renderGrid(ALL_ICONS);
}
const modal = new bootstrap.Modal(document.getElementById('iconPickerModal'));
modal.show();
document.getElementById('iconPickerModal').addEventListener('shown.bs.modal', function handler() {
document.getElementById('iconSearchInput').focus();
this.removeEventListener('shown.bs.modal', handler);
});
}
function _renderGrid(icons) {
const grid = document.getElementById('iconGrid');
grid.style.visibility = 'hidden';
if (!icons.length) {
grid.innerHTML = '<p class="text-muted small">No icons found.</p>';
return;
}
_setStatus(`Showing ${Math.min(icons.length, 200)} / ${icons.length}`);
grid.innerHTML = icons.slice(0, 200).map(icon => `
<button type="button"
class="btn btn-outline-secondary d-flex flex-column align-items-center justify-content-center gap-1"
style="width:80px;height:72px;font-size:11px"
onclick="IconPickerPick('${_esc(icon.value)}')"
title="${_esc(icon.label)}">
<i class="${icon.value}" style="font-size:1.4rem"></i>
<span class="text-truncate w-100 text-center">${icon.label}</span>
</button>
`).join('');
document.fonts.ready.then(() => {
grid.style.visibility = 'visible';
});
}
function iconPickerInput(cssClass, currentValue) {
return (
'<div class="input-group">' +
'<span class="input-group-text icon-preview-cell" style="min-width:38px">' +
(currentValue ? '<i class="' + escHtml(currentValue) + '"></i>' : '') +
'</span>' +
'<input type="text" class="form-control ' + cssClass + '" value="' + escHtml(currentValue) + '" ' +
'placeholder="Click to pick..." readonly style="cursor:pointer;background:#fff" onclick="IconPicker.open(this)">' +
'</div>'
);
}
function pick(value) {
if (!_targetInput) return;
_targetInput.value = value;
// Update preview nếu có
const previewCell = _targetInput.closest('.input-group')?.querySelector('.icon-preview-cell');
if (previewCell) previewCell.innerHTML = `<i class="${value}"></i>`;
bootstrap.Modal.getInstance(document.getElementById('iconPickerModal')).hide();
console.log('[IconPicker] picked:', value);
}
function _setStatus(msg) {
const el = document.getElementById('iconPickerStatus');
if (el) el.textContent = msg;
}
function _esc(val) {
return String(val || '').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
// ── Expose global ─────────────────────────────────────────────────────────
window.IconPicker = { init, open, pick };
window.IconPicker = { init, open, pick, inputHtml: iconPickerInput };
window.IconPicker.pick = pick;
window.IconPickerPick = pick;
})();