mirror of
https://github.com/mue/mue.git
synced 2026-07-24 01:07:23 +02:00
feat(queue): implement centralized queue management and clearing logic for background prefetch queues
This commit is contained in:
23
check_installed.html
Normal file
23
check_installed.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title>Check localStorage</title></head>
|
||||||
|
<body>
|
||||||
|
<h3>Checking installed photo packs...</h3>
|
||||||
|
<pre id="output"></pre>
|
||||||
|
<script>
|
||||||
|
const installed = JSON.parse(localStorage.getItem('installed') || '[]');
|
||||||
|
const photoPacks = installed.filter(item => item.type === 'photos');
|
||||||
|
|
||||||
|
document.getElementById('output').textContent = JSON.stringify(photoPacks, null, 2);
|
||||||
|
|
||||||
|
// Check for blur_hash
|
||||||
|
photoPacks.forEach(pack => {
|
||||||
|
console.log(`Pack: ${pack.name}`);
|
||||||
|
if (pack.photos && pack.photos.length > 0) {
|
||||||
|
console.log('First photo has blur_hash?', !!pack.photos[0].blur_hash);
|
||||||
|
console.log('First photo:', pack.photos[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -4,6 +4,7 @@ import { getOfflineImage } from './offlineImage';
|
|||||||
import { randomColourStyleBuilder } from './randomColour';
|
import { randomColourStyleBuilder } from './randomColour';
|
||||||
import videoCheck from './videoCheck';
|
import videoCheck from './videoCheck';
|
||||||
import { getAllBackgrounds, getAllBackgroundsWithMetadata } from 'utils/customBackgroundDB';
|
import { getAllBackgrounds, getAllBackgroundsWithMetadata } from 'utils/customBackgroundDB';
|
||||||
|
import { BackgroundQueueManager } from 'utils/backgroundQueue';
|
||||||
|
|
||||||
const parseJSON = (key, fallback = null) => {
|
const parseJSON = (key, fallback = null) => {
|
||||||
const item = localStorage.getItem(key);
|
const item = localStorage.getItem(key);
|
||||||
@@ -126,42 +127,29 @@ function getColourBackground() {
|
|||||||
async function getAPIBackground(isOffline) {
|
async function getAPIBackground(isOffline) {
|
||||||
if (isOffline) return getOfflineImage('api');
|
if (isOffline) return getOfflineImage('api');
|
||||||
|
|
||||||
// Use cached next image if available
|
const queueManager = new BackgroundQueueManager('imageQueue', 3);
|
||||||
const cachedQueue = parseJSON('imageQueue', []);
|
|
||||||
let data;
|
let data;
|
||||||
|
|
||||||
|
// Use cached next image if available
|
||||||
|
const cachedQueue = queueManager.getQueue();
|
||||||
if (cachedQueue.length > 0) {
|
if (cachedQueue.length > 0) {
|
||||||
data = cachedQueue.shift();
|
data = queueManager.shift();
|
||||||
localStorage.setItem('imageQueue', JSON.stringify(cachedQueue));
|
|
||||||
} else {
|
} else {
|
||||||
data = await fetchAPIImageData();
|
data = await fetchAPIImageData();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data) return getOfflineImage('api');
|
if (!data) return getOfflineImage('api');
|
||||||
|
|
||||||
localStorage.setItem('currentBackground', JSON.stringify(data));
|
try {
|
||||||
|
localStorage.setItem('currentBackground', JSON.stringify(data));
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not save currentBackground to localStorage:', e);
|
||||||
|
}
|
||||||
|
|
||||||
// Pre-fetch next 3 images in the background
|
// Pre-fetch next images in the background
|
||||||
const targetQueueSize = 3;
|
if (queueManager.needsPrefetch()) {
|
||||||
const currentQueueSize = cachedQueue.length;
|
prefetchAPIImages(queueManager, data, cachedQueue).catch((error) => {
|
||||||
|
console.error('Failed to prefetch API images:', error);
|
||||||
if (currentQueueSize < targetQueueSize) {
|
|
||||||
const excludedPuns = [
|
|
||||||
data.photoInfo.pun,
|
|
||||||
...cachedQueue.map((img) => img.photoInfo?.pun).filter(Boolean),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Prefetch remaining images asynchronously
|
|
||||||
Promise.all(
|
|
||||||
Array.from({ length: targetQueueSize - currentQueueSize }, (_, i) =>
|
|
||||||
fetchAPIImageData(excludedPuns[i] || data.photoInfo.pun),
|
|
||||||
),
|
|
||||||
).then((newImages) => {
|
|
||||||
const validImages = newImages.filter(Boolean);
|
|
||||||
if (validImages.length > 0) {
|
|
||||||
const updatedQueue = [...cachedQueue, ...validImages];
|
|
||||||
localStorage.setItem('imageQueue', JSON.stringify(updatedQueue));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +157,33 @@ async function getAPIBackground(isOffline) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets custom background
|
* 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) {
|
async function getCustomBackground(isOffline) {
|
||||||
// Get full metadata from IndexedDB
|
// Get full metadata from IndexedDB
|
||||||
@@ -186,18 +200,33 @@ async function getCustomBackground(isOffline) {
|
|||||||
|
|
||||||
if (!backgrounds || backgrounds.length === 0) return null;
|
if (!backgrounds || backgrounds.length === 0) return null;
|
||||||
|
|
||||||
const selected = backgrounds[Math.floor(Math.random() * backgrounds.length)];
|
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
|
// Check if selected is valid before using it
|
||||||
if (!selected) return null;
|
if (!selected) return null;
|
||||||
|
|
||||||
const url = selected.url || selected;
|
const url = selected.url || selected;
|
||||||
|
|
||||||
if (isOffline && !url.startsWith('data:')) {
|
|
||||||
return getOfflineImage('custom');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
|
id: selected.id,
|
||||||
url,
|
url,
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
video: videoCheck(url),
|
video: videoCheck(url),
|
||||||
@@ -207,6 +236,10 @@ async function getCustomBackground(isOffline) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isOffline && !data.url.startsWith('data:')) {
|
||||||
|
return getOfflineImage('custom');
|
||||||
|
}
|
||||||
|
|
||||||
// Don't store full image data in localStorage to avoid quota errors
|
// Don't store full image data in localStorage to avoid quota errors
|
||||||
// Just store metadata
|
// Just store metadata
|
||||||
try {
|
try {
|
||||||
@@ -223,11 +256,51 @@ async function getCustomBackground(isOffline) {
|
|||||||
console.warn('Could not save currentBackground to localStorage:', e);
|
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;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets photo pack background
|
* 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) {
|
function getPhotoPackBackground(isOffline) {
|
||||||
if (isOffline) return getOfflineImage('photo_pack');
|
if (isOffline) return getOfflineImage('photo_pack');
|
||||||
@@ -238,26 +311,82 @@ function getPhotoPackBackground(isOffline) {
|
|||||||
|
|
||||||
if (photos.length === 0) return null;
|
if (photos.length === 0) return null;
|
||||||
|
|
||||||
const interval = localStorage.getItem('backgroundchange');
|
const queueManager = new BackgroundQueueManager('photoPackQueue', 3);
|
||||||
const startTime = Number(localStorage.getItem('backgroundStartTime'));
|
let photoData;
|
||||||
const shouldRefresh =
|
|
||||||
!interval || interval === 'refresh' || startTime + Number(interval) < Date.now();
|
|
||||||
|
|
||||||
let index;
|
// Use cached next photo if available
|
||||||
if (shouldRefresh) {
|
const cachedQueue = queueManager.getQueue();
|
||||||
index = Math.floor(Math.random() * photos.length);
|
if (cachedQueue.length > 0) {
|
||||||
localStorage.setItem('marketplaceNumber', index);
|
photoData = queueManager.shift();
|
||||||
} else {
|
} else {
|
||||||
index = Number(localStorage.getItem('marketplaceNumber')) || 0;
|
// Pick random photo
|
||||||
|
const index = Math.floor(Math.random() * photos.length);
|
||||||
|
const selected = photos[index];
|
||||||
|
|
||||||
|
photoData = {
|
||||||
|
url: selected.url.default,
|
||||||
|
type: 'photo_pack',
|
||||||
|
photoInfo: {
|
||||||
|
hidden: false,
|
||||||
|
credit: selected.photographer,
|
||||||
|
location: selected.location,
|
||||||
|
blur_hash: selected.blur_hash || null,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const photo = photos[index];
|
try {
|
||||||
|
localStorage.setItem('currentBackground', JSON.stringify(photoData));
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not save currentBackground to localStorage:', e);
|
||||||
|
}
|
||||||
|
|
||||||
return photo
|
// Prefetch more photos in the background
|
||||||
? {
|
if (queueManager.needsPrefetch()) {
|
||||||
url: photo.url.default,
|
prefetchPhotoPackImages(queueManager, photos, photoData, cachedQueue).catch((error) => {
|
||||||
type: 'photo_pack',
|
console.error('Failed to prefetch photo pack images:', error);
|
||||||
photoInfo: { hidden: false, credit: photo.photographer, location: photo.location },
|
});
|
||||||
}
|
}
|
||||||
: null;
|
|
||||||
|
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: photo.url.default,
|
||||||
|
type: 'photo_pack',
|
||||||
|
photoInfo: {
|
||||||
|
hidden: false,
|
||||||
|
credit: photo.photographer,
|
||||||
|
location: photo.location,
|
||||||
|
blur_hash: photo.blur_hash || null,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
queueManager.push(normalized);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import variables from 'config/variables';
|
|||||||
import { memo, useState, useEffect, useCallback, useRef } from 'react';
|
import { memo, useState, useEffect, useCallback, useRef } from 'react';
|
||||||
import { MdSource, MdOutlineAutoAwesome } from 'react-icons/md';
|
import { MdSource, MdOutlineAutoAwesome } from 'react-icons/md';
|
||||||
import EventBus from 'utils/eventbus';
|
import EventBus from 'utils/eventbus';
|
||||||
|
import { clearQueuesOnSettingChange } from 'utils/queueOperations';
|
||||||
|
|
||||||
import { Header } from 'components/Layout/Settings';
|
import { Header } from 'components/Layout/Settings';
|
||||||
import { Dropdown } from 'components/Form/Settings';
|
import { Dropdown } from 'components/Form/Settings';
|
||||||
@@ -67,7 +68,7 @@ const BackgroundOptions = memo(({ currentSubSection, onSubSectionChange, section
|
|||||||
const updateAPI = useCallback((e) => {
|
const updateAPI = useCallback((e) => {
|
||||||
localStorage.setItem('nextImage', null);
|
localStorage.setItem('nextImage', null);
|
||||||
// Clear prefetch queue when API changes to prevent showing cached images from old API
|
// Clear prefetch queue when API changes to prevent showing cached images from old API
|
||||||
localStorage.removeItem('imageQueue');
|
clearQueuesOnSettingChange('backgroundAPI');
|
||||||
if (e === 'mue') {
|
if (e === 'mue') {
|
||||||
setBackgroundCategories(backgroundCategoriesOG);
|
setBackgroundCategories(backgroundCategoriesOG);
|
||||||
setBackgroundAPI('mue');
|
setBackgroundAPI('mue');
|
||||||
@@ -190,7 +191,7 @@ const BackgroundOptions = memo(({ currentSubSection, onSubSectionChange, section
|
|||||||
name="backgroundType"
|
name="backgroundType"
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
// Clear prefetch queue when changing background type
|
// Clear prefetch queue when changing background type
|
||||||
localStorage.removeItem('imageQueue');
|
clearQueuesOnSettingChange('backgroundType');
|
||||||
setBackgroundType(value);
|
setBackgroundType(value);
|
||||||
// Automatically refresh background when switching to custom images
|
// Automatically refresh background when switching to custom images
|
||||||
if (value === 'custom') {
|
if (value === 'custom') {
|
||||||
@@ -229,7 +230,7 @@ const BackgroundOptions = memo(({ currentSubSection, onSubSectionChange, section
|
|||||||
marketplaceEnabled={marketplaceEnabled}
|
marketplaceEnabled={marketplaceEnabled}
|
||||||
onTypeChange={(value) => {
|
onTypeChange={(value) => {
|
||||||
// Clear prefetch queue when changing background type
|
// Clear prefetch queue when changing background type
|
||||||
localStorage.removeItem('imageQueue');
|
clearQueuesOnSettingChange('backgroundType');
|
||||||
setBackgroundType(value);
|
setBackgroundType(value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import variables from 'config/variables';
|
|||||||
import { Dropdown, Radio, Text, ChipSelect } from 'components/Form/Settings';
|
import { Dropdown, Radio, Text, ChipSelect } from 'components/Form/Settings';
|
||||||
import { Row, Content, Action } from 'components/Layout/Settings/Item';
|
import { Row, Content, Action } from 'components/Layout/Settings/Item';
|
||||||
import { APIQualityOptions } from '../optionTypes';
|
import { APIQualityOptions } from '../optionTypes';
|
||||||
|
import { clearQueuesOnSettingChange } from 'utils/queueOperations';
|
||||||
|
|
||||||
const APISettings = ({ backgroundAPI, backgroundCategories, onUpdateAPI }) => {
|
const APISettings = ({ backgroundAPI, backgroundCategories, onUpdateAPI }) => {
|
||||||
return (
|
return (
|
||||||
@@ -36,7 +37,7 @@ const APISettings = ({ backgroundAPI, backgroundCategories, onUpdateAPI }) => {
|
|||||||
name="apiCategories"
|
name="apiCategories"
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
// Clear prefetch queue when categories change
|
// Clear prefetch queue when categories change
|
||||||
localStorage.removeItem('imageQueue');
|
clearQueuesOnSettingChange('apiCategories');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -49,7 +50,7 @@ const APISettings = ({ backgroundAPI, backgroundCategories, onUpdateAPI }) => {
|
|||||||
items={APIQualityOptions}
|
items={APIQualityOptions}
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
// Clear prefetch queue when quality changes
|
// Clear prefetch queue when quality changes
|
||||||
localStorage.removeItem('imageQueue');
|
clearQueuesOnSettingChange('apiQuality');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Radio
|
<Radio
|
||||||
@@ -91,7 +92,7 @@ const APISettings = ({ backgroundAPI, backgroundCategories, onUpdateAPI }) => {
|
|||||||
element="#backgroundImage"
|
element="#backgroundImage"
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
// Clear prefetch queue when Unsplash collections change
|
// Clear prefetch queue when Unsplash collections change
|
||||||
localStorage.removeItem('imageQueue');
|
clearQueuesOnSettingChange('unsplashCollections');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Action>
|
</Action>
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ function ItemCard({ item, toggleFunction, type, onCollection, isCurator, isInsta
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{item.in_collections && item.in_collections.length > 0 && !onCollection && (
|
{item.in_collections && item.in_collections.length > 0 && !onCollection && (
|
||||||
<span className="card-collection">{item.in_collections[0]}</span>
|
<span className="card-collection">{item.in_collections[0].display_name || item.in_collections[0].name}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -154,17 +154,33 @@ function DiscoverContent({ category, onBreadcrumbsChange, deepLinkData }) {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case 'marketplace:item:install':
|
case 'marketplace:item:install':
|
||||||
if (payload?.item) {
|
if (payload?.item) {
|
||||||
installItem(payload.item.type, payload.item);
|
// Fetch fresh data from API to ensure we get latest version with blur_hash
|
||||||
// Send confirmation back to iframe
|
const itemId = payload.item.id || payload.item.name;
|
||||||
if (iframeRef.current?.contentWindow) {
|
const itemType = payload.item.type;
|
||||||
iframeRef.current.contentWindow.postMessage(
|
|
||||||
{
|
fetch(`${variables.constants.API_URL}/marketplace/item/${itemId}`)
|
||||||
type: 'marketplace:item:installed',
|
.then(res => res.json())
|
||||||
payload: { id: payload.item.id || payload.item.name, installed: true },
|
.then(({ data }) => {
|
||||||
},
|
// Install with fresh data from API
|
||||||
MARKETPLACE_URL
|
installItem(data.type, data);
|
||||||
);
|
})
|
||||||
}
|
.catch(error => {
|
||||||
|
console.error('Failed to fetch item from API, using iframe data:', error);
|
||||||
|
// Fallback to iframe data if API fetch fails
|
||||||
|
installItem(itemType, payload.item);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
// Send confirmation back to iframe
|
||||||
|
if (iframeRef.current?.contentWindow) {
|
||||||
|
iframeRef.current.contentWindow.postMessage(
|
||||||
|
{
|
||||||
|
type: 'marketplace:item:installed',
|
||||||
|
payload: { id: itemId, installed: true },
|
||||||
|
},
|
||||||
|
MARKETPLACE_URL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
152
src/utils/backgroundQueue.js
Normal file
152
src/utils/backgroundQueue.js
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
/**
|
||||||
|
* Background Queue Manager
|
||||||
|
* Manages prefetch queues for background images across all background types
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to safely parse JSON from localStorage
|
||||||
|
* @param {string} key - localStorage key
|
||||||
|
* @param {*} fallback - Fallback value if parsing fails
|
||||||
|
* @returns {*} Parsed value or fallback
|
||||||
|
*/
|
||||||
|
function 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages a prefetch queue for background images
|
||||||
|
*
|
||||||
|
* @class BackgroundQueueManager
|
||||||
|
* @example
|
||||||
|
* const queueManager = new BackgroundQueueManager('imageQueue', 3);
|
||||||
|
* const queue = queueManager.getQueue();
|
||||||
|
* if (queue.length > 0) {
|
||||||
|
* const nextImage = queueManager.shift();
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export class BackgroundQueueManager {
|
||||||
|
/**
|
||||||
|
* Creates a new queue manager
|
||||||
|
* @param {string} storageKey - localStorage key for this queue
|
||||||
|
* @param {number} targetSize - Target number of items to keep in queue (default: 3)
|
||||||
|
*/
|
||||||
|
constructor(storageKey, targetSize = 3) {
|
||||||
|
this.storageKey = storageKey;
|
||||||
|
this.targetSize = targetSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current queue from localStorage
|
||||||
|
* @returns {Array} The queue array
|
||||||
|
*/
|
||||||
|
getQueue() {
|
||||||
|
try {
|
||||||
|
return parseJSON(this.storageKey, []);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to get queue from ${this.storageKey}:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the queue in localStorage
|
||||||
|
* @param {Array} queue - The queue array to save
|
||||||
|
*/
|
||||||
|
setQueue(queue) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(this.storageKey, JSON.stringify(queue));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to save queue to ${this.storageKey}:`, error);
|
||||||
|
// If quota exceeded, clear the queue and try again with empty array
|
||||||
|
if (error.name === 'QuotaExceededError') {
|
||||||
|
try {
|
||||||
|
localStorage.removeItem(this.storageKey);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to clear queue after quota error:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove and return the first item from the queue
|
||||||
|
* @returns {*} The first item in the queue, or undefined if empty
|
||||||
|
*/
|
||||||
|
shift() {
|
||||||
|
try {
|
||||||
|
const queue = this.getQueue();
|
||||||
|
if (queue.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const item = queue.shift();
|
||||||
|
this.setQueue(queue);
|
||||||
|
return item;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to shift queue ${this.storageKey}:`, error);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add items to the end of the queue
|
||||||
|
* @param {Array} items - Items to add to the queue
|
||||||
|
*/
|
||||||
|
push(items) {
|
||||||
|
try {
|
||||||
|
const queue = this.getQueue();
|
||||||
|
const itemsArray = Array.isArray(items) ? items : [items];
|
||||||
|
queue.push(...itemsArray);
|
||||||
|
this.setQueue(queue);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to push to queue ${this.storageKey}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the queue from localStorage
|
||||||
|
*/
|
||||||
|
clear() {
|
||||||
|
try {
|
||||||
|
localStorage.removeItem(this.storageKey);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to clear queue ${this.storageKey}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the queue needs prefetching (below target size)
|
||||||
|
* @returns {boolean} True if queue size is below target
|
||||||
|
*/
|
||||||
|
needsPrefetch() {
|
||||||
|
const queue = this.getQueue();
|
||||||
|
return queue.length < this.targetSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of items needed to reach target size
|
||||||
|
* @returns {number} Number of items to prefetch
|
||||||
|
*/
|
||||||
|
getSpaceNeeded() {
|
||||||
|
const queue = this.getQueue();
|
||||||
|
return Math.max(0, this.targetSize - queue.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current queue size
|
||||||
|
* @returns {number} Number of items in queue
|
||||||
|
*/
|
||||||
|
size() {
|
||||||
|
return this.getQueue().length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BackgroundQueueManager;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import EventBus from 'utils/eventbus';
|
import EventBus from 'utils/eventbus';
|
||||||
|
import { clearQueuesOnSettingChange } from 'utils/queueOperations';
|
||||||
|
|
||||||
// todo: relocate this function
|
// todo: relocate this function
|
||||||
function showReminder() {
|
function showReminder() {
|
||||||
@@ -39,7 +40,7 @@ export function install(type, input, sideload, collection) {
|
|||||||
localStorage.setItem('backgroundType', 'photo_pack');
|
localStorage.setItem('backgroundType', 'photo_pack');
|
||||||
localStorage.removeItem('backgroundchange');
|
localStorage.removeItem('backgroundchange');
|
||||||
// Clear image queue to ensure fresh background loads
|
// Clear image queue to ensure fresh background loads
|
||||||
localStorage.removeItem('imageQueue');
|
clearQueuesOnSettingChange('packInstall');
|
||||||
// Set refresh event to emit after installed data is saved
|
// Set refresh event to emit after installed data is saved
|
||||||
refreshEvent = 'backgroundrefresh';
|
refreshEvent = 'backgroundrefresh';
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import EventBus from 'utils/eventbus';
|
import EventBus from 'utils/eventbus';
|
||||||
|
import { clearQueuesOnSettingChange } from 'utils/queueOperations';
|
||||||
|
|
||||||
// todo: relocate this function
|
// todo: relocate this function
|
||||||
function showReminder() {
|
function showReminder() {
|
||||||
@@ -67,7 +68,7 @@ export function uninstall(type, name) {
|
|||||||
}
|
}
|
||||||
localStorage.removeItem('backgroundchange');
|
localStorage.removeItem('backgroundchange');
|
||||||
// Clear image queue to ensure fresh background loads
|
// Clear image queue to ensure fresh background loads
|
||||||
localStorage.removeItem('imageQueue');
|
clearQueuesOnSettingChange('packUninstall');
|
||||||
// Set refresh event to emit after installed data is saved
|
// Set refresh event to emit after installed data is saved
|
||||||
refreshEvent = 'marketplacebackgrounduninstall';
|
refreshEvent = 'marketplacebackgrounduninstall';
|
||||||
break;
|
break;
|
||||||
|
|||||||
102
src/utils/queueOperations.js
Normal file
102
src/utils/queueOperations.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* Queue Operations
|
||||||
|
* Centralized queue clearing logic for background prefetch queues
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queue key mapping for each background type
|
||||||
|
*/
|
||||||
|
const QUEUE_KEYS = {
|
||||||
|
api: 'imageQueue',
|
||||||
|
photo_pack: 'photoPackQueue',
|
||||||
|
custom: 'customQueue',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear background prefetch queues
|
||||||
|
* @param {string} type - Queue type to clear: 'all', 'api', 'photo_pack', or 'custom'
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Clear all queues
|
||||||
|
* clearBackgroundQueues('all');
|
||||||
|
*
|
||||||
|
* // Clear only API queue
|
||||||
|
* clearBackgroundQueues('api');
|
||||||
|
*/
|
||||||
|
export function clearBackgroundQueues(type = 'all') {
|
||||||
|
try {
|
||||||
|
if (type === 'all') {
|
||||||
|
// Clear all queue types
|
||||||
|
Object.values(QUEUE_KEYS).forEach((key) => {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
});
|
||||||
|
} else if (QUEUE_KEYS[type]) {
|
||||||
|
// Clear specific queue type
|
||||||
|
localStorage.removeItem(QUEUE_KEYS[type]);
|
||||||
|
} else {
|
||||||
|
console.warn(`Unknown queue type: ${type}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to clear background queues:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear queues based on setting changes
|
||||||
|
* Maps specific setting changes to the appropriate queue clearing strategy
|
||||||
|
*
|
||||||
|
* @param {string} settingName - The name of the setting that changed
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // User changed background type
|
||||||
|
* clearQueuesOnSettingChange('backgroundType');
|
||||||
|
*
|
||||||
|
* // User changed API categories
|
||||||
|
* clearQueuesOnSettingChange('apiCategories');
|
||||||
|
*
|
||||||
|
* // User installed a photo pack
|
||||||
|
* clearQueuesOnSettingChange('packInstall');
|
||||||
|
*/
|
||||||
|
export function clearQueuesOnSettingChange(settingName) {
|
||||||
|
const clearTriggers = {
|
||||||
|
// Clear all queues
|
||||||
|
backgroundType: 'all', // Background type changed
|
||||||
|
|
||||||
|
// Clear API queue only
|
||||||
|
backgroundAPI: 'api', // API provider changed (Mue <-> Unsplash)
|
||||||
|
apiCategories: 'api', // Categories changed
|
||||||
|
apiQuality: 'api', // Quality changed
|
||||||
|
unsplashCollections: 'api', // Unsplash collections changed
|
||||||
|
backgroundExclude: 'api', // Exclude list changed
|
||||||
|
|
||||||
|
// Clear photo pack queue only
|
||||||
|
packInstall: 'photo_pack', // Photo pack installed
|
||||||
|
packUninstall: 'photo_pack', // Photo pack uninstalled
|
||||||
|
|
||||||
|
// Clear custom queue only
|
||||||
|
customModified: 'custom', // Custom background modified
|
||||||
|
customDeleted: 'custom', // Custom background deleted
|
||||||
|
};
|
||||||
|
|
||||||
|
const queueType = clearTriggers[settingName];
|
||||||
|
if (queueType) {
|
||||||
|
clearBackgroundQueues(queueType);
|
||||||
|
} else {
|
||||||
|
console.warn(`No queue clearing mapped for setting: ${settingName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the queue key for a specific background type
|
||||||
|
* @param {string} type - Background type: 'api', 'photo_pack', or 'custom'
|
||||||
|
* @returns {string} The localStorage key for that queue
|
||||||
|
*/
|
||||||
|
export function getQueueKey(type) {
|
||||||
|
return QUEUE_KEYS[type] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
clearBackgroundQueues,
|
||||||
|
clearQueuesOnSettingChange,
|
||||||
|
getQueueKey,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user