making wallpapers random
This commit is contained in:
@@ -39,6 +39,12 @@ const getHorizontalAlignmentClass = (alignment: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getRandomWallpaperIndex = (wallpaperCount: number, currentIndex: number): number => {
|
||||||
|
if (wallpaperCount <= 1) return 0;
|
||||||
|
const offset = Math.floor(Math.random() * (wallpaperCount - 1)) + 1;
|
||||||
|
return (currentIndex + offset) % wallpaperCount;
|
||||||
|
};
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
const [categories, setCategories] = useState<Category[]>(() => {
|
const [categories, setCategories] = useState<Category[]>(() => {
|
||||||
try {
|
try {
|
||||||
@@ -81,23 +87,23 @@ const App: React.FC = () => {
|
|||||||
setConfig(prev => ({ ...prev, ...newConfig }));
|
setConfig(prev => ({ ...prev, ...newConfig }));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleNextWallpaper = useCallback(() => {
|
const handleRandomWallpaper = useCallback(() => {
|
||||||
const names = config.currentWallpapers;
|
const names = config.currentWallpapers;
|
||||||
if (names.length === 0) return;
|
if (names.length === 0) return;
|
||||||
try {
|
try {
|
||||||
const state = JSON.parse(localStorage.getItem('wallpaperState') || '{}');
|
const state = JSON.parse(localStorage.getItem('wallpaperState') || '{}');
|
||||||
const current = typeof state.currentIndex === 'number' ? state.currentIndex : 0;
|
const current = typeof state.currentIndex === 'number' ? state.currentIndex : 0;
|
||||||
const safeCurrent = current < 0 || current >= names.length ? 0 : current;
|
const safeCurrent = current < 0 || current >= names.length ? 0 : current;
|
||||||
const nextIndex = (safeCurrent + 1) % names.length;
|
const randomIndex = getRandomWallpaperIndex(names.length, safeCurrent);
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'wallpaperState',
|
'wallpaperState',
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
lastWallpaperChange: new Date().toISOString(),
|
lastWallpaperChange: new Date().toISOString(),
|
||||||
currentIndex: nextIndex,
|
currentIndex: randomIndex,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error advancing wallpaper state', error);
|
console.error('Error randomizing wallpaper state', error);
|
||||||
}
|
}
|
||||||
setWallpaperVersion(v => v + 1);
|
setWallpaperVersion(v => v + 1);
|
||||||
}, [config.currentWallpapers]);
|
}, [config.currentWallpapers]);
|
||||||
@@ -282,7 +288,7 @@ const App: React.FC = () => {
|
|||||||
onClose={() => setIsConfigModalOpen(false)}
|
onClose={() => setIsConfigModalOpen(false)}
|
||||||
onSave={handleSaveConfig}
|
onSave={handleSaveConfig}
|
||||||
onWallpaperChange={handleWallpaperChange}
|
onWallpaperChange={handleWallpaperChange}
|
||||||
onNextWallpaper={handleNextWallpaper}
|
onRandomWallpaper={handleRandomWallpaper}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ interface ConfigurationModalProps {
|
|||||||
onSave: (config: Config) => void;
|
onSave: (config: Config) => void;
|
||||||
currentConfig: Config;
|
currentConfig: Config;
|
||||||
onWallpaperChange: (newConfig: Partial<Config>) => void;
|
onWallpaperChange: (newConfig: Partial<Config>) => void;
|
||||||
onNextWallpaper: () => void;
|
onRandomWallpaper: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConfigurationModal: React.FC<ConfigurationModalProps> = ({
|
const ConfigurationModal: React.FC<ConfigurationModalProps> = ({
|
||||||
@@ -21,7 +21,7 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({
|
|||||||
onSave,
|
onSave,
|
||||||
currentConfig,
|
currentConfig,
|
||||||
onWallpaperChange,
|
onWallpaperChange,
|
||||||
onNextWallpaper,
|
onRandomWallpaper,
|
||||||
}) => {
|
}) => {
|
||||||
const [config, setConfig] = useState<Config>(currentConfig);
|
const [config, setConfig] = useState<Config>(currentConfig);
|
||||||
const [activeTab, setActiveTab] = useState('general');
|
const [activeTab, setActiveTab] = useState('general');
|
||||||
@@ -177,7 +177,7 @@ const ConfigurationModal: React.FC<ConfigurationModalProps> = ({
|
|||||||
onAddWallpaper={handleAddWallpaper}
|
onAddWallpaper={handleAddWallpaper}
|
||||||
onAddWallpaperFile={handleAddWallpaperFile}
|
onAddWallpaperFile={handleAddWallpaperFile}
|
||||||
onDeleteWallpaper={handleDeleteWallpaper}
|
onDeleteWallpaper={handleDeleteWallpaper}
|
||||||
onNextWallpaper={onNextWallpaper}
|
onRandomWallpaper={onRandomWallpaper}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeTab === 'clock' && (
|
{activeTab === 'clock' && (
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ const parseFrequencyToMs = (freq: string): number => {
|
|||||||
return Math.min(MAX_WALLPAPER_FREQUENCY_MS, Math.max(MIN_WALLPAPER_FREQUENCY_MS, frequencyMs));
|
return Math.min(MAX_WALLPAPER_FREQUENCY_MS, Math.max(MIN_WALLPAPER_FREQUENCY_MS, frequencyMs));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getRandomWallpaperIndex = (wallpaperCount: number, currentIndex: number): number => {
|
||||||
|
if (wallpaperCount <= 1) return 0;
|
||||||
|
const offset = Math.floor(Math.random() * (wallpaperCount - 1)) + 1;
|
||||||
|
return (currentIndex + offset) % wallpaperCount;
|
||||||
|
};
|
||||||
|
|
||||||
const wallpaperUrlCache = new Map<string, string | undefined>();
|
const wallpaperUrlCache = new Map<string, string | undefined>();
|
||||||
|
|
||||||
const getWallpaperUrlByName = async (name: string): Promise<string | undefined> => {
|
const getWallpaperUrlByName = async (name: string): Promise<string | undefined> => {
|
||||||
@@ -91,7 +97,7 @@ const Wallpaper: React.FC<WallpaperProps> = ({ wallpaperNames, blur, brightness,
|
|||||||
|
|
||||||
const shouldRotate = now - lastChange >= freqMs;
|
const shouldRotate = now - lastChange >= freqMs;
|
||||||
let resolvedIndex = shouldRotate
|
let resolvedIndex = shouldRotate
|
||||||
? (storedIndex + 1) % wallpaperNames.length
|
? getRandomWallpaperIndex(wallpaperNames.length, storedIndex)
|
||||||
: storedIndex;
|
: storedIndex;
|
||||||
|
|
||||||
const tried = new Set<number>();
|
const tried = new Set<number>();
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ interface ThemeTabProps {
|
|||||||
onAddWallpaper: (name: string, url: string) => Promise<void>;
|
onAddWallpaper: (name: string, url: string) => Promise<void>;
|
||||||
onAddWallpaperFile: (file: File) => Promise<void>;
|
onAddWallpaperFile: (file: File) => Promise<void>;
|
||||||
onDeleteWallpaper: (wallpaper: Wallpaper) => Promise<void>;
|
onDeleteWallpaper: (wallpaper: Wallpaper) => Promise<void>;
|
||||||
onNextWallpaper: () => void;
|
onRandomWallpaper: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type RangeStyle = React.CSSProperties & { '--range-progress': string };
|
type RangeStyle = React.CSSProperties & { '--range-progress': string };
|
||||||
@@ -48,7 +48,7 @@ const ThemeTab: React.FC<ThemeTabProps> = ({
|
|||||||
onAddWallpaper,
|
onAddWallpaper,
|
||||||
onAddWallpaperFile,
|
onAddWallpaperFile,
|
||||||
onDeleteWallpaper,
|
onDeleteWallpaper,
|
||||||
onNextWallpaper,
|
onRandomWallpaper,
|
||||||
}) => {
|
}) => {
|
||||||
const [newWallpaperName, setNewWallpaperName] = useState('');
|
const [newWallpaperName, setNewWallpaperName] = useState('');
|
||||||
const [newWallpaperUrl, setNewWallpaperUrl] = useState('');
|
const [newWallpaperUrl, setNewWallpaperUrl] = useState('');
|
||||||
@@ -263,7 +263,7 @@ const ThemeTab: React.FC<ThemeTabProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex justify-center pt-2">
|
<div className="flex justify-center pt-2">
|
||||||
<button
|
<button
|
||||||
onClick={onNextWallpaper}
|
onClick={onRandomWallpaper}
|
||||||
disabled={config.currentWallpapers.length === 0}
|
disabled={config.currentWallpapers.length === 0}
|
||||||
className="liquid-surface liquid-control liquid-focus disabled:opacity-40 disabled:cursor-not-allowed text-white text-sm font-semibold py-2 px-4 rounded-2xl"
|
className="liquid-surface liquid-control liquid-focus disabled:opacity-40 disabled:cursor-not-allowed text-white text-sm font-semibold py-2 px-4 rounded-2xl"
|
||||||
>
|
>
|
||||||
@@ -276,7 +276,7 @@ const ThemeTab: React.FC<ThemeTabProps> = ({
|
|||||||
>
|
>
|
||||||
<path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0zM4.5 7.5a.5.5 0 0 1 .5-.5h5.379L8.646 5.354a.5.5 0 1 1 .708-.708l2.5 2.5a.5.5 0 0 1 0 .708l-2.5 2.5a.5.5 0 0 1-.708-.708L10.379 8H5a.5.5 0 0 1-.5-.5z" />
|
<path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0zM4.5 7.5a.5.5 0 0 1 .5-.5h5.379L8.646 5.354a.5.5 0 1 1 .708-.708l2.5 2.5a.5.5 0 0 1 0 .708l-2.5 2.5a.5.5 0 0 1-.708-.708L10.379 8H5a.5.5 0 0 1-.5-.5z" />
|
||||||
</svg>
|
</svg>
|
||||||
Next Wallpaper
|
Random Wallpaper
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+2
-2
@@ -55,7 +55,7 @@ The startpage is composed of widgets and a configuration panel:
|
|||||||
- **Clock** — Optional header clock with selectable size, font, and 12h/24h format.
|
- **Clock** — Optional header clock with selectable size, font, and 12h/24h format.
|
||||||
- **Title** — Optional big header title (text + size configurable).
|
- **Title** — Optional big header title (text + size configurable).
|
||||||
- **Server Status Widget** — Bottom-center glass pill that periodically "pings" configured server addresses and shows online/offline indicators. Ping uses an image-load trick (`components/utils/jsping.js`) with a 5s timeout, at a configurable frequency.
|
- **Server Status Widget** — Bottom-center glass pill that periodically "pings" configured server addresses and shows online/offline indicators. Ping uses an image-load trick (`components/utils/jsping.js`) with a 5s timeout, at a configurable frequency.
|
||||||
- **Wallpaper background** — Fullscreen background image with adjustable blur, brightness, and opacity, rendered behind a soft readability layer for the liquid-glass UI. Supports rotating through multiple wallpapers at an hourly cadence selected by slider (`1h`–`48h`).
|
- **Wallpaper background** — Fullscreen background image with adjustable blur, brightness, and opacity, rendered behind a soft readability layer for the liquid-glass UI. Randomly rotates through multiple wallpapers at an hourly cadence selected by slider (`1h`–`48h`).
|
||||||
- Built-in wallpapers: Abstract, Abstract Red, Beach, Dark, Mountain, Waves (`components/utils/baseWallpapers.ts`).
|
- Built-in wallpapers: Abstract, Abstract Red, Beach, Dark, Mountain, Waves (`components/utils/baseWallpapers.ts`).
|
||||||
- User wallpapers: upload from a file (≤4MB, ≤4.5MB base64) or add by URL. Remote URLs remain lightweight entries in the `userWallpapers` index; uploaded image data is stored in `chrome.storage.local` when available.
|
- User wallpapers: upload from a file (≤4MB, ≤4.5MB base64) or add by URL. Remote URLs remain lightweight entries in the `userWallpapers` index; uploaded image data is stored in `chrome.storage.local` when available.
|
||||||
- **Icon library, auto-fetch & cache** — Website icons can be picked from the [Dashboard Icons](https://dashboardicons.com/) library (metadata pre-downloaded to `public/icon-metadata.json`) or auto-fetched from the target site's `apple-touch-icon`/`icon` link tags, with a fallback to Google's S2 favicon service. Tiles opportunistically cache CORS-readable icon responses as data URLs in `chrome.storage.local` and retain the original URL when caching is unavailable.
|
- **Icon library, auto-fetch & cache** — Website icons can be picked from the [Dashboard Icons](https://dashboardicons.com/) library (metadata pre-downloaded to `public/icon-metadata.json`) or auto-fetched from the target site's `apple-touch-icon`/`icon` link tags, with a fallback to Google's S2 favicon service. Tiles opportunistically cache CORS-readable icon responses as data URLs in `chrome.storage.local` and retain the original URL when caching is unavailable.
|
||||||
@@ -235,7 +235,7 @@ External assets fetched at build time by `scripts/prepare_release.sh`:
|
|||||||
- **`EditModal.tsx` has been removed.** It was a legacy drag-and-drop editor that imported non-existent `lucide-react` and `./IconPicker`; it was never wired into `App.tsx`. Use `WebsiteEditModal.tsx` / `CategoryEditModal.tsx` instead.
|
- **`EditModal.tsx` has been removed.** It was a legacy drag-and-drop editor that imported non-existent `lucide-react` and `./IconPicker`; it was never wired into `App.tsx`. Use `WebsiteEditModal.tsx` / `CategoryEditModal.tsx` instead.
|
||||||
- **`@hello-pangea/dnd`** is used only in `ServerWidgetTab.tsx` (server reorder), which itself is imported by the lazy-loaded `ConfigurationModal`, so it lives in a separate chunk and is absent from the initial page load. `WebsiteTile` moves tiles within their own category via simple left/right buttons (no cross-category movement), not drag-and-drop.
|
- **`@hello-pangea/dnd`** is used only in `ServerWidgetTab.tsx` (server reorder), which itself is imported by the lazy-loaded `ConfigurationModal`, so it lives in a separate chunk and is absent from the initial page load. `WebsiteTile` moves tiles within their own category via simple left/right buttons (no cross-category movement), not drag-and-drop.
|
||||||
- **Chrome storage is optional.** `StorageLocalManager` checks availability once (`checkChromeStorageLocalAvailable`) and caches it. Remote wallpaper URLs work without it because their URLs live in `userWallpapers`; file upload is gated off when unavailable, and `addWallpaperToChromeStorageLocal` throws if called without it.
|
- **Chrome storage is optional.** `StorageLocalManager` checks availability once (`checkChromeStorageLocalAvailable`) and caches it. Remote wallpaper URLs work without it because their URLs live in `userWallpapers`; file upload is gated off when unavailable, and `addWallpaperToChromeStorageLocal` throws if called without it.
|
||||||
- **Wallpaper rotation** is time-based, evaluated on render/mount rather than via a timer. It reads `wallpaperState` from `localStorage`, advances the index if the frequency window has elapsed, and writes it back; the frequency is clamped to 1–48 hours, while older saved values like `1d`/`2d` still resolve to their hour equivalents. The renderer clamps `currentIndex` to the valid range of the current selection and walks the list forward to find a wallpaper whose data actually resolves (so deleting the currently-displayed wallpaper, or shrinking the selection, never leaves the background blank); if no wallpaper resolves, the background layer is hidden. When the selection becomes empty, `wallpaperState` is reset and the background is hidden. A manual "Next Wallpaper" button in the Theme tab advances `currentIndex` (with wraparound) and bumps a `wallpaperVersion` nonce in `App.tsx` that retriggers the renderer.
|
- **Wallpaper rotation** is time-based, evaluated on render/mount rather than via a timer. It reads `wallpaperState` from `localStorage`, selects a random non-current wallpaper when the frequency window has elapsed, and writes its index back; the frequency is clamped to 1–48 hours, while older saved values like `1d`/`2d` still resolve to their hour equivalents. The renderer clamps `currentIndex` to the valid range of the current selection and walks the list forward to find a wallpaper whose data actually resolves (so deleting the currently-displayed wallpaper, or shrinking the selection, never leaves the background blank); if no wallpaper resolves, the background layer is hidden. When the selection becomes empty, `wallpaperState` is reset and the background is hidden. A manual "Random Wallpaper" button in the Theme tab also picks a random non-current wallpaper and bumps a `wallpaperVersion` nonce in `App.tsx` that retriggers the renderer.
|
||||||
- **Icon picker** in `WebsiteEditModal` loads `/icon-metadata.json` at runtime and expands each icon's `colors` into duplicate-name entries so color variants are searchable.
|
- **Icon picker** in `WebsiteEditModal` loads `/icon-metadata.json` at runtime and expands each icon's `colors` into duplicate-name entries so color variants are searchable.
|
||||||
- **Website icon cache** is persistent only in `chrome.storage.local`; it is rebuilt from website icon URLs after configuration import. Only CORS-readable `http`/`https` image responses no larger than 256KiB are cached.
|
- **Website icon cache** is persistent only in `chrome.storage.local`; it is rebuilt from website icon URLs after configuration import. Only CORS-readable `http`/`https` image responses no larger than 256KiB are cached.
|
||||||
- **`tsconfig.json` does not emit JS** (`noEmit: true`, bundler resolution); Vite handles all transpilation.
|
- **`tsconfig.json` does not emit JS** (`noEmit: true`, bundler resolution); Vite handles all transpilation.
|
||||||
|
|||||||
Reference in New Issue
Block a user