mirror of
https://github.com/shadoll/sLogos.git
synced 2026-02-04 11:03:24 +00:00
Refactor App.svelte for improved code consistency and readability
This commit is contained in:
218
src/App.svelte
218
src/App.svelte
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user