mirror of
https://github.com/shadoll/sLogos.git
synced 2026-02-04 11:03:24 +00:00
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:
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user