| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913 |
- // PNG 序列播放工具 - 主逻辑
- // 负责动画播放、帧管理等功能
- (function () {
- // 调试日志开关
- const DEBUG = true;
- function log(...args) {
- if (DEBUG) console.log("[SeqAniPlayer]", ...args);
- }
- function logError(...args) {
- console.error("[SeqAniPlayer ERROR]", ...args);
- }
- function logWarn(...args) {
- console.warn("[SeqAniPlayer WARN]", ...args);
- }
- const foldersApi = "http://localhost:3000/api/folders";
- // 获取当前登录用户名
- let currentUsername = null;
- function getCurrentUsername() {
- if (currentUsername) {
- return currentUsername;
- }
- try {
- const loginDataStr = localStorage.getItem('loginData');
- if (!loginDataStr) {
- return null;
- }
-
- const loginData = JSON.parse(loginDataStr);
- const now = Date.now();
-
- // 检查是否过期
- if (now >= loginData.expireTime) {
- localStorage.removeItem('loginData');
- return null;
- }
-
- currentUsername = loginData.user ? loginData.user.username : null;
- return currentUsername;
- } catch (error) {
- console.error('[SeqAniPlayer] 获取用户名失败:', error);
- return null;
- }
- }
- const fpsSlider = document.getElementById("fpsSlider");
- const fpsValue = document.getElementById("fpsValue");
- const cardsGrid = document.getElementById("cardsGrid");
- const cardCountLabel = document.getElementById("cardCount");
- const folderNameLabel = document.getElementById("folderName");
- const folderCounterLabel = document.getElementById("folderCounter");
- const frameCounterLabel = document.getElementById("frameCounter");
- const downloadBtn = document.getElementById("downloadBtn");
- const prevBtn = document.getElementById("prevFolderBtn");
- const nextBtn = document.getElementById("nextFolderBtn");
- const playerImage = document.getElementById("playerImage");
- const playerLoadingOverlay = document.getElementById("playerLoadingOverlay");
- const playerError = document.getElementById("playerError");
- const playerShell = document.querySelector(".player-image-shell");
- const dropHint = document.getElementById("dropHint");
- let availableFolders = [];
- let cards = [];
- let cardTemplate = null;
- let cardTemplatePromise = null;
- let currentIndex = -1;
- let currentFps = 8;
- let currentFolder = "";
- let frameList = [];
- let currentFrameCursor = 0;
- let stageTimer = null;
- let frameSourceMode = "remote";
- let localFrameResources = [];
- function padFrame(index) {
- return index.toString().padStart(2, "0");
- }
- function buildFrameSrc(folder, index) {
- const frameName = padFrame(index);
- const username = getCurrentUsername();
- if (!username) {
- logWarn('未登录,无法加载图片');
- return '';
- }
- // 使用API路径,从用户目录加载
- const imagePath = `${folder}/${frameName}.png`;
- return `/api/disk/preview?username=${encodeURIComponent(username)}&path=${encodeURIComponent(imagePath)}`;
- }
- function buildFolderName(prefix, index, padding) {
- const num = index.toString().padStart(padding, "0");
- return `${prefix}${num}`;
- }
- async function ensureCardTemplate() {
- if (cardTemplate) {
- return cardTemplate;
- }
- if (cardTemplatePromise) {
- return cardTemplatePromise;
- }
- cardTemplatePromise = fetch("./card.html")
- .then((response) => {
- if (!response.ok) {
- throw new Error("Failed to load card template");
- }
- return response.text();
- })
- .then((html) => {
- const wrapper = document.createElement("div");
- wrapper.innerHTML = html.trim();
- const template = wrapper.querySelector("#card-template");
- if (!template) {
- throw new Error("Card template missing");
- }
- cardTemplate = template;
- return cardTemplate;
- })
- .catch((error) => {
- console.error(error);
- cardTemplatePromise = null;
- throw error;
- });
- return cardTemplatePromise;
- }
- function destroyCards() {
- cards.forEach((card) => card.destroy && card.destroy());
- cards = [];
- if (cardsGrid) {
- cardsGrid.innerHTML = "";
- }
- }
- function updateCardCount() {
- if (!cardCountLabel) return;
- cardCountLabel.textContent = `${availableFolders.length} 个动画`;
- }
- function updateNavDisabledState() {
- const disabled = availableFolders.length <= 1;
- if (prevBtn) prevBtn.disabled = disabled;
- if (nextBtn) nextBtn.disabled = disabled;
- }
- function updateMeta() {
- const total = availableFolders.length;
- const safeIndex = currentIndex >= 0 ? currentIndex : 0;
- const folderName = availableFolders[safeIndex] || "--";
- if (folderNameLabel) {
- folderNameLabel.textContent = folderName;
- }
- if (folderCounterLabel) {
- folderCounterLabel.textContent = total ? `${safeIndex + 1} / ${total}` : "0 / 0";
- }
- }
- function showLoading(isLoading) {
- if (!playerLoadingOverlay) return;
- playerLoadingOverlay.classList.toggle("is-visible", isLoading);
- }
- function showPlayerError(message) {
- if (!playerError) return;
- playerError.textContent = message;
- playerError.hidden = false;
- }
- function hidePlayerError() {
- if (!playerError) return;
- playerError.hidden = true;
- }
- function stopStageAnimation() {
- if (stageTimer) {
- clearInterval(stageTimer);
- stageTimer = null;
- }
- }
- function setStagePlaceholderVisible(isVisible) {
- if (!playerImage) {
- return;
- }
- playerImage.classList.toggle("is-hidden", Boolean(isVisible));
- if (isVisible) {
- playerImage.removeAttribute("src");
- }
- }
- function cleanupLocalFrames() {
- if (!localFrameResources.length) {
- return;
- }
- localFrameResources.forEach((frame) => {
- if (frame && frame.url) {
- URL.revokeObjectURL(frame.url);
- }
- });
- localFrameResources = [];
- }
- function switchToRemoteSource() {
- if (frameSourceMode !== "remote") {
- cleanupLocalFrames();
- frameSourceMode = "remote";
- }
- }
- function startStageLoop() {
- log("🎬 启动动画循环");
- stopStageAnimation();
- if (!frameList.length) {
- logWarn("帧列表为空,无法启动循环");
- return;
- }
- const interval = 1000 / currentFps;
- log(`⏱️ 动画循环启动,帧数: ${frameList.length}, FPS: ${currentFps}, 间隔: ${interval}ms`);
- stageTimer = setInterval(() => {
- if (!frameList.length) {
- stopStageAnimation();
- return;
- }
- currentFrameCursor = (currentFrameCursor + 1) % frameList.length;
- updateStageImage(frameList[currentFrameCursor]);
- }, interval);
- }
- function updateStageImage(frameData) {
- if (!playerImage) {
- logWarn("playerImage 元素不存在");
- return;
- }
- if (frameSourceMode === "local") {
- const frame = typeof frameData === "object" ? frameData : frameList[frameData];
- if (!frame || !frame.url) {
- logWarn("本地模式:帧数据无效");
- return;
- }
- setStagePlaceholderVisible(false);
- if (playerImage.src !== frame.url) {
- log(`🖼️ 更新图片: ${frame.name}`);
- playerImage.src = frame.url;
- }
- return;
- }
- if (!currentFolder) {
- logWarn("远程模式:文件夹未设置");
- return;
- }
- const frameNumber = typeof frameData === "number" ? frameData : parseInt(frameData, 10);
- if (Number.isNaN(frameNumber)) {
- logWarn("远程模式:帧编号无效");
- return;
- }
- const nextSrc = buildFrameSrc(currentFolder, frameNumber);
- setStagePlaceholderVisible(false);
- if (playerImage.src !== nextSrc) {
- playerImage.src = nextSrc;
- }
- }
- function setFps(fps) {
- currentFps = fps;
- if (frameList.length > 0) {
- startStageLoop();
- }
- }
- async function buildCards() {
- if (!cardsGrid || !window.SequenceCard) {
- return;
- }
- const template = await ensureCardTemplate();
- destroyCards();
- availableFolders.forEach((folderName, index) => {
- const templateContent = template.content.firstElementChild;
- if (!templateContent) {
- return;
- }
- const cardElement = templateContent.cloneNode(true);
- cardsGrid.appendChild(cardElement);
- const cardInstance = new window.SequenceCard(
- cardElement,
- folderName,
- index,
- buildFrameSrc,
- handleCardSelect
- );
- cardInstance.loadPreview();
- cards.push(cardInstance);
- });
- updateCardCount();
- }
- function handleCardSelect(folderName, cardIndex) {
- if (typeof cardIndex !== "number") {
- return;
- }
- selectCardByIndex(cardIndex);
- }
- function highlightActiveCard() {
- cards.forEach((card) => {
- if (typeof card.setActive === "function") {
- card.setActive(card.index === currentIndex);
- }
- });
- }
- function resetStage() {
- stopStageAnimation();
- switchToRemoteSource();
- frameList = [];
- currentFolder = "";
- currentFrameCursor = 0;
- showLoading(false);
- hidePlayerError();
- setStagePlaceholderVisible(true);
- if (frameCounterLabel) {
- frameCounterLabel.textContent = "0 帧";
- }
- if (folderNameLabel) {
- folderNameLabel.textContent = "--";
- }
- if (folderCounterLabel) {
- folderCounterLabel.textContent = "0 / 0";
- }
- }
- async function selectCardByIndex(index) {
- if (!availableFolders.length) {
- resetStage();
- return;
- }
- const total = availableFolders.length;
- currentIndex = ((index % total) + total) % total;
- currentFolder = availableFolders[currentIndex];
- updateMeta();
- highlightActiveCard();
- await startStageForFolder(currentFolder);
- }
- function sanitizeFrameList(frameInfo) {
- if (frameInfo && Array.isArray(frameInfo.frames) && frameInfo.frames.length > 0) {
- return frameInfo.frames;
- }
- const maxFrame = frameInfo && frameInfo.maxFrame ? frameInfo.maxFrame : 0;
- if (!maxFrame) {
- return [];
- }
- return Array.from({ length: maxFrame }, (_, idx) => idx + 1);
- }
- async function startStageForFolder(folderName) {
- stopStageAnimation();
- switchToRemoteSource();
- frameList = [];
- currentFrameCursor = 0;
- showLoading(true);
- hidePlayerError();
- if (frameCounterLabel) {
- frameCounterLabel.textContent = "0 帧";
- }
- if (!folderName) {
- showLoading(false);
- showPlayerError("未选择动画");
- return;
- }
- try {
- const response = await fetch(`http://localhost:3000/api/frames/${folderName}`);
- if (!response.ok) {
- throw new Error("Failed to fetch frames");
- }
- const data = await response.json();
- frameList = sanitizeFrameList(data);
- if (!frameList.length) {
- showPlayerError("暂无可用帧");
- setStagePlaceholderVisible(true);
- showLoading(false);
- return;
- }
- if (frameCounterLabel) {
- frameCounterLabel.textContent = `${frameList.length} 帧`;
- }
- updateStageImage(frameList[0]);
- showLoading(false);
- startStageLoop();
- } catch (error) {
- console.error(error);
- showLoading(false);
- showPlayerError("加载失败");
- }
- }
- async function loadAvailableFolders() {
- availableFolders = [];
- try {
- const response = await fetch(foldersApi);
- if (!response.ok) {
- throw new Error("Failed to fetch folders");
- }
- const folders = await response.json();
- availableFolders = Array.isArray(folders) ? folders : [];
- } catch (error) {
- console.warn("使用回退文件夹列表:", error.message);
- const maxCount = 3002;
- for (let i = 1; i <= maxCount; i++) {
- availableFolders.push(buildFolderName("player_", i, 4));
- }
- }
- updateNavDisabledState();
- await buildCards();
- if (!availableFolders.length) {
- resetStage();
- }
- }
- async function startLocalPreview(files, folderLabel) {
- log("🎬 开始本地预览,文件数:", files?.length, "文件夹:", folderLabel);
-
- if (!Array.isArray(files) || !files.length) {
- logWarn("文件数组为空或无效");
- showPlayerError("未检测到 PNG 图片");
- return;
- }
- log("文件列表:");
- files.forEach((file, idx) => {
- log(` [${idx}] ${file.name} - ${file.size} bytes`);
- });
- stopStageAnimation();
- showLoading(true);
- hidePlayerError();
- setStagePlaceholderVisible(false);
- cleanupLocalFrames();
- const orderedFiles = sortFilesForPlayback(files);
- log("排序后的文件数:", orderedFiles.length);
-
- localFrameResources = orderedFiles.map((file, index) => ({
- url: URL.createObjectURL(file),
- name: file.name || `frame_${index + 1}`,
- loaded: false,
- }));
- frameSourceMode = "local";
- frameList = localFrameResources;
- currentFrameCursor = 0;
- currentFolder = folderLabel || "本地导入";
- currentIndex = -1;
- if (folderNameLabel) {
- folderNameLabel.textContent = currentFolder;
- }
- if (folderCounterLabel) {
- folderCounterLabel.textContent = "-";
- }
- if (frameCounterLabel) {
- frameCounterLabel.textContent = `${localFrameResources.length} 帧`;
- }
- // 预加载所有图片后再开始播放
- try {
- log("开始预加载图片...");
- await preloadLocalFrames(localFrameResources);
- if (localFrameResources.length > 0) {
- log("✅ 预加载完成,开始播放");
- updateStageImage(localFrameResources[0]);
- showLoading(false);
- startStageLoop();
- } else {
- logWarn("预加载后资源为空");
- showLoading(false);
- showPlayerError("图片加载失败");
- }
- } catch (error) {
- logError("预加载图片失败:", error);
- showLoading(false);
- showPlayerError("图片加载失败");
- }
- }
- function preloadLocalFrames(frames) {
- return new Promise((resolve) => {
- if (!frames || !frames.length) {
- log("⚠️ 预加载:帧列表为空");
- resolve();
- return;
- }
- let loadedCount = 0;
- let hasError = false;
- const totalFrames = frames.length;
- log(`🖼️ 预加载 ${totalFrames} 个帧...`);
- frames.forEach((frame, index) => {
- const img = new Image();
-
- img.onload = () => {
- frame.loaded = true;
- loadedCount++;
- log(` ✅ [${loadedCount}/${totalFrames}] ${frame.name} 加载成功`);
- if (loadedCount === totalFrames) {
- log(`🎉 所有帧加载完成!`);
- resolve();
- }
- };
- img.onerror = () => {
- hasError = true;
- loadedCount++;
- logWarn(` ❌ [${loadedCount}/${totalFrames}] 帧 ${index + 1} 加载失败: ${frame.name}`);
- if (loadedCount === totalFrames) {
- logWarn(`预加载完成,但有 ${hasError ? '错误' : '部分失败'}`);
- resolve();
- }
- };
- log(` ⏳ 开始加载 [${index + 1}/${totalFrames}]: ${frame.name}`);
- img.src = frame.url;
- });
- // 超时保护:最多等待 10 秒
- setTimeout(() => {
- if (loadedCount < totalFrames) {
- logWarn(`⏰ 预加载超时,已加载 ${loadedCount}/${totalFrames} 帧`);
- resolve();
- }
- }, 10000);
- });
- }
- function sortFilesForPlayback(files) {
- return files
- .slice()
- .sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: "base" }));
- }
- function bindDropZone() {
- if (!playerShell) {
- return;
- }
- playerShell.addEventListener("dragenter", handleDropZoneDragEnter);
- playerShell.addEventListener("dragover", handleDropZoneDragOver);
- playerShell.addEventListener("dragleave", handleDropZoneDragLeave);
- playerShell.addEventListener("drop", handleDropZoneDrop);
- }
- function preventDragDefaults(event) {
- if (!event) {
- return;
- }
- event.preventDefault();
- event.stopPropagation();
- }
- function setDropZoneState(isActive) {
- if (!playerShell) {
- return;
- }
- playerShell.classList.toggle("is-dragging", Boolean(isActive));
- }
- function handleDropZoneDragEnter(event) {
- preventDragDefaults(event);
- setDropZoneState(true);
- }
- function handleDropZoneDragOver(event) {
- preventDragDefaults(event);
- setDropZoneState(true);
- }
- function handleDropZoneDragLeave(event) {
- preventDragDefaults(event);
- if (!playerShell) {
- return;
- }
- const related = event.relatedTarget;
- if (related && playerShell.contains(related)) {
- return;
- }
- setDropZoneState(false);
- }
- async function handleDropZoneDrop(event) {
- log("📥 Drop事件触发");
- preventDragDefaults(event);
- setDropZoneState(false);
- const transfer = event.dataTransfer;
- if (!transfer) {
- logWarn("dataTransfer 为空");
- return;
- }
- log("dataTransfer.items.length:", transfer.items?.length);
- log("dataTransfer.files.length:", transfer.files?.length);
- try {
- const { files, folderLabel } = await collectDroppedPngFiles(transfer);
- log("✅ 收集到的文件数:", files.length, "文件夹名:", folderLabel);
- if (!files.length) {
- logWarn("未检测到 PNG 图片");
- showPlayerError("未检测到 PNG 图片");
- return;
- }
- await startLocalPreview(files, folderLabel);
- } catch (error) {
- logError("拖放处理错误:", error);
- showPlayerError("读取文件夹失败");
- }
- }
- async function collectDroppedPngFiles(dataTransfer) {
- const items = Array.from((dataTransfer && dataTransfer.items) || []);
- log("🔍 开始收集文件,items数量:", items.length);
- let collected = [];
- if (items.length) {
- log("处理 dataTransfer.items...");
- const results = await Promise.all(items.map((item, idx) => {
- log(` - Item ${idx}: kind=${item.kind}, type=${item.type}`);
- return readDataTransferItem(item);
- }));
- collected = results.flat();
- log("从 items 收集到:", collected.length, "个条目");
- }
- if (!collected.length && dataTransfer && dataTransfer.files) {
- log("Items为空,尝试使用 dataTransfer.files...");
- collected = Array.from(dataTransfer.files).map((file) => {
- log(` - File: ${file.name}, type=${file.type}, size=${file.size}`);
- return { file };
- });
- log("从 files 收集到:", collected.length, "个文件");
- }
- log("过滤前总数:", collected.length);
- collected.forEach((entry, idx) => {
- if (entry.file) {
- log(` [${idx}] ${entry.file.name} - type: ${entry.file.type}, path: ${entry.file.webkitRelativePath || '(无路径)'}`);
- }
- });
- const pngEntries = collected.filter(({ file }) => {
- const isPng = isTopLevelPngFile(file);
- if (!isPng && file) {
- log(` ❌ 过滤掉: ${file.name} (type=${file.type})`);
- }
- return isPng;
- });
-
- log("✅ 过滤后 PNG 文件数:", pngEntries.length);
-
- return {
- files: pngEntries.map((entry) => entry.file),
- folderLabel: deriveFolderLabel(pngEntries),
- };
- }
- function isTopLevelPngFile(file) {
- if (!file) {
- log(" 🔍 isTopLevelPngFile: file 为空");
- return false;
- }
- const isPng = file.type === "image/png" || /\.png$/i.test(file.name || "");
- log(` 🔍 isTopLevelPngFile: ${file.name}, type="${file.type}", isPng=${isPng}`);
- if (!isPng) {
- return false;
- }
- if (!file.webkitRelativePath) {
- log(` ✅ 无相对路径,视为顶级文件`);
- return true;
- }
- const segments = file.webkitRelativePath.split("/").filter(Boolean);
- const isTopLevel = segments.length <= 2;
- log(` 📂 相对路径: ${file.webkitRelativePath}, 层级=${segments.length}, isTopLevel=${isTopLevel}`);
- return isTopLevel;
- }
- function deriveFolderLabel(entries) {
- if (!entries || !entries.length) {
- return "本地导入";
- }
- for (const entry of entries) {
- if (entry.rootName) {
- return entry.rootName;
- }
- const inferred = inferFolderFromFile(entry.file);
- if (inferred) {
- return inferred;
- }
- }
- const fallback = entries[0] && entries[0].file && entries[0].file.name;
- return fallback ? fallback.replace(/\.png$/i, "") : "本地导入";
- }
- function inferFolderFromFile(file) {
- if (!file || !file.webkitRelativePath) {
- return "";
- }
- const segments = file.webkitRelativePath.split("/").filter(Boolean);
- return segments.length ? segments[0] : "";
- }
- function inferFolderFromEntry(entry) {
- if (!entry || !entry.fullPath) {
- return "";
- }
- const segments = entry.fullPath.split("/").filter(Boolean);
- if (segments.length >= 2) {
- return segments[segments.length - 2];
- }
- return segments[0] || "";
- }
- function readDataTransferItem(item) {
- return new Promise((resolve) => {
- if (!item || item.kind !== "file") {
- log(" ⚠️ Item 不是文件类型");
- resolve([]);
- return;
- }
- const entry = item.webkitGetAsEntry ? item.webkitGetAsEntry() : null;
- if (!entry) {
- log(" ⚠️ 无法获取 entry,使用 getAsFile");
- const file = item.getAsFile();
- resolve(file ? [{ file }] : []);
- return;
- }
- log(` 📁 Entry: ${entry.name}, isFile=${entry.isFile}, isDirectory=${entry.isDirectory}`);
- if (entry.isFile) {
- entry.file(
- (file) => {
- log(` ✅ 读取文件成功: ${file.name}`);
- resolve(file ? [{ file, rootName: inferFolderFromEntry(entry) }] : []);
- },
- (err) => {
- logError(` ❌ 读取文件失败:`, err);
- resolve([]);
- }
- );
- return;
- }
- if (entry.isDirectory) {
- log(` 📂 开始读取目录: ${entry.name}`);
- readDirectoryImmediateFiles(entry).then((files) => {
- log(` ✅ 目录读取完成,文件数: ${files.length}`);
- resolve(
- files.map((file) => ({
- file,
- rootName: entry.name || inferFolderFromFile(file),
- }))
- );
- });
- return;
- }
- resolve([]);
- });
- }
- function readDirectoryImmediateFiles(directoryEntry) {
- return new Promise((resolve) => {
- if (!directoryEntry || !directoryEntry.isDirectory) {
- logWarn("directoryEntry 无效或不是目录");
- resolve([]);
- return;
- }
- const reader = directoryEntry.createReader();
- const files = [];
- let batchCount = 0;
- const readBatch = () => {
- reader.readEntries(
- (entries) => {
- batchCount++;
- log(` 📦 读取批次 ${batchCount}: ${entries.length} 个条目`);
- if (!entries.length) {
- log(` ✅ 目录读取完成,总文件数: ${files.length}`);
- resolve(files);
- return;
- }
- let pending = entries.length;
- entries.forEach((entry) => {
- if (entry.isFile) {
- log(` 📄 文件: ${entry.name}`);
- entry.file(
- (file) => {
- if (file) {
- log(` ✅ 文件读取成功: ${file.name}, size=${file.size}, type=${file.type}`);
- files.push(file);
- }
- pending -= 1;
- if (pending === 0) {
- readBatch();
- }
- },
- (err) => {
- logError(` ❌ 文件读取失败: ${entry.name}`, err);
- pending -= 1;
- if (pending === 0) {
- readBatch();
- }
- }
- );
- } else {
- log(` 📁 跳过子目录: ${entry.name}`);
- // skip subdirectories completely
- pending -= 1;
- if (pending === 0) {
- readBatch();
- }
- }
- });
- },
- (err) => {
- logError(" ❌ readEntries 失败:", err);
- resolve(files);
- }
- );
- };
- readBatch();
- });
- }
- function bindControls() {
- if (fpsSlider && fpsValue) {
- fpsSlider.value = currentFps;
- fpsValue.textContent = `${currentFps} FPS`;
- fpsSlider.addEventListener("input", () => {
- const value = parseInt(fpsSlider.value, 10) || currentFps;
- setFps(value);
- fpsValue.textContent = `${value} FPS`;
- });
- }
- if (prevBtn) {
- prevBtn.addEventListener("click", () => selectCardByIndex(currentIndex - 1));
- }
- if (nextBtn) {
- nextBtn.addEventListener("click", () => selectCardByIndex(currentIndex + 1));
- }
- if (downloadBtn) {
- downloadBtn.addEventListener("click", () => {
- if (!currentFolder) {
- return;
- }
- if (window.SpriteSheetMaker && typeof window.SpriteSheetMaker.handleDownloadClick === "function") {
- window.SpriteSheetMaker.handleDownloadClick(currentFolder, currentIndex);
- }
- });
- }
- document.addEventListener("keydown", (event) => {
- if (event.key === "ArrowLeft") {
- selectCardByIndex(currentIndex - 1);
- } else if (event.key === "ArrowRight") {
- selectCardByIndex(currentIndex + 1);
- }
- });
- bindDropZone();
- }
- let previewCard = null;
- window.addEventListener("DOMContentLoaded", async () => {
- // 初始化预览卡片
- const container = document.getElementById('previewCardContainer');
- if (container && window.PreviewCard) {
- try {
- previewCard = new window.PreviewCard(container, {
- fps: 8,
- onFpsChange: (fps) => {
- // log('FPS changed to:', fps);
- setFps(fps);
- }
- });
- // log('✅ PreviewCard initialized');
- } catch (error) {
- logError('Failed to initialize PreviewCard:', error);
- }
- }
-
- bindControls();
- loadAvailableFolders();
- });
- window.addEventListener("beforeunload", cleanupLocalFrames);
- })();
|