Enhance logo preview functionality with URL anchor support and responsive SVG handling

This commit is contained in:
sHa
2025-05-01 23:29:46 +03:00
parent 8c10f643d9
commit 43a3b37661
3 changed files with 222 additions and 39 deletions

View File

@@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 1041.73 179.59" overflow="visible"
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 1041.73 179.59" overflow="visible"
xml:space="preserve">
<path fill="currentColor" d="M646.69,53.92l0,10.04c0,1.87-1.54,3.37-3.41,3.3c-2.85-0.11-6.83-0.25-9.27-0.29
c-13.63,0-21.15,8.13-21.15,22.9v36.6c0,1.82-1.48,3.3-3.3,3.3H597.6c-1.82,0-3.29-1.48-3.29-3.3V52.98c0-1.82,1.48-3.3,3.29-3.3

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -3,6 +3,7 @@
import Grid from './components/Grid.svelte';
import List from './components/List.svelte';
import Header from './components/Header.svelte';
import Preview from './components/Preview.svelte';
let viewMode = 'grid'; // 'grid' or 'list'
let searchQuery = '';
@@ -13,6 +14,8 @@
let allTags = [];
let selectedTags = [];
let tagDropdownOpen = false;
let showModal = false;
let selectedLogo = null;
// Load logos from JSON file with cache busting
onMount(async () => {
@@ -50,6 +53,9 @@
mq.addEventListener('change', () => {
if (theme === 'system') applyTheme();
});
// Open preview if URL contains anchor
openLogoByAnchor(window.location.hash);
});
// Make sure to apply theme whenever it changes
@@ -191,6 +197,20 @@
return allTags.find(t => t.text === text);
}
function openPreview(logo) {
selectedLogo = logo;
showModal = true;
}
function openLogoByAnchor(hash) {
if (!hash || !hash.startsWith('#preview-')) return;
const anchor = decodeURIComponent(hash.replace('#preview-', '').replace(/-/g, ' '));
const found = logos.find(l => l.name.replace(/\s+/g, '-').toLowerCase() === anchor.replace(/\s+/g, '-').toLowerCase());
if (found) {
openPreview(found);
}
}
// Listen for outside click to close dropdown
$: if (tagDropdownOpen) {
window.addEventListener('click', closeDropdown);
@@ -220,6 +240,14 @@
{filteredLogos}
/>
<Preview
bind:show={showModal}
bind:logo={selectedLogo}
{theme}
{logos}
openLogoByAnchor={openLogoByAnchor}
/>
<div class="logos-container">
{#if viewMode === 'grid'}
<Grid
@@ -227,12 +255,14 @@
onCopy={copyUrl}
onDownload={downloadLogo}
theme={effectiveTheme}
on:openPreview={e => openPreview(e.detail)}
/>
{:else}
<List
logos={filteredLogos}
onCopy={copyUrl}
onDownload={downloadLogo}
on:openPreview={e => openPreview(e.detail)}
/>
{/if}
</div>

View File

@@ -5,11 +5,18 @@
export let show = false;
export let logo = null;
export let theme;
export let logos = [];
export let openLogoByAnchor = () => {};
const dispatch = createEventDispatcher();
function close() {
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) {
@@ -22,9 +29,7 @@
return logo && logo.format && logo.format.toLowerCase() === 'svg';
}
// Always use $theme directly, do not cache in a function
export let theme;
$: getLogoThemeColor = logo => getDefaultLogoColor(logo.colors, theme);
$: getLogoThemeColor = logo => getDefaultLogoColor(logo.colors, theme);
// Improved debug logging for color and theme
$: {
@@ -35,35 +40,76 @@ $: getLogoThemeColor = logo => getDefaultLogoColor(logo.colors, theme);
}
}
// Update URL hash when opening/closing preview
$: 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);
}
}
// 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);
});
onDestroy(() => {
document.removeEventListener('keydown', handleKeydown);
window.removeEventListener('hashchange', onHashChange);
});
function onHashChange() {
if (window.location.hash.startsWith('#preview-')) {
openLogoByAnchor(window.location.hash);
} else {
dispatch('close');
}
}
// Svelte action to remove width/height from SVGs for responsive scaling
function removeSvgSize(node) {
function cleanSvg() {
const svgs = node.querySelectorAll('svg');
svgs.forEach(svg => {
svg.removeAttribute('width');
svg.removeAttribute('height');
svg.style.width = '100%';
svg.style.height = '100%';
});
}
cleanSvg();
// In case SVG is loaded async (e.g. InlineSvg), observe for changes
const observer = new MutationObserver(cleanSvg);
observer.observe(node, { childList: true, subtree: true });
return {
destroy() {
observer.disconnect();
}
};
}
</script>
<div class="modal-backdrop"
<div class="modal-backdrop fullscreen"
style="display: {show && logo ? 'flex' : 'none'}"
role="dialog"
aria-modal="true"
>
{#if logo}
<div class="modal-content">
<div class="modal-content fullscreen-modal">
<div class="modal-header">
<h2>{logo.name}</h2>
<button class="close-btn" on:click={close} aria-label="Close preview">×</button>
</div>
<div class="modal-body">
<div class="preview-container"
role="button"
tabindex="0"
aria-label="Close preview"
on:click={close}
on:keydown={(e) => (e.key === 'Enter' || e.key === ' ') && close()}
style="cursor:pointer;"
<div class="modal-body fullscreen-body">
<div class="preview-container fullscreen-preview"
role="img"
aria-label={logo.name}
>
<div class="preview-media-wrapper" use:removeSvgSize>
{#if isSvgLogo(logo)}
<InlineSvg
path={logo.path}
@@ -75,7 +121,8 @@ $: getLogoThemeColor = logo => getDefaultLogoColor(logo.colors, theme);
<img src={logo.path} alt={logo.name} />
{/if}
</div>
<div class="logo-details">
</div>
<div class="logo-details fullscreen-details">
{#if isSvgLogo(logo) && logo.colors}
<div class="color-switcher-preview">
<span
@@ -125,27 +172,133 @@ $: getLogoThemeColor = logo => getDefaultLogoColor(logo.colors, theme);
</div>
<style>
/* Only component-specific styles that don't exist in global.css */
.preview-container {
min-height: 200px;
max-height: 60vh;
max-width: 100%;
padding: 2rem;
.modal-backdrop.fullscreen {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
width: 100vw;
height: 100vh;
background: rgba(0,0,0,0.95);
z-index: 2000;
display: flex;
align-items: center;
justify-content: center;
overflow: auto;
}
.preview-container img {
max-height: 60vh;
.modal-content.fullscreen-modal {
width: 100vw;
height: 100vh;
max-width: 100vw;
max-height: 100vh;
border-radius: 0;
box-shadow: none;
background: transparent;
display: flex;
flex-direction: column;
padding: 0;
border: none;
}
.modal-body img {
max-width: 100%;
margin-bottom: 1rem;
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 2rem 2.5rem 1rem 2.5rem;
background: transparent;
color: var(--color-text);
z-index: 2;
}
.modal-header h2 {
font-size: 2.2rem;
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;
}
.modal-body.fullscreen-body {
flex: 1 1 auto;
display: flex;
flex-direction: row;
align-items: stretch;
justify-content: center;
width: 100vw;
height: calc(100vh - 4.5rem);
background: transparent;
padding: 0 2.5rem 2.5rem 2.5rem;
gap: 2.5rem;
}
.preview-container.fullscreen-preview {
flex: 2 1 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
background: transparent;
height: 100%;
width: 100%;
overflow: auto;
}
.preview-media-wrapper {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.preview-media-wrapper img,
.preview-media-wrapper svg {
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
object-fit: contain;
display: block;
margin: 0;
}
.logo-details.fullscreen-details {
flex: 1 1 350px;
min-width: 320px;
max-width: 400px;
background: var(--color-card, #23272e);
color: var(--color-text);
border-radius: 12px;
padding: 2rem 2rem 1.5rem 2rem;
margin: 2rem 0 2rem 0;
box-shadow: 0 2px 16px 4px rgba(0,0,0,0.18);
overflow-y: auto;
align-self: center;
z-index: 1;
}
.logo-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
@media (max-width: 900px) {
.modal-body.fullscreen-body {
flex-direction: column;
align-items: stretch;
gap: 1.5rem;
padding: 0 0.5rem 0.5rem 0.5rem;
}
.logo-details.fullscreen-details {
margin: 0 auto 1.5rem auto;
max-width: 100vw;
min-width: 0;
width: 100%;
padding: 1.2rem 0.7rem 1rem 0.7rem;
}
.modal-header {
padding: 1.2rem 0.7rem 0.7rem 0.7rem;
}
}
</style>