39948-vm/frontend/src/pages/global-transition-defaults.tsx

222 lines
7.7 KiB
TypeScript

import { mdiContentSave, mdiSwapHorizontal } from '@mdi/js';
import Head from 'next/head';
import Link from 'next/link';
import React, { ReactElement, useEffect, useState } from 'react';
import BaseButton from '../components/BaseButton';
import CardBox from '../components/CardBox';
import SectionMain from '../components/SectionMain';
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
import { getPageTitle } from '../config';
import LayoutAuthenticated from '../layouts/Authenticated';
import { logger } from '../lib/logger';
import { useAppDispatch, useAppSelector } from '../stores/hooks';
import {
fetch as fetchGlobalTransitionDefaults,
update as updateGlobalTransitionDefaults,
} from '../stores/global_transition_defaults/globalTransitionDefaultsSlice';
import type { EasingFunction, TransitionType } from '../types/transition';
const TRANSITION_TYPES: { value: TransitionType; label: string }[] = [
{ value: 'fade', label: 'Fade' },
{ value: 'none', label: 'None (instant)' },
];
const EASING_OPTIONS: { value: EasingFunction; label: string }[] = [
{ value: 'ease-in-out', label: 'Ease In-Out' },
{ value: 'ease-in', label: 'Ease In' },
{ value: 'ease-out', label: 'Ease Out' },
{ value: 'linear', label: 'Linear' },
];
const GlobalTransitionDefaultsPage = () => {
const dispatch = useAppDispatch();
const defaults = useAppSelector(
(state) => state.global_transition_defaults.data,
);
const isLoading = useAppSelector(
(state) => state.global_transition_defaults.loading,
);
const [transitionType, setTransitionType] = useState<TransitionType>('fade');
const [durationMs, setDurationMs] = useState<number>(700);
const [easing, setEasing] = useState<EasingFunction>('ease-in-out');
const [overlayColor, setOverlayColor] = useState<string>('#000000');
const [isSaving, setIsSaving] = useState(false);
const [saveSuccess, setSaveSuccess] = useState(false);
useEffect(() => {
dispatch(fetchGlobalTransitionDefaults());
}, [dispatch]);
useEffect(() => {
if (!defaults) return;
setTransitionType(defaults.transition_type);
setDurationMs(defaults.duration_ms);
setEasing(defaults.easing);
setOverlayColor(defaults.overlay_color ?? '#000000');
}, [defaults]);
const handleSave = async () => {
if (!defaults?.id) return;
setIsSaving(true);
setSaveSuccess(false);
try {
await dispatch(
updateGlobalTransitionDefaults({
id: defaults.id,
data: {
transition_type: transitionType,
duration_ms: durationMs,
easing,
overlay_color: overlayColor,
},
}),
).unwrap();
setSaveSuccess(true);
setTimeout(() => setSaveSuccess(false), 2000);
} catch (error) {
logger.error('Failed to save global transition defaults:', error);
} finally {
setIsSaving(false);
}
};
return (
<>
<Head>
<title>{getPageTitle('Global Transition Defaults')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton
icon={mdiSwapHorizontal}
title='Global Transition Defaults'
main
>
{''}
</SectionTitleLineWithButton>
<CardBox className='mb-6'>
<div className='flex flex-wrap items-center justify-between gap-3'>
<Link
href='/element-type-defaults'
className='inline-flex rounded border border-blue-600 px-4 py-2 text-sm font-medium text-blue-600 hover:bg-blue-50'
>
Back to Element Type Defaults
</Link>
<div className='flex items-center gap-3'>
{saveSuccess && (
<span className='text-xs text-green-600'>
Saved successfully!
</span>
)}
<BaseButton
label={isSaving ? 'Saving...' : 'Save'}
icon={mdiContentSave}
color='info'
onClick={handleSave}
disabled={isSaving || isLoading || !defaults}
/>
</div>
</div>
</CardBox>
<CardBox>
{isLoading && !defaults ? (
<p className='text-sm text-gray-500'>
Loading global transition defaults...
</p>
) : (
<div className='grid grid-cols-1 gap-4 md:grid-cols-4'>
<div>
<label className='mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400'>
Transition Type
</label>
<select
className='w-full rounded border border-gray-300 px-3 py-2 text-sm dark:border-dark-600 dark:bg-dark-800'
value={transitionType}
onChange={(event) =>
setTransitionType(event.target.value as TransitionType)
}
>
{TRANSITION_TYPES.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
<div>
<label className='mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400'>
Duration (ms)
</label>
<input
type='number'
min='0'
className='w-full rounded border border-gray-300 px-3 py-2 text-sm dark:border-dark-600 dark:bg-dark-800'
value={durationMs}
onChange={(event) =>
setDurationMs(
Math.max(0, parseInt(event.target.value, 10) || 0),
)
}
/>
</div>
<div>
<label className='mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400'>
Easing
</label>
<select
className='w-full rounded border border-gray-300 px-3 py-2 text-sm dark:border-dark-600 dark:bg-dark-800'
value={easing}
onChange={(event) =>
setEasing(event.target.value as EasingFunction)
}
>
{EASING_OPTIONS.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
<div>
<label className='mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400'>
Overlay Color
</label>
<div className='flex gap-2'>
<input
type='color'
className='h-9 w-12 cursor-pointer rounded border border-gray-300 p-0.5 dark:border-dark-600'
value={overlayColor}
onChange={(event) => setOverlayColor(event.target.value)}
/>
<input
type='text'
className='flex-1 rounded border border-gray-300 px-3 py-2 text-sm dark:border-dark-600 dark:bg-dark-800'
value={overlayColor}
onChange={(event) => setOverlayColor(event.target.value)}
placeholder='#000000'
/>
</div>
</div>
</div>
)}
</CardBox>
</SectionMain>
</>
);
};
GlobalTransitionDefaultsPage.getLayout = function getLayout(
page: ReactElement,
) {
return (
<LayoutAuthenticated permission='UPDATE_PAGE_ELEMENTS'>
{page}
</LayoutAuthenticated>
);
};
export default GlobalTransitionDefaultsPage;