add more fonts functionality
This commit is contained in:
parent
599b11cfc4
commit
003ddc0ab3
6 changed files with 657 additions and 67 deletions
117
index.html
117
index.html
|
|
@ -1096,6 +1096,20 @@
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<section class="content-section" id="home-editors-picks-section-empty" style="margin-top: 0">
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
"
|
||||
>
|
||||
<h2 class="section-title" style="margin-bottom: 0">Editor's Picks</h2>
|
||||
</div>
|
||||
<div class="card-grid" id="home-editors-picks-empty"></div>
|
||||
</section>
|
||||
|
||||
<div id="home-content" style="display: none">
|
||||
<section class="content-section">
|
||||
<div
|
||||
|
|
@ -1165,19 +1179,6 @@
|
|||
</div>
|
||||
<div class="card-grid" id="home-recommended-albums"></div>
|
||||
</section>
|
||||
<section class="content-section" id="home-editors-picks-section">
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
"
|
||||
>
|
||||
<h2 class="section-title" style="margin-bottom: 0">Editor's Picks</h2>
|
||||
</div>
|
||||
<div class="card-grid" id="home-editors-picks"></div>
|
||||
</section>
|
||||
<section class="content-section">
|
||||
<div
|
||||
style="
|
||||
|
|
@ -1247,6 +1248,19 @@
|
|||
</div>
|
||||
<div class="card-grid" id="home-recent-mixed"></div>
|
||||
</section>
|
||||
<section class="content-section" id="home-editors-picks-section">
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
"
|
||||
>
|
||||
<h2 class="section-title" style="margin-bottom: 0">Editor's Picks</h2>
|
||||
</div>
|
||||
<div class="card-grid" id="home-editors-picks"></div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -2101,25 +2115,70 @@
|
|||
<button class="btn-secondary" id="reset-custom-theme">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item font-settings-container">
|
||||
<div class="info">
|
||||
<span class="label">Font</span>
|
||||
<span class="description">Choose the application font</span>
|
||||
</div>
|
||||
<select id="font-select">
|
||||
<option value="'Inter', sans-serif">Inter (Default)</option>
|
||||
<option value="'Roboto', sans-serif">Roboto</option>
|
||||
<option value="'Open Sans', sans-serif">Open Sans</option>
|
||||
<option value="'Lato', sans-serif">Lato</option>
|
||||
<option value="'Montserrat', sans-serif">Montserrat</option>
|
||||
<option value="'Poppins', sans-serif">Poppins</option>
|
||||
<option
|
||||
value="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif"
|
||||
<span class="description"
|
||||
>Choose from presets, Google Fonts, URLs, or upload your own</span
|
||||
>
|
||||
System UI
|
||||
</option>
|
||||
<option value="monospace">Monospace</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="font-input-group">
|
||||
<select id="font-type-select" class="font-type-select">
|
||||
<option value="preset">Preset</option>
|
||||
<option value="google">Google Fonts</option>
|
||||
<option value="url">URL</option>
|
||||
<option value="upload">Upload</option>
|
||||
</select>
|
||||
|
||||
<div id="font-preset-section" class="font-section">
|
||||
<select id="font-preset-select">
|
||||
<option value="Inter">Inter (Default)</option>
|
||||
<option value="Roboto">Roboto</option>
|
||||
<option value="Open Sans">Open Sans</option>
|
||||
<option value="Lato">Lato</option>
|
||||
<option value="Montserrat">Montserrat</option>
|
||||
<option value="Poppins">Poppins</option>
|
||||
<option value="System UI">System UI</option>
|
||||
<option value="monospace">Monospace</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="font-google-section" class="font-section" style="display: none">
|
||||
<input
|
||||
type="text"
|
||||
id="font-google-input"
|
||||
placeholder="Enter Google Fonts URL or font name (e.g., IBM Plex Mono)"
|
||||
class="font-input"
|
||||
/>
|
||||
<button id="font-google-apply" class="btn-secondary">Apply</button>
|
||||
</div>
|
||||
|
||||
<div id="font-url-section" class="font-section" style="display: none">
|
||||
<input
|
||||
type="text"
|
||||
id="font-url-input"
|
||||
placeholder="Enter font file URL (.woff, .woff2, .ttf, .otf)"
|
||||
class="font-input"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
id="font-url-name"
|
||||
placeholder="Font name (optional)"
|
||||
class="font-input font-name-input"
|
||||
/>
|
||||
<button id="font-url-apply" class="btn-secondary">Apply</button>
|
||||
</div>
|
||||
|
||||
<div id="font-upload-section" class="font-section" style="display: none">
|
||||
<input
|
||||
type="file"
|
||||
id="font-upload-input"
|
||||
accept=".woff,.woff2,.ttf,.otf"
|
||||
class="font-file-input"
|
||||
/>
|
||||
<div id="uploaded-fonts-list" class="uploaded-fonts-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="info">
|
||||
|
|
|
|||
149
js/settings.js
149
js/settings.js
|
|
@ -22,6 +22,7 @@ import {
|
|||
libreFmSettings,
|
||||
homePageSettings,
|
||||
sidebarSectionSettings,
|
||||
fontSettings,
|
||||
} from './storage.js';
|
||||
import { audioContextManager, EQ_PRESETS } from './audio-context.js';
|
||||
import { db } from './db.js';
|
||||
|
|
@ -1280,10 +1281,158 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
|||
});
|
||||
}
|
||||
|
||||
// Font Settings
|
||||
initializeFontSettings();
|
||||
|
||||
// Settings Search functionality
|
||||
setupSettingsSearch();
|
||||
}
|
||||
|
||||
function initializeFontSettings() {
|
||||
const fontTypeSelect = document.getElementById('font-type-select');
|
||||
const fontPresetSection = document.getElementById('font-preset-section');
|
||||
const fontGoogleSection = document.getElementById('font-google-section');
|
||||
const fontUrlSection = document.getElementById('font-url-section');
|
||||
const fontUploadSection = document.getElementById('font-upload-section');
|
||||
const fontPresetSelect = document.getElementById('font-preset-select');
|
||||
const fontGoogleInput = document.getElementById('font-google-input');
|
||||
const fontGoogleApply = document.getElementById('font-google-apply');
|
||||
const fontUrlInput = document.getElementById('font-url-input');
|
||||
const fontUrlName = document.getElementById('font-url-name');
|
||||
const fontUrlApply = document.getElementById('font-url-apply');
|
||||
const fontUploadInput = document.getElementById('font-upload-input');
|
||||
const uploadedFontsList = document.getElementById('uploaded-fonts-list');
|
||||
|
||||
if (!fontTypeSelect) return;
|
||||
|
||||
// Load current font config
|
||||
const config = fontSettings.getConfig();
|
||||
|
||||
// Show correct section based on type
|
||||
function showFontSection(type) {
|
||||
fontPresetSection.style.display = type === 'preset' ? 'block' : 'none';
|
||||
fontGoogleSection.style.display = type === 'google' ? 'flex' : 'none';
|
||||
fontUrlSection.style.display = type === 'url' ? 'flex' : 'none';
|
||||
fontUploadSection.style.display = type === 'upload' ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// Initialize UI state
|
||||
fontTypeSelect.value = config.type;
|
||||
showFontSection(config.type);
|
||||
|
||||
if (config.type === 'preset') {
|
||||
fontPresetSelect.value = config.family;
|
||||
} else if (config.type === 'google') {
|
||||
fontGoogleInput.value = config.family || '';
|
||||
} else if (config.type === 'url') {
|
||||
fontUrlInput.value = config.url || '';
|
||||
fontUrlName.value = config.family || '';
|
||||
}
|
||||
|
||||
// Type selector change
|
||||
fontTypeSelect.addEventListener('change', (e) => {
|
||||
showFontSection(e.target.value);
|
||||
});
|
||||
|
||||
// Preset font change
|
||||
fontPresetSelect.addEventListener('change', (e) => {
|
||||
const value = e.target.value;
|
||||
if (value === 'System UI') {
|
||||
fontSettings.loadPresetFont(
|
||||
"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue'",
|
||||
'sans-serif'
|
||||
);
|
||||
} else if (value === 'monospace') {
|
||||
fontSettings.loadPresetFont('monospace', 'monospace');
|
||||
} else {
|
||||
fontSettings.loadPresetFont(value, 'sans-serif');
|
||||
}
|
||||
});
|
||||
|
||||
// Google Fonts apply
|
||||
fontGoogleApply.addEventListener('click', () => {
|
||||
const input = fontGoogleInput.value.trim();
|
||||
if (!input) return;
|
||||
|
||||
let fontName = input;
|
||||
|
||||
// Check if it's a Google Fonts URL
|
||||
if (input.includes('fonts.google.com')) {
|
||||
const parsed = fontSettings.parseGoogleFontsUrl(input);
|
||||
if (parsed) {
|
||||
fontName = parsed;
|
||||
}
|
||||
}
|
||||
|
||||
fontSettings.loadGoogleFont(fontName);
|
||||
});
|
||||
|
||||
// URL font apply
|
||||
fontUrlApply.addEventListener('click', () => {
|
||||
const url = fontUrlInput.value.trim();
|
||||
const name = fontUrlName.value.trim();
|
||||
if (!url) return;
|
||||
|
||||
fontSettings.loadFontFromUrl(url, name || 'CustomFont');
|
||||
});
|
||||
|
||||
// File upload
|
||||
fontUploadInput.addEventListener('change', async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
try {
|
||||
const font = await fontSettings.saveUploadedFont(file);
|
||||
await fontSettings.loadUploadedFont(font.id);
|
||||
renderUploadedFontsList();
|
||||
fontUploadInput.value = '';
|
||||
} catch (err) {
|
||||
console.error('Failed to upload font:', err);
|
||||
alert('Failed to upload font');
|
||||
}
|
||||
});
|
||||
|
||||
// Render uploaded fonts list
|
||||
function renderUploadedFontsList() {
|
||||
const fonts = fontSettings.getUploadedFontList();
|
||||
uploadedFontsList.innerHTML = '';
|
||||
|
||||
fonts.forEach((font) => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'uploaded-font-item';
|
||||
item.innerHTML = `
|
||||
<span class="font-name">${font.name}</span>
|
||||
<div class="font-actions">
|
||||
<button class="btn-icon" data-id="${font.id}" data-action="use">Use</button>
|
||||
<button class="btn-icon btn-delete" data-id="${font.id}" data-action="delete">Delete</button>
|
||||
</div>
|
||||
`;
|
||||
uploadedFontsList.appendChild(item);
|
||||
});
|
||||
|
||||
// Add event listeners for buttons
|
||||
uploadedFontsList.querySelectorAll('.btn-icon').forEach((btn) => {
|
||||
btn.addEventListener('click', async (e) => {
|
||||
const fontId = e.target.dataset.id;
|
||||
const action = e.target.dataset.action;
|
||||
|
||||
if (action === 'use') {
|
||||
await fontSettings.loadUploadedFont(fontId);
|
||||
fontTypeSelect.value = 'upload';
|
||||
showFontSection('upload');
|
||||
} else if (action === 'delete') {
|
||||
if (confirm('Delete this font?')) {
|
||||
fontSettings.deleteUploadedFont(fontId);
|
||||
renderUploadedFontsList();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
renderUploadedFontsList();
|
||||
}
|
||||
|
||||
function setupSettingsSearch() {
|
||||
const searchInput = document.getElementById('settings-search-input');
|
||||
if (!searchInput) return;
|
||||
|
|
|
|||
252
js/storage.js
252
js/storage.js
|
|
@ -1159,3 +1159,255 @@ if (typeof window !== 'undefined' && window.matchMedia) {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const fontSettings = {
|
||||
STORAGE_KEY: 'monochrome-font-config-v2',
|
||||
CUSTOM_FONTS_KEY: 'monochrome-custom-fonts',
|
||||
FONT_LINK_ID: 'monochrome-dynamic-font',
|
||||
FONT_FACE_ID: 'monochrome-dynamic-fontface',
|
||||
|
||||
getDefaultConfig() {
|
||||
return {
|
||||
type: 'preset',
|
||||
family: 'Inter',
|
||||
fallback: 'sans-serif',
|
||||
weights: [400, 500, 600, 700, 800],
|
||||
};
|
||||
},
|
||||
|
||||
getConfig() {
|
||||
try {
|
||||
const stored = localStorage.getItem(this.STORAGE_KEY);
|
||||
if (stored) {
|
||||
return JSON.parse(stored);
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return this.getDefaultConfig();
|
||||
},
|
||||
|
||||
setConfig(config) {
|
||||
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(config));
|
||||
},
|
||||
|
||||
parseGoogleFontsUrl(url) {
|
||||
try {
|
||||
if (url.includes('fonts.google.com/specimen/')) {
|
||||
const match = url.match(/specimen\/([^/?]+)/);
|
||||
if (match) {
|
||||
return decodeURIComponent(match[1]).replace(/\+/g, ' ');
|
||||
}
|
||||
}
|
||||
if (url.includes('fonts.googleapis.com/css')) {
|
||||
const match = url.match(/family=([^&:]+)/);
|
||||
if (match) {
|
||||
return decodeURIComponent(match[1]).replace(/\+/g, ' ').split(':')[0];
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
async loadGoogleFont(familyName) {
|
||||
const encodedFamily = familyName.replace(/\s+/g, '+');
|
||||
const url = `https://fonts.googleapis.com/css2?family=${encodedFamily}:wght@100;200;300;400;500;600;700;800;900&display=swap`;
|
||||
|
||||
let link = document.getElementById(this.FONT_LINK_ID);
|
||||
if (!link) {
|
||||
link = document.createElement('link');
|
||||
link.id = this.FONT_LINK_ID;
|
||||
link.rel = 'stylesheet';
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
link.href = url;
|
||||
|
||||
this.setConfig({
|
||||
type: 'google',
|
||||
family: familyName,
|
||||
fallback: 'sans-serif',
|
||||
weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],
|
||||
});
|
||||
|
||||
document.documentElement.style.setProperty('--font-family', `'${familyName}', sans-serif`);
|
||||
},
|
||||
|
||||
async loadFontFromUrl(url, familyName) {
|
||||
const weights = [100, 200, 300, 400, 500, 600, 700, 800, 900];
|
||||
const fontFaceId = this.FONT_FACE_ID;
|
||||
|
||||
let style = document.getElementById(fontFaceId);
|
||||
if (!style) {
|
||||
style = document.createElement('style');
|
||||
style.id = fontFaceId;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
const format = this.getFontFormat(url);
|
||||
const fontFamily = familyName || 'CustomFont';
|
||||
|
||||
style.textContent = `
|
||||
@font-face {
|
||||
font-family: '${fontFamily}';
|
||||
src: url('${url}') format('${format}');
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
`;
|
||||
|
||||
this.setConfig({
|
||||
type: 'url',
|
||||
family: fontFamily,
|
||||
url: url,
|
||||
fallback: 'sans-serif',
|
||||
weights: weights,
|
||||
});
|
||||
|
||||
document.documentElement.style.setProperty('--font-family', `'${fontFamily}', sans-serif`);
|
||||
},
|
||||
|
||||
getFontFormat(url) {
|
||||
const ext = url.split('.').pop().toLowerCase();
|
||||
const formats = {
|
||||
woff2: 'woff2',
|
||||
woff: 'woff',
|
||||
ttf: 'truetype',
|
||||
otf: 'opentype',
|
||||
};
|
||||
return formats[ext] || 'woff2';
|
||||
},
|
||||
|
||||
async saveUploadedFont(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const base64 = e.target.result;
|
||||
const fontId = 'uploaded-' + Date.now();
|
||||
const customFonts = this.getCustomFonts();
|
||||
|
||||
customFonts[fontId] = {
|
||||
name: file.name.replace(/\.[^/.]+$/, ''),
|
||||
base64: base64,
|
||||
format: this.getFontFormat(file.name),
|
||||
size: file.size,
|
||||
uploadedAt: Date.now(),
|
||||
};
|
||||
|
||||
localStorage.setItem(this.CUSTOM_FONTS_KEY, JSON.stringify(customFonts));
|
||||
resolve({ id: fontId, ...customFonts[fontId] });
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
},
|
||||
|
||||
getCustomFonts() {
|
||||
try {
|
||||
const stored = localStorage.getItem(this.CUSTOM_FONTS_KEY);
|
||||
return stored ? JSON.parse(stored) : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
|
||||
async loadUploadedFont(fontId) {
|
||||
const customFonts = this.getCustomFonts();
|
||||
const font = customFonts[fontId];
|
||||
|
||||
if (!font) {
|
||||
throw new Error('Font not found');
|
||||
}
|
||||
|
||||
const fontFamily = font.name || 'UploadedFont';
|
||||
const fontFaceId = this.FONT_FACE_ID;
|
||||
|
||||
let style = document.getElementById(fontFaceId);
|
||||
if (!style) {
|
||||
style = document.createElement('style');
|
||||
style.id = fontFaceId;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
style.textContent = `
|
||||
@font-face {
|
||||
font-family: '${fontFamily}';
|
||||
src: url('${font.base64}') format('${font.format}');
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
`;
|
||||
|
||||
this.setConfig({
|
||||
type: 'uploaded',
|
||||
family: fontFamily,
|
||||
fontId: fontId,
|
||||
fallback: 'sans-serif',
|
||||
weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],
|
||||
});
|
||||
|
||||
document.documentElement.style.setProperty('--font-family', `'${fontFamily}', sans-serif`);
|
||||
},
|
||||
|
||||
deleteUploadedFont(fontId) {
|
||||
const customFonts = this.getCustomFonts();
|
||||
delete customFonts[fontId];
|
||||
localStorage.setItem(this.CUSTOM_FONTS_KEY, JSON.stringify(customFonts));
|
||||
},
|
||||
|
||||
loadPresetFont(family, fallback = 'sans-serif') {
|
||||
let link = document.getElementById(this.FONT_LINK_ID);
|
||||
if (link) {
|
||||
link.remove();
|
||||
}
|
||||
|
||||
let style = document.getElementById(this.FONT_FACE_ID);
|
||||
if (style) {
|
||||
style.remove();
|
||||
}
|
||||
|
||||
this.setConfig({
|
||||
type: 'preset',
|
||||
family: family,
|
||||
fallback: fallback,
|
||||
weights: [400, 500, 600, 700, 800],
|
||||
});
|
||||
|
||||
const fontValue = family === 'monospace' ? 'monospace' : `'${family}', ${fallback}`;
|
||||
document.documentElement.style.setProperty('--font-family', fontValue);
|
||||
},
|
||||
|
||||
applyFont() {
|
||||
const config = this.getConfig();
|
||||
|
||||
switch (config.type) {
|
||||
case 'google':
|
||||
this.loadGoogleFont(config.family);
|
||||
break;
|
||||
case 'url':
|
||||
this.loadFontFromUrl(config.url, config.family);
|
||||
break;
|
||||
case 'uploaded':
|
||||
this.loadUploadedFont(config.fontId);
|
||||
break;
|
||||
case 'preset':
|
||||
default:
|
||||
this.loadPresetFont(config.family, config.fallback);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
getUploadedFontList() {
|
||||
const fonts = this.getCustomFonts();
|
||||
return Object.entries(fonts).map(([id, font]) => ({
|
||||
id,
|
||||
name: font.name,
|
||||
size: font.size,
|
||||
uploadedAt: font.uploadedAt,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
|
|
|||
44
js/ui.js
44
js/ui.js
|
|
@ -42,8 +42,9 @@ import {
|
|||
createProjectCardHTML,
|
||||
createTrackFromSong,
|
||||
} from './tracker.js';
|
||||
const savedFont = localStorage.getItem('monochrome-font');
|
||||
if (savedFont) document.documentElement.style.setProperty('--font-family', savedFont);
|
||||
import { fontSettings } from './storage.js';
|
||||
|
||||
fontSettings.applyFont();
|
||||
|
||||
function sortTracks(tracks, sortType) {
|
||||
if (sortType === 'custom') return [...tracks];
|
||||
|
|
@ -1382,12 +1383,34 @@ export class UIRenderer {
|
|||
|
||||
const welcomeEl = document.getElementById('home-welcome');
|
||||
const contentEl = document.getElementById('home-content');
|
||||
const editorsPicksSectionEmpty = document.getElementById('home-editors-picks-section-empty');
|
||||
const editorsPicksSection = document.getElementById('home-editors-picks-section');
|
||||
|
||||
const history = await db.getHistory();
|
||||
const favorites = await db.getFavorites('track');
|
||||
const playlists = await db.getPlaylists(true);
|
||||
|
||||
if (history.length === 0 && favorites.length === 0 && playlists.length === 0) {
|
||||
const hasActivity = history.length > 0 || favorites.length > 0 || playlists.length > 0;
|
||||
|
||||
// Handle Editor's Picks visibility based on settings
|
||||
if (!homePageSettings.shouldShowEditorsPicks()) {
|
||||
if (editorsPicksSectionEmpty) editorsPicksSectionEmpty.style.display = 'none';
|
||||
if (editorsPicksSection) editorsPicksSection.style.display = 'none';
|
||||
} else {
|
||||
// Show empty-state section at top when no activity, hide the bottom one
|
||||
if (editorsPicksSectionEmpty) editorsPicksSectionEmpty.style.display = hasActivity ? 'none' : '';
|
||||
// Show bottom section when has activity, render it
|
||||
if (editorsPicksSection) editorsPicksSection.style.display = hasActivity ? '' : 'none';
|
||||
}
|
||||
|
||||
// Render editor's picks in the visible container
|
||||
if (hasActivity) {
|
||||
this.renderHomeEditorsPicks(false, 'home-editors-picks');
|
||||
} else {
|
||||
this.renderHomeEditorsPicks(false, 'home-editors-picks-empty');
|
||||
}
|
||||
|
||||
if (!hasActivity) {
|
||||
if (welcomeEl) welcomeEl.style.display = 'block';
|
||||
if (contentEl) contentEl.style.display = 'none';
|
||||
return;
|
||||
|
|
@ -1414,7 +1437,6 @@ export class UIRenderer {
|
|||
|
||||
this.renderHomeSongs();
|
||||
this.renderHomeAlbums();
|
||||
this.renderHomeEditorsPicks();
|
||||
this.renderHomeArtists();
|
||||
this.renderHomeRecent();
|
||||
}
|
||||
|
|
@ -1540,16 +1562,8 @@ export class UIRenderer {
|
|||
});
|
||||
}
|
||||
|
||||
async renderHomeEditorsPicks(forceRefresh = false) {
|
||||
const picksContainer = document.getElementById('home-editors-picks');
|
||||
const section = document.getElementById('home-editors-picks-section');
|
||||
|
||||
if (!homePageSettings.shouldShowEditorsPicks()) {
|
||||
if (section) section.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
if (section) section.style.display = '';
|
||||
async renderHomeEditorsPicks(forceRefresh = false, containerId = 'home-editors-picks') {
|
||||
const picksContainer = document.getElementById(containerId);
|
||||
|
||||
if (picksContainer) {
|
||||
if (forceRefresh) picksContainer.innerHTML = this.createSkeletonCards(6);
|
||||
|
|
@ -1598,7 +1612,7 @@ export class UIRenderer {
|
|||
|
||||
if (cardsHTML.length > 0) {
|
||||
picksContainer.innerHTML = cardsHTML.join('');
|
||||
itemsToStore.forEach((item, index) => {
|
||||
itemsToStore.forEach((item, _index) => {
|
||||
const type = item.type;
|
||||
const id = item.data.id;
|
||||
const el = picksContainer.querySelector(`[data-${type}-id="${id}"]`);
|
||||
|
|
|
|||
15
package-lock.json
generated
15
package-lock.json
generated
|
|
@ -74,7 +74,6 @@
|
|||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
|
|
@ -1604,7 +1603,6 @@
|
|||
"integrity": "sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@keyv/serialize": "^1.1.1"
|
||||
}
|
||||
|
|
@ -1646,7 +1644,6 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
|
|
@ -1690,7 +1687,6 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
|
|
@ -3142,7 +3138,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@svta/cml-xml/-/cml-xml-1.0.1.tgz",
|
||||
"integrity": "sha512-11LkJa5kDEcsRMWkVI1ABH3KLCxGoiSVe4kQ293ItVj8ncTTQ7htmCGiJDjS+Cmy35UgF3e/vc0ysJIiWRTx2g==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
|
|
@ -3191,7 +3186,6 @@
|
|||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
|
|
@ -3215,7 +3209,6 @@
|
|||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
|
|
@ -3503,7 +3496,6 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
|
|
@ -4288,7 +4280,6 @@
|
|||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
|
|
@ -6645,7 +6636,6 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
|
|
@ -6729,7 +6719,6 @@
|
|||
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
|
|
@ -7679,7 +7668,6 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@csstools/css-parser-algorithms": "^3.0.5",
|
||||
"@csstools/css-syntax-patches-for-csstree": "^1.0.19",
|
||||
|
|
@ -8094,7 +8082,6 @@
|
|||
"integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.15.0",
|
||||
|
|
@ -8419,7 +8406,6 @@
|
|||
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
|
|
@ -8807,7 +8793,6 @@
|
|||
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
|
|
|
|||
147
styles.css
147
styles.css
|
|
@ -268,6 +268,7 @@
|
|||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
font-family: var(--font-family, 'Inter', sans-serif) !important;
|
||||
}
|
||||
|
||||
html {
|
||||
|
|
@ -280,7 +281,7 @@ html {
|
|||
body {
|
||||
background-color: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: var(--font-family, 'Inter', sans-serif);
|
||||
font-family: var(--font-family, 'Inter', sans-serif) !important;
|
||||
overflow: hidden;
|
||||
transition:
|
||||
background-color 0.3s ease,
|
||||
|
|
@ -318,7 +319,7 @@ kbd {
|
|||
border-radius: 4px;
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-family: inherit;
|
||||
box-shadow: 0 2px 4px rgb(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
|
|
@ -1774,7 +1775,6 @@ input[type='search']::-webkit-search-cancel-button {
|
|||
border-radius: var(--radius);
|
||||
color: var(--foreground);
|
||||
font-size: 0.9rem;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.template-input:focus {
|
||||
|
|
@ -1782,6 +1782,142 @@ input[type='search']::-webkit-search-cancel-button {
|
|||
border-color: var(--ring);
|
||||
}
|
||||
|
||||
.font-settings-container {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.font-settings-container .info {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.font-input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.font-type-select {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
background-color: var(--input);
|
||||
color: var(--foreground);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.font-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.font-section select {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
background-color: var(--input);
|
||||
color: var(--foreground);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.font-input {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
background-color: var(--input);
|
||||
color: var(--foreground);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.font-name-input {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.font-file-input {
|
||||
padding: 0.5rem;
|
||||
background-color: var(--input);
|
||||
color: var(--foreground);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.font-file-input::file-selector-button {
|
||||
background-color: var(--secondary);
|
||||
color: var(--secondary-foreground);
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.4rem 0.8rem;
|
||||
margin-right: 0.75rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
transition: background-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.font-file-input::file-selector-button:hover {
|
||||
background-color: var(--secondary-hover);
|
||||
}
|
||||
|
||||
.uploaded-fonts-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.uploaded-font-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background-color: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.uploaded-font-item .font-name {
|
||||
flex: 1;
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.uploaded-font-item .font-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.uploaded-font-item .btn-icon {
|
||||
padding: 0.25rem 0.5rem;
|
||||
background-color: var(--secondary);
|
||||
color: var(--secondary-foreground);
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.75rem;
|
||||
cursor: pointer;
|
||||
transition: background-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.uploaded-font-item .btn-icon:hover {
|
||||
background-color: var(--secondary-hover);
|
||||
}
|
||||
|
||||
.uploaded-font-item .btn-delete {
|
||||
background-color: var(--destructive);
|
||||
color: var(--destructive-foreground);
|
||||
}
|
||||
|
||||
.uploaded-font-item .btn-delete:hover {
|
||||
background-color: var(--destructive-hover);
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
|
@ -3041,7 +3177,6 @@ input:checked + .slider::before {
|
|||
|
||||
.about-tech p {
|
||||
color: var(--muted-foreground);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
|
|
@ -3205,7 +3340,6 @@ input:checked + .slider::before {
|
|||
background-color: var(--secondary);
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 4px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
|
|
@ -3376,7 +3510,6 @@ input:checked + .slider::before {
|
|||
}
|
||||
|
||||
.lyrics-timing-display {
|
||||
font-family: monospace;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--foreground);
|
||||
|
|
@ -4002,7 +4135,6 @@ img[src=''] {
|
|||
padding: 0.75rem 1.25rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
color: var(--foreground);
|
||||
font-family: Inter, sans-serif;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
|
|
@ -4926,7 +5058,6 @@ body:has(#fullscreen-cover-overlay:not([style*='display: none'])) .now-playing-b
|
|||
font-size: 0.8rem;
|
||||
color: var(--muted-foreground);
|
||||
margin-bottom: 0.5rem;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.annotation-text {
|
||||
|
|
|
|||
Loading…
Reference in a new issue