// Debug alert for mobile debugging
if (typeof debugAlert === 'function') debugAlert('files.js loaded');
// ===== Shared state (globals so tilepicker.js can read them) =====
window.selectedImage = null; // URL of currently selected sheet
window.selectedImageName = null; // Name of selected sheet
window.selectedTileSize = window.selectedTileSize || 32; // default when in Local Mode
// Internal UI state
let selectedImageDiv = null;
// Keep ALL locally chosen sheets (multiple)
const localSheets = []; // [{ name, url, size }]
// Revoke created blob URLs on page exit to avoid leaks
window.addEventListener('beforeunload', () => {
localSheets.forEach(ls => {
if (ls.url && ls.url.startsWith('blob:')) try { URL.revokeObjectURL(ls.url); } catch {}
});
});
// View mode: 'server' (folders/images) or 'local' (only local sheets)
let viewMode = 'server';
let currentSub = ''; // track current server subfolder path
// ===== DOM =====
const fileList = document.getElementById('fileList');
const breadcrumb = document.querySelector('.breadcrumb');
const preview = document.getElementById('preview');
/** Load files for a subdirectory (server) */
function loadFiles(sub) {
viewMode = 'server';
currentSub = sub || '';
fetch('media.php?sub=' + encodeURIComponent(currentSub))
.then(r => r.json())
.then(data => {
if (data.error) throw new Error(data.error);
renderBreadcrumb(data.breadcrumb);
renderServerView(data);
})
.catch(err => {
console.error('Error loading files:', err);
if (typeof debugAlert === 'function') alert('Error loading files: ' + err.message);
// Fallback: still render Local Mode option
renderBreadcrumb([{ label: 'tiles', sub: '' }]);
renderServerView({ folders: [], images: [] });
});
}
/** Breadcrumb renderer for server paths or Local Mode */
function renderBreadcrumb(crumbs) {
breadcrumb.innerHTML = '';
if (viewMode === 'local') {
// Show a simple "Local" crumb
const span = document.createElement('span');
span.textContent = 'Local';
breadcrumb.appendChild(span);
return;
}
(crumbs || []).forEach((c, i) => {
const span = document.createElement('span');
span.textContent = c.label;
span.style.cursor = 'pointer';
span.onclick = () => loadFiles(c.sub);
breadcrumb.appendChild(span);
if (i < crumbs.length - 1) {
const sep = document.createElement('span');
sep.textContent = ' / ';
breadcrumb.appendChild(sep);
}
});
}
/** Add the "➕ Local sheet…" tile (mobile-safe file input over the tile) */
function addLocalSheetTile() {
const div = document.createElement('div');
div.className = 'folder local-sheet-tile';
div.title = 'Pick image(s) from your device (Files)';
div.textContent = '➕ Local sheet…';
div.style.position = 'relative';
fileList.appendChild(div);
const input = document.createElement('input');
input.type = 'file';
input.multiple = true;
input.accept = '.png,.jpg,.jpeg,.webp,.gif,.bmp,.svg'; // favors file explorer
Object.assign(input.style, {
position: 'absolute',
inset: '0',
width: '100%',
height: '100%',
opacity: '0',
cursor: 'pointer',
zIndex: '1'
});
input.addEventListener('change', (e) => handleLocalFiles(e.target.files), { passive: true });
input.addEventListener('input', (e) => handleLocalFiles(e.target.files), { passive: true });
input.addEventListener('touchstart', () => {}, { passive: true });
div.appendChild(input);
}
/** Handle files picked or dropped locally (supports MULTIPLE) */
function handleLocalFiles(fileListLike) {
if (!fileListLike || !fileListLike.length) return;
Array.from(fileListLike).forEach(file => {
// Avoid duplicates by name+size if user re-selects
const exists = localSheets.find(ls => ls.name === file.name && ls.size === file.size);
if (exists) return;
const url = URL.createObjectURL(file);
localSheets.push({ name: file.name, url, size: file.size });
});
// Auto-select the LAST picked file (most recent)
const last = localSheets[localSheets.length - 1];
window.selectedImage = last.url;
window.selectedImageName = last.name;
selectedImageDiv = null;
showPreview({ name: last.name, url: last.url });
// Enter Local Mode immediately
enterLocalMode();
refreshTilePickerIfOpen();
}
/** Enter Local Mode UI */
function enterLocalMode() {
viewMode = 'local';
renderBreadcrumb(null);
renderLocalView();
}
/** Exit Local Mode back to folders/images */
function exitLocalMode() {
viewMode = 'server';
// Restore server view at the last subpath
loadFiles(currentSub);
}
/** Clear all local sheets */
function clearLocalSheets() {
// Revoke blob URLs
localSheets.forEach(ls => {
if (ls.url && ls.url.startsWith('blob:')) try { URL.revokeObjectURL(ls.url); } catch {}
});
localSheets.length = 0;
// Clear selection & preview
window.selectedImage = null;
window.selectedImageName = null;
preview.innerHTML = '';
renderLocalView();
}
/** Toolbar for Local Mode: Back, Clear, Tile Size */
function renderLocalToolbar() {
const bar = document.createElement('div');
bar.className = 'local-toolbar';
bar.style.cssText = `
display:flex;align-items:center;gap:8px;flex-wrap:wrap;
padding:8px 10px;margin-bottom:10px;background:#333;border-radius:6px;
`;
const backBtn = document.createElement('button');
backBtn.textContent = '← Back to folders';
backBtn.style.cssText = `
background:#555;color:#fff;border:1px solid #777;border-radius:4px;
padding:6px 10px;cursor:pointer;font-size:12px;
`;
backBtn.onclick = exitLocalMode;
const clearBtn = document.createElement('button');
clearBtn.textContent = 'Clear local sheets';
clearBtn.style.cssText = `
background:#d44;color:#fff;border:none;border-radius:4px;
padding:6px 10px;cursor:pointer;font-size:12px;
`;
clearBtn.onclick = () => {
if (confirm('Remove all locally added sheets?')) clearLocalSheets();
};
// Tile size selector (since numeric folders are hidden here)
const sizeLabel = document.createElement('span');
sizeLabel.textContent = 'Tile size:';
sizeLabel.style.cssText = 'color:#ccc;font-size:12px;';
const sizeSelect = document.createElement('select');
sizeSelect.style.cssText = `
background:#555;color:#fff;border:1px solid #777;border-radius:4px;
padding:5px 8px;font-size:12px;
`;
const sizes = [8, 12, 16, 24, 32, 48, 64, 96, 128];
sizes.forEach(s => {
const opt = document.createElement('option');
opt.value = s;
opt.textContent = `${s}px`;
if (Number(window.selectedTileSize) === s) opt.selected = true;
sizeSelect.appendChild(opt);
});
sizeSelect.onchange = () => {
window.selectedTileSize = parseInt(sizeSelect.value, 10);
// If overlay is open, rebuild grid with the new size
refreshTilePickerIfOpen();
};
bar.appendChild(backBtn);
bar.appendChild(clearBtn);
bar.appendChild(sizeLabel);
bar.appendChild(sizeSelect);
fileList.appendChild(bar);
}
/** Render Local Mode UI (only local sheets) */
function renderLocalView() {
fileList.innerHTML = '';
preview.innerHTML = preview.innerHTML; // keep current preview (if any)
renderLocalToolbar();
if (!localSheets.length) {
const empty = document.createElement('div');
empty.style.cssText = `
color:#888;text-align:center;padding:20px;border:1px dashed #555;border-radius:6px;
`;
empty.innerHTML = `
No local sheets yet.<br>
Use <b>➕ Local sheet…</b> in folders view to add files, or drag & drop onto this area.
`;
fileList.appendChild(empty);
enableLocalDragDrop(); // allow adding by drop even in Local Mode
return;
}
const grid = document.createElement('div');
grid.style.cssText = 'display:flex;flex-wrap:wrap;gap:8px;';
fileList.appendChild(grid);
localSheets.forEach(ls => {
const div = document.createElement('div');
div.className = 'image';
div.title = `Local: ${ls.name}`;
const img = document.createElement('img');
img.src = ls.url;
img.alt = ls.name;
div.appendChild(img);
div.onclick = () => {
if (selectedImageDiv) selectedImageDiv.classList.remove('selected');
div.classList.add('selected');
selectedImageDiv = div;
window.selectedImage = ls.url;
window.selectedImageName = ls.name;
showPreview({ name: ls.name, url: ls.url });
refreshTilePickerIfOpen();
};
grid.appendChild(div);
});
enableLocalDragDrop();
}
/** Drag & Drop support (works in both modes) */
function enableLocalDragDrop() {
['dragenter','dragover'].forEach(ev =>
fileList.addEventListener(ev, e => {
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
fileList.classList.add('dropping');
}, { passive: false })
);
['dragleave','drop'].forEach(ev =>
fileList.addEventListener(ev, e => {
e.preventDefault();
fileList.classList.remove('dropping');
}, { passive: false })
);
fileList.addEventListener('drop', e => {
const files = e.dataTransfer.files;
if (files && files.length) handleLocalFiles(files);
}, { passive: false });
}
/** Render Server Mode (folders + images + Local tile) */
function renderServerView(data) {
fileList.innerHTML = '';
preview.innerHTML = preview.innerHTML; // keep current preview (if any)
// 1) Local sheet tile so user can jump into Local Mode
addLocalSheetTile();
// 2) Folders (numeric folders set tile size when clicked)
(data.folders || []).forEach(f => {
const div = document.createElement('div');
div.className = 'folder';
div.textContent = f.name;
div.onclick = () => {
if (/^\d+$/.test(f.name)) window.selectedTileSize = parseInt(f.name, 10);
loadFiles(f.sub);
};
fileList.appendChild(div);
});
// 3) Server images
(data.images || []).forEach(img => {
const div = document.createElement('div');
div.className = 'image';
const el = document.createElement('img');
el.src = img.url;
el.alt = img.name;
div.appendChild(el);
div.onclick = () => {
if (selectedImageDiv) selectedImageDiv.classList.remove('selected');
div.classList.add('selected');
selectedImageDiv = div;
window.selectedImage = img.url;
window.selectedImageName = img.name;
showPreview(img);
refreshTilePickerIfOpen();
};
fileList.appendChild(div);
});
}
/** Preview + click-to-open tile picker */
function showPreview(img) {
preview.innerHTML = `
<h3>Selected: ${img.name}</h3>
<img id="previewImg" src="${img.url}" alt="${img.name}">
`;
const big = document.getElementById('previewImg');
big.onclick = () => openOverlay('tiles');
}
/** If the tile picker overlay is already open, refresh it to the new selectedImage/size */
function refreshTilePickerIfOpen() {
const overlay = document.getElementById('overlay');
if (!overlay || overlay.style.display !== 'block') return;
if (typeof openTilePickerOverlay === 'function') openTilePickerOverlay();
}
// ===== Initial drag & drop binding for Server Mode as well =====
enableLocalDragDrop();