mirror of
https://github.com/shadoll/sLogos.git
synced 2026-08-28 11:33:29 +00:00
244 lines
6.7 KiB
Svelte
244 lines
6.7 KiB
Svelte
<script>
|
|
import { onMount } from 'svelte';
|
|
import Grid from './components/Grid.svelte';
|
|
import List from './components/List.svelte';
|
|
import Header from './components/Header.svelte';
|
|
|
|
let viewMode = 'grid'; // 'grid' or 'list'
|
|
let searchQuery = '';
|
|
let logos = [];
|
|
let filteredLogos = [];
|
|
let theme = 'system';
|
|
let mq;
|
|
let allTags = [];
|
|
let selectedTags = [];
|
|
let tagDropdownOpen = false;
|
|
|
|
// Load logos from JSON file with cache busting
|
|
onMount(async () => {
|
|
try {
|
|
// Add timestamp as cache-busting query parameter
|
|
const timestamp = new Date().getTime();
|
|
const response = await fetch(`data/logos.json?t=${timestamp}`, {
|
|
// Force reload from server, don't use cache
|
|
cache: 'no-cache',
|
|
headers: {
|
|
'Cache-Control': 'no-cache',
|
|
'Pragma': 'no-cache'
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
logos = await response.json();
|
|
filteredLogos = logos;
|
|
console.log('Loaded logos:', logos.length, 'at', new Date().toLocaleTimeString());
|
|
} else {
|
|
console.error('Failed to load logos data', response.status);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading logos:', error);
|
|
}
|
|
|
|
const stored = localStorage.getItem('theme');
|
|
if (stored === 'light' || stored === 'dark' || stored === 'system') {
|
|
theme = stored;
|
|
}
|
|
applyTheme();
|
|
|
|
// Listen for system theme changes if using system
|
|
mq = window.matchMedia('(prefers-color-scheme: dark)');
|
|
mq.addEventListener('change', () => {
|
|
if (theme === 'system') applyTheme();
|
|
});
|
|
});
|
|
|
|
// Make sure to apply theme whenever it changes
|
|
$: if (theme) {
|
|
console.log('[Theme] Theme changed:', theme);
|
|
applyTheme();
|
|
}
|
|
|
|
// Compute all unique tags as objects with text and optional color
|
|
$: allTags = Array.from(
|
|
new Map(
|
|
logos.flatMap(logo => (logo.tags || []).map(tag => {
|
|
if (typeof tag === 'string') return [tag, { text: tag }];
|
|
return [tag.text, tag];
|
|
}))
|
|
).values()
|
|
).sort((a, b) => a.text.localeCompare(b.text));
|
|
|
|
$: filteredLogos = logos.filter(logo => {
|
|
const matchesSearch = logo.name.toLowerCase().includes(searchQuery.toLowerCase());
|
|
const matchesTags = !selectedTags.length || (logo.tags && logo.tags.some(tag => selectedTags.includes(typeof tag === 'string' ? tag : tag.text)));
|
|
return matchesSearch && matchesTags;
|
|
});
|
|
|
|
// Compute the effective theme for children
|
|
$: effectiveTheme = theme === 'system'
|
|
? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
|
|
: theme;
|
|
|
|
function setGridView() {
|
|
console.log('Setting view mode to: grid');
|
|
viewMode = 'grid';
|
|
}
|
|
|
|
function setListView() {
|
|
console.log('Setting view mode to: list');
|
|
viewMode = 'list';
|
|
}
|
|
|
|
function copyUrl(logoPath) {
|
|
const url = `${window.location.origin}/${logoPath}`;
|
|
// Try modern clipboard API first
|
|
if (navigator.clipboard && window.isSecureContext) {
|
|
navigator.clipboard.writeText(url)
|
|
.then(() => {
|
|
alert('URL copied to clipboard!');
|
|
})
|
|
.catch(err => {
|
|
// Fallback: use execCommand for legacy support
|
|
try {
|
|
const input = document.createElement('input');
|
|
input.value = url;
|
|
document.body.appendChild(input);
|
|
input.select();
|
|
document.execCommand('copy');
|
|
document.body.removeChild(input);
|
|
alert('URL copied to clipboard!');
|
|
} catch (fallbackErr) {
|
|
// Final fallback: show prompt for manual copy
|
|
window.prompt('Copy this URL:', url);
|
|
}
|
|
});
|
|
} else {
|
|
// Fallback for non-secure context or missing clipboard API
|
|
try {
|
|
const input = document.createElement('input');
|
|
input.value = url;
|
|
document.body.appendChild(input);
|
|
input.select();
|
|
document.execCommand('copy');
|
|
document.body.removeChild(input);
|
|
alert('URL copied to clipboard!');
|
|
} catch (fallbackErr) {
|
|
window.prompt('Copy this URL:', url);
|
|
}
|
|
}
|
|
}
|
|
|
|
function downloadLogo(logoPath, logoName) {
|
|
const link = document.createElement('a');
|
|
link.href = logoPath;
|
|
link.download = logoName;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
}
|
|
|
|
function applyTheme() {
|
|
let effectiveTheme = theme;
|
|
if (theme === 'system') {
|
|
effectiveTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
}
|
|
// Apply theme both ways for compatibility
|
|
document.documentElement.setAttribute('data-theme', effectiveTheme);
|
|
document.documentElement.className = effectiveTheme; // Add class-based theming
|
|
console.log('[Theme] Applied theme:', effectiveTheme);
|
|
}
|
|
|
|
function setTheme(newTheme) {
|
|
if (newTheme === 'light' || newTheme === 'dark' || newTheme === 'system') {
|
|
theme = newTheme;
|
|
localStorage.setItem('theme', newTheme);
|
|
console.log('[Theme] setTheme:', newTheme);
|
|
// Apply theme immediately after setting
|
|
setTimeout(() => applyTheme(), 0);
|
|
}
|
|
}
|
|
|
|
function toggleTag(tag) {
|
|
if (selectedTags.includes(tag)) {
|
|
selectedTags = selectedTags.filter(t => t !== tag);
|
|
} else {
|
|
selectedTags = [...selectedTags, tag];
|
|
}
|
|
}
|
|
|
|
function addTag(tag) {
|
|
if (!selectedTags.includes(tag)) {
|
|
selectedTags = [...selectedTags, tag];
|
|
}
|
|
tagDropdownOpen = false;
|
|
}
|
|
|
|
function removeTag(tag) {
|
|
selectedTags = selectedTags.filter(t => t !== tag);
|
|
}
|
|
|
|
function toggleDropdown() {
|
|
tagDropdownOpen = !tagDropdownOpen;
|
|
}
|
|
|
|
function closeDropdown(e) {
|
|
if (!e.target.closest('.tag-dropdown')) {
|
|
tagDropdownOpen = false;
|
|
}
|
|
}
|
|
|
|
function getTagObj(text) {
|
|
return allTags.find(t => t.text === text);
|
|
}
|
|
|
|
// Listen for outside click to close dropdown
|
|
$: if (tagDropdownOpen) {
|
|
window.addEventListener('click', closeDropdown);
|
|
} else {
|
|
window.removeEventListener('click', closeDropdown);
|
|
}
|
|
</script>
|
|
|
|
<main class="container">
|
|
<Header
|
|
{logos}
|
|
{theme}
|
|
{setTheme}
|
|
{viewMode}
|
|
{setGridView}
|
|
{setListView}
|
|
bind:searchQuery
|
|
{allTags}
|
|
{selectedTags}
|
|
{tagDropdownOpen}
|
|
{toggleDropdown}
|
|
{addTag}
|
|
{removeTag}
|
|
{toggleTag}
|
|
{getTagObj}
|
|
{closeDropdown}
|
|
{filteredLogos}
|
|
/>
|
|
|
|
<div class="logos-container">
|
|
{#if viewMode === 'grid'}
|
|
<Grid
|
|
logos={filteredLogos}
|
|
onCopy={copyUrl}
|
|
onDownload={downloadLogo}
|
|
theme={effectiveTheme}
|
|
/>
|
|
{:else}
|
|
<List
|
|
logos={filteredLogos}
|
|
onCopy={copyUrl}
|
|
onDownload={downloadLogo}
|
|
/>
|
|
{/if}
|
|
</div>
|
|
|
|
<footer>
|
|
<p>shadoll Logo Gallery. All logos are property of their respective owners.</p>
|
|
</footer>
|
|
</main>
|