fixing wallpaper issue

This commit is contained in:
2026-08-07 12:53:39 -03:00
parent 7635471ed6
commit 0ecf0eed62
6 changed files with 165 additions and 180 deletions
+7 -9
View File
@@ -43,16 +43,14 @@ const getWallpaperUrlByName = async (name: string): Promise<string | undefined>
JSON.parse(localStorage.getItem('userWallpapers') || '[]');
const foundInUser = storedUserWallpapers.find((w: WallpaperType) => w.name === name);
if (foundInUser) {
try {
const wallpaperData = await getWallpaperFromChromeStorageLocal(name);
if (wallpaperData && wallpaperData.startsWith('http')) {
resolved = wallpaperData;
} else {
resolved = wallpaperData || undefined;
resolved = foundInUser.url || foundInUser.base64;
if (!resolved) {
try {
resolved = (await getWallpaperFromChromeStorageLocal(name)) || undefined;
} catch (error) {
console.error('Error getting wallpaper from chrome storage', error);
resolved = undefined;
}
} catch (error) {
console.error('Error getting wallpaper from chrome storage', error);
resolved = undefined;
}
}
} catch (error) {
+98 -96
View File
@@ -62,7 +62,11 @@ const ThemeTab: React.FC<ThemeTabProps> = ({
setNewWallpaperName('');
setNewWallpaperUrl('');
} catch (error) {
alert('Error adding wallpaper. Please check the URL and try again.');
alert(
error instanceof Error
? error.message
: 'Error adding wallpaper. Please check the URL and try again.',
);
console.error(error);
}
};
@@ -160,105 +164,103 @@ const ThemeTab: React.FC<ThemeTabProps> = ({
<span className="w-12 text-right text-sm text-slate-200">{config.wallpaperOpacity}%</span>
</div>
</div>
{chromeStorageAvailable && (
<>
<div>
<h3 className="text-slate-300 text-sm font-semibold mb-2">User Wallpapers</h3>
<div className="flex flex-col gap-2">
{userWallpapers.map((wallpaper) => (
<div
key={wallpaper.name}
className="liquid-surface flex items-center justify-between rounded-xl p-2.5"
<div>
<h3 className="text-slate-300 text-sm font-semibold mb-2">User Wallpapers</h3>
<div className="flex flex-col gap-2">
{userWallpapers.map((wallpaper) => (
<div
key={wallpaper.name}
className="liquid-surface flex items-center justify-between rounded-xl p-2.5"
>
<span className="truncate">{wallpaper.name}</span>
<button
onClick={() => onDeleteWallpaper(wallpaper)}
className="liquid-edit-action liquid-focus text-red-300 hover:text-red-100"
aria-label={`Delete ${wallpaper.name}`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
className="bi bi-trash"
viewBox="0 0 16 16"
>
<span className="truncate">{wallpaper.name}</span>
<button
onClick={() => onDeleteWallpaper(wallpaper)}
className="liquid-edit-action liquid-focus text-red-300 hover:text-red-100"
aria-label={`Delete ${wallpaper.name}`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
className="bi bi-trash"
viewBox="0 0 16 16"
>
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z" />
<path
fillRule="evenodd"
d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"
/>
</svg>
</button>
</div>
))}
</div>
</div>
<div>
<h3 className="text-slate-300 text-sm font-semibold mb-2">Add New Wallpaper</h3>
<div className="flex flex-col gap-2">
<input
type="text"
placeholder="Wallpaper Name (optional for URLs)"
value={newWallpaperName}
onChange={(e) => setNewWallpaperName(e.target.value)}
className="liquid-input p-2.5"
/>
<div className="flex flex-col gap-2 sm:flex-row">
<input
type="text"
placeholder="Image URL"
value={newWallpaperUrl}
onChange={(e) => setNewWallpaperUrl(e.target.value)}
className="liquid-input p-2.5"
/>
<button
onClick={handleAddWallpaper}
className="liquid-button liquid-button-primary liquid-focus py-2.5 px-4"
>
Add
</button>
</div>
<div className="flex items-center justify-center w-full">
<label
htmlFor="file-upload"
className="liquid-surface liquid-ghost-tile flex flex-col items-center justify-center w-full h-32 cursor-pointer transition-all duration-200 ease-ios"
>
<div className="flex flex-col items-center justify-center pt-5 pb-6">
<svg
className="w-8 h-8 mb-4 text-gray-400"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 20 16"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"
/>
</svg>
<p className="mb-2 text-sm text-gray-400">
<span className="font-semibold">Click to upload</span> or drag and drop
</p>
<p className="text-xs text-gray-400">PNG, JPG, WEBP, etc.</p>
</div>
<input
id="file-upload"
type="file"
className="hidden"
onChange={handleFileUpload}
ref={fileInputRef}
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z" />
<path
fillRule="evenodd"
d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"
/>
</label>
</div>
</svg>
</button>
</div>
))}
</div>
</div>
<div>
<h3 className="text-slate-300 text-sm font-semibold mb-2">Add New Wallpaper</h3>
<div className="flex flex-col gap-2">
<input
type="text"
placeholder="Wallpaper Name (optional for URLs)"
value={newWallpaperName}
onChange={(e) => setNewWallpaperName(e.target.value)}
className="liquid-input p-2.5"
/>
<div className="flex flex-col gap-2 sm:flex-row">
<input
type="text"
placeholder="Image URL"
value={newWallpaperUrl}
onChange={(e) => setNewWallpaperUrl(e.target.value)}
className="liquid-input p-2.5"
/>
<button
onClick={handleAddWallpaper}
className="liquid-button liquid-button-primary liquid-focus py-2.5 px-4"
>
Add
</button>
</div>
</>
)}
{chromeStorageAvailable && (
<div className="flex items-center justify-center w-full">
<label
htmlFor="file-upload"
className="liquid-surface liquid-ghost-tile flex flex-col items-center justify-center w-full h-32 cursor-pointer transition-all duration-200 ease-ios"
>
<div className="flex flex-col items-center justify-center pt-5 pb-6">
<svg
className="w-8 h-8 mb-4 text-gray-400"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 20 16"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"
/>
</svg>
<p className="mb-2 text-sm text-gray-400">
<span className="font-semibold">Click to upload</span> or drag and drop
</p>
<p className="text-xs text-gray-400">PNG, JPG, WEBP, etc.</p>
</div>
<input
id="file-upload"
type="file"
className="hidden"
onChange={handleFileUpload}
ref={fileInputRef}
/>
</label>
</div>
)}
</div>
</div>
<div className="flex justify-center pt-2">
<button
onClick={onNextWallpaper}
+24 -2
View File
@@ -1,6 +1,7 @@
import { Config, Wallpaper } from '../../types';
import {
addWallpaperToChromeStorageLocal,
checkChromeStorageLocalAvailable,
removeWallpaperFromChromeStorageLocal,
} from '../utils/StorageLocalManager';
@@ -43,6 +44,16 @@ const safeParse = (value: string | null): unknown => {
const toStorageString = (value: unknown): string =>
typeof value === 'string' ? value : JSON.stringify(value);
const getWallpaperNameFromUrl = (url: URL): string => {
const pathName = url.pathname.split('/').filter(Boolean).pop();
if (!pathName) return url.hostname;
try {
return decodeURIComponent(pathName);
} catch {
return pathName;
}
};
const isPlainObject = (v: unknown): v is Record<string, unknown> =>
typeof v === 'object' && v !== null && !Array.isArray(v);
@@ -100,8 +111,18 @@ export const ConfigurationService = {
},
async addWallpaper(name: string, url: string): Promise<Wallpaper> {
const finalName = await addWallpaperToChromeStorageLocal(name, url);
return { name: finalName };
const trimmedUrl = url.trim();
let parsedUrl: URL;
try {
parsedUrl = new URL(trimmedUrl);
} catch {
throw new Error('Please enter a valid wallpaper URL.');
}
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
throw new Error('Wallpaper URLs must use HTTP or HTTPS.');
}
const finalName = name.trim() || getWallpaperNameFromUrl(parsedUrl) || 'Wallpaper';
return { name: finalName, url: parsedUrl.href };
},
async addWallpaperFile(file: File): Promise<Wallpaper> {
@@ -130,6 +151,7 @@ export const ConfigurationService = {
},
async deleteWallpaper(wallpaper: Wallpaper): Promise<void> {
if (wallpaper.url || wallpaper.base64 || !checkChromeStorageLocalAvailable()) return;
await removeWallpaperFromChromeStorageLocal(wallpaper.name);
},
+28 -65
View File
@@ -89,83 +89,46 @@ export async function removeCachedIconFromChromeStorageLocal(sourceUrl: string):
/**
* Adds a new wallpaper to chrome.storage.local.
* If the URL is fetchable, it will be stored as base64 and the name will be derived from the URL.
* If the URL is not fetchable (e.g., CORS), it will be stored as a URL and the provided name will be used.
* File uploads are stored as base64 while remote wallpapers remain URLs.
* @param name Wallpaper name (string), used as a fallback.
* @param url Wallpaper image URL (string) or base64 data URL.
* @returns Promise<string> The name under which the wallpaper was stored.
* @throws Error if chrome.storage.local is unavailable or if a name is not provided for a non-fetchable URL.
* @throws Error if chrome.storage.local is unavailable or the wallpaper data is invalid.
*/
export async function addWallpaperToChromeStorageLocal(name: string, url: string): Promise<string> {
if (!checkChromeStorageLocalAvailable()) {
throw new Error('chrome.storage.local is not available');
}
let finalName = name.trim();
if (url.startsWith('data:')) {
// This is a base64 encoded image from a file upload.
// The name is the file name.
return new Promise<void>((resolve, reject) => {
if (window.chrome?.storage?.local) {
window.chrome.storage.local.set({ [name]: url }, function () {
if (window.chrome?.runtime?.lastError) {
reject(new Error(window.chrome.runtime.lastError.message));
} else {
resolve();
}
});
} else {
reject(new Error('chrome.storage.local is not available'));
}
}).then(() => name);
}
// This is a URL. Let's try to fetch it.
try {
const response = await fetch(url);
if (!response.ok) throw new Error('Failed to fetch image');
const imageBlob = await response.blob();
const reader = new FileReader();
const base64 = await new Promise<string>((resolve, reject) => {
reader.onloadend = () => resolve(reader.result as string);
reader.onerror = reject;
reader.readAsDataURL(imageBlob);
});
// If successful, use the filename from URL as the name.
const finalName = url.substring(url.lastIndexOf('/') + 1).replace(/[?#].*$/, '') || name;
return new Promise<void>((resolve, reject) => {
if (window.chrome?.storage?.local) {
window.chrome.storage.local.set({ [finalName]: base64 }, function () {
if (window.chrome?.runtime?.lastError) {
reject(new Error(window.chrome.runtime.lastError.message));
} else {
resolve();
}
});
} else {
reject(new Error('chrome.storage.local is not available'));
}
}).then(() => finalName);
} catch (error) {
// If fetch fails (e.g., CORS), store the URL directly with the user-provided name.
console.warn('Could not fetch wallpaper, storing URL instead. Error:', error);
if (!name) {
throw new Error("A name for the wallpaper is required when the URL can't be accessed.");
if (!finalName) throw new Error('A name is required for an uploaded wallpaper.');
} else {
let parsedUrl: URL;
try {
parsedUrl = new URL(url);
} catch {
throw new Error('Please enter a valid wallpaper URL.');
}
return new Promise<void>((resolve, reject) => {
if (window.chrome?.storage?.local) {
window.chrome.storage.local.set({ [name]: url }, function () {
if (window.chrome?.runtime?.lastError) {
reject(new Error(window.chrome.runtime.lastError.message));
} else {
resolve();
}
});
} else {
reject(new Error('chrome.storage.local is not available'));
}
}).then(() => name);
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
throw new Error('Wallpaper URLs must use HTTP or HTTPS.');
}
finalName = finalName || parsedUrl.pathname.split('/').filter(Boolean).pop() || parsedUrl.hostname;
}
return new Promise<void>((resolve, reject) => {
if (!window.chrome?.storage?.local) {
reject(new Error('chrome.storage.local is not available'));
return;
}
window.chrome.storage.local.set({ [finalName]: url }, function () {
if (window.chrome?.runtime?.lastError) {
reject(new Error(window.chrome.runtime.lastError.message));
} else {
resolve();
}
});
}).then(() => finalName);
}
/**