Refactor App.svelte for improved code consistency and readability

This commit is contained in:
sHa
2025-05-02 10:16:51 +03:00
parent 92acb6e982
commit 2938631e85

View File

@@ -1,15 +1,15 @@
<script> <script>
import { onMount } from 'svelte'; import { onMount } from "svelte";
import Grid from './components/Grid.svelte'; import Grid from "./components/Grid.svelte";
import List from './components/List.svelte'; import List from "./components/List.svelte";
import Header from './components/Header.svelte'; import Header from "./components/Header.svelte";
import Preview from './components/Preview.svelte'; import Preview from "./components/Preview.svelte";
let viewMode = 'grid'; // 'grid' or 'list' let viewMode = "grid"; // 'grid' or 'list'
let searchQuery = ''; let searchQuery = "";
let logos = []; let logos = [];
let filteredLogos = []; let filteredLogos = [];
let theme = 'system'; let theme = "system";
let mq; let mq;
let allTags = []; let allTags = [];
let selectedTags = []; let selectedTags = [];
@@ -24,34 +24,39 @@
const timestamp = new Date().getTime(); const timestamp = new Date().getTime();
const response = await fetch(`data/logos.json?t=${timestamp}`, { const response = await fetch(`data/logos.json?t=${timestamp}`, {
// Force reload from server, don't use cache // Force reload from server, don't use cache
cache: 'no-cache', cache: "no-cache",
headers: { headers: {
'Cache-Control': 'no-cache', "Cache-Control": "no-cache",
'Pragma': 'no-cache' Pragma: "no-cache",
} },
}); });
if (response.ok) { if (response.ok) {
logos = await response.json(); logos = await response.json();
filteredLogos = logos; filteredLogos = logos;
console.log('Loaded logos:', logos.length, 'at', new Date().toLocaleTimeString()); console.log(
"Loaded logos:",
logos.length,
"at",
new Date().toLocaleTimeString(),
);
} else { } else {
console.error('Failed to load logos data', response.status); console.error("Failed to load logos data", response.status);
} }
} catch (error) { } catch (error) {
console.error('Error loading logos:', error); console.error("Error loading logos:", error);
} }
const stored = localStorage.getItem('theme'); const stored = localStorage.getItem("theme");
if (stored === 'light' || stored === 'dark' || stored === 'system') { if (stored === "light" || stored === "dark" || stored === "system") {
theme = stored; theme = stored;
} }
applyTheme(); applyTheme();
// Listen for system theme changes if using system // Listen for system theme changes if using system
mq = window.matchMedia('(prefers-color-scheme: dark)'); mq = window.matchMedia("(prefers-color-scheme: dark)");
mq.addEventListener('change', () => { mq.addEventListener("change", () => {
if (theme === 'system') applyTheme(); if (theme === "system") applyTheme();
}); });
// Open preview if URL contains anchor // Open preview if URL contains anchor
@@ -60,82 +65,95 @@
// Make sure to apply theme whenever it changes // Make sure to apply theme whenever it changes
$: if (theme) { $: if (theme) {
console.log('[Theme] Theme changed:', theme); console.log("[Theme] Theme changed:", theme);
applyTheme(); applyTheme();
} }
// Compute all unique tags as objects with text and optional color // Compute all unique tags as objects with text and optional color
$: allTags = Array.from( $: allTags = Array.from(
new Map( new Map(
logos.flatMap(logo => (logo.tags || []).map(tag => { logos.flatMap((logo) =>
if (typeof tag === 'string') return [tag, { text: tag }]; (logo.tags || []).map((tag) => {
if (typeof tag === "string") return [tag, { text: tag }];
return [tag.text, tag]; return [tag.text, tag];
})) }),
).values() ),
).values(),
).sort((a, b) => a.text.localeCompare(b.text)); ).sort((a, b) => a.text.localeCompare(b.text));
$: filteredLogos = logos.filter(logo => { $: filteredLogos = logos.filter((logo) => {
const matchesSearch = logo.name.toLowerCase().includes(searchQuery.toLowerCase()); const matchesSearch = logo.name
const matchesTags = !selectedTags.length || (logo.tags && logo.tags.some(tag => selectedTags.includes(typeof tag === 'string' ? tag : tag.text))); .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; return matchesSearch && matchesTags;
}); });
// Compute the effective theme for children // Compute the effective theme for children
$: effectiveTheme = theme === 'system' $: effectiveTheme =
? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') theme === "system"
? window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light"
: theme; : theme;
function setGridView() { function setGridView() {
console.log('Setting view mode to: grid'); console.log("Setting view mode to: grid");
viewMode = 'grid'; viewMode = "grid";
} }
function setListView() { function setListView() {
console.log('Setting view mode to: list'); console.log("Setting view mode to: list");
viewMode = 'list'; viewMode = "list";
} }
function copyUrl(logoPath) { function copyUrl(logoPath) {
const url = `${window.location.origin}/${logoPath}`; const url = `${window.location.origin}/${logoPath}`;
// Try modern clipboard API first // Try modern clipboard API first
if (navigator.clipboard && window.isSecureContext) { if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(url) navigator.clipboard
.writeText(url)
.then(() => { .then(() => {
alert('URL copied to clipboard!'); alert("URL copied to clipboard!");
}) })
.catch(err => { .catch((err) => {
// Fallback: use execCommand for legacy support // Fallback: use execCommand for legacy support
try { try {
const input = document.createElement('input'); const input = document.createElement("input");
input.value = url; input.value = url;
document.body.appendChild(input); document.body.appendChild(input);
input.select(); input.select();
document.execCommand('copy'); document.execCommand("copy");
document.body.removeChild(input); document.body.removeChild(input);
alert('URL copied to clipboard!'); alert("URL copied to clipboard!");
} catch (fallbackErr) { } catch (fallbackErr) {
// Final fallback: show prompt for manual copy // Final fallback: show prompt for manual copy
window.prompt('Copy this URL:', url); window.prompt("Copy this URL:", url);
} }
}); });
} else { } else {
// Fallback for non-secure context or missing clipboard API // Fallback for non-secure context or missing clipboard API
try { try {
const input = document.createElement('input'); const input = document.createElement("input");
input.value = url; input.value = url;
document.body.appendChild(input); document.body.appendChild(input);
input.select(); input.select();
document.execCommand('copy'); document.execCommand("copy");
document.body.removeChild(input); document.body.removeChild(input);
alert('URL copied to clipboard!'); alert("URL copied to clipboard!");
} catch (fallbackErr) { } catch (fallbackErr) {
window.prompt('Copy this URL:', url); window.prompt("Copy this URL:", url);
} }
} }
} }
function downloadLogo(logoPath, logoName) { function downloadLogo(logoPath, logoName) {
const link = document.createElement('a'); const link = document.createElement("a");
link.href = logoPath; link.href = logoPath;
link.download = logoName; link.download = logoName;
document.body.appendChild(link); document.body.appendChild(link);
@@ -145,20 +163,22 @@
function applyTheme() { function applyTheme() {
let effectiveTheme = theme; let effectiveTheme = theme;
if (theme === 'system') { if (theme === "system") {
effectiveTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; effectiveTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
} }
// Apply theme both ways for compatibility // Apply theme both ways for compatibility
document.documentElement.setAttribute('data-theme', effectiveTheme); document.documentElement.setAttribute("data-theme", effectiveTheme);
document.documentElement.className = effectiveTheme; // Add class-based theming document.documentElement.className = effectiveTheme; // Add class-based theming
console.log('[Theme] Applied theme:', effectiveTheme); console.log("[Theme] Applied theme:", effectiveTheme);
} }
function setTheme(newTheme) { function setTheme(newTheme) {
if (newTheme === 'light' || newTheme === 'dark' || newTheme === 'system') { if (newTheme === "light" || newTheme === "dark" || newTheme === "system") {
theme = newTheme; theme = newTheme;
localStorage.setItem('theme', newTheme); localStorage.setItem("theme", newTheme);
console.log('[Theme] setTheme:', newTheme); console.log("[Theme] setTheme:", newTheme);
// Apply theme immediately after setting // Apply theme immediately after setting
setTimeout(() => applyTheme(), 0); setTimeout(() => applyTheme(), 0);
} }
@@ -166,7 +186,7 @@
function toggleTag(tag) { function toggleTag(tag) {
if (selectedTags.includes(tag)) { if (selectedTags.includes(tag)) {
selectedTags = selectedTags.filter(t => t !== tag); selectedTags = selectedTags.filter((t) => t !== tag);
} else { } else {
selectedTags = [...selectedTags, tag]; selectedTags = [...selectedTags, tag];
} }
@@ -180,7 +200,7 @@
} }
function removeTag(tag) { function removeTag(tag) {
selectedTags = selectedTags.filter(t => t !== tag); selectedTags = selectedTags.filter((t) => t !== tag);
} }
function toggleDropdown() { function toggleDropdown() {
@@ -188,13 +208,13 @@
} }
function closeDropdown(e) { function closeDropdown(e) {
if (!e.target.closest('.tag-dropdown')) { if (!e.target.closest(".tag-dropdown")) {
tagDropdownOpen = false; tagDropdownOpen = false;
} }
} }
function getTagObj(text) { function getTagObj(text) {
return allTags.find(t => t.text === text); return allTags.find((t) => t.text === text);
} }
function openPreview(logo) { function openPreview(logo) {
@@ -203,9 +223,15 @@
} }
function openLogoByAnchor(hash) { function openLogoByAnchor(hash) {
if (!hash || !hash.startsWith('#preview-')) return; if (!hash || !hash.startsWith("#preview-")) return;
const anchor = decodeURIComponent(hash.replace('#preview-', '').replace(/-/g, ' ')); const anchor = decodeURIComponent(
const found = logos.find(l => l.name.replace(/\s+/g, '-').toLowerCase() === anchor.replace(/\s+/g, '-').toLowerCase()); hash.replace("#preview-", "").replace(/-/g, " "),
);
const found = logos.find(
(l) =>
l.name.replace(/\s+/g, "-").toLowerCase() ===
anchor.replace(/\s+/g, "-").toLowerCase(),
);
if (found) { if (found) {
openPreview(found); openPreview(found);
} }
@@ -213,9 +239,9 @@
// Listen for outside click to close dropdown // Listen for outside click to close dropdown
$: if (tagDropdownOpen) { $: if (tagDropdownOpen) {
window.addEventListener('click', closeDropdown); window.addEventListener("click", closeDropdown);
} else { } else {
window.removeEventListener('click', closeDropdown); window.removeEventListener("click", closeDropdown);
} }
</script> </script>
@@ -245,29 +271,87 @@
bind:logo={selectedLogo} bind:logo={selectedLogo}
{theme} {theme}
{logos} {logos}
openLogoByAnchor={openLogoByAnchor} {openLogoByAnchor}
/> />
<div class="logos-container"> <div class="logos-container">
{#if viewMode === 'grid'} {#if viewMode === "grid"}
<Grid <Grid
logos={filteredLogos} logos={filteredLogos}
onCopy={copyUrl} onCopy={copyUrl}
onDownload={downloadLogo} onDownload={downloadLogo}
theme={effectiveTheme} theme={effectiveTheme}
on:openPreview={e => openPreview(e.detail)} on:openPreview={(e) => openPreview(e.detail)}
/> />
{:else} {:else}
<List <List
logos={filteredLogos} logos={filteredLogos}
onCopy={copyUrl} onCopy={copyUrl}
onDownload={downloadLogo} onDownload={downloadLogo}
on:openPreview={e => openPreview(e.detail)} on:openPreview={(e) => openPreview(e.detail)}
/> />
{/if} {/if}
</div> </div>
<footer> <footer>
<p>shadoll Logo Gallery. All logos are property of their respective owners.</p> <div class="footer-flex">
<span class="footer-left">shadoll Logo Gallery</span>
<span class="footer-center">All logos are property of their respective owners.</span>
<a
href="https://github.com/shadoll/sLogos"
target="_blank"
rel="noopener noreferrer"
class="footer-github"
>
<svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="#ccc"
style="margin-right:0.3em;"
><path
d="M12 0.297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.387 0.6 0.113 0.82-0.258 0.82-0.577 0-0.285-0.011-1.04-0.017-2.04-3.338 0.726-4.042-1.61-4.042-1.61-0.546-1.387-1.333-1.756-1.333-1.756-1.089-0.745 0.084-0.729 0.084-0.729 1.205 0.084 1.84 1.236 1.84 1.236 1.07 1.834 2.809 1.304 3.495 0.997 0.108-0.775 0.418-1.305 0.762-1.605-2.665-0.305-5.466-1.334-5.466-5.931 0-1.311 0.469-2.381 1.236-3.221-0.124-0.303-0.535-1.523 0.117-3.176 0 0 1.008-0.322 3.301 1.23 0.957-0.266 1.983-0.399 3.003-0.404 1.02 0.005 2.047 0.138 3.006 0.404 2.291-1.553 3.297-1.23 3.297-1.23 0.653 1.653 0.242 2.873 0.119 3.176 0.77 0.84 1.235 1.91 1.235 3.221 0 4.609-2.803 5.624-5.475 5.921 0.43 0.372 0.823 1.102 0.823 2.222 0 1.606-0.015 2.898-0.015 3.293 0 0.322 0.216 0.694 0.825 0.576 4.765-1.589 8.199-6.085 8.199-11.386 0-6.627-5.373-12-12-12z"
/></svg>
</a>
</div>
</footer> </footer>
<style>
.footer-flex {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
gap: 1em;
}
.footer-left {
flex: 1;
text-align: left;
}
.footer-center {
flex: 2;
text-align: left;
}
.footer-github {
flex: 0;
margin-left: 1em;
display: inline-flex;
align-items: center;
}
@media (max-width: 700px) {
.footer-flex {
flex-direction: column;
align-items: flex-start;
gap: 0.3em;
}
.footer-left, .footer-center {
text-align: left;
width: 100%;
}
.footer-github {
margin-left: 0;
margin-top: 0.5em;
}
}
</style>
</main> </main>