| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905 |
- (function () {
- // 主预览卡片类
- class PreviewCard {
- constructor(container, options = {}) {
- this.container = container;
- this.options = {
- fps: 8,
- onFpsChange: null,
- ...options
- };
-
- this.previewImage = null;
- this.loadingOverlay = null;
- this.dropHint = null;
- this.imageError = null;
- this.fpsSlider = null;
- this.fpsValue = null;
- this.dropZone = null;
-
- this.currentFps = this.options.fps;
- this.frameList = [];
- this.currentFrameIndex = 0;
- this.animationTimer = null;
- this.frameSourceMode = "remote";
- this.localFrameResources = [];
- this.currentFolderName = '';
-
- // 当前登录用户名
- this.currentUsername = null;
-
- this.init();
- this.initUserListener();
- }
-
- // 获取当前登录用户名
- getCurrentUsername() {
- // 如果已经缓存了用户名,直接返回
- if (this.currentUsername) {
- return this.currentUsername;
- }
-
- // 从导航栏 iframe 中获取用户名
- try {
- let targetWindow = window.parent;
- while (targetWindow && targetWindow !== window) {
- try {
- const navigationFrame = targetWindow.document.getElementById('navigationFrame');
- if (navigationFrame && navigationFrame.contentWindow) {
- const navWindow = navigationFrame.contentWindow;
- const navDoc = navigationFrame.contentDocument || navWindow.document;
-
- // 检查用户是否已登录
- const userAvatarContainer = navDoc.getElementById('userAvatarContainer');
- if (userAvatarContainer) {
- const computedStyle = navDoc.defaultView.getComputedStyle(userAvatarContainer);
- if (computedStyle.display !== 'none') {
- // 用户已登录,从 userAvatar 的 alt 属性获取用户名
- const userAvatar = navDoc.getElementById('userAvatar');
- if (userAvatar && userAvatar.alt && userAvatar.alt !== '用户头像') {
- const username = userAvatar.alt;
- this.currentUsername = username;
- return username;
- }
- }
- }
- }
- } catch (e) {
- // 跨域或访问限制,继续尝试上层窗口
- }
-
- if (targetWindow.parent && targetWindow.parent !== targetWindow) {
- targetWindow = targetWindow.parent;
- } else {
- break;
- }
- }
- } catch (error) {
- console.warn('[PreviewCard] 无法获取用户名:', error);
- }
-
- return this.currentUsername;
- }
-
- // 监听登录成功消息
- initUserListener() {
- window.addEventListener('message', (event) => {
- if (event.data && event.data.type === 'login-success' && event.data.user) {
- this.currentUsername = event.data.user.username;
- } else if (event.data && event.data.type === 'logout') {
- this.currentUsername = null;
- }
- });
- }
-
- async init() {
- await this.loadTemplate();
- this.bindElements();
- this.bindEvents();
- this.setStagePlaceholderVisible(true);
-
- // 从 localStorage 恢复登录状态
- this.restoreLoginFromStorage();
- }
-
- // 从 localStorage 恢复登录状态
- restoreLoginFromStorage() {
- try {
- const loginDataStr = localStorage.getItem('loginData');
- if (!loginDataStr) {
- return;
- }
-
- const loginData = JSON.parse(loginDataStr);
- const now = Date.now();
-
- // 检查是否过期
- if (now >= loginData.expireTime) {
- localStorage.removeItem('loginData');
- return;
- }
-
- // 未过期,恢复登录状态
- if (loginData.user && loginData.user.username) {
- this.currentUsername = loginData.user.username;
- console.log('[PreviewCard] 从 localStorage 恢复登录状态:', loginData.user.username);
- }
- } catch (error) {
- console.error('[PreviewCard] 恢复登录状态失败:', error);
- }
- }
-
- async loadTemplate() {
- const response = await fetch('./card.html');
- const html = await response.text();
- const wrapper = document.createElement('div');
- wrapper.innerHTML = html.trim();
- const template = wrapper.querySelector('#preview-card-template');
- if (!template) {
- throw new Error('Preview card template not found');
- }
- const content = template.content.cloneNode(true);
- this.container.appendChild(content);
- }
-
- bindElements() {
- this.previewImage = this.container.querySelector('.preview-image');
- this.loadingOverlay = this.container.querySelector('.loading-overlay');
- this.loadingText = this.container.querySelector('.loading-text');
- this.dropHint = this.container.querySelector('.drop-hint');
- this.imageError = this.container.querySelector('.image-error');
- this.fpsSlider = this.container.querySelector('.fps-slider');
- this.fpsValue = this.container.querySelector('.fps-value');
- this.dropZone = this.container.querySelector('.preview-card-stage');
- this.infoBar = this.container.querySelector('.preview-info-bar');
- this.folderNameElement = this.container.querySelector('.folder-name');
- this.btnExport = this.container.querySelector('.btn-export');
- this.btnDownload = this.container.querySelector('.btn-download');
- this.btnAI = this.container.querySelector('.btn-ai');
- }
-
- bindEvents() {
- // FPS控制
- if (this.fpsSlider) {
- this.fpsSlider.addEventListener('input', () => {
- const value = parseInt(this.fpsSlider.value, 10) || this.currentFps;
- this.setFps(value);
- if (this.fpsValue) {
- this.fpsValue.textContent = `${value} FPS`;
- }
- if (typeof this.options.onFpsChange === 'function') {
- this.options.onFpsChange(value);
- }
- });
- }
-
- // 拖放事件
- if (this.dropZone) {
- this.dropZone.addEventListener('dragenter', (e) => this.handleDragEnter(e));
- this.dropZone.addEventListener('dragover', (e) => this.handleDragOver(e));
- this.dropZone.addEventListener('dragleave', (e) => this.handleDragLeave(e));
- this.dropZone.addEventListener('drop', (e) => this.handleDrop(e));
- }
-
- // 导出按钮(旧版兼容)
- if (this.btnExport) {
- this.btnExport.addEventListener('click', () => this.handleExport());
- }
-
- // 下载按钮
- if (this.btnDownload) {
- this.btnDownload.addEventListener('click', () => this.handleExport());
- }
-
- // AI生图按钮
- if (this.btnAI) {
- this.btnAI.addEventListener('click', () => this.handleAIGenerate());
- }
- }
-
- handleDragEnter(event) {
- event.preventDefault();
- event.stopPropagation();
- this.dropZone.classList.add('is-dragging');
- }
-
- handleDragOver(event) {
- event.preventDefault();
- event.stopPropagation();
- }
-
- handleDragLeave(event) {
- event.preventDefault();
- if (!this.dropZone.contains(event.relatedTarget)) {
- this.dropZone.classList.remove('is-dragging');
- }
- }
-
- async handleDrop(event) {
- event.preventDefault();
- event.stopPropagation();
- this.dropZone.classList.remove('is-dragging');
-
- // 获取拖入的文件夹名称
- const transfer = event.dataTransfer;
- if (!transfer || !transfer.items || transfer.items.length === 0) {
- // console.warn('[PreviewCard] No data transfer items');
- return;
- }
-
- // 获取文本数据(可能是JSON对象或文件夹名称)
- const item = transfer.items[0];
- if (item.kind === 'string' && item.type === 'text/plain') {
- item.getAsString(async (dataString) => {
- // console.log('[PreviewCard] Dropped folder:', dataString);
-
- // 尝试解析为JSON对象
- let folderName = dataString;
- let fileType = 'directory'; // 默认假设是文件夹
- let pngCount = undefined; // PNG文件数量(如果有)
-
- try {
- const data = JSON.parse(dataString);
- // 如果是对象,提取路径、名称和类型
- if (data && typeof data === 'object') {
- folderName = data.path || data.name || dataString;
- fileType = data.type || 'directory';
- pngCount = data.pngCount; // 从拖拽数据中获取PNG数量
- }
- } catch (e) {
- // 如果不是JSON,直接使用原始字符串
- folderName = dataString;
- }
-
- // 验证:必须是文件夹
- if (fileType !== 'directory') {
- this.showError('❌ 请拖入文件夹,不支持单个文件');
- return;
- }
-
- // 验证:文件夹中是否包含PNG图片
- const isValid = await this.validateFolderHasPNG(folderName, pngCount);
- if (!isValid) {
- this.showError('❌ 该文件夹不包含PNG图片');
- return;
- }
-
- // console.log('[PreviewCard] Resolved folder name:', folderName);
- await this.loadAndCacheFolderAnimation(folderName);
- });
- }
- }
-
- async validateFolderHasPNG(folderName, pngCount) {
- try {
- // 1. 优先使用传递的pngCount(来自拖拽数据)
- if (pngCount !== undefined) {
- // console.log('[PreviewCard] 使用缓存的pngCount:', pngCount);
- return pngCount > 0;
- }
-
- // 2. 尝试从DiskManager的缓存中获取
- if (window.diskManager && window.diskManager.getFileFromCache) {
- const cachedFile = window.diskManager.getFileFromCache(folderName);
- if (cachedFile && cachedFile.pngCount !== undefined) {
- // console.log('[PreviewCard] 从DiskManager缓存获取pngCount:', cachedFile.pngCount);
- return cachedFile.pngCount > 0;
- }
- }
-
- // 3. 最后才请求服务器(使用新的disk API)
- // console.log('[PreviewCard] 请求服务器验证文件夹:', folderName);
- const username = this.getCurrentUsername();
- if (!username) {
- return false;
- }
- const response = await fetch(`/api/disk/list?username=${encodeURIComponent(username)}&path=${encodeURIComponent(folderName)}`);
- if (!response.ok) {
- return false;
- }
-
- const data = await response.json();
-
- // 检查是否是文件夹并且有PNG文件
- if (data.success && data.files) {
- // 检查当前路径对应的文件夹信息
- // 注意:list API返回的是文件夹内的文件列表,不是文件夹本身
- // 所以我们需要统计PNG文件数量
- const pngFiles = data.files.filter(f =>
- f.type === 'file' && f.name.toLowerCase().endsWith('.png')
- );
- return pngFiles.length > 0;
- }
-
- return false;
- } catch (error) {
- // console.error('[PreviewCard] 验证文件夹失败:', error);
- return false;
- }
- }
-
- async loadAndCacheFolderAnimation(folderName) {
- try {
- // 显示加载动画
- this.showLoading(true);
- this.hideError();
- this.setStagePlaceholderVisible(false);
-
- // console.log('[PreviewCard] 开始加载文件夹:', folderName);
-
- // 1. 从网盘系统获取该文件夹的文件列表
- const username = this.getCurrentUsername();
- if (!username) {
- throw new Error('请先登录');
- }
- const listResponse = await fetch(`/api/disk/list?username=${encodeURIComponent(username)}&path=${encodeURIComponent(folderName)}`);
- if (!listResponse.ok) {
- throw new Error('获取文件列表失败');
- }
-
- const listData = await listResponse.json();
- // console.log('[PreviewCard] 文件列表响应:', listData);
-
- if (!listData.success || !listData.files) {
- throw new Error('获取文件列表失败');
- }
-
- // 2. 筛选出PNG文件并按文件名排序
- const pngFiles = listData.files
- .filter(f => f.type === 'file' && f.name.toLowerCase().endsWith('.png'))
- .sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true }));
-
- // console.log('[PreviewCard] PNG文件列表:', pngFiles.map(f => f.name));
-
- if (!pngFiles.length) {
- throw new Error('文件夹中没有可用的PNG图片');
- }
-
- // console.log('[PreviewCard] 找到 PNG 文件数量:', pngFiles.length);
-
- // 3. 打开或创建缓存
- const cache = await caches.open('animation-frames-v1');
-
- // 4. 构造帧URL列表(使用文件的实际名称)
- const frameUrls = pngFiles.map(file => {
- // 使用文件的完整路径(file.path)来构造URL
- const username = this.getCurrentUsername();
- if (!username) {
- return null; // 未登录时返回 null
- }
- return `/api/disk/preview?username=${encodeURIComponent(username)}&path=${encodeURIComponent(file.path)}`;
- });
-
- // console.log('[PreviewCard] 开始下载和缓存图片...');
-
- // 5. 逐个下载并缓存
- const cachedFrames = [];
- for (let i = 0; i < frameUrls.length; i++) {
- const url = frameUrls[i];
- const fileName = pngFiles[i].name;
-
- // 更新进度
- this.showLoading(true, `正在缓存图片... (${i + 1}/${frameUrls.length})`);
-
- // 检查缓存中是否已存在
- const cachedResponse = await cache.match(url);
- if (cachedResponse) {
- // console.log(`[PreviewCard] [${i + 1}/${frameUrls.length}] 从缓存加载: ${fileName}`);
- cachedFrames.push({ url, index: i, name: fileName });
- } else {
- // 下载并缓存
- try {
- // console.log(`[PreviewCard] [${i + 1}/${frameUrls.length}] 下载: ${fileName}`);
- const response = await fetch(url);
- if (response.ok) {
- await cache.put(url, response.clone());
- // console.log(`[PreviewCard] ✓ 已缓存: ${fileName}`);
- cachedFrames.push({ url, index: i, name: fileName });
- } else {
- // console.warn(`[PreviewCard] ✗ 下载失败 (${response.status}): ${fileName}`);
- }
- } catch (error) {
- // console.error(`[PreviewCard] ✗ 下载错误: ${fileName}`, error);
- }
- }
- }
-
- if (cachedFrames.length === 0) {
- throw new Error('没有成功缓存任何图片');
- }
-
- // console.log('[PreviewCard] ✅ 缓存完成,共', cachedFrames.length, '帧');
-
- // 6. 设置文件夹名称
- this.setFolderName(folderName);
-
- // 7. 加载并播放动画
- this.loadFrames(cachedFrames, 'cached');
- this.showLoading(false);
-
- // 添加播放状态类
- if (this.dropZone) {
- this.dropZone.classList.add('is-playing');
- }
-
- } catch (error) {
- // console.error('[PreviewCard] 加载失败:', error);
- this.showLoading(false);
- this.showError(error.message || '加载失败');
- this.setStagePlaceholderVisible(true);
- this.setFolderName(''); // 清除文件夹名称
- // 移除播放状态
- if (this.dropZone) {
- this.dropZone.classList.remove('is-playing');
- }
- }
- }
-
- 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);
- }
-
- showLoading(isLoading, text = '正在加载图片...') {
- if (!this.loadingOverlay) return;
- if (isLoading) {
- this.loadingOverlay.classList.add('is-visible');
- this.loadingOverlay.setAttribute('aria-hidden', 'false');
- if (this.loadingText) {
- this.loadingText.textContent = text;
- }
- } else {
- this.loadingOverlay.classList.remove('is-visible');
- this.loadingOverlay.setAttribute('aria-hidden', 'true');
- }
- }
-
- showError(message) {
- // 使用全局alert显示错误
- this.showGlobalAlert(message);
- }
-
- hideError() {
- if (!this.imageError) return;
- this.imageError.hidden = true;
- }
-
- showGlobalAlert(text, duration = 1500) {
- // 直接调用父窗口的 GlobalAlert(不通过 postMessage)
- try {
- // 向上查找有 GlobalAlert 的窗口
- let targetWindow = window.parent;
- while (targetWindow && targetWindow !== window) {
- if (targetWindow.GlobalAlert) {
- targetWindow.GlobalAlert.show(text, duration);
- return;
- }
- if (targetWindow.parent && targetWindow.parent !== targetWindow) {
- targetWindow = targetWindow.parent;
- } else {
- break;
- }
- }
-
- // 降级处理
- console.log('[Alert]', text);
- } catch (error) {
- console.error('[Card] 显示 alert 失败:', error);
- }
- }
-
- setStagePlaceholderVisible(isVisible) {
- if (this.dropHint) {
- this.dropHint.hidden = !isVisible;
- }
- if (this.previewImage) {
- this.previewImage.classList.toggle('is-hidden', isVisible);
- if (isVisible) {
- this.previewImage.removeAttribute('src');
- }
- }
- }
-
- setFolderName(name) {
- this.currentFolderName = name;
- if (this.folderNameElement) {
- this.folderNameElement.textContent = name || '--';
- }
- if (this.infoBar) {
- this.infoBar.hidden = !name;
- }
- }
-
- async handleExport() {
- if (!this.currentFolderName || !this.frameList.length) {
- this.showGlobalAlert('没有可导出的动画');
- return;
- }
-
- // 直接打开导出弹出框,不先生成预览图
- // 预览图将在弹出框中生成
- this.openExportView();
- }
-
- /**
- * 处理AI生图按钮点击
- */
- async handleAIGenerate() {
- if (!this.currentFolderName || !this.frameList.length) {
- this.showGlobalAlert('没有可用于AI生图的动画');
- return;
- }
-
- // 打开AI生图界面
- this.openAIGenerateView();
- }
-
- /**
- * 打开AI生图界面
- */
- openAIGenerateView() {
- // 先生成预览图数据,然后打开AI生图界面
- this.generatePreviewImage().then(result => {
- // 向所有父级窗口发送消息(处理多层iframe情况)
- let targetWindow = window.parent;
- while (targetWindow && targetWindow !== window) {
- targetWindow.postMessage({
- type: 'open-ai-generate-view',
- folderName: this.currentFolderName,
- spritesheetData: result.imageUrl,
- spritesheetLayout: result.layout
- }, '*');
-
- // 尝试向更上层发送
- if (targetWindow.parent && targetWindow.parent !== targetWindow) {
- targetWindow = targetWindow.parent;
- } else {
- break;
- }
- }
- }).catch(error => {
- console.error('[PreviewCard] 生成预览图失败:', error);
- this.showGlobalAlert('生成预览图失败:' + error.message);
- });
- }
-
- /**
- * 生成预览图
- * @returns {Promise<string>} 预览图的 base64 URL
- */
- async generatePreviewImage() {
- const folderName = this.currentFolderName;
-
- // 获取用户名
- const username = this.getCurrentUsername();
- if (!username) {
- throw new Error('请先登录');
- }
-
- // 获取帧列表(从服务端获取,服务端会判断是否有图片)
- const encodedFolderName = encodeURIComponent(folderName);
- let apiUrl = `http://localhost:3000/api/frames/${encodedFolderName}`;
- if (username) {
- apiUrl += `?username=${encodeURIComponent(username)}`;
- }
- const response = await fetch(apiUrl);
-
- if (!response.ok) {
- // 服务端返回错误,解析错误信息
- const errorData = await response.json().catch(() => ({}));
- throw new Error(errorData.error || '无法获取帧列表');
- }
-
- const data = await response.json();
- const frameNumbers = data.frames || [];
-
- // 服务端已经判断过是否有图片,如果返回200但frames为空,说明有问题
- if (frameNumbers.length === 0) {
- throw new Error('该文件夹中没有图片');
- }
-
- // 加载所有图片(使用正确的API路径)
- const images = [];
- for (let i = 0; i < frameNumbers.length; i++) {
- const frameNum = frameNumbers[i];
- const frameName = frameNum.toString().padStart(2, '0');
- // 使用API路径,从用户目录加载
- const imagePath = `${folderName}/${frameName}.png`;
- const imgSrc = `/api/disk/preview?username=${encodeURIComponent(username)}&path=${encodeURIComponent(imagePath)}`;
-
- const img = await new Promise((resolve, reject) => {
- const image = new Image();
- image.crossOrigin = 'anonymous';
- image.onload = () => resolve(image);
- image.onerror = () => reject(new Error(`Failed to load image: ${imgSrc}`));
- image.src = imgSrc;
- });
-
- images.push({
- img: img,
- width: img.width,
- height: img.height,
- frameNum: frameNum
- });
- }
-
- // 计算布局(简化版,使用简单的网格布局)
- const frameWidth = images[0].width;
- const frameHeight = images[0].height;
- const cols = Math.ceil(Math.sqrt(images.length));
- const rows = Math.ceil(images.length / cols);
-
- // 创建 Canvas 并绘制
- const canvas = document.createElement('canvas');
- canvas.width = frameWidth * cols;
- canvas.height = frameHeight * rows;
- const ctx = canvas.getContext('2d');
-
- // 填充透明背景
- ctx.clearRect(0, 0, canvas.width, canvas.height);
-
- // 保存布局信息
- const layout = [];
-
- // 绘制所有图片
- images.forEach((item, index) => {
- const col = index % cols;
- const row = Math.floor(index / cols);
- const x = col * frameWidth;
- const y = row * frameHeight;
- ctx.drawImage(item.img, x, y);
-
- // 保存布局信息
- layout.push({
- x: x,
- y: y,
- width: item.width,
- height: item.height,
- frameNum: item.frameNum
- });
- });
-
- // 转换为 base64
- return new Promise((resolve) => {
- canvas.toBlob((blob) => {
- const reader = new FileReader();
- reader.onload = () => {
- resolve({
- imageUrl: reader.result,
- layout: {
- layout: layout,
- sheetWidth: canvas.width,
- sheetHeight: canvas.height
- }
- });
- };
- reader.readAsDataURL(blob);
- }, 'image/png');
- });
- }
-
- /**
- * 打开导出弹出框
- */
- openExportView() {
- // 通过postMessage通知父页面打开导出弹出框
- // 传递文件夹名称,让弹出框自己生成预览图
- let targetWindow = window.parent;
- while (targetWindow && targetWindow !== window) {
- targetWindow.postMessage({
- type: 'open-export-view',
- folderName: this.currentFolderName
- }, '*');
-
- // 尝试向更上层发送
- if (targetWindow.parent && targetWindow.parent !== targetWindow) {
- targetWindow = targetWindow.parent;
- } else {
- break;
- }
- }
- }
-
- setFps(fps) {
- this.currentFps = fps;
- if (this.frameList.length > 0) {
- this.startAnimation();
- }
- }
-
- startAnimation() {
- this.stopAnimation();
- if (!this.frameList.length) return;
-
- const interval = 1000 / this.currentFps;
- this.animationTimer = setInterval(() => {
- if (!this.frameList.length) {
- this.stopAnimation();
- return;
- }
- this.currentFrameIndex = (this.currentFrameIndex + 1) % this.frameList.length;
- this.updateFrame(this.frameList[this.currentFrameIndex]);
- }, interval);
- }
-
- stopAnimation() {
- if (this.animationTimer) {
- clearInterval(this.animationTimer);
- this.animationTimer = null;
- }
- }
-
- updateFrame(frameData) {
- if (!this.previewImage) return;
-
- if ((this.frameSourceMode === 'local' || this.frameSourceMode === 'cached') && frameData && frameData.url) {
- this.setStagePlaceholderVisible(false);
- if (this.previewImage.src !== frameData.url) {
- this.previewImage.src = frameData.url;
- }
- }
- }
-
- loadFrames(frames, mode = 'local') {
- this.frameSourceMode = mode;
- this.frameList = frames;
- this.currentFrameIndex = 0;
-
- // console.log('[PreviewCard] 加载帧列表, 模式:', mode, '数量:', frames.length);
-
- if (frames.length > 0) {
- this.updateFrame(frames[0]);
- this.startAnimation();
- }
- }
-
- destroy() {
- this.stopAnimation();
- if (this.container) {
- this.container.innerHTML = '';
- }
- }
- }
-
- // 小卡片类
- class SequenceCard {
- constructor(cardElement, folderName, index, buildFrameSrc, onSelect) {
- this.cardElement = cardElement;
- this.folderName = folderName;
- this.index = index;
- this.buildFrameSrc = buildFrameSrc;
- this.onSelect = onSelect;
- this.imageElement = cardElement.querySelector(".card-image");
- this.spinnerElement = cardElement.querySelector(".loading-spinner");
- this.errorElement = cardElement.querySelector(".image-error");
- this.labelElement = cardElement.querySelector(".card-label");
- this.downloadButton = cardElement.querySelector(".card-download-btn");
- this.handleCardClick = this.handleCardClick.bind(this);
- this.handleDownloadClick = this.handleDownloadClick.bind(this);
- this.init();
- }
- init() {
- this.cardElement.dataset.folder = this.folderName || "";
- this.cardElement.dataset.index = this.index;
- this.cardElement.dataset.valid = "true";
- if (this.labelElement) {
- this.labelElement.textContent = this.formatLabel(this.folderName);
- }
- this.cardElement.addEventListener("click", this.handleCardClick);
- if (this.downloadButton) {
- this.boundDownloadHandler = (event) => {
- event.stopPropagation();
- event.preventDefault();
- this.handleDownloadClick();
- };
- this.downloadButton.addEventListener("click", this.boundDownloadHandler);
- }
- this.bindImageEvents();
- }
- bindImageEvents() {
- if (!this.imageElement) {
- return;
- }
- this.imageElement.addEventListener("load", () => {
- this.toggleSpinner(false);
- this.hideError();
- });
- this.imageElement.addEventListener("error", () => {
- this.toggleSpinner(false);
- this.showError("图片加载失败");
- });
- }
- formatLabel(name) {
- if (!name) {
- return "--";
- }
- return name.replace(/_/g, " ").toUpperCase();
- }
- loadPreview() {
- if (!this.imageElement || !this.buildFrameSrc || !this.folderName) {
- return;
- }
- this.toggleSpinner(true);
- const previewSrc = this.buildFrameSrc(this.folderName, 1);
- if (this.imageElement.src !== previewSrc) {
- this.imageElement.src = previewSrc;
- }
- }
- setActive(isActive) {
- if (isActive) {
- this.cardElement.classList.add("is-active");
- } else {
- this.cardElement.classList.remove("is-active");
- }
- }
- handleCardClick() {
- if (typeof this.onSelect === "function") {
- this.onSelect(this.folderName, this.index);
- }
- }
- handleDownloadClick() {
- if (!this.folderName) {
- return;
- }
- if (window.SpriteSheetMaker && typeof window.SpriteSheetMaker.handleDownloadClick === "function") {
- window.SpriteSheetMaker.handleDownloadClick(this.folderName, this.index);
- }
- }
- toggleSpinner(visible) {
- if (!this.spinnerElement) {
- return;
- }
- this.spinnerElement.style.display = visible ? "block" : "none";
- }
- showError(message) {
- if (!this.errorElement) {
- return;
- }
- this.errorElement.textContent = message;
- this.errorElement.hidden = false;
- }
- hideError() {
- if (!this.errorElement) {
- return;
- }
- this.errorElement.hidden = true;
- }
- destroy() {
- this.cardElement.removeEventListener("click", this.handleCardClick);
- if (this.downloadButton && this.boundDownloadHandler) {
- this.downloadButton.removeEventListener("click", this.boundDownloadHandler);
- }
- }
- }
- window.PreviewCard = PreviewCard;
- window.SequenceCard = SequenceCard;
- })();
|