import { PlusIcon } from '@radix-ui/react-icons'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; import { useModule } from '../../lib/react-utils'; import { modules } from '../../store/api/reducer'; import { accessLevels, AccessLevelType, TwitchBotCustomCommand, } from '../../store/api/types'; import AlertContent from '../components/AlertContent'; import DialogContent from '../components/DialogContent'; import { Button, ComboBox, Dialog, DialogActions, DialogClose, Field, FieldNote, FlexRow, InputBox, Label, PageContainer, PageHeader, PageTitle, styled, Textarea, TextBlock, } from '../theme'; import { Alert, AlertTrigger } from '../theme/alert'; const CommandList = styled('div', { marginTop: '1rem' }); const CommandItemContainer = styled('article', { backgroundColor: '$gray2', 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 CommandHeader = styled('header', { display: 'flex', gap: '0.5rem', alignItems: 'center', marginBottom: '0.4rem', }); const CommandName = styled('span', { color: '$teal10', fontWeight: 'bold', variants: { status: { enabled: {}, disabled: { color: '$gray10', }, }, }, }); const CommandDescription = styled('span', { flex: 1, }); const CommandActions = styled('div', { display: 'flex', alignItems: 'center', gap: '0.25rem', }); const CommandText = styled('div', { fontFamily: 'Space Mono', fontSize: '10pt', margin: '-0.5rem', marginTop: '0', padding: '0.5rem', backgroundColor: '$gray4', lineHeight: '1.2rem', }); const ACLIndicator = styled('span', { fontFamily: 'Space Mono', fontSize: '10pt', marginRight: '0.5rem', }); interface CommandItemProps { name: string; item: TwitchBotCustomCommand; onToggle?: () => void; onEdit?: () => void; onDelete?: () => void; } function CommandItemEl({ name, item, onToggle, onEdit, onDelete, }: CommandItemProps): React.ReactElement { const { t } = useTranslation(); return ( {name} {item.description} {item.access_level !== 'everyone' && ( {t(`pages.botcommands.acl.${item.access_level}`)} {item.access_level !== 'streamer' && '+'} )} (onDelete ? onDelete() : null)} /> {item.response} ); } const CommandItem = React.memo(CommandItemEl); type DialogPrompt = | { kind: 'new' } | { kind: 'edit'; name: string; item: TwitchBotCustomCommand }; function CommandDialog({ kind, name, item, onSubmit, }: { kind: 'new' | 'edit'; name?: string; item?: TwitchBotCustomCommand; onSubmit?: (name: string, item: TwitchBotCustomCommand) => void; }) { const [commandName, setCommandName] = useState(name ?? ''); const [description, setDescription] = useState(item?.description ?? ''); const [response, setResponse] = useState(item?.response ?? ''); const [accessLevel, setAccessLevel] = useState( item?.access_level ?? 'everyone', ); const { t } = useTranslation(); return (
{ e.preventDefault(); if (onSubmit) { onSubmit(commandName, { ...item, description, response, access_level: accessLevel as AccessLevelType, }); } }} > setCommandName(e.target.value)} placeholder={t('pages.botcommands.command-name-placeholder')} /> setDescription(e.target.value)} placeholder={t('pages.botcommands.command-desc-placeholder')} /> setAccessLevel(e.target.value as AccessLevelType)} > {accessLevels.map((level) => ( ))} {t('pages.botcommands.command-acl-help')}
); } export default function TwitchBotCommandsPage(): React.ReactElement { const [botCommands, setBotCommands] = useModule(modules.twitchBotCommands); const [commandFilter, setCommandFilter] = useState(''); const [activeDialog, setActiveDialog] = useState(null); const { t } = useTranslation(); const dispatch = useDispatch(); const commandFilterLC = commandFilter.toLowerCase(); const setCommand = (newName: string, data: TwitchBotCustomCommand): void => { switch (activeDialog.kind) { case 'new': dispatch( setBotCommands({ ...botCommands, [newName]: { ...data, enabled: true, }, }), ); break; case 'edit': { const oldName = activeDialog.name; dispatch( setBotCommands({ ...botCommands, [oldName]: undefined, [newName]: data, }), ); break; } } setActiveDialog(null); }; const deleteCommand = (cmd: string): void => { dispatch( setBotCommands({ ...botCommands, [cmd]: undefined, }), ); }; const toggleCommand = (cmd: string): void => { dispatch( setBotCommands({ ...botCommands, [cmd]: { ...botCommands[cmd], enabled: !botCommands[cmd].enabled, }, }), ); }; return ( {t('pages.botcommands.title')} {t('pages.botcommands.desc')} setCommandFilter(e.target.value)} /> {Object.keys(botCommands ?? {}) ?.filter((cmd) => cmd.toLowerCase().includes(commandFilterLC)) .sort() .map((cmd) => ( toggleCommand(cmd)} onEdit={() => setActiveDialog({ kind: 'edit', name: cmd, item: botCommands[cmd], }) } onDelete={() => deleteCommand(cmd)} /> ))} { if (!open) { // Reset dialog status on dialog close setActiveDialog(null); } }} > {activeDialog && ( setCommand(name, data)} /> )} ); }