diff --git a/src/App.jsx b/src/App.jsx index 600c809d..83f6b1d2 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -8,10 +8,13 @@ import { loadSettings, moveSettings } from 'utils/settings'; import EventBus from 'utils/eventbus'; import variables from 'config/variables'; import { TranslationProvider } from 'contexts/TranslationContext'; -import { installDefaultPhotoPacks } from 'utils/marketplace/installDefaultPacks'; +import { registerAllHandlers } from 'utils/marketplace/registerHandlers'; +import { installDefaultPacks } from 'utils/marketplace/installDefaultPacks'; const useAppSetup = () => { useEffect(() => { + registerAllHandlers(); + const firstRun = localStorage.getItem('firstRun'); const stats = localStorage.getItem('stats'); @@ -22,7 +25,7 @@ const useAppSetup = () => { loadSettings(); - installDefaultPhotoPacks(); + installDefaultPacks(); const refreshHandler = (data) => { if (data === 'other' || data === 'greeting' || data === 'clock' || data === 'quote') { diff --git a/src/config/defaultPacks.json b/src/config/defaultPacks.json new file mode 100644 index 00000000..513ffd42 --- /dev/null +++ b/src/config/defaultPacks.json @@ -0,0 +1,9 @@ +{ + "photos": [ + { "id": "d58909e759ec", "enabled": true }, + { "id": "c0094361594f", "enabled": false } + ], + "quotes": [ + { "id": "0c8a5bdebd13", "enabled": true } + ] +} diff --git a/src/features/background/api/photoPackAPI.js b/src/features/background/api/photoPackAPI.js index bfa031ae..b446e2fd 100644 --- a/src/features/background/api/photoPackAPI.js +++ b/src/features/background/api/photoPackAPI.js @@ -293,16 +293,37 @@ export function buildPhotoPool() { const apiPacksReady = JSON.parse(localStorage.getItem('api_packs_ready') || '[]'); const apiPackCache = JSON.parse(localStorage.getItem('api_pack_cache') || '{}'); + console.log('[Build Pool] Building photo pool', { + installed_count: installed.filter((p) => p.type === 'photos').length, + enabledPacks, + apiPacksReady, + }); + installed.forEach((pack) => { if (pack.type !== 'photos') return; const packId = pack.id || pack.name; - if (enabledPacks[packId] === false) return; + const isEnabled = enabledPacks[packId] !== false; + + console.log(`[Build Pool] Processing pack: ${pack.display_name || pack.name}`, { + packId, + enabled: isEnabled, + enabledPacksValue: enabledPacks[packId], + api_enabled: pack.api_enabled, + api_ready: apiPacksReady.includes(pack.id), + cached_photos: pack.api_enabled ? apiPackCache[pack.id]?.photos?.length || 0 : pack.photos?.length || 0, + }); + + if (enabledPacks[packId] === false) { + console.log(`[Build Pool] Skipping disabled pack: ${pack.display_name || pack.name}`); + return; + } if (pack.api_enabled) { if (apiPacksReady.includes(pack.id)) { const cached = apiPackCache[pack.id]; if (cached && cached.photos.length > 0) { + console.log(`[Build Pool] Adding ${cached.photos.length} API photos from ${pack.display_name || pack.name}`); cached.photos.forEach((photo) => { pool.push({ ...photo, @@ -310,9 +331,14 @@ export function buildPhotoPool() { pack_id: pack.id, }); }); + } else { + console.log(`[Build Pool] API pack ${pack.display_name || pack.name} has no cached photos`); } + } else { + console.log(`[Build Pool] API pack ${pack.display_name || pack.name} is not ready yet`); } } else { + console.log(`[Build Pool] Adding ${pack.photos.length} static photos from ${pack.display_name || pack.name}`); pack.photos.forEach((photo) => { pool.push({ photographer: photo.photographer, @@ -326,5 +352,6 @@ export function buildPhotoPool() { } }); + console.log(`[Build Pool] Final pool size: ${pool.length} photos`); return pool; } diff --git a/src/features/background/components/PhotoInformation.jsx b/src/features/background/components/PhotoInformation.jsx index 896642a9..0ec776ad 100644 --- a/src/features/background/components/PhotoInformation.jsx +++ b/src/features/background/components/PhotoInformation.jsx @@ -48,7 +48,8 @@ const formatText = (text) => { const downloadImage = async (info) => { const link = document.createElement('a'); link.href = await toDataURL(getProxiedImageUrl(info.url)); - link.download = `mue-${formatText(info.credit)}-${formatText(info.location)}.jpg`; // image is more likely to be webp or avif btw + const locationText = typeof info.location === 'string' ? info.location : info.location?.name || 'unknown'; + link.download = `mue-${formatText(info.credit)}-${formatText(locationText)}.jpg`; document.body.appendChild(link); link.click(); document.body.removeChild(link); @@ -341,14 +342,20 @@ function PhotoInformation({ info, url, api }) { {(showExtraInfo || other) && info.description ? info.description.length > 40 ? info.description.substring(0, 40) + '...' : info.description - : info.location?.split(',').slice(-2).join(', ').trim()} + : typeof info.location === 'string' + ? info.location.split(',').slice(-2).join(', ').trim() + : info.location?.name || ''} {photo} {credit} diff --git a/src/features/background/marketplace/photoPackHandler.js b/src/features/background/marketplace/photoPackHandler.js new file mode 100644 index 00000000..28b7faa6 --- /dev/null +++ b/src/features/background/marketplace/photoPackHandler.js @@ -0,0 +1,105 @@ +import { clearQueuesOnSettingChange } from 'utils/queueOperations'; +import { refreshAPIPackCache } from 'features/background/api/photoPackAPI'; + +export const photoPackHandler = { + install: (packData, context) => { + console.log(`[Photo Handler] Installing ${packData.display_name || packData.name}`, { + api_enabled: packData.api_enabled, + requires_api_key: packData.requires_api_key, + photo_count: packData.photos?.length, + isNewInstall: context.isNewInstall, + }); + + const currentPhotos = JSON.parse(localStorage.getItem('photo_packs')) || []; + const hadPhotoPacks = currentPhotos.length > 0; + + if (packData.api_enabled) { + console.log(`[Photo Handler] Setting up API pack ${packData.id}`); + const defaultSettings = {}; + packData.settings_schema?.forEach((field) => { + defaultSettings[field.key] = field.default || ''; + }); + localStorage.setItem(`photopack_settings_${packData.id}`, JSON.stringify(defaultSettings)); + + const apiPackCache = JSON.parse(localStorage.getItem('api_pack_cache') || '{}'); + apiPackCache[packData.id] = { + photos: [], + last_fetched: 0, + last_refresh_attempt: 0, + }; + localStorage.setItem('api_pack_cache', JSON.stringify(apiPackCache)); + + if (!currentPhotos.length) { + localStorage.setItem('photo_packs', JSON.stringify([])); + } + + if (!packData.requires_api_key) { + console.log(`[Photo Handler] Refreshing API cache for ${packData.id}`); + refreshAPIPackCache(packData.id); + } + } else { + console.log(`[Photo Handler] Adding ${packData.photos.length} static photos to pool`); + packData.photos.forEach((photo) => { + currentPhotos.push(photo); + }); + localStorage.setItem('photo_packs', JSON.stringify(currentPhotos)); + console.log(`[Photo Handler] Photo pool now has ${currentPhotos.length} photos`); + } + + if (localStorage.getItem('backgroundType') !== 'photo_pack') { + localStorage.setItem('oldBackgroundType', localStorage.getItem('backgroundType')); + } + localStorage.setItem('backgroundType', 'photo_pack'); + localStorage.removeItem('backgroundchange'); + clearQueuesOnSettingChange('packInstall'); + + const backgroundElement = document.getElementById('backgroundImage'); + const hasBackground = backgroundElement && backgroundElement.style.backgroundImage; + + console.log(`[Photo Handler] Background check:`, { + elementExists: !!backgroundElement, + hasBackgroundImage: !!hasBackground, + willRefresh: !hasBackground, + }); + + return { + refreshEvent: !hasBackground ? 'backgroundrefresh' : null, + }; + }, + + uninstall: (packData, context) => { + let installedContents = JSON.parse(localStorage.getItem('photo_packs')) || []; + + if (packData) { + if (packData.api_enabled) { + const apiPackCache = JSON.parse(localStorage.getItem('api_pack_cache') || '{}'); + delete apiPackCache[packData.id]; + localStorage.setItem('api_pack_cache', JSON.stringify(apiPackCache)); + + const apiPacksReady = JSON.parse(localStorage.getItem('api_packs_ready') || '[]'); + const filtered = apiPacksReady.filter((id) => id !== packData.id); + localStorage.setItem('api_packs_ready', JSON.stringify(filtered)); + } else if (packData.photos) { + installedContents = installedContents.filter((item) => { + return !packData.photos.some((content) => content.url?.default === item.url?.default); + }); + localStorage.setItem('photo_packs', JSON.stringify(installedContents)); + } + } + + const remainingInstalled = JSON.parse(localStorage.getItem('installed')).filter( + (item) => item.type === 'photos' && item.name !== context.name, + ); + + if (remainingInstalled.length === 0) { + localStorage.setItem('backgroundType', localStorage.getItem('oldBackgroundType') || 'api'); + localStorage.removeItem('oldBackgroundType'); + localStorage.removeItem('photo_packs'); + } + + localStorage.removeItem('backgroundchange'); + clearQueuesOnSettingChange('packUninstall'); + + return { refreshEvent: null }; + }, +}; diff --git a/src/features/quote/marketplace/quotePackHandler.js b/src/features/quote/marketplace/quotePackHandler.js new file mode 100644 index 00000000..30edfa1a --- /dev/null +++ b/src/features/quote/marketplace/quotePackHandler.js @@ -0,0 +1,43 @@ +export const quotePackHandler = { + install: (packData, context) => { + const currentQuotes = JSON.parse(localStorage.getItem('quote_packs')) || []; + packData.quotes.forEach((quote) => { + currentQuotes.push(quote); + }); + localStorage.setItem('quote_packs', JSON.stringify(currentQuotes)); + + if (localStorage.getItem('quoteType') !== 'quote_pack') { + localStorage.setItem('oldQuoteType', localStorage.getItem('quoteType')); + } + localStorage.setItem('quoteType', 'quote_pack'); + localStorage.removeItem('quotechange'); + localStorage.removeItem('quoteQueue'); + localStorage.removeItem('currentQuote'); + + return { refreshEvent: 'quote' }; + }, + + uninstall: (packData, context) => { + let installedContents = JSON.parse(localStorage.getItem('quote_packs')) || []; + + if (packData && packData.quotes) { + installedContents = installedContents.filter((item) => { + return !packData.quotes.some( + (content) => content.quote === item.quote && content.author === item.author, + ); + }); + } + localStorage.setItem('quote_packs', JSON.stringify(installedContents)); + + if (installedContents.length === 0) { + localStorage.setItem('quoteType', localStorage.getItem('oldQuoteType') || 'api'); + localStorage.removeItem('oldQuoteType'); + localStorage.removeItem('quote_packs'); + } + localStorage.removeItem('quotechange'); + localStorage.removeItem('quoteQueue'); + localStorage.removeItem('currentQuote'); + + return { refreshEvent: 'marketplacequoteuninstall' }; + }, +}; diff --git a/src/utils/marketplace/handlerRegistry.js b/src/utils/marketplace/handlerRegistry.js new file mode 100644 index 00000000..42e1002c --- /dev/null +++ b/src/utils/marketplace/handlerRegistry.js @@ -0,0 +1,19 @@ +const handlers = new Map(); + +export function registerHandler(type, handler) { + if (!handler.install || typeof handler.install !== 'function') { + throw new Error(`Handler for type "${type}" must have an install function`); + } + if (!handler.uninstall || typeof handler.uninstall !== 'function') { + throw new Error(`Handler for type "${type}" must have an uninstall function`); + } + handlers.set(type, handler); +} + +export function getHandler(type) { + return handlers.get(type) || null; +} + +export function hasHandler(type) { + return handlers.has(type); +} diff --git a/src/utils/marketplace/handlers/settingsHandler.js b/src/utils/marketplace/handlers/settingsHandler.js new file mode 100644 index 00000000..1cab9ee6 --- /dev/null +++ b/src/utils/marketplace/handlers/settingsHandler.js @@ -0,0 +1,34 @@ +function showReminder() { + document.querySelector('.reminder-info').style.display = 'flex'; + localStorage.setItem('showReminder', true); +} + +export const settingsHandler = { + install: (packData, context) => { + localStorage.removeItem('backup_settings'); + + const oldSettings = []; + Object.keys(localStorage).forEach((key) => { + oldSettings.push({ name: key, value: localStorage.getItem(key) }); + }); + + localStorage.setItem('backup_settings', JSON.stringify(oldSettings)); + Object.keys(packData.settings).forEach((key) => { + localStorage.setItem(key, packData.settings[key]); + }); + showReminder(); + + return { refreshEvent: null }; + }, + + uninstall: (packData, context) => { + const oldSettings = JSON.parse(localStorage.getItem('backup_settings')); + localStorage.clear(); + oldSettings.forEach((item) => { + localStorage.setItem(item.name, item.value); + }); + showReminder(); + + return { refreshEvent: null }; + }, +}; diff --git a/src/utils/marketplace/index.js b/src/utils/marketplace/index.js index f909cce8..83a88c34 100644 --- a/src/utils/marketplace/index.js +++ b/src/utils/marketplace/index.js @@ -2,5 +2,6 @@ import { install } from './install'; import { uninstall } from './uninstall'; import { urlParser } from './urlParser'; import { getProxiedImageUrl } from './imageProxy'; +import { registerHandler, getHandler, hasHandler } from './handlerRegistry'; -export { install, uninstall, urlParser, getProxiedImageUrl }; +export { install, uninstall, urlParser, getProxiedImageUrl, registerHandler, getHandler, hasHandler }; diff --git a/src/utils/marketplace/install.js b/src/utils/marketplace/install.js index 8a931c98..6d1a55fc 100644 --- a/src/utils/marketplace/install.js +++ b/src/utils/marketplace/install.js @@ -2,6 +2,7 @@ import EventBus from 'utils/eventbus'; import { clearQueuesOnSettingChange } from 'utils/queueOperations'; import variables from 'config/variables'; import { refreshAPIPackCache } from 'features/background/api/photoPackAPI'; +import { getHandler } from './handlerRegistry'; // todo: relocate this function function showReminder() { @@ -26,12 +27,27 @@ async function trackDownload(itemId) { } export function install(type, input, sideload, collection) { - let refreshEvent = null; + console.log(`[Install] Installing ${type}: ${input.display_name || input.name}`, { + sideload, + collection, + id: input.id, + }); const installed = JSON.parse(localStorage.getItem('installed') || '[]'); const isNewInstall = !installed.some((item) => item.id === input.id || item.name === input.name); - switch (type) { + console.log(`[Install] isNewInstall: ${isNewInstall}`); + + let refreshEvent = null; + + const handler = getHandler(type); + if (handler) { + console.log(`[Install] Using handler for type: ${type}`); + const result = handler.install(input, { isNewInstall, installed }); + refreshEvent = result.refreshEvent; + } else { + console.log(`[Install] No handler found, using fallback switch for type: ${type}`); + switch (type) { case 'settings': { localStorage.removeItem('backup_settings'); @@ -87,7 +103,10 @@ export function install(type, input, sideload, collection) { localStorage.setItem('backgroundType', 'photo_pack'); localStorage.removeItem('backgroundchange'); clearQueuesOnSettingChange('packInstall'); - if (!hadPhotoPacks) { + + const backgroundElement = document.getElementById('backgroundImage'); + const hasBackground = backgroundElement && backgroundElement.style.backgroundImage; + if (!hasBackground) { refreshEvent = 'backgroundrefresh'; } break; @@ -113,6 +132,7 @@ export function install(type, input, sideload, collection) { default: break; + } } if (sideload) { @@ -122,12 +142,14 @@ export function install(type, input, sideload, collection) { installed.push(input); localStorage.setItem('installed', JSON.stringify(installed)); + console.log(`[Install] Updated installed list, count: ${installed.length}`); if (isNewInstall) { const packId = input.id || input.name; const enabledPacks = JSON.parse(localStorage.getItem('enabledPacks') || '{}'); enabledPacks[packId] = true; localStorage.setItem('enabledPacks', JSON.stringify(enabledPacks)); + console.log(`[Install] Set pack ${packId} as enabled`); } if (isNewInstall && input.id) { @@ -135,6 +157,9 @@ export function install(type, input, sideload, collection) { } if (refreshEvent) { + console.log(`[Install] Emitting refresh event: ${refreshEvent}`); EventBus.emit('refresh', refreshEvent); } + + console.log(`[Install] Installation complete for ${input.display_name || input.name}`); } diff --git a/src/utils/marketplace/installDefaultPacks.js b/src/utils/marketplace/installDefaultPacks.js index 17f79401..2eac8af5 100644 --- a/src/utils/marketplace/installDefaultPacks.js +++ b/src/utils/marketplace/installDefaultPacks.js @@ -1,64 +1,56 @@ import variables from 'config/variables'; +import defaultPacks from 'config/defaultPacks.json'; +import { install } from './install'; import { refreshAPIPackCache } from 'features/background/api/photoPackAPI'; -const DEFAULT_PHOTO_PACKS = [ - { id: 'd58909e759ec', enabled: true }, - { id: 'c0094361594f', enabled: false }, -]; - -export async function installDefaultPhotoPacks() { +export async function installDefaultPacks() { if (localStorage.getItem('defaultPacksNeedInstall') !== 'true') { + console.log('[Default Packs] Already installed, skipping'); return; } + console.log('[Default Packs] Installing default packs...', defaultPacks); + const installed = []; const enabledPacks = {}; - for (const pack of DEFAULT_PHOTO_PACKS) { - try { - const response = await fetch(`${variables.constants.API_URL}/marketplace/item/${pack.id}`); - const { data } = await response.json(); + for (const [type, packs] of Object.entries(defaultPacks)) { + console.log(`[Default Packs] Processing ${type} packs:`, packs); - if (data && data.type === 'photos') { - installed.push(data); - enabledPacks[data.id] = pack.enabled; + for (const pack of packs) { + try { + console.log(`[Default Packs] Fetching pack ${pack.id}, enabled: ${pack.enabled}`); + const response = await fetch(`${variables.constants.API_URL}/marketplace/item/${pack.id}`); + const { data } = await response.json(); - if (data.api_enabled) { - const defaultSettings = {}; - data.settings_schema?.forEach((field) => { - defaultSettings[field.key] = field.default || ''; - }); - localStorage.setItem(`photopack_settings_${data.id}`, JSON.stringify(defaultSettings)); + if (data && data.type === type) { + console.log(`[Default Packs] Installing ${data.display_name || data.name}`, data); + installed.push(data); + enabledPacks[data.id] = pack.enabled; - const apiPackCache = JSON.parse(localStorage.getItem('api_pack_cache') || '{}'); - apiPackCache[data.id] = { - photos: [], - last_fetched: 0, - last_refresh_attempt: 0, - }; - localStorage.setItem('api_pack_cache', JSON.stringify(apiPackCache)); + install(type, data, false, true); - if (pack.enabled && !data.requires_api_key) { + if (type === 'photos' && data.api_enabled && pack.enabled && !data.requires_api_key) { + console.log(`[Default Packs] Refreshing API cache for enabled pack: ${data.id}`); refreshAPIPackCache(data.id); } } + } catch (error) { + console.error(`[Default Packs] Failed to install default ${type} pack ${pack.id}:`, error); } - } catch (error) { - console.error(`Failed to install default pack ${pack.id}:`, error); } } if (installed.length > 0) { - const existingInstalled = JSON.parse(localStorage.getItem('installed') || '[]'); - localStorage.setItem('installed', JSON.stringify([...existingInstalled, ...installed])); - const existingEnabledPacks = JSON.parse(localStorage.getItem('enabledPacks') || '{}'); localStorage.setItem( 'enabledPacks', JSON.stringify({ ...existingEnabledPacks, ...enabledPacks }), ); + console.log('[Default Packs] Set enabledPacks:', enabledPacks); localStorage.removeItem('defaultPacksNeedInstall'); window.dispatchEvent(new Event('installedAddonsChanged')); + console.log('[Default Packs] Installation complete'); } } diff --git a/src/utils/marketplace/registerHandlers.js b/src/utils/marketplace/registerHandlers.js new file mode 100644 index 00000000..cf9ff6a8 --- /dev/null +++ b/src/utils/marketplace/registerHandlers.js @@ -0,0 +1,10 @@ +import { registerHandler } from './handlerRegistry'; +import { photoPackHandler } from 'features/background/marketplace/photoPackHandler'; +import { quotePackHandler } from 'features/quote/marketplace/quotePackHandler'; +import { settingsHandler } from './handlers/settingsHandler'; + +export function registerAllHandlers() { + registerHandler('photos', photoPackHandler); + registerHandler('quotes', quotePackHandler); + registerHandler('settings', settingsHandler); +} diff --git a/src/utils/marketplace/uninstall.js b/src/utils/marketplace/uninstall.js index 37dc0c56..eb8c1b16 100644 --- a/src/utils/marketplace/uninstall.js +++ b/src/utils/marketplace/uninstall.js @@ -1,5 +1,6 @@ import EventBus from 'utils/eventbus'; import { clearQueuesOnSettingChange } from 'utils/queueOperations'; +import { getHandler } from './handlerRegistry'; // todo: relocate this function function showReminder() { @@ -8,10 +9,20 @@ function showReminder() { } export function uninstall(type, name) { - let installedContents, packContents; let refreshEvent = null; - switch (type) { + const handler = getHandler(type); + if (handler) { + const installed = JSON.parse(localStorage.getItem('installed')); + const packData = installed.find((item) => item.name === name); + if (packData) { + const result = handler.uninstall(packData, { name, installed }); + refreshEvent = result.refreshEvent; + } + } else { + let installedContents, packContents; + + switch (type) { case 'settings': { const oldSettings = JSON.parse(localStorage.getItem('backup_settings')); localStorage.clear(); @@ -83,11 +94,12 @@ export function uninstall(type, name) { localStorage.removeItem('backgroundchange'); clearQueuesOnSettingChange('packUninstall'); - refreshEvent = 'marketplacebackgrounduninstall'; + refreshEvent = null; break; default: break; + } } const installed = JSON.parse(localStorage.getItem('installed'));