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