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:
@@ -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);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user