Spaces:
Running
Running
| /** | |
| * VideoCharm - Main Application | |
| * Core application logic and initialization | |
| */ | |
| const App = (function() { | |
| // DOM Elements | |
| let loadingScreen; | |
| let toast; | |
| let navItems; | |
| let screens; | |
| let historyList; | |
| let favoritesList; | |
| let emptyHistory; | |
| let emptyFavorites; | |
| let clearHistoryBtn; | |
| let clearFavoritesBtn; | |
| let likesCount; | |
| let favoritesCount; | |
| let viewsCount; | |
| let swipeIndicator; | |
| let profileActions; | |
| /** | |
| * Initialize the application | |
| */ | |
| function init() { | |
| // Get DOM elements | |
| loadingScreen = document.getElementById('loadingScreen'); | |
| toast = document.getElementById('toast'); | |
| navItems = document.querySelectorAll('.nav-item'); | |
| screens = document.querySelectorAll('.screen'); | |
| historyList = document.getElementById('historyList'); | |
| favoritesList = document.getElementById('favoritesList'); | |
| emptyHistory = document.getElementById('emptyHistory'); | |
| emptyFavorites = document.getElementById('emptyFavorites'); | |
| clearHistoryBtn = document.getElementById('clearHistoryBtn'); | |
| clearFavoritesBtn = document.getElementById('clearFavoritesBtn'); | |
| likesCount = document.getElementById('likesCount'); | |
| favoritesCount = document.getElementById('favoritesCount'); | |
| viewsCount = document.getElementById('viewsCount'); | |
| swipeIndicator = document.getElementById('swipeIndicator'); | |
| profileActions = document.querySelectorAll('.action-row'); | |
| // Initialize storage | |
| StorageManager.init(); | |
| // Initialize video player | |
| VideoPlayer.init(); | |
| // Set up event listeners | |
| setupEventListeners(); | |
| // Load initial video and start playing immediately | |
| VideoPlayer.loadVideos(1, true).then(() => { | |
| // Show swipe indicator after initial load | |
| setTimeout(() => { | |
| swipeIndicator.style.display = 'flex'; | |
| setTimeout(() => { | |
| swipeIndicator.style.opacity = '0'; | |
| setTimeout(() => { | |
| swipeIndicator.style.display = 'none'; | |
| }, 500); | |
| }, 3000); | |
| }, 500); | |
| // Load more videos in background for smooth experience | |
| setTimeout(() => { | |
| VideoPlayer.loadVideos(3, false); | |
| }, 1000); | |
| }); | |
| // Update statistics | |
| updateStats(); | |
| } | |
| /** | |
| * Set up event listeners | |
| */ | |
| function setupEventListeners() { | |
| // Navigation | |
| navItems.forEach(item => { | |
| item.addEventListener('click', function() { | |
| const targetScreen = this.getAttribute('data-screen'); | |
| switchToScreen(targetScreen); | |
| }); | |
| }); | |
| // Clear history button | |
| clearHistoryBtn.addEventListener('click', function() { | |
| if (confirm('确定要清空所有观看历史吗?')) { | |
| StorageManager.clearHistory(); | |
| loadHistoryList(); | |
| updateStats(); | |
| showToast('历史记录已清空'); | |
| } | |
| }); | |
| // Clear favorites button | |
| clearFavoritesBtn.addEventListener('click', function() { | |
| if (confirm('确定要清空所有收藏吗?')) { | |
| StorageManager.clearFavorites(); | |
| loadFavoritesList(); | |
| updateStats(); | |
| showToast('收藏夹已清空'); | |
| } | |
| }); | |
| // Profile actions | |
| profileActions.forEach(action => { | |
| action.addEventListener('click', function() { | |
| const actionType = this.getAttribute('data-action'); | |
| handleProfileAction(actionType); | |
| }); | |
| }); | |
| // Comment button | |
| document.getElementById('commentButton').addEventListener('click', function() { | |
| showToast('评论功能开发中,敬请期待!'); | |
| }); | |
| // Share button | |
| document.getElementById('shareButton').addEventListener('click', function() { | |
| showToast('分享功能开发中,敬请期待!'); | |
| }); | |
| } | |
| /** | |
| * Switch to a different screen | |
| * @param {String} screenId ID of screen to switch to | |
| */ | |
| function switchToScreen(screenId) { | |
| // Update navigation | |
| navItems.forEach(item => { | |
| item.classList.remove('active'); | |
| if (item.getAttribute('data-screen') === screenId) { | |
| item.classList.add('active'); | |
| } | |
| }); | |
| // Update screens | |
| screens.forEach(screen => { | |
| screen.classList.remove('active'); | |
| if (screen.id === screenId) { | |
| screen.classList.add('active'); | |
| } | |
| }); | |
| // Handle specific screen actions | |
| if (screenId === 'historyScreen') { | |
| loadHistoryList(); | |
| } else if (screenId === 'favoritesScreen') { | |
| loadFavoritesList(); | |
| } | |
| } | |
| /** | |
| * Load history list from storage | |
| */ | |
| function loadHistoryList() { | |
| const history = StorageManager.getHistory(); | |
| // Clear list | |
| historyList.innerHTML = ''; | |
| if (history.length === 0) { | |
| // Show empty state | |
| emptyHistory.style.display = 'flex'; | |
| historyList.style.display = 'none'; | |
| } else { | |
| // Hide empty state | |
| emptyHistory.style.display = 'none'; | |
| historyList.style.display = 'grid'; | |
| // Add history items | |
| history.forEach(item => { | |
| const videoItem = createVideoListItem(item, 'history'); | |
| historyList.appendChild(videoItem); | |
| }); | |
| } | |
| } | |
| /** | |
| * Load favorites list from storage | |
| */ | |
| function loadFavoritesList() { | |
| const favorites = StorageManager.getFavorites(); | |
| // Clear list | |
| favoritesList.innerHTML = ''; | |
| if (favorites.length === 0) { | |
| // Show empty state | |
| emptyFavorites.style.display = 'flex'; | |
| favoritesList.style.display = 'none'; | |
| } else { | |
| // Hide empty state | |
| emptyFavorites.style.display = 'none'; | |
| favoritesList.style.display = 'grid'; | |
| // Add favorite items | |
| favorites.forEach(item => { | |
| const videoItem = createVideoListItem(item, 'favorite'); | |
| favoritesList.appendChild(videoItem); | |
| }); | |
| } | |
| } | |
| /** | |
| * Create a video list item for history or favorites | |
| * @param {Object} videoData Video data | |
| * @param {String} type Item type ('history' or 'favorite') | |
| * @returns {HTMLElement} Video list item element | |
| */ | |
| function createVideoListItem(videoData, type) { | |
| const videoItem = document.createElement('div'); | |
| videoItem.className = 'video-item'; | |
| videoItem.setAttribute('data-id', videoData.id); | |
| // Use thumbnail if available, otherwise use placeholder | |
| let thumbnailUrl; | |
| if (videoData.thumbnail) { | |
| thumbnailUrl = videoData.thumbnail; | |
| } else { | |
| thumbnailUrl = generatePlaceholderImage(videoData.id); | |
| } | |
| videoItem.innerHTML = ` | |
| <div class="video-thumbnail"> | |
| <img src="${thumbnailUrl}" alt="${videoData.title}" onerror="this.src='${generatePlaceholderImage(videoData.id)}'"> | |
| <div class="video-duration">${formatDuration(videoData.duration)}</div> | |
| </div> | |
| <div class="video-item-info"> | |
| <div class="video-item-title">${videoData.title}</div> | |
| <div class="video-item-meta"> | |
| <div class="time-ago"> | |
| <svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | |
| <path d="M11.99 2C6.47 2 2 6.48 2 12C2 17.52 6.47 22 11.99 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 11.99 2ZM12 20C7.58 20 4 16.42 4 12C4 7.58 7.58 4 12 4C16.42 4 20 7.58 20 12C20 16.42 16.42 20 12 20ZM12.5 7H11V13L16.2 16.2L17 14.9L12.5 12.2V7Z"/> | |
| </svg> | |
| ${formatTimeAgo(videoData.timestamp)} | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| // Add click event | |
| videoItem.addEventListener('click', function() { | |
| VideoPlayer.playVideoFromLibrary(videoData); | |
| showToast(`正在播放${type === 'favorite' ? '收藏' : '历史'}视频`); | |
| }); | |
| return videoItem; | |
| } | |
| /** | |
| * Update profile statistics | |
| */ | |
| function updateStats() { | |
| const likes = StorageManager.getLikes().length; | |
| const favorites = StorageManager.getFavorites().length; | |
| const views = StorageManager.getViews(); | |
| likesCount.textContent = likes; | |
| favoritesCount.textContent = favorites; | |
| viewsCount.textContent = views; | |
| } | |
| /** | |
| * Handle profile action | |
| * @param {String} actionType Type of action | |
| */ | |
| function handleProfileAction(actionType) { | |
| switch (actionType) { | |
| case 'settings': | |
| case 'preferences': | |
| case 'about': | |
| showToast(`${actionType} 功能开发中,敬请期待`); | |
| break; | |
| case 'logout': | |
| if (confirm('确定要退出登录吗?')) { | |
| showToast('退出登录成功'); | |
| } | |
| break; | |
| } | |
| } | |
| /** | |
| * Show loading screen | |
| */ | |
| function showLoading() { | |
| loadingScreen.style.display = 'flex'; | |
| } | |
| /** | |
| * Hide loading screen | |
| */ | |
| function hideLoading() { | |
| loadingScreen.style.display = 'none'; | |
| } | |
| /** | |
| * Show toast message | |
| * @param {String} message Message to show | |
| * @param {Number} duration Duration in ms | |
| */ | |
| function showToast(message, duration = 2000) { | |
| toast.textContent = message; | |
| toast.classList.add('show'); | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| }, duration); | |
| } | |
| /** | |
| * Format duration in seconds to MM:SS | |
| * @param {Number} seconds Duration in seconds | |
| * @returns {String} Formatted duration | |
| */ | |
| function formatDuration(seconds) { | |
| if (!seconds) return '00:00'; | |
| const minutes = Math.floor(seconds / 60); | |
| const remainingSeconds = Math.floor(seconds % 60); | |
| return `${minutes < 10 ? '0' : ''}${minutes}:${remainingSeconds < 10 ? '0' : ''}${remainingSeconds}`; | |
| } | |
| /** | |
| * Format timestamp to relative time | |
| * @param {Number} timestamp Timestamp | |
| * @returns {String} Formatted time | |
| */ | |
| function formatTimeAgo(timestamp) { | |
| const now = new Date().getTime(); | |
| const diff = now - timestamp; | |
| // Less than 1 minute | |
| if (diff < 60 * 1000) { | |
| return "刚刚"; | |
| } | |
| // Less than 1 hour | |
| if (diff < 60 * 60 * 1000) { | |
| return Math.floor(diff / (60 * 1000)) + "分钟前"; | |
| } | |
| // Less than 24 hours | |
| if (diff < 24 * 60 * 60 * 1000) { | |
| return Math.floor(diff / (60 * 60 * 1000)) + "小时前"; | |
| } | |
| // Less than 7 days | |
| if (diff < 7 * 24 * 60 * 60 * 1000) { | |
| return Math.floor(diff / (24 * 60 * 60 * 1000)) + "天前"; | |
| } | |
| // Format as date | |
| const date = new Date(timestamp); | |
| return `${date.getMonth() + 1}月${date.getDate()}日`; | |
| } | |
| /** | |
| * Generate consistent placeholder image URL based on ID | |
| * @param {String} id Item ID for consistent image generation | |
| * @returns {String} Image URL | |
| */ | |
| function generatePlaceholderImage(id) { | |
| // Generate a deterministic number from the string ID | |
| let hash = 0; | |
| if (id && id.length > 0) { | |
| for (let i = 0; i < id.length; i++) { | |
| hash = ((hash << 5) - hash) + id.charCodeAt(i); | |
| hash |= 0; // Convert to 32bit integer | |
| } | |
| } | |
| const randomId = Math.abs(hash % 1000); // Ensure positive | |
| return `https://picsum.photos/seed/${randomId}/300/500`; | |
| } | |
| // Public API | |
| return { | |
| init: init, | |
| showLoading: showLoading, | |
| hideLoading: hideLoading, | |
| showToast: showToast, | |
| switchToScreen: switchToScreen, | |
| updateStats: updateStats | |
| }; | |
| })(); | |
| // Initialize application when DOM is loaded | |
| document.addEventListener('DOMContentLoaded', function() { | |
| App.init(); | |
| }); |