Feat: adding romaji convert feature on lyric
This commit is contained in:
parent
b3437dc99a
commit
df2b77eb7d
6 changed files with 1209 additions and 668 deletions
20
index.html
20
index.html
|
|
@ -30,7 +30,7 @@
|
||||||
<li data-action="download">Download</li>
|
<li data-action="download">Download</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="side-panel" class="side-panel">
|
<div id="side-panel" class="side-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<h3 id="side-panel-title">Panel</h3>
|
<h3 id="side-panel-title">Panel</h3>
|
||||||
|
|
@ -315,7 +315,7 @@
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="track-list" id="album-detail-tracklist"></div>
|
<div class="track-list" id="album-detail-tracklist"></div>
|
||||||
|
|
||||||
<section class="content-section" id="album-section-more-albums" style="display: none; margin-top: 3rem;">
|
<section class="content-section" id="album-section-more-albums" style="display: none; margin-top: 3rem;">
|
||||||
<h2 class="section-title" id="album-title-more-albums">More from Artist</h2>
|
<h2 class="section-title" id="album-title-more-albums">More from Artist</h2>
|
||||||
<div class="card-grid" id="album-detail-more-albums"></div>
|
<div class="card-grid" id="album-detail-more-albums"></div>
|
||||||
|
|
@ -609,6 +609,16 @@
|
||||||
<span class="slider"></span>
|
<span class="slider"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="info">
|
||||||
|
<span class="label">Romaji Lyrics</span>
|
||||||
|
<span class="description">Convert Japanese lyrics to Romaji (Latin characters)</span>
|
||||||
|
</div>
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox" id="romaji-lyrics-toggle">
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<span class="label">Filename Template</span>
|
<span class="label">Filename Template</span>
|
||||||
|
|
@ -669,7 +679,7 @@
|
||||||
<h2 class="section-title">About Monochrome</h2>
|
<h2 class="section-title">About Monochrome</h2>
|
||||||
<div class="about-content">
|
<div class="about-content">
|
||||||
<p class="about-description">
|
<p class="about-description">
|
||||||
Monochrome is a lightweight, privacy-focused music streaming client designed for high-fidelity audio playback.
|
Monochrome is a lightweight, privacy-focused music streaming client designed for high-fidelity audio playback.
|
||||||
Built with modern web technologies, it provides a clean, distraction-free listening experience.
|
Built with modern web technologies, it provides a clean, distraction-free listening experience.
|
||||||
<br>
|
<br>
|
||||||
<strong>NOTE:</strong> The instance you are currently on (monochrome.samidy.com) is a Community Fork adding additional features (library/playlists, accounts, mix + Big UI Improvments & More).
|
<strong>NOTE:</strong> The instance you are currently on (monochrome.samidy.com) is a Community Fork adding additional features (library/playlists, accounts, mix + Big UI Improvments & More).
|
||||||
|
|
@ -765,9 +775,9 @@
|
||||||
<div class="donate-content">
|
<div class="donate-content">
|
||||||
<p style="text-align: center;" class="donate-description">If Monochrome has been useful To you and you're able to, consider giving a little donation. <br> It helps Pay for the domain, and you get To support us :)</p>
|
<p style="text-align: center;" class="donate-description">If Monochrome has been useful To you and you're able to, consider giving a little donation. <br> It helps Pay for the domain, and you get To support us :)</p>
|
||||||
<button id="donate-btn" class="btn-secondary">Donate To Monochrome</button>
|
<button id="donate-btn" class="btn-secondary">Donate To Monochrome</button>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="now-playing-bar">
|
<footer class="now-playing-bar">
|
||||||
<div class="track-info">
|
<div class="track-info">
|
||||||
<img src="./assets/appicon.png" alt="Current Track Cover" class="cover">
|
<img src="./assets/appicon.png" alt="Current Track Cover" class="cover">
|
||||||
|
|
|
||||||
948
js/lyrics.js
948
js/lyrics.js
File diff suppressed because it is too large
Load diff
854
js/settings.js
854
js/settings.js
|
|
@ -1,442 +1,514 @@
|
||||||
//js/settings
|
//js/settings
|
||||||
import { themeManager, lastFMStorage, nowPlayingSettings, lyricsSettings, backgroundSettings, trackListSettings, cardSettings } from './storage.js';
|
import {
|
||||||
import { db } from './db.js';
|
themeManager,
|
||||||
import { authManager } from './firebase/auth.js';
|
lastFMStorage,
|
||||||
import { syncManager } from './firebase/sync.js';
|
nowPlayingSettings,
|
||||||
import { initializeFirebaseSettingsUI } from './firebase/config.js';
|
lyricsSettings,
|
||||||
|
backgroundSettings,
|
||||||
|
trackListSettings,
|
||||||
|
cardSettings,
|
||||||
|
} from "./storage.js";
|
||||||
|
import { db } from "./db.js";
|
||||||
|
import { authManager } from "./firebase/auth.js";
|
||||||
|
import { syncManager } from "./firebase/sync.js";
|
||||||
|
import { initializeFirebaseSettingsUI } from "./firebase/config.js";
|
||||||
|
|
||||||
export function initializeSettings(scrobbler, player, api, ui) {
|
export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
// Initialize Firebase UI & Settings
|
// Initialize Firebase UI & Settings
|
||||||
authManager.updateUI(authManager.user);
|
authManager.updateUI(authManager.user);
|
||||||
initializeFirebaseSettingsUI();
|
initializeFirebaseSettingsUI();
|
||||||
|
|
||||||
// Email Auth UI Logic
|
// Email Auth UI Logic
|
||||||
const toggleEmailBtn = document.getElementById('toggle-email-auth-btn');
|
const toggleEmailBtn = document.getElementById("toggle-email-auth-btn");
|
||||||
const cancelEmailBtn = document.getElementById('cancel-email-auth-btn');
|
const cancelEmailBtn = document.getElementById("cancel-email-auth-btn");
|
||||||
const authContainer = document.getElementById('email-auth-container');
|
const authContainer = document.getElementById("email-auth-container");
|
||||||
const authButtonsContainer = document.getElementById('auth-buttons-container');
|
const authButtonsContainer = document.getElementById(
|
||||||
const emailInput = document.getElementById('auth-email');
|
"auth-buttons-container",
|
||||||
const passwordInput = document.getElementById('auth-password');
|
);
|
||||||
const signInBtn = document.getElementById('email-signin-btn');
|
const emailInput = document.getElementById("auth-email");
|
||||||
const signUpBtn = document.getElementById('email-signup-btn');
|
const passwordInput = document.getElementById("auth-password");
|
||||||
|
const signInBtn = document.getElementById("email-signin-btn");
|
||||||
|
const signUpBtn = document.getElementById("email-signup-btn");
|
||||||
|
|
||||||
if (toggleEmailBtn && authContainer && authButtonsContainer) {
|
if (toggleEmailBtn && authContainer && authButtonsContainer) {
|
||||||
toggleEmailBtn.addEventListener('click', () => {
|
toggleEmailBtn.addEventListener("click", () => {
|
||||||
authContainer.style.display = 'flex';
|
authContainer.style.display = "flex";
|
||||||
authButtonsContainer.style.display = 'none';
|
authButtonsContainer.style.display = "none";
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancelEmailBtn && authContainer && authButtonsContainer) {
|
||||||
|
cancelEmailBtn.addEventListener("click", () => {
|
||||||
|
authContainer.style.display = "none";
|
||||||
|
authButtonsContainer.style.display = "flex";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signInBtn) {
|
||||||
|
signInBtn.addEventListener("click", async () => {
|
||||||
|
const email = emailInput.value;
|
||||||
|
const password = passwordInput.value;
|
||||||
|
if (!email || !password) {
|
||||||
|
alert("Please enter both email and password.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await authManager.signInWithEmail(email, password);
|
||||||
|
authContainer.style.display = "none";
|
||||||
|
authButtonsContainer.style.display = "flex";
|
||||||
|
emailInput.value = "";
|
||||||
|
passwordInput.value = "";
|
||||||
|
} catch (e) {
|
||||||
|
// Error handled in authManager
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signUpBtn) {
|
||||||
|
signUpBtn.addEventListener("click", async () => {
|
||||||
|
const email = emailInput.value;
|
||||||
|
const password = passwordInput.value;
|
||||||
|
if (!email || !password) {
|
||||||
|
alert("Please enter both email and password.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await authManager.signUpWithEmail(email, password);
|
||||||
|
authContainer.style.display = "none";
|
||||||
|
authButtonsContainer.style.display = "flex";
|
||||||
|
emailInput.value = "";
|
||||||
|
passwordInput.value = "";
|
||||||
|
} catch (e) {
|
||||||
|
// Error handled in authManager
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastfmConnectBtn = document.getElementById("lastfm-connect-btn");
|
||||||
|
const lastfmStatus = document.getElementById("lastfm-status");
|
||||||
|
const lastfmToggle = document.getElementById("lastfm-toggle");
|
||||||
|
const lastfmToggleSetting = document.getElementById("lastfm-toggle-setting");
|
||||||
|
const lastfmLoveToggle = document.getElementById("lastfm-love-toggle");
|
||||||
|
const lastfmLoveSetting = document.getElementById("lastfm-love-setting");
|
||||||
|
|
||||||
|
function updateLastFMUI() {
|
||||||
|
if (scrobbler.isAuthenticated()) {
|
||||||
|
lastfmStatus.textContent = `Connected as ${scrobbler.username}`;
|
||||||
|
lastfmConnectBtn.textContent = "Disconnect";
|
||||||
|
lastfmConnectBtn.classList.add("danger");
|
||||||
|
lastfmToggleSetting.style.display = "flex";
|
||||||
|
lastfmLoveSetting.style.display = "flex";
|
||||||
|
lastfmToggle.checked = lastFMStorage.isEnabled();
|
||||||
|
lastfmLoveToggle.checked = lastFMStorage.shouldLoveOnLike();
|
||||||
|
} else {
|
||||||
|
lastfmStatus.textContent =
|
||||||
|
"Connect your Last.fm account to scrobble tracks";
|
||||||
|
lastfmConnectBtn.textContent = "Connect Last.fm";
|
||||||
|
lastfmConnectBtn.classList.remove("danger");
|
||||||
|
lastfmToggleSetting.style.display = "none";
|
||||||
|
lastfmLoveSetting.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLastFMUI();
|
||||||
|
|
||||||
|
lastfmConnectBtn?.addEventListener("click", async () => {
|
||||||
|
if (scrobbler.isAuthenticated()) {
|
||||||
|
if (confirm("Disconnect from Last.fm?")) {
|
||||||
|
scrobbler.disconnect();
|
||||||
|
updateLastFMUI();
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancelEmailBtn && authContainer && authButtonsContainer) {
|
const authWindow = window.open("", "_blank");
|
||||||
cancelEmailBtn.addEventListener('click', () => {
|
lastfmConnectBtn.disabled = true;
|
||||||
authContainer.style.display = 'none';
|
lastfmConnectBtn.textContent = "Opening Last.fm...";
|
||||||
authButtonsContainer.style.display = 'flex';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signInBtn) {
|
try {
|
||||||
signInBtn.addEventListener('click', async () => {
|
const { token, url } = await scrobbler.getAuthUrl();
|
||||||
const email = emailInput.value;
|
|
||||||
const password = passwordInput.value;
|
|
||||||
if (!email || !password) {
|
|
||||||
alert('Please enter both email and password.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await authManager.signInWithEmail(email, password);
|
|
||||||
authContainer.style.display = 'none';
|
|
||||||
authButtonsContainer.style.display = 'flex';
|
|
||||||
emailInput.value = '';
|
|
||||||
passwordInput.value = '';
|
|
||||||
} catch (e) {
|
|
||||||
// Error handled in authManager
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signUpBtn) {
|
if (authWindow) {
|
||||||
signUpBtn.addEventListener('click', async () => {
|
authWindow.location.href = url;
|
||||||
const email = emailInput.value;
|
} else {
|
||||||
const password = passwordInput.value;
|
alert("Popup blocked! Please allow popups.");
|
||||||
if (!email || !password) {
|
lastfmConnectBtn.textContent = "Connect Last.fm";
|
||||||
alert('Please enter both email and password.');
|
lastfmConnectBtn.disabled = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
await authManager.signUpWithEmail(email, password);
|
|
||||||
authContainer.style.display = 'none';
|
|
||||||
authButtonsContainer.style.display = 'flex';
|
|
||||||
emailInput.value = '';
|
|
||||||
passwordInput.value = '';
|
|
||||||
} catch (e) {
|
|
||||||
// Error handled in authManager
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastfmConnectBtn = document.getElementById('lastfm-connect-btn');
|
lastfmConnectBtn.textContent = "Waiting for authorization...";
|
||||||
const lastfmStatus = document.getElementById('lastfm-status');
|
|
||||||
const lastfmToggle = document.getElementById('lastfm-toggle');
|
|
||||||
const lastfmToggleSetting = document.getElementById('lastfm-toggle-setting');
|
|
||||||
const lastfmLoveToggle = document.getElementById('lastfm-love-toggle');
|
|
||||||
const lastfmLoveSetting = document.getElementById('lastfm-love-setting');
|
|
||||||
|
|
||||||
function updateLastFMUI() {
|
let attempts = 0;
|
||||||
if (scrobbler.isAuthenticated()) {
|
const maxAttempts = 30;
|
||||||
lastfmStatus.textContent = `Connected as ${scrobbler.username}`;
|
|
||||||
lastfmConnectBtn.textContent = 'Disconnect';
|
const checkAuth = setInterval(async () => {
|
||||||
lastfmConnectBtn.classList.add('danger');
|
attempts++;
|
||||||
lastfmToggleSetting.style.display = 'flex';
|
|
||||||
lastfmLoveSetting.style.display = 'flex';
|
if (attempts > maxAttempts) {
|
||||||
lastfmToggle.checked = lastFMStorage.isEnabled();
|
clearInterval(checkAuth);
|
||||||
lastfmLoveToggle.checked = lastFMStorage.shouldLoveOnLike();
|
lastfmConnectBtn.textContent = "Connect Last.fm";
|
||||||
} else {
|
lastfmConnectBtn.disabled = false;
|
||||||
lastfmStatus.textContent = 'Connect your Last.fm account to scrobble tracks';
|
if (authWindow && !authWindow.closed) authWindow.close();
|
||||||
lastfmConnectBtn.textContent = 'Connect Last.fm';
|
alert("Authorization timed out. Please try again.");
|
||||||
lastfmConnectBtn.classList.remove('danger');
|
return;
|
||||||
lastfmToggleSetting.style.display = 'none';
|
|
||||||
lastfmLoveSetting.style.display = 'none';
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
updateLastFMUI();
|
|
||||||
|
|
||||||
lastfmConnectBtn?.addEventListener('click', async () => {
|
|
||||||
if (scrobbler.isAuthenticated()) {
|
|
||||||
if (confirm('Disconnect from Last.fm?')) {
|
|
||||||
scrobbler.disconnect();
|
|
||||||
updateLastFMUI();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const authWindow = window.open('', '_blank');
|
|
||||||
lastfmConnectBtn.disabled = true;
|
|
||||||
lastfmConnectBtn.textContent = 'Opening Last.fm...';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { token, url } = await scrobbler.getAuthUrl();
|
const result = await scrobbler.completeAuthentication(token);
|
||||||
|
|
||||||
if (authWindow) {
|
if (result.success) {
|
||||||
authWindow.location.href = url;
|
clearInterval(checkAuth);
|
||||||
} else {
|
|
||||||
alert('Popup blocked! Please allow popups.');
|
|
||||||
lastfmConnectBtn.textContent = 'Connect Last.fm';
|
|
||||||
lastfmConnectBtn.disabled = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastfmConnectBtn.textContent = 'Waiting for authorization...';
|
|
||||||
|
|
||||||
let attempts = 0;
|
|
||||||
const maxAttempts = 30;
|
|
||||||
|
|
||||||
const checkAuth = setInterval(async () => {
|
|
||||||
attempts++;
|
|
||||||
|
|
||||||
if (attempts > maxAttempts) {
|
|
||||||
clearInterval(checkAuth);
|
|
||||||
lastfmConnectBtn.textContent = 'Connect Last.fm';
|
|
||||||
lastfmConnectBtn.disabled = false;
|
|
||||||
if (authWindow && !authWindow.closed) authWindow.close();
|
|
||||||
alert('Authorization timed out. Please try again.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await scrobbler.completeAuthentication(token);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
clearInterval(checkAuth);
|
|
||||||
if (authWindow && !authWindow.closed) authWindow.close();
|
|
||||||
updateLastFMUI();
|
|
||||||
lastfmConnectBtn.disabled = false;
|
|
||||||
lastFMStorage.setEnabled(true);
|
|
||||||
lastfmToggle.checked = true;
|
|
||||||
alert(`Successfully connected to Last.fm as ${result.username}!`);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Still waiting
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Last.fm connection failed:', error);
|
|
||||||
alert('Failed to connect to Last.fm: ' + error.message);
|
|
||||||
lastfmConnectBtn.textContent = 'Connect Last.fm';
|
|
||||||
lastfmConnectBtn.disabled = false;
|
|
||||||
if (authWindow && !authWindow.closed) authWindow.close();
|
if (authWindow && !authWindow.closed) authWindow.close();
|
||||||
|
updateLastFMUI();
|
||||||
|
lastfmConnectBtn.disabled = false;
|
||||||
|
lastFMStorage.setEnabled(true);
|
||||||
|
lastfmToggle.checked = true;
|
||||||
|
alert(`Successfully connected to Last.fm as ${result.username}!`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Still waiting
|
||||||
}
|
}
|
||||||
|
}, 2000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Last.fm connection failed:", error);
|
||||||
|
alert("Failed to connect to Last.fm: " + error.message);
|
||||||
|
lastfmConnectBtn.textContent = "Connect Last.fm";
|
||||||
|
lastfmConnectBtn.disabled = false;
|
||||||
|
if (authWindow && !authWindow.closed) authWindow.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
lastfmToggle?.addEventListener("change", (e) => {
|
||||||
|
lastFMStorage.setEnabled(e.target.checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
lastfmLoveToggle?.addEventListener("change", (e) => {
|
||||||
|
lastFMStorage.setLoveOnLike(e.target.checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Theme picker
|
||||||
|
const themePicker = document.getElementById("theme-picker");
|
||||||
|
const currentTheme = themeManager.getTheme();
|
||||||
|
|
||||||
|
themePicker.querySelectorAll(".theme-option").forEach((option) => {
|
||||||
|
if (option.dataset.theme === currentTheme) {
|
||||||
|
option.classList.add("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
option.addEventListener("click", () => {
|
||||||
|
const theme = option.dataset.theme;
|
||||||
|
|
||||||
|
themePicker
|
||||||
|
.querySelectorAll(".theme-option")
|
||||||
|
.forEach((opt) => opt.classList.remove("active"));
|
||||||
|
option.classList.add("active");
|
||||||
|
|
||||||
|
if (theme === "custom") {
|
||||||
|
document.getElementById("custom-theme-editor").classList.add("show");
|
||||||
|
renderCustomThemeEditor();
|
||||||
|
} else {
|
||||||
|
document.getElementById("custom-theme-editor").classList.remove("show");
|
||||||
|
themeManager.setTheme(theme);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
lastfmToggle?.addEventListener('change', (e) => {
|
function renderCustomThemeEditor() {
|
||||||
lastFMStorage.setEnabled(e.target.checked);
|
const grid = document.getElementById("theme-color-grid");
|
||||||
});
|
const customTheme = themeManager.getCustomTheme() || {
|
||||||
|
background: "#000000",
|
||||||
|
foreground: "#fafafa",
|
||||||
|
primary: "#ffffff",
|
||||||
|
secondary: "#27272a",
|
||||||
|
muted: "#27272a",
|
||||||
|
border: "#27272a",
|
||||||
|
highlight: "#ffffff",
|
||||||
|
};
|
||||||
|
|
||||||
lastfmLoveToggle?.addEventListener('change', (e) => {
|
grid.innerHTML = Object.entries(customTheme)
|
||||||
lastFMStorage.setLoveOnLike(e.target.checked);
|
.map(
|
||||||
});
|
([key, value]) => `
|
||||||
|
|
||||||
// Theme picker
|
|
||||||
const themePicker = document.getElementById('theme-picker');
|
|
||||||
const currentTheme = themeManager.getTheme();
|
|
||||||
|
|
||||||
themePicker.querySelectorAll('.theme-option').forEach(option => {
|
|
||||||
if (option.dataset.theme === currentTheme) {
|
|
||||||
option.classList.add('active');
|
|
||||||
}
|
|
||||||
|
|
||||||
option.addEventListener('click', () => {
|
|
||||||
const theme = option.dataset.theme;
|
|
||||||
|
|
||||||
themePicker.querySelectorAll('.theme-option').forEach(opt => opt.classList.remove('active'));
|
|
||||||
option.classList.add('active');
|
|
||||||
|
|
||||||
if (theme === 'custom') {
|
|
||||||
document.getElementById('custom-theme-editor').classList.add('show');
|
|
||||||
renderCustomThemeEditor();
|
|
||||||
} else {
|
|
||||||
document.getElementById('custom-theme-editor').classList.remove('show');
|
|
||||||
themeManager.setTheme(theme);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function renderCustomThemeEditor() {
|
|
||||||
const grid = document.getElementById('theme-color-grid');
|
|
||||||
const customTheme = themeManager.getCustomTheme() || {
|
|
||||||
background: '#000000',
|
|
||||||
foreground: '#fafafa',
|
|
||||||
primary: '#ffffff',
|
|
||||||
secondary: '#27272a',
|
|
||||||
muted: '#27272a',
|
|
||||||
border: '#27272a',
|
|
||||||
highlight: '#ffffff'
|
|
||||||
};
|
|
||||||
|
|
||||||
grid.innerHTML = Object.entries(customTheme).map(([key, value]) => `
|
|
||||||
<div class="theme-color-input">
|
<div class="theme-color-input">
|
||||||
<label>${key}</label>
|
<label>${key}</label>
|
||||||
<input type="color" data-color="${key}" value="${value}">
|
<input type="color" data-color="${key}" value="${value}">
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`,
|
||||||
}
|
)
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('apply-custom-theme')?.addEventListener('click', () => {
|
document
|
||||||
const colors = {};
|
.getElementById("apply-custom-theme")
|
||||||
document.querySelectorAll('#theme-color-grid input[type="color"]').forEach(input => {
|
?.addEventListener("click", () => {
|
||||||
colors[input.dataset.color] = input.value;
|
const colors = {};
|
||||||
|
document
|
||||||
|
.querySelectorAll('#theme-color-grid input[type="color"]')
|
||||||
|
.forEach((input) => {
|
||||||
|
colors[input.dataset.color] = input.value;
|
||||||
});
|
});
|
||||||
themeManager.setCustomTheme(colors);
|
themeManager.setCustomTheme(colors);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('reset-custom-theme')?.addEventListener('click', () => {
|
document
|
||||||
renderCustomThemeEditor();
|
.getElementById("reset-custom-theme")
|
||||||
|
?.addEventListener("click", () => {
|
||||||
|
renderCustomThemeEditor();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Quality setting
|
// Quality setting
|
||||||
const qualitySetting = document.getElementById('quality-setting');
|
const qualitySetting = document.getElementById("quality-setting");
|
||||||
if (qualitySetting) {
|
if (qualitySetting) {
|
||||||
const savedQuality = localStorage.getItem('playback-quality') || 'LOSSLESS';
|
const savedQuality = localStorage.getItem("playback-quality") || "LOSSLESS";
|
||||||
qualitySetting.value = savedQuality;
|
qualitySetting.value = savedQuality;
|
||||||
player.setQuality(savedQuality);
|
player.setQuality(savedQuality);
|
||||||
|
|
||||||
qualitySetting.addEventListener('change', (e) => {
|
qualitySetting.addEventListener("change", (e) => {
|
||||||
const newQuality = e.target.value;
|
const newQuality = e.target.value;
|
||||||
player.setQuality(newQuality);
|
player.setQuality(newQuality);
|
||||||
localStorage.setItem('playback-quality', newQuality);
|
localStorage.setItem("playback-quality", newQuality);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now Playing Mode
|
|
||||||
const nowPlayingMode = document.getElementById('now-playing-mode');
|
|
||||||
if (nowPlayingMode) {
|
|
||||||
nowPlayingMode.value = nowPlayingSettings.getMode();
|
|
||||||
nowPlayingMode.addEventListener('change', (e) => {
|
|
||||||
nowPlayingSettings.setMode(e.target.value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track List Actions Mode
|
|
||||||
const trackListActionsMode = document.getElementById('track-list-actions-mode');
|
|
||||||
if (trackListActionsMode) {
|
|
||||||
trackListActionsMode.value = trackListSettings.getMode();
|
|
||||||
trackListActionsMode.addEventListener('change', (e) => {
|
|
||||||
trackListSettings.setMode(e.target.value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compact Artist Toggle
|
|
||||||
const compactArtistToggle = document.getElementById('compact-artist-toggle');
|
|
||||||
if (compactArtistToggle) {
|
|
||||||
compactArtistToggle.checked = cardSettings.isCompactArtist();
|
|
||||||
compactArtistToggle.addEventListener('change', (e) => {
|
|
||||||
cardSettings.setCompactArtist(e.target.checked);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compact Album Toggle
|
|
||||||
const compactAlbumToggle = document.getElementById('compact-album-toggle');
|
|
||||||
if (compactAlbumToggle) {
|
|
||||||
compactAlbumToggle.checked = cardSettings.isCompactAlbum();
|
|
||||||
compactAlbumToggle.addEventListener('change', (e) => {
|
|
||||||
cardSettings.setCompactAlbum(e.target.checked);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download Lyrics Toggle
|
|
||||||
const downloadLyricsToggle = document.getElementById('download-lyrics-toggle');
|
|
||||||
if (downloadLyricsToggle) {
|
|
||||||
downloadLyricsToggle.checked = lyricsSettings.shouldDownloadLyrics();
|
|
||||||
downloadLyricsToggle.addEventListener('change', (e) => {
|
|
||||||
lyricsSettings.setDownloadLyrics(e.target.checked);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Album Background Toggle
|
|
||||||
const albumBackgroundToggle = document.getElementById('album-background-toggle');
|
|
||||||
if (albumBackgroundToggle) {
|
|
||||||
albumBackgroundToggle.checked = backgroundSettings.isEnabled();
|
|
||||||
albumBackgroundToggle.addEventListener('change', (e) => {
|
|
||||||
backgroundSettings.setEnabled(e.target.checked);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filename template setting
|
|
||||||
const filenameTemplate = document.getElementById('filename-template');
|
|
||||||
if (filenameTemplate) {
|
|
||||||
filenameTemplate.value = localStorage.getItem('filename-template') || '{trackNumber} - {artist} - {title}';
|
|
||||||
filenameTemplate.addEventListener('change', (e) => {
|
|
||||||
localStorage.setItem('filename-template', e.target.value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZIP folder template
|
|
||||||
const zipFolderTemplate = document.getElementById('zip-folder-template');
|
|
||||||
if (zipFolderTemplate) {
|
|
||||||
zipFolderTemplate.value = localStorage.getItem('zip-folder-template') || '{albumTitle} - {albumArtist}';
|
|
||||||
zipFolderTemplate.addEventListener('change', (e) => {
|
|
||||||
localStorage.setItem('zip-folder-template', e.target.value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// API settings
|
|
||||||
document.getElementById('refresh-speed-test-btn')?.addEventListener('click', async () => {
|
|
||||||
const btn = document.getElementById('refresh-speed-test-btn');
|
|
||||||
const originalText = btn.textContent;
|
|
||||||
btn.textContent = 'Testing...';
|
|
||||||
btn.disabled = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await api.settings.refreshSpeedTests();
|
|
||||||
ui.renderApiSettings();
|
|
||||||
btn.textContent = 'Done!';
|
|
||||||
setTimeout(() => {
|
|
||||||
btn.textContent = originalText;
|
|
||||||
btn.disabled = false;
|
|
||||||
}, 1500);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to refresh speed tests:', error);
|
|
||||||
btn.textContent = 'Error';
|
|
||||||
setTimeout(() => {
|
|
||||||
btn.textContent = originalText;
|
|
||||||
btn.disabled = false;
|
|
||||||
}, 1500);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('api-instance-list')?.addEventListener('click', async (e) => {
|
// Now Playing Mode
|
||||||
const button = e.target.closest('button');
|
const nowPlayingMode = document.getElementById("now-playing-mode");
|
||||||
if (!button) return;
|
if (nowPlayingMode) {
|
||||||
|
nowPlayingMode.value = nowPlayingSettings.getMode();
|
||||||
|
nowPlayingMode.addEventListener("change", (e) => {
|
||||||
|
nowPlayingSettings.setMode(e.target.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const li = button.closest('li');
|
// Track List Actions Mode
|
||||||
const index = parseInt(li.dataset.index, 10);
|
const trackListActionsMode = document.getElementById(
|
||||||
const type = li.dataset.type || 'api'; // Default to api if not present
|
"track-list-actions-mode",
|
||||||
|
);
|
||||||
const instances = await api.settings.getInstances(type);
|
if (trackListActionsMode) {
|
||||||
|
trackListActionsMode.value = trackListSettings.getMode();
|
||||||
|
trackListActionsMode.addEventListener("change", (e) => {
|
||||||
|
trackListSettings.setMode(e.target.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (button.classList.contains('move-up') && index > 0) {
|
// Compact Artist Toggle
|
||||||
[instances[index], instances[index - 1]] = [instances[index - 1], instances[index]];
|
const compactArtistToggle = document.getElementById("compact-artist-toggle");
|
||||||
} else if (button.classList.contains('move-down') && index < instances.length - 1) {
|
if (compactArtistToggle) {
|
||||||
[instances[index], instances[index + 1]] = [instances[index + 1], instances[index]];
|
compactArtistToggle.checked = cardSettings.isCompactArtist();
|
||||||
}
|
compactArtistToggle.addEventListener("change", (e) => {
|
||||||
|
cardSettings.setCompactArtist(e.target.checked);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
api.settings.saveInstances(instances, type);
|
// Compact Album Toggle
|
||||||
|
const compactAlbumToggle = document.getElementById("compact-album-toggle");
|
||||||
|
if (compactAlbumToggle) {
|
||||||
|
compactAlbumToggle.checked = cardSettings.isCompactAlbum();
|
||||||
|
compactAlbumToggle.addEventListener("change", (e) => {
|
||||||
|
cardSettings.setCompactAlbum(e.target.checked);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download Lyrics Toggle
|
||||||
|
const downloadLyricsToggle = document.getElementById(
|
||||||
|
"download-lyrics-toggle",
|
||||||
|
);
|
||||||
|
if (downloadLyricsToggle) {
|
||||||
|
downloadLyricsToggle.checked = lyricsSettings.shouldDownloadLyrics();
|
||||||
|
downloadLyricsToggle.addEventListener("change", (e) => {
|
||||||
|
lyricsSettings.setDownloadLyrics(e.target.checked);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Romaji Lyrics Toggle
|
||||||
|
const romajiLyricsToggle = document.getElementById("romaji-lyrics-toggle");
|
||||||
|
if (romajiLyricsToggle) {
|
||||||
|
romajiLyricsToggle.checked =
|
||||||
|
localStorage.getItem("lyricsRomajiMode") === "true";
|
||||||
|
romajiLyricsToggle.addEventListener("change", (e) => {
|
||||||
|
localStorage.setItem(
|
||||||
|
"lyricsRomajiMode",
|
||||||
|
e.target.checked ? "true" : "false",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Album Background Toggle
|
||||||
|
const albumBackgroundToggle = document.getElementById(
|
||||||
|
"album-background-toggle",
|
||||||
|
);
|
||||||
|
if (albumBackgroundToggle) {
|
||||||
|
albumBackgroundToggle.checked = backgroundSettings.isEnabled();
|
||||||
|
albumBackgroundToggle.addEventListener("change", (e) => {
|
||||||
|
backgroundSettings.setEnabled(e.target.checked);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filename template setting
|
||||||
|
const filenameTemplate = document.getElementById("filename-template");
|
||||||
|
if (filenameTemplate) {
|
||||||
|
filenameTemplate.value =
|
||||||
|
localStorage.getItem("filename-template") ||
|
||||||
|
"{trackNumber} - {artist} - {title}";
|
||||||
|
filenameTemplate.addEventListener("change", (e) => {
|
||||||
|
localStorage.setItem("filename-template", e.target.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZIP folder template
|
||||||
|
const zipFolderTemplate = document.getElementById("zip-folder-template");
|
||||||
|
if (zipFolderTemplate) {
|
||||||
|
zipFolderTemplate.value =
|
||||||
|
localStorage.getItem("zip-folder-template") ||
|
||||||
|
"{albumTitle} - {albumArtist}";
|
||||||
|
zipFolderTemplate.addEventListener("change", (e) => {
|
||||||
|
localStorage.setItem("zip-folder-template", e.target.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// API settings
|
||||||
|
document
|
||||||
|
.getElementById("refresh-speed-test-btn")
|
||||||
|
?.addEventListener("click", async () => {
|
||||||
|
const btn = document.getElementById("refresh-speed-test-btn");
|
||||||
|
const originalText = btn.textContent;
|
||||||
|
btn.textContent = "Testing...";
|
||||||
|
btn.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.settings.refreshSpeedTests();
|
||||||
ui.renderApiSettings();
|
ui.renderApiSettings();
|
||||||
|
btn.textContent = "Done!";
|
||||||
|
setTimeout(() => {
|
||||||
|
btn.textContent = originalText;
|
||||||
|
btn.disabled = false;
|
||||||
|
}, 1500);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to refresh speed tests:", error);
|
||||||
|
btn.textContent = "Error";
|
||||||
|
setTimeout(() => {
|
||||||
|
btn.textContent = originalText;
|
||||||
|
btn.disabled = false;
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('clear-cache-btn')?.addEventListener('click', async () => {
|
document
|
||||||
const btn = document.getElementById('clear-cache-btn');
|
.getElementById("api-instance-list")
|
||||||
const originalText = btn.textContent;
|
?.addEventListener("click", async (e) => {
|
||||||
btn.textContent = 'Clearing...';
|
const button = e.target.closest("button");
|
||||||
btn.disabled = true;
|
if (!button) return;
|
||||||
|
|
||||||
|
const li = button.closest("li");
|
||||||
|
const index = parseInt(li.dataset.index, 10);
|
||||||
|
const type = li.dataset.type || "api"; // Default to api if not present
|
||||||
|
|
||||||
|
const instances = await api.settings.getInstances(type);
|
||||||
|
|
||||||
|
if (button.classList.contains("move-up") && index > 0) {
|
||||||
|
[instances[index], instances[index - 1]] = [
|
||||||
|
instances[index - 1],
|
||||||
|
instances[index],
|
||||||
|
];
|
||||||
|
} else if (
|
||||||
|
button.classList.contains("move-down") &&
|
||||||
|
index < instances.length - 1
|
||||||
|
) {
|
||||||
|
[instances[index], instances[index + 1]] = [
|
||||||
|
instances[index + 1],
|
||||||
|
instances[index],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
api.settings.saveInstances(instances, type);
|
||||||
|
ui.renderApiSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("clear-cache-btn")
|
||||||
|
?.addEventListener("click", async () => {
|
||||||
|
const btn = document.getElementById("clear-cache-btn");
|
||||||
|
const originalText = btn.textContent;
|
||||||
|
btn.textContent = "Clearing...";
|
||||||
|
btn.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.clearCache();
|
||||||
|
btn.textContent = "Cleared!";
|
||||||
|
setTimeout(() => {
|
||||||
|
btn.textContent = originalText;
|
||||||
|
btn.disabled = false;
|
||||||
|
if (window.location.hash.includes("settings")) {
|
||||||
|
ui.renderApiSettings();
|
||||||
|
}
|
||||||
|
}, 1500);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to clear cache:", error);
|
||||||
|
btn.textContent = "Error";
|
||||||
|
setTimeout(() => {
|
||||||
|
btn.textContent = originalText;
|
||||||
|
btn.disabled = false;
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("firebase-clear-cloud-btn")
|
||||||
|
?.addEventListener("click", async () => {
|
||||||
|
if (
|
||||||
|
confirm(
|
||||||
|
"Are you sure you want to delete ALL your data from the cloud? This cannot be undone.",
|
||||||
|
)
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
await api.clearCache();
|
await syncManager.clearCloudData();
|
||||||
btn.textContent = 'Cleared!';
|
alert("Cloud data cleared successfully.");
|
||||||
setTimeout(() => {
|
authManager.signOut();
|
||||||
btn.textContent = originalText;
|
|
||||||
btn.disabled = false;
|
|
||||||
if (window.location.hash.includes('settings')) {
|
|
||||||
ui.renderApiSettings();
|
|
||||||
}
|
|
||||||
}, 1500);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to clear cache:', error);
|
console.error("Failed to clear cloud data:", error);
|
||||||
btn.textContent = 'Error';
|
alert("Failed to clear cloud data: " + error.message);
|
||||||
setTimeout(() => {
|
|
||||||
btn.textContent = originalText;
|
|
||||||
btn.disabled = false;
|
|
||||||
}, 1500);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('firebase-clear-cloud-btn')?.addEventListener('click', async () => {
|
// Backup & Restore
|
||||||
if (confirm('Are you sure you want to delete ALL your data from the cloud? This cannot be undone.')) {
|
document
|
||||||
try {
|
.getElementById("export-library-btn")
|
||||||
await syncManager.clearCloudData();
|
?.addEventListener("click", async () => {
|
||||||
alert('Cloud data cleared successfully.');
|
const data = await db.exportData();
|
||||||
authManager.signOut();
|
const blob = new Blob([JSON.stringify(data, null, 2)], {
|
||||||
} catch (error) {
|
type: "application/json",
|
||||||
console.error('Failed to clear cloud data:', error);
|
});
|
||||||
alert('Failed to clear cloud data: ' + error.message);
|
const url = URL.createObjectURL(blob);
|
||||||
}
|
const a = document.createElement("a");
|
||||||
}
|
a.href = url;
|
||||||
|
a.download = `monochrome-library-${new Date().toISOString().split("T")[0]}.json`;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Backup & Restore
|
const importInput = document.getElementById("import-library-input");
|
||||||
document.getElementById('export-library-btn')?.addEventListener('click', async () => {
|
document
|
||||||
const data = await db.exportData();
|
.getElementById("import-library-btn")
|
||||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
?.addEventListener("click", () => {
|
||||||
const url = URL.createObjectURL(blob);
|
importInput.click();
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = `monochrome-library-${new Date().toISOString().split('T')[0]}.json`;
|
|
||||||
a.click();
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const importInput = document.getElementById('import-library-input');
|
importInput?.addEventListener("change", async (e) => {
|
||||||
document.getElementById('import-library-btn')?.addEventListener('click', () => {
|
const file = e.target.files[0];
|
||||||
importInput.click();
|
if (!file) return;
|
||||||
});
|
|
||||||
|
|
||||||
importInput?.addEventListener('change', async (e) => {
|
const reader = new FileReader();
|
||||||
const file = e.target.files[0];
|
reader.onload = async (event) => {
|
||||||
if (!file) return;
|
try {
|
||||||
|
const data = JSON.parse(event.target.result);
|
||||||
const reader = new FileReader();
|
await db.importData(data);
|
||||||
reader.onload = async (event) => {
|
alert("Library imported successfully!");
|
||||||
try {
|
window.location.reload(); // Simple way to refresh all state
|
||||||
const data = JSON.parse(event.target.result);
|
} catch (err) {
|
||||||
await db.importData(data);
|
console.error("Import failed:", err);
|
||||||
alert('Library imported successfully!');
|
alert("Failed to import library. Please check the file format.");
|
||||||
window.location.reload(); // Simple way to refresh all state
|
}
|
||||||
} catch (err) {
|
};
|
||||||
console.error('Import failed:', err);
|
reader.readAsText(file);
|
||||||
alert('Failed to import library. Please check the file format.');
|
});
|
||||||
}
|
|
||||||
};
|
|
||||||
reader.readAsText(file);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
package-lock.json
generated
12
package-lock.json
generated
|
|
@ -10,7 +10,8 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite": "^7.3.0",
|
"vite": "^7.3.0",
|
||||||
"vite-plugin-pwa": "^1.2.0"
|
"vite-plugin-pwa": "^1.2.0",
|
||||||
|
"wanakana": "^5.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@apideck/better-ajv-errors": {
|
"node_modules/@apideck/better-ajv-errors": {
|
||||||
|
|
@ -5749,6 +5750,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wanakana": {
|
||||||
|
"version": "5.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/wanakana/-/wanakana-5.3.1.tgz",
|
||||||
|
"integrity": "sha512-OSDqupzTlzl2LGyqTdhcXcl6ezMiFhcUwLBP8YKaBIbMYW1wAwDvupw2T9G9oVaKT9RmaSpyTXjxddFPUcFFIw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/webidl-conversions": {
|
"node_modules/webidl-conversions": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
"homepage": "https://github.com/SamidyFR/monochrome#readme",
|
"homepage": "https://github.com/SamidyFR/monochrome#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite": "^7.3.0",
|
"vite": "^7.3.0",
|
||||||
"vite-plugin-pwa": "^1.2.0"
|
"vite-plugin-pwa": "^1.2.0",
|
||||||
|
"wanakana": "^5.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,25 @@
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from "vite";
|
||||||
import { VitePWA } from 'vite-plugin-pwa';
|
import { VitePWA } from "vite-plugin-pwa";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
base: './',
|
base: "./",
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist',
|
outDir: "dist",
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
VitePWA({
|
VitePWA({
|
||||||
registerType: 'prompt',
|
registerType: "prompt",
|
||||||
workbox: {
|
workbox: {
|
||||||
globPatterns: ['**/*.{js,css,html,ico,png,svg,json}'],
|
globPatterns: ["**/*.{js,css,html,ico,png,svg,json}"],
|
||||||
cleanupOutdatedCaches: true,
|
cleanupOutdatedCaches: true,
|
||||||
// Define runtime caching strategies
|
// Define runtime caching strategies
|
||||||
runtimeCaching: [
|
runtimeCaching: [
|
||||||
{
|
{
|
||||||
urlPattern: ({ request }) => request.destination === 'image',
|
urlPattern: ({ request }) => request.destination === "image",
|
||||||
handler: 'CacheFirst',
|
handler: "CacheFirst",
|
||||||
options: {
|
options: {
|
||||||
cacheName: 'images',
|
cacheName: "images",
|
||||||
expiration: {
|
expiration: {
|
||||||
maxEntries: 100,
|
maxEntries: 100,
|
||||||
maxAgeSeconds: 60 * 24 * 60 * 60, // 60 Days
|
maxAgeSeconds: 60 * 24 * 60 * 60, // 60 Days
|
||||||
|
|
@ -27,21 +27,23 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
urlPattern: ({ request }) => request.destination === 'audio' || request.destination === 'video',
|
urlPattern: ({ request }) =>
|
||||||
handler: 'CacheFirst',
|
request.destination === "audio" ||
|
||||||
|
request.destination === "video",
|
||||||
|
handler: "CacheFirst",
|
||||||
options: {
|
options: {
|
||||||
cacheName: 'media',
|
cacheName: "media",
|
||||||
expiration: {
|
expiration: {
|
||||||
maxEntries: 50,
|
maxEntries: 50,
|
||||||
maxAgeSeconds: 60 * 24 * 60 * 60, // 60 Days
|
maxAgeSeconds: 60 * 24 * 60 * 60, // 60 Days
|
||||||
},
|
},
|
||||||
rangeRequests: true, // Support scrubbing
|
rangeRequests: true, // Support scrubbing
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
includeAssets: ['instances.json', 'discord.html'],
|
includeAssets: ["instances.json", "discord.html"],
|
||||||
manifest: false // Use existing public/manifest.json
|
manifest: false, // Use existing public/manifest.json
|
||||||
})
|
}),
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue