import { PlusIcon } from '@radix-ui/react-icons'; import { TFunction } from 'i18next'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useModule } from '~/lib/react'; import { useAppDispatch } from '~/store'; import { modules } from '~/store/api/reducer'; import { TwitchChatTimer } from '~/store/api/types'; import AlertContent from '../components/AlertContent'; import DialogContent from '../components/DialogContent'; import Interval from '../components/forms/Interval'; import { hours, minutes } from '../components/forms/units'; import MultiInput from '../components/forms/MultiInput'; import { Button, Dialog, DialogActions, DialogClose, Field, FlexRow, InputBox, Label, MultiButton, NoneText, PageContainer, PageHeader, PageTitle, styled, TextBlock, } from '../theme'; import { Alert, AlertTrigger } from '../theme/alert'; const TimerList = styled('div', { marginTop: '1rem' }); const TimerItemContainer = styled('article', { backgroundColor: '$gray3', margin: '0.5rem 0', padding: '0.5rem', borderLeft: '5px solid $teal8', borderRadius: '0.25rem', borderBottom: '1px solid $gray4', transition: 'all 50ms', '&:hover': { backgroundColor: '$gray3', }, variants: { status: { enabled: {}, disabled: { borderLeftColor: '$red7', backgroundColor: '$gray3', color: '$gray10', }, }, }, }); const TimerHeader = styled('header', { display: 'flex', gap: '0.5rem', alignItems: 'center', marginBottom: '0.4rem', }); const TimerName = styled('span', { color: '$teal10', fontWeight: 'bold', variants: { status: { enabled: {}, disabled: { color: '$gray10', }, }, }, }); const TimerDescription = styled('span', { flex: 1, }); const TimerActions = styled('div', { display: 'flex', alignItems: 'center', gap: '0.25rem', }); const TimerText = styled('div', { fontFamily: 'Space Mono', fontSize: '10pt', margin: '0 -0.5rem', marginTop: '0', marginBottom: '0.3rem', padding: '0.5rem', backgroundColor: '$gray4', lineHeight: '1.2rem', '&:last-child': { marginBottom: '-0.5rem', }, }); function humanTime(t: TFunction<'translation'>, secs: number): string { const mins = Math.floor(secs / 60); const hrs = Math.floor(mins / 60); if (hrs > 0) { return t('time.x-hours', { time: hrs }); } if (mins > 0) { return t('time.x-minutes', { time: mins }); } return t('time.x-seconds', { time: secs }); } interface TimerItemProps { name: string; item: TwitchChatTimer; onToggle?: () => void; onEdit?: () => void; onDelete?: () => void; } function TimerItem({ name, item, onToggle, onEdit, onDelete, }: TimerItemProps): React.ReactElement { const { t } = useTranslation(); return ( {name} ( {t('pages.bottimers.timer-parameters', { time: humanTime(t, item.minimum_delay), messages: item.minimum_chat_activity, interval: humanTime(t, 300), })} ) (onDelete ? onDelete() : null)} /> {item.messages?.map((message, index) => ( {message} ))} ); } type DialogPrompt = | { kind: 'new' } | { kind: 'edit'; name: string; item: TwitchChatTimer }; function TimerDialog({ kind, name, item, onSubmit, }: { kind: 'new' | 'edit'; name?: string; item?: TwitchChatTimer; onSubmit?: (name: string, item: TwitchChatTimer) => void; }) { const [timerConfig] = useModule(modules.twitchChatTimers); const [timerName, setName] = useState(name ?? ''); const [messages, setMessages] = useState(item?.messages ?? ['']); const [minDelay, setMinDelay] = useState(item?.minimum_delay ?? 300); const [minActivity, setMinActivity] = useState( item?.minimum_chat_activity ?? 5, ); const { t } = useTranslation(); return (
{ if (!(e.target as HTMLFormElement).checkValidity()) { return; } e.preventDefault(); if (onSubmit) { onSubmit(timerName, { ...item, messages, minimum_delay: minDelay, minimum_chat_activity: minActivity, }); } }} > { setName(e.target.value); // If timer name is different but matches another defined timer, set as invalid if ( e.target.value !== name && e.target.value in timerConfig.timers ) { (e.target as HTMLInputElement).setCustomValidity( t('pages.bottimers.name-already-in-use'), ); } else { (e.target as HTMLInputElement).setCustomValidity(''); } }} placeholder={t('pages.bottimers.timer-name-placeholder')} required={true} /> { const intNum = parseInt(ev.target.value, 10); if (Number.isNaN(intNum)) { return; } setMinActivity(intNum); }} placeholder="#" /> {t('pages.bottimers.timer-activity-desc')}
); } export default function TwitchChatTimersPage(): React.ReactElement { const [timerConfig, setTimerConfig] = useModule(modules.twitchChatTimers); const [filter, setFilter] = useState(''); const [activeDialog, setActiveDialog] = useState(null); const { t } = useTranslation(); const dispatch = useAppDispatch(); const filterLC = filter.toLowerCase(); const setTimer = (newName: string, data: TwitchChatTimer): void => { switch (activeDialog.kind) { case 'new': void dispatch( setTimerConfig({ ...timerConfig, timers: { ...timerConfig.timers, [newName]: { ...data, enabled: true, }, }, }), ); break; case 'edit': { const oldName = activeDialog.name; void dispatch( setTimerConfig({ ...timerConfig, timers: { ...timerConfig.timers, [oldName]: undefined, [newName]: data, }, }), ); break; } } setActiveDialog(null); }; const deleteTimer = (cmd: string): void => { void dispatch( setTimerConfig({ ...timerConfig, timers: { ...timerConfig.timers, [cmd]: undefined, }, }), ); }; const toggleTimer = (cmd: string): void => { void dispatch( setTimerConfig({ ...timerConfig, timers: { ...timerConfig.timers, [cmd]: { ...timerConfig.timers[cmd], enabled: !timerConfig.timers[cmd].enabled, }, }, }), ); }; return ( {t('pages.bottimers.title')} {t('pages.bottimers.desc')} setFilter(e.target.value)} /> {timerConfig?.timers ? ( Object.keys(timerConfig?.timers ?? {}) ?.filter((cmd) => cmd.toLowerCase().includes(filterLC)) .sort() .map((cmd) => ( toggleTimer(cmd)} onEdit={() => setActiveDialog({ kind: 'edit', name: cmd, item: timerConfig.timers[cmd], }) } onDelete={() => deleteTimer(cmd)} /> )) ) : ( {t('pages.bottimers.no-timers')} )} { if (!open) { // Reset dialog status on dialog close setActiveDialog(null); } }} > {activeDialog && ( setTimer(name, data)} /> )} ); }