Implement routing and improve app data management

- Integrated svelte-spa-router for navigation between Home and Preview pages.
- Refactored App.svelte to manage global app data and handle search queries, compact mode, and view mode changes.
- Updated Grid and List components to navigate to the Preview page instead of using modals.
- Created Home.svelte to serve as the main page, utilizing global app data for rendering.
- Enhanced Preview.svelte to fetch logos and display the selected logo based on URL parameters.
- Improved data handling and reactivity across components, ensuring UI updates reflect changes in global state.
- Added logging for better debugging and tracking of state changes.
This commit is contained in:
sHa
2025-05-15 10:32:15 +03:00
parent 8fb057324e
commit d30e2f9e0b
12 changed files with 805 additions and 350 deletions

View File

@@ -13,19 +13,14 @@
export let allLogos = [];
let showModal = false;
let selectedLogo = null;
function openPreview(logo) {
selectedLogo = logo;
showModal = true;
}
function closeModal() {
showModal = false;
let selectedLogo = null; function openPreview(logo) {
// Navigate to preview page using router
const routerPath = `/preview/${encodeURIComponent(logo.name.replace(/\s+/g, '-').toLowerCase())}`;
window.location.hash = routerPath;
}
function isSvgLogo(logo) {
return logo.format && logo.format.toLowerCase() === 'svg';
return logo && logo.format && logo.format.toLowerCase() === 'svg';
}
export let theme;
@@ -44,23 +39,20 @@ $: getLogoThemeColor = logo => getDefaultLogoColor(logo.colors, theme);
}
</script>
<Preview
show={showModal}
logo={selectedLogo}
theme={theme}
{onCopy}
{onDownload}
on:close={closeModal}
/>
<!-- Preview is now handled via the standalone page, no longer needed here -->
<div class="logo-grid">
{#each logos as logo}
<!-- For debugging: {console.log("Grid: rendering logo", logo)} -->
<div class="logo-card">
<div class="logo-image"
role="button"
tabindex="0"
aria-label="Preview {logo.name}"
on:click={() => openPreview(logo)}
on:click={() => {
console.log("Logo clicked, calling openPreview");
openPreview(logo);
}}
on:keydown={(e) => (e.key === 'Enter' || e.key === ' ') && openPreview(logo)}
style="cursor:pointer;"
>
@@ -85,6 +77,7 @@ $: getLogoThemeColor = logo => getDefaultLogoColor(logo.colors, theme);
<div class="logo-title-row">
<h3>{logo.name}</h3>
<button class="brand-filter-btn" title="Filter by brand" on:click={() => {
console.log("Grid: Filtering by brand:", logo.brand);
setSearchQuery(logo.brand);
// Update URL with search param
const params = new URLSearchParams(window.location.search);

View File

@@ -12,11 +12,11 @@
export let setSearchQuery;
export let allTags = [];
export let selectedTags = [];
export let tagDropdownOpen;
export let toggleDropdown;
export let addTag;
export let removeTag;
export let getTagObj;
export let tagDropdownOpen = false;
export let toggleDropdown = () => console.log("toggleDropdown not provided");
export let addTag = () => console.log("addTag not provided");
export let removeTag = () => console.log("removeTag not provided");
export let getTagObj = (tag) => ({ text: tag });
export let compactMode = false;
export let setCompactMode = () => {};
@@ -34,6 +34,8 @@
window.location.pathname +
(params.toString() ? "?" + params.toString() : "");
history.replaceState(null, "", newUrl);
console.log("Header: Search query set to:", searchQuery);
}
</script>
@@ -144,6 +146,7 @@
window.location.pathname +
(params.toString() ? "?" + params.toString() : "");
history.replaceState(null, "", newUrl);
console.log("Header: Search query cleared");
}}
aria-label="Clear search"
>

View File

