diff --git a/src/content/image-handler.js b/src/content/image-handler.js index 80273b7..7bdbe41 100644 --- a/src/content/image-handler.js +++ b/src/content/image-handler.js @@ -1,12 +1,20 @@ -let masonryTimer = null; +let masonryFrameRunning = false; function scheduleMasonryRelayout(grid) { - clearTimeout(masonryTimer); - masonryTimer = setTimeout(() => { + if (masonryFrameRunning) return; + masonryFrameRunning = true; + + requestAnimationFrame(() => { relayoutMasonry(grid); - }, 50); + masonryFrameRunning = false; + }); } -export function createImageCard(id, tags, imageUrl, thumbnailUrl, type, options = {}) { +export function triggerMasonryRelayout(gallery) { + if (!gallery) return; + setTimeout(() => relayoutMasonry(gallery), 350); +} + +export function createImageCard(id, tags, imageUrl, thumbnailUrl, options = {}) { const { showMessage, showTagModal, @@ -16,7 +24,7 @@ export function createImageCard(id, tags, imageUrl, thumbnailUrl, type, options const card = document.createElement('div'); card.className = 'image-entry loading newly-added'; card.dataset.id = id; - card.dataset.type = type; + card.dataset.type = 'image'; card.title = tags.join(', '); const img = document.createElement('img'); @@ -28,10 +36,10 @@ export function createImageCard(id, tags, imageUrl, thumbnailUrl, type, options img.classList.add('loaded'); card.classList.remove('loading'); - if (type !== 'book') { - setTimeout(() => resizeMasonryItem(card), 20); + setTimeout(() => { + resizeMasonryItem(card); scheduleMasonryRelayout(card.parentElement); - } + }, 100); setTimeout(() => { card.classList.remove('newly-added'); @@ -45,23 +53,75 @@ export function createImageCard(id, tags, imageUrl, thumbnailUrl, type, options card.appendChild(img); - if (type === 'book') { - const readOverlay = document.createElement('div'); - readOverlay.className = 'book-read-overlay'; - readOverlay.innerHTML = ` - - Click To Read - `; - card.appendChild(readOverlay); - return card; - } + const buttonsOverlay = createButtonsOverlay( + id, imageUrl, thumbnailUrl, tags, + card, favoriteIds, showMessage, showTagModal, options + ); + card.appendChild(buttonsOverlay); + return card; +} + +export function createBookCard(id, tags, imageUrl, thumbnailUrl, title, options = {}) { + const card = document.createElement('div'); + card.className = 'image-entry loading newly-added'; + card.dataset.id = id; + card.dataset.type = 'book'; + card.dataset.title = title || 'Unknown'; + card.title = tags.join(', '); + + const img = document.createElement('img'); + img.src = thumbnailUrl || imageUrl; + img.loading = 'lazy'; + img.alt = title || tags.join(' '); + + img.onload = () => { + img.classList.add('loaded'); + card.classList.remove('loading'); + + setTimeout(() => card.classList.remove('newly-added'), 400); + }; + + img.onerror = () => { + card.classList.remove('loading'); + img.classList.add('loaded'); + }; + + card.appendChild(img); + + const readOverlay = document.createElement('div'); + readOverlay.className = 'book-read-overlay'; + readOverlay.innerHTML = ` + + Click To Read + `; + card.appendChild(readOverlay); + + return card; +} + +function createButtonsOverlay(id, imageUrl, thumbnailUrl, tags, card, favoriteIds, showMessage, showTagModal, options) { const buttonsOverlay = document.createElement('div'); buttonsOverlay.className = 'image-buttons'; + const tagBtn = document.createElement('button'); + tagBtn.innerHTML = ``; + tagBtn.title = "View Tags"; + tagBtn.onclick = e => { + e.stopPropagation(); + showTagModal(tags); + }; + const favBtn = createFavoriteButton(id, imageUrl, thumbnailUrl, tags, card, favoriteIds, showMessage, options); + buttonsOverlay.appendChild(tagBtn); + buttonsOverlay.appendChild(favBtn); + + return buttonsOverlay; +} + +function createFavoriteButton(id, imageUrl, thumbnailUrl, tags, card, favoriteIds, showMessage, options) { const favBtn = document.createElement('button'); favBtn.className = 'heart-button'; favBtn.dataset.id = id; @@ -69,7 +129,7 @@ export function createImageCard(id, tags, imageUrl, thumbnailUrl, type, options const isFavorited = favoriteIds.has(String(id)); updateHeartIcon(favBtn, isFavorited); - favBtn.onclick = async (e) => { + favBtn.onclick = async e => { e.stopPropagation(); e.preventDefault(); @@ -87,81 +147,80 @@ export function createImageCard(id, tags, imageUrl, thumbnailUrl, type, options options.applyLayoutToGallery(options.favoritesGallery, options.currentLayout); } } - } else { - showMessage('Failed to remove favorite', 'error'); - } + } else showMessage('Failed to remove favorite', 'error'); } else { const favoriteData = { id: String(id), image_url: imageUrl, thumbnail_url: thumbnailUrl, tags: tags.join(','), - title: card.title || 'Unknown' + title: card.title || card.dataset.title || 'Unknown' }; const success = await window.api.addFavorite(favoriteData); if (success) { favoriteIds.add(String(id)); updateHeartIcon(favBtn, true); showMessage('Added to favorites', 'success'); - } else { - showMessage('Failed to save favorite', 'error'); - } + } else showMessage('Failed to save favorite', 'error'); } }; - const tagBtn = document.createElement('button'); - tagBtn.innerHTML = ``; - tagBtn.title = "View Tags"; - tagBtn.onclick = (e) => { - e.stopPropagation(); - showTagModal(tags); - }; + return favBtn; +} - buttonsOverlay.appendChild(tagBtn); - buttonsOverlay.appendChild(favBtn); - card.appendChild(buttonsOverlay); +function updateHeartIcon(btn, isFavorited) { + btn.innerHTML = isFavorited + ? `` + : ``; + btn.title = isFavorited ? "Remove from Favorites" : "Add to Favorites"; +} - return card; + +function getSidebarClosedWidth() { + const s = document.querySelector('.sidebar'); + if (!s) return 0; + + return parseFloat( + getComputedStyle(document.documentElement) + .getPropertyValue('--sidebar-width-collapsed') + ); } function resizeMasonryItem(item) { if (item.dataset.type === 'book') return; const grid = item.parentElement; - const rowHeight = parseInt(getComputedStyle(grid).gridAutoRows); - const rowGap = parseInt(getComputedStyle(grid).gap); - + if (!grid) return; const img = item.querySelector("img"); + if (!img || !img.complete || img.naturalWidth === 0) return; - if (!img) return; + const gridCurrent = grid.getBoundingClientRect().width; + const sidebar = document.querySelector(".sidebar"); + const sidebarExpanded = parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--sidebar-width-expanded")); + const sidebarCollapsed = parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--sidebar-width-collapsed")); - if (!img.complete || img.naturalWidth === 0) { - return; - } + const gridClosedWidth = gridCurrent - (sidebarExpanded - sidebarCollapsed); - const width = img.clientWidth; - const imageHeight = (img.naturalHeight / img.naturalWidth) * width; + const currentItemWidth = item.getBoundingClientRect().width; + const simulatedWidth = (currentItemWidth / gridCurrent) * gridClosedWidth; - const extra = 2; - const totalHeight = imageHeight + extra; + const gridStyles = getComputedStyle(grid); + const rowHeight = parseInt(gridStyles.gridAutoRows); + const rowGap = parseInt(gridStyles.gap); + + const imageHeight = (img.naturalHeight / img.naturalWidth) * simulatedWidth; + const totalHeight = imageHeight; const rowSpan = Math.ceil((totalHeight + rowGap) / (rowHeight + rowGap)); - item.style.gridRowEnd = "span " + rowSpan; - item.style.height = totalHeight + "px"; + item.style.gridRowEnd = `span ${rowSpan}`; + item.style.height = "auto"; } -function updateHeartIcon(btn, isFavorited) { - if (isFavorited) { - btn.innerHTML = ``; - btn.title = "Remove from Favorites"; - } else { - btn.innerHTML = ``; - btn.title = "Add to Favorites"; - } -} + function relayoutMasonry(grid) { - const items = grid.querySelectorAll('.image-entry:not([data-type="book"])'); + if (!grid) return; + const items = grid.querySelectorAll('.image-entry[data-type="image"]'); items.forEach(item => resizeMasonryItem(item)); } @@ -176,4 +235,4 @@ export function populateTagModal(container, tags) { span.textContent = tag; container.appendChild(span); }); -} \ No newline at end of file +} diff --git a/src/renderer.js b/src/renderer.js index e0c89be..05cd6ce 100644 --- a/src/renderer.js +++ b/src/renderer.js @@ -2,7 +2,7 @@ import { populateSources } from './extensions/load-extensions.js'; import { setupGlobalKeybinds } from './utils/keybinds.js'; import { getDomElements } from './utils/dom-loader.js'; import { performSearch, loadMoreResults } from './modules/search-handler.js'; -import { createImageCard, populateTagModal } from './content/image-handler.js'; +import { createImageCard, createBookCard, populateTagModal, triggerMasonryRelayout } from './content/image-handler.js'; import { showMessage as uiShowMessage } from './modules/ui-utils.js'; import { applyLayoutToGallery } from './modules/layout-manager.js'; @@ -23,12 +23,38 @@ document.addEventListener('DOMContentLoaded', async () => { } } catch (e) { console.error(e); } - function showMessage(msg, type = 'success') { if (domRefs.messageBar) uiShowMessage(domRefs.messageBar, msg, type); } - function showTagModal(tags) { if (domRefs.tagInfoContent) { populateTagModal(domRefs.tagInfoContent, tags); domRefs.tagInfoModal.classList.remove('hidden'); } } - function localCreateImageCard(id, tags, img, thumb, type) { - return createImageCard(id, tags, img, thumb, type, { currentLayout, showMessage, showTagModal, applyLayoutToGallery, favoritesGallery: document.getElementById('favorites-gallery'), favoriteIds }); + function showMessage(msg, type = 'success') { + if (domRefs.messageBar) uiShowMessage(domRefs.messageBar, msg, type); + } + + function showTagModal(tags) { + if (domRefs.tagInfoContent) { + populateTagModal(domRefs.tagInfoContent, tags); + domRefs.tagInfoModal.classList.remove('hidden'); + } + } + + function localCreateImageCard(id, tags, img, thumb, type) { + const sharedOptions = { + currentLayout, + showMessage, + showTagModal, + applyLayoutToGallery, + favoritesGallery: document.getElementById('favorites-gallery'), + favoriteIds + }; + + if (type === 'book') { + const title = tags[0] || 'Unknown'; + return createBookCard(id, tags, img, thumb, title, sharedOptions); + } else { + return createImageCard(id, tags, img, thumb, sharedOptions); + } + } + + function updateHeader() { + if (domRefs.headerContext) domRefs.headerContext.classList.add('hidden'); } - function updateHeader() { if (domRefs.headerContext) domRefs.headerContext.classList.add('hidden'); } const callbacks = { showMessage, applyLayoutToGallery, updateHeader, createImageCard: localCreateImageCard }; @@ -290,13 +316,16 @@ document.addEventListener('DOMContentLoaded', async () => { const favGallery = document.getElementById('favorites-gallery'); const rawFavorites = await window.api.getFavorites(); favGallery.innerHTML = ''; - if (!rawFavorites || rawFavorites.length === 0) favGallery.innerHTML = '
No favorites found.
No favorites found.