Restructure video data storage by moving hardcoded arrays to an external JSON file. Implement async data loading functions in both script.js and video.js to fetch data from the new JSON file. This improves maintainability and makes it easier to update video content without modifying source code.
361 lines
12 KiB
JavaScript
361 lines
12 KiB
JavaScript
let videos = [];
|
|
let currentCategory = 'All';
|
|
|
|
let searchInput, searchBtn, searchResults, searchResultsContent, videoGrid, shortsGrid, categoryBtns, subscriptionsList;
|
|
|
|
async function init() {
|
|
searchInput = document.querySelector('.search-input');
|
|
searchBtn = document.querySelector('.search-btn');
|
|
searchResults = document.querySelector('.search-results');
|
|
searchResultsContent = document.querySelector('.search-results-content');
|
|
videoGrid = document.querySelector('.video-grid');
|
|
shortsGrid = document.querySelector('.shorts-grid');
|
|
categoryBtns = document.querySelectorAll('.category-btn');
|
|
subscriptionsList = document.querySelector('.subscriptions-list');
|
|
|
|
await loadVideos();
|
|
if (videoGrid && shortsGrid) {
|
|
renderVideos();
|
|
}
|
|
if (subscriptionsList) {
|
|
renderSubscriptions();
|
|
}
|
|
bindEvents();
|
|
}
|
|
|
|
async function loadVideos() {
|
|
try {
|
|
const response = await fetch('src/video.json');
|
|
const data = await response.json();
|
|
videos = data.videos;
|
|
} catch (error) {
|
|
console.error('Failed to load videos:', error);
|
|
}
|
|
}
|
|
|
|
function renderVideos() {
|
|
const filteredVideos = filterVideosByCategory(currentCategory);
|
|
const videosOnly = filteredVideos.filter(video => video.type === 'video');
|
|
const shortsOnly = filteredVideos.filter(video => video.type === 'short');
|
|
|
|
videoGrid.innerHTML = '';
|
|
shortsGrid.innerHTML = '';
|
|
|
|
// Render long videos
|
|
videosOnly.forEach(video => {
|
|
const videoCard = createVideoCard(video);
|
|
videoGrid.appendChild(videoCard);
|
|
});
|
|
|
|
// Render shorts videos
|
|
shortsOnly.forEach(video => {
|
|
const shortsCard = createShortsCard(video);
|
|
shortsGrid.appendChild(shortsCard);
|
|
});
|
|
}
|
|
|
|
function filterVideosByCategory(category) {
|
|
if (category === 'All') {
|
|
return videos;
|
|
}
|
|
|
|
const categoryMap = {
|
|
'Music': ['music'],
|
|
'Game': ['game'],
|
|
'Technology': ['tech'],
|
|
'Art': ['art'],
|
|
'Recently': videos.filter(v => v.uploadDate.includes('days ago')),
|
|
'Watched': []
|
|
};
|
|
|
|
if (categoryMap[category]) {
|
|
if (Array.isArray(categoryMap[category]) && categoryMap[category].length > 0 && typeof categoryMap[category][0] === 'string') {
|
|
return videos.filter(v => categoryMap[category].includes(v.category));
|
|
} else if (Array.isArray(categoryMap[category])) {
|
|
return categoryMap[category];
|
|
}
|
|
}
|
|
|
|
return videos;
|
|
}
|
|
|
|
function createVideoCard(video) {
|
|
const card = document.createElement('div');
|
|
card.className = 'video-card';
|
|
|
|
card.innerHTML = `
|
|
<div class="video-thumbnail">
|
|
<img src="${video.thumbnail}" alt="${video.title}">
|
|
<span class="video-duration">${video.duration}</span>
|
|
</div>
|
|
<div class="video-info">
|
|
<div class="video-channel-avatar">
|
|
<i class="fas fa-user avatar-img"></i>
|
|
</div>
|
|
<div class="video-details">
|
|
<h3 class="video-title">${video.title}</h3>
|
|
<div class="video-meta">
|
|
<div class="video-channel">${video.channel}</div>
|
|
<div class="video-stats">
|
|
<span>${video.views} views</span>
|
|
<span>${video.uploadDate}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="video-menu">
|
|
<button class="menu-toggle">
|
|
<i class="fas fa-ellipsis-v"></i>
|
|
</button>
|
|
<div class="menu-dropdown">
|
|
<div class="menu-item">
|
|
<i class="fas fa-plus"></i>
|
|
<span>Add to Watch Later</span>
|
|
</div>
|
|
<div class="menu-item">
|
|
<i class="fas fa-clock"></i>
|
|
<span>Save to "Watch Later"</span>
|
|
</div>
|
|
<div class="menu-item">
|
|
<i class="fas fa-list"></i>
|
|
<span>Save to Playlist</span>
|
|
</div>
|
|
<div class="menu-item">
|
|
<i class="fas fa-download"></i>
|
|
<span>Download</span>
|
|
</div>
|
|
<div class="menu-item">
|
|
<i class="fas fa-share"></i>
|
|
<span>Share</span>
|
|
</div>
|
|
<div class="menu-item">
|
|
<i class="fas fa-thumbs-down"></i>
|
|
<span>Not Interested</span>
|
|
</div>
|
|
<div class="menu-item">
|
|
<i class="fas fa-ban"></i>
|
|
<span>Report Channel</span>
|
|
</div>
|
|
<div class="menu-item">
|
|
<i class="fas fa-flag"></i>
|
|
<span>Report Video</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
const menuToggle = card.querySelector('.menu-toggle');
|
|
const menuDropdown = card.querySelector('.menu-dropdown');
|
|
|
|
menuToggle.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
menuDropdown.classList.toggle('show');
|
|
});
|
|
|
|
document.addEventListener('click', () => { // Close the menu when clicking outside
|
|
menuDropdown.classList.remove('show');
|
|
});
|
|
|
|
// Add click event to the card
|
|
card.addEventListener('click', () => {
|
|
window.location.href = `video.html?id=${video.id}`;
|
|
});
|
|
|
|
return card;
|
|
}
|
|
|
|
function createShortsCard(video) {
|
|
const card = document.createElement('div');
|
|
card.className = 'shorts-card';
|
|
|
|
card.innerHTML = `
|
|
<div class="shorts-thumbnail">
|
|
<img src="${video.thumbnail}" alt="${video.title}">
|
|
</div>
|
|
<div class="shorts-info">
|
|
<h3 class="shorts-title">${video.title}</h3>
|
|
<div class="shorts-stats">
|
|
<span><i class="fas fa-eye"></i>${video.views}</span>
|
|
<span><i class="fas fa-clock"></i>${video.uploadDate}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Add click event to the card
|
|
card.addEventListener('click', () => {
|
|
window.location.href = `short.html?id=${video.id}`;
|
|
});
|
|
|
|
return card;
|
|
}
|
|
|
|
function renderSubscriptions() { // Render the subscriptions list
|
|
const subscriptions = [
|
|
{ name: '花譜 -KAF-', avatar: 'img/avatar/kaf.png' },
|
|
{ name: 'ryo (supercell)', avatar: 'img/avatar/ryo.jpg' },
|
|
{ name: 'ヰ世界情緒', avatar: 'img/avatar/isekaijoucho.jpg' },
|
|
{ name: 'MyGo!!!!!', avatar: 'img/avatar/mygo.png' },
|
|
{ name: 'ZUTOMAYO', avatar: 'img/avatar/ztmy.jpg' },
|
|
{ name: 'Aqu3ra', avatar: 'img/avatar/aqu3ra.png' },
|
|
{ name: '『超かぐや姫 ! 』公式', avatar: 'img/avatar/cpk.png' },
|
|
{ name: '礼衣 / Rei', avatar: 'img/avatar/rei.jpg' },
|
|
{ name: 'tayori', avatar: 'img/avatar/tayori.jpg' }
|
|
];
|
|
|
|
subscriptionsList.innerHTML = '';
|
|
|
|
subscriptions.forEach(sub => {
|
|
const item = document.createElement('div');
|
|
item.className = 'subscription-item';
|
|
|
|
item.innerHTML = `
|
|
<div class="subscription-avatar">
|
|
<img src="${sub.avatar}" alt="${sub.name}">
|
|
</div>
|
|
<div class="subscription-name">${sub.name}</div>
|
|
`;
|
|
|
|
subscriptionsList.appendChild(item);
|
|
});
|
|
}
|
|
function performSearch(query) { // Perform the search and render the results
|
|
if (!query.trim()) {
|
|
searchResults.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
const results = videos.filter(video =>
|
|
video.title.toLowerCase().includes(query.toLowerCase()) ||
|
|
video.channel.toLowerCase().includes(query.toLowerCase())
|
|
);
|
|
|
|
renderSearchResults(results, query);
|
|
}
|
|
|
|
// Render the search results
|
|
function renderSearchResults(results, query) {
|
|
searchResultsContent.innerHTML = '';
|
|
|
|
if (results.length === 0) {
|
|
searchResultsContent.innerHTML = '<p>No videos found related to your search.</p>';
|
|
searchResults.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
results.forEach(video => {
|
|
const item = document.createElement('div');
|
|
item.className = 'search-result-item';
|
|
|
|
// Highlight the search keywords in the title and channel name
|
|
const highlightedTitle = video.title.replace(new RegExp(`(${query})`, 'gi'), '<span class="highlight">$1</span>');
|
|
const highlightedChannel = video.channel.replace(new RegExp(`(${query})`, 'gi'), '<span class="highlight">$1</span>');
|
|
|
|
item.innerHTML = `
|
|
<div class="search-result-thumbnail">
|
|
<img src="${video.thumbnail}" alt="${video.title}">
|
|
</div>
|
|
<div class="search-result-details">
|
|
<h4 class="search-result-title">${highlightedTitle}</h4>
|
|
<div class="search-result-channel">${highlightedChannel}</div>
|
|
<div class="search-result-stats">${video.views} views · ${video.uploadDate}</div>
|
|
</div>
|
|
`;
|
|
|
|
// Add click event to the search result item
|
|
item.addEventListener('click', () => {
|
|
window.location.href = `video.html?id=${video.id}`;
|
|
});
|
|
|
|
searchResultsContent.appendChild(item);
|
|
});
|
|
|
|
searchResults.style.display = 'block';
|
|
}
|
|
|
|
// Bind events to the DOM elements
|
|
function bindEvents() { // Bind events to the DOM elements
|
|
// Menu button event - toggle sidebar
|
|
const menuBtn = document.querySelector('.menu-btn');
|
|
const sidebar = document.querySelector('.sidebar');
|
|
|
|
if (menuBtn && sidebar) {
|
|
menuBtn.addEventListener('click', () => {
|
|
sidebar.classList.toggle('collapsed');
|
|
});
|
|
}
|
|
|
|
// Search event
|
|
if (searchInput) {
|
|
searchInput.addEventListener('input', (e) => {
|
|
performSearch(e.target.value);
|
|
});
|
|
}
|
|
|
|
if (searchBtn) {
|
|
searchBtn.addEventListener('click', () => {
|
|
performSearch(searchInput ? searchInput.value : '');
|
|
});
|
|
}
|
|
|
|
// Close the search results when clicking outside
|
|
if (searchResults) {
|
|
document.addEventListener('click', (e) => {
|
|
if (!e.target.closest('.search-container') && !e.target.closest('.search-results')) {
|
|
searchResults.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Category buttons event
|
|
if (categoryBtns.length > 0) {
|
|
categoryBtns.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
// Remove all active states
|
|
categoryBtns.forEach(b => b.classList.remove('active'));
|
|
// Add current active state
|
|
btn.classList.add('active');
|
|
// Update current category
|
|
currentCategory = btn.textContent;
|
|
// Re-render videos and shorts
|
|
if (videoGrid && shortsGrid) {
|
|
renderVideos();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Sidebar navigation buttons event
|
|
const navBtns = document.querySelectorAll('.nav-btn');
|
|
if (navBtns.length > 0) {
|
|
navBtns.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
// Remove all active states
|
|
navBtns.forEach(b => b.classList.remove('active'));
|
|
// Add current active state
|
|
btn.classList.add('active');
|
|
});
|
|
});
|
|
}
|
|
|
|
// Subscription section expand/collapse event
|
|
const subscriptionTitle = document.querySelector('.nav-section-title');
|
|
if (subscriptionTitle) {
|
|
subscriptionTitle.addEventListener('click', () => {
|
|
const subscriptionsList = document.querySelector('.subscriptions-list');
|
|
const chevron = subscriptionTitle.querySelector('i');
|
|
|
|
if (subscriptionsList && chevron) {
|
|
subscriptionsList.classList.toggle('collapsed');
|
|
|
|
if (subscriptionsList.classList.contains('collapsed')) {
|
|
chevron.style.transform = 'rotate(-90deg)';
|
|
} else {
|
|
chevron.style.transform = 'rotate(0deg)';
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Page load event: initialize the application
|
|
window.addEventListener('DOMContentLoaded', init); |