812 lines
28 KiB
TypeScript
812 lines
28 KiB
TypeScript
import {
|
||
Accessibility,
|
||
AlignJustify,
|
||
Check,
|
||
CloudSun,
|
||
Eye,
|
||
GalleryHorizontalEnd,
|
||
Gauge,
|
||
Grid3X3,
|
||
Layers3,
|
||
Maximize2,
|
||
MonitorCog,
|
||
Image as ImageIcon,
|
||
RefreshCw,
|
||
Server,
|
||
Sparkles,
|
||
Trash2,
|
||
Upload,
|
||
} from "lucide-react";
|
||
import { useEffect, useRef, useState } from "react";
|
||
import type {
|
||
Accent,
|
||
BackgroundStyle,
|
||
ContentWidth,
|
||
DashboardPayload,
|
||
Density,
|
||
Theme,
|
||
ViewMode,
|
||
WallpaperFit,
|
||
} from "../../shared/contracts";
|
||
import { api } from "../api";
|
||
|
||
type Performer = (
|
||
action: () => Promise<unknown>,
|
||
message: string,
|
||
) => Promise<boolean>;
|
||
|
||
interface AppearanceSettingsProps {
|
||
data: DashboardPayload;
|
||
perform: Performer;
|
||
}
|
||
|
||
const themes: { id: Theme; label: string; copy: string }[] = [
|
||
{
|
||
id: "dark",
|
||
label: "Donker",
|
||
copy: "Diep navy en rustig contrast.",
|
||
},
|
||
{ id: "light", label: "Licht", copy: "Helder, rustig en verfijnd." },
|
||
{ id: "harbor", label: "Haven", copy: "Koel marineblauw voor late avonden." },
|
||
];
|
||
|
||
const accents: { id: Accent; label: string }[] = [
|
||
{ id: "coral", label: "Koraal" },
|
||
{ id: "ocean", label: "Oceaan" },
|
||
{ id: "violet", label: "Violet" },
|
||
{ id: "lime", label: "Limoen" },
|
||
];
|
||
|
||
const views: { id: ViewMode; label: string; copy: string }[] = [
|
||
{
|
||
id: "overview",
|
||
label: "Volledig overzicht",
|
||
copy: "Alle verzamelingen op één doorlopende pagina.",
|
||
},
|
||
{
|
||
id: "categories",
|
||
label: "Categorienavigatie",
|
||
copy: "Wissel tussen gerichte verzamelingstabbladen.",
|
||
},
|
||
];
|
||
|
||
const widths: {
|
||
id: ContentWidth;
|
||
label: string;
|
||
copy: string;
|
||
icon: typeof Maximize2;
|
||
}[] = [
|
||
{
|
||
id: "focused",
|
||
label: "Gericht",
|
||
copy: "Rustig en gecentreerd tot 1120 px.",
|
||
icon: AlignJustify,
|
||
},
|
||
{
|
||
id: "wide",
|
||
label: "Breed",
|
||
copy: "Evenwichtig voor alledaagse desktops.",
|
||
icon: GalleryHorizontalEnd,
|
||
},
|
||
{
|
||
id: "fluid",
|
||
label: "Vloeiend",
|
||
copy: "Benut ultrabrede schermen volledig.",
|
||
icon: Maximize2,
|
||
},
|
||
];
|
||
|
||
const densities: {
|
||
id: Density;
|
||
label: string;
|
||
copy: string;
|
||
icon: typeof Gauge;
|
||
}[] = [
|
||
{
|
||
id: "compact",
|
||
label: "Compact",
|
||
copy: "Meer services per rij.",
|
||
icon: Grid3X3,
|
||
},
|
||
{
|
||
id: "comfortable",
|
||
label: "Comfortabel",
|
||
copy: "Een evenwichtige standaard voor dagelijks gebruik.",
|
||
icon: Gauge,
|
||
},
|
||
{
|
||
id: "spacious",
|
||
label: "Ruim",
|
||
copy: "Grotere, rustigere starttegels.",
|
||
icon: Layers3,
|
||
},
|
||
];
|
||
|
||
const backgrounds: { id: BackgroundStyle; label: string; copy: string }[] = [
|
||
{ id: "aurora", label: "Aurora", copy: "Zachte omgevingskleur en diepte." },
|
||
{ id: "grid", label: "Blauwdruk", copy: "Een subtiel technisch raster." },
|
||
{ id: "minimal", label: "Minimaal", copy: "Zuivere kleur zonder textuur." },
|
||
];
|
||
|
||
export function AppearanceSettings({ data, perform }: AppearanceSettingsProps) {
|
||
const preferences = data.preferences;
|
||
const wallpaperInput = useRef<HTMLInputElement>(null);
|
||
const [wallpaperDraft, setWallpaperDraft] = useState<{
|
||
fit: WallpaperFit;
|
||
positionX: number;
|
||
positionY: number;
|
||
zoom: number;
|
||
}>(() => ({
|
||
fit: preferences.wallpaperFit,
|
||
positionX: preferences.wallpaperPositionX,
|
||
positionY: preferences.wallpaperPositionY,
|
||
zoom: preferences.wallpaperZoom,
|
||
}));
|
||
|
||
useEffect(() => {
|
||
setWallpaperDraft({
|
||
fit: preferences.wallpaperFit,
|
||
positionX: preferences.wallpaperPositionX,
|
||
positionY: preferences.wallpaperPositionY,
|
||
zoom: preferences.wallpaperZoom,
|
||
});
|
||
}, [
|
||
preferences.wallpaperFit,
|
||
preferences.wallpaperPositionX,
|
||
preferences.wallpaperPositionY,
|
||
preferences.wallpaperZoom,
|
||
]);
|
||
|
||
const wallpaperDraftChanged =
|
||
wallpaperDraft.fit !== preferences.wallpaperFit ||
|
||
wallpaperDraft.positionX !== preferences.wallpaperPositionX ||
|
||
wallpaperDraft.positionY !== preferences.wallpaperPositionY ||
|
||
wallpaperDraft.zoom !== preferences.wallpaperZoom;
|
||
|
||
return (
|
||
<>
|
||
<header className="settings-heading">
|
||
<p className="eyebrow">Instellingen › Weergave</p>
|
||
<h2>Thema</h2>
|
||
<p>Kies de visuele stijl die het beste bij je workflow past.</p>
|
||
</header>
|
||
<div className="theme-grid">
|
||
{themes.map((theme) => (
|
||
<button
|
||
key={theme.id}
|
||
className={`theme-card ${preferences.theme === theme.id ? "selected" : ""}`}
|
||
aria-pressed={preferences.theme === theme.id}
|
||
onClick={() =>
|
||
perform(
|
||
() => api.updatePreferences({ theme: theme.id }),
|
||
`Thema ${theme.label} toegepast`,
|
||
)
|
||
}
|
||
>
|
||
<span className={`theme-preview ${theme.id}`}>
|
||
<i />
|
||
<i />
|
||
<i />
|
||
</span>
|
||
<span>
|
||
<strong>{theme.label}</strong>
|
||
<small>{theme.copy}</small>
|
||
</span>
|
||
{preferences.theme === theme.id && <Check size={17} />}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
<div className="canon-preference-columns">
|
||
<section>
|
||
<h3 className="setting-label">Dashboardmodus</h3>
|
||
<div className="view-grid">
|
||
{views.map((view) => (
|
||
<button
|
||
key={view.id}
|
||
className={`view-card ${preferences.viewMode === view.id ? "selected" : ""}`}
|
||
aria-pressed={preferences.viewMode === view.id}
|
||
onClick={() =>
|
||
perform(
|
||
() => api.updatePreferences({ viewMode: view.id }),
|
||
`${view.label} geselecteerd`,
|
||
)
|
||
}
|
||
>
|
||
<MonitorCog size={22} />
|
||
<span>
|
||
<strong>{view.label}</strong>
|
||
<small>{view.copy}</small>
|
||
</span>
|
||
{preferences.viewMode === view.id && <Check size={17} />}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</section>
|
||
<section>
|
||
<h3 className="setting-label">Grid-dichtheid</h3>
|
||
<div className="choice-grid density-grid">
|
||
{densities.map((density) => {
|
||
const Icon = density.icon;
|
||
return (
|
||
<button
|
||
key={density.id}
|
||
className={`option-card ${preferences.density === density.id ? "selected" : ""}`}
|
||
aria-pressed={preferences.density === density.id}
|
||
onClick={() =>
|
||
perform(
|
||
() => api.updatePreferences({ density: density.id }),
|
||
`Tegels ${density.label} geselecteerd`,
|
||
)
|
||
}
|
||
>
|
||
<Icon size={20} />
|
||
<strong>{density.label}</strong>
|
||
<small>{density.copy}</small>
|
||
{preferences.density === density.id && <Check size={15} />}
|
||
</button>
|
||
);
|
||
})}
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<details className="appearance-advanced">
|
||
<summary>
|
||
<span>
|
||
<strong>Meer personalisatie</strong>
|
||
<small>
|
||
Accent, canvasbreedte, achtergrond en dashboarddetails
|
||
</small>
|
||
</span>
|
||
</summary>
|
||
<div className="appearance-advanced-content">
|
||
<h3 className="setting-label">Accentkleur</h3>
|
||
<div className="accent-grid">
|
||
{accents.map((accent) => (
|
||
<button
|
||
key={accent.id}
|
||
className={`accent-choice ${preferences.accent === accent.id ? "selected" : ""}`}
|
||
onClick={() =>
|
||
perform(
|
||
() => api.updatePreferences({ accent: accent.id }),
|
||
`Accent ${accent.label} toegepast`,
|
||
)
|
||
}
|
||
aria-label={`Accent ${accent.label}`}
|
||
aria-pressed={preferences.accent === accent.id}
|
||
>
|
||
<span className={`accent-swatch ${accent.id}`} />
|
||
<span>{accent.label}</span>
|
||
{preferences.accent === accent.id && <Check size={15} />}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
<h3 className="setting-label">Ruimtegebruik</h3>
|
||
<div className="choice-grid three-up">
|
||
{widths.map((width) => {
|
||
const Icon = width.icon;
|
||
return (
|
||
<button
|
||
key={width.id}
|
||
className={`option-card ${preferences.contentWidth === width.id ? "selected" : ""}`}
|
||
aria-pressed={preferences.contentWidth === width.id}
|
||
onClick={() =>
|
||
perform(
|
||
() => api.updatePreferences({ contentWidth: width.id }),
|
||
`Canvas ${width.label} geselecteerd`,
|
||
)
|
||
}
|
||
>
|
||
<Icon size={20} />
|
||
<strong>{width.label}</strong>
|
||
<small>{width.copy}</small>
|
||
{preferences.contentWidth === width.id && <Check size={15} />}
|
||
</button>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
<h3 className="setting-label">Achtergrond</h3>
|
||
<div className="background-grid">
|
||
{backgrounds.map((background) => (
|
||
<button
|
||
key={background.id}
|
||
className={`background-card ${background.id} ${preferences.backgroundStyle === background.id ? "selected" : ""}`}
|
||
aria-pressed={preferences.backgroundStyle === background.id}
|
||
onClick={() =>
|
||
perform(
|
||
() =>
|
||
api.updatePreferences({ backgroundStyle: background.id }),
|
||
`Achtergrond ${background.label} geselecteerd`,
|
||
)
|
||
}
|
||
>
|
||
<span />
|
||
<strong>{background.label}</strong>
|
||
<small>{background.copy}</small>
|
||
{preferences.backgroundStyle === background.id && (
|
||
<Check size={15} />
|
||
)}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
<section className="wallpaper-settings">
|
||
<header className="wallpaper-editor-heading">
|
||
<span className="wallpaper-editor-icon">
|
||
<ImageIcon size={20} />
|
||
</span>
|
||
<span>
|
||
<strong>Wallpaperstudio</strong>
|
||
<small>
|
||
Stem de uitsnede af op het zichtbare centrale vlak. De preview
|
||
reageert direct; pas toe wanneer het beeld goed staat.
|
||
</small>
|
||
</span>
|
||
<div className="wallpaper-actions">
|
||
<input
|
||
ref={wallpaperInput}
|
||
className="sr-only"
|
||
type="file"
|
||
accept="image/png,image/jpeg,image/webp"
|
||
onChange={(event) => {
|
||
const file = event.target.files?.[0];
|
||
if (!file) return;
|
||
void perform(
|
||
() => api.uploadWallpaper(file),
|
||
"Wallpaper bijgewerkt",
|
||
);
|
||
event.target.value = "";
|
||
}}
|
||
/>
|
||
<button
|
||
type="button"
|
||
className="secondary-button"
|
||
onClick={() => wallpaperInput.current?.click()}
|
||
>
|
||
<Upload size={16} />{" "}
|
||
{data.wallpaper ? "Vervangen" : "Uploaden"}
|
||
</button>
|
||
{data.wallpaper && (
|
||
<button
|
||
type="button"
|
||
className="ghost-danger-button"
|
||
onClick={() =>
|
||
void perform(
|
||
() => api.removeWallpaper(),
|
||
"Wallpaper verwijderd",
|
||
)
|
||
}
|
||
>
|
||
<Trash2 size={16} /> Verwijderen
|
||
</button>
|
||
)}
|
||
</div>
|
||
</header>
|
||
|
||
<div
|
||
className={`wallpaper-editor-preview ${preferences.backgroundStyle} ${data.wallpaper ? "has-wallpaper" : ""}`}
|
||
aria-label="Voorbeeld van de dashboardwallpaper"
|
||
>
|
||
{data.wallpaper ? (
|
||
<>
|
||
<img
|
||
className="wallpaper-preview-image"
|
||
src={data.wallpaper.url}
|
||
alt="Huidige dashboardwallpaper"
|
||
style={{
|
||
objectFit: wallpaperDraft.fit,
|
||
objectPosition: `${wallpaperDraft.positionX}% ${wallpaperDraft.positionY}%`,
|
||
transform: `scale(${wallpaperDraft.zoom / 100})`,
|
||
transformOrigin: `${wallpaperDraft.positionX}% ${wallpaperDraft.positionY}%`,
|
||
}}
|
||
/>
|
||
<img
|
||
className="wallpaper-preview-ambient"
|
||
src={data.wallpaper.url}
|
||
alt=""
|
||
aria-hidden="true"
|
||
style={{
|
||
objectPosition: `${wallpaperDraft.positionX}% ${wallpaperDraft.positionY}%`,
|
||
}}
|
||
/>
|
||
</>
|
||
) : (
|
||
<span className="wallpaper-preview-empty">
|
||
<ImageIcon size={30} />
|
||
<small>Upload eerst een PNG, JPEG of WebP tot 8 MB.</small>
|
||
</span>
|
||
)}
|
||
<span className="wallpaper-preview-overlay" />
|
||
<span className="wallpaper-preview-sidebar" />
|
||
<span className="wallpaper-preview-topbar" />
|
||
<span className="wallpaper-preview-card one" />
|
||
<span className="wallpaper-preview-card two" />
|
||
<span className="wallpaper-preview-card three" />
|
||
</div>
|
||
|
||
<div className="wallpaper-editor-controls">
|
||
<div className="wallpaper-fit-control">
|
||
<span>
|
||
<strong>Schaalmethode</strong>
|
||
<small>
|
||
Passend toont de volledige afbeelding; Vullend benut het
|
||
volledige vlak.
|
||
</small>
|
||
</span>
|
||
<div className="segmented-control" aria-label="Schaalmethode">
|
||
{(
|
||
[
|
||
["contain", "Passend"],
|
||
["cover", "Vullend"],
|
||
] as const
|
||
).map(([fit, label]) => (
|
||
<button
|
||
key={fit}
|
||
type="button"
|
||
className={wallpaperDraft.fit === fit ? "active" : ""}
|
||
aria-pressed={wallpaperDraft.fit === fit}
|
||
onClick={() =>
|
||
setWallpaperDraft((current) => ({ ...current, fit }))
|
||
}
|
||
>
|
||
{label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="wallpaper-slider-grid">
|
||
<label>
|
||
<span>
|
||
Horizontale focus{" "}
|
||
<strong>{wallpaperDraft.positionX}%</strong>
|
||
</span>
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max="100"
|
||
value={wallpaperDraft.positionX}
|
||
aria-label="Horizontale wallpaperfocus"
|
||
onChange={(event) =>
|
||
setWallpaperDraft((current) => ({
|
||
...current,
|
||
positionX: Number(event.target.value),
|
||
}))
|
||
}
|
||
/>
|
||
</label>
|
||
<label>
|
||
<span>
|
||
Verticale focus <strong>{wallpaperDraft.positionY}%</strong>
|
||
</span>
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max="100"
|
||
value={wallpaperDraft.positionY}
|
||
aria-label="Verticale wallpaperfocus"
|
||
onChange={(event) =>
|
||
setWallpaperDraft((current) => ({
|
||
...current,
|
||
positionY: Number(event.target.value),
|
||
}))
|
||
}
|
||
/>
|
||
</label>
|
||
<label>
|
||
<span>
|
||
Zoom <strong>{wallpaperDraft.zoom}%</strong>
|
||
</span>
|
||
<input
|
||
type="range"
|
||
min="80"
|
||
max="160"
|
||
value={wallpaperDraft.zoom}
|
||
aria-label="Wallpaperzoom"
|
||
onChange={(event) =>
|
||
setWallpaperDraft((current) => ({
|
||
...current,
|
||
zoom: Number(event.target.value),
|
||
}))
|
||
}
|
||
/>
|
||
</label>
|
||
</div>
|
||
|
||
<div className="wallpaper-editor-footer">
|
||
<small>
|
||
De dashboardwallpaper blijft vast in het zichtbare centrale
|
||
vlak terwijl de inhoud scrolt.
|
||
</small>
|
||
<div>
|
||
<button
|
||
type="button"
|
||
className="secondary-button"
|
||
onClick={() =>
|
||
setWallpaperDraft({
|
||
fit: "contain",
|
||
positionX: 50,
|
||
positionY: 50,
|
||
zoom: 100,
|
||
})
|
||
}
|
||
>
|
||
Herstellen
|
||
</button>
|
||
<button
|
||
type="button"
|
||
className="primary-button"
|
||
disabled={!data.wallpaper || !wallpaperDraftChanged}
|
||
onClick={() =>
|
||
void perform(
|
||
() =>
|
||
api.updatePreferences({
|
||
wallpaperFit: wallpaperDraft.fit,
|
||
wallpaperPositionX: wallpaperDraft.positionX,
|
||
wallpaperPositionY: wallpaperDraft.positionY,
|
||
wallpaperZoom: wallpaperDraft.zoom,
|
||
}),
|
||
"Wallpaperweergave toegepast",
|
||
)
|
||
}
|
||
>
|
||
<Check size={16} /> Toepassen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<h3 className="setting-label">Dashboarddetails</h3>
|
||
<div className="toggle-list">
|
||
<article>
|
||
<span className="toggle-icon">
|
||
<CloudSun size={18} />
|
||
</span>
|
||
<span>
|
||
<strong>Weer in de topbalk</strong>
|
||
<small>
|
||
Toon actuele temperatuur en weersituatie voor Mol.
|
||
</small>
|
||
</span>
|
||
<button
|
||
className={`switch ${preferences.weatherEnabled ? "on" : ""}`}
|
||
role="switch"
|
||
aria-label="Weer in de topbalk tonen"
|
||
aria-checked={preferences.weatherEnabled}
|
||
onClick={() =>
|
||
perform(
|
||
() =>
|
||
api.updatePreferences({
|
||
weatherEnabled: !preferences.weatherEnabled,
|
||
}),
|
||
preferences.weatherEnabled
|
||
? "Weer verborgen"
|
||
: "Weer getoond",
|
||
)
|
||
}
|
||
>
|
||
<span />
|
||
</button>
|
||
</article>
|
||
<article className="weather-location-row">
|
||
<span className="toggle-icon">
|
||
<CloudSun size={18} />
|
||
</span>
|
||
<span>
|
||
<strong>Weerlocatie</strong>
|
||
<small>Naam en coördinaten voor de weerkaart.</small>
|
||
</span>
|
||
<div className="weather-location-fields">
|
||
<input
|
||
aria-label="Naam weerlocatie"
|
||
defaultValue={preferences.weatherLocation}
|
||
onBlur={(event) => {
|
||
const value = event.target.value.trim();
|
||
if (value && value !== preferences.weatherLocation)
|
||
void perform(
|
||
() => api.updatePreferences({ weatherLocation: value }),
|
||
"Weerlocatie bijgewerkt",
|
||
);
|
||
}}
|
||
/>
|
||
<input
|
||
aria-label="Breedtegraad"
|
||
type="number"
|
||
step="0.001"
|
||
min="-90"
|
||
max="90"
|
||
defaultValue={preferences.weatherLatitude}
|
||
onBlur={(event) => {
|
||
const value = Number(event.target.value);
|
||
if (Number.isFinite(value))
|
||
void perform(
|
||
() => api.updatePreferences({ weatherLatitude: value }),
|
||
"Breedtegraad bijgewerkt",
|
||
);
|
||
}}
|
||
/>
|
||
<input
|
||
aria-label="Lengtegraad"
|
||
type="number"
|
||
step="0.001"
|
||
min="-180"
|
||
max="180"
|
||
defaultValue={preferences.weatherLongitude}
|
||
onBlur={(event) => {
|
||
const value = Number(event.target.value);
|
||
if (Number.isFinite(value))
|
||
void perform(
|
||
() =>
|
||
api.updatePreferences({ weatherLongitude: value }),
|
||
"Lengtegraad bijgewerkt",
|
||
);
|
||
}}
|
||
/>
|
||
</div>
|
||
</article>
|
||
<article>
|
||
<span className="toggle-icon">
|
||
<Sparkles size={18} />
|
||
</span>
|
||
<span>
|
||
<strong>Welkomstblok</strong>
|
||
<small>
|
||
Toon de begroeting, het overzicht en snelle statistieken.
|
||
</small>
|
||
</span>
|
||
<button
|
||
className={`switch ${preferences.showHero ? "on" : ""}`}
|
||
role="switch"
|
||
aria-label="Welkomstblok tonen"
|
||
aria-checked={preferences.showHero}
|
||
onClick={() =>
|
||
perform(
|
||
() =>
|
||
api.updatePreferences({
|
||
showHero: !preferences.showHero,
|
||
}),
|
||
preferences.showHero
|
||
? "Welkomstblok verborgen"
|
||
: "Welkomstblok getoond",
|
||
)
|
||
}
|
||
>
|
||
<span />
|
||
</button>
|
||
</article>
|
||
<article>
|
||
<span className="toggle-icon">
|
||
<Eye size={18} />
|
||
</span>
|
||
<span>
|
||
<strong>Servicestatus</strong>
|
||
<small>
|
||
Toon beschikbaarheidstekst en statusindicatoren op tegels.
|
||
</small>
|
||
</span>
|
||
<button
|
||
className={`switch ${preferences.showStatus ? "on" : ""}`}
|
||
role="switch"
|
||
aria-label="Servicestatus tonen"
|
||
aria-checked={preferences.showStatus}
|
||
onClick={() =>
|
||
perform(
|
||
() =>
|
||
api.updatePreferences({
|
||
showStatus: !preferences.showStatus,
|
||
}),
|
||
preferences.showStatus
|
||
? "Servicestatus verborgen"
|
||
: "Servicestatus getoond",
|
||
)
|
||
}
|
||
>
|
||
<span />
|
||
</button>
|
||
</article>
|
||
<article>
|
||
<span className="toggle-icon">
|
||
<Server size={18} />
|
||
</span>
|
||
<span>
|
||
<strong>Unraid-serverpaneel</strong>
|
||
<small>
|
||
Toon hostcapaciteit, containerstatus en Docker-details.
|
||
</small>
|
||
</span>
|
||
<button
|
||
className={`switch ${preferences.showServerWidget ? "on" : ""}`}
|
||
role="switch"
|
||
aria-label="Unraid-serverpaneel tonen"
|
||
aria-checked={preferences.showServerWidget}
|
||
onClick={() =>
|
||
perform(
|
||
() =>
|
||
api.updatePreferences({
|
||
showServerWidget: !preferences.showServerWidget,
|
||
}),
|
||
preferences.showServerWidget
|
||
? "Unraid-paneel verborgen"
|
||
: "Unraid-paneel getoond",
|
||
)
|
||
}
|
||
>
|
||
<span />
|
||
</button>
|
||
</article>
|
||
<article>
|
||
<span className="toggle-icon">
|
||
<Accessibility size={18} />
|
||
</span>
|
||
<span>
|
||
<strong>Verminderde beweging</strong>
|
||
<small>
|
||
Schakel decoratieve animaties en vloeiende overgangen uit.
|
||
</small>
|
||
</span>
|
||
<button
|
||
className={`switch ${preferences.reducedMotion ? "on" : ""}`}
|
||
role="switch"
|
||
aria-label="Verminderde beweging"
|
||
aria-checked={preferences.reducedMotion}
|
||
onClick={() =>
|
||
perform(
|
||
() =>
|
||
api.updatePreferences({
|
||
reducedMotion: !preferences.reducedMotion,
|
||
}),
|
||
preferences.reducedMotion
|
||
? "Animaties ingeschakeld"
|
||
: "Beweging verminderd",
|
||
)
|
||
}
|
||
>
|
||
<span />
|
||
</button>
|
||
</article>
|
||
<article>
|
||
<span className="toggle-icon">
|
||
<RefreshCw size={18} />
|
||
</span>
|
||
<span>
|
||
<strong>Verversingsinterval</strong>
|
||
<small>
|
||
Hoe vaak DockDeck statussen en actieve panelen opnieuw
|
||
ophaalt.
|
||
</small>
|
||
</span>
|
||
<label className="inline-select-control">
|
||
<span className="sr-only">Verversingsinterval</span>
|
||
<select
|
||
name="pollingIntervalMs"
|
||
value={preferences.pollingIntervalMs}
|
||
aria-label="Verversingsinterval"
|
||
onChange={(event) =>
|
||
void perform(
|
||
() =>
|
||
api.updatePreferences({
|
||
pollingIntervalMs: Number(event.target.value),
|
||
}),
|
||
"Verversingsinterval bijgewerkt",
|
||
)
|
||
}
|
||
>
|
||
<option value={10000}>10 seconden</option>
|
||
<option value={30000}>30 seconden</option>
|
||
<option value={60000}>1 minuut</option>
|
||
<option value={120000}>2 minuten</option>
|
||
<option value={300000}>5 minuten</option>
|
||
</select>
|
||
</label>
|
||
</article>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
</>
|
||
);
|
||
}
|