mirror of
https://git.sr.ht/~ashkeel/strimertul
synced 2024-09-20 02:00:49 +00:00
WIP Timer UI
This commit is contained in:
parent
f9538f062d
commit
08d3b68f2e
10 changed files with 400 additions and 32 deletions
|
@ -62,30 +62,7 @@ export function useUserPoints(): LoyaltyStorage {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useInterval(
|
|
||||||
initialValue: number,
|
|
||||||
): [
|
|
||||||
number,
|
|
||||||
number,
|
|
||||||
number,
|
|
||||||
(newNum: number) => void,
|
|
||||||
(newMult: number) => void,
|
|
||||||
] {
|
|
||||||
const [value, setValue] = useState(initialValue);
|
|
||||||
|
|
||||||
const [numInitialValue, multInitialValue] = getInterval(value);
|
|
||||||
const [num, setNum] = useState(numInitialValue);
|
|
||||||
const [mult, setMult] = useState(multInitialValue);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setValue(num * mult);
|
|
||||||
}, [num, mult]);
|
|
||||||
|
|
||||||
return [value, num, mult, setNum, setMult];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
useModule,
|
useModule,
|
||||||
useUserPoints,
|
useUserPoints,
|
||||||
useInterval,
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"/twitch/settings": "Module configuration",
|
"/twitch/settings": "Module configuration",
|
||||||
"/twitch/bot/settings": "Bot configuration",
|
"/twitch/bot/settings": "Bot configuration",
|
||||||
"/twitch/bot/commands": "Bot commands",
|
"/twitch/bot/commands": "Bot commands",
|
||||||
|
"/twitch/bot/timers": "Bot timers",
|
||||||
"/twitch/bot/modules": "Bot modules",
|
"/twitch/bot/modules": "Bot modules",
|
||||||
"/loyalty": "Loyalty points",
|
"/loyalty": "Loyalty points",
|
||||||
"/loyalty/settings": "Configuration",
|
"/loyalty/settings": "Configuration",
|
||||||
|
@ -204,6 +205,20 @@
|
||||||
"new-command": "New command",
|
"new-command": "New command",
|
||||||
"search": "Search by name",
|
"search": "Search by name",
|
||||||
"modify-command": "Modify command"
|
"modify-command": "Modify command"
|
||||||
|
},
|
||||||
|
"timers": {
|
||||||
|
"header": "Bot timers",
|
||||||
|
"new-timer": "New timer",
|
||||||
|
"modify-timer": "Modify timer",
|
||||||
|
"search": "Search by timer name",
|
||||||
|
"messages": "Messages",
|
||||||
|
"name-hint": "Timer name",
|
||||||
|
"name": "Name",
|
||||||
|
"message-help": "What to write in chat",
|
||||||
|
"minimum-delay": "Interval",
|
||||||
|
"minimum-delay-help": "How many time must pass between each repeat.",
|
||||||
|
"minimum-activity": "Minimum chat activity",
|
||||||
|
"minimum-activity-post": "messages in the last 5 minutes"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,6 +147,13 @@ export const modules = {
|
||||||
state.moduleConfigs.twitchBotConfig = payload;
|
state.moduleConfigs.twitchBotConfig = payload;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
twitchBotModulesConfig: makeModule(
|
||||||
|
'twitch/bot-modules/config',
|
||||||
|
(state) => state.twitchBot?.modules,
|
||||||
|
(state, { payload }) => {
|
||||||
|
state.twitchBot.modules = payload;
|
||||||
|
},
|
||||||
|
),
|
||||||
twitchBotCommands: makeModule(
|
twitchBotCommands: makeModule(
|
||||||
'twitch/bot-custom-commands',
|
'twitch/bot-custom-commands',
|
||||||
(state) => state.twitchBot?.commands,
|
(state) => state.twitchBot?.commands,
|
||||||
|
@ -154,6 +161,13 @@ export const modules = {
|
||||||
state.twitchBot.commands = payload;
|
state.twitchBot.commands = payload;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
twitchBotTimers: makeModule(
|
||||||
|
'twitch/bot-modules/timers/config',
|
||||||
|
(state) => state.twitchBot?.timers,
|
||||||
|
(state, { payload }) => {
|
||||||
|
state.twitchBot.timers = payload;
|
||||||
|
},
|
||||||
|
),
|
||||||
stulbeConfig: makeModule(
|
stulbeConfig: makeModule(
|
||||||
'stulbe/config',
|
'stulbe/config',
|
||||||
(state) => state.moduleConfigs?.stulbeConfig,
|
(state) => state.moduleConfigs?.stulbeConfig,
|
||||||
|
@ -229,6 +243,8 @@ const initialState: APIState = {
|
||||||
},
|
},
|
||||||
twitchBot: {
|
twitchBot: {
|
||||||
commands: null,
|
commands: null,
|
||||||
|
modules: null,
|
||||||
|
timers: null,
|
||||||
},
|
},
|
||||||
moduleConfigs: {
|
moduleConfigs: {
|
||||||
moduleConfig: null,
|
moduleConfig: null,
|
||||||
|
|
|
@ -30,6 +30,10 @@ interface TwitchBotConfig {
|
||||||
chat_history: number;
|
chat_history: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TwitchModulesConfig {
|
||||||
|
enable_timers: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
type AccessLevelType = 'everyone' | 'vip' | 'moderators' | 'streamer';
|
type AccessLevelType = 'everyone' | 'vip' | 'moderators' | 'streamer';
|
||||||
|
|
||||||
export interface TwitchBotCustomCommand {
|
export interface TwitchBotCustomCommand {
|
||||||
|
@ -57,6 +61,18 @@ interface LoyaltyConfig {
|
||||||
banlist: string[];
|
banlist: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TwitchBotTimer {
|
||||||
|
enabled: boolean;
|
||||||
|
name: string;
|
||||||
|
minimum_chat_activity: number;
|
||||||
|
minimum_delay: number;
|
||||||
|
messages: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TwitchBotTimersConfig {
|
||||||
|
timers: Record<string, TwitchBotTimer>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface LoyaltyPointsEntry {
|
export interface LoyaltyPointsEntry {
|
||||||
points: number;
|
points: number;
|
||||||
}
|
}
|
||||||
|
@ -105,6 +121,8 @@ export interface APIState {
|
||||||
};
|
};
|
||||||
twitchBot: {
|
twitchBot: {
|
||||||
commands: TwitchBotCustomCommands;
|
commands: TwitchBotCustomCommands;
|
||||||
|
modules: TwitchModulesConfig;
|
||||||
|
timers: TwitchBotTimersConfig;
|
||||||
};
|
};
|
||||||
moduleConfigs: {
|
moduleConfigs: {
|
||||||
moduleConfig: ModuleConfig;
|
moduleConfig: ModuleConfig;
|
||||||
|
|
|
@ -22,6 +22,7 @@ import TwitchBotCommandsPage from './pages/twitch/Commands';
|
||||||
import TwitchBotModulesPage from './pages/twitch/Modules';
|
import TwitchBotModulesPage from './pages/twitch/Modules';
|
||||||
import StulbeConfigPage from './pages/stulbe/Config';
|
import StulbeConfigPage from './pages/stulbe/Config';
|
||||||
import StulbeWebhooksPage from './pages/stulbe/Webhook';
|
import StulbeWebhooksPage from './pages/stulbe/Webhook';
|
||||||
|
import TwitchBotTimersPage from './pages/twitch/Timers';
|
||||||
|
|
||||||
interface RouteItem {
|
interface RouteItem {
|
||||||
name?: string;
|
name?: string;
|
||||||
|
@ -38,6 +39,7 @@ const menu: RouteItem[] = [
|
||||||
{ route: '/twitch/settings' },
|
{ route: '/twitch/settings' },
|
||||||
{ route: '/twitch/bot/settings' },
|
{ route: '/twitch/bot/settings' },
|
||||||
{ route: '/twitch/bot/commands' },
|
{ route: '/twitch/bot/commands' },
|
||||||
|
{ route: '/twitch/bot/timers' },
|
||||||
{ route: '/twitch/bot/modules' },
|
{ route: '/twitch/bot/modules' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -127,6 +129,7 @@ export default function App(): React.ReactElement {
|
||||||
<TwitchSettingsPage path="settings" />
|
<TwitchSettingsPage path="settings" />
|
||||||
<TwitchBotSettingsPage path="bot/settings" />
|
<TwitchBotSettingsPage path="bot/settings" />
|
||||||
<TwitchBotCommandsPage path="bot/commands" />
|
<TwitchBotCommandsPage path="bot/commands" />
|
||||||
|
<TwitchBotTimersPage path="bot/timers" />
|
||||||
<TwitchBotModulesPage path="bot/modules" />
|
<TwitchBotModulesPage path="bot/modules" />
|
||||||
</TwitchPage>
|
</TwitchPage>
|
||||||
<LoyaltyPage path="loyalty">
|
<LoyaltyPage path="loyalty">
|
||||||
|
|
|
@ -1,19 +1,29 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useInterval } from '../../lib/react-utils';
|
import { getInterval } from '../../lib/time-utils';
|
||||||
|
|
||||||
export interface IntervalProps {
|
export interface IntervalProps {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
value: number;
|
value: number;
|
||||||
|
min?: number;
|
||||||
onChange?: (value: number) => void;
|
onChange?: (value: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Interval({ active, value, onChange }: IntervalProps) {
|
function Interval({ active, value, min, onChange }: IntervalProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [valueNum, num, mult, setNum, setMult] = useInterval(value);
|
|
||||||
|
const [numInitialValue, multInitialValue] = getInterval(value);
|
||||||
|
const [num, setNum] = useState(numInitialValue);
|
||||||
|
const [mult, setMult] = useState(multInitialValue);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onChange(valueNum);
|
const seconds = num * mult;
|
||||||
}, [valueNum]);
|
if (min && seconds < min) {
|
||||||
|
setNum(5);
|
||||||
|
setMult(1);
|
||||||
|
}
|
||||||
|
onChange(Math.max(min ?? 0, seconds));
|
||||||
|
}, [num, mult]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -24,6 +34,7 @@ function Interval({ active, value, onChange }: IntervalProps) {
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="#"
|
placeholder="#"
|
||||||
value={num ?? ''}
|
value={num ?? ''}
|
||||||
|
style={{ width: '6em' }}
|
||||||
onChange={(ev) => {
|
onChange={(ev) => {
|
||||||
const intNum = parseInt(ev.target.value, 10);
|
const intNum = parseInt(ev.target.value, 10);
|
||||||
if (Number.isNaN(intNum)) {
|
if (Number.isNaN(intNum)) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import React, { useState } from 'react';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import prettyTime from 'pretty-ms';
|
import prettyTime from 'pretty-ms';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useInterval, useModule } from '../../../lib/react-utils';
|
import { useModule } from '../../../lib/react-utils';
|
||||||
import { RootState } from '../../../store';
|
import { RootState } from '../../../store';
|
||||||
import { createRedeem, modules } from '../../../store/api/reducer';
|
import { createRedeem, modules } from '../../../store/api/reducer';
|
||||||
import Modal from '../../components/Modal';
|
import Modal from '../../components/Modal';
|
||||||
|
|
|
@ -122,7 +122,12 @@ export default function LoyaltySettingPage(
|
||||||
{t('loyalty.config.points-every')}
|
{t('loyalty.config.points-every')}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<Interval value={interval} onChange={setInterval} active={active} />
|
<Interval
|
||||||
|
value={interval}
|
||||||
|
onChange={setInterval}
|
||||||
|
active={active}
|
||||||
|
min={5}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Field>
|
</Field>
|
||||||
<Field name={t('loyalty.config.bonus-points')}>
|
<Field name={t('loyalty.config.bonus-points')}>
|
||||||
|
|
319
frontend/src/ui/pages/twitch/Timers.tsx
Normal file
319
frontend/src/ui/pages/twitch/Timers.tsx
Normal file
|
@ -0,0 +1,319 @@
|
||||||
|
import { RouteComponentProps } from '@reach/router';
|
||||||
|
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 Modal from '../../components/Modal';
|
||||||
|
import { TwitchBotTimer } from '../../../store/api/types';
|
||||||
|
import Field from '../../components/Field';
|
||||||
|
import Interval from '../../components/Interval';
|
||||||
|
|
||||||
|
interface TimerItemProps {
|
||||||
|
item: TwitchBotTimer;
|
||||||
|
onToggleState: () => void;
|
||||||
|
onEdit: () => void;
|
||||||
|
onDelete: () => void;
|
||||||
|
}
|
||||||
|
function TimerItem({ item, onToggleState, onEdit, onDelete }: TimerItemProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card customcommand" style={{ marginBottom: '3px' }}>
|
||||||
|
<header className="card-header">
|
||||||
|
<div className="card-header-title">
|
||||||
|
{item.enabled ? (
|
||||||
|
<code>{item.name}</code>
|
||||||
|
) : (
|
||||||
|
<span className="reward-disabled">
|
||||||
|
<code>{item.name}</code>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
className="card-header-icon"
|
||||||
|
aria-label="expand"
|
||||||
|
onClick={() => setExpanded(!expanded)}
|
||||||
|
>
|
||||||
|
<span className={expanded ? 'icon expand-off' : 'icon expand-on'}>
|
||||||
|
❯
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</header>
|
||||||
|
{expanded ? (
|
||||||
|
<div className="content">
|
||||||
|
{t('twitch.timers.messages')}:{' '}
|
||||||
|
{item.messages.map((message) => (
|
||||||
|
<blockquote>{message}</blockquote>
|
||||||
|
))}
|
||||||
|
<div style={{ marginTop: '1rem' }}>
|
||||||
|
<a className="button is-small" onClick={onToggleState}>
|
||||||
|
{item.enabled ? 'Disable' : 'Enable'}
|
||||||
|
</a>{' '}
|
||||||
|
<a className="button is-small" onClick={onEdit}>
|
||||||
|
{t('actions.edit')}
|
||||||
|
</a>{' '}
|
||||||
|
<a className="button is-small" onClick={onDelete}>
|
||||||
|
{t('actions.delete')}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TimerModalProps {
|
||||||
|
active: boolean;
|
||||||
|
onConfirm: (newName: string, r: TwitchBotTimer) => void;
|
||||||
|
onClose: () => void;
|
||||||
|
initialData?: TwitchBotTimer;
|
||||||
|
initialName?: string;
|
||||||
|
title: string;
|
||||||
|
confirmText: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TimerModal({
|
||||||
|
active,
|
||||||
|
onConfirm,
|
||||||
|
onClose,
|
||||||
|
initialName,
|
||||||
|
initialData,
|
||||||
|
title,
|
||||||
|
confirmText,
|
||||||
|
}: TimerModalProps) {
|
||||||
|
const [name, setName] = useState(initialName ?? '');
|
||||||
|
const [messages, setMessages] = useState(initialData?.messages ?? ['']);
|
||||||
|
const [minDelay, setMinDelay] = useState(initialData?.minimum_delay ?? 300);
|
||||||
|
const [minActivity, setMinActivity] = useState(
|
||||||
|
initialData?.minimum_chat_activity ?? 5,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const validForm = name !== '' && messages.length > 0 && messages[0] !== '';
|
||||||
|
|
||||||
|
const confirm = () => {
|
||||||
|
if (onConfirm) {
|
||||||
|
onConfirm(name, {
|
||||||
|
name,
|
||||||
|
messages,
|
||||||
|
minimum_chat_activity: 0,
|
||||||
|
minimum_delay: 0,
|
||||||
|
enabled: initialData?.enabled ?? false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setMessageIndex = (value: string, index: number) => {
|
||||||
|
const newMessages = [...messages];
|
||||||
|
newMessages[index] = value;
|
||||||
|
setMessages(newMessages);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
active={active}
|
||||||
|
title={title}
|
||||||
|
showCancel={true}
|
||||||
|
bgDismiss={true}
|
||||||
|
confirmName={confirmText}
|
||||||
|
confirmClass="is-success"
|
||||||
|
confirmEnabled={validForm}
|
||||||
|
onConfirm={() => confirm()}
|
||||||
|
onClose={() => onClose()}
|
||||||
|
>
|
||||||
|
<Field name={t('twitch.timers.name')} horizontal>
|
||||||
|
<div className="field-body">
|
||||||
|
<div className="field">
|
||||||
|
<p className="control">
|
||||||
|
<input
|
||||||
|
className={name !== '' ? 'input' : 'input is-danger'}
|
||||||
|
type="text"
|
||||||
|
placeholder={t('twitch.timers.name-hint')}
|
||||||
|
value={name}
|
||||||
|
onChange={(ev) => setName(ev.target.value)}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Field>
|
||||||
|
<Field name={t('twitch.timers.minimum-delay')} horizontal>
|
||||||
|
<div className="field-body">
|
||||||
|
<div className="field has-addons" style={{ marginBottom: 0 }}>
|
||||||
|
<Interval
|
||||||
|
value={minDelay}
|
||||||
|
onChange={setMinDelay}
|
||||||
|
active={active}
|
||||||
|
min={5}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Field>
|
||||||
|
<Field name={t('twitch.timers.minimum-activity')} horizontal>
|
||||||
|
<div className="field-body">
|
||||||
|
<div className="field has-addons" style={{ marginBottom: 0 }}>
|
||||||
|
<p className="control">
|
||||||
|
<input
|
||||||
|
disabled={!active}
|
||||||
|
className="input"
|
||||||
|
type="number"
|
||||||
|
placeholder="#"
|
||||||
|
style={{ width: '6rem' }}
|
||||||
|
value={minActivity ?? 0}
|
||||||
|
onChange={(ev) => {
|
||||||
|
const amount = parseInt(ev.target.value, 10);
|
||||||
|
if (Number.isNaN(amount)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setMinActivity(amount);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<p className="control">
|
||||||
|
<a className="button is-static">
|
||||||
|
{t('twitch.timers.minimum-activity-post')}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Field>
|
||||||
|
<Field name={t('twitch.timers.messages')} horizontal>
|
||||||
|
<div className="field-body">
|
||||||
|
{messages.map((message, index) => (
|
||||||
|
<div className="field">
|
||||||
|
<p className="control">
|
||||||
|
<textarea
|
||||||
|
className={message !== '' ? 'textarea' : 'textarea is-danger'}
|
||||||
|
placeholder={t('twitch.timers.message-help')}
|
||||||
|
onChange={(ev) => setMessageIndex(ev.target.value, index)}
|
||||||
|
value={message}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Field>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TwitchBotTimersPage(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
props: RouteComponentProps<unknown>,
|
||||||
|
): React.ReactElement {
|
||||||
|
const [timers, setTimers] = useModule(modules.twitchBotTimers);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [createModal, setCreateModal] = useState(false);
|
||||||
|
const [showModifyTimer, setShowModifyTimer] = useState(null);
|
||||||
|
const [timerFilter, setTimerFilter] = useState('');
|
||||||
|
const timerFilterLC = timerFilter.toLowerCase();
|
||||||
|
|
||||||
|
const createTimer = (name: string, data: TwitchBotTimer): void => {
|
||||||
|
dispatch(
|
||||||
|
setTimers({
|
||||||
|
...timers,
|
||||||
|
[name]: data,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
setCreateModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const modifyTimer = (
|
||||||
|
oldName: string,
|
||||||
|
newName: string,
|
||||||
|
data: TwitchBotTimer,
|
||||||
|
): void => {
|
||||||
|
dispatch(
|
||||||
|
setTimers({
|
||||||
|
...timers,
|
||||||
|
[oldName]: undefined,
|
||||||
|
[newName]: {
|
||||||
|
...timers[oldName],
|
||||||
|
...data,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
setShowModifyTimer(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteTimer = (cmd: string): void => {
|
||||||
|
dispatch(
|
||||||
|
setTimers({
|
||||||
|
...timers,
|
||||||
|
[cmd]: undefined,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleTimer = (cmd: string): void => {
|
||||||
|
dispatch(
|
||||||
|
setTimers({
|
||||||
|
...timers,
|
||||||
|
[cmd]: {
|
||||||
|
...timers[cmd],
|
||||||
|
enabled: !timers[cmd].enabled,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1 className="title is-4">{t('twitch.timers.header')}</h1>
|
||||||
|
<div className="field is-grouped">
|
||||||
|
<p className="control">
|
||||||
|
<button className="button" onClick={() => setCreateModal(true)}>
|
||||||
|
{t('twitch.timers.new-timer')}
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="control">
|
||||||
|
<input
|
||||||
|
className="input"
|
||||||
|
type="text"
|
||||||
|
placeholder={t('twitch.timers.search')}
|
||||||
|
value={timerFilter}
|
||||||
|
onChange={(ev) => setTimerFilter(ev.target.value)}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TimerModal
|
||||||
|
title={t('twitch.timers.new-timer')}
|
||||||
|
confirmText={t('actions.create')}
|
||||||
|
active={createModal}
|
||||||
|
onConfirm={createTimer}
|
||||||
|
onClose={() => setCreateModal(false)}
|
||||||
|
/>
|
||||||
|
{showModifyTimer ? (
|
||||||
|
<TimerModal
|
||||||
|
title={t('twitch.timers.modify-timer')}
|
||||||
|
confirmText={t('actions.edit')}
|
||||||
|
active={true}
|
||||||
|
onConfirm={(newName, cmdData) =>
|
||||||
|
modifyTimer(showModifyTimer, newName, cmdData)
|
||||||
|
}
|
||||||
|
initialName={showModifyTimer}
|
||||||
|
initialData={showModifyTimer ? timers[showModifyTimer] : null}
|
||||||
|
onClose={() => setShowModifyTimer(null)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<div className="reward-list" style={{ marginTop: '1rem' }}>
|
||||||
|
{Object.keys(timers ?? {})
|
||||||
|
?.filter((cmd) => cmd.toLowerCase().includes(timerFilterLC))
|
||||||
|
.map((timer) => (
|
||||||
|
<TimerItem
|
||||||
|
key={timer}
|
||||||
|
item={timer[timer]}
|
||||||
|
onDelete={() => deleteTimer(timer)}
|
||||||
|
onEdit={() => setShowModifyTimer(timer)}
|
||||||
|
onToggleState={() => toggleTimer(timer)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -91,7 +91,11 @@ func (m *BotTimerModule) runTimers() {
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if now.Sub(lastTriggeredTime) < time.Duration(timer.MinimumDelay)*time.Second {
|
minDelay := timer.MinimumDelay
|
||||||
|
if minDelay < 5 {
|
||||||
|
minDelay = 5
|
||||||
|
}
|
||||||
|
if now.Sub(lastTriggeredTime) < time.Duration(minDelay)*time.Second {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Make sure chat activity is high enough
|
// Make sure chat activity is high enough
|
||||||
|
|
Loading…
Reference in a new issue