general fixes & enhancements
This commit is contained in:
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import Dropdown from '../Dropdown';
|
||||
import ToggleSwitch from '../ToggleSwitch';
|
||||
import { Config } from '../../types';
|
||||
import { SIZE_OPTIONS } from '../utils/styleUtils';
|
||||
|
||||
interface ClockTabProps {
|
||||
config: Config;
|
||||
@@ -28,12 +29,7 @@ const ClockTab: React.FC<ClockTabProps> = ({ config, onChange }) => {
|
||||
name="clock.size"
|
||||
value={config.clock.size}
|
||||
onChange={(e) => updateClock({ size: e.target.value as string })}
|
||||
options={[
|
||||
{ value: 'tiny', label: 'Tiny' },
|
||||
{ value: 'small', label: 'Small' },
|
||||
{ value: 'medium', label: 'Medium' },
|
||||
{ value: 'large', label: 'Large' },
|
||||
]}
|
||||
options={SIZE_OPTIONS}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import Dropdown from '../Dropdown';
|
||||
import { Config } from '../../types';
|
||||
import { SIZE_OPTIONS } from '../utils/styleUtils';
|
||||
|
||||
interface GeneralTabProps {
|
||||
config: Config;
|
||||
@@ -25,12 +26,7 @@ const GeneralTab: React.FC<GeneralTabProps> = ({ config, onChange }) => {
|
||||
name="titleSize"
|
||||
value={config.titleSize}
|
||||
onChange={(e) => onChange({ titleSize: e.target.value as string })}
|
||||
options={[
|
||||
{ value: 'tiny', label: 'Tiny' },
|
||||
{ value: 'small', label: 'Small' },
|
||||
{ value: 'medium', label: 'Medium' },
|
||||
{ value: 'large', label: 'Large' },
|
||||
]}
|
||||
options={SIZE_OPTIONS}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
|
||||
type RangeStyle = React.CSSProperties & { '--range-progress': string };
|
||||
|
||||
export const getRangeStyle = (value: number, min: number, max: number): RangeStyle => {
|
||||
const progress = Math.min(100, Math.max(0, ((value - min) / (max - min)) * 100));
|
||||
return { '--range-progress': `${progress}%` };
|
||||
};
|
||||
|
||||
interface RangeSliderProps {
|
||||
label: string;
|
||||
value: number;
|
||||
min: number;
|
||||
max: number;
|
||||
step?: number;
|
||||
valueSuffix?: string;
|
||||
formatValue?: (value: number) => string;
|
||||
onChange: (value: number) => void;
|
||||
}
|
||||
|
||||
const RangeSlider: React.FC<RangeSliderProps> = ({
|
||||
label,
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
step = 1,
|
||||
valueSuffix,
|
||||
formatValue,
|
||||
onChange,
|
||||
}) => {
|
||||
return (
|
||||
<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">{label}</label>
|
||||
<div className="flex items-center gap-4">
|
||||
<input
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
value={value}
|
||||
onChange={(e) => onChange(Number(e.target.value))}
|
||||
className="liquid-range"
|
||||
style={getRangeStyle(value, min, max)}
|
||||
/>
|
||||
<span className="min-w-20 text-right text-sm text-slate-200">
|
||||
{formatValue ? formatValue(value) : `${value}${valueSuffix ?? ''}`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RangeSlider;
|
||||
@@ -2,19 +2,14 @@ import React, { useState } from 'react';
|
||||
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
|
||||
import ToggleSwitch from '../ToggleSwitch';
|
||||
import { Config, Server } from '../../types';
|
||||
import RangeSlider from './RangeSlider';
|
||||
import { TrashIcon } from '../icons';
|
||||
|
||||
interface ServerWidgetTabProps {
|
||||
config: Config;
|
||||
onChange: (updates: Partial<Config>) => void;
|
||||
}
|
||||
|
||||
type RangeStyle = React.CSSProperties & { '--range-progress': string };
|
||||
|
||||
const getRangeStyle = (value: number, min: number, max: number): RangeStyle => {
|
||||
const progress = Math.min(100, Math.max(0, ((value - min) / (max - min)) * 100));
|
||||
return { '--range-progress': `${progress}%` };
|
||||
};
|
||||
|
||||
const ServerWidgetTab: React.FC<ServerWidgetTabProps> = ({ config, onChange }) => {
|
||||
const [newServerName, setNewServerName] = useState('');
|
||||
const [newServerAddress, setNewServerAddress] = useState('');
|
||||
@@ -60,21 +55,14 @@ const ServerWidgetTab: React.FC<ServerWidgetTabProps> = ({ config, onChange }) =
|
||||
</div>
|
||||
{config.serverWidget.enabled && (
|
||||
<>
|
||||
<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">Ping Frequency</label>
|
||||
<div className="flex items-center gap-4">
|
||||
<input
|
||||
type="range"
|
||||
min="5"
|
||||
max="60"
|
||||
value={config.serverWidget.pingFrequency}
|
||||
onChange={(e) => updateServerWidget({ pingFrequency: Number(e.target.value) })}
|
||||
className="liquid-range"
|
||||
style={getRangeStyle(config.serverWidget.pingFrequency, 5, 60)}
|
||||
/>
|
||||
<span className="w-12 text-right text-sm text-slate-200">{config.serverWidget.pingFrequency}s</span>
|
||||
</div>
|
||||
</div>
|
||||
<RangeSlider
|
||||
label="Ping Frequency"
|
||||
value={config.serverWidget.pingFrequency}
|
||||
min={5}
|
||||
max={60}
|
||||
valueSuffix="s"
|
||||
onChange={(value) => updateServerWidget({ pingFrequency: value })}
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-slate-300 text-sm font-semibold mb-2">Servers</h3>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
@@ -103,20 +91,7 @@ const ServerWidgetTab: React.FC<ServerWidgetTabProps> = ({ config, onChange }) =
|
||||
className="liquid-edit-action liquid-focus text-red-300 hover:text-red-100"
|
||||
aria-label={`Remove ${server.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>
|
||||
<TrashIcon size={16} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -156,4 +131,4 @@ const ServerWidgetTab: React.FC<ServerWidgetTabProps> = ({ config, onChange }) =
|
||||
);
|
||||
};
|
||||
|
||||
export default ServerWidgetTab;
|
||||
export default ServerWidgetTab;
|
||||
@@ -1,6 +1,14 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import Dropdown from '../Dropdown';
|
||||
import { Config, Wallpaper } from '../../types';
|
||||
import RangeSlider from './RangeSlider';
|
||||
import { TrashIcon } from '../icons';
|
||||
import {
|
||||
getWallpaperFrequencyHours,
|
||||
formatWallpaperFrequency,
|
||||
MIN_WALLPAPER_FREQUENCY_HOURS,
|
||||
MAX_WALLPAPER_FREQUENCY_HOURS,
|
||||
} from '../utils/wallpaperUtils';
|
||||
|
||||
interface ThemeTabProps {
|
||||
config: Config;
|
||||
@@ -14,31 +22,6 @@ interface ThemeTabProps {
|
||||
onRandomWallpaper: () => void;
|
||||
}
|
||||
|
||||
type RangeStyle = React.CSSProperties & { '--range-progress': string };
|
||||
|
||||
const getRangeStyle = (value: number, min: number, max: number): RangeStyle => {
|
||||
const progress = Math.min(100, Math.max(0, ((value - min) / (max - min)) * 100));
|
||||
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,
|
||||
@@ -52,7 +35,6 @@ 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 () => {
|
||||
@@ -76,8 +58,8 @@ const ThemeTab: React.FC<ThemeTabProps> = ({
|
||||
if (!file) return;
|
||||
try {
|
||||
await onAddWallpaperFile(file);
|
||||
} catch (error: any) {
|
||||
alert(error?.message || 'Error adding wallpaper. Please try again.');
|
||||
} catch (error) {
|
||||
alert(error instanceof Error ? error.message : 'Error adding wallpaper. Please try again.');
|
||||
console.error(error);
|
||||
}
|
||||
e.target.value = '';
|
||||
@@ -96,74 +78,39 @@ const ThemeTab: React.FC<ThemeTabProps> = ({
|
||||
/>
|
||||
</div>
|
||||
{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>
|
||||
<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>
|
||||
<RangeSlider
|
||||
label="Change Frequency"
|
||||
value={wallpaperFrequencyHours}
|
||||
min={MIN_WALLPAPER_FREQUENCY_HOURS}
|
||||
max={MAX_WALLPAPER_FREQUENCY_HOURS}
|
||||
formatValue={formatWallpaperFrequency}
|
||||
onChange={(value) => onChange({ wallpaperFrequency: `${value}h` })}
|
||||
/>
|
||||
)}
|
||||
<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">Wallpaper Blur</label>
|
||||
<div className="flex items-center gap-4">
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="50"
|
||||
value={config.wallpaperBlur}
|
||||
onChange={(e) => onChange({ wallpaperBlur: Number(e.target.value) })}
|
||||
className="liquid-range"
|
||||
style={getRangeStyle(config.wallpaperBlur, 0, 50)}
|
||||
/>
|
||||
<span className="w-12 text-right text-sm text-slate-200">{config.wallpaperBlur}px</span>
|
||||
</div>
|
||||
</div>
|
||||
<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">Wallpaper Brightness</label>
|
||||
<div className="flex items-center gap-4">
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="200"
|
||||
value={config.wallpaperBrightness}
|
||||
onChange={(e) => onChange({ wallpaperBrightness: Number(e.target.value) })}
|
||||
className="liquid-range"
|
||||
style={getRangeStyle(config.wallpaperBrightness, 0, 200)}
|
||||
/>
|
||||
<span className="w-12 text-right text-sm text-slate-200">{config.wallpaperBrightness}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<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">Wallpaper Opacity</label>
|
||||
<div className="flex items-center gap-4">
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="100"
|
||||
value={config.wallpaperOpacity}
|
||||
onChange={(e) => onChange({ wallpaperOpacity: Number(e.target.value) })}
|
||||
className="liquid-range"
|
||||
style={getRangeStyle(config.wallpaperOpacity, 1, 100)}
|
||||
/>
|
||||
<span className="w-12 text-right text-sm text-slate-200">{config.wallpaperOpacity}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<RangeSlider
|
||||
label="Wallpaper Blur"
|
||||
value={config.wallpaperBlur}
|
||||
min={0}
|
||||
max={50}
|
||||
valueSuffix="px"
|
||||
onChange={(value) => onChange({ wallpaperBlur: value })}
|
||||
/>
|
||||
<RangeSlider
|
||||
label="Wallpaper Brightness"
|
||||
value={config.wallpaperBrightness}
|
||||
min={0}
|
||||
max={200}
|
||||
valueSuffix="%"
|
||||
onChange={(value) => onChange({ wallpaperBrightness: value })}
|
||||
/>
|
||||
<RangeSlider
|
||||
label="Wallpaper Opacity"
|
||||
value={config.wallpaperOpacity}
|
||||
min={1}
|
||||
max={100}
|
||||
valueSuffix="%"
|
||||
onChange={(value) => onChange({ wallpaperOpacity: value })}
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-slate-300 text-sm font-semibold mb-2">User Wallpapers</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
@@ -178,20 +125,7 @@ const ThemeTab: React.FC<ThemeTabProps> = ({
|
||||
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>
|
||||
<TrashIcon size={16} />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
@@ -254,7 +188,6 @@ const ThemeTab: React.FC<ThemeTabProps> = ({
|
||||
type="file"
|
||||
className="hidden"
|
||||
onChange={handleFileUpload}
|
||||
ref={fileInputRef}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
@@ -283,4 +216,4 @@ const ThemeTab: React.FC<ThemeTabProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeTab;
|
||||
export default ThemeTab;
|
||||
Reference in New Issue
Block a user