// UI控制器 - 处理所有UI交互和显示逻辑 import { createResourceExtractor } from './js/app.js'; class UIController { constructor() { this.resourcesContainer = document.getElementById('resources-container'); this.errorContainer = document.getElementById('error-container'); this.progressContainer = document.getElementById('progress-container'); this.progressFill = document.querySelector('.progress-fill'); this.progressText = document.querySelector('.progress-text'); this.selectedFilesDiv = document.getElementById('selected-files'); this.processBtn = document.getElementById('process-btn'); this.fileInput = document.getElementById('file-input'); this.selectedFiles = []; this.resourceExtractor = null; this.activeAudioBlobUrls = new Set(); // 存储活跃的音频Blob URL this.init(); } async init() { // 设置页面标题 document.title = "Unity资源提取器"; // 初始化资源提取器 this.resourceExtractor = await createResourceExtractor(); // 绑定事件 this.bindEvents(); // 绑定下载按钮事件委托 this.bindDownloadEvents(); } bindEvents() { // 文件选择事件 - 确保正确添加文件 this.fileInput.addEventListener('change', (e) => { if (e.target.files && e.target.files.length > 0) { this.addSelectedFiles(Array.from(e.target.files)); this.fileInput.value = ''; // 清空input,允许选择相同文件 } }); // 处理按钮点击事件 this.processBtn.addEventListener('click', async () => { await this.processFiles(); }); // 监听资源提取器的事件 this.resourceExtractor.on('error', (error) => { this.showError(error); }); this.resourceExtractor.on('resource', (resource) => { this.displayResource(resource); }); this.resourceExtractor.on('progress', (progress) => { this.updateProgress(progress); }); } // 绑定下载按钮事件委托 bindDownloadEvents() { this.resourcesContainer.addEventListener('click', (e) => { const downloadBtn = e.target.closest('.download-btn'); if (downloadBtn) { e.preventDefault(); const url = downloadBtn.dataset.url; const filename = downloadBtn.dataset.filename; if (url) { // 创建下载链接 const a = document.createElement('a'); a.href = url; a.download = filename; // 将链接添加到DOM document.body.appendChild(a); a.click(); // 清理 document.body.removeChild(a); } } }); } addSelectedFiles(newFiles) { // 去重:检查是否已存在同名同大小的文件 newFiles.forEach(newFile => { const isDuplicate = this.selectedFiles.some(existingFile => existingFile.name === newFile.name && existingFile.size === newFile.size && existingFile.lastModified === newFile.lastModified ); if (!isDuplicate) { this.selectedFiles.push(newFile); } }); this.updateSelectedFiles(); this.processBtn.disabled = this.selectedFiles.length === 0; } updateSelectedFiles() { this.selectedFilesDiv.innerHTML = ''; this.selectedFiles.forEach((file, index) => { const fileDiv = document.createElement("div"); fileDiv.className = 'selected-file'; fileDiv.innerHTML = ` ${index + 1}. ${this.truncateFilename(file.name)} (${(file.size / 1024).toFixed(2)} KB) `; this.selectedFilesDiv.appendChild(fileDiv); }); // 绑定移除按钮事件 document.querySelectorAll('.remove-file').forEach(btn => { btn.addEventListener('click', (e) => { const index = parseInt(e.target.dataset.index); this.selectedFiles.splice(index, 1); this.updateSelectedFiles(); this.processBtn.disabled = this.selectedFiles.length === 0; }); }); } // 截断过长的文件名 truncateFilename(filename, maxLength = 30) { if (filename.length <= maxLength) { return filename; } const extensionIndex = filename.lastIndexOf('.'); if (extensionIndex === -1) { // 没有扩展名 return filename.substring(0, maxLength - 3) + '...'; } const name = filename.substring(0, extensionIndex); const ext = filename.substring(extensionIndex); if (name.length <= maxLength - ext.length - 3) { return filename; } const truncatedName = name.substring(0, maxLength - ext.length - 3) + '...'; return truncatedName + ext; } // 格式化文件名用于显示 formatFilenameForDisplay(filename) { return this.truncateFilename(filename, 25); } async processFiles() { if (this.selectedFiles.length === 0) { this.showError('请先选择要处理的文件'); return; } this.showProgress(); // 注意:不清理之前的结果,保留所有已处理的资源 for (let i = 0; i < this.selectedFiles.length; i++) { const file = this.selectedFiles[i]; this.progressText.textContent = `处理文件中: ${this.formatFilenameForDisplay(file.name)} (${i + 1}/${this.selectedFiles.length})`; this.updateProgress(i / this.selectedFiles.length); try { await this.resourceExtractor.processFile(file, this); } catch (error) { this.showError(`处理文件 ${this.formatFilenameForDisplay(file.name)} 时出错: ${error.message}`); } } this.hideProgress(); this.selectedFiles = []; this.updateSelectedFiles(); this.processBtn.disabled = true; } displayResource(resource) { const resourceDiv = document.createElement('div'); resourceDiv.className = 'resource-item'; resourceDiv.dataset.resourceId = Date.now(); // 为资源项添加唯一标识 // 存储音频资源的Blob URL if (resource.type === 'audio') { this.activeAudioBlobUrls.add(resource.url); } switch (resource.type) { case 'text': resourceDiv.innerHTML = this.createTextResource(resource); break; case 'image': resourceDiv.innerHTML = this.createImageResource(resource); break; case 'audio': resourceDiv.innerHTML = this.createAudioResource(resource); // 添加自定义音频播放器的事件绑定 setTimeout(() => this.setupCustomAudioPlayer(resourceDiv, resource), 0); break; default: resourceDiv.innerHTML = `
未知资源类型: ${resource.type}
`; } this.resourcesContainer.appendChild(resourceDiv); } createTextResource(resource) { // 获取原始文件名 const originalFileName = resource.originalFileName || 'unknown.bundle'; const fileNameWithoutExt = originalFileName.replace(/\.[^/.]+$/, ""); return `
文本 ${this.formatFilenameForDisplay(originalFileName)} ${resource.size || 'N/A'}
`; } createImageResource(resource) { // 获取原始文件名 const originalFileName = resource.originalFileName || 'unknown.bundle'; const fileNameWithoutExt = originalFileName.replace(/\.[^/.]+$/, ""); return `
图片 ${this.formatFilenameForDisplay(originalFileName)} ${resource.width}×${resource.height}
图片预览
`; } createAudioResource(resource) { // 获取原始文件名 const originalFileName = resource.originalFileName || 'unknown.bundle'; const fileNameWithoutExt = originalFileName.replace(/\.[^/.]+$/, ""); return `
音频 ${this.formatFilenameForDisplay(originalFileName)} ${resource.duration ? `${resource.duration.toFixed(2)}秒` : 'N/A'}
00:00 / 00:00
`; } setupCustomAudioPlayer(container, resource) { const audioElement = container.querySelector('.native-audio'); const playPauseBtn = container.querySelector('.play-pause-btn'); const progressSlider = container.querySelector('.progress-slider'); const timeDisplay = container.querySelector('.time-display'); // 更新播放状态 const updatePlayButton = () => { const playIcon = container.querySelector('.play-icon'); const pauseIcon = container.querySelector('.pause-icon'); if (audioElement.paused) { playIcon.style.display = 'block'; pauseIcon.style.display = 'none'; playPauseBtn.setAttribute('title', '播放'); } else { playIcon.style.display = 'none'; pauseIcon.style.display = 'block'; playPauseBtn.setAttribute('title', '暂停'); } }; // 格式化时间 const formatTime = (seconds) => { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; }; // 更新进度和时间显示 const updateProgress = () => { if (!isNaN(audioElement.duration)) { const progress = (audioElement.currentTime / audioElement.duration) * 100; progressSlider.value = progress; const currentTime = formatTime(audioElement.currentTime); const duration = formatTime(audioElement.duration); timeDisplay.textContent = `${currentTime} / ${duration}`; } }; // 播放/暂停按钮点击事件 playPauseBtn.addEventListener('click', () => { if (audioElement.paused) { audioElement.play().catch(e => { console.error('播放音频失败:', e); this.showError('播放音频失败: ' + e.message); }); } else { audioElement.pause(); } updatePlayButton(); }); // 进度条拖动事件 progressSlider.addEventListener('input', () => { const time = (progressSlider.value / 100) * audioElement.duration; audioElement.currentTime = time; }); // 音频事件监听 audioElement.addEventListener('timeupdate', updateProgress); audioElement.addEventListener('play', updatePlayButton); audioElement.addEventListener('pause', updatePlayButton); // 音频加载完成后更新时长 audioElement.addEventListener('loadedmetadata', () => { updateProgress(); }); // 音频结束时重置 audioElement.addEventListener('ended', () => { audioElement.currentTime = 0; updateProgress(); updatePlayButton(); }); // 初始更新 updatePlayButton(); // 如果资源中有时长信息,先显示 if (resource.duration) { const duration = formatTime(resource.duration); timeDisplay.textContent = `00:00 / ${duration}`; } } showProgress() { this.progressContainer.style.display = 'block'; } hideProgress() { this.progressContainer.style.display = 'none'; } updateProgress(percent) { this.progressFill.style.width = `${percent * 100}%`; } showError(message) { this.errorContainer.style.display = 'block'; this.errorContainer.innerHTML = `
${message}
`; setTimeout(() => { this.errorContainer.style.display = 'none'; }, 5000); } // 清理音频Blob URL cleanupAudioBlobUrls() { this.activeAudioBlobUrls.forEach(url => { if (url.startsWith('blob:')) { URL.revokeObjectURL(url); } }); this.activeAudioBlobUrls.clear(); } } // 页面加载完成后初始化UI控制器 document.addEventListener('DOMContentLoaded', () => { const uiController = new UIController(); // 在页面卸载前清理音频Blob URL window.addEventListener('beforeunload', () => { uiController.cleanupAudioBlobUrls(); }); });