Compare commits
	
		
			3 Commits
		
	
	
		
			v0.1.3-VT
			...
			c60cb24dd4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c60cb24dd4 | |||
| 2f3949c2e3 | |||
| 9b818b05f9 | 
@@ -27,7 +27,7 @@ jobs:
 | 
				
			|||||||
      - name: Create zip archive
 | 
					      - name: Create zip archive
 | 
				
			||||||
        run: zip -r vision-start-${{ gitea.ref_name }}.zip vision-start
 | 
					        run: zip -r vision-start-${{ gitea.ref_name }}.zip vision-start
 | 
				
			||||||
      - name: Upload artifact
 | 
					      - name: Upload artifact
 | 
				
			||||||
        uses: actions/upload-artifact@v4
 | 
					        uses: actions/upload-artifact@v3
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          name: release-zip
 | 
					          name: release-zip
 | 
				
			||||||
          path: vision-start-${{ gitea.ref_name }}.zip
 | 
					          path: vision-start-${{ gitea.ref_name }}.zip
 | 
				
			||||||
@@ -44,7 +44,7 @@ jobs:
 | 
				
			|||||||
      - name: Setup required tools
 | 
					      - name: Setup required tools
 | 
				
			||||||
        run: sudo apt-get install jq curl -y
 | 
					        run: sudo apt-get install jq curl -y
 | 
				
			||||||
      - name: Download artifact
 | 
					      - name: Download artifact
 | 
				
			||||||
        uses: actions/download-artifact@v4
 | 
					        uses: actions/download-artifact@v3
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          name: release-zip
 | 
					          name: release-zip
 | 
				
			||||||
      - name: Run VirusTotal check
 | 
					      - name: Run VirusTotal check
 | 
				
			||||||
@@ -74,7 +74,7 @@ jobs:
 | 
				
			|||||||
      - name: Check out repository code
 | 
					      - name: Check out repository code
 | 
				
			||||||
        uses: actions/checkout@v4
 | 
					        uses: actions/checkout@v4
 | 
				
			||||||
      - name: Download artifact
 | 
					      - name: Download artifact
 | 
				
			||||||
        uses: actions/download-artifact@v4
 | 
					        uses: actions/download-artifact@v3
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          name: release-zip
 | 
					          name: release-zip
 | 
				
			||||||
      - name: Release zip
 | 
					      - name: Release zip
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										88
									
								
								App.tsx
									
									
									
									
									
								
							
							
						
						
									
										88
									
								
								App.tsx
									
									
									
									
									
								
							@@ -1,21 +1,20 @@
 | 
				
			|||||||
import React, { useState, useEffect } from 'react';
 | 
					import { useState, useEffect } from 'react';
 | 
				
			||||||
import ConfigurationModal from './components/ConfigurationModal';
 | 
					import ConfigurationModal from './components/ConfigurationModal';
 | 
				
			||||||
import ServerWidget from './components/ServerWidget';
 | 
					import ServerWidget from './components/ServerWidget';
 | 
				
			||||||
import { DEFAULT_CATEGORIES } from './constants';
 | 
					import { DEFAULT_CATEGORIES } from './constants';
 | 
				
			||||||
import { Category, Website, Wallpaper, Config } from './types';
 | 
					import { Category, Website, Config } from './types';
 | 
				
			||||||
import WebsiteEditModal from './components/WebsiteEditModal';
 | 
					import WebsiteEditModal from './components/WebsiteEditModal';
 | 
				
			||||||
import CategoryEditModal from './components/CategoryEditModal';
 | 
					import CategoryEditModal from './components/CategoryEditModal';
 | 
				
			||||||
import Header from './components/layout/Header';
 | 
					import Header from './components/layout/Header';
 | 
				
			||||||
import EditButton from './components/layout/EditButton';
 | 
					import EditButton from './components/layout/EditButton';
 | 
				
			||||||
import ConfigurationButton from './components/layout/ConfigurationButton';
 | 
					import ConfigurationButton from './components/layout/ConfigurationButton';
 | 
				
			||||||
import CategoryGroup from './components/layout/CategoryGroup';
 | 
					import CategoryGroup from './components/layout/CategoryGroup';
 | 
				
			||||||
 | 
					import Wallpaper from './components/Wallpaper';
 | 
				
			||||||
