mirror of
https://github.com/mue/mue.git
synced 2026-07-04 13:42:11 +02:00
- Implemented `getProxiedImageUrl` function to apply DuckDuckGo image proxy to image URLs based on user settings. - Updated various components to use the proxied image URLs, including background loader, photo information, lightbox, and item cards. - Added a new setting in the advanced options to enable/disable the DuckDuckGo image proxy. - Updated localization files to include new strings for the image proxy feature. - Initialized the proxy setting in default settings to false.
396 lines
12 KiB
JavaScript
396 lines
12 KiB
JavaScript
import variables from 'config/variables';
|
|
import { supportsAVIF } from './avifSupport';
|
|
import { getOfflineImage } from './offlineImage';
|
|
import { randomColourStyleBuilder } from './randomColour';
|
|
import videoCheck from './videoCheck';
|
|
import { getAllBackgrounds, getAllBackgroundsWithMetadata } from 'utils/customBackgroundDB';
|
|
import { BackgroundQueueManager } from 'utils/backgroundQueue';
|
|
import { getProxiedImageUrl } from 'utils/marketplace';
|
|
|
|
const parseJSON = (key, fallback = null) => {
|
|
const item = localStorage.getItem(key);
|
|
if (item === null || item === 'null') {
|
|
return fallback;
|
|
}
|
|
try {
|
|
const parsed = JSON.parse(item);
|
|
return parsed !== null ? parsed : fallback;
|
|
} catch {
|
|
return fallback;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Fetches image data from the configured API
|
|
*/
|
|
export async function fetchAPIImageData(excludedPun = null) {
|
|
const api = localStorage.getItem('backgroundAPI') || 'mue';
|
|
const quality = localStorage.getItem('apiQuality') || 'high';
|
|
const categories = parseJSON('apiCategories', localStorage.getItem('apiCategories'));
|
|
const excludes = [...parseJSON('backgroundExclude', []), ...(excludedPun ? [excludedPun] : [])];
|
|
|
|
const baseURL = `${variables.constants.API_URL}/images`;
|
|
const collection = localStorage.getItem('unsplashCollections');
|
|
|
|
const url =
|
|
api === 'unsplash' || api === 'pexels'
|
|
? `${baseURL}/unsplash?${collection ? `collections=${collection}` : `categories=${categories || ''}`}&quality=${quality}`
|
|
: `${baseURL}/random?categories=${categories || ''}&quality=${quality}&excludes=${excludes}`;
|
|
|
|
try {
|
|
const accept = `application/json, ${(await supportsAVIF()) ? 'image/avif' : 'image/webp'}`;
|
|
const data = await (await fetch(url, { headers: { accept } })).json();
|
|
|
|
return {
|
|
url: data.file,
|
|
type: 'api',
|
|
currentAPI: api,
|
|
photoInfo: {
|
|
hidden: false,
|
|
category: data.category,
|
|
credit: data.photographer,
|
|
location: data.location.name,
|
|
camera: data.camera,
|
|
url: data.file,
|
|
photographerURL: api === 'unsplash' ? data.photographer_page : undefined,
|
|
photoURL: api === 'unsplash' ? data.photo_page : undefined,
|
|
latitude: data.location.latitude,
|
|
longitude: data.location.longitude,
|
|
views: data.views,
|
|
downloads: data.downloads,
|
|
likes: data.likes,
|
|
description: data.description,
|
|
colour: data.colour,
|
|
blur_hash: data.blur_hash,
|
|
pun: data.pun,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error('Failed to fetch API image:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets background data based on current configuration
|
|
*/
|
|
export async function getBackgroundData() {
|
|
const isOffline =
|
|
localStorage.getItem('offlineMode') === 'true' ||
|
|
localStorage.getItem('showWelcome') === 'true';
|
|
|
|
// Handle favourited background
|
|
const fav = parseJSON('favourite');
|
|
if (fav) {
|
|
if (fav.type === 'random_colour' || fav.type === 'random_gradient') {
|
|
return { type: 'colour', style: `background:${fav.url}` };
|
|
}
|
|
return { url: fav.url, photoInfo: { ...fav, url: fav.url } };
|
|
}
|
|
|
|
const type = localStorage.getItem('backgroundType');
|
|
|
|
switch (type) {
|
|
case 'api':
|
|
return getAPIBackground(isOffline);
|
|
|
|
case 'colour':
|
|
return getColourBackground();
|
|
|
|
case 'random_colour':
|
|
case 'random_gradient':
|
|
return randomColourStyleBuilder(type);
|
|
|
|
case 'custom':
|
|
return await getCustomBackground(isOffline);
|
|
|
|
case 'photo_pack':
|
|
return getPhotoPackBackground(isOffline);
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets solid colour background
|
|
*/
|
|
function getColourBackground() {
|
|
return {
|
|
type: 'colour',
|
|
style: `background: ${localStorage.getItem('customBackgroundColour') || 'rgb(0,0,0)'}`,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Gets API background with caching and prefetching
|
|
*/
|
|
async function getAPIBackground(isOffline) {
|
|
if (isOffline) return getOfflineImage('api');
|
|
|
|
const queueManager = new BackgroundQueueManager('imageQueue', 3);
|
|
let data;
|
|
|
|
// Use cached next image if available
|
|
const cachedQueue = queueManager.getQueue();
|
|
if (cachedQueue.length > 0) {
|
|
data = queueManager.shift();
|
|
} else {
|
|
data = await fetchAPIImageData();
|
|
}
|
|
|
|
if (!data) return getOfflineImage('api');
|
|
|
|
try {
|
|
localStorage.setItem('currentBackground', JSON.stringify(data));
|
|
} catch (e) {
|
|
console.warn('Could not save currentBackground to localStorage:', e);
|
|
}
|
|
|
|
// Pre-fetch next images in the background
|
|
if (queueManager.needsPrefetch()) {
|
|
prefetchAPIImages(queueManager, data, cachedQueue).catch((error) => {
|
|
console.error('Failed to prefetch API images:', error);
|
|
});
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Prefetch API images in the background
|
|
* @param {BackgroundQueueManager} queueManager - The queue manager
|
|
* @param {Object} currentImage - The current image data
|
|
* @param {Array} currentQueue - The current queue state
|
|
*/
|
|
async function prefetchAPIImages(queueManager, currentImage, currentQueue) {
|
|
const count = queueManager.getSpaceNeeded();
|
|
const excludedPuns = [
|
|
currentImage.photoInfo.pun,
|
|
...currentQueue.map((img) => img.photoInfo?.pun).filter(Boolean),
|
|
];
|
|
|
|
// Prefetch remaining images asynchronously
|
|
const newImages = await Promise.all(
|
|
Array.from({ length: count }, (_, i) =>
|
|
fetchAPIImageData(excludedPuns[i] || currentImage.photoInfo.pun),
|
|
),
|
|
);
|
|
|
|
const validImages = newImages.filter(Boolean);
|
|
if (validImages.length > 0) {
|
|
queueManager.push(validImages);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets custom background with prefetching
|
|
*/
|
|
async function getCustomBackground(isOffline) {
|
|
// Get full metadata from IndexedDB
|
|
let backgrounds = await getAllBackgroundsWithMetadata();
|
|
|
|
// Fallback to localStorage URLs if IndexedDB is empty
|
|
if (!backgrounds || backgrounds.length === 0) {
|
|
const urls = parseJSON('customBackground', []);
|
|
if (urls && urls.length > 0) {
|
|
// Convert old URL format to metadata format
|
|
backgrounds = urls.map((url) => ({ url, photoInfo: { hidden: true } }));
|
|
}
|
|
}
|
|
|
|
if (!backgrounds || backgrounds.length === 0) return null;
|
|
|
|
const queueManager = new BackgroundQueueManager('customQueue', 3);
|
|
let selected;
|
|
|
|
// Use cached next background ID if available
|
|
const cachedQueue = queueManager.getQueue();
|
|
if (cachedQueue.length > 0) {
|
|
// Queue contains IDs only, not full data
|
|
const queuedId = queueManager.shift();
|
|
// Look up the full background data by ID
|
|
selected = backgrounds.find((bg) => bg.id === queuedId);
|
|
|
|
// If not found (maybe deleted), pick random
|
|
if (!selected) {
|
|
selected = backgrounds[Math.floor(Math.random() * backgrounds.length)];
|
|
}
|
|
} else {
|
|
// Pick random background
|
|
selected = backgrounds[Math.floor(Math.random() * backgrounds.length)];
|
|
}
|
|
|
|
// Check if selected is valid before using it
|
|
if (!selected) return null;
|
|
|
|
const url = selected.url || selected;
|
|
|
|
const data = {
|
|
id: selected.id,
|
|
url,
|
|
type: 'custom',
|
|
video: videoCheck(url),
|
|
photoInfo: {
|
|
hidden: true,
|
|
blur_hash: selected.blurHash || null,
|
|
},
|
|
};
|
|
|
|
if (isOffline && !data.url.startsWith('data:')) {
|
|
return getOfflineImage('custom');
|
|
}
|
|
|
|
// Don't store full image data in localStorage to avoid quota errors
|
|
// Just store metadata
|
|
try {
|
|
localStorage.setItem(
|
|
'currentBackground',
|
|
JSON.stringify({
|
|
type: 'custom',
|
|
video: data.video,
|
|
photoInfo: data.photoInfo,
|
|
}),
|
|
);
|
|
} catch (e) {
|
|
// Ignore quota errors for currentBackground
|
|
console.warn('Could not save currentBackground to localStorage:', e);
|
|
}
|
|
|
|
// Prefetch more backgrounds in the background (skip videos)
|
|
if (queueManager.needsPrefetch() && !data.video && selected.id) {
|
|
prefetchCustomBackgrounds(queueManager, backgrounds, selected.id, cachedQueue).catch((error) => {
|
|
console.error('Failed to prefetch custom backgrounds:', error);
|
|
});
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Prefetch custom backgrounds in the background
|
|
* Store only IDs in queue to avoid localStorage quota issues with large data URLs
|
|
* @param {BackgroundQueueManager} queueManager - The queue manager
|
|
* @param {Array} allBackgrounds - All available custom backgrounds
|
|
* @param {number} currentId - The current background ID
|
|
* @param {Array} currentQueue - The current queue state (array of IDs)
|
|
*/
|
|
async function prefetchCustomBackgrounds(queueManager, allBackgrounds, currentId, currentQueue) {
|
|
const count = queueManager.getSpaceNeeded();
|
|
|
|
// Get already used IDs (queue now contains IDs only)
|
|
const usedIds = [currentId, ...currentQueue.filter(Boolean)];
|
|
|
|
// Filter available (exclude videos from prefetch and already used)
|
|
const available = allBackgrounds.filter(
|
|
(bg) => bg.id && !usedIds.includes(bg.id) && !videoCheck(bg.url || bg),
|
|
);
|
|
|
|
if (available.length === 0) return;
|
|
|
|
// Shuffle and take N
|
|
const shuffled = available.sort(() => Math.random() - 0.5);
|
|
const selected = shuffled.slice(0, count);
|
|
|
|
// Store only IDs to avoid quota issues (custom backgrounds use large data URLs)
|
|
const ids = selected.map((bg) => bg.id).filter(Boolean);
|
|
|
|
if (ids.length > 0) {
|
|
queueManager.push(ids);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets photo pack background with prefetching and blurhash
|
|
*/
|
|
function getPhotoPackBackground(isOffline) {
|
|
if (isOffline) return getOfflineImage('photo_pack');
|
|
|
|
const photos = parseJSON('installed', []).flatMap((item) =>
|
|
item.type === 'photos' && item.photos ? item.photos : [],
|
|
);
|
|
|
|
if (photos.length === 0) return null;
|
|
|
|
const queueManager = new BackgroundQueueManager('photoPackQueue', 3);
|
|
let photoData;
|
|
|
|
// Use cached next photo if available
|
|
const cachedQueue = queueManager.getQueue();
|
|
if (cachedQueue.length > 0) {
|
|
photoData = queueManager.shift();
|
|
} else {
|
|
// Pick random photo
|
|
const index = Math.floor(Math.random() * photos.length);
|
|
const selected = photos[index];
|
|
|
|
photoData = {
|
|
url: getProxiedImageUrl(selected.url.default),
|
|
type: 'photo_pack',
|
|
photoInfo: {
|
|
hidden: false,
|
|
credit: selected.photographer,
|
|
location: selected.location,
|
|
blur_hash: selected.blur_hash || null,
|
|
url: selected.url.default,
|
|
},
|
|
};
|
|
}
|
|
|
|
try {
|
|
localStorage.setItem('currentBackground', JSON.stringify(photoData));
|
|
} catch (e) {
|
|
console.warn('Could not save currentBackground to localStorage:', e);
|
|
}
|
|
|
|
// Prefetch more photos in the background
|
|
if (queueManager.needsPrefetch()) {
|
|
prefetchPhotoPackImages(queueManager, photos, photoData, cachedQueue).catch((error) => {
|
|
console.error('Failed to prefetch photo pack images:', error);
|
|
});
|
|
}
|
|
|
|
return photoData;
|
|
}
|
|
|
|
/**
|
|
* Prefetch photo pack images in the background
|
|
* @param {BackgroundQueueManager} queueManager - The queue manager
|
|
* @param {Array} allPhotos - All available photos from installed packs
|
|
* @param {Object} currentPhoto - The current photo data
|
|
* @param {Array} currentQueue - The current queue state
|
|
*/
|
|
async function prefetchPhotoPackImages(queueManager, allPhotos, currentPhoto, currentQueue) {
|
|
const count = queueManager.getSpaceNeeded();
|
|
|
|
// Get already used URLs
|
|
const usedUrls = [
|
|
currentPhoto.url,
|
|
...currentQueue.map((p) => p.url),
|
|
];
|
|
|
|
// Filter available photos
|
|
const available = allPhotos.filter((p) => !usedUrls.includes(p.url.default));
|
|
|
|
if (available.length === 0) return;
|
|
|
|
// Shuffle and take N
|
|
const shuffled = available.sort(() => Math.random() - 0.5);
|
|
const selected = shuffled.slice(0, count);
|
|
|
|
// Normalize metadata
|
|
const normalized = selected.map((photo) => ({
|
|
url: getProxiedImageUrl(photo.url.default),
|
|
type: 'photo_pack',
|
|
photoInfo: {
|
|
hidden: false,
|
|
credit: photo.photographer,
|
|
location: photo.location,
|
|
blur_hash: photo.blur_hash || null,
|
|
url: photo.url.default,
|
|
},
|
|
}));
|
|
|
|
queueManager.push(normalized);
|
|
}
|