Files
WaifuBoard/scripts/renderer.js
itsskaiya 7f01bace06 Organized all script files
Removed useless things from all files
Added an update notification if there is an update
Updated the directory for the app icon
2025-11-18 13:23:50 -05:00

619 lines
20 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
const browseButton = document.getElementById('browse-button');
const favoritesButton = document.getElementById('favorites-button');
const settingsButton = document.getElementById('settings-button');
const browsePage = document.getElementById('browse-page');
const pageTitle = document.getElementById('page-title');
const headerContext = document.getElementById('header-context');
const searchIconButton = document.getElementById('search-icon-button');
const searchModal = document.getElementById('search-modal');
const searchCloseButton = document.getElementById('search-close-button');
const searchInput = document.getElementById('search-input');
const searchButton = document.getElementById('search-button');
const sourceList = document.getElementById('source-list');
const contentGallery = document.getElementById('content-gallery');
const favoritesGallery = document.getElementById('favorites-gallery');
const loadingSpinner = document.getElementById('loading-spinner');
const infiniteLoadingSpinner = document.getElementById(
'infinite-loading-spinner'
);
const messageBar = document.getElementById('message-bar');
const galleryPlaceholder = document.getElementById('gallery-placeholder');
const layoutRadios = document.querySelectorAll('input[name="layout"]');
const tagInfoModal = document.getElementById('tag-info-modal');
const tagInfoCloseButton = document.getElementById(
'tag-info-close-button'
);
const tagInfoContent = document.getElementById('tag-info-content');
let currentFavorites = [];
let currentSource = '';
let currentQuery = '';
let currentLayout = 'scroll';
let currentPage = 1;
let isLoading = false;
let hasNextPage = true;
async function populateSources() {
console.log('Requesting sources from main process...');
const sources = await window.api.getSources();
sourceList.innerHTML = '';
if (sources && sources.length > 0) {
sources.forEach((source) => {
const button = document.createElement('button');
button.className =
'source-button w-12 h-12 flex items-center justify-center rounded-xl text-gray-400 hover:bg-gray-700 hover:text-white transition-all duration-200';
button.dataset.source = source.name;
button.title = source.name;
const favicon = document.createElement('img');
favicon.className = 'w-8 h-8 rounded';
let mainDomain = source.url;
try {
const hostname = new URL(source.url).hostname;
const parts = hostname.split('.');
if (parts.length > 2 && ['api', 'www'].includes(parts[0])) {
mainDomain = parts.slice(1).join('.');
} else {
mainDomain = hostname;
}
} catch (e) {
console.warn(`Could not parse domain from ${source.url}:`, e);
mainDomain = source.name;
}
favicon.src = `https://www.google.com/s2/favicons?domain=${mainDomain}&sz=32`;
favicon.alt = source.name;
favicon.onerror = () => {
button.innerHTML = `<span class="font-bold text-sm">${source.name.substring(
0,
2
)}</span>`;
favicon.remove();
};
button.appendChild(favicon);
sourceList.appendChild(button);
});
console.log('Sources populated:', sources);
if (sourceList.children.length > 0) {
const firstButton = sourceList.children[0];
firstButton.classList.add('active');
currentSource = firstButton.dataset.source;
updateHeader();
}
} else {
console.warn('No sources were loaded from the main process.');
}
}
sourceList.addEventListener('click', (e) => {
const button = e.target.closest('.source-button');
if (button) {
sourceList
.querySelectorAll('.source-button')
.forEach((btn) => btn.classList.remove('active'));
button.classList.add('active');
currentSource = button.dataset.source;
console.log('Source changed to:', currentSource);
updateHeader();
if (currentQuery) {
performSearch();
}
}
});
function showPage(pageId) {
document.querySelectorAll('.page').forEach((page) => {
page.classList.add('hidden');
});
document.querySelectorAll('.nav-button').forEach((tab) => {
tab.classList.remove('bg-indigo-600', 'text-white');
tab.classList.add('text-gray-400', 'hover:bg-gray-700');
});
const activePage = document.getElementById(pageId);
activePage.classList.remove('hidden');
let activeTab;
if (pageId === 'browse-page') {
activeTab = browseButton;
pageTitle.textContent = 'Browse';
updateHeader();
} else if (pageId === 'favorites-page') {
activeTab = favoritesButton;
pageTitle.textContent = 'Favorites';
headerContext.textContent = '';
loadFavorites();
} else if (pageId === 'settings-page') {
activeTab = settingsButton;
pageTitle.textContent = 'Settings';
headerContext.textContent = '';
}
activeTab.classList.add('bg-indigo-600', 'text-white');
activeTab.classList.remove('text-gray-400', 'hover:bg-gray-700');
}
browseButton.addEventListener('click', () => showPage('browse-page'));
favoritesButton.addEventListener('click', () => showPage('favorites-page'));
settingsButton.addEventListener('click', () => showPage('settings-page'));
searchIconButton.addEventListener('click', () => {
searchModal.classList.remove('hidden');
searchInput.focus();
searchInput.select();
});
searchCloseButton.addEventListener('click', () => {
searchModal.classList.add('hidden');
});
searchButton.addEventListener('click', () => {
performSearch();
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
searchModal.classList.add('hidden');
}
});
tagInfoCloseButton.addEventListener('click', () => {
tagInfoModal.classList.add('hidden');
});
tagInfoModal.addEventListener('click', (e) => {
if (e.target === tagInfoModal) {
tagInfoModal.classList.add('hidden');
}
});
function showTagModal(tags) {
tagInfoContent.innerHTML = '';
if (!tags || tags.length === 0) {
tagInfoContent.innerHTML =
'<p class="text-gray-400">No tags available for this image.</p>';
tagInfoModal.classList.remove('hidden');
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);
}
});
tagInfoContent.appendChild(fragment);
tagInfoModal.classList.remove('hidden');
}
function updateHeader() {
if (currentSource) {
headerContext.textContent = `Source: ${currentSource}`;
} else {
headerContext.textContent = 'No source selected';
}
}
async function performSearch() {
if (!currentSource) {
showMessage('Please select a source from the sidebar.', 'error');
return;
}
currentPage = 1;
hasNextPage = true;
isLoading = false;
currentQuery = searchInput.value.trim().replace(/[, ]+/g, ' ');
if (galleryPlaceholder) galleryPlaceholder.classList.add('hidden');
applyLayoutToGallery(contentGallery, currentLayout);
contentGallery.innerHTML = '';
updateHeader();
searchModal.classList.add('hidden');
loadMoreResults();
}
async function loadMoreResults() {
if (isLoading || !hasNextPage) {
return;
}
isLoading = true;
if (currentPage === 1) {
loadingSpinner.classList.remove('hidden');
} else {
infiniteLoadingSpinner.classList.remove('hidden');
}
const result = await window.api.search(
currentSource,
currentQuery,
currentPage
);
loadingSpinner.classList.add('hidden');
infiniteLoadingSpinner.classList.add('hidden');
if (
!result.success ||
!result.data.results ||
result.data.results.length === 0
) {
hasNextPage = false;
if (currentPage === 1) {
applyLayoutToGallery(contentGallery, currentLayout);
contentGallery.innerHTML =
'<p class="text-gray-400 text-center text-lg">No results found. Please try another search term.</p>';
}
isLoading = false;
return;
}
const validResults = result.data.results.filter((item) => item.image);
if (validResults.length === 0) {
hasNextPage = false;
if (currentPage === 1) {
applyLayoutToGallery(contentGallery, currentLayout);
contentGallery.innerHTML =
'<p class="text-gray-400 text-center text-lg">Found results, but none had valid images.</p>';
}
isLoading = false;
return;
}
const fragment = document.createDocumentFragment();
validResults.forEach((item) => {
const thumbnailUrl = item.image;
const displayUrl = item.sampleImageUrl || item.fullImageUrl || thumbnailUrl;
const card = createImageCard(
item.id.toString(),
item.tags,
displayUrl,
thumbnailUrl,
'browse'
);
fragment.appendChild(card);
});
contentGallery.appendChild(fragment);
hasNextPage = result.data.hasNextPage;
currentPage++;
isLoading = false;
}
browsePage.addEventListener('scroll', () => {
if (
browsePage.scrollTop + browsePage.clientHeight >=
browsePage.scrollHeight - 600
) {
loadMoreResults();
}
});
async function loadFavorites() {
applyLayoutToGallery(favoritesGallery, currentLayout);
favoritesGallery.innerHTML =
'<div class="text-center p-10"><p class="text-gray-400">Loading favorites...</p></div>';
currentFavorites = await window.api.getFavorites();
if (currentFavorites.length === 0) {
applyLayoutToGallery(favoritesGallery, currentLayout);
favoritesGallery.innerHTML =
'<p class="text-gray-400 text-center text-lg">You haven\'t saved any favorites yet.</p>';
return;
}
applyLayoutToGallery(favoritesGallery, currentLayout);
favoritesGallery.innerHTML = '';
const fragment = document.createDocumentFragment();
currentFavorites.forEach((fav) => {
const card = createImageCard(
fav.id,
fav.tags ? fav.tags.split(',') : [],
fav.image_url,
fav.thumbnail_url,
'fav'
);
fragment.appendChild(card);
});
favoritesGallery.appendChild(fragment);
}
async function handleAddFavorite(id, tags, imageUrl, thumbnailUrl) {
const safeTags = Array.isArray(tags) ? tags : [];
const title = safeTags.length > 0 ? safeTags[0] : 'Favorite';
const allTags = safeTags.join(',');
const result = await window.api.addFavorite({
id,
title,
imageUrl,
thumbnailUrl,
tags: allTags,
});
if (result.success) {
showMessage('Added to favorites!', 'success');
} else {
showMessage(result.error, 'error');
}
}
async function handleRemoveFavorite(id) {
const result = await window.api.removeFavorite(id);
if (result.success) {
showMessage('Removed from favorites.', 'success');
const cardToRemove = document.querySelector(
`#favorites-gallery [data-id='${id}']`
);
if (cardToRemove) {
cardToRemove.classList.add('opacity-0', 'scale-90');
setTimeout(() => {
cardToRemove.remove();
if (favoritesGallery.children.length === 0) {
applyLayoutToGallery(favoritesGallery, currentLayout);
favoritesGallery.innerHTML =
'<p class="text-gray-400 text-center text-lg">You haven\'t saved any favorites yet.</p>';
}
}, 300);
}
} else {
showMessage(result.error, 'error');
}
}
function createImageCard(id, tags, imageUrl, thumbnailUrl, type) {
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));
if (type === 'browse') {
buttonContainer.appendChild(
createAddFavoriteButton(id, safeTags, imageUrl, thumbnailUrl)
);
} else {
buttonContainer.appendChild(createRemoveFavoriteButton(id));
}
imageContainer.appendChild(buttonContainer);
return entry;
}
function getFullImageUrl(thumbnailUrl, source) {
if (!thumbnailUrl) return '';
try {
if (source === 'WaifuPics') {
return thumbnailUrl;
}
if (source === 'Rule34' && thumbnailUrl.includes('thumbnail_')) {
return thumbnailUrl
.replace('/thumbnails/', '/images/')
.replace('thumbnail_', '');
}
if (source === 'Gelbooru' && thumbnailUrl.includes('/thumbnails/')) {
return thumbnailUrl
.replace('/thumbnails/', '/images/')
.replace('thumbnail_', '');
}
if (source === 'Safebooru' && thumbnailUrl.includes('/thumbnails/')) {
return thumbnailUrl
.replace('/thumbnails/', '/images/')
.replace('thumbnail_', '');
}
if (thumbnailUrl.includes('/thumbnails/')) {
return thumbnailUrl
.replace('/thumbnails/', '/images/')
.replace('thumbnail_', '');
}
} catch (e) {
console.error('Error parsing full image URL:', e);
}
return thumbnailUrl;
}
function createInfoButton(safeTags) {
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();
showTagModal(safeTags);
};
return button;
}
function createAddFavoriteButton(id, safeTags, imageUrl, thumbnailUrl) {
const button = document.createElement('button');
button.title = 'Add to Favorites';
button.className =
'p-2 rounded-full bg-black/50 text-white hover:bg-indigo-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.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.31h5.518a.562.562 0 01.31.95l-4.203 3.03a.563.563 0 00-.182.53l1.501 4.87a.562.562 0 01-.82.624l-4.204-3.03a.563.563 0 00-.576 0l-4.204 3.03a.562.562 0 01-.82-.624l1.501-4.87a.563.563 0 00-.182-.53L2.498 9.87a.562.562 0 01.31-.95h5.518a.563.563 0 00.475-.31L11.48 3.5z" />
</svg>`;
button.onclick = (e) => {
e.stopPropagation();
handleAddFavorite(id, safeTags, imageUrl, thumbnailUrl);
};
return button;
}
function createRemoveFavoriteButton(id) {
const button = document.createElement('button');
button.title = 'Remove from Favorites';
button.className =
'p-2 rounded-full bg-black/50 text-white hover:bg-red-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="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12.578 0a48.108 48.108 0 01-3.478-.397m15.408 0l-2.147-2.147A1.125 1.125 0 0016.34 3H7.66a1.125 1.125 0 00-.795.325L4.772 5.79m14.456 0l-2.29-2.29a1.125 1.125 0 00-.795-.324H8.455a1.125 1.125 0 00-.795.324L5.37 5.79m13.84 0L20.25 7.5" />
</svg>`;
button.onclick = (e) => {
e.stopPropagation();
handleRemoveFavorite(id);
};
return button;
}
function showMessage(message, type = 'success') {
if (!messageBar) return;
messageBar.textContent = message;
if (type === 'error') {
messageBar.classList.remove('bg-green-600');
messageBar.classList.add('bg-red-600');
} else {
messageBar.classList.remove('bg-red-600');
messageBar.classList.add('bg-green-600');
}
messageBar.classList.remove('hidden', 'translate-y-16');
setTimeout(() => {
messageBar.classList.add('hidden', 'translate-y-16');
}, 3000);
}
function loadSettings() {
const savedLayout = localStorage.getItem('waifuBoardLayout') || 'scroll';
currentLayout = savedLayout;
const savedRadio = document.querySelector(
`input[name="layout"][value="${savedLayout}"]`
);
if (savedRadio) {
savedRadio.checked = true;
} else {
document.getElementById('layout-scroll').checked = true;
currentLayout = 'scroll';
localStorage.setItem('waifuBoardLayout', 'scroll');
}
}
function handleLayoutChange(e) {
const newLayout = e.target.value;
localStorage.setItem('waifuBoardLayout', newLayout);
currentLayout = newLayout;
console.log('Layout changed to:', newLayout);
if (browsePage.classList.contains('hidden')) {
loadFavorites();
} else {
if (currentQuery) {
performSearch();
} else {
applyLayoutToGallery(contentGallery, currentLayout);
}
}
}
function applyLayoutToGallery(galleryElement, layout) {
galleryElement.className = 'p-4 w-full';
if (layout === 'scroll') {
galleryElement.classList.add('max-w-3xl', 'mx-auto', 'space-y-8');
} else if (layout === 'grid') {
galleryElement.classList.add('gallery-masonry');
} else if (layout === 'compact') {
galleryElement.classList.add('gallery-grid');
}
}
layoutRadios.forEach((radio) => {
radio.addEventListener('change', handleLayoutChange);
});
loadSettings();
populateSources();
showPage('browse-page');
});