adding extra theme options
All checks were successful
Build and Release to Staging / Build Vision Start (push) Successful in 13s
Build and Release to Staging / Build Vision Start Image (push) Successful in 1m25s
Build and Release to Staging / Deploy Vision Start (staging) (push) Successful in 7s

This commit is contained in:
2026-07-10 20:11:40 -03:00
parent 254d8e26b6
commit c9dafd76d1
4 changed files with 82 additions and 30 deletions

View File

@@ -13,15 +13,18 @@ interface WallpaperProps {
wallpaperVersion: number;
}
const MIN_WALLPAPER_FREQUENCY_MS = 60 * 60 * 1000;
const MAX_WALLPAPER_FREQUENCY_MS = 48 * 60 * 60 * 1000;
const DEFAULT_WALLPAPER_FREQUENCY_MS = 24 * 60 * 60 * 1000;
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;
if (!freq) return DEFAULT_WALLPAPER_FREQUENCY_MS;
const match = freq.match(/^(\d+)(h|d)$/);
if (!match) return DEFAULT_WALLPAPER_FREQUENCY_MS;
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;
const frequencyMs = unit === 'd' ? value * 24 * 60 * 60 * 1000 : value * 60 * 60 * 1000;
return Math.min(MAX_WALLPAPER_FREQUENCY_MS, Math.max(MIN_WALLPAPER_FREQUENCY_MS, frequencyMs));
};
const wallpaperUrlCache = new Map<string, string | undefined>();

View File

@@ -21,6 +21,24 @@ const getRangeStyle = (value: number, min: number, max: number): RangeStyle => {
return { '--range-progress': `${progress}%` };
};
const MIN_WALLPAPER_FREQUENCY_HOURS = 1;
const MAX_WALLPAPER_FREQUENCY_HOURS = 48;
const DEFAULT_WALLPAPER_FREQUENCY_HOURS = 24;
const clampWallpaperFrequencyHours = (hours: number): number =>
Math.min(MAX_WALLPAPER_FREQUENCY_HOURS, Math.max(MIN_WALLPAPER_FREQUENCY_HOURS, Math.round(hours)));
const getWallpaperFrequencyHours = (frequency: string): number => {
const match = frequency.match(/^(\d+)(h|d)$/);
if (!match) return DEFAULT_WALLPAPER_FREQUENCY_HOURS;
const value = Number(match[1]);
const hours = match[2] === 'd' ? value * 24 : value;
return clampWallpaperFrequencyHours(hours);
};
const formatWallpaperFrequency = (hours: number): string =>
`${hours} ${hours === 1 ? 'hour' : 'hours'}`;
const ThemeTab: React.FC<ThemeTabProps> = ({
config,
onChange,
@@ -35,6 +53,7 @@ const ThemeTab: React.FC<ThemeTabProps> = ({
const [newWallpaperName, setNewWallpaperName] = useState('');
const [newWallpaperUrl, setNewWallpaperUrl] = useState('');
const fileInputRef = useRef<HTMLInputElement>(null);
const wallpaperFrequencyHours = getWallpaperFrequencyHours(config.wallpaperFrequency);
const handleAddWallpaper = async () => {
if (newWallpaperUrl.trim() === '') return;
@@ -75,19 +94,25 @@ const ThemeTab: React.FC<ThemeTabProps> = ({
{Array.isArray(config.currentWallpapers) && config.currentWallpapers.length > 1 && (
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<label className="text-slate-300 text-sm font-semibold">Change Frequency</label>
<Dropdown
name="wallpaperFrequency"
value={config.wallpaperFrequency}
onChange={(e) => onChange({ wallpaperFrequency: e.target.value as string })}
options={[
{ value: '1h', label: '1 hour' },
{ value: '3h', label: '3 hours' },
{ value: '6h', label: '6 hours' },
{ value: '12h', label: '12 hours' },
{ value: '1d', label: '1 day' },
{ value: '2d', label: '2 days' },
]}
/>
<div className="flex items-center gap-4">
<input
type="range"
min={MIN_WALLPAPER_FREQUENCY_HOURS}
max={MAX_WALLPAPER_FREQUENCY_HOURS}
step="1"
value={wallpaperFrequencyHours}
onChange={(e) => onChange({ wallpaperFrequency: `${Number(e.target.value)}h` })}
className="liquid-range"
style={getRangeStyle(
wallpaperFrequencyHours,
MIN_WALLPAPER_FREQUENCY_HOURS,
MAX_WALLPAPER_FREQUENCY_HOURS,
)}
/>
<span className="w-20 text-right text-sm text-slate-200">
{formatWallpaperFrequency(wallpaperFrequencyHours)}
</span>
</div>
</div>
)}
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">

View File

@@ -43,13 +43,37 @@ const safeParse = (value: string | null): unknown => {
const toStorageString = (value: unknown): string =>
typeof value === 'string' ? value : JSON.stringify(value);
const isPlainObject = (v: unknown): v is Record<string, unknown> =>
typeof v === 'object' && v !== null && !Array.isArray(v);
const deepMerge = <T>(base: T, stored: unknown): T => {
if (isPlainObject(base)) {
const result: Record<string, unknown> = { ...(base as Record<string, unknown>) };
const storedObj = isPlainObject(stored) ? stored : {};
for (const key of Object.keys(result)) {
if (Object.prototype.hasOwnProperty.call(storedObj, key)) {
result[key] = deepMerge(result[key], storedObj[key]);
}
}
return result as T;
}
if (Array.isArray(base)) {
return (Array.isArray(stored) ? stored : base) as T;
}
if (stored === null) return base;
return typeof stored === typeof base ? (stored as T) : base;
};
export const normalizeConfig = (stored: unknown): Config =>
deepMerge(DEFAULT_CONFIG, stored);
export const ConfigurationService = {
loadConfig(): Config {
try {
const stored = localStorage.getItem('config');
if (stored) {
const parsed = JSON.parse(stored);
return { ...DEFAULT_CONFIG, ...parsed };
return normalizeConfig(parsed);
}
} catch (error) {
console.error('Error parsing config from localStorage', error);
@@ -159,12 +183,12 @@ export const ConfigurationService = {
throw new Error(`No required keys found. Expected: ${REQUIRED_LOCAL_STORAGE_KEYS.join(', ')}`);
}
const importedConfig = (localStorageData as Record<string, unknown>).config as Config;
const importedConfig = (localStorageData as Record<string, unknown>).config;
const importedUserWallpapers = (localStorageData as Record<string, unknown>)
.userWallpapers as Wallpaper[];
.userWallpapers;
return {
config: importedConfig || { ...DEFAULT_CONFIG },
config: normalizeConfig(importedConfig),
userWallpapers: Array.isArray(importedUserWallpapers) ? importedUserWallpapers : [],
};
},