import { t } from '../i18n.js';
import { DEFAULTS } from '../constants.js';
/**
* createAssetElement - Creates a single asset item DOM element
* @param {Object} asset
* @returns {HTMLElement}
*/
export function createAssetElement(asset, compositeId = null) {
const item = document.createElement('div');
item.className = 'asset-item';
item.draggable = true;
item.dataset.type = asset.type || asset.id;
item.dataset.assetId = compositeId || asset.id;
item.title = asset.label;
// Support legacy 'path', new 'views.icon' or FontAwesome icon class
const isFontAwesome = asset.is_img === false || (!asset.path && !asset.views && asset.icon && asset.icon.includes('fa-'));
if (isFontAwesome) {
item.innerHTML = `
${t(asset.id) || asset.label || asset.id}
`;
} else {
const iconPath = (asset.views && asset.views.icon) ? asset.views.icon : (asset.path || DEFAULTS.DEFAULT_ICON);
item.innerHTML = `
${t(asset.id) || asset.label || asset.id}
`;
}
item.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('assetId', item.dataset.assetId);
});
return item;
}
/**
* renderFlyout - Renders the floating category window when sidebar is collapsed
* @param {HTMLElement} container
* @param {Array} assets
* @param {Object} meta
*/
export function renderFlyout(container, assets, meta) {
document.querySelectorAll('.category-flyout').forEach(f => f.remove());
const flyout = document.createElement('div');
flyout.className = 'category-flyout glass-panel active';
const rect = container.getBoundingClientRect();
flyout.style.top = `${Math.max(10, Math.min(window.innerHeight - 400, rect.top))}px`;
flyout.style.left = '90px';
flyout.innerHTML = `
`;
const content = flyout.querySelector('.flyout-content');
// Group by category for flyout as well
const groups = assets.reduce((acc, a) => {
const cat = a.category || 'Other';
if (!acc[cat]) acc[cat] = [];
acc[cat].push(a);
return acc;
}, {});
Object.keys(groups).sort().forEach(catName => {
if (Object.keys(groups).length > 1 || catName !== 'Other') {
const label = document.createElement('div');
label.className = 'asset-group-label';
label.textContent = catName;
content.appendChild(label);
}
const grid = document.createElement('div');
grid.className = 'asset-grid flyout-grid';
groups[catName].forEach(asset => grid.appendChild(createAssetElement(asset)));
content.appendChild(grid);
});
document.body.appendChild(flyout);
const closeFlyout = (e) => {
if (!flyout.contains(e.target)) {
flyout.remove();
document.removeEventListener('click', closeFlyout);
}
};
setTimeout(() => document.addEventListener('click', closeFlyout), 10);
}
/**
* renderLibrary - The main loop that renders the entire asset library
*/
export function renderLibrary(data, library, renderPackSelector) {
const { state } = data; // We expect state to be passed or accessible
const packs = data.packs || [];
const filteredPacks = packs.filter(p => state.selectedPackIds.includes(p.id));
// 1. Clear Library (except fixed categories and header)
const dynamicPacks = library.querySelectorAll('.asset-category:not(.fixed-category)');
dynamicPacks.forEach(p => p.remove());
// 2. Group Custom Assets by Pack -> Category
const packGroups = state.assetsData.reduce((acc, asset) => {
const packId = asset.pack_id || 'legacy';
if (packId !== 'legacy' && !state.selectedPackIds.includes(packId)) return acc;
const category = asset.category || 'Other';
if (!acc[packId]) acc[packId] = {};
if (!acc[packId][category]) acc[packId][category] = [];
acc[packId][category].push(asset);
return acc;
}, {});
// 3. Render each visible Pack
Object.keys(packGroups).forEach((packId, index) => {
const packMeta = packs.find(p => p.id === packId) || { id: packId, name: (packId === 'legacy' ? t('other_assets') : packId) };
const categories = packGroups[packId];
const allAssetsInPack = Object.values(categories).flat();
// Ensure proper path is set for pack-based assets
if (packId !== 'legacy') {
allAssetsInPack.forEach(asset => {
if (asset.views && asset.views.icon) {
const icon = asset.views.icon;
// Only prepend packs/ if not already present
if (!icon.startsWith('packs/')) {
asset.path = `packs/${packId}/${icon}`;
} else {
asset.path = icon;
}
}
});
}
const firstAsset = allAssetsInPack[0] || {};
const iconPath = (firstAsset.views && firstAsset.views.icon) ? firstAsset.views.icon : (firstAsset.path || DEFAULTS.DEFAULT_ICON);
const catContainer = document.createElement('div');
catContainer.className = `asset-category shadow-sm`;
const header = document.createElement('div');
header.className = 'category-header';
header.innerHTML = `${packMeta.name || packMeta.id}`;
header.onclick = () => {
if (!document.getElementById('sidebar').classList.contains('collapsed')) {
catContainer.classList.toggle('active');
}
};
const collapsedIcon = document.createElement('div');
collapsedIcon.className = 'category-collapsed-icon';
collapsedIcon.title = packMeta.name || packMeta.id;
collapsedIcon.innerHTML = `
`;
collapsedIcon.onclick = (e) => {
if (document.getElementById('sidebar').classList.contains('collapsed')) {
e.stopPropagation();
renderFlyout(catContainer, allAssetsInPack, packMeta);
}
};
const content = document.createElement('div');
content.className = 'category-content';
// Render each Category within the Pack
Object.keys(categories).sort().forEach(catName => {
const assets = categories[catName];
if (Object.keys(categories).length > 1 || catName !== 'Other') {
const subHeader = document.createElement('div');
subHeader.className = 'asset-group-label';
subHeader.textContent = catName;
content.appendChild(subHeader);
}
const itemGrid = document.createElement('div');
itemGrid.className = 'asset-grid';
assets.forEach(asset => {
const compositeId = `${packId}|${asset.id}`;
state.assetMap[compositeId] = asset;
itemGrid.appendChild(createAssetElement(asset, compositeId));
});
content.appendChild(itemGrid);
});
catContainer.append(header, collapsedIcon, content);
library.appendChild(catContainer);
});
}