import React, { createContext, useContext, useState, useCallback, useEffect, } from "react"; const STORAGE_KEYS = { FAVORITES: "ai-image-favorites", SEARCH_HISTORY: "ai-image-search-history", }; const defaultFilters = () => ({ orientation: "", size: "", license: "", rating: "", color: "", imageType: "", order: "", sort: "", category: "", source: "", mediaType: "", }); const GeneratorContext = createContext(null); function readViewMode() { try { // Read from backend settings const config = typeof AI_IMAGE_AdminConfig !== "undefined" ? AI_IMAGE_AdminConfig : {}; const defaultMode = config.general_settings?.default_view_mode || "grid"; return defaultMode === "list" ? "list" : "grid"; } catch { return "grid"; } } function readItemsPerPage() { try { const config = typeof AI_IMAGE_AdminConfig !== "undefined" ? AI_IMAGE_AdminConfig : {}; const perPage = config.general_settings?.items_per_page; if (typeof perPage === "number" && perPage >= 20 && perPage <= 100) { return perPage; } return 30; } catch { return 30; } } function readThumbnailSize() { try { const config = typeof AI_IMAGE_AdminConfig !== "undefined" ? AI_IMAGE_AdminConfig : {}; const size = config.general_settings?.thumbnail_size; if (size === "small" || size === "medium" || size === "large") { return size; } return "medium"; } catch { return "medium"; } } function readLoadMoreMode() { try { const config = typeof AI_IMAGE_AdminConfig !== "undefined" ? AI_IMAGE_AdminConfig : {}; const mode = config.general_settings?.load_more_mode; if (mode === "auto" || mode === "manual") { return mode; } return "auto"; } catch { return "auto"; } } function readFavorites() { try { const raw = localStorage.getItem(STORAGE_KEYS.FAVORITES); if (!raw) return {}; const parsed = JSON.parse(raw); return typeof parsed === "object" && parsed !== null ? parsed : {}; } catch { return {}; } } function readSearchHistory() { try { const raw = localStorage.getItem(STORAGE_KEYS.SEARCH_HISTORY); if (!raw) return {}; const parsed = JSON.parse(raw); return typeof parsed === "object" && parsed !== null ? parsed : {}; } catch { return {}; } } /** Keep favourites bounded so "Favorite all" cannot exhaust the storage quota. */ const MAX_FAVORITES_PER_PROVIDER = 200; /** * Trim each provider's favourites to the newest entries. * * Objects preserve insertion order for string keys, so the oldest entries are * the ones dropped. */ function capFavorites(data) { const capped = {}; for (const [providerId, items] of Object.entries(data || {})) { if (!items || typeof items !== "object" || Array.isArray(items)) continue; const entries = Object.entries(items); capped[providerId] = Object.fromEntries( entries.length > MAX_FAVORITES_PER_PROVIDER ? entries.slice(entries.length - MAX_FAVORITES_PER_PROVIDER) : entries ); } return capped; } function writeFavorites(data) { try { localStorage.setItem(STORAGE_KEYS.FAVORITES, JSON.stringify(capFavorites(data))); } catch (e) { // A quota failure means the in-memory state no longer matches what is // stored, so tell the user rather than failing silently on reload. console.error("GeneratorContext: could not write favorites", e); window.dispatchEvent( new CustomEvent("aiImageStorageFull", { detail: { store: "favorites" } }) ); } } function writeSearchHistory(byProvider) { try { const limited = {}; for (const [providerId, list] of Object.entries(byProvider)) { limited[providerId] = Array.isArray(list) ? list.slice(0, 10) : []; } localStorage.setItem(STORAGE_KEYS.SEARCH_HISTORY, JSON.stringify(limited)); } catch (e) { console.error("GeneratorContext: could not write search history", e); } } export function GeneratorProvider({ children }) { const [viewMode, setViewModeState] = useState(readViewMode); const [filters, setFiltersState] = useState({}); const [favorites, setFavoritesState] = useState(readFavorites); const [searchHistory, setSearchHistoryState] = useState(readSearchHistory); const [selectedImages, setSelectedImages] = useState(new Set()); const [selectedItemsData, setSelectedItemsData] = useState({}); const [selectionMode, setSelectionMode] = useState(false); const [previewImage, setPreviewImage] = useState(null); const [previewImageList, setPreviewImageList] = useState([]); const [isGenerating, setIsGenerating] = useState(false); // Read display settings from config (now as state so they can be updated) const [itemsPerPage, setItemsPerPage] = useState(readItemsPerPage); const [thumbnailSize, setThumbnailSize] = useState(readThumbnailSize); const [loadMoreMode, setLoadMoreMode] = useState(readLoadMoreMode); useEffect(() => { writeFavorites(favorites); }, [favorites]); useEffect(() => { writeSearchHistory(searchHistory); }, [searchHistory]); // Listen for settings updates useEffect(() => { const handleSettingsUpdate = (event) => { const updatedSettings = event.detail; if (updatedSettings) { // Update AI_IMAGE_AdminConfig if it exists if (typeof AI_IMAGE_AdminConfig !== "undefined" && AI_IMAGE_AdminConfig.general_settings) { if (updatedSettings.items_per_page !== undefined) { AI_IMAGE_AdminConfig.general_settings.items_per_page = updatedSettings.items_per_page; setItemsPerPage(updatedSettings.items_per_page); } if (updatedSettings.thumbnail_size !== undefined) { AI_IMAGE_AdminConfig.general_settings.thumbnail_size = updatedSettings.thumbnail_size; setThumbnailSize(updatedSettings.thumbnail_size); } if (updatedSettings.load_more_mode !== undefined) { AI_IMAGE_AdminConfig.general_settings.load_more_mode = updatedSettings.load_more_mode; setLoadMoreMode(updatedSettings.load_more_mode); } if (updatedSettings.default_view_mode !== undefined) { AI_IMAGE_AdminConfig.general_settings.default_view_mode = updatedSettings.default_view_mode; setViewModeState(updatedSettings.default_view_mode === "list" ? "list" : "grid"); } } } }; window.addEventListener('aiImageSettingsUpdated', handleSettingsUpdate); return () => window.removeEventListener('aiImageSettingsUpdated', handleSettingsUpdate); }, []); const setViewMode = useCallback((mode) => { setViewModeState((m) => (mode === "list" ? "list" : "grid")); }, []); const getFilters = useCallback((providerId) => { return { ...defaultFilters(), ...(filters[providerId] || {}), }; }, [filters]); const setFilter = useCallback((providerId, filterKey, value) => { setFiltersState((prev) => ({ ...prev, [providerId]: { ...defaultFilters(), ...(prev[providerId] || {}), [filterKey]: value, }, })); }, []); const clearFilters = useCallback((providerId) => { setFiltersState((prev) => { const next = { ...prev }; if (providerId) delete next[providerId]; else return {}; return next; }); }, []); const toggleFavorite = useCallback((providerId, imageId, imageData = {}) => { setFavoritesState((prev) => { const byProvider = { ...prev }; const obj = { ...(typeof byProvider[providerId] === "object" && byProvider[providerId] && !Array.isArray(byProvider[providerId]) ? byProvider[providerId] : {}) }; const exists = obj[imageId]; if (exists) delete obj[imageId]; else obj[imageId] = { id: imageId, ...imageData }; byProvider[providerId] = Object.keys(obj).length ? obj : undefined; if (!byProvider[providerId]) delete byProvider[providerId]; return byProvider; }); }, []); const isFavorite = useCallback( (providerId, imageId) => { const byProvider = favorites[providerId]; if (!byProvider || Array.isArray(byProvider)) return false; return Boolean(byProvider[imageId]); }, [favorites] ); const getFavoritesList = useCallback(() => { const list = []; for (const [providerId, items] of Object.entries(favorites)) { if (items && typeof items === "object" && !Array.isArray(items)) { Object.entries(items).forEach(([id, data]) => list.push({ providerId, imageId: id, ...data }) ); } } return list; }, [favorites]); const addSearchToHistory = useCallback((providerId, query) => { const q = (query || "").trim(); if (!q) return; setSearchHistoryState((prev) => { const list = [...(prev[providerId] || [])]; const filtered = list.filter((s) => s !== q); return { ...prev, [providerId]: [q, ...filtered].slice(0, 10) }; }); }, []); const getSearchHistory = useCallback( (providerId) => searchHistory[providerId] || [], [searchHistory] ); const clearSearchHistory = useCallback((providerId) => { setSearchHistoryState((prev) => { const next = { ...prev }; if (providerId) delete next[providerId]; else return {}; return next; }); }, []); const selectionKey = useCallback((providerId, imageId) => `${providerId}:${imageId}`, []); const toggleSelection = useCallback((providerId, imageId, itemData) => { const key = `${providerId}:${imageId}`; setSelectedImages((prev) => { const next = new Set(prev); const isRemoving = next.has(key); if (isRemoving) next.delete(key); else next.add(key); setSelectedItemsData((d) => { const nextData = { ...d }; if (isRemoving) delete nextData[key]; else if (itemData && (itemData.importUrl || itemData.imageSrc)) { nextData[key] = { importUrl: itemData.importUrl, imageSrc: itemData.imageSrc, imageAlt: itemData.imageAlt, author: itemData.author, previewUrl: itemData.previewUrl }; } return nextData; }); return next; }); }, []); const isSelected = useCallback( (providerId, imageId) => selectedImages.has(`${providerId}:${imageId}`), [selectedImages] ); const selectAll = useCallback((providerId, imageIds, imageData = {}) => { setSelectedImages((prev) => { const next = new Set(prev); (imageIds || []).forEach((id) => next.add(`${providerId}:${id}`)); return next; }); setSelectedItemsData((prev) => ({ ...prev, ...imageData })); }, []); const clearSelection = useCallback(() => { setSelectedImages(new Set()); setSelectedItemsData({}); setSelectionMode(false); }, []); const setSelectionModeOn = useCallback((on) => { setSelectionMode(Boolean(on)); if (!on) { setSelectedImages(new Set()); setSelectedItemsData({}); } }, []); const getSelectedUrls = useCallback(() => { const urls = []; selectedImages.forEach((key) => { const data = selectedItemsData[key]; if (data?.importUrl) urls.push(data.importUrl); }); return urls; }, [selectedImages, selectedItemsData]); const getSelectedItemsData = useCallback(() => { const items = []; selectedImages.forEach((key) => { const [providerId, imageId] = key.split(":"); const data = selectedItemsData[key]; if (data) items.push({ providerId, imageId, ...data }); }); return items; }, [selectedImages, selectedItemsData]); const getSelectedCount = useCallback(() => selectedImages.size, [selectedImages]); const getSelectedIdsByProvider = useCallback(() => { const byProvider = {}; selectedImages.forEach((key) => { const [providerId, imageId] = key.split(":"); if (!byProvider[providerId]) byProvider[providerId] = []; byProvider[providerId].push(imageId); }); return byProvider; }, [selectedImages]); const value = { viewMode, setViewMode, itemsPerPage, thumbnailSize, loadMoreMode, filters, getFilters, setFilter, clearFilters, favorites, toggleFavorite, isFavorite, getFavoritesList, searchHistory, addSearchToHistory, getSearchHistory, clearSearchHistory, selectedImages, selectionMode, setSelectionMode: setSelectionModeOn, toggleSelection, isSelected, selectAll, clearSelection, getSelectedCount, getSelectedIdsByProvider, getSelectedUrls, getSelectedItemsData, selectionKey, previewImage, setPreviewImage, previewImageList, setPreviewImageList, isGenerating, setIsGenerating, }; return ( {children} ); } export function useGenerator() { const context = useContext(GeneratorContext); if (!context) { throw new Error("useGenerator must be used within GeneratorProvider"); } return context; }