From 0f058b518955dc4ed8f9eb797723b4939f1b78ec Mon Sep 17 00:00:00 2001 From: itsskaiya Date: Tue, 18 Nov 2025 16:08:56 -0500 Subject: [PATCH] Fixed image loading taking a few seconds to load content --- package.json | 2 +- scripts/renderer.js | 1200 ++++++++++++++++----------------- scripts/updateNotification.js | 2 +- 3 files changed, 585 insertions(+), 619 deletions(-) diff --git a/package.json b/package.json index 7ce28c0..a3f894d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "waifu-board", - "version": "v1.1.1", + "version": "v1.2.0", "description": "An image board app to store and browse your favorite waifus!", "main": "main.js", "scripts": { diff --git a/scripts/renderer.js b/scripts/renderer.js index 5d3935b..8acc568 100644 --- a/scripts/renderer.js +++ b/scripts/renderer.js @@ -1,619 +1,585 @@ 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 = `${source.name.substring( - 0, - 2 - )}`; - 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 = - '

No tags available for this image.

'; - 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 = - '

No results found. Please try another search term.

'; - } - - 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 = - '

Found results, but none had valid images.

'; - } - 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 = - '

Loading favorites...

'; - currentFavorites = await window.api.getFavorites(); - - if (currentFavorites.length === 0) { - applyLayoutToGallery(favoritesGallery, currentLayout); - favoritesGallery.innerHTML = - '

You haven\'t saved any favorites yet.

'; - 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 = - '

You haven\'t saved any favorites yet.

'; - } - }, 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 = ` - - `; - 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 = ` - - `; - 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 = ` - - `; - 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'); +  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 = `${source.name.substring( +            0, +            2 +          )}`; +          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 = +        '

No tags available for this image.

'; +      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 = +          '

No results found. Please try another search term.

'; +      } + +      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 = +          '

Found results, but none had valid images.

'; +      } +      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 = +      '

Loading favorites...

'; +    currentFavorites = await window.api.getFavorites(); + +    if (currentFavorites.length === 0) { +      applyLayoutToGallery(favoritesGallery, currentLayout); +      favoritesGallery.innerHTML = +        '

You haven\'t saved any favorites yet.

'; +      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 = +              '

You haven\'t saved any favorites yet.

'; +          } +        }, 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; +  } + +  // ------------------------------------------------------------------ +  // DELETED: The source-specific getFullImageUrl function is removed. +  // The extensions must now provide the correct URLs in the search result. +  // ------------------------------------------------------------------ + + +  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 = ` +        +      `; +    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 = ` +        +      `; +    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 = ` +        +      `; +    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'); }); \ No newline at end of file diff --git a/scripts/updateNotification.js b/scripts/updateNotification.js index 9cdba8c..8036341 100644 --- a/scripts/updateNotification.js +++ b/scripts/updateNotification.js @@ -1,6 +1,6 @@ const GITHUB_OWNER = 'ItsSkaiya'; const GITHUB_REPO = 'WaifuBoard'; -const CURRENT_VERSION = 'v1.1.1'; +const CURRENT_VERSION = 'v1.2.0'; let currentVersionDisplay; let latestVersionDisplay;