222 lines
7.7 KiB
TypeScript
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;
|