import { baseWallpapers } from './components/utils/baseWallpapers';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const defaultConfig: Config = {
 | 
					const defaultConfig: Config = {
 | 
				
			||||||
  title: 'Vision Start',
 | 
					  title: 'Vision Start',
 | 
				
			||||||
  subtitle: 'Your personal portal to the web.',
 | 
					  subtitle: 'Your personal portal to the web.',
 | 
				
			||||||
  backgroundUrls: ['https://i.imgur.com/C6ynAtX.jpeg'],
 | 
					  currentWallpapers: ['Abstract'],
 | 
				
			||||||
  wallpaperFrequency: '1d',
 | 
					  wallpaperFrequency: '1d',
 | 
				
			||||||
  wallpaperBlur: 0,
 | 
					  wallpaperBlur: 0,
 | 
				
			||||||
  wallpaperBrightness: 100,
 | 
					  wallpaperBrightness: 100,
 | 
				
			||||||
@@ -60,9 +59,6 @@ const App: React.FC = () => {
 | 
				
			|||||||
      const storedConfig = localStorage.getItem('config');
 | 
					      const storedConfig = localStorage.getItem('config');
 | 
				
			||||||
      if (storedConfig) {
 | 
					      if (storedConfig) {
 | 
				
			||||||
        const parsedConfig = JSON.parse(storedConfig);
 | 
					        const parsedConfig = JSON.parse(storedConfig);
 | 
				
			||||||
        if (!parsedConfig.backgroundUrls) {
 | 
					 | 
				
			||||||
          parsedConfig.backgroundUrls = [parsedConfig.backgroundUrl].filter(Boolean);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return { ...defaultConfig, ...parsedConfig };
 | 
					        return { ...defaultConfig, ...parsedConfig };
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
@@ -70,62 +66,12 @@ const App: React.FC = () => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    return { ...defaultConfig };
 | 
					    return { ...defaultConfig };
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  const [userWallpapers, setUserWallpapers] = useState<Wallpaper[]>(() => {
 | 
					 | 
				
			||||||
    const storedUserWallpapers = localStorage.getItem('userWallpapers');
 | 
					 | 
				
			||||||
    return storedUserWallpapers ? JSON.parse(storedUserWallpapers) : [];
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
  const [currentWallpaper, setCurrentWallpaper] = useState<string>('');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const allWallpapers = [...baseWallpapers, ...userWallpapers];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    const getFrequencyInMs = (frequency: string) => {
 | 
					 | 
				
			||||||
      const value = parseInt(frequency.slice(0, -1));
 | 
					 | 
				
			||||||
      const unit = frequency.slice(-1);
 | 
					 | 
				
			||||||
      if (unit === 'h') return value * 60 * 60 * 1000;
 | 
					 | 
				
			||||||
      if (unit === 'd') return value * 24 * 60 * 60 * 1000;
 | 
					 | 
				
			||||||
      return 24 * 60 * 60 * 1000; // Default to 1 day
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const wallpaperState = JSON.parse(localStorage.getItem('wallpaperState') || '{}');
 | 
					 | 
				
			||||||
    const lastChanged = wallpaperState.lastChanged ? new Date(wallpaperState.lastChanged).getTime() : 0;
 | 
					 | 
				
			||||||
    const frequency = getFrequencyInMs(config.wallpaperFrequency);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const updateWallpaper = () => {
 | 
					 | 
				
			||||||
      const availableWallpapers = allWallpapers.filter(w => config.backgroundUrls.includes(w.url || w.base64));
 | 
					 | 
				
			||||||
      if (availableWallpapers.length > 0) {
 | 
					 | 
				
			||||||
        const currentWallpaperFromState = allWallpapers.find(w => w.name === wallpaperState.current);
 | 
					 | 
				
			||||||
        const currentIndex = currentWallpaperFromState ? availableWallpapers.findIndex(w => w.name === currentWallpaperFromState.name) : -1;
 | 
					 | 
				
			||||||
        const nextIndex = (currentIndex + 1) % availableWallpapers.length;
 | 
					 | 
				
			||||||
        const newWallpaper = availableWallpapers[nextIndex];
 | 
					 | 
				
			||||||
        const newWallpaperUrl = newWallpaper.url || newWallpaper.base64;
 | 
					 | 
				
			||||||
        setCurrentWallpaper(newWallpaperUrl || '');
 | 
					 | 
				
			||||||
        localStorage.setItem('wallpaperState', JSON.stringify({ current: newWallpaper.name, lastChanged: new Date().toISOString() }));
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        setCurrentWallpaper('');
 | 
					 | 
				
			||||||
        localStorage.removeItem('wallpaperState');
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const currentWallpaperDetails = allWallpapers.find(w => w.name === wallpaperState.current);
 | 
					 | 
				
			||||||
    const isCurrentWallpaperValid = currentWallpaperDetails && config.backgroundUrls.includes(currentWallpaperDetails.url || currentWallpaperDetails.base64 || '');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!isCurrentWallpaperValid || Date.now() - lastChanged > frequency) {
 | 
					 | 
				
			||||||
      updateWallpaper();
 | 
					 | 
				
			||||||
    } else if (currentWallpaperDetails) {
 | 
					 | 
				
			||||||
      setCurrentWallpaper(currentWallpaperDetails.url || currentWallpaperDetails.base64 || '');
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      // Fallback for when there's no valid wallpaper state
 | 
					 | 
				
			||||||
      updateWallpaper();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }, [config.backgroundUrls, config.wallpaperFrequency, allWallpapers]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    localStorage.setItem('categories', JSON.stringify(categories));
 | 
					 | 
				
			||||||
    localStorage.setItem('config', JSON.stringify(config));
 | 
					    localStorage.setItem('config', JSON.stringify(config));
 | 
				
			||||||
  }, [categories, config]);
 | 
					  }, [config]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleSaveConfig = (newConfig: any) => {
 | 
					  const handleSaveConfig = (newConfig: Config) => {
 | 
				
			||||||
    setConfig(newConfig);
 | 
					    setConfig(newConfig);
 | 
				
			||||||
    setIsConfigModalOpen(false);
 | 
					    setIsConfigModalOpen(false);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -201,7 +147,6 @@ const App: React.FC = () => {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleMoveWebsite = (website: Website, direction: 'left' | 'right') => {
 | 
					  const handleMoveWebsite = (website: Website, direction: 'left' | 'right') => {
 | 
				
			||||||
    const categoryIndex = categories.findIndex(c => c.id === website.categoryId);
 | 
					 | 
				
			||||||
    if (categoryIndex === -1) return;
 | 
					    if (categoryIndex === -1) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const category = categories[categoryIndex];
 | 
					    const category = categories[categoryIndex];
 | 
				
			||||||
@@ -259,14 +204,13 @@ const App: React.FC = () => {
 | 
				
			|||||||
    <main
 | 
					    <main
 | 
				
			||||||
      className={`min-h-screen w-full flex flex-col items-center ${getAlignmentClass(config.alignment)} p-4`}
 | 
					      className={`min-h-screen w-full flex flex-col items-center ${getAlignmentClass(config.alignment)} p-4`}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <div
 | 
					      <Wallpaper
 | 
				
			||||||
        className="fixed inset-0 w-full h-full bg-cover bg-center bg-fixed -z-10"
 | 
					        wallpaperNames={config.currentWallpapers}
 | 
				
			||||||
        style={{
 | 
					        blur={config.wallpaperBlur}
 | 
				
			||||||
          backgroundImage: `url('${currentWallpaper}')`,
 | 
					        brightness={config.wallpaperBrightness}
 | 
				
			||||||
          filter: `blur(${config.wallpaperBlur}px) brightness(${config.wallpaperBrightness}%)`,
 | 
					        opacity={config.wallpaperOpacity}
 | 
				
			||||||
          opacity: `${config.wallpaperOpacity}%`,
 | 
					        wallpaperFrequency={config.wallpaperFrequency}
 | 
				
			||||||
        }}
 | 
					      />
 | 
				
			||||||
      ></div>
 | 
					 | 
				
			||||||
      <EditButton isEditing={isEditing} onClick={() => setIsEditing(!isEditing)} />
 | 
					      <EditButton isEditing={isEditing} onClick={() => setIsEditing(!isEditing)} />
 | 
				
			||||||
      <ConfigurationButton onClick={() => setIsConfigModalOpen(true)} />
 | 
					      <ConfigurationButton onClick={() => setIsConfigModalOpen(true)} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -305,11 +249,7 @@ const App: React.FC = () => {
 | 
				
			|||||||
        )}
 | 
					        )}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {config.serverWidget.enabled && (
 | 
					      {config.serverWidget.enabled && <ServerWidget config={config} />}
 | 
				
			||||||
        <div className="absolute bottom-4 right-4">
 | 
					 | 
				
			||||||
          <ServerWidget config={config} />
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      )}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {(editingWebsite || addingWebsite) && (
 | 
					      {(editingWebsite || addingWebsite) && (
 | 
				
			||||||
        <WebsiteEditModal
 | 
					        <WebsiteEditModal
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										25
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								README.md
									
									
									
									
									
								
							@@ -18,17 +18,6 @@ Vision Start is not yet available on Chrome Web Store, but it can be installed m
 | 
				
			|||||||
6. Click on "Load unpacked" and select the `vision-start` folder you extracted in step 3
 | 
					6. Click on "Load unpacked" and select the `vision-start` folder you extracted in step 3
 | 
				
			||||||
7. The extension should now be installed! Just open a new tab to see it in action.
 | 
					7. The extension should now be installed! Just open a new tab to see it in action.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Backgrounds
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
It comes with a selection of some nice pre-defined backgrounds. You can also upload up to one image to it.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* **Abstract**
 | 
					 | 
				
			||||||
* **Abstract Red**
 | 
					 | 
				
			||||||
* **Beach**
 | 
					 | 
				
			||||||
* **Dark**
 | 
					 | 
				
			||||||
* **Mountain**
 | 
					 | 
				
			||||||
* **Waves**
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Features
 | 
					## Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* **Customizable Website Tiles:** Add, edit, and organize your favorite websites for quick access.
 | 
					* **Customizable Website Tiles:** Add, edit, and organize your favorite websites for quick access.
 | 
				
			||||||
@@ -38,6 +27,12 @@ It comes with a selection of some nice pre-defined backgrounds. You can also upl
 | 
				
			|||||||
* **Icon Library:** It uses the [Dashboard Icon library](https://dashboardicons.com/) for a better look and feel. It also supports auto-fetch for some websites.
 | 
					* **Icon Library:** It uses the [Dashboard Icon library](https://dashboardicons.com/) for a better look and feel. It also supports auto-fetch for some websites.
 | 
				
			||||||
* **Future**: a long to do list :(
 | 
					* **Future**: a long to do list :(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Backgrounds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It comes with a selection of some nice pre-defined backgrounds: **Abstract**, **Abstract Red**, **Beach**, **Dark**, **Mountain**, **Waves**.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can also upload your own images on it (or fetch it from the web).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Running Locally
 | 
					## Running Locally
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Prerequisites:** Node.js
 | 
					**Prerequisites:** Node.js
 | 
				
			||||||
@@ -60,10 +55,10 @@ npm run dev
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
* [x] Multiple Wallpapers
 | 
					* [x] Multiple Wallpapers
 | 
				
			||||||
* [x] Remake icons
 | 
					* [x] Remake icons
 | 
				
			||||||
* [] Increase offline compatibility (might not be possible)
 | 
					* [/] Increase offline compatibility (might not be possible)
 | 
				
			||||||
  * Use chrome.storage.local for user wallpapers -- this one is
 | 
					  - [x] Use chrome.storage.local for user wallpapers -- this one is
 | 
				
			||||||
  * Use chrome.storage.local for some logos -- a bit hard
 | 
					  - [ ] Use chrome.storage.local for some logos -- a bit hard
 | 
				
			||||||
    * Some logos have CORS enabled, we can add `"<all_urls>"` to the manifest.json file and cache them on storage local
 | 
					    - Some logos have CORS enabled, we can add `"<all_urls>"` to the manifest.json file and cache them on storage local
 | 
				
			||||||
* Dynamic Weather Widget
 | 
					* Dynamic Weather Widget
 | 
				
			||||||
  * A box with information about the current weather, with manual entry on the location
 | 
					  * A box with information about the current weather, with manual entry on the location
 | 
				
			||||||
  * Display current temperature, weather condition (e.g., "Sunny," "Cloudy"), and a corresponding icon
 | 
					  * Display current temperature, weather condition (e.g., "Sunny," "Cloudy"), and a corresponding icon
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import React, { useState } from 'react';
 | 
					import { useState } from 'react';
 | 
				
			||||||
import { Category } from '../types';
 | 
					import { Category } from '../types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface CategoryEditModalProps {
 | 
					interface CategoryEditModalProps {
 | 
				
			||||||
@@ -12,10 +12,6 @@ interface CategoryEditModalProps {
 | 
				
			|||||||
const CategoryEditModal: React.FC<CategoryEditModalProps> = ({ category, edit, onClose, onSave, onDelete }) => {
 | 
					const CategoryEditModal: React.FC<CategoryEditModalProps> = ({ category, edit, onClose, onSave, onDelete }) => {
 | 
				
			||||||
  const [name, setName] = useState(category ? category.name : '');
 | 
					  const [name, setName] = useState(category ? category.name : '');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleSave = () => {
 | 
					 | 
				
			||||||
    onSave(name);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleOverlayClick = (e: React.MouseEvent<HTMLDivElement>) => {
 | 
					  const handleOverlayClick = (e: React.MouseEvent<HTMLDivElement>) => {
 | 
				
			||||||
    if (e.target === e.currentTarget) {
 | 
					    if (e.target === e.currentTarget) {
 | 
				
			||||||
      onClose();
 | 
					      onClose();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@ import { Server, Wallpaper } from '../types';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import Dropdown from './Dropdown';
 | 
					import Dropdown from './Dropdown';
 | 
				
			||||||
import { baseWallpapers } from './utils/baseWallpapers';
 | 
					import { baseWallpapers } from './utils/baseWallpapers';
 | 
				
			||||||
 | 
					import { addWallpaperToChromeStorageLocal, removeWallpaperFromChromeStorageLocal, checkChromeStorageLocalAvailable } from './utils/StorageLocalManager';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ConfigurationModalProps {
 | 
					interface ConfigurationModalProps {
 | 
				
			||||||
  onClose: () => void;
 | 
					  onClose: () => void;
 | 
				
			||||||
@@ -37,7 +38,9 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
 | 
				
			|||||||
      format: 'h:mm A',
 | 
					      format: 'h:mm A',
 | 
				
			||||||
      ...currentConfig.clock,
 | 
					      ...currentConfig.clock,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    backgroundUrls: currentConfig.backgroundUrls || [],
 | 
					    currentWallpapers: Array.isArray(currentConfig.currentWallpapers)
 | 
				
			||||||
 | 
					      ? currentConfig.currentWallpapers.filter((name: string) => typeof name === 'string')
 | 
				
			||||||
 | 
					      : [],
 | 
				
			||||||
    wallpaperFrequency: currentConfig.wallpaperFrequency || '1d',
 | 
					    wallpaperFrequency: currentConfig.wallpaperFrequency || '1d',
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  const [activeTab, setActiveTab] = useState('general');
 | 
					  const [activeTab, setActiveTab] = useState('general');
 | 
				
			||||||
@@ -46,12 +49,14 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
 | 
				
			|||||||
  const [newWallpaperName, setNewWallpaperName] = useState('');
 | 
					  const [newWallpaperName, setNewWallpaperName] = useState('');
 | 
				
			||||||
  const [newWallpaperUrl, setNewWallpaperUrl] = useState('');
 | 
					  const [newWallpaperUrl, setNewWallpaperUrl] = useState('');
 | 
				
			||||||
  const [userWallpapers, setUserWallpapers] = useState<Wallpaper[]>([]);
 | 
					  const [userWallpapers, setUserWallpapers] = useState<Wallpaper[]>([]);
 | 
				
			||||||
 | 
					  const [chromeStorageAvailable, setChromeStorageAvailable] = useState(false);
 | 
				
			||||||
  const menuRef = useRef<HTMLDivElement>(null);
 | 
					  const menuRef = useRef<HTMLDivElement>(null);
 | 
				
			||||||
  const fileInputRef = useRef<HTMLInputElement>(null);
 | 
					  const fileInputRef = useRef<HTMLInputElement>(null);
 | 
				
			||||||
  const isSaving = useRef(false);
 | 
					  const isSaving = useRef(false);
 | 
				
			||||||
  const [isVisible, setIsVisible] = useState(false);
 | 
					  const [isVisible, setIsVisible] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    setChromeStorageAvailable(checkChromeStorageLocalAvailable());
 | 
				
			||||||
    const storedUserWallpapers = localStorage.getItem('userWallpapers');
 | 
					    const storedUserWallpapers = localStorage.getItem('userWallpapers');
 | 
				
			||||||
    if (storedUserWallpapers) {
 | 
					    if (storedUserWallpapers) {
 | 
				
			||||||
      setUserWallpapers(JSON.parse(storedUserWallpapers));
 | 
					      setUserWallpapers(JSON.parse(storedUserWallpapers));
 | 
				
			||||||
@@ -59,7 +64,6 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
 | 
				
			|||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    // A small timeout to allow the component to mount before starting the transition
 | 
					 | 
				
			||||||
    const timer = setTimeout(() => {
 | 
					    const timer = setTimeout(() => {
 | 
				
			||||||
      setIsVisible(true);
 | 
					      setIsVisible(true);
 | 
				
			||||||
    }, 10);
 | 
					    }, 10);
 | 
				
			||||||
@@ -69,7 +73,7 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
 | 
				
			|||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    return () => {
 | 
					    return () => {
 | 
				
			||||||
      if (!isSaving.current) {
 | 
					      if (!isSaving.current) {
 | 
				
			||||||
        onWallpaperChange({ backgroundUrls: currentConfig.backgroundUrls });
 | 
					        onWallpaperChange({ currentWallpapers: currentConfig.currentWallpapers });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
@@ -78,12 +82,15 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
 | 
				
			|||||||
    setIsVisible(false);
 | 
					    setIsVisible(false);
 | 
				
			||||||
    setTimeout(() => {
 | 
					    setTimeout(() => {
 | 
				
			||||||
      onClose();
 | 
					      onClose();
 | 
				
			||||||
    }, 300); // This duration should match the transition duration
 | 
					    }, 300);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement> | { target: { name: string; value: string | string[] } }) => {
 | 
					  const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement> | { target: { name: string; value: string | string[] } }) => {
 | 
				
			||||||
    const { name, value } = e.target;
 | 
					    const { name, value } = e.target;
 | 
				
			||||||
    if (name.startsWith('serverWidget.')) {
 | 
					    if (name === 'currentWallpapers') {
 | 
				
			||||||
 | 
					      const wallpaperNames = Array.isArray(value) ? value : [value];
 | 
				
			||||||
 | 
					      setConfig({ ...config, currentWallpapers: wallpaperNames });
 | 
				
			||||||
 | 
					    } else if (name.startsWith('serverWidget.')) {
 | 
				
			||||||
      const field = name.split('.')[1];
 | 
					      const field = name.split('.')[1];
 | 
				
			||||||
      setConfig({
 | 
					      setConfig({
 | 
				
			||||||
        ...config,
 | 
					        ...config,
 | 
				
			||||||
@@ -101,8 +108,13 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    onWallpaperChange({ backgroundUrls: config.backgroundUrls });
 | 
					    onWallpaperChange({ currentWallpapers: config.currentWallpapers });
 | 
				
			||||||
  }, [config.backgroundUrls]);
 | 
					    // Set wallpaperState in localStorage with lastWallpaperChange datetime
 | 
				
			||||||
 | 
					    localStorage.setItem('wallpaperState', JSON.stringify({
 | 
				
			||||||
 | 
					      lastWallpaperChange: new Date().toISOString(),
 | 
				
			||||||
 | 
					      currentIndex: 0,
 | 
				
			||||||
 | 
					    }));
 | 
				
			||||||
 | 
					  }, [config.currentWallpapers]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleClockToggleChange = (checked: boolean) => {
 | 
					  const handleClockToggleChange = (checked: boolean) => {
 | 
				
			||||||
    setConfig({ ...config, clock: { ...config.clock, enabled: checked } });
 | 
					    setConfig({ ...config, clock: { ...config.clock, enabled: checked } });
 | 
				
			||||||
@@ -162,21 +174,21 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleAddWallpaper = () => {
 | 
					  const handleAddWallpaper = async () => {
 | 
				
			||||||
    if (newWallpaperName.trim() === '' || newWallpaperUrl.trim() === '') return;
 | 
					    if (newWallpaperUrl.trim() === '') return;
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
    const newWallpaper: Wallpaper = {
 | 
					      const finalName = await addWallpaperToChromeStorageLocal(newWallpaperName, newWallpaperUrl);
 | 
				
			||||||
      name: newWallpaperName,
 | 
					      const newWallpaper: Wallpaper = { name: finalName };
 | 
				
			||||||
      url: newWallpaperUrl,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const updatedUserWallpapers = [...userWallpapers, newWallpaper];
 | 
					      const updatedUserWallpapers = [...userWallpapers, newWallpaper];
 | 
				
			||||||
      setUserWallpapers(updatedUserWallpapers);
 | 
					      setUserWallpapers(updatedUserWallpapers);
 | 
				
			||||||
      localStorage.setItem('userWallpapers', JSON.stringify(updatedUserWallpapers));
 | 
					      localStorage.setItem('userWallpapers', JSON.stringify(updatedUserWallpapers));
 | 
				
			||||||
    setConfig({ ...config, backgroundUrls: [...config.backgroundUrls, newWallpaperUrl] });
 | 
					      setConfig({ ...config, currentWallpapers: [...config.currentWallpapers, newWallpaper.name] });
 | 
				
			||||||
 | 
					 | 
				
			||||||
      setNewWallpaperName('');
 | 
					      setNewWallpaperName('');
 | 
				
			||||||
      setNewWallpaperUrl('');
 | 
					      setNewWallpaperUrl('');
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      alert('Error adding wallpaper. Please check the URL and try again.');
 | 
				
			||||||
 | 
					      console.error(error);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
 | 
					  const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
 | 
				
			||||||
@@ -186,39 +198,43 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
 | 
				
			|||||||
        alert('File size exceeds 4MB. Please choose a smaller file.');
 | 
					        alert('File size exceeds 4MB. Please choose a smaller file.');
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					 | 
				
			||||||
      const reader = new FileReader();
 | 
					      const reader = new FileReader();
 | 
				
			||||||
      reader.onload = () => {
 | 
					      reader.onload = async () => {
 | 
				
			||||||
        const base64 = reader.result as string;
 | 
					        const base64 = reader.result as string;
 | 
				
			||||||
        if (base64.length > 4.5 * 1024 * 1024) {
 | 
					        if (base64.length > 4.5 * 1024 * 1024) {
 | 
				
			||||||
          alert('The uploaded image is too large. Please choose a smaller file.');
 | 
					          alert('The uploaded image is too large. Please choose a smaller file.');
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
        const updatedUserWallpapers = userWallpapers.filter(w => !w.base64);
 | 
					          const finalName = await addWallpaperToChromeStorageLocal(file.name, base64);
 | 
				
			||||||
        const newWallpaper: Wallpaper = {
 | 
					          const newWallpaper: Wallpaper = { name: finalName };
 | 
				
			||||||
          name: file.name,
 | 
					          const updatedUserWallpapers = [...userWallpapers, newWallpaper];
 | 
				
			||||||
          base64,
 | 
					          setUserWallpapers(updatedUserWallpapers);
 | 
				
			||||||
        };
 | 
					          localStorage.setItem('userWallpapers', JSON.stringify(updatedUserWallpapers));
 | 
				
			||||||
        setUserWallpapers([...updatedUserWallpapers, newWallpaper]);
 | 
					          setConfig({ ...config, currentWallpapers: [...config.currentWallpapers, newWallpaper.name] });
 | 
				
			||||||
        localStorage.setItem('userWallpapers', JSON.stringify([...updatedUserWallpapers, newWallpaper]));
 | 
					        } catch (error) {
 | 
				
			||||||
        setConfig({ ...config, backgroundUrls: [...config.backgroundUrls, base64] });
 | 
					          alert('Error adding wallpaper. Please try again.');
 | 
				
			||||||
 | 
					          console.error(error);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
      reader.readAsDataURL(file);
 | 
					      reader.readAsDataURL(file);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleDeleteWallpaper = (wallpaper: Wallpaper) => {
 | 
					  const handleDeleteUserWallpaper = async (wallpaper: Wallpaper) => {
 | 
				
			||||||
    const wallpaperIdentifier = wallpaper.url || wallpaper.base64;
 | 
					    try {
 | 
				
			||||||
    const updatedUserWallpapers = userWallpapers.filter(w => (w.url || w.base64) !== wallpaperIdentifier);
 | 
					      await removeWallpaperFromChromeStorageLocal(wallpaper.name);
 | 
				
			||||||
 | 
					      const updatedUserWallpapers = userWallpapers.filter(w => w.name !== wallpaper.name);
 | 
				
			||||||
      setUserWallpapers(updatedUserWallpapers);
 | 
					      setUserWallpapers(updatedUserWallpapers);
 | 
				
			||||||
      localStorage.setItem('userWallpapers', JSON.stringify(updatedUserWallpapers));
 | 
					      localStorage.setItem('userWallpapers', JSON.stringify(updatedUserWallpapers));
 | 
				
			||||||
 | 
					      const newcurrentWallpapers = config.currentWallpapers.filter((name: string) => name !== wallpaper.name);
 | 
				
			||||||
    const newBackgroundUrls = config.backgroundUrls.filter((url: string) => url !== wallpaperIdentifier);
 | 
					      const newConfig = { ...config, currentWallpapers: newcurrentWallpapers };
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    const newConfig = { ...config, backgroundUrls: newBackgroundUrls };
 | 
					 | 
				
			||||||
      setConfig(newConfig);
 | 
					      setConfig(newConfig);
 | 
				
			||||||
    onWallpaperChange({ backgroundUrls: newBackgroundUrls });
 | 
					      onWallpaperChange({ currentWallpapers: newcurrentWallpapers });
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      alert('Error deleting wallpaper. Please try again.');
 | 
				
			||||||
 | 
					      console.error(error);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const allWallpapers = [...baseWallpapers, ...userWallpapers];
 | 
					  const allWallpapers = [...baseWallpapers, ...userWallpapers];
 | 
				
			||||||
@@ -365,35 +381,17 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
 | 
				
			|||||||
              <div className="flex items-center justify-between">
 | 
					              <div className="flex items-center justify-between">
 | 
				
			||||||
                <label className="text-slate-300 text-sm font-semibold">Background</label>
 | 
					                <label className="text-slate-300 text-sm font-semibold">Background</label>
 | 
				
			||||||
                <Dropdown
 | 
					                <Dropdown
 | 
				
			||||||
                  name="backgroundUrls"
 | 
					                  name="currentWallpapers"
 | 
				
			||||||
                  value={config.backgroundUrls}
 | 
					                  value={config.currentWallpapers}
 | 
				
			||||||
                  onChange={handleChange}
 | 
					                  onChange={handleChange}
 | 
				
			||||||
                  multiple
 | 
					                  multiple
 | 
				
			||||||
                  options={allWallpapers.map(w => ({
 | 
					                  options={allWallpapers.map(w => ({
 | 
				
			||||||
                    value: w.url || w.base64 || '', 
 | 
					                    value: w.name,
 | 
				
			||||||
                    label: (
 | 
					                    label: w.name
 | 
				
			||||||
                      <div className="flex items-center justify-between w-full">
 | 
					 | 
				
			||||||
                        <span>{w.name}</span>
 | 
					 | 
				
			||||||
                        {!baseWallpapers.find(bw => (bw.url || bw.base64) === (w.url || w.base64)) && (
 | 
					 | 
				
			||||||
                          <button 
 | 
					 | 
				
			||||||
                            onClick={(e) => {
 | 
					 | 
				
			||||||
                              e.stopPropagation();
 | 
					 | 
				
			||||||
                              handleDeleteWallpaper(w);
 | 
					 | 
				
			||||||
                            }}
 | 
					 | 
				
			||||||
                            className="text-red-500 hover:text-red-400 ml-4 p-1 rounded-full flex items-center justify-center"
 | 
					 | 
				
			||||||
                          >
 | 
					 | 
				
			||||||
                            <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" 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>
 | 
				
			||||||
              {Array.isArray(config.backgroundUrls) && config.backgroundUrls.length > 1 && (
 | 
					              {Array.isArray(config.currentWallpapers) && config.currentWallpapers.length > 1 && (
 | 
				
			||||||
                <div className="flex items-center justify-between">
 | 
					                <div className="flex items-center justify-between">
 | 
				
			||||||
                  <label className="text-slate-300 text-sm font-semibold">Change Frequency</label>
 | 
					                  <label className="text-slate-300 text-sm font-semibold">Change Frequency</label>
 | 
				
			||||||
                  <Dropdown
 | 
					                  <Dropdown
 | 
				
			||||||
@@ -456,12 +454,33 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
 | 
				
			|||||||
                  <span>{config.wallpaperOpacity}%</span>
 | 
					                  <span>{config.wallpaperOpacity}%</span>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
              </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="flex items-center justify-between bg-white/10 p-2 rounded-lg">
 | 
				
			||||||
 | 
					                          <span className="truncate">{wallpaper.name}</span>
 | 
				
			||||||
 | 
					                          <button
 | 
				
			||||||
 | 
					                            onClick={() => handleDeleteUserWallpaper(wallpaper)}
 | 
				
			||||||
 | 
					                            className="text-red-500 hover:text-red-400"
 | 
				
			||||||
 | 
					                          >
 | 
				
			||||||
 | 
					                            <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>
 | 
					                  <div>
 | 
				
			||||||
                    <h3 className="text-slate-300 text-sm font-semibold mb-2">Add New Wallpaper</h3>
 | 
					                    <h3 className="text-slate-300 text-sm font-semibold mb-2">Add New Wallpaper</h3>
 | 
				
			||||||
                    <div className="flex flex-col gap-2">
 | 
					                    <div className="flex flex-col gap-2">
 | 
				
			||||||
                      <input
 | 
					                      <input
 | 
				
			||||||
                        type="text"
 | 
					                        type="text"
 | 
				
			||||||
                    placeholder="Wallpaper Name"
 | 
					                        placeholder="Wallpaper Name (optional for URLs)"
 | 
				
			||||||
                        value={newWallpaperName}
 | 
					                        value={newWallpaperName}
 | 
				
			||||||
                        onChange={(e) => setNewWallpaperName(e.target.value)}
 | 
					                        onChange={(e) => setNewWallpaperName(e.target.value)}
 | 
				
			||||||
                        className="bg-white/10 p-2 rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-cyan-400"
 | 
					                        className="bg-white/10 p-2 rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-cyan-400"
 | 
				
			||||||
@@ -498,6 +517,8 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({ onClose, onSave
 | 
				
			|||||||
                      </div>
 | 
					                      </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
 | 
					                </>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import React, { useState, useEffect } from 'react';
 | 
					import { useState, useEffect } from 'react';
 | 
				
			||||||
import { Server } from '../types';
 | 
					import { Server } from '../types';
 | 
				
			||||||
import ping from './utils/jsping.js';
 | 
					import ping from './utils/jsping.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import React from 'react';
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ToggleSwitchProps {
 | 
					interface ToggleSwitchProps {
 | 
				
			||||||
  checked: boolean;
 | 
					  checked: boolean;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										106
									
								
								components/Wallpaper.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								components/Wallpaper.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,106 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					import { useState, useEffect } from 'react';
 | 
				
			||||||
 | 
					import { baseWallpapers } from './utils/baseWallpapers';
 | 
				
			||||||
 | 
					import { Wallpaper as WallpaperType } from '../types';
 | 
				
			||||||
 | 
					import { getWallpaperFromChromeStorageLocal } from './utils/StorageLocalManager';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface WallpaperProps {
 | 
				
			||||||
 | 
					  wallpaperNames: string[];
 | 
				
			||||||
 | 
					  blur: number;
 | 
				
			||||||
 | 
					  brightness: number;
 | 
				
			||||||
 | 
					  opacity: number;
 | 
				
			||||||
 | 
					  wallpaperFrequency: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getWallpaperUrlByName = async (name: string): Promise<string | undefined> => {
 | 
				
			||||||
 | 
					  const foundInBase = baseWallpapers.find((w: WallpaperType) => w.name === name);
 | 
				
			||||||
 | 
					  if (foundInBase) {
 | 
				
			||||||
 | 
					    return foundInBase.url || foundInBase.base64;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const userWallpapers: WallpaperType[] = JSON.parse(localStorage.getItem('userWallpapers') || '[]');
 | 
				
			||||||
 | 
					  const foundInUser = userWallpapers.find((w: WallpaperType) => w.name === name);
 | 
				
			||||||
 | 
					  if (foundInUser) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const wallpaperData = await getWallpaperFromChromeStorageLocal(name);
 | 
				
			||||||
 | 
					      if (wallpaperData && wallpaperData.startsWith('http')) {
 | 
				
			||||||
 | 
					        return wallpaperData;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return wallpaperData || undefined;
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      console.error('Error getting wallpaper from chrome storage', error);
 | 
				
			||||||
 | 
					      return undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return undefined;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Wallpaper: React.FC<WallpaperProps> = ({ wallpaperNames, blur, brightness, opacity, wallpaperFrequency }) => {
 | 
				
			||||||
 | 
					  const [imageUrl, setImageUrl] = useState<string | undefined>(undefined);
 | 
				
			||||||
 | 
					  const [currentWallpaperIndex, setCurrentWallpaperIndex] = useState<number>(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Helper to parse wallpaperFrequency string to ms
 | 
				
			||||||
 | 
					  const parseFrequencyToMs = (freq: string): number => {
 | 
				
			||||||
 | 
					    if (!freq) return 24 * 60 * 60 * 1000; // default 1 day
 | 
				
			||||||
 | 
					    const match = freq.match(/(\d+)(h|d)/);
 | 
				
			||||||
 | 
					    if (!match) return 24 * 60 * 60 * 1000;
 | 
				
			||||||
 | 
					    const value = parseInt(match[1], 10);
 | 
				
			||||||
 | 
					    const unit = match[2];
 | 
				
			||||||
 | 
					    if (unit === 'h') return value * 60 * 60 * 1000;
 | 
				
			||||||
 | 
					    if (unit === 'd') return value * 24 * 60 * 60 * 1000;
 | 
				
			||||||
 | 
					    return 24 * 60 * 60 * 1000;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const updateWallpaper = async () => {
 | 
				
			||||||
 | 
					      if (wallpaperNames.length === 0) return;
 | 
				
			||||||
 | 
					      // Read wallpaperState from localStorage
 | 
				
			||||||
 | 
					      const wallpaperState = JSON.parse(localStorage.getItem('wallpaperState') || '{}');
 | 
				
			||||||
 | 
					      const lastChange = wallpaperState.lastWallpaperChange ? new Date(wallpaperState.lastWallpaperChange).getTime() : 0;
 | 
				
			||||||
 | 
					      const now = Date.now();
 | 
				
			||||||
 | 
					      const freqMs = parseFrequencyToMs(wallpaperFrequency);
 | 
				
			||||||
 | 
					      let currentIndex = typeof wallpaperState.currentIndex === 'number' ? wallpaperState.currentIndex : 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // If enough time has passed, pick a new wallpaper
 | 
				
			||||||
 | 
					      if (now - lastChange >= freqMs) {
 | 
				
			||||||
 | 
					        currentIndex = (currentIndex + 1) % wallpaperNames.length;
 | 
				
			||||||
 | 
					        localStorage.setItem('wallpaperState', JSON.stringify({
 | 
				
			||||||
 | 
					          lastWallpaperChange: new Date().toISOString(),
 | 
				
			||||||
 | 
					          currentIndex
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        // Keep currentIndex in sync with localStorage if not updating
 | 
				
			||||||
 | 
					        localStorage.setItem('wallpaperState', JSON.stringify({
 | 
				
			||||||
 | 
					          lastWallpaperChange: wallpaperState.lastWallpaperChange || new Date().toISOString(),
 | 
				
			||||||
 | 
					          currentIndex
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      setCurrentWallpaperIndex(currentIndex);
 | 
				
			||||||
 | 
					      const wallpaperName = wallpaperNames[currentIndex];
 | 
				
			||||||
 | 
					      const url = await getWallpaperUrlByName(wallpaperName);
 | 
				
			||||||
 | 
					      setImageUrl(url);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    updateWallpaper();
 | 
				
			||||||
 | 
					    // No timer, just run on render/dependency change
 | 
				
			||||||
 | 
					  }, [wallpaperNames, wallpaperFrequency]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!imageUrl) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      className="fixed inset-0 -z-10 w-full h-full"
 | 
				
			||||||
 | 
					      style={{
 | 
				
			||||||
 | 
					        backgroundImage: `url(${imageUrl})`,
 | 
				
			||||||
 | 
					        backgroundSize: 'cover',
 | 
				
			||||||
 | 
					        backgroundPosition: 'center',
 | 
				
			||||||
 | 
					        filter: `blur(${blur}px) brightness(${brightness / 100})`,
 | 
				
			||||||
 | 
					        opacity: opacity / 100,
 | 
				
			||||||
 | 
					        transition: 'filter 0.3s, opacity 0.3s',
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					      aria-label="Wallpaper background"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Wallpaper;
 | 
				
			||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import React, { useState, useEffect } from 'react';
 | 
					import { useState, useEffect } from 'react';
 | 
				
			||||||
import { Website } from '../types';
 | 
					import { Website } from '../types';
 | 
				
			||||||
import { getWebsiteIcon } from './utils/iconService';
 | 
					import { getWebsiteIcon } from './utils/iconService';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,6 @@
 | 
				
			|||||||
import React, { useState } from 'react';
 | 
					import React, { useState } from 'react';
 | 
				
			||||||
import { Website } from '../types';
 | 
					import { Website } from '../types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
interface WebsiteTileProps {
 | 
					interface WebsiteTileProps {
 | 
				
			||||||
  website: Website;
 | 
					  website: Website;
 | 
				
			||||||
  isEditing: boolean;
 | 
					  isEditing: boolean;
 | 
				
			||||||
@@ -28,9 +27,9 @@ const getTileSizeClass = (size: string | undefined) => {
 | 
				
			|||||||
const getIconPixelSize = (size: string | undefined): number => {
 | 
					const getIconPixelSize = (size: string | undefined): number => {
 | 
				
			||||||
  switch (size) {
 | 
					  switch (size) {
 | 
				
			||||||
    case 'small':
 | 
					    case 'small':
 | 
				
			||||||
      return 32;
 | 
					      return 34;
 | 
				
			||||||
    case 'medium':
 | 
					    case 'medium':
 | 
				
			||||||
      return 40;
 | 
					      return 42;
 | 
				
			||||||
    case 'large':
 | 
					    case 'large':
 | 
				
			||||||
      return 48;
 | 
					      return 48;
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
@@ -92,7 +91,7 @@ const WebsiteTile: React.FC<WebsiteTileProps> = ({ website, isEditing, onEdit, o
 | 
				
			|||||||
        )}
 | 
					        )}
 | 
				
			||||||
        <div className={`flex items-center transition-all duration-300 ease-in ${isLoading ? 'mt-18' : 'flex-col'} ${isLoading ? 'gap-2' : ''}`}>
 | 
					        <div className={`flex items-center transition-all duration-300 ease-in ${isLoading ? 'mt-18' : 'flex-col'} ${isLoading ? 'gap-2' : ''}`}>
 | 
				
			||||||
          <div className={`transition-all duration-300 ease-in ${isLoading ? iconSizeLoadingClass : iconSizeClass}`}>
 | 
					          <div className={`transition-all duration-300 ease-in ${isLoading ? iconSizeLoadingClass : iconSizeClass}`}>
 | 
				
			||||||
            <img src={website.icon} alt={`${website.name} icon`} className="object-contain" />
 | 
					            <img src={website.icon} alt={`${website.name} icon`} className={`object-contain w-full h-full`} />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <span className={`text-slate-100 font-medium text-base tracking-wide text-center transition-all duration-300 ease-in ${isLoading ? 'text-sm' : ''}`}>
 | 
					          <span className={`text-slate-100 font-medium text-base tracking-wide text-center transition-all duration-300 ease-in ${isLoading ? 'text-sm' : ''}`}>
 | 
				
			||||||
            {website.name}
 | 
					            {website.name}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,3 @@
 | 
				
			|||||||
import React from 'react';
 | 
					 | 
				
			||||||
import WebsiteTile from '../WebsiteTile';
 | 
					import WebsiteTile from '../WebsiteTile';
 | 
				
			||||||
import { Category, Website } from '../../types';
 | 
					import { Category, Website } from '../../types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import React from 'react';
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ConfigurationButtonProps {
 | 
					interface ConfigurationButtonProps {
 | 
				
			||||||
  onClick: () => void;
 | 
					  onClick: () => void;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import React from 'react';
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface EditButtonProps {
 | 
					interface EditButtonProps {
 | 
				
			||||||
  isEditing: boolean;
 | 
					  isEditing: boolean;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,3 @@
 | 
				
			|||||||
import React from 'react';
 | 
					 | 
				
			||||||
import Clock from '../Clock';
 | 
					import Clock from '../Clock';
 | 
				
			||||||
import { Config } from '../../types';
 | 
					import { Config } from '../../types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										164
									
								
								components/utils/StorageLocalManager.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								components/utils/StorageLocalManager.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,164 @@
 | 
				
			|||||||
 | 
					// TypeScript interface for window.chrome
 | 
				
			||||||
 | 
					declare global {
 | 
				
			||||||
 | 
					  interface Window {
 | 
				
			||||||
 | 
					    chrome?: {
 | 
				
			||||||
 | 
					      storage?: {
 | 
				
			||||||
 | 
					        local?: {
 | 
				
			||||||
 | 
					          set: (items: object, callback?: () => void) => void;
 | 
				
			||||||
 | 
					          get: (keys: string[] | string, callback: (items: { [key: string]: string }) => void) => void;
 | 
				
			||||||
 | 
					          remove: (keys: string | string[], callback?: () => void) => void;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      runtime?: {
 | 
				
			||||||
 | 
					        lastError?: { message: string };
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let isChromeStorageLocalAvailable: boolean | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Checks if chrome.storage.local is available and caches the result.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function checkChromeStorageLocalAvailable(): boolean {
 | 
				
			||||||
 | 
					  if (isChromeStorageLocalAvailable !== null) return isChromeStorageLocalAvailable;
 | 
				
			||||||
 | 
					  isChromeStorageLocalAvailable =
 | 
				
			||||||
 | 
					    typeof window !== 'undefined' &&
 | 
				
			||||||
 | 
					    typeof window.chrome !== 'undefined' &&
 | 
				
			||||||
 | 
					    typeof window.chrome.storage !== 'undefined' &&
 | 
				
			||||||
 | 
					    typeof window.chrome.storage.local !== 'undefined';
 | 
				
			||||||
 | 
					  return isChromeStorageLocalAvailable;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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.
 | 
				
			||||||
 | 
					 * @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.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function addWallpaperToChromeStorageLocal(name: string, url: string): Promise<string> {
 | 
				
			||||||
 | 
					  if (!checkChromeStorageLocalAvailable()) {
 | 
				
			||||||
 | 
					    throw new Error('chrome.storage.local is not available');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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.");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    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);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Gets a specific wallpaper from chrome.storage.local by name.
 | 
				
			||||||
 | 
					 * @param name Wallpaper name (string)
 | 
				
			||||||
 | 
					 * @returns Promise<string | null> (base64 string or null)
 | 
				
			||||||
 | 
					 * @throws Error if chrome.storage.local is unavailable
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function getWallpaperFromChromeStorageLocal(name: string): Promise<string | null> {
 | 
				
			||||||
 | 
					  if (!checkChromeStorageLocalAvailable()) {
 | 
				
			||||||
 | 
					    throw new Error('chrome.storage.local is not available');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return new Promise<string | null>((resolve, reject) => {
 | 
				
			||||||
 | 
					    if (window.chrome?.storage?.local) {
 | 
				
			||||||
 | 
					      window.chrome.storage.local.get([name], function (result: { [key: string]: string }) {
 | 
				
			||||||
 | 
					        if (window.chrome?.runtime?.lastError) {
 | 
				
			||||||
 | 
					          reject(new Error(window.chrome.runtime.lastError.message));
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          resolve(result[name] || null);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      reject(new Error('chrome.storage.local is not available'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Removes a wallpaper from chrome.storage.local by name.
 | 
				
			||||||
 | 
					 * @param name Wallpaper name (string)
 | 
				
			||||||
 | 
					 * @returns Promise<void>
 | 
				
			||||||
 | 
					 * @throws Error if chrome.storage.local is unavailable
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function removeWallpaperFromChromeStorageLocal(name: string): Promise<void> {
 | 
				
			||||||
 | 
					  if (!checkChromeStorageLocalAvailable()) {
 | 
				
			||||||
 | 
					    throw new Error('chrome.storage.local is not available');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return new Promise<void>((resolve, reject) => {
 | 
				
			||||||
 | 
					    if (window.chrome?.storage?.local) {
 | 
				
			||||||
 | 
					      window.chrome.storage.local.remove(name, 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'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -8,4 +8,15 @@ export default {
 | 
				
			|||||||
    extend: {},
 | 
					    extend: {},
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  plugins: [],
 | 
					  plugins: [],
 | 
				
			||||||
 | 
					  safelist: [
 | 
				
			||||||
 | 
					    'w-[24px]', 'h-[24px]',
 | 
				
			||||||
 | 
					    'w-[28px]', 'h-[28px]',
 | 
				
			||||||
 | 
					    'w-[32px]', 'h-[32px]',
 | 
				
			||||||
 | 
					    'w-[34px]', 'h-[34px]',
 | 
				
			||||||
 | 
					    'w-[36px]', 'h-[36px]',
 | 
				
			||||||
 | 
					    'w-[40px]', 'h-[40px]',
 | 
				
			||||||
 | 
					    'w-[42px]', 'h-[42px]',
 | 
				
			||||||
 | 
					    'w-[48px]', 'h-[48px]',
 | 
				
			||||||
 | 
					    // add any other sizes you use
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										2
									
								
								types.ts
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								types.ts
									
									
									
									
									
								
							@@ -27,7 +27,7 @@ export interface Wallpaper {
 | 
				
			|||||||
export interface Config {
 | 
					export interface Config {
 | 
				
			||||||
  title: string;
 | 
					  title: string;
 | 
				
			||||||
  subtitle: string;
 | 
					  subtitle: string;
 | 
				
			||||||
  backgroundUrls: string[];
 | 
					  currentWallpapers: string[];
 | 
				
			||||||
  wallpaperFrequency: string;
 | 
					  wallpaperFrequency: string;
 | 
				
			||||||
  wallpaperBlur: number;
 | 
					  wallpaperBlur: number;
 | 
				
			||||||
  wallpaperBrightness: number;
 | 
					  wallpaperBrightness: number;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user