From 17e2d1e0bd1e709d867f51434f331bec874b7c34 Mon Sep 17 00:00:00 2001 From: MattIPv4 Date: Mon, 23 Jan 2023 21:55:33 +0000 Subject: [PATCH 1/6] Add editorconfig file --- .editorconfig | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..55e81c6f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +[*] +indent_size = 2 +indent_style = space +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +indent_size = unset From 1f356f4ec9ee8153d4c72c083fb3b5e1f4fef869 Mon Sep 17 00:00:00 2001 From: MattIPv4 Date: Mon, 23 Jan 2023 21:55:54 +0000 Subject: [PATCH 2/6] Add lint tool for editorconfig --- package-lock.json | 21 +++++++++++++++++++++ package.json | 3 +++ 2 files changed, 24 insertions(+) diff --git a/package-lock.json b/package-lock.json index d69871f0..38e65546 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "devDependencies": { "@types/cors": "^2.8.12", "@types/tmi.js": "^1.8.2", + "editorconfig-checker": "^5.0.1", "react-app-rewire-multiple-entry": "^2.2.2", "react-app-rewired": "^2.2.1" } @@ -6455,6 +6456,20 @@ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" }, + "node_modules/editorconfig-checker": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/editorconfig-checker/-/editorconfig-checker-5.0.1.tgz", + "integrity": "sha512-6hXq9VVDkyCxVYKdGtIj+yhVR1fi/6W6Ykz/+kItLPARulJvr2/VXgWZ5OGWx1UYm2RD6XOzWyx1JF6DLgQ/8Q==", + "dev": true, + "bin": { + "ec": "dist/index.js", + "editorconfig-checker": "dist/index.js" + }, + "funding": { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/mstruebing" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -21821,6 +21836,12 @@ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" }, + "editorconfig-checker": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/editorconfig-checker/-/editorconfig-checker-5.0.1.tgz", + "integrity": "sha512-6hXq9VVDkyCxVYKdGtIj+yhVR1fi/6W6Ykz/+kItLPARulJvr2/VXgWZ5OGWx1UYm2RD6XOzWyx1JF6DLgQ/8Q==", + "dev": true + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", diff --git a/package.json b/package.json index 04cc6c17..335aae4f 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,8 @@ "watch": "sass --no-source-map --watch src:src", "build": "react-app-rewired build", "test": "react-app-rewired test", + "lint": "npm run lint:editorconfig", + "lint:editorconfig": "editorconfig-checker", "eject": "react-scripts eject" }, "eslintConfig": { @@ -60,6 +62,7 @@ "devDependencies": { "@types/cors": "^2.8.12", "@types/tmi.js": "^1.8.2", + "editorconfig-checker": "^5.0.1", "react-app-rewire-multiple-entry": "^2.2.2", "react-app-rewired": "^2.2.1" } From 0d0859f455e5eeadb1a796af6b6db016e464bdb7 Mon Sep 17 00:00:00 2001 From: MattIPv4 Date: Mon, 23 Jan 2023 21:56:12 +0000 Subject: [PATCH 3/6] Fix existing editorconfig violations --- .idea/scopes/Extension.xml | 3 +- .vscode/settings.json | 38 ++-- Alveus Ambassadors.json | 2 +- src/index.scss | 10 +- src/pages/overlay/App.tsx | 166 ++++++++------- .../activationButtons/ActivationButtons.tsx | 16 +- .../activationButtons.module.scss | 37 ++-- .../ambassadorList/AmbassadorList.tsx | 148 ++++++------- .../ambassadorList/ambassadorList.module.scss | 195 ++++++++++-------- .../disableChatPopup/DisableChatPopup.tsx | 25 +-- .../disableChatPopup.module.scss | 120 +++++------ .../overlay/components/overlay/Overlay.tsx | 93 ++++----- .../components/overlay/overlay.module.scss | 58 +++--- .../overlaySettings/OverlaySettings.tsx | 35 ++-- .../overlaySettings.module.scss | 24 +-- src/pages/overlay/index.scss | 12 +- src/pages/overlay/index.tsx | 2 +- src/pages/panel/components/App.scss | 10 +- src/pages/panel/components/App.tsx | 2 +- .../AmbassadorCardOverlay.tsx | 3 +- .../ambassadorCardOverlay.module.scss | 84 ++++---- .../ambassadorPanel/AmbassadorPanel.tsx | 13 +- .../ambassadorPanel.module.scss | 15 +- src/pages/panel/components/nav/Nav.tsx | 6 +- .../panel/components/nav/nav.module.scss | 33 ++- src/utils/chatCommand.ts | 106 +++++----- .../compositions/ambassador/Ambassador.tsx | 3 +- .../ambassador/ambassador.module.scss | 26 ++- src/utils/dateManager.ts | 166 +++++++-------- .../ambassadorButton/AmbassadorButton.tsx | 6 +- .../ambassadorButton.module.scss | 50 ++--- .../global/ambassadorCard/AmbassadorCard.tsx | 90 ++++---- .../ambassadorCard/ambassadorCard.module.scss | 128 ++++++------ .../global/loadingSpinner/LoadingSpinner.tsx | 2 +- .../loadingSpinner/loadingSpinner.module.scss | 5 +- src/variables.scss | 2 +- 36 files changed, 880 insertions(+), 854 deletions(-) diff --git a/.idea/scopes/Extension.xml b/.idea/scopes/Extension.xml index 59240e43..d2f1b1bc 100644 --- a/.idea/scopes/Extension.xml +++ b/.idea/scopes/Extension.xml @@ -1,3 +1,4 @@ - \ No newline at end of file + + diff --git a/.vscode/settings.json b/.vscode/settings.json index 156354f4..2615de23 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,21 +1,21 @@ { - "files.exclude": { - "**/.git": true, - "**/.svn": true, - "**/.hg": true, - "**/CVS": true, - "**/.DS_Store": true, - "**/Thumbs.db": true, - "**/.classpath": true, - "**/.project": true, - "**/.settings": true, - "**/.factorypath": true, - "**/*.css": true - }, - "hide-files.files": [], - "workbench.colorCustomizations": { - "activityBar.background": "#3A254A", - "titleBar.activeBackground": "#513368", - "titleBar.activeForeground": "#FCFBFD" - } + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/Thumbs.db": true, + "**/.classpath": true, + "**/.project": true, + "**/.settings": true, + "**/.factorypath": true, + "**/*.css": true + }, + "hide-files.files": [], + "workbench.colorCustomizations": { + "activityBar.background": "#3A254A", + "titleBar.activeBackground": "#513368", + "titleBar.activeForeground": "#FCFBFD" + } } diff --git a/Alveus Ambassadors.json b/Alveus Ambassadors.json index 589c88ff..3c287fbd 100644 --- a/Alveus Ambassadors.json +++ b/Alveus Ambassadors.json @@ -84,4 +84,4 @@ "name": "Alveus Animals", "usingRandomFrontendHostingPort": false, "version": 3 -} \ No newline at end of file +} diff --git a/src/index.scss b/src/index.scss index 5f653be2..d76007c3 100644 --- a/src/index.scss +++ b/src/index.scss @@ -1,15 +1,15 @@ //Global styles @import './variables.scss'; -*{ - margin: 0; - padding: 0; +* { + margin: 0; + padding: 0; } + body { font-family: $font-family; color: $primary-text; - display: flex; justify-content: center; overflow-x: hidden; // no horizontal scrollbar -} \ No newline at end of file +} diff --git a/src/pages/overlay/App.tsx b/src/pages/overlay/App.tsx index a4fe519d..87035be1 100644 --- a/src/pages/overlay/App.tsx +++ b/src/pages/overlay/App.tsx @@ -9,101 +9,97 @@ import OverlaySettings from "./components/overlaySettings/OverlaySettings" import styles from "./App.module.css" interface Settings { - disableChatPopup: boolean + disableChatPopup: boolean } -export default function App(){ - const [overlaySettings, setOverlaySettings] = useState(() => { - // Load settings from local storage, merging with defaults - const settings = JSON.parse(localStorage.getItem("settings") || "{}") - return { - disableChatPopup: false, - ...settings - } - }) +export default function App() { + const [overlaySettings, setOverlaySettings] = useState(() => { + // Load settings from local storage, merging with defaults + const settings = JSON.parse(localStorage.getItem("settings") || "{}") + return { + disableChatPopup: false, + ...settings + } + }) - useEffect(() => { - // save settings to local storage - localStorage.setItem("settings", JSON.stringify(overlaySettings)) - }, [overlaySettings]) + useEffect(() => { + // save settings to local storage + localStorage.setItem("settings", JSON.stringify(overlaySettings)) + }, [overlaySettings]) - const toggleDisableChatPopup = useCallback(() => { - setOverlaySettings((current: Settings) => ({ - ...current, - disableChatPopup: !current.disableChatPopup - })) - }, []) + const toggleDisableChatPopup = useCallback(() => { + setOverlaySettings((current: Settings) => ({ + ...current, + disableChatPopup: !current.disableChatPopup + })) + }, []) - // Show/hide the overlay based on mouse movement - const appRef = useRef(null) - const sleepTimer = useRef(undefined) - const [sleeping, setSleeping] = useState(false) + // Show/hide the overlay based on mouse movement + const appRef = useRef(null) + const sleepTimer = useRef(undefined) + const [sleeping, setSleeping] = useState(false) - // Allow children to know when we have been woken up - const [awoken, setAwoken] = useState<(() => void)[]>([]) - const addAwoken = useCallback((callback: () => void) => { - setAwoken(current => [...current, callback]) - }, []) - const removeAwoken = useCallback((callback: () => void) => { - setAwoken(current => current.filter(c => c !== callback)) - }, []) - const awokenObj = useMemo(() => ({ add: addAwoken, remove: removeAwoken }), [addAwoken, removeAwoken]) + // Allow children to know when we have been woken up + const [awoken, setAwoken] = useState<(() => void)[]>([]) + const addAwoken = useCallback((callback: () => void) => { + setAwoken(current => [...current, callback]) + }, []) + const removeAwoken = useCallback((callback: () => void) => { + setAwoken(current => current.filter(c => c !== callback)) + }, []) + const awokenObj = useMemo(() => ({ add: addAwoken, remove: removeAwoken }), [addAwoken, removeAwoken]) - // Wake the overlay for x milliseconds - const wake = useCallback((time: number) => { - setSleeping(false) - awoken.forEach(fn => fn()) - if (sleepTimer.current) clearTimeout(sleepTimer.current) - sleepTimer.current = setTimeout(() => { - setSleeping(true) - }, time) - }, [awoken]) + // Wake the overlay for x milliseconds + const wake = useCallback((time: number) => { + setSleeping(false) + awoken.forEach(fn => fn()) + if (sleepTimer.current) clearTimeout(sleepTimer.current) + sleepTimer.current = setTimeout(() => { + setSleeping(true) + }, time) + }, [awoken]) - // When the user interacts, have a 5s timeout before hiding the overlay - const interacted = useCallback(() => { - wake(5000) - }, [wake]) + // When the user interacts, have a 5s timeout before hiding the overlay + const interacted = useCallback(() => { + wake(5000) + }, [wake]) - // Bind a capturing event listener for scrolling (so we can see scrolling for children) - useEffect(() => { - appRef.current?.addEventListener("scroll", interacted, true) - return () => appRef.current?.removeEventListener("scroll", interacted, true) - }, [interacted]) + // Bind a capturing event listener for scrolling (so we can see scrolling for children) + useEffect(() => { + appRef.current?.addEventListener("scroll", interacted, true) + return () => appRef.current?.removeEventListener("scroll", interacted, true) + }, [interacted]) - // Immediately sleep the overlay - const sleep = useCallback(() => { - setSleeping(true) - if (sleepTimer.current) clearTimeout(sleepTimer.current) - }, []) + // Immediately sleep the overlay + const sleep = useCallback(() => { + setSleeping(true) + if (sleepTimer.current) clearTimeout(sleepTimer.current) + }, []) - // When we unmount, clear the sleep timer - useEffect(() => () => { - if (sleepTimer.current) clearTimeout(sleepTimer.current) - }, []) + // When we unmount, clear the sleep timer + useEffect(() => () => { + if (sleepTimer.current) clearTimeout(sleepTimer.current) + }, []) - return ( -
- - -
- ) + return ( +
+ + +
+ ) } diff --git a/src/pages/overlay/components/activationButtons/ActivationButtons.tsx b/src/pages/overlay/components/activationButtons/ActivationButtons.tsx index fed84506..4ade0da3 100644 --- a/src/pages/overlay/components/activationButtons/ActivationButtons.tsx +++ b/src/pages/overlay/components/activationButtons/ActivationButtons.tsx @@ -2,21 +2,21 @@ import AlveusLogo from '../../../../assets/alveus-logo.png'; import styles from './activationButtons.module.css' -/** +/** * @Description Activation buttons are buttons that are used to activate parts of the overlay. - */ + */ interface ActivationButtonProps { toggleShowAmbassadorList: () => void; } -export default function ActivationButtons(props: ActivationButtonProps) { +export default function ActivationButtons(props: ActivationButtonProps) { return (
- + - {/** Plan on adding another activation button to show non-ambassadors*/} + {/** Plan on adding another activation button to show non-ambassadors*/}
) -} \ No newline at end of file +} diff --git a/src/pages/overlay/components/activationButtons/activationButtons.module.scss b/src/pages/overlay/components/activationButtons/activationButtons.module.scss index 04fc149e..d89b3b10 100644 --- a/src/pages/overlay/components/activationButtons/activationButtons.module.scss +++ b/src/pages/overlay/components/activationButtons/activationButtons.module.scss @@ -1,23 +1,24 @@ @import '../../../../variables.scss'; -.activationButtons{ - margin-top: 40px; - button{ - all: unset; - border-radius: 5px; - background-color: $primary-color; - padding: 5px; +.activationButtons { + margin-top: 40px; - img{ - width: 55px; - height: 55px; - } + button { + all: unset; + border-radius: 5px; + background-color: $primary-color; + padding: 5px; - &:hover{ - outline: 1px solid white; - cursor: pointer; - filter: brightness(1.2); - transition: 0.3s; - } + img { + width: 55px; + height: 55px; } -} \ No newline at end of file + + &:hover { + outline: 1px solid white; + cursor: pointer; + filter: brightness(1.2); + transition: 0.3s; + } + } +} diff --git a/src/pages/overlay/components/ambassadorList/AmbassadorList.tsx b/src/pages/overlay/components/ambassadorList/AmbassadorList.tsx index 53ad9413..82931595 100644 --- a/src/pages/overlay/components/ambassadorList/AmbassadorList.tsx +++ b/src/pages/overlay/components/ambassadorList/AmbassadorList.tsx @@ -11,87 +11,87 @@ import AmbassadorButton from '../../../../utils/global/ambassadorButton/Ambassad import styles from './ambassadorList.module.css' import arrow from '../../../../assets/arrow.jpg' -export interface AmbassadorListProps{ - showAmbassadorList: boolean - chatChosenAmbassador?: string +export interface AmbassadorListProps { + showAmbassadorList: boolean + chatChosenAmbassador?: string } -export default function AmbassadorList(props: AmbassadorListProps){ - const [ambassadors] = useState(AmbassadorData) - const [activeAmbassador, setActiveAmbassador] = useState() - const upArrowRef = useRef(null) - const ambassadorList = useRef(null) - const downArrowRef = useRef(null) +export default function AmbassadorList(props: AmbassadorListProps) { + const [ambassadors] = useState(AmbassadorData) + const [activeAmbassador, setActiveAmbassador] = useState() - useEffect(() =>{ // show the card of the ambassador that Twitch chat chose - if(props.chatChosenAmbassador !== undefined){ - const ambassador = ambassadors.find(ambassador => ambassador.name.split(" ")[0].toLowerCase() === props.chatChosenAmbassador) - if(ambassador){ - setActiveAmbassador(ambassador) - scrollListToAmbassador(ambassador.name.split(" ")[0].toLowerCase()) - } - } - }, [props.chatChosenAmbassador]) + const upArrowRef = useRef(null) + const ambassadorList = useRef(null) + const downArrowRef = useRef(null) - const scrollListToAmbassador = (name: string) => { - if(!ambassadorList.current) - return - - const offset = 200 - const anchorElement = ambassadorList.current.querySelector(`#${name}`) - if(anchorElement instanceof HTMLDivElement) - ambassadorList.current.scrollTo({top: Math.max(0, anchorElement.offsetTop - offset), behavior: "smooth"}) - } - const ambassadorListScroll = (direction: number) => { - if(ambassadorList.current) - ambassadorList.current.scroll({top: ambassadorList.current.scrollTop - direction, left: 0, behavior: 'smooth'}) - } - const handleArrowVisibility = () => { - if(ambassadorList.current){ - if(ambassadorList.current.scrollTop === 0) - upArrowRef.current?.classList.add(styles.hideArrow) - else if(ambassadorList.current.scrollTop + ambassadorList.current.clientHeight === ambassadorList.current.scrollHeight) - downArrowRef.current?.classList.add(styles.hideArrow) - else{ - upArrowRef.current?.classList.remove(styles.hideArrow) - downArrowRef.current?.classList.remove(styles.hideArrow) - } - } + useEffect(() =>{ // show the card of the ambassador that Twitch chat chose + if (props.chatChosenAmbassador !== undefined) { + const ambassador = ambassadors.find(ambassador => ambassador.name.split(" ")[0].toLowerCase() === props.chatChosenAmbassador) + if (ambassador) { + setActiveAmbassador(ambassador) + scrollListToAmbassador(ambassador.name.split(" ")[0].toLowerCase()) + } } + }, [props.chatChosenAmbassador]) - return ( -
-
- ambassadorListScroll(250)} alt="Up Arrow"/> -
handleArrowVisibility()}> - {ambassadors && ambassadors.map(ambassador => ( - { + if (!ambassadorList.current) return - getCard={() => {setActiveAmbassador(activeAmbassador?.name === ambassador.name ? undefined : ambassador)}} + const offset = 200 + const anchorElement = ambassadorList.current.querySelector(`#${name}`) + if (anchorElement instanceof HTMLDivElement) + ambassadorList.current.scrollTo({top: Math.max(0, anchorElement.offsetTop - offset), behavior: "smooth"}) + } + const ambassadorListScroll = (direction: number) => { + if (ambassadorList.current) + ambassadorList.current.scroll({top: ambassadorList.current.scrollTop - direction, left: 0, behavior: 'smooth'}) + } + const handleArrowVisibility = () => { + if (ambassadorList.current) { + if (ambassadorList.current.scrollTop === 0) + upArrowRef.current?.classList.add(styles.hideArrow) + else if (ambassadorList.current.scrollTop + ambassadorList.current.clientHeight === ambassadorList.current.scrollHeight) + downArrowRef.current?.classList.add(styles.hideArrow) + else { + upArrowRef.current?.classList.remove(styles.hideArrow) + downArrowRef.current?.classList.remove(styles.hideArrow) + } + } + } - ClassName={`${styles.ambassadorButton} ${activeAmbassador?.name === ambassador.name ? styles.ambassadorButtonClicked : undefined}`} - Id={ambassador.name.split(" ")[0].toLowerCase()} - /> - ))} -
- ambassadorListScroll(-250)} alt="Down Arrow"/> -
+ return ( +
+
+ ambassadorListScroll(250)} alt="Up Arrow"/> +
handleArrowVisibility()}> + {ambassadors && ambassadors.map(ambassador => ( + {setActiveAmbassador(undefined)}} - ClassName={styles.ambassadorCard} - />: null - } + getCard={() => {setActiveAmbassador(activeAmbassador?.name === ambassador.name ? undefined : ambassador)}} + + ClassName={`${styles.ambassadorButton} ${activeAmbassador?.name === ambassador.name ? styles.ambassadorButtonClicked : undefined}`} + Id={ambassador.name.split(" ")[0].toLowerCase()} + /> + ))}
- ) -} \ No newline at end of file + ambassadorListScroll(-250)} alt="Down Arrow"/> +
+ + {activeAmbassador && props.showAmbassadorList ? ( + {setActiveAmbassador(undefined)}} + ClassName={styles.ambassadorCard} + /> + ) : null} +
+ ) +} diff --git a/src/pages/overlay/components/ambassadorList/ambassadorList.module.scss b/src/pages/overlay/components/ambassadorList/ambassadorList.module.scss index 835cfff1..a71d2cbd 100644 --- a/src/pages/overlay/components/ambassadorList/ambassadorList.module.scss +++ b/src/pages/overlay/components/ambassadorList/ambassadorList.module.scss @@ -1,104 +1,121 @@ -.ambassadorList{ +.ambassadorList { + display: flex; + + .scrollAmbassadors { display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + transition: 0.3s; + z-index: 1; - .scrollAmbassadors{ - display: flex; - flex-direction: column; - align-items: center; - gap: 10px; - transition: 0.3s; - z-index: 1; - - .ambassadors{ - overflow: scroll; - height: 70vh; - scrollbar-width: none;//remove scrollbar - - display: flex; - flex-direction: column; - align-items: center; - padding: 10px; - - & > *{ - margin-bottom: 20px; - } - &::-webkit-scrollbar { - display: none; - } - } - .arrow{ - width: 30px; - &:hover{ - scale: 1.4; - cursor: pointer; - } - transition: 0.3s; - } - .down{ - transform: rotate(180deg); - } - } - .visible{ - visibility: visible; - opacity: 1; - } - .hidden{ - visibility: hidden; - opacity: 0; - translate: -40px; - } - .ambassadorButton{ - transition: 0.3s; - &:hover{ - transform: scale(1.1); - } + .ambassadors { + overflow: scroll; + height: 70vh; + scrollbar-width: none;//remove scrollbar + + display: flex; + flex-direction: column; + align-items: center; + padding: 10px; + + & > * { + margin-bottom: 20px; + } + + &::-webkit-scrollbar { + display: none; + } } - .ambassadorButtonClicked{ - transform: scale(1.1); - outline: 3px solid orange; + + .arrow { + width: 30px; + transition: 0.3s; + + &:hover { + scale: 1.4; + cursor: pointer; + } } - .ambassadorCard{ - align-self: center; - animation: slideIn 0.5s ease-in-out; - z-index: 0; + + .down { + transform: rotate(180deg); } - .hideArrow{ - visibility: hidden; - opacity: 0; + } + + .visible { + visibility: visible; + opacity: 1; + } + + .hidden { + visibility: hidden; + opacity: 0; + translate: -40px; + } + + .ambassadorButton { + transition: 0.3s; + + &:hover { + transform: scale(1.1); } + } + + .ambassadorButtonClicked { + transform: scale(1.1); + outline: 3px solid orange; + } + + .ambassadorCard { + align-self: center; + animation: slideIn 0.5s ease-in-out; + z-index: 0; + } + + .hideArrow { + visibility: hidden; + opacity: 0; + } } @keyframes slideIn { - 0% { - opacity: 0; - transform: translateX(-40%); - } - 100% { - opacity: 1; - transform: translateX(0); - } + 0% { + opacity: 0; + transform: translateX(-40%); + } + + 100% { + opacity: 1; + transform: translateX(0); + } } + // accessibility for vestibular motion disorders @keyframes dissolveIn { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } } + @media (prefers-reduced-motion) { - .ambassadorList{ - .ambassadorCard{ - animation: dissolveIn 0.5s ease-in-out; - -webkit-animation: dissolveIn 0.5s ease-in-out; - } - .visible{ - opacity: 1; - } - .hidden{ - opacity: 0; - translate: 0; - } + .ambassadorList { + .ambassadorCard { + animation: dissolveIn 0.5s ease-in-out; + -webkit-animation: dissolveIn 0.5s ease-in-out; + } + + .visible { + opacity: 1; } -} \ No newline at end of file + + .hidden { + opacity: 0; + translate: 0; + } + } +} diff --git a/src/pages/overlay/components/disableChatPopup/DisableChatPopup.tsx b/src/pages/overlay/components/disableChatPopup/DisableChatPopup.tsx index cdb04210..0a69e0cc 100644 --- a/src/pages/overlay/components/disableChatPopup/DisableChatPopup.tsx +++ b/src/pages/overlay/components/disableChatPopup/DisableChatPopup.tsx @@ -2,17 +2,18 @@ import styles from './disableChatPopup.module.css' interface DisableChatPopupProps { - disableChatPopup: boolean - toggleDisableChatPopup: () => void + disableChatPopup: boolean + toggleDisableChatPopup: () => void } -export default function DisableChatPopup(props: DisableChatPopupProps){ - return ( -
- - -
- ) + +export default function DisableChatPopup(props: DisableChatPopupProps) { + return ( +
+ + +
+ ) } diff --git a/src/pages/overlay/components/disableChatPopup/disableChatPopup.module.scss b/src/pages/overlay/components/disableChatPopup/disableChatPopup.module.scss index 27491cfa..0d7047fd 100644 --- a/src/pages/overlay/components/disableChatPopup/disableChatPopup.module.scss +++ b/src/pages/overlay/components/disableChatPopup/disableChatPopup.module.scss @@ -1,77 +1,77 @@ @import '../../../../variables.scss'; .switchContainer { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - background-color: #18181b; - border-radius: 5px; - text-align: center; - padding: 10px; - width: 60px; - box-sizing: border-box; - color: white; - font-size: x-small; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background-color: #18181b; + border-radius: 5px; + text-align: center; + padding: 10px; + width: 60px; + box-sizing: border-box; + color: white; + font-size: x-small; - // The switch - the box around the slider - .switch { - position: relative; - display: inline-block; - width: 40px; - height: 23px; - margin-top: 5px; + // The switch - the box around the slider + .switch { + position: relative; + display: inline-block; + width: 40px; + height: 23px; + margin-top: 5px; - input { - // Hide default HTML checkbox - opacity: 0; - width: 0; - height: 0; + input { + // Hide default HTML checkbox + opacity: 0; + width: 0; + height: 0; - &:focus + .slider { - box-shadow: 0 0 1px #2196f3; - } + &:focus + .slider { + box-shadow: 0 0 1px #2196f3; + } - // Show the active state - &:checked + .slider { - background-color: $primary-color; + // Show the active state + &:checked + .slider { + background-color: $primary-color; - &::before { - transform: translateX(15px); - } - } + &::before { + transform: translateX(15px); } + } } + } - // The slider itself - .slider { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - border: 1px solid black; - border-radius: 34px; - background-color: $tertiary-color; + // The slider itself + .slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + border: 1px solid black; + border-radius: 34px; + background-color: $tertiary-color; - // The knob inside the slider - &::before { - position: absolute; - content: ""; - height: 17px; - width: 17px; - left: 3px; - top: 2px; - border-radius: 50%; - background-color: white; - transition: transform 0.4s; - } + // The knob inside the slider + &::before { + position: absolute; + content: ""; + height: 17px; + width: 17px; + left: 3px; + top: 2px; + border-radius: 50%; + background-color: white; + transition: transform 0.4s; } + } } @media (prefers-reduced-motion) { - .slider:before { - transition: none; - } + .slider:before { + transition: none; + } } diff --git a/src/pages/overlay/components/overlay/Overlay.tsx b/src/pages/overlay/components/overlay/Overlay.tsx index 77fb5c09..c057555f 100644 --- a/src/pages/overlay/components/overlay/Overlay.tsx +++ b/src/pages/overlay/components/overlay/Overlay.tsx @@ -10,60 +10,61 @@ import useChatCommand from '../../../../utils/chatCommand' import styles from './overlay.module.css' interface OverlayProps { - sleeping: boolean, - awoken: { - add: (callback: () => void) => void, - remove: (callback: () => void) => void - } - wake: (time: number) => void, - settings: { - disableChatPopup: boolean - } + sleeping: boolean, + awoken: { + add: (callback: () => void) => void, + remove: (callback: () => void) => void + } + wake: (time: number) => void, + settings: { + disableChatPopup: boolean + } } + export default function Overlay(props: OverlayProps) { - const [showAmbassadorList, setShowAmbassadorList] = useState(false) - const chosenAmbassador = useChatCommand() - const timeoutRef = useRef(undefined) - const awakingRef = useRef(false) + const [showAmbassadorList, setShowAmbassadorList] = useState(false) + const chosenAmbassador = useChatCommand() + const timeoutRef = useRef(undefined) + const awakingRef = useRef(false) - // When a chat command is run, show the list and auto-dismiss it after 6s - useEffect(() => { - if (chosenAmbassador !== undefined && !props.settings.disableChatPopup) { - // Show the list, and dismiss it after 6s - setShowAmbassadorList(true) - timeoutRef.current = setTimeout(() => { setShowAmbassadorList(false) }, 6000) + // When a chat command is run, show the list and auto-dismiss it after 6s + useEffect(() => { + if (chosenAmbassador !== undefined && !props.settings.disableChatPopup) { + // Show the list, and dismiss it after 6s + setShowAmbassadorList(true) + timeoutRef.current = setTimeout(() => { setShowAmbassadorList(false) }, 6000) - // Track that we're waking up, so that we don't immediately clear the timeout - awakingRef.current = true + // Track that we're waking up, so that we don't immediately clear the timeout + awakingRef.current = true - // Wake the overlay for 8s - props.wake(8000) - } + // Wake the overlay for 8s + props.wake(8000) + } - return () => { - if (timeoutRef.current) clearTimeout(timeoutRef.current) - } - }, [chosenAmbassador, props.wake]) + return () => { + if (timeoutRef.current) clearTimeout(timeoutRef.current) + } + }, [chosenAmbassador, props.wake]) - // If the user interacts with the overlay, clear the auto-dismiss timer - useEffect(() => { - const callback = () => { - if (awakingRef.current) awakingRef.current = false - else if (timeoutRef.current) clearTimeout(timeoutRef.current) - } - props.awoken.add(callback) - return () => props.awoken.remove(callback) - }, [props.awoken]) + // If the user interacts with the overlay, clear the auto-dismiss timer + useEffect(() => { + const callback = () => { + if (awakingRef.current) awakingRef.current = false + else if (timeoutRef.current) clearTimeout(timeoutRef.current) + } + props.awoken.add(callback) + return () => props.awoken.remove(callback) + }, [props.awoken]) - return ( + return (
- setShowAmbassadorList(!showAmbassadorList)} - /> - + setShowAmbassadorList(!showAmbassadorList)} + /> +
- ) + ) } diff --git a/src/pages/overlay/components/overlay/overlay.module.scss b/src/pages/overlay/components/overlay/overlay.module.scss index 7e3f760b..c0b91f23 100644 --- a/src/pages/overlay/components/overlay/overlay.module.scss +++ b/src/pages/overlay/components/overlay/overlay.module.scss @@ -1,51 +1,51 @@ @import '../../../../variables.scss'; .overlay { - display: flex; - gap: 10px; - transition: 0.3s ease-out; - transition-property: visibility, translate; + display: flex; + gap: 10px; + transition: 0.3s ease-out; + transition-property: visibility, translate; } .visible { - visibility: visible; + visibility: visible; } .hidden { - visibility: hidden; - translate: -40px; + visibility: hidden; + translate: -40px; } //media query min height 545px @media screen and (max-height: 555px) { - .overlay { - // scale down the overlay - scale: 0.8; - // move the overlay up - transform: translateX(-12%); - - .scrollAmbassadors .ambassadorList { - height: 80vh; - } + .overlay { + // scale down the overlay + scale: 0.8; + // move the overlay up + transform: translateX(-12%); + + .scrollAmbassadors .ambassadorList { + height: 80vh; } + } } @media screen and (max-height: 440px) { - .overlay { - // scale down the overlay - scale: 0.6; - // move the overlay up - transform: translateX(-32%); - - .scrollAmbassadors .ambassadorList { - height: 90vh; - } + .overlay { + // scale down the overlay + scale: 0.6; + // move the overlay up + transform: translateX(-32%); + + .scrollAmbassadors .ambassadorList { + height: 90vh; } + } } @media (prefers-reduced-motion) { - // just rely on app fade - .hidden { - translate: 0; - } + // just rely on app fade + .hidden { + translate: 0; + } } diff --git a/src/pages/overlay/components/overlaySettings/OverlaySettings.tsx b/src/pages/overlay/components/overlaySettings/OverlaySettings.tsx index 4390b897..4f99b8cd 100644 --- a/src/pages/overlay/components/overlaySettings/OverlaySettings.tsx +++ b/src/pages/overlay/components/overlaySettings/OverlaySettings.tsx @@ -5,23 +5,24 @@ import DisableChatPopup from "../disableChatPopup/DisableChatPopup" import styles from "./overlaySettings.module.css" interface OverlaySettingsProps { - sleeping: boolean, - settings: { - disableChatPopup: boolean - } - toggleDisableChatPopup: () => void + sleeping: boolean, + settings: { + disableChatPopup: boolean + } + toggleDisableChatPopup: () => void } -export default function OverlaySettings(props: OverlaySettingsProps){ - const toggleDisableChatPopup = () => { - props.toggleDisableChatPopup() - } - return ( -
- toggleDisableChatPopup()} - /> -
- ) +export default function OverlaySettings(props: OverlaySettingsProps) { + const toggleDisableChatPopup = () => { + props.toggleDisableChatPopup() + } + + return ( +
+ toggleDisableChatPopup()} + /> +
+ ) } diff --git a/src/pages/overlay/components/overlaySettings/overlaySettings.module.scss b/src/pages/overlay/components/overlaySettings/overlaySettings.module.scss index ad5bd7bf..fcf3bb79 100644 --- a/src/pages/overlay/components/overlaySettings/overlaySettings.module.scss +++ b/src/pages/overlay/components/overlaySettings/overlaySettings.module.scss @@ -1,23 +1,23 @@ .overlaySettings { - position: absolute; - top: 40px; // Matching the margin-top of the activation buttons - right: 0; - transition: 0.3s ease-out; - transition-property: visibility, translate; + position: absolute; + top: 40px; // Matching the margin-top of the activation buttons + right: 0; + transition: 0.3s ease-out; + transition-property: visibility, translate; } .visible { - visibility: visible; + visibility: visible; } .hidden { - visibility: hidden; - translate: 40px; + visibility: hidden; + translate: 40px; } @media (prefers-reduced-motion) { - // just rely on app fade - .hidden { - translate: 0; - } + // just rely on app fade + .hidden { + translate: 0; + } } diff --git a/src/pages/overlay/index.scss b/src/pages/overlay/index.scss index 82460be4..1c3a0c74 100644 --- a/src/pages/overlay/index.scss +++ b/src/pages/overlay/index.scss @@ -1,10 +1,10 @@ @import '../../variables.scss'; -*{ - margin: 0; - padding: 0; +* { + margin: 0; + padding: 0; } -body{ - font-family: $font-family; -} \ No newline at end of file +body { + font-family: $font-family; +} diff --git a/src/pages/overlay/index.tsx b/src/pages/overlay/index.tsx index 28c61bdf..8d0657a3 100644 --- a/src/pages/overlay/index.tsx +++ b/src/pages/overlay/index.tsx @@ -11,4 +11,4 @@ root.render( -); \ No newline at end of file +); diff --git a/src/pages/panel/components/App.scss b/src/pages/panel/components/App.scss index 76dc77ad..807f3eb5 100644 --- a/src/pages/panel/components/App.scss +++ b/src/pages/panel/components/App.scss @@ -1,21 +1,21 @@ @import '../../../variables.scss'; -.App{ +.App { background-color: $secondary-color; - width: 100vw; min-height: 100vh; - outline: 1px solid black; } //Scroll Bar -::-webkit-scrollbar { +::-webkit-scrollbar { width: 7px; } + ::-webkit-scrollbar-track { //track background: white; } + ::-webkit-scrollbar-thumb { //handle background: $primary-color; -} \ No newline at end of file +} diff --git a/src/pages/panel/components/App.tsx b/src/pages/panel/components/App.tsx index 0bb7cec2..b0c3b631 100644 --- a/src/pages/panel/components/App.tsx +++ b/src/pages/panel/components/App.tsx @@ -1,4 +1,4 @@ -//components +//components import Nav from './nav/Nav'; import AmbassadorPanel from './ambassadorPanel/AmbassadorPanel'; diff --git a/src/pages/panel/components/ambassadorCardOverlay/AmbassadorCardOverlay.tsx b/src/pages/panel/components/ambassadorCardOverlay/AmbassadorCardOverlay.tsx index 0e1a7308..494975da 100644 --- a/src/pages/panel/components/ambassadorCardOverlay/AmbassadorCardOverlay.tsx +++ b/src/pages/panel/components/ambassadorCardOverlay/AmbassadorCardOverlay.tsx @@ -7,12 +7,13 @@ interface AmbassadorCardOverlayProps{ ambassadorCard: AmbassadorCardProps close: () => void } + export default function AmbassadorCardOverlay(props: AmbassadorCardOverlayProps) { return (
diff --git a/src/pages/panel/components/ambassadorCardOverlay/ambassadorCardOverlay.module.scss b/src/pages/panel/components/ambassadorCardOverlay/ambassadorCardOverlay.module.scss index 9889eec3..94806b62 100644 --- a/src/pages/panel/components/ambassadorCardOverlay/ambassadorCardOverlay.module.scss +++ b/src/pages/panel/components/ambassadorCardOverlay/ambassadorCardOverlay.module.scss @@ -1,53 +1,55 @@ .background { - display: flex; - justify-content: center; - align-items: center; - - position: fixed; /* Stay in place */ - z-index: 1; /* Sit on top */ - left: 0; - top: 0; - width: 100%; /* Full width */ - height: 100%; /* Full height */ - background-color: rgb(0,0,0); /* Fallback color */ - background-color: rgba(0, 0, 0, 0.5); /* Black w/ opacity */ + display: flex; + justify-content: center; + align-items: center; + position: fixed; /* Stay in place */ + z-index: 1; /* Sit on top */ + left: 0; + top: 0; + width: 100%; /* Full width */ + height: 100%; /* Full height */ + background-color: rgb(0,0,0); /* Fallback color */ + background-color: rgba(0, 0, 0, 0.5); /* Black w/ opacity */ } -.ambassadorCard{ - //slide in from the top - animation: slideIn 0.5s ease-in-out; - -webkit-animation: slideIn 0.5s ease-in-out; +.ambassadorCard { + //slide in from the top + animation: slideIn 0.5s ease-in-out; + -webkit-animation: slideIn 0.5s ease-in-out; } @keyframes slideIn { - 0% { - transform: translateY(-100%); - -webkit-transform: translateY(-100%); - -moz-transform: translateY(-100%); - -ms-transform: translateY(-100%); - -o-transform: translateY(-100%); - } - 100% { - transform: translateY(0); - -webkit-transform: translateY(0); - -moz-transform: translateY(0); - -ms-transform: translateY(0); - -o-transform: translateY(0); - } + 0% { + transform: translateY(-100%); + -webkit-transform: translateY(-100%); + -moz-transform: translateY(-100%); + -ms-transform: translateY(-100%); + -o-transform: translateY(-100%); + } + + 100% { + transform: translateY(0); + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -ms-transform: translateY(0); + -o-transform: translateY(0); + } } // accessibility for vestibular motion disorders @keyframes dissolveIn { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } } + @media (prefers-reduced-motion) { - .ambassadorCard{ - animation: dissolveIn 0.5s ease-in-out; - -webkit-animation: dissolveIn 0.5s ease-in-out; - } -} \ No newline at end of file + .ambassadorCard { + animation: dissolveIn 0.5s ease-in-out; + -webkit-animation: dissolveIn 0.5s ease-in-out; + } +} diff --git a/src/pages/panel/components/ambassadorPanel/AmbassadorPanel.tsx b/src/pages/panel/components/ambassadorPanel/AmbassadorPanel.tsx index 6704dea2..25babd93 100644 --- a/src/pages/panel/components/ambassadorPanel/AmbassadorPanel.tsx +++ b/src/pages/panel/components/ambassadorPanel/AmbassadorPanel.tsx @@ -17,12 +17,11 @@ export default function AmbassadorPanel() { const chosenAmbassador = useChatCommand()?.slice(1) useEffect(() => { - if(chosenAmbassador !== undefined){ + if (chosenAmbassador !== undefined) setAmbassadorCard(ambassadors.find(ambassador => ambassador.name.split(" ")[0].toLowerCase() === chosenAmbassador)?.name || "") - } }, [chosenAmbassador, ambassadors]) - function handleClose(): void{ + function handleClose(): void { setAmbassadorCard("") } function handleGetCard(name: string): void { @@ -30,17 +29,15 @@ export default function AmbassadorPanel() { } return ( -
+
{ambassadors && ambassadors.map(ambassador => ( <> - {ambassadorCard === ambassador.name ? + {ambassadorCard === ambassador.name ? ( - : null - } + ) : null} - Alveus Logo -

Alveus Ambassadors

+ Alveus Logo +

Alveus Ambassadors

) } diff --git a/src/pages/panel/components/nav/nav.module.scss b/src/pages/panel/components/nav/nav.module.scss index 7504068f..10d6ce9b 100644 --- a/src/pages/panel/components/nav/nav.module.scss +++ b/src/pages/panel/components/nav/nav.module.scss @@ -1,21 +1,20 @@ @import '../../../../variables.scss'; -.nav{ - display: flex; - justify-content: center; - align-items: center; +.nav { + display: flex; + justify-content: center; + align-items: center; + background-color: $primary-color; + box-shadow: 0 3px 4px #2c2c2c; + padding: 10px; - background-color: $primary-color; - box-shadow: 0 3px 4px #2c2c2c; + img { + width: 30px; + height: 30px; + margin-right: 10px; + } - padding: 10px; - - img{ - width: 30px; - height: 30px; - margin-right: 10px; - } - h1{ - font-size: 1.2rem; - } -} \ No newline at end of file + h1 { + font-size: 1.2rem; + } +} diff --git a/src/utils/chatCommand.ts b/src/utils/chatCommand.ts index 4c7db41f..fa38cb69 100644 --- a/src/utils/chatCommand.ts +++ b/src/utils/chatCommand.ts @@ -3,73 +3,73 @@ import tmi, { ChatUserstate } from 'tmi.js' import AmbassadorData from '../assets/ambassadors.json' /** - * @description Some ambassadors have names with diacritics in them (Ex: Jalapeño). - * Note: a diacritic is a mark added to a letter to change its sound or meaning (Ex: ñ). + * @description Some ambassadors have names with diacritics in them (Ex: Jalapeño). + * Note: a diacritic is a mark added to a letter to change its sound or meaning (Ex: ñ). * This function creates a map of an ambassador with diacritics in their name and their name without diacritics. * (Ex: { jalapeno: jalapeño }) * @returns a map of the normalized names and original names */ const getMapOfAmbassadorWithDiacritics = (): Map => { - //store names that have letters with diacritics in them - const ambassadorsWithDiacriticsInNames = AmbassadorData.filter( - (ambassador) =>{ - const ambassadorOriginalFirstName = ambassador.name.split(' ')[0].toLowerCase() - const ambassadorFirstNameWithRemovedDiacritic = ambassadorOriginalFirstName.normalize('NFD').replace(/[\u0300-\u036f]/g, '') + //store names that have letters with diacritics in them + const ambassadorsWithDiacriticsInNames = AmbassadorData.filter( + (ambassador) =>{ + const ambassadorOriginalFirstName = ambassador.name.split(' ')[0].toLowerCase() + const ambassadorFirstNameWithRemovedDiacritic = ambassadorOriginalFirstName.normalize('NFD').replace(/[\u0300-\u036f]/g, '') - return ambassadorOriginalFirstName !== ambassadorFirstNameWithRemovedDiacritic - } - ) - // a hashmap of the normalized names and their original names - const diacriticMap = new Map() - ambassadorsWithDiacriticsInNames.forEach((ambassador) => { - const ambassadorOriginalFirstName = ambassador.name.split(' ')[0].toLowerCase() - const ambassadorFirstNameWithRemovedDiacritic = ambassadorOriginalFirstName.normalize('NFD').replace(/[\u0300-\u036f]/g, '') - diacriticMap.set(ambassadorFirstNameWithRemovedDiacritic, ambassadorOriginalFirstName) - }) + return ambassadorOriginalFirstName !== ambassadorFirstNameWithRemovedDiacritic + } + ) + // a hashmap of the normalized names and their original names + const diacriticMap = new Map() + ambassadorsWithDiacriticsInNames.forEach((ambassador) => { + const ambassadorOriginalFirstName = ambassador.name.split(' ')[0].toLowerCase() + const ambassadorFirstNameWithRemovedDiacritic = ambassadorOriginalFirstName.normalize('NFD').replace(/[\u0300-\u036f]/g, '') + diacriticMap.set(ambassadorFirstNameWithRemovedDiacritic, ambassadorOriginalFirstName) + }) - return diacriticMap + return diacriticMap } export default function useChatCommand() { - const [command, setCommand] = useState() - const ambassadorNames = AmbassadorData.map((ambassador) => ambassador.name.split(' ')[0].toLowerCase()) + const [command, setCommand] = useState() + const ambassadorNames = AmbassadorData.map((ambassador) => ambassador.name.split(' ')[0].toLowerCase()) - const diacriticsMap: Map = getMapOfAmbassadorWithDiacritics() + const diacriticsMap: Map = getMapOfAmbassadorWithDiacritics() - const client = new tmi.Client({ - connection: { - secure: true, - reconnect: true - }, - channels: [ - // 'AbdullahMorrison', //! For testing purposes - 'Maya', - 'AlveusSanctuary' - ] - }) + const client = new tmi.Client({ + connection: { + secure: true, + reconnect: true + }, + channels: [ + // 'AbdullahMorrison', //! For testing purposes + 'Maya', + 'AlveusSanctuary' + ] + }) - useEffect(() => { - client.on('message', messageHandler) - client.on('connected', connectedHandler) - client.connect() - }, []) + useEffect(() => { + client.on('message', messageHandler) + client.on('connected', connectedHandler) + client.connect() + }, []) - const messageHandler = (channel: string, tags: ChatUserstate, msg: string, self: boolean) => { - //ignore if user is not a moderator or broadcaster or if the user is not AbdullahMorrison - if(!tags.mod && !tags.badges?.broadcaster && tags.username !== 'abdullahmorrison') return - // Ignore echoed messages (messages sent by the bot) and messages that don't start with '!' - if (self || !msg.trim().startsWith('!')) return + const messageHandler = (channel: string, tags: ChatUserstate, msg: string, self: boolean) => { + //ignore if user is not a moderator or broadcaster or if the user is not AbdullahMorrison + if (!tags.mod && !tags.badges?.broadcaster && tags.username !== 'abdullahmorrison') return + // Ignore echoed messages (messages sent by the bot) and messages that don't start with '!' + if (self || !msg.trim().startsWith('!')) return - const commandName = msg.trim().toLowerCase() - if(ambassadorNames.find((name) => name === commandName.slice(1))) { - setCommand(commandName) - }else if(diacriticsMap.get(commandName.slice(1))) { // Check if a user typed a name without diacritics (Ex: !jalapeno should be !Jalapeño) - setCommand("!"+diacriticsMap.get(commandName.slice(1))) - } - } - const connectedHandler = () => { - console.log('*Twitch extension is connected to chat*') + const commandName = msg.trim().toLowerCase() + if (ambassadorNames.find((name) => name === commandName.slice(1))) { + setCommand(commandName) + } else if (diacriticsMap.get(commandName.slice(1))) { // Check if a user typed a name without diacritics (Ex: !jalapeno should be !Jalapeño) + setCommand("!"+diacriticsMap.get(commandName.slice(1))) } + } + const connectedHandler = () => { + console.log('*Twitch extension is connected to chat*') + } - return command -} \ No newline at end of file + return command +} diff --git a/src/utils/compositions/ambassador/Ambassador.tsx b/src/utils/compositions/ambassador/Ambassador.tsx index 537d0011..64187d98 100644 --- a/src/utils/compositions/ambassador/Ambassador.tsx +++ b/src/utils/compositions/ambassador/Ambassador.tsx @@ -8,9 +8,10 @@ interface AmbassadorProps{ onClick?: () => void } + export default function Ambassador(props: AmbassadorProps) { return ( -
+
{props.children}
) diff --git a/src/utils/compositions/ambassador/ambassador.module.scss b/src/utils/compositions/ambassador/ambassador.module.scss index 52ad0978..c5b5b7b6 100644 --- a/src/utils/compositions/ambassador/ambassador.module.scss +++ b/src/utils/compositions/ambassador/ambassador.module.scss @@ -1,18 +1,16 @@ @import '../../../variables.scss'; -.ambassador{ - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; +.ambassador { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 20px 15px; + background-color: $primary-color; + border-radius: 10px; + text-align: center; - padding: 20px 15px; - - background-color: $primary-color; + img { border-radius: 10px; - text-align: center; - - img{ - border-radius: 10px; - } -} \ No newline at end of file + } +} diff --git a/src/utils/dateManager.ts b/src/utils/dateManager.ts index 865be9c4..d4bee97d 100644 --- a/src/utils/dateManager.ts +++ b/src/utils/dateManager.ts @@ -1,98 +1,98 @@ /** - * calculates the age of the ambassador based on the date of birth - * in weeks, months, or years - * @param dateOfBirth date of birth in the format YYYY-MM-DD or YYYY-MM or YYYY - */ - export function calculateAge(dateOfBirth: string): string { - const accurateDOB = dateOfBirth.split('-').length === 3 - - const today = new Date(); - const dob = new Date(dateOfBirth); + * calculates the age of the ambassador based on the date of birth + * in weeks, months, or years + * @param dateOfBirth date of birth in the format YYYY-MM-DD or YYYY-MM or YYYY + */ +export function calculateAge(dateOfBirth: string): string { + const accurateDOB = dateOfBirth.split('-').length === 3 - let ageInMilliseconds = today.getTime() - dob.getTime(); - let ageInYears = ageInMilliseconds / 3.154e+10; // 3.154e+10 is the number of milliseconds in a year - if (ageInYears < 1) { - let ageInMonths = ageInMilliseconds / 2.628e+9; // 2.628e+9 is the number of milliseconds in a month - if (ageInMonths < 1) { - let ageInWeeks = ageInMilliseconds / 6.048e+8; // 6.048e+8 is the number of milliseconds in a week - if (ageInWeeks < 1) { - let ageInDays = Math.floor(ageInMilliseconds / 8.64e+7); // 8.64e+7 is the number of milliseconds in a day - return `${ageInDays} day`+ (ageInDays > 1 ? "s" : "") - } else { - return (!accurateDOB ? "~" : "")+`${Math.floor(ageInWeeks)} wk`+ (Math.floor(ageInWeeks) > 1 ? "s" : "") - } + const today = new Date(); + const dob = new Date(dateOfBirth); + + let ageInMilliseconds = today.getTime() - dob.getTime(); + let ageInYears = ageInMilliseconds / 3.154e+10; // 3.154e+10 is the number of milliseconds in a year + if (ageInYears < 1) { + let ageInMonths = ageInMilliseconds / 2.628e+9; // 2.628e+9 is the number of milliseconds in a month + if (ageInMonths < 1) { + let ageInWeeks = ageInMilliseconds / 6.048e+8; // 6.048e+8 is the number of milliseconds in a week + if (ageInWeeks < 1) { + let ageInDays = Math.floor(ageInMilliseconds / 8.64e+7); // 8.64e+7 is the number of milliseconds in a day + return `${ageInDays} day`+ (ageInDays > 1 ? "s" : "") } else { - return (!accurateDOB ? "~" : "")+`${Math.floor(ageInMonths)} mth`+ (Math.floor(ageInMonths) > 1 ? "s" : "") + return (!accurateDOB ? "~" : "")+`${Math.floor(ageInWeeks)} wk`+ (Math.floor(ageInWeeks) > 1 ? "s" : "") } } else { - return (!accurateDOB ? "~" : "")+`${Math.floor(ageInYears)} yr`+ (Math.floor(ageInYears) > 1 ? "s" : "") + return (!accurateDOB ? "~" : "")+`${Math.floor(ageInMonths)} mth`+ (Math.floor(ageInMonths) > 1 ? "s" : "") } + } else { + return (!accurateDOB ? "~" : "")+`${Math.floor(ageInYears)} yr`+ (Math.floor(ageInYears) > 1 ? "s" : "") } - /** - * converts a date to a string of the date in the format Month DD, YYYY or Month YYYY or YYYY - * @param date date in the format YYYY-MM-DD or YYYY-MM or YYYY - * @returns a string of the date in the format Month DD, YYYY or Month YYYY or YYYY - */ - export function formatDate(date: string): string{ - const dateArray = date.split("-") - let day = dateArray[2] - let month = dateArray[1] - let year = dateArray[0] +} - if(month && day){ - month = monthConverter(parseInt(month)) - day = parseInt(day) + getDaySuffix(parseInt(day)) - } - else if(month){ - month = monthConverter(parseInt(month)) - } +/** + * converts a date to a string of the date in the format Month DD, YYYY or Month YYYY or YYYY + * @param date date in the format YYYY-MM-DD or YYYY-MM or YYYY + * @returns a string of the date in the format Month DD, YYYY or Month YYYY or YYYY + */ +export function formatDate(date: string): string{ + const dateArray = date.split("-") + let day = dateArray[2] + let month = dateArray[1] + let year = dateArray[0] - if(day && month && year) - return `${month} ${day}, ${year}` - else if(month && year) - return `~ ${month}, ${year}` - - return `~ ${year}` + if (month && day) { + month = monthConverter(parseInt(month)) + day = parseInt(day) + getDaySuffix(parseInt(day)) + } else if (month) { + month = monthConverter(parseInt(month)) } - /** - * converts the numerical month to the name of the month - * @param month month number (1-12) - * @returns the name corresponding to the month number - */ - function monthConverter(month: number){ - const monthNames = ["January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December"] + if (day && month && year) + return `${month} ${day}, ${year}` + else if (month && year) + return `~ ${month}, ${year}` - if(month < 0 || month > 12) - return "Invalid month" - - return monthNames[month-1] - } - /** - * @param day day of the month (1-31) - * @returns the suffix of the day (st, nd, rd, or th) - */ - function getDaySuffix(day: number): string{ - if(day < 1 || day > 31) - return "" - - if(day === 1 || day === 21 || day === 31) - return "st" - else if(day === 2 || day === 22) - return "nd" - else if(day === 3 || day === 23) - return "rd" - else - return "th" - } + return `~ ${year}` +} + +/** + * converts the numerical month to the name of the month + * @param month month number (1-12) + * @returns the name corresponding to the month number + */ +function monthConverter(month: number){ + const monthNames = ["January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"] + + if (month < 0 || month > 12) + return "Invalid month" + + return monthNames[month-1] +} +/** + * @param day day of the month (1-31) + * @returns the suffix of the day (st, nd, rd, or th) + */ +function getDaySuffix(day: number): string{ + if (day < 1 || day > 31) + return "" + + if (day === 1 || day === 21 || day === 31) + return "st" + else if (day === 2 || day === 22) + return "nd" + else if (day === 3 || day === 23) + return "rd" + else + return "th" +} - export function isBirthday(dateOfBirth: string): boolean{ - if(dateOfBirth.split('-').length !== 3) - return false +export function isBirthday(dateOfBirth: string): boolean{ + if (dateOfBirth.split('-').length !== 3) + return false - const today = new Date(); - const dob = new Date(dateOfBirth); + const today = new Date(); + const dob = new Date(dateOfBirth); - return today.getUTCMonth() === dob.getUTCMonth() && today.getUTCDate() === dob.getUTCDate() - } \ No newline at end of file + return today.getUTCMonth() === dob.getUTCMonth() && today.getUTCDate() === dob.getUTCDate() +} diff --git a/src/utils/global/ambassadorButton/AmbassadorButton.tsx b/src/utils/global/ambassadorButton/AmbassadorButton.tsx index 93de656e..a1c70807 100644 --- a/src/utils/global/ambassadorButton/AmbassadorButton.tsx +++ b/src/utils/global/ambassadorButton/AmbassadorButton.tsx @@ -25,9 +25,9 @@ export default function AmbassadorButton(props: AmbassadorButtonProps) { } return ( - {props.img.altText} -

{props.name}

-

{props.species}

+ {props.img.altText} +

{props.name}

+

{props.species}

) } diff --git a/src/utils/global/ambassadorButton/ambassadorButton.module.scss b/src/utils/global/ambassadorButton/ambassadorButton.module.scss index 9f0d5348..db07c79a 100644 --- a/src/utils/global/ambassadorButton/ambassadorButton.module.scss +++ b/src/utils/global/ambassadorButton/ambassadorButton.module.scss @@ -1,28 +1,30 @@ @import '../../../variables.scss'; -.ambassador{ - width: 100px; - height: 100px; - box-shadow: 0 5px 8px $accent-color; +.ambassador { + width: 100px; + height: 100px; + box-shadow: 0 5px 8px $accent-color; - &:hover{ - cursor: pointer; - filter: brightness(1.1); - } + &:hover { + cursor: pointer; + filter: brightness(1.1); + } - .img{ - border-radius: 5px; - height: 70px; - max-width: 100px; - object-fit: cover; - margin-bottom: 5px; - } - .name{ - color: $primary-text; - font-size: 0.8rem; - } - .species{ - font-size: 0.7rem; - color: $secondary-text; - } -} \ No newline at end of file + .img { + border-radius: 5px; + height: 70px; + max-width: 100px; + object-fit: cover; + margin-bottom: 5px; + } + + .name { + color: $primary-text; + font-size: 0.8rem; + } + + .species { + font-size: 0.7rem; + color: $secondary-text; + } +} diff --git a/src/utils/global/ambassadorCard/AmbassadorCard.tsx b/src/utils/global/ambassadorCard/AmbassadorCard.tsx index 7d08a6e8..22e1c148 100644 --- a/src/utils/global/ambassadorCard/AmbassadorCard.tsx +++ b/src/utils/global/ambassadorCard/AmbassadorCard.tsx @@ -8,12 +8,12 @@ export interface AmbassadorCardProps { name: string species: string img: { - src: string - altText: string + src: string + altText: string } scientificName: string sex?: string - dateOfBirth: string + dateOfBirth: string iucnStatus: string story: string conservationMission: string @@ -21,56 +21,56 @@ export interface AmbassadorCardProps { close?: ()=>void ClassName?: string } + export default function AmbassadorCard(props: AmbassadorCardProps) { return ( - - { - props.close ?
×
- : null - } + + {props.close ? ( +
×
+ ) : null} -

{props.cardData.name}

- {props.cardData.img.altText} +

{props.cardData.name}

+ {props.cardData.img.altText} -
-

Species

-

{props.cardData.species}

-

{props.cardData.scientificName}

-
+
+

Species

+

{props.cardData.species}

+

{props.cardData.scientificName}

+
-
-
-

Sex

-

{props.cardData.sex}

-
-
-

Age

-

{ - props.cardData.dateOfBirth !== "" ? calculateAge(props.cardData.dateOfBirth) : "Unknown" - }

-
-
-

Birthday

-

{ - props.cardData.dateOfBirth !== "" ? formatDate(props.cardData.dateOfBirth) : "Unknown" - }

-
+
+
+

Sex

+

{props.cardData.sex}

- -
-

IUCN Status

-

{props.cardData.iucnStatus}

+
+

Age

+

+ {props.cardData.dateOfBirth !== "" ? calculateAge(props.cardData.dateOfBirth) : "Unknown"} +

+
+

Birthday

+

+ {props.cardData.dateOfBirth !== "" ? formatDate(props.cardData.dateOfBirth) : "Unknown"} +

+
+
-
-

Story

-

{props.cardData.story}

-
+
+

IUCN Status

+

{props.cardData.iucnStatus}

+
-
-

Conservation Mission

-

{props.cardData.conservationMission}

-
- +
+

Story

+

{props.cardData.story}

+
+ +
+

Conservation Mission

+

{props.cardData.conservationMission}

+
+ ) } diff --git a/src/utils/global/ambassadorCard/ambassadorCard.module.scss b/src/utils/global/ambassadorCard/ambassadorCard.module.scss index 86e64bf8..e095a846 100644 --- a/src/utils/global/ambassadorCard/ambassadorCard.module.scss +++ b/src/utils/global/ambassadorCard/ambassadorCard.module.scss @@ -1,72 +1,80 @@ @import '../../../variables.scss'; -.ambassadorCard{ - width: 275px; - height: 425px; - justify-content: start; - align-items: baseline; - background-color: $accent-color; - border: 10px solid $primary-color; - border-radius: 20px; - box-shadow: 0 3px 3px $accent-color; - padding: 5px 0; - padding-bottom: 10px; +.ambassadorCard { + width: 275px; + height: 425px; + justify-content: start; + align-items: baseline; + background-color: $accent-color; + border: 10px solid $primary-color; + border-radius: 20px; + box-shadow: 0 3px 3px $accent-color; + padding: 5px 0; + padding-bottom: 10px; + color: $primary-text; + text-align: left; + font-size: 0.7rem; + position: relative; - color: $primary-text; - text-align: left; - font-size: 0.7rem; + h3 { + font-size: 0.9rem; + } - position: relative; - h3{ - font-size: 0.9rem; - } - .close{ - position: absolute; - top: -5px; - right: 10px; - padding: 5px; - font-size: x-large; - cursor: pointer; + .close { + position: absolute; + top: -5px; + right: 10px; + padding: 5px; + font-size: x-large; + cursor: pointer; - &:hover{ - filter: brightness(0.7); - } + &:hover{ + filter: brightness(0.7); } + } - .img{ - //crop image with a ratio of 2.2:1 - width: 100%; - height: 125px; + .img { + //crop image with a ratio of 2.2:1 + width: 100%; + height: 125px; + align-self: center; + border-radius: 0; + margin-bottom: 5px; + } - align-self: center; - border-radius: 0; - margin-bottom: 5px; - } - .name, .row, .story, .conservationmission{ - margin-left: 5px; - } - .name{ - font-size: 1.3rem; - } + .name, + .row, + .story, + .conservationmission { + margin-left: 5px; + } - .row{ - margin-bottom: 5px; - } - .story, .conservationmission{ - height: 60px; - padding-bottom: 5px; - } - .compact{ //multiple data put in one row - display: flex; - &>div{ - margin-right: 20px; - } + .name { + font-size: 1.3rem; + } + + .row { + margin-bottom: 5px; + } + + .story, + .conservationmission { + height: 60px; + padding-bottom: 5px; + } + + .compact { //multiple data put in one row + display: flex; + + & > div { + margin-right: 20px; } + } } -.birthday::after{ - content: url('../../../assets/partyHat.jpg'); - position: absolute; - top: -70px; - left: 100px; -} \ No newline at end of file +.birthday::after { + content: url('../../../assets/partyHat.jpg'); + position: absolute; + top: -70px; + left: 100px; +} diff --git a/src/utils/global/loadingSpinner/LoadingSpinner.tsx b/src/utils/global/loadingSpinner/LoadingSpinner.tsx index 7ef6feac..e37f0b2b 100644 --- a/src/utils/global/loadingSpinner/LoadingSpinner.tsx +++ b/src/utils/global/loadingSpinner/LoadingSpinner.tsx @@ -3,7 +3,7 @@ import styles from './loadingSpinner.module.css' export default function LoadingSpinner() { return (
-
+
) } diff --git a/src/utils/global/loadingSpinner/loadingSpinner.module.scss b/src/utils/global/loadingSpinner/loadingSpinner.module.scss index 8f10e39f..afe9e5e3 100644 --- a/src/utils/global/loadingSpinner/loadingSpinner.module.scss +++ b/src/utils/global/loadingSpinner/loadingSpinner.module.scss @@ -1,6 +1,6 @@ @import '../../../variables.scss'; -.loading{ +.loading { width: 50px; height: 50px; border: 10px solid $primary-color; /* Light grey */ @@ -13,7 +13,8 @@ 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } -} \ No newline at end of file +} diff --git a/src/variables.scss b/src/variables.scss index e4d3b015..52ccab00 100644 --- a/src/variables.scss +++ b/src/variables.scss @@ -8,4 +8,4 @@ $accent-color: #272b27; $primary-text: white; $secondary-text: #dcdcdc; -$font-family: 'Nunito', sans-serif; \ No newline at end of file +$font-family: 'Nunito', sans-serif; From 4683c474e4aafd104de9029f5a4845e35d241b84 Mon Sep 17 00:00:00 2001 From: MattIPv4 Date: Mon, 23 Jan 2023 21:57:57 +0000 Subject: [PATCH 4/6] Add GitHub Actions workflow --- .github/workflows/test.yml | 31 +++++++++++++++++++++++++++++++ .nvmrc | 1 + 2 files changed, 32 insertions(+) create mode 100644 .github/workflows/test.yml create mode 100644 .nvmrc diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..da28c45e --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,31 @@ +name: Test + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout commit + uses: actions/checkout@v3 + + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version-file: .nvmrc + cache: npm + + - name: Install dependencies + run: npm ci + env: + NODE_ENV: development + CI: true + + - name: Run linting + run: npm run lint + + - name: Build extension + run: npm run build diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..0e9dc6b5 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v18.13.0 From a590e510f676e399abd9e784c75ffee36ca9826a Mon Sep 17 00:00:00 2001 From: MattIPv4 Date: Mon, 23 Jan 2023 22:04:28 +0000 Subject: [PATCH 5/6] Update build scripts --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 335aae4f..ece538f9 100644 --- a/package.json +++ b/package.json @@ -32,10 +32,11 @@ "web-vitals": "^2.1.4" }, "scripts": { - "start": "npm-run-all --parallel watch serve", + "start": "npm-run-all --parallel sass:watch serve", "serve": "env-cmd -f .env react-app-rewired start", - "watch": "sass --no-source-map --watch src:src", - "build": "react-app-rewired build", + "sass:build": "sass --no-source-map src:src", + "sass:watch": "npm run sass:build -- --watch", + "build": "npm run sass:build && react-app-rewired build", "test": "react-app-rewired test", "lint": "npm run lint:editorconfig", "lint:editorconfig": "editorconfig-checker", From 36df446a8d2c9e5eb8bcd27141d3ff43da2f2d37 Mon Sep 17 00:00:00 2001 From: MattIPv4 Date: Mon, 23 Jan 2023 22:22:35 +0000 Subject: [PATCH 6/6] Resolve React warnings --- src/pages/overlay/App.tsx | 6 ++-- .../ambassadorList/AmbassadorList.tsx | 2 +- .../overlay/components/overlay/Overlay.tsx | 16 ++++++---- src/utils/chatCommand.ts | 32 +++++++++---------- 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/pages/overlay/App.tsx b/src/pages/overlay/App.tsx index 87035be1..aec4038f 100644 --- a/src/pages/overlay/App.tsx +++ b/src/pages/overlay/App.tsx @@ -66,8 +66,10 @@ export default function App() { // Bind a capturing event listener for scrolling (so we can see scrolling for children) useEffect(() => { - appRef.current?.addEventListener("scroll", interacted, true) - return () => appRef.current?.removeEventListener("scroll", interacted, true) + if (!appRef.current) return + const node = appRef.current + node.addEventListener("scroll", interacted, true) + return () => node.removeEventListener("scroll", interacted, true) }, [interacted]) // Immediately sleep the overlay diff --git a/src/pages/overlay/components/ambassadorList/AmbassadorList.tsx b/src/pages/overlay/components/ambassadorList/AmbassadorList.tsx index 82931595..fc0bd22a 100644 --- a/src/pages/overlay/components/ambassadorList/AmbassadorList.tsx +++ b/src/pages/overlay/components/ambassadorList/AmbassadorList.tsx @@ -32,7 +32,7 @@ export default function AmbassadorList(props: AmbassadorListProps) { scrollListToAmbassador(ambassador.name.split(" ")[0].toLowerCase()) } } - }, [props.chatChosenAmbassador]) + }, [props.chatChosenAmbassador, ambassadors]) const scrollListToAmbassador = (name: string) => { if (!ambassadorList.current) return diff --git a/src/pages/overlay/components/overlay/Overlay.tsx b/src/pages/overlay/components/overlay/Overlay.tsx index c057555f..77a72686 100644 --- a/src/pages/overlay/components/overlay/Overlay.tsx +++ b/src/pages/overlay/components/overlay/Overlay.tsx @@ -22,6 +22,8 @@ interface OverlayProps { } export default function Overlay(props: OverlayProps) { + const { sleeping, awoken, wake, settings } = props + const [showAmbassadorList, setShowAmbassadorList] = useState(false) const chosenAmbassador = useChatCommand() const timeoutRef = useRef(undefined) @@ -29,7 +31,7 @@ export default function Overlay(props: OverlayProps) { // When a chat command is run, show the list and auto-dismiss it after 6s useEffect(() => { - if (chosenAmbassador !== undefined && !props.settings.disableChatPopup) { + if (chosenAmbassador !== undefined && !settings.disableChatPopup) { // Show the list, and dismiss it after 6s setShowAmbassadorList(true) timeoutRef.current = setTimeout(() => { setShowAmbassadorList(false) }, 6000) @@ -38,13 +40,13 @@ export default function Overlay(props: OverlayProps) { awakingRef.current = true // Wake the overlay for 8s - props.wake(8000) + wake(8000) } return () => { if (timeoutRef.current) clearTimeout(timeoutRef.current) } - }, [chosenAmbassador, props.wake]) + }, [chosenAmbassador, settings.disableChatPopup, wake]) // If the user interacts with the overlay, clear the auto-dismiss timer useEffect(() => { @@ -52,12 +54,12 @@ export default function Overlay(props: OverlayProps) { if (awakingRef.current) awakingRef.current = false else if (timeoutRef.current) clearTimeout(timeoutRef.current) } - props.awoken.add(callback) - return () => props.awoken.remove(callback) - }, [props.awoken]) + awoken.add(callback) + return () => awoken.remove(callback) + }, [awoken]) return ( -
+
setShowAmbassadorList(!showAmbassadorList)} /> diff --git a/src/utils/chatCommand.ts b/src/utils/chatCommand.ts index fa38cb69..dba04bdd 100644 --- a/src/utils/chatCommand.ts +++ b/src/utils/chatCommand.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import tmi, { ChatUserstate } from 'tmi.js' import AmbassadorData from '../assets/ambassadors.json' @@ -32,11 +32,10 @@ const getMapOfAmbassadorWithDiacritics = (): Map => { export default function useChatCommand() { const [command, setCommand] = useState() - const ambassadorNames = AmbassadorData.map((ambassador) => ambassador.name.split(' ')[0].toLowerCase()) + const [ambassadorNames] = useState(AmbassadorData.map((ambassador) => ambassador.name.split(' ')[0].toLowerCase())) + const [diacriticsMap] = useState>(getMapOfAmbassadorWithDiacritics()) - const diacriticsMap: Map = getMapOfAmbassadorWithDiacritics() - - const client = new tmi.Client({ + const [client] = useState(new tmi.Client({ connection: { secure: true, reconnect: true @@ -46,15 +45,9 @@ export default function useChatCommand() { 'Maya', 'AlveusSanctuary' ] - }) - - useEffect(() => { - client.on('message', messageHandler) - client.on('connected', connectedHandler) - client.connect() - }, []) + })) - const messageHandler = (channel: string, tags: ChatUserstate, msg: string, self: boolean) => { + const messageHandler = useCallback((channel: string, tags: ChatUserstate, msg: string, self: boolean) => { //ignore if user is not a moderator or broadcaster or if the user is not AbdullahMorrison if (!tags.mod && !tags.badges?.broadcaster && tags.username !== 'abdullahmorrison') return // Ignore echoed messages (messages sent by the bot) and messages that don't start with '!' @@ -66,10 +59,17 @@ export default function useChatCommand() { } else if (diacriticsMap.get(commandName.slice(1))) { // Check if a user typed a name without diacritics (Ex: !jalapeno should be !Jalapeño) setCommand("!"+diacriticsMap.get(commandName.slice(1))) } - } - const connectedHandler = () => { + }, [ambassadorNames, diacriticsMap]) + + const connectedHandler = useCallback(() => { console.log('*Twitch extension is connected to chat*') - } + }, []) + + useEffect(() => { + client.on('message', messageHandler) + client.on('connected', connectedHandler) + client.connect() + }, [client, messageHandler, connectedHandler]) return command }