mirror of
https://github.com/shadoll/sLogos.git
synced 2025-12-20 03:26:59 +00:00
Enhance logo preview functionality with URL anchor support and responsive SVG handling
This commit is contained in:
@@ -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">
|
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
|
<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
|
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 |
@@ -3,6 +3,7 @@
|
|||||||
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';
|
||||||
|
|
||||||
let viewMode = 'grid'; // 'grid' or 'list'
|
let viewMode = 'grid'; // 'grid' or 'list'
|
||||||
let searchQuery = '';
|
let searchQuery = '';
|
||||||
@@ -13,6 +14,8 @@
|
|||||||
let allTags = [];
|
let allTags = [];
|
||||||
let selectedTags = [];
|
let selectedTags = [];
|
||||||
let tagDropdownOpen = false;
|
let tagDropdownOpen = false;
|
||||||
|
let showModal = false;
|
||||||
|
let selectedLogo = null;
|
||||||
|
|
||||||
// Load logos from JSON file with cache busting
|
// Load logos from JSON file with cache busting
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
@@ -50,6 +53,9 @@
|
|||||||
mq.addEventListener('change', () => {
|
mq.addEventListener('change', () => {
|
||||||
if (theme === 'system') applyTheme();
|
if (theme === 'system') applyTheme();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Open preview if URL contains anchor
|
||||||
|
openLogoByAnchor(window.location.hash);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Make sure to apply theme whenever it changes
|
// Make sure to apply theme whenever it changes
|
||||||
@@ -191,6 +197,20 @@
|
|||||||
return allTags.find(t => t.text === text);
|
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
|
// Listen for outside click to close dropdown
|
||||||
$: if (tagDropdownOpen) {
|
$: if (tagDropdownOpen) {
|
||||||
window.addEventListener('click', closeDropdown);
|
window.addEventListener('click', closeDropdown);
|
||||||
@@ -220,6 +240,14 @@
|
|||||||
{filteredLogos}
|
{filteredLogos}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Preview
|
||||||
|
bind:show={showModal}
|
||||||
|
bind:logo={selectedLogo}
|
||||||
|
{theme}
|
||||||
|
{logos}
|
||||||
|
openLogoByAnchor={openLogoByAnchor}
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="logos-container">
|
<div class="logos-container">
|
||||||
{#if viewMode === 'grid'}
|
{#if viewMode === 'grid'}
|
||||||
<Grid
|
<Grid
|
||||||
@@ -227,12 +255,14 @@
|
|||||||
onCopy={copyUrl}
|
onCopy={copyUrl}
|
||||||
onDownload={downloadLogo}
|
onDownload={downloadLogo}
|
||||||
theme={effectiveTheme}
|
theme={effectiveTheme}
|
||||||
|
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)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,11 +5,18 @@
|
|||||||
|
|
||||||
export let show = false;
|
export let show = false;
|
||||||
export let logo = null;
|
export let logo = null;
|
||||||
|
export let theme;
|
||||||
|
export let logos = [];
|
||||||
|
export let openLogoByAnchor = () => {};
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
dispatch('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) {
|
function handleKeydown(event) {
|
||||||
@@ -22,9 +29,7 @@
|
|||||||
return logo && logo.format && logo.format.toLowerCase() === 'svg';
|
return logo && logo.format && logo.format.toLowerCase() === 'svg';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always use $theme directly, do not cache in a function
|
$: getLogoThemeColor = logo => getDefaultLogoColor(logo.colors, theme);
|
||||||
export let theme;
|
|
||||||
$: getLogoThemeColor = logo => getDefaultLogoColor(logo.colors, theme);
|
|
||||||
|
|
||||||
// Improved debug logging for color and theme
|
// Improved debug logging for color and theme
|
||||||
$: {
|
$: {
|
||||||
@@ -35,47 +40,89 @@ $: 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(() => {
|
onMount(() => {
|
||||||
document.addEventListener('keydown', handleKeydown);
|
document.addEventListener('keydown', handleKeydown);
|
||||||
|
if (window.location.hash.startsWith('#preview-')) {
|
||||||
|
openLogoByAnchor(window.location.hash);
|
||||||
|
}
|
||||||
|
window.addEventListener('hashchange', onHashChange);
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
document.removeEventListener('keydown', handleKeydown);
|
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>
|
</script>
|
||||||
|
|
||||||
<div class="modal-backdrop"
|
<div class="modal-backdrop fullscreen"
|
||||||
style="display: {show && logo ? 'flex' : 'none'}"
|
style="display: {show && logo ? 'flex' : 'none'}"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
>
|
>
|
||||||
{#if logo}
|
{#if logo}
|
||||||
<div class="modal-content">
|
<div class="modal-content fullscreen-modal">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h2>{logo.name}</h2>
|
<h2>{logo.name}</h2>
|
||||||
<button class="close-btn" on:click={close} aria-label="Close preview">×</button>
|
<button class="close-btn" on:click={close} aria-label="Close preview">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body fullscreen-body">
|
||||||
<div class="preview-container"
|
<div class="preview-container fullscreen-preview"
|
||||||
role="button"
|
role="img"
|
||||||
tabindex="0"
|
aria-label={logo.name}
|
||||||
aria-label="Close preview"
|
|
||||||
on:click={close}
|
|
||||||
on:keydown={(e) => (e.key === 'Enter' || e.key === ' ') && close()}
|
|
||||||
style="cursor:pointer;"
|
|
||||||
>
|
>
|
||||||
{#if isSvgLogo(logo)}
|
<div class="preview-media-wrapper" use:removeSvgSize>
|
||||||
<InlineSvg
|
{#if isSvgLogo(logo)}
|
||||||
path={logo.path}
|
<InlineSvg
|
||||||
color={logo.colors ? (logo._activeColor || getLogoThemeColor(logo)) : undefined}
|
path={logo.path}
|
||||||
colorConfig={logo.colors ? logo.colorConfig : undefined}
|
color={logo.colors ? (logo._activeColor || getLogoThemeColor(logo)) : undefined}
|
||||||
alt={logo.name}
|
colorConfig={logo.colors ? logo.colorConfig : undefined}
|
||||||
/>
|
alt={logo.name}
|
||||||
{:else}
|
/>
|
||||||
<img src={logo.path} alt={logo.name} />
|
{:else}
|
||||||
{/if}
|
<img src={logo.path} alt={logo.name} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="logo-details">
|
<div class="logo-details fullscreen-details">
|
||||||
{#if isSvgLogo(logo) && logo.colors}
|
{#if isSvgLogo(logo) && logo.colors}
|
||||||
<div class="color-switcher-preview">
|
<div class="color-switcher-preview">
|
||||||
<span
|
<span
|
||||||
@@ -125,27 +172,133 @@ $: getLogoThemeColor = logo => getDefaultLogoColor(logo.colors, theme);
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Only component-specific styles that don't exist in global.css */
|
.modal-backdrop.fullscreen {
|
||||||
.preview-container {
|
position: fixed;
|
||||||
min-height: 200px;
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
max-height: 60vh;
|
width: 100vw;
|
||||||
max-width: 100%;
|
height: 100vh;
|
||||||
padding: 2rem;
|
background: rgba(0,0,0,0.95);
|
||||||
|
z-index: 2000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
.modal-content.fullscreen-modal {
|
||||||
.preview-container img {
|
width: 100vw;
|
||||||
max-height: 60vh;
|
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-header {
|
||||||
.modal-body img {
|
display: flex;
|
||||||
max-width: 100%;
|
justify-content: space-between;
|
||||||
margin-bottom: 1rem;
|
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 {
|
.logo-tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.5rem;
|
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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user