Files
songlistTemplate/temp_songListPlugin.js
2025-06-19 08:37:08 +02:00

420 lines
17 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
// Ensure required DOM elements exist
const genreTablesContainer = document.getElementById('genreTables');
const languageSelect = document.getElementById('languageSelect');
if (!genreTablesContainer || !languageSelect) {
console.error('Required elements not found: #genreTables or #languageSelect');
return;
}
window.currentLanguage = languageSelect.value;
/**
* Loads a JavaScript file containing song data and makes it available globally
* @param {string} filename - The name of the file to load (without .js extension)
* @returns {Promise<Array>} A promise that resolves with the loaded song data array
*/
function loadJSFile(filename) {
return new Promise((resolve, reject) => {
// Check if already loaded
if (window[filename] !== undefined) {
if (Array.isArray(window[filename])) {
return resolve(window[filename]);
}
return reject(new Error(`${filename} is already defined but is not an array`));
}
const script = document.createElement('script');
script.src = `./scripts/customsongs/${filename}.js`;
script.async = true;
script.defer = true;
script.dataset.loadedBy = 'loadJSFile';
script.dataset.filename = filename;
// Set timeout for script loading
const timeoutId = setTimeout(() => {
cleanupScript(script);
reject(new Error(`Timeout loading ${filename}.js`));
}, 10000); // 10 second timeout
script.onload = () => {
clearTimeout(timeoutId);
if (!window[filename] || !Array.isArray(window[filename])) {
console.error(`${filename} did not export an array`);
window[filename] = [];
}
resolve(window[filename]);
// Clean up the script tag after loading
cleanupScript(script);
};
script.onerror = (error) => {
clearTimeout(timeoutId);
cleanupScript(script);
reject(new Error(`Failed to load ${filename}.js: ${error.message || 'Unknown error'}`));
};
document.head.appendChild(script);
});
}
/**
* Removes a script tag from the DOM and cleans up references
* @param {HTMLScriptElement} script - The script element to remove
*/
function cleanupScript(script) {
if (script && script.parentNode) {
script.onload = null;
script.onerror = null;
script.parentNode.removeChild(script);
}
}
// Load song data files
Promise.all([
loadJSFile('chartexample')
]).then(() => {
console.log('All song data loaded successfully');
// Initialize the application after loading all data
initializeApp();
}).catch(error => {
console.error('Error loading song data:', error);
// Initialize with empty data if loading fails
window.chartexample = window.chartexample || [];
initializeApp();
});
/**
* Initializes the application after all required data is loaded
*/
function initializeApp() {
// Initial population and language setup
updateAll();
// Set up language change handler
const languageSelect = document.getElementById('languageSelect');
if (languageSelect) {
languageSelect.addEventListener('change', handleLanguageChange);
}
}
/**
* Handles language change events
*/
function handleLanguageChange() {
window.currentLanguage = this.value;
updateAll();
}
// Genre mapping and display
const songGenreMap = {
1: 'pops',
2: 'anime',
3: 'vocaloid',
4: 'gamemusic',
5: 'variety',
};
// Sort songs by genre only, maintaining the original order within each genre from customcharts.js
// Add any files you want to display as tables here.
const songListing = [
...(window.customcharts || []),
...(window.chartexample || []),
...(window.customcollections || []),
].sort((a, b) => a.genre - b.genre);
// When language selected, keep the ordering by songid
languageSelect.addEventListener('change', () => {
window.currentLanguage = languageSelect.value;
updateLanguageAttrs();
createGenreTables();
});
const genreLabels = {
pops: {
en: 'Pops',
ja: 'ポップス'
},
anime: {
en: 'Anime',
ja: 'アニメ'
},
vocaloid: {
en: 'Vocaloid',
ja: 'ボーカロイド'
},
gamemusic: {
en: 'Game Music',
ja: 'ゲームミュージック'
},
variety: {
en: 'Variety',
ja: 'バラエティ'
},
};
const difficultyLabels = {
easy: {
en: 'Easy',
ja: 'かんたん',
short: 'E',
//icon: 'assets/icons/easy.png'
},
normal: {
en: 'Normal',
ja: 'ふつう',
short: 'N',
//icon: 'assets/icons/normal.png'
},
hard: {
en: 'Hard',
ja: 'むずかしい',
short: 'H',
//icon: 'assets/icons/hard.png'
},
insane: {
en: 'Extreme',
ja: 'おに',
short: 'I',
// icon: 'assets/icons/insane.png'
},
expert: {
en: 'Ura',
ja: '裏',
short: 'X',
//icon: 'assets/icons/expert.png'
},
};
const difficulties = ['easy', 'normal', 'hard', 'insane', 'expert'];
function updateLanguageAttrs() {
document.querySelectorAll('[data-en]').forEach(element => {
element.textContent = element.getAttribute(`data-${currentLanguage}`) || element.getAttribute('data-en');
});
}
function createGenreTables() {
const fragment = document.createDocumentFragment();
Object.entries(songGenreMap).forEach(([genreId, genreKey]) => {
const songs = songListing.filter(song => song.genre == genreId);
// Skip creating the table if there are no songs in this genre
if (songs.length === 0) {
return; // Skip to next genre
}
const label = genreLabels[genreKey]?.[window.currentLanguage] || genreKey;
const container = document.createElement('div');
container.className = 'genre-table-container';
const header = document.createElement('div');
header.className = `genre-table-header genre-${genreKey}`;
header.innerHTML = `<span>${label} (${songs.length})</span><span class="toggle-icon">▼</span>`;
const table = document.createElement('table');
table.className = `genre-table genre-${genreKey}`;
table.style.display = 'table'; // Show by default
const thead = document.createElement('thead');
const headRow = document.createElement('tr');
headRow.appendChild(createTh('Title', '曲名', '40%'));
difficulties.forEach(d => headRow.appendChild(
createTh(
difficultyLabels[d].en,
difficultyLabels[d].ja,
undefined,
difficultyLabels[d].icon,
difficultyLabels[d].en
)
));
// Add Video link icon with tooltips
const videolinkTh = document.createElement('th');
videolinkTh.setAttribute('data-en', 'Link');
videolinkTh.setAttribute('data-ja', 'リンク');
videolinkTh.title = window.currentLanguage === 'ja' ? '動画リンク' : 'Video Link';
// whether to include video icon. uncomment this below to enable.
// videolinkTh.innerHTML = "<img src='assets/icons/film.png' alt='Video Link' title='Video Link' style='width: 28px; height: 28px; display: block; margin: 0 auto;'>";
videolinkTh.innerHTML = '<p style="width: 28px; height: 28px; display: block; margin: 0 auto; text-align: center;">▶</p>';
headRow.appendChild(videolinkTh);
thead.appendChild(headRow);
table.appendChild(thead);
const tbody = document.createElement('tbody');
// Maintain the original order from customcharts.js
// No sorting needed as we want to preserve the original order
for (const song of songs) {
const row = document.createElement('tr');
row.className = 'song-row';
row.setAttribute('data-song-title', song.title);
const titleCell = document.createElement('td');
titleCell.innerHTML = `<div class="song-title">${window.currentLanguage === 'ja' && song.titleJA ? song.titleJA : song.title}</div><div class="song-subtitle"><small>${window.currentLanguage === 'ja' && song.subtitleJA ? song.subtitleJA : song.subtitle}</small></div>`;
row.appendChild(titleCell);
for (const diff of difficulties) {
const cell = document.createElement('td');
cell.className = 'difficulty-cell';
const level = song.difficulty[diff];
if (level) {
const levelContainer = document.createElement('div');
levelContainer.style.position = 'relative';
levelContainer.textContent = level;
// Add branch icon if this difficulty has branches
if (song.isBranch && song.isBranch[diff] === true) {
const branchIcon = document.createElement('span');
branchIcon.className = 'branch-icon';
branchIcon.title = window.currentLanguage === 'ja' ? '分岐あり' : 'Has branches';
branchIcon.textContent = '↗';
branchIcon.style.display = 'block';
branchIcon.style.fontSize = '0.8em';
branchIcon.style.lineHeight = '1';
branchIcon.style.marginTop = '2px';
levelContainer.appendChild(branchIcon);
}
cell.appendChild(levelContainer);
} else {
cell.textContent = '—';
}
row.appendChild(cell);
}
// YouTube link cell
const videolinkCell = document.createElement('td');
videolinkCell.style.textAlign = 'center';
videolinkCell.style.verticalAlign = 'middle';
if (song.videoLink) {
const videolink = document.createElement('a');
videolink.href = song.videoLink;
videolink.target = '_blank';
videolink.rel = 'noopener noreferrer';
videolink.className = 'song-link';
videolink.textContent = '▶';
videolink.title = window.currentLanguage === 'ja' ? 'YouTubeで再生' : 'Play on YouTube';
videolinkCell.appendChild(videolink);
}
row.appendChild(videolinkCell);
tbody.appendChild(row);
}
table.appendChild(tbody);
header.addEventListener('click', () => {
const isCollapsed = table.style.display === 'none';
table.style.display = isCollapsed ? 'table' : 'none';
header.querySelector('.toggle-icon').textContent = isCollapsed ? '▼' : '▶';
});
container.appendChild(header);
container.appendChild(table);
fragment.appendChild(container);
});
genreTablesContainer.innerHTML = '';
genreTablesContainer.appendChild(fragment);
}
function createTh(en, ja, width, iconUrl, iconAlt) {
const th = document.createElement('th');
th.setAttribute('data-en', en);
th.setAttribute('data-ja', ja);
if (width) th.style.width = width;
if (iconUrl) {
const img = document.createElement('img');
img.src = iconUrl;
img.alt = iconAlt || en;
img.title = en;
img.style.width = '28px';
img.style.height = '28px';
img.style.objectFit = 'contain';
img.style.display = 'block';
img.style.margin = '0 auto';
th.appendChild(img);
} else {
th.textContent = window.currentLanguage === 'ja' ? ja : en;
}
return th;
}
function updateAll() {
updateLanguageAttrs();
createGenreTables();
// Remove and recreate color-coded listing
const colorCodedListing = document.querySelector('.color-coded-listing');
if (colorCodedListing) colorCodedListing.remove();
createColorCodedListing();
}
// Application initialization is now handled by initializeApp()
// after all required data is loaded
});
// (No stray code outside functions)
// Function to create color-coded song listing
function createColorCodedListing() {
// Check if the listing already exists to avoid duplicates
if (document.querySelector('.color-coded-listing')) {
return;
}
const container = document.createElement('div');
container.className = 'color-coded-listing';
// Get the genres from the songGenreMap
const genres = Object.values(songGenreMap);
// Create a section for each genre
genres.forEach(genre => {
const section = document.createElement('div');
section.className = `genre-section genre-${genre}`;
const title = document.createElement('h2');
title.textContent = genre.charAt(0).toUpperCase() + genre.slice(1);
section.appendChild(title);
const songList = document.createElement('div');
songList.className = 'song-list';
// Filter songs by genre and add to this section
const genreSongs = songListing.filter(song => songGenreMap[song.genre] === genre);
genreSongs.forEach(song => {
const songItem = document.createElement('div');
songItem.className = `song-item genre-${genre}`;
const title = document.createElement('span');
title.className = 'song-title';
title.textContent = currentLanguage === 'ja' && song.titleJA ? song.titleJA : song.title;
const subtitle = document.createElement('span');
subtitle.className = 'song-subtitle';
subtitle.textContent = currentLanguage === 'ja' && song.subtitleJA ? song.subtitleJA : song.subtitle;
songItem.appendChild(title);
songItem.appendChild(document.createElement('br'));
songItem.appendChild(subtitle);
// Add click handler to scroll to the song in the table
songItem.addEventListener('click', () => {
const songRow = document.querySelector(`tr[data-song-title="${song.title}"]`);
if (songRow) {
songRow.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
songRow.classList.add('highlight');
setTimeout(() => songRow.classList.remove('highlight'), 2000);
}
});
songList.appendChild(songItem);
});
section.appendChild(songList);
container.appendChild(section);
});
// Insert after the table
const tableSection = document.querySelector('main > section:first-child');
if (tableSection) {
tableSection.insertAdjacentElement('afterend', container);
} else {
document.querySelector('main').appendChild(container);
}
}
// Custom charts are sorted by genre only, maintaining the original order within each genre
// Make songGenreMap and songListing available globally
window.songGenreMap = songGenreMap;
window.songListing = songListing;
window.currentLanguage = 'en'; // Will be updated by the language selector
// Language change handling is now managed by handleLanguageChange()
// which is set up in the initializeApp() function