Updated headless browser (Should be as fast as it was before)

Added in Book Boards! (NEW!)
Updated rendering logic
Updated search logic
Updated extension loading logic
Updated image handling logic
This commit is contained in:
2025-11-22 10:55:27 -05:00
parent dca07a26f8
commit 652db0586b
13 changed files with 975 additions and 391 deletions

View File

@@ -1,115 +1,123 @@
import { createAddFavoriteButton, createRemoveFavoriteButton } from '../favorites/favorites-handler.js';
export function createImageCard(id, tags, imageUrl, thumbnailUrl, type, options = {}) {
const {
showMessage,
showTagModal,
favoriteIds = new Set()
} = options;
const card = document.createElement('div');
card.className = 'image-entry';
card.dataset.id = id;
card.dataset.type = type;
card.title = tags.join(', ');
const img = document.createElement('img');
img.src = thumbnailUrl || imageUrl;
img.loading = 'lazy';
img.alt = tags.join(' ');
img.onload = () => img.classList.add('loaded');
card.appendChild(img);
if (type === 'book') {
const readOverlay = document.createElement('div');
readOverlay.className = 'book-read-overlay';
readOverlay.innerHTML = `
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
</svg>
<span>Click To Read</span>
`;
card.appendChild(readOverlay);
return card;
}
const buttonsOverlay = document.createElement('div');
buttonsOverlay.className = 'image-buttons';
const favBtn = document.createElement('button');
favBtn.className = 'heart-button';
favBtn.dataset.id = id;
const isFavorited = favoriteIds.has(String(id));
updateHeartIcon(favBtn, isFavorited);
favBtn.onclick = async (e) => {
e.stopPropagation();
e.preventDefault();
const currentlyFavorited = favoriteIds.has(String(id));
if (currentlyFavorited) {
const success = await window.api.removeFavorite(id);
if (success) {
favoriteIds.delete(String(id));
updateHeartIcon(favBtn, false);
showMessage('Removed from favorites', 'success');
if (window.location.pathname.includes('favorites.html')) {
card.remove();
if (options.applyLayoutToGallery && options.favoritesGallery) {
options.applyLayoutToGallery(options.favoritesGallery, options.currentLayout);
}
}
} 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'
};
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');
}
}
};
const tagBtn = document.createElement('button');
tagBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path><line x1="7" y1="7" x2="7.01" y2="7"></line></svg>`;
tagBtn.title = "View Tags";
tagBtn.onclick = (e) => {
e.stopPropagation();
showTagModal(tags);
};
buttonsOverlay.appendChild(tagBtn);
buttonsOverlay.appendChild(favBtn);
card.appendChild(buttonsOverlay);
return card;
}
function updateHeartIcon(btn, isFavorited) {
if (isFavorited) {
btn.innerHTML = `<svg viewBox="0 0 24 24" fill="#ef4444" stroke="#ef4444" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>`;
btn.title = "Remove from Favorites";
} else {
btn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>`;
btn.title = "Add to Favorites";
}
}
export function populateTagModal(container, tags) {
container.innerHTML = '';
if (!tags || tags.length === 0) {
container.innerHTML =
'<p class="text-gray-400">No tags available for this image.</p>';
return;
}
const fragment = document.createDocumentFragment();
tags.forEach((tag) => {
if (tag) {
const tagPill = document.createElement('span');
tagPill.className =
'px-2.5 py-1 bg-gray-700 text-gray-300 text-xs font-medium rounded-full';
tagPill.textContent = tag.replace(/_/g, ' ');
fragment.appendChild(tagPill);
container.innerHTML = '';
if (!tags || tags.length === 0) {
container.innerHTML = '<p style="color:var(--text-tertiary)">No tags available.</p>';
return;
}
});
container.appendChild(fragment);
}
function createInfoButton(safeTags, showTagModalCallback) {
const button = document.createElement('button');
button.title = 'Show Info';
button.className =
'p-2 rounded-full bg-black/50 text-white hover:bg-blue-600 backdrop-blur-sm transition-colors';
button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" />
</svg>`;
button.onclick = (e) => {
e.stopPropagation();
showTagModalCallback(safeTags);
};
return button;
}
export function createImageCard(id, tags, imageUrl, thumbnailUrl, type, context) {
const {
currentLayout,
showMessage,
showTagModal,
applyLayoutToGallery,
favoritesGallery
} = context;
const safeTags = Array.isArray(tags) ? tags : [];
const entry = document.createElement('div');
entry.dataset.id = id;
entry.className = `image-entry group relative bg-gray-800 rounded-lg shadow-lg overflow-hidden transition-all duration-300`;
if (currentLayout === 'compact') {
entry.classList.add('aspect-square');
}
const imageContainer = document.createElement('div');
imageContainer.className = 'w-full bg-gray-700 animate-pulse relative';
if (currentLayout === 'compact') {
imageContainer.classList.add('h-full');
} else {
imageContainer.classList.add('min-h-[200px]');
}
entry.appendChild(imageContainer);
const img = document.createElement('img');
img.src = imageUrl;
img.alt = safeTags.join(', ');
img.className = 'w-full h-auto object-contain bg-gray-900 opacity-0';
img.loading = 'lazy';
img.referrerPolicy = 'no-referrer';
if (currentLayout === 'compact') {
img.className = 'w-full h-full object-cover bg-gray-900 opacity-0';
}
img.onload = () => {
imageContainer.classList.remove('animate-pulse', 'bg-gray-700');
img.classList.remove('opacity-0');
img.classList.add('transition-opacity', 'duration-500');
};
img.onerror = () => {
console.warn(`Failed to load full image: ${imageUrl}. Falling back to thumbnail.`);
img.src = thumbnailUrl;
imageContainer.classList.remove('animate-pulse', 'bg-gray-700');
img.classList.remove('opacity-0');
img.classList.add('transition-opacity', 'duration-500');
img.onerror = null;
};
imageContainer.appendChild(img);
const buttonContainer = document.createElement('div');
buttonContainer.className =
'image-buttons absolute top-3 right-3 flex flex-col space-y-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200';
buttonContainer.appendChild(createInfoButton(safeTags, showTagModal));
if (type === 'browse') {
buttonContainer.appendChild(
createAddFavoriteButton(id, safeTags, imageUrl, thumbnailUrl, showMessage)
);
} else {
buttonContainer.appendChild(
createRemoveFavoriteButton(id, favoritesGallery, showMessage, applyLayoutToGallery, currentLayout)
);
}
imageContainer.appendChild(buttonContainer);
return entry;
tags.forEach(tag => {
const span = document.createElement('span');
span.textContent = tag;
container.appendChild(span);
});
}