@@ -6,9 +6,8 @@
export let targets = null;
export let sets = null;
export let activeSet = null;
export let colors = null; // Add colors object for access to all color values
export const alt = "";
export let colors = null;
export let alt = "";
let svgHtml = "";
async function fetchAndColorSvg() {
@@ -89,39 +88,29 @@
}
}
}
// Otherwise, use the legacy format for backward compatibility
else {
let targetElements;
if (colorConfig.selector) {
targetElements = doc.querySelectorAll(colorConfig.selector);
} else if (colorConfig.target) {
targetElements = doc.querySelectorAll(colorConfig.target);
} else {
targetElements = [];
}
targetElements.forEach((el) => {
if (colorConfig.attribute) {
// Legacy: force a single attribute
el.setAttribute(colorConfig.attribute, color);
} else {
// Always override fill and stroke unless they are 'none'
if (el.hasAttribute("fill") && el.getAttribute("fill") !== "none") {
el.setAttribute("fill", color);
}
if (el.hasAttribute("stroke") && el.getAttribute("stroke") !== "none") {
el.setAttribute("stroke", color);
}
if (!el.hasAttribute("fill") && !el.hasAttribute("stroke")) {
// If neither, prefer fill
el.setAttribute("fill", color);
}
}
});
}
svgHtml = doc.documentElement.outerHTML;
}
$: path, color, colorConfig, targets, sets, activeSet, colors, fetchAndColorSvg();
</script>
{@html svgHtml}
<div class="svg-wrapper" role="img" aria-label={alt || "SVG image"}>
{@html svgHtml}
</div>
<style>
.svg-wrapper {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.svg-wrapper :global(svg) {
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
</style>

View File

@@ -1,7 +1,6 @@
<script>
import Actions from './Actions.svelte';
import Preview from "./Preview.svelte";
import Actions from "./Actions.svelte";
import InlineSvg from "./InlineSvg.svelte";
import { getThemeColor, getDefaultLogoColor } from "../utils/colorTheme.js";
import { generateColorSetCircle } from "../utils/colorCircles.js";
@@ -16,15 +15,10 @@
let showModal = false;
let selectedLogo = null;
let theme = getTheme();
function openPreview(logo) {
selectedLogo = logo;
showModal = true;
}
function closeModal() {
showModal = false;
let theme = getTheme(); function openPreview(logo) {
// Navigate to preview page instead of showing modal
const previewUrl = `#/preview/${encodeURIComponent(logo.name.replace(/\s+/g, '-').toLowerCase())}`;
window.location.href = previewUrl;
}
function isSvgLogo(logo) {
@@ -78,8 +72,6 @@
}
</script>
<Preview show={showModal} logo={selectedLogo} on:close={closeModal} />
<div class="logo-list">
{#each logos as logo}
<div class="logo-item">
@@ -179,6 +171,7 @@
class="brand-filter-btn common-btn"
title="Filter by brand"
on:click={() => {
console.log("List: Filtering by brand:", logo.brand);
setSearchQuery(logo.brand);
// Update URL with search param
const params = new URLSearchParams(window.location.search);

View File

@@ -1,5 +1,5 @@
<script>
import { onMount, onDestroy, createEventDispatcher } from 'svelte';
import { onMount } from 'svelte';
import InlineSvg from './InlineSvg.svelte';
import Actions from './Actions.svelte';
import { getDefaultLogoColor, getThemeColor } from '../utils/colorTheme.js';
@@ -9,7 +9,7 @@
export let show = false;
export let logo = null;
export let theme;
export let openLogoByAnchor = () => {};
export const openLogoByAnchor = () => {};
export let onDownload = (path, name) => {
const a = document.createElement('a');
a.href = path;
@@ -23,22 +23,7 @@
let svgSource = '';
let isFetchingSvgSource = false;
const dispatch = createEventDispatcher();
function close() {
show = false;
dispatch('close');
// Remove preview anchor from URL
if (window.location.hash.startsWith('#preview-')) {
history.replaceState(null, '', window.location.pathname + window.location.search);
}
}
function handleKeydown(event) {
if (event.key === 'Escape') {
close();
}
}
// No event dispatching, parent is fully responsible for controlling the component
function isSvgLogo(logo) {
return logo && logo.format && logo.format.toLowerCase() === 'svg';
@@ -62,29 +47,11 @@
$: validColorConfig = logo && typeof logo.colorConfig === 'object' && logo.colorConfig.selector ? logo.colorConfig : undefined;
// Sync show state with URL hash
$: {
if (typeof window !== 'undefined') {
if (window.location.hash.startsWith('#preview-')) {
show = true;
} else {
show = false;
}
}
}
// No URL manipulation in the component anymore
// Parent components should handle all URL and navigation concerns
// Update URL hash when opening/closing preview
// Only fetch SVG source when displayed
$: if (show && logo) {
const anchor = '#preview-' + encodeURIComponent(logo.name.replace(/\s+/g, '-').toLowerCase());
if (window.location.hash !== anchor) {
history.replaceState(null, '', window.location.pathname + window.location.search + anchor);
}
}
// Watch for show/close to update scroll lock
$: if (show && logo) {
document.body.style.overflow = 'hidden';
// Fetch SVG source when logo is displayed and is an SVG
if (logo.format === 'SVG' && !svgSource) {
isFetchingSvgSource = true;
@@ -98,37 +65,10 @@
isFetchingSvgSource = false;
});
}
} else {
document.body.style.overflow = '';
}
// On mount, check for preview anchor and open if present
onMount(() => {
document.addEventListener('keydown', handleKeydown);
if (window.location.hash.startsWith('#preview-')) {
openLogoByAnchor(window.location.hash);
}
window.addEventListener('hashchange', onHashChange);
// Lock background scroll when preview is open
if (show && logo) {
document.body.style.overflow = 'hidden';
}
});
onDestroy(() => {
document.removeEventListener('keydown', handleKeydown);
window.removeEventListener('hashchange', onHashChange);
// Restore scroll when component is destroyed
document.body.style.overflow = '';
});
function onHashChange() {
if (window.location.hash.startsWith('#preview-')) {
openLogoByAnchor(window.location.hash);
} else {
dispatch('close');
}
}
// Component doesn't handle any window events or URL changes
// Parent should handle all navigation logic
// Svelte action to remove width/height from SVGs for responsive scaling
function removeSvgSize(node) {
@@ -154,15 +94,16 @@
</script>
<div class="modal-backdrop fullscreen"
style="display: {show && logo ? 'flex' : 'none'}"
style="display: {show === true ? 'flex' : 'none'}"
role="dialog"
aria-modal="true"
>
{#if logo}
<div class="modal-content fullscreen-modal">
<div class="modal-header">
<div class="header-spacer"></div>
<h2>{logo.name}</h2>
<button class="close-btn" on:click={close} aria-label="Close preview">×</button>
<div class="header-spacer"></div>
</div>
<div class="modal-body fullscreen-body">
<div class="preview-container fullscreen-preview"
@@ -355,17 +296,9 @@
color: var(--color-accent, #4f8cff);
margin: 0;
}
.close-btn {
font-size: 2.5rem;
background: none;
border: none;
color: var(--color-text);
cursor: pointer;
transition: color 0.2s;
z-index: 2;
}
.close-btn:hover {
color: #f44336;
.header-spacer {
/* This empty div helps maintain the centered title with the back button on the left */
width: 70px;
}
.modal-body.fullscreen-body {
flex: 1 1 auto;