diff --git a/packages/storyteller/src/pages/tts_configs/TtsConfigsEditRulePage.tsx b/packages/storyteller/src/pages/tts_configs/TtsConfigsEditRulePage.tsx index 038ec1086e..fc7d7f3d39 100644 --- a/packages/storyteller/src/pages/tts_configs/TtsConfigsEditRulePage.tsx +++ b/packages/storyteller/src/pages/tts_configs/TtsConfigsEditRulePage.tsx @@ -14,6 +14,7 @@ import { EventResponseType } from './event_response_builder/EventResponseType'; import { EventResponseComponent } from './event_response_builder/EventResponseComponent'; import { BitsRuleType } from './event_match_predicate_builder/types/BitsRuleType'; import { EventMatchPredicateBuilderComponent } from './event_match_predicate_builder/EventMatchPredicateBuilderComponent'; +import { CheerState } from './event_match_predicate_builder/CheerState'; interface Props { sessionWrapper: SessionWrapper, @@ -37,36 +38,20 @@ function TtsConfigsEditRulePage(props: Props) { const [newEventMatchPredicate, setNewEventMatchPredicate] = useState({}); const [newEventResponse, setNewEventResponse] = useState({}); + const [cheerState, setCheerState] = useState({}); + // ========== ??? ========== - /// TODO GET RID OF THIS + // TODO: GET RID OF THIS const [eventResponseType, setEventResponseType] = useState(EventResponseType.TtsSingleVoice); - // ===== Predicate Field values (editing) ===== - - // Used in: - // BitsCheermoteNameExactMatch - // BitsCheermotePrefixSpendThreshold - const [cheerNameOrPrefix, setCheerNameOrPrefix] = useState(''); - - // Used in: - // BitsSpendThreshold - // BitsCheermotePrefixSpendThreshold - const [minimumBitsSpent, setMinimumBitsSpent] = useState(1); - - // Used in: - // ChannelPointsRewardNameExactMatch - const [rewardName, setRewardName] = useState(''); - - // ===== Response Field values (editing) ===== - - // Used in: - // TtsSingleVoice + // TODO: GET RID OF THIS const [ttsModelToken, setTtsModelToken] = useState(''); // ===== Field values (final) ===== + // TODO: GET RID OF non-"new" (see above) const [eventMatchPredicate, setEventMatchPredicate] = useState({}); const [eventResponse, setEventResponse] = useState({}); const [ruleIsDisabled, setRuleIsDisabled] = useState(false); @@ -92,15 +77,15 @@ function TtsConfigsEditRulePage(props: Props) { serverBitsRuleType = BitsRuleType.BitsCheermoteNameExactMatch; } else if (!!response.twitch_event_rule.event_match_predicate.bits_cheermote_prefix_spend_threshold) { serverBitsRuleType = BitsRuleType.BitsCheermotePrefixSpendThreshold; - setMinimumBitsSpent(response.twitch_event_rule.event_match_predicate.bits_cheermote_prefix_spend_threshold.minimum_bits_spent); + //setMinimumBitsSpent(response.twitch_event_rule.event_match_predicate.bits_cheermote_prefix_spend_threshold.minimum_bits_spent); } else if (!!response.twitch_event_rule.event_match_predicate.bits_spend_threshold) { serverBitsRuleType = BitsRuleType.BitsSpendThreshold; - setMinimumBitsSpent(response.twitch_event_rule.event_match_predicate.bits_spend_threshold.minimum_bits_spent); + //setMinimumBitsSpent(response.twitch_event_rule.event_match_predicate.bits_spend_threshold.minimum_bits_spent); } break; case TwitchEventCategory.ChannelPoints: // NB: Only one rule type if (!!response.twitch_event_rule.event_match_predicate.channel_points_reward_name_exact_match) { - setRewardName(response.twitch_event_rule.event_match_predicate.channel_points_reward_name_exact_match.reward_name); + //setRewardName(response.twitch_event_rule.event_match_predicate.channel_points_reward_name_exact_match.reward_name); } break; case TwitchEventCategory.ChatCommand: // TODO: Not yet supported @@ -187,7 +172,7 @@ function TtsConfigsEditRulePage(props: Props) { updated_at: twitchEventRule.updated_at, // Updated in UI - event_match_predicate: newEventMatchPredicate, + event_match_predicate: eventMatchPredicate, event_response: eventResponse, rule_is_disabled: ruleIsDisabled, }; diff --git a/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/CheerState.ts b/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/CheerState.ts index 501caaa60f..4c0b5d24b7 100644 --- a/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/CheerState.ts +++ b/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/CheerState.ts @@ -3,6 +3,7 @@ import { CHEER_LOOKUP_MAP, TwitchCheerPrefix } from "../../../twitch/Cheers"; import { CheerUtil } from "../../../twitch/CheerUtil"; import { BitsRuleType } from "./types/BitsRuleType"; +// TODO: This needs tests badly. export interface CheerStateOfficial { cheerPrefix?: TwitchCheerPrefix, @@ -51,17 +52,21 @@ export function convertExactMatchToCheerState(freeformText: string) : CheerState } // Convert "prefix + bits" into CheerState -export function parsePrefixAndBitsToCheerState(freeformText: string, bits?: number) : CheerState { - const trimmed = freeformText.trim(); +export function convertPrefixAndBitsToCheerState(freeformText?: string, bits?: number) : CheerState { + const trimmed = !!freeformText ? freeformText.trim() : ''; let { cheerPrefix, bitValue } = CheerUtil.parseCheerString(trimmed); let maybeCheer = CHEER_LOOKUP_MAP.get(cheerPrefix || ''); + // NB: Prefer function args over parsed value since we might be parsing + // custom cheers such as "Sus11" or "1984" (7tv.app) + let maybeBits = bits || bitValue; + if (maybeCheer !== undefined) { return { cheerPrefix: maybeCheer, - bits: bitValue, + bits: maybeBits, } } @@ -69,21 +74,20 @@ export function parsePrefixAndBitsToCheerState(freeformText: string, bits?: numb // eg. "1984" return { cheerFull: freeformText, - bits: bitValue, + bits: maybeBits, }; } - // Turn a cheer state into a predicate export function predicateToCheerState(predicate: EventMatchPredicate) : CheerState { if (!!predicate.bits_cheermote_name_exact_match) { return convertExactMatchToCheerState(predicate.bits_cheermote_name_exact_match.cheermote_name) } else if (!!predicate.bits_cheermote_prefix_spend_threshold) { - return { - cheerFull: predicate.bits_cheermote_prefix_spend_threshold.cheermote_prefix, - bits: predicate.bits_cheermote_prefix_spend_threshold.minimum_bits_spent, - } + return convertPrefixAndBitsToCheerState( + predicate.bits_cheermote_prefix_spend_threshold.cheermote_prefix, + predicate.bits_cheermote_prefix_spend_threshold.minimum_bits_spent, + ); } else if (!!predicate.bits_spend_threshold) { return { bits: predicate.bits_spend_threshold.minimum_bits_spent, @@ -126,8 +130,10 @@ export function cheerStateToPredicate( /* +Truth table-ish breakdown (incomplete). + "Corgo" is an official cheer -"Zombo" is a custom cheer +"Zombo", "Sus12", and "1984" are custom cheers ------------------------------------------------------------------------------------------------ diff --git a/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/EventMatchPredicateBuilderComponent.tsx b/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/EventMatchPredicateBuilderComponent.tsx index 694106f7e7..9790eccc9b 100644 --- a/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/EventMatchPredicateBuilderComponent.tsx +++ b/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/EventMatchPredicateBuilderComponent.tsx @@ -8,6 +8,8 @@ import { BitsCheermotePrefixSpendThresholdForm } from './subforms/BitsCheermoteP import { BitsSpendThresholdForm } from './subforms/BitsSpendThresholdForm'; import { ChannelPointsRewardNameExactMatchForm } from './subforms/ChannelPointsRewardNameExactMatchForm'; import { ChannelPointsRuleType } from './types/ChannelPointsRuleType'; +import { CheerUtil } from '../../../twitch/CheerUtil'; +import { CheerState, predicateToCheerState } from './CheerState'; // TODO: Don't duplicate const CHEER_REGEX = /^([A-Za-z]+)(\d+)?$/; @@ -33,15 +35,33 @@ function EventMatchPredicateBuilderComponent(props: EventMatchPredicateBuilderCo // ========== Cached Values for Editing ========== + + + // New, and remove everything else: + const [cheerState, setCheerState] = useState({}); + + + + + + + // Used in: // BitsCheermoteNameExactMatch // BitsCheermotePrefixSpendThreshold - const [cheerNameOrPrefix, setCheerNameOrPrefix] = useState(''); + const [cheerNameOrPrefix, setCheerNameOrPrefix] = useState(''); // TODO: DIE - // Used in: +// // Used in: +// // BitsSpendThreshold +// // BitsCheermotePrefixSpendThreshold +// const [minimumBitsSpent, setMinimumBitsSpent] = useState(1); // TODO: DIE + + // Shared state for : // BitsSpendThreshold + // BitsCheermoteNameExactMatch // BitsCheermotePrefixSpendThreshold - const [minimumBitsSpent, setMinimumBitsSpent] = useState(1); + const [bitsValue, setBitsValue] = useState(1); + const [cheerPrefix, setCheerPrefix] = useState(''); // Used in: // ChannelPointsRewardNameExactMatch @@ -55,18 +75,27 @@ function EventMatchPredicateBuilderComponent(props: EventMatchPredicateBuilderCo let newBitsRuleType = BitsRuleType.BitsCheermoteNameExactMatch; let newChannelPointsRuleType = ChannelPointsRuleType.ChannelPointsRewardNameExactMatch; + let cheerAndBitsV = undefined; + let bitsV = undefined; + let cheerPrefixV = undefined; + + let newCheerState = predicateToCheerState(props.eventMatchPredicate); + switch (props.twitchEventCategory) { case TwitchEventCategory.Bits: if (!!props.eventMatchPredicate.bits_cheermote_name_exact_match) { newBitsRuleType = BitsRuleType.BitsCheermoteNameExactMatch; - setCheerNameOrPrefix(props.eventMatchPredicate.bits_cheermote_name_exact_match.cheermote_name); + cheerAndBitsV = props.eventMatchPredicate.bits_cheermote_name_exact_match.cheermote_name; + } else if (!!props.eventMatchPredicate.bits_cheermote_prefix_spend_threshold) { newBitsRuleType = BitsRuleType.BitsCheermotePrefixSpendThreshold; - setMinimumBitsSpent(props.eventMatchPredicate.bits_cheermote_prefix_spend_threshold.minimum_bits_spent); - setCheerNameOrPrefix(props.eventMatchPredicate.bits_cheermote_prefix_spend_threshold.cheermote_prefix); + cheerPrefixV = props.eventMatchPredicate.bits_cheermote_prefix_spend_threshold.cheermote_prefix; + bitsV = props.eventMatchPredicate.bits_cheermote_prefix_spend_threshold.minimum_bits_spent; + } else if (!!props.eventMatchPredicate.bits_spend_threshold) { newBitsRuleType = BitsRuleType.BitsSpendThreshold; - setMinimumBitsSpent(props.eventMatchPredicate.bits_spend_threshold.minimum_bits_spent); + bitsV = props.eventMatchPredicate.bits_spend_threshold.minimum_bits_spent; + } break; case TwitchEventCategory.ChannelPoints: // NB: Only one rule type @@ -80,15 +109,59 @@ function EventMatchPredicateBuilderComponent(props: EventMatchPredicateBuilderCo break; } + setCheerState(newCheerState); + + console.table(props.eventMatchPredicate) + console.log('cheerState', newCheerState); + + if (!!cheerAndBitsV) { + let { cheerPrefix, bitValue } = CheerUtil.parseCheerString(cheerAndBitsV); + setCheerPrefix(cheerPrefix || ''); + setBitsValue(bitValue || 1); + setCheerNameOrPrefix(cheerAndBitsV); // TODO: DIE + console.log('parsed', cheerPrefix, bitValue); + } + if (!!bitsV) { + setBitsValue(bitsV || 1); + } + if (!!cheerPrefixV) { + setCheerPrefix(cheerPrefixV || ''); + } + setBitsRuleType(newBitsRuleType); setChannelPointsRuleType(newChannelPointsRuleType); }, [props.twitchEventCategory, props.eventMatchPredicate]); + // TODO: We need to recalculate the model. const handleChangedBitsRuleType = (ev: React.FormEvent) : boolean => { const value = (ev.target as HTMLSelectElement).value; - const ruleType = value as BitsRuleType; - setBitsRuleType(ruleType); + const newRuleType = value as BitsRuleType; + + let predicate : EventMatchPredicate = {}; + + switch (newRuleType) { + case BitsRuleType.BitsCheermoteNameExactMatch: + predicate.bits_cheermote_name_exact_match = { + cheermote_name: cheerNameOrPrefix, + } + break; + case BitsRuleType.BitsCheermotePrefixSpendThreshold: + predicate.bits_cheermote_prefix_spend_threshold = { + cheermote_prefix: cheerNameOrPrefix, + minimum_bits_spent: bitsValue, + } + break; + case BitsRuleType.BitsSpendThreshold: + predicate.bits_spend_threshold = { + minimum_bits_spent: bitsValue, + } + break; + } + + props.updateEventMatchPredicate(predicate); + setBitsRuleType(newRuleType); + return true; } @@ -120,24 +193,52 @@ function EventMatchPredicateBuilderComponent(props: EventMatchPredicateBuilderCo // break; // } - const handleChangedCheerNameOrPrefix = (nameOrPrefix: string) => { + //const handleChangedCheerNameOrPrefix = (prefix: string) => { + const handleChangedCheerPrefix = (prefix: string) => { + let predicate : EventMatchPredicate = {}; + + switch (bitsRuleType) { + case BitsRuleType.BitsCheermoteNameExactMatch: + let joined = CheerUtil.joinCheerAndPrefix(prefix, bitsValue); + predicate.bits_cheermote_name_exact_match = { + cheermote_name: joined, // New value + } + break; + case BitsRuleType.BitsCheermotePrefixSpendThreshold: + predicate.bits_cheermote_prefix_spend_threshold = { + cheermote_prefix: prefix, // New value + minimum_bits_spent: bitsValue, + } + break; + case BitsRuleType.BitsSpendThreshold: + predicate.bits_spend_threshold = { + minimum_bits_spent: bitsValue, + } + break; + } + + props.updateEventMatchPredicate(predicate); + } + + const handleChangedCheerName = (name: string) => { let predicate : EventMatchPredicate = {}; switch (bitsRuleType) { case BitsRuleType.BitsCheermoteNameExactMatch: predicate.bits_cheermote_name_exact_match = { - cheermote_name: nameOrPrefix, // New value + cheermote_name: name, // New value } break; case BitsRuleType.BitsCheermotePrefixSpendThreshold: + let { cheerPrefix, bitValue } = CheerUtil.parseCheerString(name); predicate.bits_cheermote_prefix_spend_threshold = { - cheermote_prefix: nameOrPrefix, // New value - minimum_bits_spent: minimumBitsSpent, + cheermote_prefix: cheerPrefix || name, // New value + minimum_bits_spent: bitValue || bitsValue, } break; case BitsRuleType.BitsSpendThreshold: predicate.bits_spend_threshold = { - minimum_bits_spent: minimumBitsSpent, + minimum_bits_spent: bitsValue, } break; } @@ -150,13 +251,14 @@ function EventMatchPredicateBuilderComponent(props: EventMatchPredicateBuilderCo switch (bitsRuleType) { case BitsRuleType.BitsCheermoteNameExactMatch: + let joined = CheerUtil.joinCheerAndPrefix(cheerPrefix, minimumSpent); predicate.bits_cheermote_name_exact_match = { - cheermote_name: cheerNameOrPrefix, // Untouched existing value + cheermote_name: joined, // Combined value } break; case BitsRuleType.BitsCheermotePrefixSpendThreshold: predicate.bits_cheermote_prefix_spend_threshold = { - cheermote_prefix: cheerNameOrPrefix, // Untouched existing value + cheermote_prefix: cheerPrefix, // Untouched existing value minimum_bits_spent: minimumSpent, // New value } break; @@ -210,23 +312,22 @@ function EventMatchPredicateBuilderComponent(props: EventMatchPredicateBuilderCo switch (bitsRuleType) { case BitsRuleType.BitsCheermoteNameExactMatch: matchingRulesForm = ; break; case BitsRuleType.BitsCheermotePrefixSpendThreshold: matchingRulesForm = break; case BitsRuleType.BitsSpendThreshold: matchingRulesForm = ; break; diff --git a/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermoteNameExactMatchForm.tsx b/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermoteNameExactMatchForm.tsx index 07849fda8c..671e71e208 100644 --- a/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermoteNameExactMatchForm.tsx +++ b/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermoteNameExactMatchForm.tsx @@ -1,63 +1,65 @@ import React, { useEffect, useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faGem } from '@fortawesome/free-solid-svg-icons'; -import { CHEER_BIT_LEVELS, CHEER_PREFIXES } from '../../../../twitch/Cheers'; +import { CHEER_BIT_LEVELS, CHEER_PREFIXES, CHEER_PREFIX_TO_STRING_MAP } from '../../../../twitch/Cheers'; +import { CheerState, CheerStateIsCustom, CheerStateIsOfficial } from '../CheerState'; // TODO: Don't duplicate const CHEER_REGEX = /^([A-Za-z]+)(\d+)?$/; interface BitsCheermoteNameExactMatchProps { - cheerName: string, - updateCheerNameOrPrefix: (cheerNameOrPrefix: string) => void, + cheerState: CheerState, + + updateCheerName: (cheerNameOrPrefix: string) => void, + updateCheerPrefix: (cheerNameOrPrefix: string) => void, // NB: 'minimumBitsSpent' and its update are technically not a field here, but we can parse it out // to communicate back upstream in case the field changes, or infer it from a context switch to this // field. Basically, we're trying not to lose information. - minimumBitsSpent: number, updateMinimumBitsSpent: (minimumSpent: number) => void, }; function BitsCheermoteNameExactMatchForm(props: BitsCheermoteNameExactMatchProps) { // The two dropdowns - const [cheerPrefix, setCheerPrefix] = useState(props.cheerName); - const [bitsValue, setBitsValue] = useState(props.minimumBitsSpent); + const [cheerPrefix, setCheerPrefix] = useState(''); + const [bitsValue, setBitsValue] = useState(1); // The freeform text input - const [manualCheerValue, setManualCheerValue] = useState(props.cheerName); + const [manualCheerValue, setManualCheerValue] = useState(''); // NB: useState is not always setting from props correctly (after several re-renders) // The following answers suggests using useEffect: // https://stackoverflow.com/a/54866051 (less clear by also using useState(), but good comments) // https://stackoverflow.com/a/62982753 useEffect(() => { - let newBits = props.minimumBitsSpent; - let newManualCheerValue = props.cheerName; - let joinBitsAndCheer = true; + let newPrefix = ''; + let newBits = 1; + let newFullName = ''; - // Cheer name is the full value, eg. 'Corgo100' - const matches = props.cheerName.trim().match(CHEER_REGEX) + if (CheerStateIsOfficial(props.cheerState)) { + if (!!props.cheerState.cheerPrefix) { + newPrefix = CHEER_PREFIX_TO_STRING_MAP.get((props.cheerState.cheerPrefix)) || ''; + } + + if (!!props.cheerState.bits && !isNaN(props.cheerState.bits) && props.cheerState.bits > 0) { + newBits = props.cheerState.bits; + newFullName = `${newPrefix}${newBits}`; + } else { + newFullName = newPrefix; + } - if (!!matches && matches.length > 1) { - setCheerPrefix(matches[1]); // First match group - if (matches.length == 3 && matches[2] !== undefined) { - // If we detect a number, let's not conjoin bits and cheer. - joinBitsAndCheer = false; - // NB(1): We're getting this value from a fullly named cheer, eg 'Corgo5000'. - // NB(2): The second match group can be 'undefined' if no number is present. (Optional matching, I guess.) - let maybeBits = parseInt(matches[2]); - if (!isNaN(maybeBits)) { - newBits = maybeBits; - } + } else if (CheerStateIsCustom(props.cheerState)) { + newFullName = props.cheerState.cheerFull || ''; + + if (!!props.cheerState.bits && !isNaN(props.cheerState.bits) && props.cheerState.bits > 0) { + newBits = props.cheerState.bits; } } - // NB: This is for arriving from "CheermotePrefixSpendThreshold", where the values are disjoint. - if (joinBitsAndCheer) { - newManualCheerValue = `${cheerPrefix}${newBits}`; - } - - setManualCheerValue(newManualCheerValue); // The freeform text field + setCheerPrefix(newPrefix); setBitsValue(newBits); - }, [props.cheerName, props.minimumBitsSpent]); + setManualCheerValue(newFullName); + + }, [props.cheerState]); const updateCheerPrefix = (ev: React.FormEvent) : boolean => { const value = (ev.target as HTMLSelectElement).value; @@ -96,7 +98,7 @@ function BitsCheermoteNameExactMatchForm(props: BitsCheermoteNameExactMatchProps } setManualCheerValue(value); - props.updateCheerNameOrPrefix(value); + props.updateCheerName(value); return true; } @@ -107,7 +109,7 @@ function BitsCheermoteNameExactMatchForm(props: BitsCheermoteNameExactMatchProps } const cheerValue = `${prefix}${bits}`; setManualCheerValue(cheerValue); - props.updateCheerNameOrPrefix(cheerValue); + props.updateCheerName(cheerValue); props.updateMinimumBitsSpent(bits); // NB: Technically not in this form, but helps when switching } diff --git a/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermotePrefixSpendThresholdForm.tsx b/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermotePrefixSpendThresholdForm.tsx index e634fe498c..ea9f8174b9 100644 --- a/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermotePrefixSpendThresholdForm.tsx +++ b/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermotePrefixSpendThresholdForm.tsx @@ -1,24 +1,25 @@ import React, { useEffect, useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faGem } from '@fortawesome/free-solid-svg-icons'; -import { CHEER_BIT_LEVELS, CHEER_PREFIXES } from '../../../../twitch/Cheers'; +import { CHEER_BIT_LEVELS, CHEER_PREFIXES, CHEER_PREFIX_TO_STRING_MAP } from '../../../../twitch/Cheers'; +import { CheerState, CheerStateIsCustom, CheerStateIsOfficial } from '../CheerState'; // TODO: Don't duplicate const CHEER_REGEX = /^([A-Za-z]+)(\d+)?$/; interface BitsCheermotePrefixSpendThresholdProps { - cheerPrefix: string, - updateCheerNameOrPrefix: (cheerNameOrPrefix: string) => void, - minimumBitsSpent: number, + cheerState: CheerState, + + updateCheerPrefix: (cheerNameOrPrefix: string) => void, updateMinimumBitsSpent: (minimumSpent: number) => void, }; function BitsCheermotePrefixSpendThresholdForm(props: BitsCheermotePrefixSpendThresholdProps) { - const [cheerPrefix, setCheerPrefix] = useState(props.cheerPrefix); - const [customCheerPrefix, setCustomCheerPrefix] = useState(props.cheerPrefix); + const [cheerPrefix, setCheerPrefix] = useState(''); + const [customCheerPrefix, setCustomCheerPrefix] = useState(''); - const [bitsValue, setBitsValue] = useState(props.minimumBitsSpent); - const [customBitsValue, setCustomBitsValue] = useState(props.minimumBitsSpent); + const [bitsValue, setBitsValue] = useState(1); + const [customBitsValue, setCustomBitsValue] = useState(1); // This handles initialization after mount. // NB: useState is not always setting from props correctly (after several re-renders) @@ -26,33 +27,37 @@ function BitsCheermotePrefixSpendThresholdForm(props: BitsCheermotePrefixSpendTh // https://stackoverflow.com/a/54866051 (less clear by also using useState(), but good comments) // https://stackoverflow.com/a/62982753 useEffect(() => { - let newBitsValue = 1; + let newPrefix = ''; + let newCustomPrefix = ''; + let newBits = 1; + let newCustomBits = 1; - // Cheer prefix could be just a prefix (eg. 'Corgo') OR the full name (eg. 'Corgo100') - // This depends on what the previous view was. - const matches = props.cheerPrefix.trim().match(CHEER_REGEX) + if (CheerStateIsOfficial(props.cheerState)) { + if (!!props.cheerState.cheerPrefix) { + newPrefix = CHEER_PREFIX_TO_STRING_MAP.get((props.cheerState.cheerPrefix)) || ''; + newCustomPrefix = CHEER_PREFIX_TO_STRING_MAP.get((props.cheerState.cheerPrefix)) || ''; + } + + if (!!props.cheerState.bits && !isNaN(props.cheerState.bits) && props.cheerState.bits > 0) { + newBits = props.cheerState.bits; + newCustomBits = props.cheerState.bits; + } - if (!!matches && matches.length > 1) { - // First match group - setCheerPrefix(matches[1]); - setCustomCheerPrefix(matches[1]); - if (matches.length == 3 && matches[2] !== undefined) { - // NB: Second match group can be 'undefined' if no number is present. (Zero-width matching?) - let maybeValid = parseInt(matches[2]); - if (!isNaN(maybeValid)) { - newBitsValue = maybeValid; - } + } else if (CheerStateIsCustom(props.cheerState)) { + newCustomPrefix = props.cheerState.cheerFull || ''; + + if (!!props.cheerState.bits && !isNaN(props.cheerState.bits) && props.cheerState.bits > 0) { + newBits = props.cheerState.bits; + newCustomBits = props.cheerState.bits; } } - if (!!props.minimumBitsSpent && !isNaN(props.minimumBitsSpent) && props.minimumBitsSpent > 0) { - newBitsValue = props.minimumBitsSpent; - } + setCheerPrefix(newPrefix); + setCustomCheerPrefix(newCustomPrefix); + setBitsValue(newBits); + setCustomBitsValue(newCustomBits); - setBitsValue(newBitsValue); - setCustomBitsValue(newBitsValue); - - }, [props.cheerPrefix, props.minimumBitsSpent]); + }, [props.cheerState]); const updateCheerPrefix = (ev: React.FormEvent) : boolean => { const value = (ev.target as HTMLSelectElement).value; @@ -64,7 +69,7 @@ function BitsCheermotePrefixSpendThresholdForm(props: BitsCheermotePrefixSpendTh const updateTextCheerValue = (ev: React.FormEvent) : boolean => { const value = (ev.target as HTMLInputElement).value; setCustomCheerPrefix(value); - props.updateCheerNameOrPrefix(value); + props.updateCheerPrefix(value); return true; } diff --git a/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsSpendThresholdForm.tsx b/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsSpendThresholdForm.tsx index fb6ad7b29b..0474a4754b 100644 --- a/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsSpendThresholdForm.tsx +++ b/packages/storyteller/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsSpendThresholdForm.tsx @@ -2,27 +2,28 @@ import React, { useEffect, useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faGem } from '@fortawesome/free-solid-svg-icons'; import { CHEER_BIT_LEVELS } from '../../../../twitch/Cheers'; +import { CheerState } from '../CheerState'; interface BitsSpendThresholdFormProps { - minimumBitsSpent: number, + cheerState: CheerState, updateMinimumBitsSpent: (minimumSpent: number) => void, }; function BitsSpendThresholdForm(props: BitsSpendThresholdFormProps) { // The dropdown - const [bitsValue, setBitsValue] = useState(props.minimumBitsSpent); + const [bitsValue, setBitsValue] = useState(1); // The freeform text input - const [customAmount, setCustomAmount] = useState(props.minimumBitsSpent); + const [customAmount, setCustomAmount] = useState(1); // NB: useState is not always setting from props correctly (after several re-renders) // The following answers suggests using useEffect: // https://stackoverflow.com/a/54866051 (less clear by also using useState(), but good comments) // https://stackoverflow.com/a/62982753 useEffect(() => { - if (!isNaN(props.minimumBitsSpent)) { - setBitsValue(props.minimumBitsSpent) - setCustomAmount(props.minimumBitsSpent) + if (!!props.cheerState.bits && !isNaN(props.cheerState.bits) && props.cheerState.bits > 0) { + setBitsValue(props.cheerState.bits); + setCustomAmount(props.cheerState.bits); } - }, [props.minimumBitsSpent]); + }, [props.cheerState]); const handleBitSelect = (ev: React.FormEvent) : boolean => { const value = (ev.target as HTMLSelectElement).value; diff --git a/packages/storyteller/src/twitch/Cheers.ts b/packages/storyteller/src/twitch/Cheers.ts index 833a01efd6..ea0901fe74 100644 --- a/packages/storyteller/src/twitch/Cheers.ts +++ b/packages/storyteller/src/twitch/Cheers.ts @@ -1,5 +1,39 @@ +export enum TwitchCheerPrefix { + Cheer = "Cheer", + DoodleCheer = "DoodleCheer", + BibleThump = "BibleThump", + Cheerwhal = "cheerwhal", + Corgo = "Corgo", + Uni = "uni", + ShowLove = "ShowLove", + Party = "Party", + SeemsGood = "SeemsGood", + Pride = "Pride", + Kappa = "Kappa", + FrankerZ = "FrankerZ", + HeyGuys = "HeyGuys", + DansGame = "DansGame", + EleGiggle = "EleGiggle", + TriHard = "TriHard", + Kreygasm = "Kreygasm", + FourHead = "4Head", + SwiftRage = "SwiftRage", + NotLikeThis = "NotLikeThis", + FailFish = "FailFish", + VoHiYo = "VoHiYo", + PjSalt = "PJSalt", + MrDestructoid = "MrDestructoid", + BDay = "bday", + RipChear = "RIPCheer", + Shamrock = "Shamrock", + BitBoss = "BitBoss", + Streamlabs = "Streamlabs", + Muxy = "Muxy", + HolidayCheer = "HolidayCheer", +} +// TODO: Immutability /// This list seems pretty comprehensive /// https://github.com/nossebro/TwitchPubSubMirror/blob/master/TwitchPubSubMirror_StreamlabsSystem.py export const CHEER_PREFIXES : string[] = [ @@ -36,5 +70,46 @@ export const CHEER_PREFIXES : string[] = [ "HolidayCheer" ]; +// TODO: Immutability +export const CHEER_LOOKUP_MAP : Map = new Map([ + ["Cheer", TwitchCheerPrefix.Cheer], + ["DoodleCheer", TwitchCheerPrefix.DoodleCheer], + ["BibleThump", TwitchCheerPrefix.BibleThump], + ["cheerwhal", TwitchCheerPrefix.Cheerwhal], + ["Corgo", TwitchCheerPrefix.Corgo], + ["uni", TwitchCheerPrefix.Uni], + ["ShowLove", TwitchCheerPrefix.ShowLove], + ["Party", TwitchCheerPrefix.Party], + ["SeemsGood", TwitchCheerPrefix.SeemsGood], + ["Pride", TwitchCheerPrefix.Pride], + ["Kappa", TwitchCheerPrefix.Kappa], + ["FrankerZ", TwitchCheerPrefix.FrankerZ], + ["HeyGuys", TwitchCheerPrefix.HeyGuys], + ["DansGame", TwitchCheerPrefix.DansGame], + ["EleGiggle", TwitchCheerPrefix.EleGiggle], + ["TriHard", TwitchCheerPrefix.TriHard], + ["Kreygasm", TwitchCheerPrefix.Kreygasm], + ["4Head", TwitchCheerPrefix.FourHead], + ["SwiftRage", TwitchCheerPrefix.SwiftRage], + ["NotLikeThis", TwitchCheerPrefix.NotLikeThis], + ["FailFish", TwitchCheerPrefix.NotLikeThis], + ["VoHiYo", TwitchCheerPrefix.VoHiYo], + ["PJSalt", TwitchCheerPrefix.PjSalt], + ["MrDestructoid", TwitchCheerPrefix.MrDestructoid], + ["bday", TwitchCheerPrefix.BDay], + ["RIPCheer", TwitchCheerPrefix.BDay], + ["Shamrock", TwitchCheerPrefix.Shamrock], + ["BitBoss", TwitchCheerPrefix.BitBoss], + ["Streamlabs", TwitchCheerPrefix.Streamlabs], + ["Muxy", TwitchCheerPrefix.Muxy], + ["HolidayCheer", TwitchCheerPrefix.HolidayCheer], +]); + +// TODO: Immutability +export const CHEER_PREFIX_TO_STRING_MAP : Map = new Map( + Array.from(CHEER_LOOKUP_MAP, entry => [entry[1], entry[0]]) +) + +// TODO: Immutability /// Levels of cheering export const CHEER_BIT_LEVELS = [1, 100, 1000, 5000, 10000];