diff --git a/package.json b/package.json
index 97e5ef6423..4fe7a149cf 100644
--- a/package.json
+++ b/package.json
@@ -17,14 +17,6 @@
"build-storyteller": "lerna exec --scope @storyteller/storyteller.io -- yarn build",
"start-storyteller": "PORT=7008 BROWSER=none lerna exec --scope @storyteller/storyteller.io -- yarn start",
"start-storyteller-w": "set PORT=7008 && lerna exec --scope @storyteller/storyteller.io -- yarn start-w",
- "build-powerstream": "lerna exec --scope @storyteller/power.stream -- yarn build",
- "start-powerstream": "PORT=7001 BROWSER=none lerna exec --scope @storyteller/power.stream -- yarn start",
- "start-powerstream-w": "set PORT=7001 && lerna exec --scope @storyteller/power.stream -- yarn start-w",
- "build-powerstream-dashboard": "lerna exec --scope @storyteller/power.stream-dashboard -- yarn build",
- "start-powerstream-dashboard": "PORT=7002 BROWSER=none lerna exec --scope @storyteller/power.stream-dashboard -- yarn start",
- "start-powerstream-dashboard-w": "set PORT=7002 && lerna exec --scope @storyteller/power.stream-dashboard -- yarn start-w",
- "build-powerstream-obs": "lerna exec --scope @storyteller/power.stream-obs -- yarn build",
- "start-powerstream-obs": "PORT=7007 BROWSER=none lerna exec --scope @storyteller/power.stream-obs -- yarn start",
"build-trumped": "lerna exec --scope @storyteller/trumped.com -- yarn build",
"start-trumped": "PORT=7000 BROWSER=none lerna exec --scope @storyteller/trumped.com -- yarn start",
"start-trumped-80": "PORT=80 BROWSER=none lerna exec --scope @storyteller/trumped.com -- yarn start",
diff --git a/packages/dashboard.power.stream/.gitignore b/packages/dashboard.power.stream/.gitignore
deleted file mode 100644
index 4d29575de8..0000000000
--- a/packages/dashboard.power.stream/.gitignore
+++ /dev/null
@@ -1,23 +0,0 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-/.pnp
-.pnp.js
-
-# testing
-/coverage
-
-# production
-/build
-
-# misc
-.DS_Store
-.env.local
-.env.development.local
-.env.test.local
-.env.production.local
-
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
diff --git a/packages/dashboard.power.stream/README.md b/packages/dashboard.power.stream/README.md
deleted file mode 100644
index b87cb00449..0000000000
--- a/packages/dashboard.power.stream/README.md
+++ /dev/null
@@ -1,46 +0,0 @@
-# Getting Started with Create React App
-
-This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
-
-## Available Scripts
-
-In the project directory, you can run:
-
-### `npm start`
-
-Runs the app in the development mode.\
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
-
-The page will reload if you make edits.\
-You will also see any lint errors in the console.
-
-### `npm test`
-
-Launches the test runner in the interactive watch mode.\
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
-
-### `npm run build`
-
-Builds the app for production to the `build` folder.\
-It correctly bundles React in production mode and optimizes the build for the best performance.
-
-The build is minified and the filenames include the hashes.\
-Your app is ready to be deployed!
-
-See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
-
-### `npm run eject`
-
-**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
-
-If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
-
-Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
-
-You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
-
-## Learn More
-
-You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
-
-To learn React, check out the [React documentation](https://reactjs.org/).
diff --git a/packages/dashboard.power.stream/craco.config.js b/packages/dashboard.power.stream/craco.config.js
deleted file mode 100644
index c17acbd833..0000000000
--- a/packages/dashboard.power.stream/craco.config.js
+++ /dev/null
@@ -1,24 +0,0 @@
-const path = require("path");
-const { getLoader, loaderByName } = require("@craco/craco");
-
-const packages = [];
-packages.push(path.join(__dirname, "../components"));
-
-module.exports = {
- webpack: {
- configure: (webpackConfig, arg) => {
- const { isFound, match } = getLoader(
- webpackConfig,
- loaderByName("babel-loader")
- );
- if (isFound) {
- const include = Array.isArray(match.loader.include)
- ? match.loader.include
- : [match.loader.include];
-
- match.loader.include = include.concat(packages);
- }
- return webpackConfig;
- },
- },
-};
diff --git a/packages/dashboard.power.stream/package.json b/packages/dashboard.power.stream/package.json
deleted file mode 100644
index e75f620594..0000000000
--- a/packages/dashboard.power.stream/package.json
+++ /dev/null
@@ -1,54 +0,0 @@
-{
- "name": "@storyteller/dashboard.power.stream",
- "version": "0.1.0",
- "private": true,
- "dependencies": {
- "@craco/craco": "^6.4.3",
- "@fortawesome/fontawesome-svg-core": "^6.2.1",
- "@fortawesome/free-brands-svg-icons": "^6.2.1",
- "@fortawesome/free-solid-svg-icons": "^6.2.1",
- "@fortawesome/react-fontawesome": "^0.2.0",
- "@storyteller/components": "0.1.0",
- "@testing-library/jest-dom": "^5.16.1",
- "@testing-library/react": "^9.5.0",
- "@testing-library/user-event": "^13.5.0",
- "@types/jest": "^27.4.0",
- "@types/node": "^16.11.21",
- "@types/react": "^16.9.36",
- "@types/react-dom": "^16.9.8",
- "bulma": "^0.9.0",
- "craco": "^0.0.3",
- "react": "^16.13.1",
- "react-autocomplete": "^1.8.1",
- "react-dom": "^16.13.1",
- "react-router-dom": "^5.2.0",
- "react-scripts": "5.0.0",
- "typescript": "^4.5.5",
- "web-vitals": "^2.1.4"
- },
- "scripts": {
- "start": "craco start",
- "start-w": "set BROWSER=none && craco start",
- "build": "craco build",
- "test": "craco test",
- "eject": "craco eject"
- },
- "eslintConfig": {
- "extends": [
- "react-app",
- "react-app/jest"
- ]
- },
- "browserslist": {
- "production": [
- ">0.2%",
- "not dead",
- "not op_mini all"
- ],
- "development": [
- "last 1 chrome version",
- "last 1 firefox version",
- "last 1 safari version"
- ]
- }
-}
diff --git a/packages/dashboard.power.stream/public/assets/storyteller-logo.png b/packages/dashboard.power.stream/public/assets/storyteller-logo.png
deleted file mode 100644
index 6504997d28..0000000000
Binary files a/packages/dashboard.power.stream/public/assets/storyteller-logo.png and /dev/null differ
diff --git a/packages/dashboard.power.stream/public/favicon.ico b/packages/dashboard.power.stream/public/favicon.ico
deleted file mode 100644
index a11777cc47..0000000000
Binary files a/packages/dashboard.power.stream/public/favicon.ico and /dev/null differ
diff --git a/packages/dashboard.power.stream/public/index.html b/packages/dashboard.power.stream/public/index.html
deleted file mode 100644
index 235ed26432..0000000000
--- a/packages/dashboard.power.stream/public/index.html
+++ /dev/null
@@ -1,108 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Storyteller. Stream tech.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- You need to enable JavaScript to run this app.
-
-
-
-
-
diff --git a/packages/dashboard.power.stream/public/logo192.png b/packages/dashboard.power.stream/public/logo192.png
deleted file mode 100644
index fc44b0a379..0000000000
Binary files a/packages/dashboard.power.stream/public/logo192.png and /dev/null differ
diff --git a/packages/dashboard.power.stream/public/logo512.png b/packages/dashboard.power.stream/public/logo512.png
deleted file mode 100644
index a4e47a6545..0000000000
Binary files a/packages/dashboard.power.stream/public/logo512.png and /dev/null differ
diff --git a/packages/dashboard.power.stream/public/manifest.json b/packages/dashboard.power.stream/public/manifest.json
deleted file mode 100644
index 4a14ea6044..0000000000
--- a/packages/dashboard.power.stream/public/manifest.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "short_name": "Storyteller",
- "name": "Stream tech",
- "icons": [
- {
- "src": "favicon.ico",
- "sizes": "64x64 32x32 24x24 16x16",
- "type": "image/x-icon"
- },
- {
- "src": "logo192.png",
- "type": "image/png",
- "sizes": "192x192"
- },
- {
- "src": "logo512.png",
- "type": "image/png",
- "sizes": "512x512"
- }
- ],
- "start_url": ".",
- "display": "standalone",
- "theme_color": "#000000",
- "background_color": "#ffffff"
-}
diff --git a/packages/dashboard.power.stream/public/mascot/kitsune_pose5_black_2000.webp b/packages/dashboard.power.stream/public/mascot/kitsune_pose5_black_2000.webp
deleted file mode 100644
index e24d31683a..0000000000
Binary files a/packages/dashboard.power.stream/public/mascot/kitsune_pose5_black_2000.webp and /dev/null differ
diff --git a/packages/dashboard.power.stream/public/mascot/kitsune_pose6_black_2000.webp b/packages/dashboard.power.stream/public/mascot/kitsune_pose6_black_2000.webp
deleted file mode 100644
index eaadc8d66a..0000000000
Binary files a/packages/dashboard.power.stream/public/mascot/kitsune_pose6_black_2000.webp and /dev/null differ
diff --git a/packages/dashboard.power.stream/public/mascot/kitsune_pose8_black_2000.webp b/packages/dashboard.power.stream/public/mascot/kitsune_pose8_black_2000.webp
deleted file mode 100644
index 9928cda0b8..0000000000
Binary files a/packages/dashboard.power.stream/public/mascot/kitsune_pose8_black_2000.webp and /dev/null differ
diff --git a/packages/dashboard.power.stream/public/obs-tutorial/obs-step1.webp b/packages/dashboard.power.stream/public/obs-tutorial/obs-step1.webp
deleted file mode 100644
index 821cc7eac7..0000000000
Binary files a/packages/dashboard.power.stream/public/obs-tutorial/obs-step1.webp and /dev/null differ
diff --git a/packages/dashboard.power.stream/public/obs-tutorial/obs-step2.webp b/packages/dashboard.power.stream/public/obs-tutorial/obs-step2.webp
deleted file mode 100644
index f726ee2660..0000000000
Binary files a/packages/dashboard.power.stream/public/obs-tutorial/obs-step2.webp and /dev/null differ
diff --git a/packages/dashboard.power.stream/public/obs-tutorial/obs-step3.webp b/packages/dashboard.power.stream/public/obs-tutorial/obs-step3.webp
deleted file mode 100644
index ff92432f9f..0000000000
Binary files a/packages/dashboard.power.stream/public/obs-tutorial/obs-step3.webp and /dev/null differ
diff --git a/packages/dashboard.power.stream/public/robots.txt b/packages/dashboard.power.stream/public/robots.txt
deleted file mode 100644
index e9e57dc4d4..0000000000
--- a/packages/dashboard.power.stream/public/robots.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-# https://www.robotstxt.org/robotstxt.html
-User-agent: *
-Disallow:
diff --git a/packages/dashboard.power.stream/public/storyteller-nav-logo-mascot-pink.png b/packages/dashboard.power.stream/public/storyteller-nav-logo-mascot-pink.png
deleted file mode 100644
index 0057c9bf51..0000000000
Binary files a/packages/dashboard.power.stream/public/storyteller-nav-logo-mascot-pink.png and /dev/null differ
diff --git a/packages/dashboard.power.stream/src/App.css b/packages/dashboard.power.stream/src/App.css
deleted file mode 100644
index 74b5e05345..0000000000
--- a/packages/dashboard.power.stream/src/App.css
+++ /dev/null
@@ -1,38 +0,0 @@
-.App {
- text-align: center;
-}
-
-.App-logo {
- height: 40vmin;
- pointer-events: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .App-logo {
- animation: App-logo-spin infinite 20s linear;
- }
-}
-
-.App-header {
- background-color: #282c34;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: calc(10px + 2vmin);
- color: white;
-}
-
-.App-link {
- color: #61dafb;
-}
-
-@keyframes App-logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
diff --git a/packages/dashboard.power.stream/src/App.scss b/packages/dashboard.power.stream/src/App.scss
deleted file mode 100644
index 23129940a4..0000000000
--- a/packages/dashboard.power.stream/src/App.scss
+++ /dev/null
@@ -1,485 +0,0 @@
-//@import-normalize; /* bring in normalize.css styles */
-
-// $page-width: 700px;
-$primary: #fc6b68;
-$white: #fff;
-$light-text-color: #777;
-$button-text-color: #666;
-
-$body-font-family: "Fira Sans", serif;
-$nav-font-family: "Fira Sans", sans-serif;
-$footer-font-family: "Fira Sans Condensed", sans-serif;
-$textarea-font-family: "Fira Sans", sans-serif;
-$headings-font-family: "Merriweather", serif;
-
-// body {
-// font-family: $body-font-family;
-// font-weight: 300; // For Fira Sans
-// }
-
-// .mainwrap {
-// max-width: 1000px;
-// margin: auto;
-// }
-
-.git-sha {
- color: #999;
- font-size: 0.8rem;
-}
-
-html {
- height: 100%;
-}
-
-body {
- height: 100%;
- font-family: "Fira Sans", sans-serif;
- color: #fff;
-}
-
-hr {
- border: 0;
- border-top: 1px solid rgba(255, 255, 255, 0.1);
-}
-
-button:focus,
-button:active,
-button:hover,
-label:focus,
-.btn:active,
-.btn.focus {
- outline: 0px !important;
- -webkit-appearance: none;
- box-shadow: none !important;
-}
-
-a {
- font-weight: 400;
- color: #fc6b68;
- text-decoration: none;
- cursor: pointer;
- transition: 0.1s;
-}
-
-.text-link {
- color: #fc6b68;
-}
-
-a:hover {
- color: #ff8d8b;
-}
-
-h1 {
- font-weight: 700;
-}
-
-h3 {
- color: rgba(255, 255, 255, 0.8);
-}
-
-p {
- font-weight: 300;
- margin-bottom: 0;
- color: rgba(255, 255, 255, 0.8);
-}
-
-.pre-heading {
- font-size: 18px;
- color: #fc6b68;
- text-transform: uppercase;
-}
-
-.rotateimg180 {
- -webkit-transform: rotate(180deg);
- -moz-transform: rotate(180deg);
- -ms-transform: rotate(180deg);
- -o-transform: rotate(180deg);
- transform: rotate(180deg);
-}
-
-.container {
- max-width: 1200px;
-}
-
-.container,
-.container-fluid,
-.container-lg,
-.container-md,
-.container-sm,
-.container-xl,
-.container-xxl {
- width: 100%;
- padding-right: var(--bs-gutter-x, 1rem);
- padding-left: var(--bs-gutter-x, 1rem);
-}
-
-.noselect {
- -webkit-touch-callout: none; /* iOS Safari */
- -webkit-user-select: none; /* Safari */
- -khtml-user-select: none; /* Konqueror HTML */
- -moz-user-select: none; /* Old versions of Firefox */
- -ms-user-select: none; /* Internet Explorer/Edge */
- user-select: none; /* Non-prefixed version, currently
- supported by Chrome, Edge, Opera and Firefox */
-}
-
-/*margin top/bottom for panels*/
-.py-6 {
- padding-top: 1.6rem !important;
- padding-bottom: 0rem !important;
-}
-
-.navbar {
- z-index: 100;
-}
-
-.navbar-main {
- z-index: 100;
- width: 100%;
- transition: all 0.1s;
- border-color: rgba(255, 255, 255, 0.07) !important;
- border-bottom: 0px solid;
- padding-top: 10px;
- padding-bottom: 10px;
-}
-
-.navbar-fixed {
- position: fixed;
-}
-
-.navbar-main.scrolled {
- background-color: #2e222c;
- border-color: rgba(255, 255, 255, 0.07) !important;
- border-bottom: 1px solid;
-}
-
-.navbar-dark .navbar-toggler {
- border: none;
-}
-
-// .nav-bg-dark {
-// background-color: #17101c !important;
-// }
-
-.nav-pills .nav-link.active,
-.nav-pills .show > .nav-link {
- color: #fff;
- background-color: $primary;
-
- &:hover {
- background-color: $primary;
- }
-}
-
-.nav-pills .nav-link {
- border-radius: 5px;
- color: rgba(#fff, 0.7);
- transition: 0.2s;
- padding: 12px 14px !important;
-
- &:hover {
- background-color: rgba($white, 0.1);
- }
-}
-
-.icon {
- content: "";
- width: 20px;
- margin-right: 16px;
-}
-
-.p-page-top {
- padding-top: 110px;
-}
-
-.p-page {
- padding-top: 100px;
- padding-bottom: 100px;
-}
-
-.offcanvas {
- width: 100vw;
- height: 100%;
-}
-
-.offcanvas-header {
- padding-top: 10px;
- padding-bottom: 10px;
-}
-
-.nav-link {
- font-size: 15px;
- font-weight: 500;
- padding-left: 16px !important;
- padding-right: 16px !important;
-}
-
-.navbar-brand {
- padding-top: 0;
-}
-
-.lead {
- font-size: 18px;
-}
-
-.parent {
- position: relative;
-}
-
-span.word {
- position: relative;
- z-index: 1;
-}
-
-.fw-semibold {
- font-weight: 500;
-}
-
-.fs-7 {
- font-size: 14px;
-}
-
-span.word:after {
- background-color: #fc6b68;
- content: " ";
- height: 15%;
- position: absolute;
- left: 0;
- margin-left: 0rem;
- top: 74%;
- width: calc(100%);
- z-index: -1;
-}
-
-.bg-gradient {
- background: rgb(13, 16, 25) !important;
- background: linear-gradient(
- 325deg,
- rgba(13, 16, 25, 1) 0%,
- rgba(42, 21, 34, 1) 55%,
- rgba(16, 14, 26, 1) 100%
- ) !important;
- position: relative;
- overflow: hidden;
- min-height: 100%;
-}
-
-.bg-hero {
- background: rgb(18, 14, 26);
- background: linear-gradient(
- 180deg,
- rgba(18, 14, 26, 1) 0%,
- rgba(48, 23, 32, 1) 100%
- );
-}
-
-.btn {
- border-radius: 5px;
- outline: none;
- font-size: 15px;
- font-weight: 600;
- padding: 10px 24px;
- z-index: 1;
-}
-
-.btn-primary,
-.btn-primary:active,
-.btn-primary:focus {
- background-color: #fc6b68;
- border: 2.5px solid;
- border-color: #fc6b68;
- transition: 0.2s;
-}
-
-.btn-primary:hover {
- background-color: #ff8d8b;
- border: 2.5px solid;
- border-color: #ff8d8b;
-}
-
-.btn-secondary,
-.btn-secondary:focus,
-.btn-secondary:active {
- background-color: #fff;
- color: #443a40;
- border: 2.5px solid;
- border-color: #fff;
- transition: 0.2s;
-}
-
-.btn-secondary:hover {
- background-color: #ccc;
- color: #443a40;
- border: 2.5px solid;
- border-color: #ccc;
-}
-
-.btn-destructive,
-.btn-destructive:active,
-.btn-destructive:focus {
- background-color: #fff;
- color: #fc6b68;
- border: 2.5px solid;
- border-color: #fff;
- transition: 0.2s;
-}
-
-.btn-destructive:hover {
- background-color: #fdcfce;
- color: #fc6b68;
- border: 2.5px solid;
- border-color: #fdcfce;
-}
-
-.btn-hero {
- padding-left: 35px !important;
- padding-right: 35px !important;
-}
-
-.btn-small {
- padding: 0rem;
-}
-
-.nav-login {
- font-size: 15px;
- font-weight: 500;
- color: #fff;
- text-decoration: none;
- transition: 0.1s;
-}
-
-.nav-login:hover {
- color: rgba(255, 255, 255, 0.6);
- text-decoration: none;
- transition: 0.1s;
-}
-
-.hero-section {
- height: 100vh;
- width: 100%;
- min-height: 600px;
- max-height: 1080px;
- text-align: center;
- overflow: hidden;
- /* background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='1920' height='300' preserveAspectRatio='none' viewBox='0 0 1920 300'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1327%26quot%3b)' fill='none'%3e%3cpath d='M486 46L1358 45' stroke-width='10' stroke='url(%23SvgjsLinearGradient1328)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M312 288L-212 287' stroke-width='6' stroke='url(%23SvgjsLinearGradient1328)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M1583 12L2334 11' stroke-width='8' stroke='url(%23SvgjsLinearGradient1329)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1219 65L1985 64' stroke-width='10' stroke='url(%23SvgjsLinearGradient1329)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1877 57L2438 56' stroke-width='8' stroke='url(%23SvgjsLinearGradient1328)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M347 173L-797 172' stroke-width='10' stroke='url(%23SvgjsLinearGradient1328)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M762 234L-332 233' stroke-width='6' stroke='url(%23SvgjsLinearGradient1329)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M621 133L2031 132' stroke-width='8' stroke='url(%23SvgjsLinearGradient1328)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1327'%3e%3crect width='1920' height='300' fill='white'%3e%3c/rect%3e%3c/mask%3e%3clinearGradient x1='0%25' y1='0%25' x2='100%25' y2='0%25' id='SvgjsLinearGradient1328'%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0.05)' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3clinearGradient x1='100%25' y1='0%25' x2='0%25' y2='0%25' id='SvgjsLinearGradient1329'%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0.05)' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e"); */
- background-position: 0 120px;
- background-repeat: no-repeat;
- background-size: 100%;
-}
-
-.hero-inner {
- padding-top: 100px;
- height: 100%;
-}
-
-.hero-text {
- max-width: 800px;
-}
-
-.hero-img-section {
- display: flex;
- position: relative;
- justify-content: center;
- height: 100%;
- width: 100%;
- pointer-events: none;
-}
-
-.hero-img {
- position: absolute;
- bottom: 0;
- max-height: 110%;
- max-width: 140%;
- z-index: 1;
-}
-
-.hero-floor {
- position: absolute;
- bottom: 0;
- width: 100%;
- height: 100%;
- max-height: 100%;
- min-height: 50%;
-}
-
-@keyframes animatedBackground {
- from {
- background-position: 0px 0;
- }
- to {
- background-position: -1448px 0;
- }
-}
-
-#hero-audio-wave {
- position: absolute;
- bottom: 0;
- margin-bottom: 12rem;
- opacity: 15%;
- width: 115%;
- min-height: 150px;
- // background-image: url("/assets/hero-audio-wave.png");
- background-repeat: repeat-x;
- animation: animatedBackground 20s linear infinite;
- mix-blend-mode: screen;
-}
-
-.panel {
- position: relative;
- width: 100%;
- background-color: rgba(255, 255, 255, 0.1);
- border-color: #ffffff12 !important;
- border: 2px solid;
- border-radius: 8px;
- backdrop-filter: blur(10px);
- -webkit-backdrop-filter: blur(10px);
- -webkit-box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
- box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
-
- &.inner {
- background-color: rgba(255, 255, 255, 0.01);
- box-shadow: none;
- }
-}
-
-.panel-outline {
- border-color: rgba(252, 107, 104, 0.5) !important;
-}
-
-.hero-floating-panel {
- text-align: left;
- position: absolute;
- bottom: 180px;
- z-index: 3;
- width: auto;
- height: auto;
- padding: 1rem 1.25rem;
- pointer-events: none;
-}
-
-.hero-floating-panel-left {
- margin-left: -800px;
- margin-bottom: 25px;
-}
-
-.hero-floating-panel-right {
- margin-right: -760px;
- margin-bottom: 100px;
-}
-
-.hero-floating-panel-text {
- font-size: 14px;
- color: rgba(255, 255, 255, 0.8);
-}
-
-.panel-title {
- position: static;
- text-align: center;
- text-shadow: 0px 3px 26px #00000060;
- margin-top: -35px;
-}
-
-.section {
- padding-top: 4rem;
- padding-bottom: 4rem;
-}
diff --git a/packages/dashboard.power.stream/src/App.test.tsx b/packages/dashboard.power.stream/src/App.test.tsx
deleted file mode 100644
index bfe47f2987..0000000000
--- a/packages/dashboard.power.stream/src/App.test.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-//import { render, screen } from '@testing-library/react';
-import App from './App';
-
-test('renders learn react link', () => {
- // NB(2022-02-01): This broke when converting to a monorepo.
- // I hadn't been using the tests anyhow.
- //render( );
- //const linkElement = screen.getByText(/learn react/i);
- //expect(linkElement).toBeInTheDocument();
-});
diff --git a/packages/dashboard.power.stream/src/App.tsx b/packages/dashboard.power.stream/src/App.tsx
deleted file mode 100644
index 4fe7fed01c..0000000000
--- a/packages/dashboard.power.stream/src/App.tsx
+++ /dev/null
@@ -1,209 +0,0 @@
-// import 'bulma/css/bulma.css'
-import "./App.scss";
-
-import React from "react";
-import { BrowserRouter, Route, Switch } from "react-router-dom";
-import { GitSha } from "@storyteller/components/src/elements/GitSha";
-import {
- ListTtsModels,
- TtsModelListItem,
-} from "@storyteller/components/src/api/tts/ListTtsModels";
-import StreamPage from "./pages/stream/StreamPage";
-import i18n from "i18next";
-import { ComingSoonPage } from "./pages/coming-soon/ComingSoonPage";
-import { LoginPage } from "./pages/login/LoginPage";
-import { SignupPage } from "./pages/signup/SignupPage";
-import { TtsConfigsIndexPage } from "./pages/tts_configs/TtsConfigsIndexPage";
-import {
- DetectLocale,
- DetectLocaleIsOk,
-} from "@storyteller/components/src/api/locale/DetectLocale";
-import { IndexPage } from "./pages/index/IndexPage";
-import { STORYTELLER_MERGED_TRANSLATIONS } from "./_i18n/StorytellerTranslations";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import { TopNav } from "./layout/TopNav";
-import { initReactI18next } from "react-i18next";
-import { TtsConfigsCreateRulePage } from "./pages/tts_configs/TtsConfigsCreateRulePage";
-import { TtsConfigsDeleteRulePage } from "./pages/tts_configs/TtsConfigsDeleteRulePage";
-import { TtsConfigsEditRulePage } from "./pages/tts_configs/TtsConfigsEditRulePage";
-import { TtsConfigsReorderPage } from "./pages/tts_configs/TtsConfigsReorderPage";
-import { ObsConfigsPage } from "./pages/obs_configs/ObsConfigsPage";
-import { Dashboard } from "./pages/dashboard/Dashboard";
-
-i18n
- .use(initReactI18next) // passes i18n down to react-i18next
- .init({
- // the translations
- // (tip move them in a JSON file and import them,
- // or even better, manage them via a UI: https://react.i18next.com/guides/multiple-translation-files#manage-your-translations-with-a-management-gui)
- resources: STORYTELLER_MERGED_TRANSLATIONS,
- //lng: 'en', // if you're using a language detector, do not define the lng option
- fallbackLng: "en",
-
- interpolation: {
- escapeValue: false, // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
- },
- });
-
-interface Props {}
-
-interface State {
- sessionWrapper: SessionWrapper;
- allTtsModels: TtsModelListItem[];
- allTtsModelsByToken: Map;
-}
-
-// Root element is a non-functional component for easier global lifecycle management
-class App extends React.Component {
- constructor(props: Props) {
- super(props);
- this.state = {
- sessionWrapper: SessionWrapper.emptySession(),
- allTtsModels: [],
- allTtsModelsByToken: new Map(),
- };
- }
-
- async componentDidMount() {
- await this.querySession();
- await this.queryLanguage();
- await this.queryTtsModels();
- setInterval(async () => {
- await this.querySession();
- }, 60000);
- }
-
- querySession = async () => {
- const sessionWrapper = await SessionWrapper.lookupSession();
- this.setState({
- sessionWrapper: sessionWrapper,
- });
- };
-
- queryLanguage = async () => {
- let locale = await DetectLocale();
- if (DetectLocaleIsOk(locale)) {
- // TODO
- }
- };
-
- queryTtsModels = async () => {
- if (this.state.allTtsModels.length > 0) {
- return; // Already queried.
- }
- const models = await ListTtsModels();
- if (models) {
- let allTtsModelsByToken = new Map();
-
- models.forEach((item) => {
- allTtsModelsByToken.set(item.model_token, item);
- });
-
- //dynamicallyCategorizeModels(models);
- this.setState({
- allTtsModels: models,
- allTtsModelsByToken: allTtsModelsByToken,
- });
- //if (!maybeSelectedTtsModel && models.length > 0) {
- // let model = models[0];
- // const featuredModels = models.filter(m => m.is_front_page_featured);
- // if (featuredModels.length > 0) {
- // // Random featured model
- // model = featuredModels[
- // Math.floor(Math.random()*featuredModels.length)
- // ];
- // }
- // setMaybeSelectedTtsModel(model);
- //}
- }
- };
-
- public render() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/*
*/}
-
-
-
- );
- }
-}
-
-export default App;
diff --git a/packages/dashboard.power.stream/src/_i18n/StorytellerTranslations.ts b/packages/dashboard.power.stream/src/_i18n/StorytellerTranslations.ts
deleted file mode 100644
index 044cd67185..0000000000
--- a/packages/dashboard.power.stream/src/_i18n/StorytellerTranslations.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import { COMMON_TRANSLATIONS, MergeDeepDictionary } from '@storyteller/components/src/_i18n/CommonTranslations';
-
-// Use \u{00a0} = character literal
-
-const STORYTELLER_TRANSLATIONS : any = {
- // English: 46.6% Twitch (#1), 30+% FakeYou (#1)
- en: {
- translation: {
- commonUi: {
- },
- pages: {
- },
- },
- },
- // Spanish: 10.5% Twitch (#2), 20+% FakeYou (#2)
- es: {
- translation: {
- commonUi: {
- },
- pages: {
- },
- },
- },
-
- // ---------- OTHER LANGUAGES ----------
-
- // German: 6.5% Twitch (#4)
- de: {
- translation: {
- }
- },
- // French: 5.6% Twitch (#6)
- fr: {
- translation: {
- }
- },
- // Indonesian 2% FakeYou
- id: {
- translation: {
- }
- },
- // Japanese: 2.5% Twitch (#8)
- ja: {
- translation: {
- },
- },
- // Korean: 5.4% Twitch (#7)
- ko: {
- translation: {
- }
- },
- // Portuguese: 6.2% Twitch (#5)
- pt: {
- translation: {
- },
- },
- // 6.5% Twitch (#3)
- ru: {
- translation: {
- }
- },
- // Turkish: 4% FakeYou (#3)
- tr: {
- translation: {
- }
- },
-}
-
-// TODO: Type these as i18next dictionaries
-const STORYTELLER_MERGED_TRANSLATIONS : any = MergeDeepDictionary(STORYTELLER_TRANSLATIONS, COMMON_TRANSLATIONS);
-
-export { STORYTELLER_MERGED_TRANSLATIONS }
diff --git a/packages/dashboard.power.stream/src/index.css b/packages/dashboard.power.stream/src/index.css
deleted file mode 100644
index d942c42f27..0000000000
--- a/packages/dashboard.power.stream/src/index.css
+++ /dev/null
@@ -1,13 +0,0 @@
-body {
- margin: 0;
- /*font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
- sans-serif;*/
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
- monospace;
-}
diff --git a/packages/dashboard.power.stream/src/index.tsx b/packages/dashboard.power.stream/src/index.tsx
deleted file mode 100644
index ef2edf8ea3..0000000000
--- a/packages/dashboard.power.stream/src/index.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import './index.css';
-import App from './App';
-import reportWebVitals from './reportWebVitals';
-
-ReactDOM.render(
-
-
- ,
- document.getElementById('root')
-);
-
-// If you want to start measuring performance in your app, pass a function
-// to log results (for example: reportWebVitals(console.log))
-// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals();
diff --git a/packages/dashboard.power.stream/src/layout/TopNav.tsx b/packages/dashboard.power.stream/src/layout/TopNav.tsx
deleted file mode 100644
index 0e7fd99c11..0000000000
--- a/packages/dashboard.power.stream/src/layout/TopNav.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import React, { useState } from "react";
-import { Link } from "react-router-dom";
-import { TopNavSessionButton } from "./TopNavSessionButton";
-
-interface Props {
- sessionWrapper: SessionWrapper;
- querySessionCallback: () => void;
-}
-
-function TopNav(props: Props) {
- return (
- <>
-
-
-
-
-
-
- Navbar toggle
-
-
-
-
-
- >
- );
-}
-
-export { TopNav };
diff --git a/packages/dashboard.power.stream/src/layout/TopNavSessionButton.tsx b/packages/dashboard.power.stream/src/layout/TopNavSessionButton.tsx
deleted file mode 100644
index 26872802ab..0000000000
--- a/packages/dashboard.power.stream/src/layout/TopNavSessionButton.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import React from 'react';
-import { Link, useHistory } from 'react-router-dom';
-import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
-import { Logout } from '@storyteller/components/src/api/session/Logout';
-import { t } from 'i18next';
-import { Gravatar } from '@storyteller/components/src/elements/Gravatar';
-
-interface Props {
- sessionWrapper: SessionWrapper,
- enableAlpha: boolean,
- querySessionAction: () => void,
- closeHamburgerAction: () => void,
-}
-
-function TopNavSessionButton(props: Props) {
- let history = useHistory();
-
- if (!props.enableAlpha) {
- return
- }
-
- const logoutHandler = async () => {
- await Logout();
- props.querySessionAction();
- history.push('/');
- }
-
- let loggedIn = props.sessionWrapper.isLoggedIn();
- let displayName = props.sessionWrapper.getDisplayName();
- let gravatarHash = props.sessionWrapper.getEmailGravatarHash();
- let gravatar = ;
-
- if (displayName === undefined) {
- displayName = 'My Account';
- }
-
- if (gravatarHash !== undefined) {
- gravatar =
- }
-
- let sessionLink = ;
- let logoutLink = ;
-
- if (loggedIn) {
- let url = `https://fakeyou.com/profile/${displayName}`;
- sessionLink = (
- props.closeHamburgerAction()}
- className="button is-alert is-inverted is-pulled-right"
- > {gravatar} {displayName} (FakeYou)
- );
- logoutLink = {
- await logoutHandler();
- props.closeHamburgerAction();
- }}
- >{t('common.logout')} ;
- } else {
- sessionLink = (
- props.closeHamburgerAction()}
- >{t('common.signUpLogin')}
- );
- }
-
- return (
-
- {logoutLink}
- {sessionLink}
-
- );
-}
-
-export { TopNavSessionButton };
diff --git a/packages/dashboard.power.stream/src/logo.svg b/packages/dashboard.power.stream/src/logo.svg
deleted file mode 100644
index 9dfc1c058c..0000000000
--- a/packages/dashboard.power.stream/src/logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/dashboard.power.stream/src/pages/coming-soon/ComingSoonPage.tsx b/packages/dashboard.power.stream/src/pages/coming-soon/ComingSoonPage.tsx
deleted file mode 100644
index 664ad64c51..0000000000
--- a/packages/dashboard.power.stream/src/pages/coming-soon/ComingSoonPage.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import React from 'react';
-
-function ComingSoonPage() {
- return (
-
-
-
-
- Coming Soon
-
-
- We're almost done!
-
-
-
We'll be launching the following features shortly:
-
-
- Audience rewards
-
- Stream TTS
- Deepfake Emotes (TTS + Green Screen W2L)
-
-
- Voice and video
-
- Real time voice conversion (change your voice to someone else's)
- VoxelCam 3D volumetric camera (a webcam, but 3D, and directly injectable
- into games)
-
-
- Big Secret Project
-
-
- {/*Storyteller Engine
-
- This is a high quality cinematic game engine.
- You don't need to own an expensive computer. It can run in the cloud.
- Real time 3D motion capture of one or more streamers / actors.
- Think VTubing, but Star Wars quality.
- Dedicated control plane for camera, lighting, environmental control OPs.
- Embed rich content: games, browser panes, video feeds
- Let your audience control your world
-
- */}
-
-
- {/*
You'll be able to make films, Hollywood-quality streams, and so much more!
*/}
-
-
-
-
-
-
- )
-}
-
-export { ComingSoonPage }
\ No newline at end of file
diff --git a/packages/dashboard.power.stream/src/pages/dashboard/Dashboard.tsx b/packages/dashboard.power.stream/src/pages/dashboard/Dashboard.tsx
deleted file mode 100644
index ca4dec8f1a..0000000000
--- a/packages/dashboard.power.stream/src/pages/dashboard/Dashboard.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import React from "react";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import { Route, Switch } from "react-router-dom";
-import { Sidebar } from "./Sidebar";
-import { DashboardPage } from "./DashboardPage";
-import { Test } from "./Test";
-
-interface Props {
- sessionWrapper: SessionWrapper;
-}
-
-class Dashboard extends React.Component {
- // function Dashboard(props: Props) {
- // if (!props.sessionWrapper.isLoggedIn()) {
- // return (
- //
- //
- //
Must Log In
- //
- //
- // );
- // }
- public render() {
- return (
- <>
-
-
-
-
- {/* Sidebar */}
-
-
-
- {/* Page routes */}
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
- }
-}
-
-export { Dashboard };
diff --git a/packages/dashboard.power.stream/src/pages/dashboard/DashboardPage.tsx b/packages/dashboard.power.stream/src/pages/dashboard/DashboardPage.tsx
deleted file mode 100644
index 84f3c10cff..0000000000
--- a/packages/dashboard.power.stream/src/pages/dashboard/DashboardPage.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Feed } from "./components/Feed";
-
-function DashboardPage() {
- // if (!props.sessionWrapper.isLoggedIn()) {
- // return (
- //
- //
- //
Must Log In
- //
- //
- // );
- // }
-
- return (
-
-
- Dashboard
-
-
- Link your Twitch account
-
-
-
- );
-}
-
-export { DashboardPage };
diff --git a/packages/dashboard.power.stream/src/pages/dashboard/Sidebar.tsx b/packages/dashboard.power.stream/src/pages/dashboard/Sidebar.tsx
deleted file mode 100644
index ec045d9d51..0000000000
--- a/packages/dashboard.power.stream/src/pages/dashboard/Sidebar.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import {
- faCog,
- faHome,
- faVideo,
- faVolumeUp,
-} from "@fortawesome/free-solid-svg-icons";
-import { Link } from "react-router-dom";
-
-function Sidebar(props: Props) {
- // if (!props.sessionWrapper.isLoggedIn()) {
- // return (
- //
- //
- //
Must Log In
- //
- //
- // );
- // }
-
- return (
-
- );
-}
-
-export { Sidebar };
diff --git a/packages/dashboard.power.stream/src/pages/dashboard/Test.tsx b/packages/dashboard.power.stream/src/pages/dashboard/Test.tsx
deleted file mode 100644
index fa9799ac71..0000000000
--- a/packages/dashboard.power.stream/src/pages/dashboard/Test.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import {
- faCog,
- faHome,
- faVideo,
- faVolumeUp,
-} from "@fortawesome/free-solid-svg-icons";
-
-function Test(props: Props) {
- // if (!props.sessionWrapper.isLoggedIn()) {
- // return (
- //
- //
- //
Must Log In
- //
- //
- // );
- // }
-
- return (
-
-
- TESTY TEST
-
-
- Link your Twitch account
-
-
- );
-}
-
-export { Test };
diff --git a/packages/dashboard.power.stream/src/pages/dashboard/components/Feed.tsx b/packages/dashboard.power.stream/src/pages/dashboard/components/Feed.tsx
deleted file mode 100644
index 4d25c17518..0000000000
--- a/packages/dashboard.power.stream/src/pages/dashboard/components/Feed.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faUndo, faVolumeUp } from "@fortawesome/free-solid-svg-icons";
-
-function Feed() {
- // if (!props.sessionWrapper.isLoggedIn()) {
- // return (
- //
- //
- //
Must Log In
- //
- //
- // );
- // }
-
- return (
-
-
-
-
-
-
-
BFlatastic sent a TTS
-
Hello, this is a test message.
-
-
-
-
-
-
-
-
-
-
-
BFlatastic sent a TTS
-
Hello, this is a test message.
-
-
-
-
-
-
-
-
-
-
-
BFlatastic sent a TTS
-
Hello, this is a test message.
-
-
-
-
-
-
-
- );
-}
-
-export { Feed };
diff --git a/packages/dashboard.power.stream/src/pages/index/IndexPage.tsx b/packages/dashboard.power.stream/src/pages/index/IndexPage.tsx
deleted file mode 100644
index f2fe875390..0000000000
--- a/packages/dashboard.power.stream/src/pages/index/IndexPage.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import React from "react";
-import { FakeYouExternalLink } from "@storyteller/components/src/elements/FakeYouExternalLink";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import { LoggedInIndex } from "./subpages/LoggedInIndex";
-import { LoggedOutIndex } from "./subpages/LoggedOutIndex";
-
-interface Props {
- sessionWrapper: SessionWrapper;
-}
-
-function IndexPage(props: Props) {
- return (
-
-
-
-
-
- Text to Speech For Your Stream
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
- eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
-
-
- Sign Up Now
-
-
- Join Discord
-
-
-
-
-
-
-
-
-
-
-
Tracer (Overwatch)
-
- “Look out world! Tracer's here.”
-
-
-
-
Sonic the Hedgehog
-
- "This is what speed looks like."
-
-
-
-
-
-
- );
-}
-
-export { IndexPage };
diff --git a/packages/dashboard.power.stream/src/pages/index/subpages/LoggedInIndex.tsx b/packages/dashboard.power.stream/src/pages/index/subpages/LoggedInIndex.tsx
deleted file mode 100644
index f048e71c47..0000000000
--- a/packages/dashboard.power.stream/src/pages/index/subpages/LoggedInIndex.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { Link } from 'react-router-dom';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faCogs, faVideo } from '@fortawesome/free-solid-svg-icons';
-import { faDiscord, faTwitch } from '@fortawesome/free-brands-svg-icons';
-import { DiscordLink } from '@storyteller/components/src/elements/DiscordLink';
-import { StorytellerUrlConfig } from '@storyteller/components/src/urls/StorytellerUrlConfig';
-import { CheckTwitchOauth, CheckTwitchOauthIsError, CheckTwitchOauthIsOk } from '@storyteller/components/src/api/storyteller/twitch_oauth/CheckTwitchOauth';
-
-function LoggedInIndex() {
- const oauthUrl = new StorytellerUrlConfig().twitchOauthEnrollRedirect();
-
- const [oauthTokenFound, setOauthTokenFound] = useState(false);
-
- const checkTwitchOauth = useCallback(async () => {
- const result = await CheckTwitchOauth();
-
- if (CheckTwitchOauthIsOk(result)) {
- setOauthTokenFound(result.oauth_token_found);
- } else if (CheckTwitchOauthIsError(result)) {
- }
- }, []);
-
- useEffect(() => {
- checkTwitchOauth();
- }, [checkTwitchOauth])
-
- let linkText = <>Link Your Twitch > ;
-
- if (oauthTokenFound) {
- linkText = <>Re-link Your Twitch > ;
- }
-
-
- return (
-
-
-
-
-
- {linkText}
-
-
-
-
-
- Configure TTS
-
-
-
-
-
- Configure OBS
-
-
-
-
-
-
-
-
-
Join us in Discord!
-
- We'd like to chat with you.
-
-
- so that we know
- what you're thinking.
-
-
-
-
-
-
- )
-}
-
-export { LoggedInIndex }
\ No newline at end of file
diff --git a/packages/dashboard.power.stream/src/pages/index/subpages/LoggedOutIndex.tsx b/packages/dashboard.power.stream/src/pages/index/subpages/LoggedOutIndex.tsx
deleted file mode 100644
index 329c20bdd9..0000000000
--- a/packages/dashboard.power.stream/src/pages/index/subpages/LoggedOutIndex.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faRocket, faSignInAlt } from '@fortawesome/free-solid-svg-icons';
-
-function LoggedOutIndex() {
- return (
-
-
-
-
-
You'll need an account to continue…
- (If you have a FakeYou.com account, it'll work here too.)
-
-
- Sign Up
-
-
-
-
-
- Log In
-
-
-
-
-
-
-
- )
-}
-
-export { LoggedOutIndex }
\ No newline at end of file
diff --git a/packages/dashboard.power.stream/src/pages/login/LoginPage.tsx b/packages/dashboard.power.stream/src/pages/login/LoginPage.tsx
deleted file mode 100644
index 59d322adfc..0000000000
--- a/packages/dashboard.power.stream/src/pages/login/LoginPage.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import React, { useState } from 'react';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
-import { useHistory } from "react-router-dom";
-import { iconPasswordField, iconUser } from '@storyteller/components/src/icons/SemanticIcons';
-import { CreateSession, CreateSessionIsError, CreateSessionIsSuccess } from '@storyteller/components/src/api/session/CreateSession';
-
-interface Props {
- sessionWrapper: SessionWrapper,
- querySessionAction: () => void,
-}
-
-function LoginPage(props: Props) {
- let history = useHistory();
-
- const [password, setPassword] = useState('')
- const [usernameOrEmail, setUsernameOrEmail] = useState('')
- const [errorMessage, setErrorMessage] = useState('')
-
- if (props.sessionWrapper.isLoggedIn()) {
- history.push('/');
- }
-
- const handleUsernameOrEmailChange = (ev: React.FormEvent) => {
- ev.preventDefault();
- const usernameOrEmailValue = (ev.target as HTMLInputElement).value;
- setUsernameOrEmail(usernameOrEmailValue);
- setErrorMessage("");
- return false;
- };
-
- const handlePasswordChange = (ev: React.FormEvent) => {
- ev.preventDefault();
- const passwordValue = (ev.target as HTMLInputElement).value;
- setPassword(passwordValue);
- setErrorMessage("");
- return false;
- };
-
- const handleFormSubmit = async (ev: React.FormEvent) : Promise => {
- ev.preventDefault();
-
- const request = {
- username_or_email: usernameOrEmail,
- password: password,
- }
-
- const response = await CreateSession(request);
-
- if (CreateSessionIsError(response)) {
- setErrorMessage(response.error_message);
- } else if (CreateSessionIsSuccess(response)) {
- console.log('querying new session');
- props.querySessionAction();
- history.push('/');
- }
-
- return false;
- }
-
- let errorWarning =
- if (errorMessage) {
- errorWarning = (
-
- Login Error: {errorMessage}
-
- )
- }
-
- return (
-
-
-
-
Login
-
(If you have a FakeYou.com account, it'll work here too.)
-
- {errorWarning}
-
-
-
-
-
- )
-}
-
-export { LoginPage };
diff --git a/packages/dashboard.power.stream/src/pages/obs_configs/ObsConfigsPage.tsx b/packages/dashboard.power.stream/src/pages/obs_configs/ObsConfigsPage.tsx
deleted file mode 100644
index 721bc9a30f..0000000000
--- a/packages/dashboard.power.stream/src/pages/obs_configs/ObsConfigsPage.tsx
+++ /dev/null
@@ -1,151 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faDiscord, faTwitch } from '@fortawesome/free-brands-svg-icons';
-import { CheckTwitchOauth, CheckTwitchOauthIsError, CheckTwitchOauthIsOk } from '@storyteller/components/src/api/storyteller/twitch_oauth/CheckTwitchOauth';
-import { StorytellerUrlConfig } from '@storyteller/components/src/urls/StorytellerUrlConfig';
-import { faExternalLinkAlt, faPlus } from '@fortawesome/free-solid-svg-icons';
-
-interface Props {
- sessionWrapper: SessionWrapper,
-}
-
-function ObsConfigsPage(props: Props) {
- const [maybeTwitchUsername, setMaybeTwitchUsername] = useState("");
-
- const checkTwitchOauth = useCallback(async () => {
- const result = await CheckTwitchOauth();
-
- if (CheckTwitchOauthIsOk(result)) {
- setMaybeTwitchUsername(result.maybe_twitch_username || "");
- } else if (CheckTwitchOauthIsError(result)) {
- }
- }, []);
-
- useEffect(() => {
- checkTwitchOauth();
- }, [checkTwitchOauth])
-
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return Must Log In ;
- }
-
- const username = !!maybeTwitchUsername ? maybeTwitchUsername : 'YOUR_USERNAME';
-
- const obsUrl = new StorytellerUrlConfig().obsPageFortwitch(username);
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- OBS Setup
-
-
- Ready to Broadcast!
-
-
-
-
-
-
-
-
-
-
-
-
Your OBS Browser Source
-
-
Once you've linked Twitch with Storyteller, you can add the following page as an OBS Browser Source :
-
-
-
-
-
-
-
-
- Open In New Tab
-
-
-
-
-
-
-
Setup Instructions
-
-
If you're not familiar with OBS or you need a refresher, here's a quick glance at the setup.
-
-
-
-
Step 1) Click to add a new Browser Source
-
-
-
-
The bottom of OBS should have a plus icon ( ) to add new sources.
- You'll want to add a new Browser Source .
-
-
-
-
Step 2) Paste the URL into the configuration window
-
-
-
-
-
-
Remember, the URL is {obsUrl}
-
-
-
-
Step 3) Activate the Page
-
-
-
-
-
-
-
Right click the layer and select “Interact” .
- This will let you click the button and activate the service.
-
-
-
-
-
Good to go!
-
-
You can now hide the layer and make it invisible. The audio levels are also independently adjustable.
-
-
-
-
Have fun! We'll be back soon with some even more jaw-dropping utilities.
-
-
-
-
- )
-}
-
-export { ObsConfigsPage }
\ No newline at end of file
diff --git a/packages/dashboard.power.stream/src/pages/signup/SignupPage.tsx b/packages/dashboard.power.stream/src/pages/signup/SignupPage.tsx
deleted file mode 100644
index ec81752c69..0000000000
--- a/packages/dashboard.power.stream/src/pages/signup/SignupPage.tsx
+++ /dev/null
@@ -1,331 +0,0 @@
-import React, { useState } from 'react';
-import { CreateAccount, CreateAccountIsError, CreateAccountIsSuccess } from '@storyteller/components/src/api/user/CreateAccount';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Link } from 'react-router-dom';
-import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
-import { iconEmailField, iconPasswordField, iconUser } from '@storyteller/components/src/icons/SemanticIcons';
-
-enum FieldTriState {
- EMPTY_FALSE,
- FALSE,
- TRUE,
-}
-
-interface Props {
- sessionWrapper: SessionWrapper,
- querySessionCallback : () => void,
-}
-
-function SignupPage(props: Props) {
-
- const [username, setUsername] = useState('');
- const [usernameValid, setUsernameValid] = useState(FieldTriState.EMPTY_FALSE);
- const [usernameInvalidReason, setUsernameInvalidReason] = useState('');
-
- const [email, setEmail] = useState('');
- const [emailValid, setEmailValid] = useState(FieldTriState.EMPTY_FALSE);
- const [emailInvalidReason, setEmailInvalidReason] = useState('');
-
- const [password, setPassword] = useState('');
- const [passwordValid, setPasswordValid] = useState(FieldTriState.EMPTY_FALSE);
- const [passwordInvalidReason, setPasswordInvalidReason] = useState('');
-
- const [passwordConfirmation, setPasswordConfirmation] = useState('');
- const [passwordConfirmationValid, setPasswordConfirmationValid] = useState(FieldTriState.EMPTY_FALSE);
- const [passwordConfirmationInvalidReason, setPasswordConfirmationInvalidReason] = useState('');
-
- const handleUsernameChange = (ev: React.FormEvent) => {
- ev.preventDefault();
-
- const newUsername = (ev.target as HTMLInputElement).value;
-
- let usernameValid = FieldTriState.EMPTY_FALSE;
- let usernameInvalidReason = "";
-
- if (newUsername.length > 1) {
- if (newUsername.length < 3) {
- usernameValid = FieldTriState.FALSE;
- usernameInvalidReason = "username is too short";
- } else if (newUsername.length > 15) {
- usernameValid = FieldTriState.FALSE;
- usernameInvalidReason = "username is too long";
- } else {
- usernameValid = FieldTriState.TRUE;
- }
- }
-
- setUsername(newUsername);
- setUsernameValid(usernameValid);
- setUsernameInvalidReason(usernameInvalidReason);
-
- return false;
- }
-
- const handleEmailChange = (ev: React.FormEvent) => {
- ev.preventDefault();
-
- const newEmail = (ev.target as HTMLInputElement).value;
-
- let emailValid = FieldTriState.EMPTY_FALSE;
- let emailInvalidReason = "";
-
- if (newEmail.length > 1) {
- if (newEmail.length < 3) {
- emailValid = FieldTriState.FALSE;
- emailInvalidReason = "email is too short";
- } else if (!newEmail.includes("@")) {
- emailValid = FieldTriState.FALSE;
- emailInvalidReason = "email is invalid";
- } else {
- emailValid = FieldTriState.TRUE;
- }
- }
-
- setEmail(newEmail);
- setEmailValid(emailValid);
- setEmailInvalidReason(emailInvalidReason);
-
- return false;
- }
-
- const handlePasswordChange = (ev: React.FormEvent) => {
- ev.preventDefault();
-
- const newPassword = (ev.target as HTMLInputElement).value;
-
- let passwordValid = FieldTriState.EMPTY_FALSE;
- let passwordInvalidReason = "";
- let passwordConfirmationValid = FieldTriState.EMPTY_FALSE;
- let passwordConfirmationInvalidReason = "";
-
- if (newPassword.length > 1) {
- if (newPassword.length < 5) {
- passwordValid = FieldTriState.FALSE;
- passwordInvalidReason = "password is too short";
- } else {
- passwordValid = FieldTriState.TRUE;
- }
-
- if (newPassword !== passwordConfirmation) {
- passwordConfirmationValid = FieldTriState.FALSE;
- passwordConfirmationInvalidReason = "passwords do not match";
- } else {
- passwordConfirmationValid = FieldTriState.TRUE;
- passwordConfirmationInvalidReason = "";
-
- }
- }
-
- setPassword(newPassword);
- setPasswordValid(passwordValid);
- setPasswordInvalidReason(passwordInvalidReason);
- setPasswordConfirmationValid(passwordConfirmationValid);
- setPasswordConfirmationInvalidReason(passwordConfirmationInvalidReason)
-
- return false;
- }
-
- const handlePasswordConfirmationChange = (ev: React.FormEvent) => {
- ev.preventDefault();
-
- const newPasswordConfirmation = (ev.target as HTMLInputElement).value;
-
- let passwordConfirmationValid = FieldTriState.EMPTY_FALSE;
- let passwordConfirmationInvalidReason = "";
-
- if (newPasswordConfirmation.length > 1) {
-
- if (newPasswordConfirmation !== password) {
- passwordConfirmationValid = FieldTriState.FALSE;
- passwordConfirmationInvalidReason = "passwords do not match";
- } else {
- passwordConfirmationValid = FieldTriState.TRUE;
- passwordConfirmationInvalidReason = "";
-
- }
- }
-
- setPasswordConfirmation(newPasswordConfirmation);
- setPasswordConfirmationValid(passwordConfirmationValid);
- setPasswordConfirmationInvalidReason(passwordConfirmationInvalidReason);
-
- return false;
- }
-
- const handleFormSubmit = async (ev: React.FormEvent) : Promise => {
- ev.preventDefault();
-
- if (!usernameValid ||
- !emailValid ||
- !passwordValid ||
- !passwordConfirmationValid) {
- return false;
- }
-
- const request = {
- username: username,
- email_address: email,
- password: password,
- password_confirmation: passwordConfirmation,
- };
-
- const response = await CreateAccount(request);
-
- if (CreateAccountIsError(response)) {
- if ('email_address' in response.error_fields) {
- setEmailValid(FieldTriState.FALSE);
- setEmailInvalidReason(response.error_fields['email_address'] || "");
- }
- if ('username' in response.error_fields) {
- setUsernameValid(FieldTriState.FALSE);
- setUsernameInvalidReason(response.error_fields['username'] || "");
- }
- } else if (CreateAccountIsSuccess(response)) {
- console.log('querying new session');
- props.querySessionCallback();
-
- // TODO: Switch to functional component.
- window.location.href = '/';
- }
-
- return false;
- }
-
-
- if (props.sessionWrapper.isLoggedIn()) {
- return Invalid view for logged in users.
;
- }
-
- let usernameInputClass = "input";
- let usernameHelpClass = "help";
- switch (usernameValid) {
- case FieldTriState.EMPTY_FALSE:
- break;
- case FieldTriState.FALSE:
- usernameInputClass += " is-danger";
- usernameHelpClass += " is-danger";
- break;
- case FieldTriState.TRUE:
- usernameInputClass += " is-success";
- usernameHelpClass += " is-success";
- break;
- }
-
- let emailInputClass = "input";
- let emailHelpClass = "help";
- switch (emailValid) {
- case FieldTriState.EMPTY_FALSE:
- break;
- case FieldTriState.FALSE:
- emailInputClass += " is-danger";
- emailHelpClass += " is-danger";
- break;
- case FieldTriState.TRUE:
- emailInputClass += " is-success";
- emailHelpClass += " is-success";
- break;
- }
-
- let passwordInputClass = "input";
- let passwordHelpClass = "help";
- switch (passwordValid) {
- case FieldTriState.EMPTY_FALSE:
- break;
- case FieldTriState.FALSE:
- passwordInputClass += " is-danger";
- passwordHelpClass += " is-danger";
- break;
- case FieldTriState.TRUE:
- passwordInputClass += " is-success";
- passwordHelpClass += " is-success";
- break;
- }
-
- let passwordConfirmationInputClass = "input";
- let passwordConfirmationHelpClass = "help";
- switch (passwordConfirmationValid) {
- case FieldTriState.EMPTY_FALSE:
- break;
- case FieldTriState.FALSE:
- passwordConfirmationInputClass += " is-danger";
- passwordConfirmationHelpClass += " is-danger";
- break;
- case FieldTriState.TRUE:
- passwordConfirmationInputClass += " is-success";
- passwordConfirmationHelpClass += " is-success";
- break;
- }
-
- return (
-
-
Sign Up
-
-
-
-
-
-
Already have an account? Log in instead!
-
-
- )
-}
-
-export { SignupPage };
\ No newline at end of file
diff --git a/packages/dashboard.power.stream/src/pages/stream/StreamPage.tsx b/packages/dashboard.power.stream/src/pages/stream/StreamPage.tsx
deleted file mode 100644
index cd52946b0d..0000000000
--- a/packages/dashboard.power.stream/src/pages/stream/StreamPage.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import { faTwitch } from '@fortawesome/free-brands-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import React, { useState } from 'react';
-import { useHistory, withRouter } from 'react-router-dom';
-
-function StreamPage() {
- const history = useHistory();
-
- const [twitchUsername, setTwitchUsername] = useState('')
-
- const handleTwitchUsernameChange = (ev: React.FormEvent) => {
- ev.preventDefault();
- const value = (ev.target as HTMLInputElement).value;
- setTwitchUsername(value);
- return false;
- };
-
- const openObs = (ev: React.FormEvent) => {
- ev.preventDefault();
- let url = `/obs/${twitchUsername}`;
- history.push(url);
- return false;
- };
-
- const buttonDisabled = twitchUsername.trim().length === 0;
-
- return (
-
-
-
-
- Stream TTS
-
-
- Early Alpha Preview
-
-
-
This is an early demo of our Stream TTS, powered by FakeYou.
- It does not currently offer customization, though our plans are to
- allow for a high degree of configurability: set your own voices, rewards, etc.
-
-
You don't need to set up any software on your end. Simply point OBS or your
- broadcast software the URL below:
-
-
-
-
- 1. Connect with Twitch
-
-
-
-
-
-
-
-
-
-
-
- 2. Open OBS Page
-
-
-
-
-
- )
-}
-
-export default withRouter(StreamPage);
\ No newline at end of file
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/TtsConfigsCreateRulePage.tsx b/packages/dashboard.power.stream/src/pages/tts_configs/TtsConfigsCreateRulePage.tsx
deleted file mode 100644
index 0adc6bee8c..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/TtsConfigsCreateRulePage.tsx
+++ /dev/null
@@ -1,167 +0,0 @@
-import React, { useState } from 'react';
-import { v4 as uuidv4 } from 'uuid';
-import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
-import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
-import { TwitchEventRule } from '@storyteller/components/src/api/storyteller/twitch_event_rules/GetTwitchEventRule';
-import { Link, useHistory, useParams } from 'react-router-dom';
-import { TwitchEventCategory, TWITCH_EVENT_CATEGORY_BY_STRING } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/TwitchEventCategory';
-import { EventResponse } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventResponse';
-import { EventMatchPredicate } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventMatchPredicate';
-import { CreateTwitchEventRule, CreateTwitchEventRuleRequest } from '@storyteller/components/src/api/storyteller/twitch_event_rules/CreateTwitchEventRule';
-import { EventMatchPredicateBuilderComponent } from './event_match_predicate_builder/EventMatchPredicateBuilderComponent';
-import { EventResponseComponent } from './event_response_builder/EventResponseComponent';
-import { TwitchEventRuleElement } from './rule_cards/TwitchEventRuleElement';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faAngleLeft, faSave } from '@fortawesome/free-solid-svg-icons';
-
-interface Props {
- sessionWrapper: SessionWrapper,
- allTtsModels: TtsModelListItem[],
- allTtsModelsByToken: Map,
-}
-
-function TtsConfigsCreateRulePage(props: Props) {
- const { event_category } = useParams() as { event_category : string };
-
- const history = useHistory();
-
- // TODO: Use centralized configs
- const indexLink = '/tts_configs';
-
- // ========== In-Progress Model Edits ==========
-
- // Sent to sub-components to initialize UI
- // NB: This is a HACK. The subcomponent views were originally created for the "Edit" UI
- // and this had to be done to get them to work for "Create" UI. Sending empty dictionaries
- // or statically initialized values froze the UI.
- const [serverEventMatchPredicate] = useState({});
- const [serverEventResponse] = useState({});
-
- const [modifiedEventMatchPredicate, setModifiedEventMatchPredicate] = useState({});
- const [modifiedEventResponse, setModifiedEventResponse] = useState({});
- const [ruleIsDisabled] = useState(false);
-
- let maybeTwitchEventCategory = TWITCH_EVENT_CATEGORY_BY_STRING.get(event_category);
-
- if (maybeTwitchEventCategory === undefined) {
- history.push(indexLink);
- return <>>;
- }
-
- // NB: To satisfy the type system and not be a union on 'undefined'
- const twitchEventCategory = maybeTwitchEventCategory;
-
- const updateModifiedEventMatchPredicate = (predicate: EventMatchPredicate) => {
- setModifiedEventMatchPredicate(predicate);
- }
-
- const updateModifiedEventResponse = (response: EventResponse) => {
- setModifiedEventResponse(response);
- }
-
- const handleFormSubmit = async (ev: React.FormEvent) : Promise => {
- ev.preventDefault();
-
- // TODO: Check for errors.
-
- let newEventMatchPredicate = modifiedEventMatchPredicate;
- let newEventResponse = modifiedEventResponse;
-
- // TODO: UUID idempotency token that updates on every model change.
-
- const request : CreateTwitchEventRuleRequest = {
- idempotency_token: uuidv4(),
- event_category: twitchEventCategory,
- event_match_predicate: newEventMatchPredicate,
- event_response: newEventResponse,
- rule_is_disabled: ruleIsDisabled,
- user_specified_rule_order: 1000,
- };
-
- const result = await CreateTwitchEventRule(request);
- if (result.success) {
- history.push(indexLink);
- }
-
- return false;
- }
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return Must Log In ;
- }
-
- // NB: This is a hypothetical version of what we'll update to
- let renderRule : TwitchEventRule = {
- // Not yet saved, so fake values
- token: 'virtual',
- user_specified_rule_order: 1000,
- created_at: new Date(),
- updated_at: new Date(),
-
- // Updated in UI
- event_category: twitchEventCategory,
- event_match_predicate: modifiedEventMatchPredicate,
- event_response: modifiedEventResponse,
- rule_is_disabled: ruleIsDisabled,
- };
-
- const titlesByCategory = new Map([
- [TwitchEventCategory.Bits, 'Create New Bits / Cheers Rule'],
- [TwitchEventCategory.ChannelPoints, 'Create New Channel Points Rule'],
- ]);
-
- const pageTitle = titlesByCategory.get(twitchEventCategory);
-
- return (
- <>
-
-
{pageTitle}
-
-
-
-
-
-
-
-
-
-
- Cancel / Go Back
-
-
- >
- )
-}
-
-export { TtsConfigsCreateRulePage }
\ No newline at end of file
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/TtsConfigsDeleteRulePage.tsx b/packages/dashboard.power.stream/src/pages/tts_configs/TtsConfigsDeleteRulePage.tsx
deleted file mode 100644
index 6e6bdbe1e5..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/TtsConfigsDeleteRulePage.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
-import { GetTwitchEventRule, GetTwitchEventRuleIsError, GetTwitchEventRuleIsOk, TwitchEventRule } from '@storyteller/components/src/api/storyteller/twitch_event_rules/GetTwitchEventRule';
-import { DeleteTwitchEventRule } from '@storyteller/components/src/api/storyteller/twitch_event_rules/DeleteTwitchEventRule';
-import { TwitchEventRuleElement } from './rule_cards/TwitchEventRuleElement';
-import { Link, useHistory, useParams } from 'react-router-dom';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faAngleLeft, faTrash } from '@fortawesome/free-solid-svg-icons';
-import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
-
-interface Props {
- sessionWrapper: SessionWrapper,
- allTtsModelsByToken: Map,
-}
-
-function TtsConfigsDeleteRulePage(props: Props) {
- const { token } : { token : string } = useParams();
-
- const history = useHistory();
-
- const [twitchEventRule, setTwitchEventRule] = useState(undefined);
-
- const getTwitchEventRule = useCallback(async (token: string) => {
- const response = await GetTwitchEventRule(token);
-
- if (GetTwitchEventRuleIsOk(response)) {
- setTwitchEventRule(response.twitch_event_rule);
- } else if (GetTwitchEventRuleIsError(response)) {
- // TODO
- }
- }, []);
-
- useEffect(() => {
- getTwitchEventRule(token);
- }, [getTwitchEventRule, token]);
-
- const indexLink = '/tts_configs';
-
- const handleDeleteFormSubmit = async (ev: React.FormEvent) : Promise => {
- ev.preventDefault();
-
- const result = await DeleteTwitchEventRule(token);
- if (result.success) {
- history.push(indexLink);
- }
-
- return false;
- }
-
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return Must Log In ;
- }
-
- if (twitchEventRule === undefined) {
- return Loading... ;
- }
-
- return (
- <>
-
-
Delete Rule ?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Cancel / Go Back
-
-
- >
- )
-}
-
-export { TtsConfigsDeleteRulePage }
\ No newline at end of file
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/TtsConfigsEditRulePage.tsx b/packages/dashboard.power.stream/src/pages/tts_configs/TtsConfigsEditRulePage.tsx
deleted file mode 100644
index d880de1ff0..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/TtsConfigsEditRulePage.tsx
+++ /dev/null
@@ -1,171 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
-import { GetTwitchEventRule, GetTwitchEventRuleIsError, GetTwitchEventRuleIsOk, TwitchEventRule } from '@storyteller/components/src/api/storyteller/twitch_event_rules/GetTwitchEventRule';
-import { EditTwitchEventRule, EditTwitchEventRuleRequest } from '@storyteller/components/src/api/storyteller/twitch_event_rules/EditTwitchEventRule';
-import { TwitchEventRuleElement } from './rule_cards/TwitchEventRuleElement';
-import { Link, useHistory, useParams } from 'react-router-dom';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faAngleLeft, faSave } from '@fortawesome/free-solid-svg-icons';
-import { EventMatchPredicate } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventMatchPredicate';
-import { EventResponse } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventResponse';
-import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
-import { EventResponseComponent } from './event_response_builder/EventResponseComponent';
-import { EventMatchPredicateBuilderComponent } from './event_match_predicate_builder/EventMatchPredicateBuilderComponent';
-
-interface Props {
- sessionWrapper: SessionWrapper,
- allTtsModels: TtsModelListItem[],
- allTtsModelsByToken: Map,
-}
-
-function TtsConfigsEditRulePage(props: Props) {
- const { token } : { token : string } = useParams();
-
- // TODO: Use centralized URL configs
- const indexLink = '/tts_configs';
-
- const history = useHistory();
-
- // ========== Initial Load of Server State ==========
-
- const [twitchEventRule, setTwitchEventRule] = useState(undefined);
-
- // Sent to sub-components to initialize UI
- const [serverEventMatchPredicate, setServerEventMatchPredicate] = useState({});
- const [serverEventResponse, setServerEventResponse] = useState({});
-
- // ========== In-Progress Model Edits ==========
-
- const [modifiedEventMatchPredicate, setModifiedEventMatchPredicate] = useState({});
- const [modifiedEventResponse, setModifiedEventResponse] = useState({});
- const [ruleIsDisabled, setRuleIsDisabled] = useState(false);
-
- const getTwitchEventRule = useCallback(async (token: string) => {
- const response = await GetTwitchEventRule(token);
-
- if (GetTwitchEventRuleIsOk(response)) {
- // Source of truth
- setTwitchEventRule(response.twitch_event_rule);
- setServerEventMatchPredicate(response.twitch_event_rule.event_match_predicate);
- setServerEventResponse(response.twitch_event_rule.event_response);
-
- // In-progress modifications
- setModifiedEventMatchPredicate(response.twitch_event_rule.event_match_predicate);
- setModifiedEventResponse(response.twitch_event_rule.event_response);
- setRuleIsDisabled(response.twitch_event_rule.rule_is_disabled);
-
- } else if (GetTwitchEventRuleIsError(response)) {
- // TODO
- }
- }, []);
-
- useEffect(() => {
- getTwitchEventRule(token);
- }, [getTwitchEventRule, token]);
-
- const updateModifiedEventMatchPredicate = (predicate: EventMatchPredicate) => {
- setModifiedEventMatchPredicate(predicate);
- }
-
- const updateModifiedEventResponse = (response: EventResponse) => {
- setModifiedEventResponse(response);
- }
-
- const handleFormSubmit = async (ev: React.FormEvent) : Promise => {
- ev.preventDefault();
-
- // TODO: Check for errors.
-
- let newEventMatchPredicate = modifiedEventMatchPredicate;
- let newEventResponse = modifiedEventResponse;
-
- const request : EditTwitchEventRuleRequest = {
- event_match_predicate: newEventMatchPredicate,
- event_response: newEventResponse,
- rule_is_disabled: ruleIsDisabled,
- };
-
- const result = await EditTwitchEventRule(token, request);
- if (result.success) {
- history.push(indexLink);
- }
-
- return false;
- }
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return Must Log In ;
- }
-
- if (twitchEventRule === undefined) {
- return Loading... ;
- }
-
- // NB: This is a hypothetical version of what we'll update to
- let renderRule : TwitchEventRule = {
- // Unchanged
- token: twitchEventRule.token,
- event_category: twitchEventRule.event_category,
- user_specified_rule_order: twitchEventRule.user_specified_rule_order,
- created_at: twitchEventRule.created_at,
- updated_at: twitchEventRule.updated_at,
-
- // Updated in UI
- event_match_predicate: modifiedEventMatchPredicate,
- event_response: modifiedEventResponse,
- rule_is_disabled: ruleIsDisabled,
- };
-
- return (
- <>
-
-
Edit Rule
-
-
-
-
-
-
-
-
-
-
- Cancel / Go Back
-
-
- >
- )
-}
-
-export { TtsConfigsEditRulePage }
\ No newline at end of file
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/TtsConfigsIndexPage.tsx b/packages/dashboard.power.stream/src/pages/tts_configs/TtsConfigsIndexPage.tsx
deleted file mode 100644
index 2a1c62bfe0..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/TtsConfigsIndexPage.tsx
+++ /dev/null
@@ -1,212 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
-import { ListTwitchEventRules, ListTwitchEventRulesIsError, ListTwitchEventRulesIsOk, TwitchEventRule } from '@storyteller/components/src/api/storyteller/twitch_event_rules/ListTwitchEventRules';
-import { TwitchEventRuleElement } from './rule_cards/TwitchEventRuleElement';
-import { TwitchEventCategory } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/TwitchEventCategory';
-import { DiscordLink } from '@storyteller/components/src/elements/DiscordLink';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faBox, faDonate, faFilm, faGem, faHeart, faLightbulb, faMeteor, faPlus, faSort, faTerminal, faUsers } from '@fortawesome/free-solid-svg-icons';
-import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
-import { Link } from 'react-router-dom';
-
-interface Props {
- sessionWrapper: SessionWrapper,
- allTtsModelsByToken: Map,
-}
-
-function TtsConfigsIndexPage(props: Props) {
- const [twitchEventRules, setTwitchEventRules] = useState([]);
-
- const listTwitchEventRules = useCallback(async () => {
- const response = await ListTwitchEventRules();
-
- if (ListTwitchEventRulesIsOk(response)) {
- setTwitchEventRules(response.twitch_event_rules);
- } else if (ListTwitchEventRulesIsError(response)) {
- // TODO
- }
- }, []);
-
- useEffect(() => {
- listTwitchEventRules();
- }, [listTwitchEventRules]);
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return Must Log In ;
- }
-
- const cheerEventRules = twitchEventRules.filter(rule => rule.event_category === TwitchEventCategory.Bits);
-
- const channelPointsEventRules = twitchEventRules.filter(rule => rule.event_category === TwitchEventCategory.ChannelPoints);
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Stream Setup
-
-
- Configure TTS (and soon much more!)
-
-
-
-
-
-
-
-
-
-
- You can create rules for matching different categories of events : cheers (bits),
- channel point rewards, etc.
-
- When an event occurs in your stream, we compare it against the rules in its
- category. The first matched rule (from the top) is selected, and the action
- that it specifies is taken. For now this is limited to FakeYou text to speech, but we'll be
- adding lots of new capabilities soon.
-
-
-
Bits / Cheers
-
- These settings are best for small and medium-sized channels.
-
-
-
- {cheerEventRules.map(rule => {
- return
- })}
-
-
-
- Create New Bits / Cheer Rule
-
-
-
-
-
- Reorder Rules
-
-
-
-
-
-
-
Channel Points / Rewards
-
- These settings are best for small channels.
-
- {channelPointsEventRules.map(rule => {
- return
- })}
-
-
-
- Create Channel Points Rule
-
-
-
-
-
- Reorder Rules
-
-
-
-
-
-
-
Coming Soon…
-
-
-
-
-
Subs, Resubs, and Gifted Subs
-
- These settings are great for all channel sizes.
-
-
-
-
-
-
Donation Services (Streamlabs, StreamElements, etc.)
-
- These settings are great for large channels, but work for channels of all sizes.
-
-
-
-
-
-
-
Text /slash Commands
-
- These settings are great for very small channels.
-
-
-
-
-
-
-
User-generated Animated Deepfake Rewards
-
- A lip-synced, green screened “Famous Person” pops up and says something.
-
-
-
-
-
-
-
Multi Voice and Sound Effects
-
- User-selected voices can be strung together in a single reward, interspersed with sound effects.
-
-
-
-
-
-
-
Suggestions?
-
- Let us know what to build!
-
-
Can you think of anything we haven't
- provided? so
- that we can build it!
-
-
-
- >
- )
-}
-
-export { TtsConfigsIndexPage }
\ No newline at end of file
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/TtsConfigsReorderPage.tsx b/packages/dashboard.power.stream/src/pages/tts_configs/TtsConfigsReorderPage.tsx
deleted file mode 100644
index 3417747217..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/TtsConfigsReorderPage.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
-import { ListTwitchEventRules, ListTwitchEventRulesIsError, ListTwitchEventRulesIsOk, TwitchEventRule } from '@storyteller/components/src/api/storyteller/twitch_event_rules/ListTwitchEventRules';
-import { ReorderTwitchEventRules, ReorderTwitchEventRulesRequest } from '@storyteller/components/src/api/storyteller/twitch_event_rules/ReorderTwitchEventRules';
-import { TwitchEventCategory, TWITCH_EVENT_CATEGORY_BY_STRING } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/TwitchEventCategory';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faSave } from '@fortawesome/free-solid-svg-icons';
-import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
-import { useHistory, useParams } from 'react-router-dom';
-import { ReorderableTwitchEventRuleElement } from './rule_cards/ReorderableTwitchEventRuleElement';
-
-interface Props {
- sessionWrapper: SessionWrapper,
- allTtsModelsByToken: Map,
-}
-
-function TtsConfigsReorderPage(props: Props) {
- const { event_category } = useParams() as { event_category : string };
-
- const history = useHistory();
-
- // TODO: Use centralized configs
- const indexLink = '/tts_configs';
-
- const [twitchEventRules, setTwitchEventRules] = useState([]);
-
- const listTwitchEventRules = useCallback(async (twitchEventCategory: TwitchEventCategory) => {
- const response = await ListTwitchEventRules();
-
- if (ListTwitchEventRulesIsOk(response)) {
- const allRules = response.twitch_event_rules;
- const releventRules = allRules.filter(rule => rule.event_category === twitchEventCategory);
-
- setTwitchEventRules(releventRules);
-
- } else if (ListTwitchEventRulesIsError(response)) {
- // TODO
- }
- }, []);
-
- useEffect(() => {
- console.log('useEffect');
- let maybeTwitchEventCategory = TWITCH_EVENT_CATEGORY_BY_STRING.get(event_category);
-
- if (maybeTwitchEventCategory === undefined) {
- history.push(indexLink);
- } else {
- listTwitchEventRules(maybeTwitchEventCategory);
- }
-
- }, [listTwitchEventRules, event_category, history]);
-
- const handleMoveUp = (ruleIndex: number) => {
- let newEventRules = [...twitchEventRules];
-
- if (ruleIndex >= newEventRules.length || // Walk off end
- ruleIndex === 0) { // Can't move up.
- return false;
- }
-
- let aboveIndex = ruleIndex - 1;
- let aboveRule = newEventRules[aboveIndex];
-
- newEventRules[aboveIndex] = newEventRules[ruleIndex];
- newEventRules[ruleIndex] = aboveRule;
-
- setTwitchEventRules(newEventRules);
- }
-
- const handleMoveDown = (ruleIndex: number) => {
- let newEventRules = [...twitchEventRules];
-
- if (ruleIndex < 0 || // Invalid
- ruleIndex >= newEventRules.length - 1) { // Can't move down.
- return false;
- }
-
- let belowIndex = ruleIndex + 1;
-
- console.log(ruleIndex, belowIndex);
-
- let belowRule = newEventRules[belowIndex];
-
- newEventRules[belowIndex] = newEventRules[ruleIndex];
- newEventRules[ruleIndex] = belowRule;
-
- setTwitchEventRules(newEventRules);
- }
-
- const handleSaveOrderings = async (ev: React.FormEvent) : Promise => {
-
- const pairs = twitchEventRules.map((rule, index) => {
- return {
- rule_token: rule.token,
- position: index,
- }
- })
-
- const request : ReorderTwitchEventRulesRequest = {
- rule_token_position_pairs: pairs,
- };
-
- const result = await ReorderTwitchEventRules(request);
- if (result.success) {
- history.push(indexLink);
- }
-
- return false;
- }
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return Must Log In ;
- }
-
- return (
- <>
-
-
Reorder Rules
- This affects match priority. Rules at the top come first.
-
-
-
-
-
- You can create rules for matching different categories of events : cheers (bits),
- channel point rewards, etc.
-
- When an event occurs in your stream, we compare it against the rules in its
- category. The first matched rule (from the top) is selected, and the action
- that it specifies is taken. For now this is limited to FakeYou text to speech, but we'll be
- adding lots of new capabilities soon.
-
-
-
- {twitchEventRules.map((rule, index) => {
- return
- })}
-
-
-
- Save Orderings
-
- >
- )
-}
-
-export { TtsConfigsReorderPage }
\ No newline at end of file
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/CheerState.ts b/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/CheerState.ts
deleted file mode 100644
index 05f5ee932b..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/CheerState.ts
+++ /dev/null
@@ -1,247 +0,0 @@
-import { EventMatchPredicate } from "@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventMatchPredicate";
-import { CHEER_LOOKUP_MAP, CHEER_PREFIX_TO_STRING_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,
- bits?: number,
-}
-
-export interface CheerStateCustom {
- // NB: This can contain a numeric suffix independent of bits.
- // eg. third party cheers from 7tv.app: Sus11, Sus12, 1984, 4Weird.
- cheerFull?: string,
- bits?: number,
-}
-
-// Due to the complexity of the forms, this is used as the source of truth while editing.
-// Each type of predicate can serialize and deserialize into the predicates.
-export type CheerState = CheerStateOfficial | CheerStateCustom;
-
-export function CheerStateIsOfficial(cheerState: CheerState): cheerState is CheerStateOfficial {
- return cheerState.hasOwnProperty('cheerPrefix');
-}
-
-export function CheerStateIsCustom(cheerState: CheerState): cheerState is CheerStateCustom {
- return cheerState.hasOwnProperty('cheerFull');
-}
-
-// Convert "exact match" strings into CheerState
-export function convertExactMatchToCheerState(freeformText: string) : CheerState {
- const trimmed = freeformText.trim();
-
- let { cheerPrefix, bitValue } = CheerUtil.parseCheerString(trimmed);
-
- let maybeCheer = CHEER_LOOKUP_MAP.get(cheerPrefix || '');
-
- if (maybeCheer !== undefined) {
- return {
- cheerPrefix: maybeCheer,
- bits: bitValue,
- }
- }
-
- // NB: In this case, bits value may be wrong.
- // eg. "1984"
- return {
- cheerFull: freeformText,
- bits: bitValue,
- };
-}
-
-// Convert "prefix + bits" into CheerState
-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: maybeBits,
- }
- }
-
- // NB: In this case, bits value may be wrong.
- // eg. "1984"
- return {
- cheerFull: freeformText,
- 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 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,
- }
- } else {
- return {};
- }
-}
-
-// Turn a cheer state into a predicate
-export function cheerStateToPredicate(
- cheerState: CheerState,
- bitsRuleType: BitsRuleType) : EventMatchPredicate
-{
- let predicate : EventMatchPredicate = {};
-
- switch (bitsRuleType) {
- case BitsRuleType.BitsCheermoteNameExactMatch:
- let cheermoteName = '';
- if (CheerStateIsOfficial(cheerState)) {
- let cheerPrefix = '';
- let bitsValue = '';
- if (!!cheerState.cheerPrefix) {
- cheerPrefix = CHEER_PREFIX_TO_STRING_MAP.get(cheerState.cheerPrefix) || '';
- }
- if (!!cheerState.bits && !isNaN(cheerState.bits) && cheerState.bits > 1) {
- bitsValue = cheerState.bits.toString();
- }
-
- if (!!cheerPrefix) {
- // Don't bother setting unless we have a prefix
- cheermoteName = `${cheerPrefix}${bitsValue}`
- }
-
- } else if (CheerStateIsCustom(cheerState)) {
- // Cheer name is the full value, eg. 'ZomboCom1000'
- cheermoteName = cheerState.cheerFull || '';
- }
-
- predicate.bits_cheermote_name_exact_match = {
- cheermote_name: cheermoteName,
- }
- break;
-
- case BitsRuleType.BitsCheermotePrefixSpendThreshold:
- let newBitsValue = 1;
- if (!!cheerState.bits && !isNaN(cheerState.bits) && cheerState.bits > 1) {
- newBitsValue = cheerState.bits;
- }
-
- let newPrefix = '';
- if (CheerStateIsOfficial(cheerState)) {
- if (!!cheerState.cheerPrefix) {
- newPrefix = CHEER_PREFIX_TO_STRING_MAP.get(cheerState.cheerPrefix) || '';
- }
- } else if (CheerStateIsCustom(cheerState)) {
- newPrefix = cheerState.cheerFull || '';
- }
-
- predicate.bits_cheermote_prefix_spend_threshold = {
- cheermote_prefix: newPrefix,
- minimum_bits_spent: newBitsValue,
- }
- break;
-
- case BitsRuleType.BitsSpendThreshold:
- let newBits = 1;
- if (!!cheerState.bits && !isNaN(cheerState.bits) && cheerState.bits > 1) {
- newBits = cheerState.bits;
- }
- predicate.bits_spend_threshold = {
- minimum_bits_spent: newBits,
- }
- break;
- }
-
- return predicate;
-}
-
-
-/*
-
-Truth table-ish breakdown (incomplete).
-
-"Corgo" is an official cheer
-"Zombo", "Sus12", and "1984" are custom cheers
-
-------------------------------------------------------------------------------------------------
-
-Backend Re-serialize to backend
-
-Bits 1000 => CheerState { bits: 1000 }
-
-ExactMatch Corgo100 => CheerState { official: Corgo, bits: 100 } => ExactMatch Corgo100
-ExactMatch Zombo100 => CheerState { full: Zombo100, bits: 100 } => ExactMatch Zombo100 (from "full")
-
-PrefixBits (Corgo, 100) => CheerState { official: Corgo, bits: 100 } => PrefixBits (Corgo, 100)
-PrefixBits (Corgo, 123) => CheerState { official: Corgo, bits: 123 } => PrefixBits (Corgo, 123)
-PrefixBits (Zombo, 123) => CheerState { full: Zombo, bits: 123 } => PrefixBits (Zombo, 123)
-
-------------------------------------------------------------------------------------------------
-
-Backend To another [view type] w/ UI state
-
-Bits 1000 => CheerState { bits: 1000 } => [ExactMatch] PrefixSelect: (n/a) BitSelect: 1000 FreeForm: (n/a)
-Bits 1234 => CheerState { bits: 1234 } => [ExactMatch] PrefixSelect: (n/a) BitSelect: (n/a) FreeForm: (n/a) [CANNOT POPULATE]
-
-Bits 1000 => CheerState { bits: 1000 } => [PrefixBits] PefixSelect: (n/a) FreeForm: (n/a) Bits: 1000
-Bits 1234 => CheerState { bits: 1234 } => [PrefixBits] PefixSelect: (n/a) FreeForm: (n/a) Bits: 1234
-
-ExactMatch Corgo100 => CheerState { official: Corgo, bits: 100 } => [ExactMatch] PrefixSelect: Corgo BitSelect: 100 FreeForm: Corgo100
-ExactMatch Zombo100 => CheerState { full: Zombo100, bits: 100 } => [ExactMatch] PrefixSelect: (n/a) BitSelect: 100 FreeForm: Zombo100
-ExactMatch Corgo100 => CheerState { official: Corgo, bits: 100 } => [Bits] BitsSelect: 100 BitsFreeForm: 100
-*) ExactMatch Corgo100 => CheerState { official: Corgo, bits: 100 } => [PrefixBits] PrefixSelect: Corgo FreeForm: Corgo Bits: 100
-*) ExactMatch Zombo100 => CheerState { full: Zombo100, bits: 100 } => [PrefixBits] PrefixSelect: (n/a) FreeForm: Zombo100 Bits: 100
-
-PrefixBits (Corgo, 100) => CheerState { official: Corgo, bits: 100 } => PrefixBits (Corgo, 100)
-PrefixBits (Corgo, 123) => CheerState { official: Corgo, bits: 123 } => PrefixBits (Corgo, 123)
-PrefixBits (Zombo, 123) => CheerState { full: Zombo, bits: 123 } => PrefixBits (Zombo, 123)
-
-# Notably, prefix is usually always "full":
-PrefixBits (1884, 1000) => CheerState { full: 1984, bits: 1000 } => [PrefixBits] PrefixSelect: (n/a) FreeForm: 1984 Bits: 1000
-PrefixBits (Sus12, 1000) => CheerState { full: Sus12, bits: 1000 } => [PrefixBits] PrefixSelect: (n/a) FreeForm: Sus12 Bits: 1000
-PrefixBits (Corgo1000, 5000) => CheerState { full: Corgo1000, bits: 5000} => [PrefixBits] PrefixSelect: (n/a) FreeForm: Corgo1000 Bits: 5000
-
-PrefixBits (1884, 1000) => CheerState { full: 1984, bits: 1000 } => [ExactMatch] PrefixSelect: (n/a) FreeForm: 1984 Bits: 1000
-PrefixBits (Sus12, 1000) => CheerState { full: Sus12, bits: 1000 } => [ExactMatch] PrefixSelect: (n/a) FreeForm: Sus12 Bits: 1000
-PrefixBits (Corgo1000, 5000) => CheerState { full: Corgo1000, bits: 5000} => [ExactMatch] PrefixSelect: (Corgo) FreeForm: Corgo1000 Bits: 1000 (*ignore bits*)
-
-
-# Unless it is *only* an official prefix
-PrefixBits (Corgo, 5000) => CheerState { official: Corgo, bits: 5000} => [PrefixBits] PrefixSelect: Corgo FreeForm: Corgo Bits: 5000
-PrefixBits (Corgo, 5000) => CheerState { official: Corgo, bits: 5000} => [ExactMatch] PrefixSelect: Corgo FreeForm: Corgo5000
-
-
-------------------------------------------------------------------------------------------------
-
-CheerState UI CheerState
-
-CheerState { official: Corgo, bits: 5000} => [Bits] BitsSelect: 5000, BitsFreeForm: 5000 => CheerState { official: (retain), bits: 5000 }
-CheerState { official: Corgo, bits: 5000} => [PrefixBits] PrefixSelect: Corgo FreeForm: Corgo Bits: 5000 => CheerState { official: Corgo, bits: 5000 }
-CheerState { official: Corgo, bits: 5000} => [ExactMatch] PrefixSelect: Corgo FreeForm: Corgo5000 => CheerState { official: Corgo, bits: 5000 } [split freeform]
-
-CheerState { full: Zombo, bits: 5000} => [Bits] BitsSelect: 5000, BitsFreeForm: 5000
-CheerState { full: Zombo, bits: 5000} => [PrefixBits] PrefixSelect: (n/a) FreeForm: Zombo Bits: 5000
-CheerState { full: Zombo, bits: 5000} => [ExactMatch] PrefixSelect: (n/a) FreeForm: Zombo5000
-
-CheerState { full: Zombo, bits: 5000} => [Bits] BitsSelect: 5000, BitsFreeForm: 5000
-CheerState { full: Zombo, bits: 5000} => [Bits] BitsSelect: 5000, BitsFreeForm: 5000
-
-CheerState { full: 1984, bits: 5000} => [PrefixBits] PrefixSelect: Corgo FreeForm: Corgo Bits: 5000
-CheerState { full: Sus12, bits: 5000} => [ExactMatch] PrefixSelect: Corgo FreeForm: Corgo5000
-CheerState { full: Corgo1000, bits: 5000} => [ExactMatch] PrefixSelect: Corgo FreeForm: Corgo5000
-
-
-*/
\ No newline at end of file
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/EventMatchPredicateBuilderComponent.tsx b/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/EventMatchPredicateBuilderComponent.tsx
deleted file mode 100644
index ff14e57b9c..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/EventMatchPredicateBuilderComponent.tsx
+++ /dev/null
@@ -1,311 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { BitsRuleType } from './types/BitsRuleType';
-import { TwitchEventCategory } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/TwitchEventCategory';
-import { EventMatchPredicate } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventMatchPredicate';
-import { BitsCheermoteNameExactMatchForm } from './subforms/BitsCheermoteNameExactMatchForm';
-import { BitsCheermotePrefixSpendThresholdForm } from './subforms/BitsCheermotePrefixSpendThresholdForm';
-import { BitsSpendThresholdForm } from './subforms/BitsSpendThresholdForm';
-import { ChannelPointsRewardNameExactMatchForm } from './subforms/ChannelPointsRewardNameExactMatchForm';
-import { ChannelPointsRuleType } from './types/ChannelPointsRuleType';
-import { CheerUtil } from '../../../twitch/CheerUtil';
-import { CheerState, CheerStateIsCustom, CheerStateIsOfficial, cheerStateToPredicate, predicateToCheerState } from './CheerState';
-import { CHEER_LOOKUP_MAP } from '../../../twitch/Cheers';
-
-// NB: This was created for "Edit" view, but we've forced it to work for "Create" view by hardcoding
-// values that don't actually come from the server.
-
-interface EventMatchPredicateBuilderComponentProps {
- // CANNOT BE CHANGED AFTER CREATION
- twitchEventCategory: TwitchEventCategory,
-
- // Original event match predicate from the server
- // (also immutable for sanity and because we lose information on UI changes)
- serverEventMatchPredicate: EventMatchPredicate,
-
- // Updates sent back up the tree
- updateModifiedEventMatchPredicate: (predicate: EventMatchPredicate) => void,
-};
-
-function EventMatchPredicateBuilderComponent(props: EventMatchPredicateBuilderComponentProps) {
- // ========== Core UI flow ==========
-
- const [bitsRuleType, setBitsRuleType] = useState(BitsRuleType.BitsCheermoteNameExactMatch);
- const [channelPointsRuleType, setChannelPointsRuleType] = useState(ChannelPointsRuleType.ChannelPointsRewardNameExactMatch);
-
- // ========== Cached Values for Editing ==========
-
- // A very complicated container that juggles bits and cheermote state.
- // The state transitions are complicated, and I've tried to document as best as possible.
- // TODO: This really needs some testing.
- const [cheerState, setCheerState] = useState({});
-
- // Used in:
- // ChannelPointsRewardNameExactMatch
- const [rewardName, setRewardName] = 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 newBitsRuleType = BitsRuleType.BitsCheermoteNameExactMatch;
- let newChannelPointsRuleType = ChannelPointsRuleType.ChannelPointsRewardNameExactMatch;
-
- let newCheerState = predicateToCheerState(props.serverEventMatchPredicate);
-
- switch (props.twitchEventCategory) {
- case TwitchEventCategory.Bits:
- if (!!props.serverEventMatchPredicate.bits_cheermote_name_exact_match) {
- newBitsRuleType = BitsRuleType.BitsCheermoteNameExactMatch;
- } else if (!!props.serverEventMatchPredicate.bits_cheermote_prefix_spend_threshold) {
- newBitsRuleType = BitsRuleType.BitsCheermotePrefixSpendThreshold;
- } else if (!!props.serverEventMatchPredicate.bits_spend_threshold) {
- newBitsRuleType = BitsRuleType.BitsSpendThreshold;
- }
- break;
- case TwitchEventCategory.ChannelPoints: // NB: Only one rule type
- if (!!props.serverEventMatchPredicate.channel_points_reward_name_exact_match) {
- setRewardName(props.serverEventMatchPredicate.channel_points_reward_name_exact_match.reward_name);
- newChannelPointsRuleType = ChannelPointsRuleType.ChannelPointsRewardNameExactMatch;
- }
- break;
- case TwitchEventCategory.ChatCommand: // TODO: Not yet supported
- default:
- break;
- }
-
- setCheerState(newCheerState);
-
- setBitsRuleType(newBitsRuleType);
- setChannelPointsRuleType(newChannelPointsRuleType);
-
- }, [props.twitchEventCategory, props.serverEventMatchPredicate]);
-
- // TODO: We need to recalculate the model.
- const handleChangedBitsRuleType = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- const newRuleType = value as BitsRuleType;
-
- let predicate = cheerStateToPredicate(cheerState, newRuleType);
-
- setBitsRuleType(newRuleType);
- props.updateModifiedEventMatchPredicate(predicate);
-
- return true;
- }
-
- const handleChangedChannelPointsRuleType = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- const ruleType = value as ChannelPointsRuleType;
- setChannelPointsRuleType(ruleType);
- return true;
- }
-
- const handleChangedCheerPrefix = (newPrefix: string) => {
- // 1) Update Cheer State
- // 2) Update NewEventMatchPredicate
- let newCheerState : CheerState = {};
-
- let maybeCheerPrefix = CHEER_LOOKUP_MAP.get(newPrefix || '');
-
- if (!!maybeCheerPrefix) {
- newCheerState = {
- cheerPrefix: maybeCheerPrefix,
- bits: cheerState.bits, // Unchanged
- }
- } else {
- newCheerState = {
- cheerFull: newPrefix,
- bits: cheerState.bits, // Unchanged
- }
- }
-
- let predicate = cheerStateToPredicate(newCheerState, bitsRuleType);
-
- setCheerState(newCheerState);
- props.updateModifiedEventMatchPredicate(predicate);
- }
-
- const handleChangedCheerName = (name: string) => {
- // 1) Update Cheer State
- // 2) Update NewEventMatchPredicate
- let newCheerState : CheerState = {};
-
- let { cheerPrefix, bitValue } = CheerUtil.parseCheerString(name);
-
- let newBitValue = bitValue;
- let maybeCheerPrefix = CHEER_LOOKUP_MAP.get(cheerPrefix || '');
-
- if (!!maybeCheerPrefix) {
- // Official prefix.
- newCheerState = {
- cheerPrefix: maybeCheerPrefix,
- bits: newBitValue,
- }
- } else if (!!cheerPrefix) {
- // Custom prefix.
-
- // Test if hypothetically we should retain an independent of name bit value.
- // Hypothetically, Sus13 -> Sus12, but the bit value is 5000 in a separate field.
- // We don't want to repalace with 13.
- if (CheerStateIsCustom(cheerState)) {
- let { cheerPrefix, bitValue } = CheerUtil.parseCheerString(cheerState.cheerFull || '');
-
- if ((!!cheerPrefix && !!bitValue) // We have a complete name
- && (bitValue !== cheerState.bits)) // The bit value is not independent of the cheer name, as in eg. (Foobar1000, 1000)
- {
- // Keep the old value.
- newBitValue = cheerState.bits;
- }
- }
-
- newCheerState = {
- cheerFull: name, // Should always be the full value
- bits: newBitValue,
- }
- }
-
- let predicate = cheerStateToPredicate(newCheerState, bitsRuleType);
-
- setCheerState(newCheerState);
- props.updateModifiedEventMatchPredicate(predicate);
- }
-
- const handleChangedMinimumBitsSpent = (minimumSpent: number) => {
- // 1) Update Cheer State
- // 2) Update NewEventMatchPredicate
- let newCheerState = {};
-
- if (CheerStateIsOfficial(cheerState)) {
- newCheerState = {
- cheerPrefix: cheerState.cheerPrefix, // Unchanged
- bits: minimumSpent,
- }
- } else if (CheerStateIsCustom(cheerState)) {
- // Test if the cheer name includes numbers, eg. 'Sus12', or '1984', that differ from bit spend
- let { cheerPrefix, bitValue } = CheerUtil.parseCheerString(cheerState.cheerFull || '');
- let cheerFull = cheerState.cheerFull;
-
- if (!!cheerPrefix && !!bitValue && bitValue === cheerState.bits) {
- // Let's replace the cheer.
- cheerFull = CheerUtil.joinCheerAndPrefix(cheerPrefix, minimumSpent);
- }
-
- newCheerState = {
- cheerFull: cheerFull,
- bits: minimumSpent,
- }
-
- } else {
- newCheerState = {
- bits: minimumSpent,
- }
- }
-
- let predicate = cheerStateToPredicate(newCheerState, bitsRuleType);
-
- setCheerState(newCheerState);
- props.updateModifiedEventMatchPredicate(predicate);
- }
-
- // TODO
- const handleChangedRewardName = (name: string) => {
- let predicate : EventMatchPredicate = {};
-
- switch (channelPointsRuleType) {
- case ChannelPointsRuleType.ChannelPointsRewardNameExactMatch:
- predicate.channel_points_reward_name_exact_match = {
- reward_name: name, // New value
- }
- break;
- }
-
- props.updateModifiedEventMatchPredicate(predicate);
- }
-
- let ruleTypeSelect = <>>
- let matchingRulesForm = <>>
-
- if (props.twitchEventCategory === TwitchEventCategory.Bits) {
- ruleTypeSelect = (
- <>
-
- Bits Spend Threshold
- Cheermote Name (Exact Match)
- Cheermote Prefix and Bits Spend Threshold
-
- >
- );
-
- switch (bitsRuleType) {
- case BitsRuleType.BitsCheermoteNameExactMatch:
- matchingRulesForm = ;
- break;
- case BitsRuleType.BitsCheermotePrefixSpendThreshold:
- matchingRulesForm =
- break;
- case BitsRuleType.BitsSpendThreshold:
- matchingRulesForm = ;
- break;
- }
-
- } else if (props.twitchEventCategory === TwitchEventCategory.ChannelPoints) {
- ruleTypeSelect = (
- <>
-
- Channel Points Reward Name (exact match)
-
- >
- );
-
- matchingRulesForm = ;
- }
-
- return (
- <>
- 1) Pick what to match on
-
-
-
-
-
-
- 2) Configure the matching
-
- {matchingRulesForm}
- >
- )
-}
-
-export { EventMatchPredicateBuilderComponent }
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermoteNameExactMatchForm.tsx b/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermoteNameExactMatchForm.tsx
deleted file mode 100644
index 0ff460078f..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermoteNameExactMatchForm.tsx
+++ /dev/null
@@ -1,169 +0,0 @@
-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, 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 {
- cheerState: CheerState,
- updateCheerName: (cheerNameOrPrefix: string) => void,
-};
-
-function BitsCheermoteNameExactMatchForm(props: BitsCheermoteNameExactMatchProps) {
- // The two dropdowns
- const [cheerPrefix, setCheerPrefix] = useState('');
- const [bitsValue, setBitsValue] = useState(1);
- // The freeform text input
- 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 newPrefix = '';
- let newFullName = '';
- let newBits = 1;
- let hasBits = false;
-
- if (!!props.cheerState.bits && !isNaN(props.cheerState.bits) && props.cheerState.bits > 0) {
- newBits = props.cheerState.bits;
- hasBits = true;
- }
-
- if (CheerStateIsOfficial(props.cheerState)) {
- if (!!props.cheerState.cheerPrefix) {
- newPrefix = CHEER_PREFIX_TO_STRING_MAP.get((props.cheerState.cheerPrefix)) || '';
- }
-
- if (hasBits) {
- newFullName = `${newPrefix}${newBits}`;
- } else {
- newFullName = newPrefix;
- }
-
- } else if (CheerStateIsCustom(props.cheerState)) {
- newFullName = props.cheerState.cheerFull || '';
- }
-
- setCheerPrefix(newPrefix);
- setBitsValue(newBits);
- setManualCheerValue(newFullName);
-
- }, [props.cheerState]);
-
- const updateCheerPrefix = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- setCheerPrefix(value);
- recalcuateFieldValue(value, bitsValue);
- return true;
- }
-
- const updateBitsValue = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- const numericValue = parseInt(value);
- if (!isNaN(numericValue)) {
- // NB: The form shouldn't yield NaN, but just in case.
- setBitsValue(numericValue);
- recalcuateFieldValue(cheerPrefix, numericValue);
- }
- return true;
- }
-
- const updateTextCheerValue = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLInputElement).value;
-
- // Cheer name is the full value, eg. 'Corgo100'
- const matches = value.trim().match(CHEER_REGEX)
-
- if (!!matches && matches.length > 1) {
- setCheerPrefix(matches[1]); // First match group
- if (matches.length === 3 && matches[2] !== undefined) {
- // NB: Second match group can be 'undefined' if no number is present. (Zero-width matching?)
- const maybeBits = parseInt(matches[2]);
- if (!isNaN(maybeBits)) {
- setBitsValue(maybeBits); // Second match group
- }
- }
- }
-
- setManualCheerValue(value);
- props.updateCheerName(value);
- return true;
- }
-
- // When the dropdowns are used, replace any manual text entry.
- const recalcuateFieldValue = (prefix: string, bits: number) => {
- const cheerValue = `${prefix}${bits}`;
- setManualCheerValue(cheerValue);
- props.updateCheerName(cheerValue);
- }
-
- return (
- <>
-
-
-
Pick the cheer
-
-
- Select cheer...
- {CHEER_PREFIXES.map(cheerPrefix => {
- return (
- {cheerPrefix}
- );
- })}
-
-
-
-
-
Then the bit value
-
-
-
- {CHEER_BIT_LEVELS.map(level => {
- return (
- {level}
- );
- })}
-
-
-
-
-
-
To match against this (or set something custom)
-
-
-
-
-
-
-
-
- >
- )
-}
-
-export { BitsCheermoteNameExactMatchForm }
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermotePrefixSpendThresholdForm.tsx b/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermotePrefixSpendThresholdForm.tsx
deleted file mode 100644
index 362eca4550..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermotePrefixSpendThresholdForm.tsx
+++ /dev/null
@@ -1,195 +0,0 @@
-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, CHEER_PREFIX_TO_STRING_MAP } from '../../../../twitch/Cheers';
-import { CheerState, CheerStateIsCustom, CheerStateIsOfficial } from '../CheerState';
-
-interface BitsCheermotePrefixSpendThresholdProps {
- cheerState: CheerState,
-
- updateCheerPrefix: (cheerNameOrPrefix: string) => void,
- updateMinimumBitsSpent: (minimumSpent: number) => void,
-};
-
-function BitsCheermotePrefixSpendThresholdForm(props: BitsCheermotePrefixSpendThresholdProps) {
- const [cheerPrefix, setCheerPrefix] = useState('');
- const [customCheerPrefix, setCustomCheerPrefix] = useState('');
-
- 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)
- // 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 newPrefix = '';
- let newCustomPrefix = '';
- let newBits = 1;
- let newCustomBits = 1;
-
- if (!!props.cheerState.bits && !isNaN(props.cheerState.bits) && props.cheerState.bits > 0) {
- newBits = props.cheerState.bits;
- newCustomBits = props.cheerState.bits;
- }
-
- 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)) || '';
- }
- } else if (CheerStateIsCustom(props.cheerState)) {
- newCustomPrefix = props.cheerState.cheerFull || '';
- }
-
- setCheerPrefix(newPrefix);
- setCustomCheerPrefix(newCustomPrefix);
- setBitsValue(newBits);
- setCustomBitsValue(newCustomBits);
-
- }, [props.cheerState]);
-
- const updateCheerPrefix = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- setCheerPrefix(value);
- setCustomCheerPrefix(value);
- props.updateCheerPrefix(value);
- return true;
- }
-
- const updateTextCheerValue = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLInputElement).value;
- setCheerPrefix(value);
- setCustomCheerPrefix(value);
- props.updateCheerPrefix(value);
- return true;
- }
-
- const handleBitSelect = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- const numericValue = parseInt(value);
- setBitsValue(numericValue);
- setCustomBitsValue(numericValue);
- props.updateMinimumBitsSpent(numericValue);
- return true;
- }
- const handleCustomBitsValueUpdate = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLInputElement).value;
- const numericValue = parseInt(value);
- setCustomBitsValue(numericValue);
- props.updateMinimumBitsSpent(numericValue);
- return true;
- }
-
- return (
- <>
-
-
- This rule type requires both the cheermote
- to match and the bits spent to be equal or greater than the amount specified.
-
-
-
- Cheermote matching
-
-
-
-
Pick the cheer prefix
-
-
- Select cheer...
- {CHEER_PREFIXES.map(cheerPrefix => {
- return (
- {cheerPrefix}
- );
- })}
-
-
-
-
-
To match against this (or set something custom)
-
-
-
-
-
-
-
-
-
-
-
- The "Cheer prefix" can be an actual cheer prefix, such as "SeemsGood", but it can also be an arbitrary
- string value. So if you make it "ManBearPig", that will be searched for in cheers.
-
- Cheermotes will be removed from the words used in TTS, but currently custom strings will not (though
- this may change in the future).
-
-
-
- Bits spent
-
-
-
-
Preset thresholds
-
-
-
- {CHEER_BIT_LEVELS.map(level => {
- return (
- Spend at least {level} bits
- );
- })}
-
-
-
-
-
-
Or a custom minimum amount.
-
-
-
-
-
-
-
-
-
-
- Remember that all Cheermotes in a single message add up,
- eg. “Cheer100 + Cheer100 + Party100” is 300 bits.
- A custom amount should give you flexibility to charge, say, 300 or 500 bits.
- Or 420. Whatever you want.
-
-
- >
- )
-}
-
-export { BitsCheermotePrefixSpendThresholdForm }
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsSpendThresholdForm.tsx b/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsSpendThresholdForm.tsx
deleted file mode 100644
index 0474a4754b..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsSpendThresholdForm.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-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 {
- cheerState: CheerState,
- updateMinimumBitsSpent: (minimumSpent: number) => void,
-};
-
-function BitsSpendThresholdForm(props: BitsSpendThresholdFormProps) {
- // The dropdown
- const [bitsValue, setBitsValue] = useState(1);
- // The freeform text input
- 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 (!!props.cheerState.bits && !isNaN(props.cheerState.bits) && props.cheerState.bits > 0) {
- setBitsValue(props.cheerState.bits);
- setCustomAmount(props.cheerState.bits);
- }
- }, [props.cheerState]);
-
- const handleBitSelect = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- const numericValue = parseInt(value);
-
- if (!isNaN(numericValue) && numericValue > 0) {
- // Only update on valid positive integers
- setBitsValue(numericValue);
- setCustomAmount(numericValue);
- props.updateMinimumBitsSpent(numericValue);
- }
-
- return true;
- }
-
- const handleCustomAmountUpdate = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLInputElement).value;
- const numericValue = parseInt(value);
-
- if (!isNaN(numericValue) && numericValue > 0) {
- // Only update on valid positive integers
- setBitsValue(numericValue);
- setCustomAmount(numericValue);
- props.updateMinimumBitsSpent(numericValue);
- }
-
- return true;
- }
-
- return (
- <>
-
-
-
Preset thresholds
-
-
-
- Choose or type...
- {CHEER_BIT_LEVELS.map(level => {
- return (
- Spend at least {level} bits
- );
- })}
-
-
-
-
-
-
Or a custom minimum amount.
-
-
-
-
-
-
-
-
-
-
- Remember that all Cheermotes in a single message add up,
- eg. “Cheer100 + Cheer100 + Party100” is 300 bits.
- A custom amount should give you flexibility to charge, say, 300 or 500 bits.
- Or 420. Whatever you want.
-
-
- >
- )
-}
-
-export { BitsSpendThresholdForm }
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/ChannelPointsRewardNameExactMatchForm.tsx b/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/ChannelPointsRewardNameExactMatchForm.tsx
deleted file mode 100644
index d489c6024e..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/ChannelPointsRewardNameExactMatchForm.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React, { useEffect, useState } from 'react';
-
-interface ChannelPointsRewardNameExactMatchProps {
- rewardName: string,
- updateRewardName: (name: string) => void,
-};
-
-function ChannelPointsRewardNameExactMatchForm(props: ChannelPointsRewardNameExactMatchProps) {
- const [rewardName, setRewardName] = 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(() => {
- setRewardName(props.rewardName);
- }, [props.rewardName]);
-
- const updateRewardName = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLInputElement).value;
- setRewardName(value);
- props.updateRewardName(value);
- return true;
- }
-
- return (
- <>
-
-
Match the following reward name
-
-
-
-
- >
- )
-}
-
-export { ChannelPointsRewardNameExactMatchForm }
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/types/BitsRuleType.ts b/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/types/BitsRuleType.ts
deleted file mode 100644
index b89d958ade..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/types/BitsRuleType.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-
-// For bits / cheermotes
-export enum BitsRuleType {
- BitsCheermoteNameExactMatch = "bits_cheermote_name_exact_match",
- BitsCheermotePrefixSpendThreshold = "bits_cheermote_prefix_spend_threshold",
- BitsSpendThreshold = "bits_spend_threshold",
-}
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/types/ChannelPointsRuleType.ts b/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/types/ChannelPointsRuleType.ts
deleted file mode 100644
index d4379b3d7b..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/event_match_predicate_builder/types/ChannelPointsRuleType.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-
-// For channel points
-export enum ChannelPointsRuleType {
- ChannelPointsRewardNameExactMatch = 'channel_points_reward_name_exact_match',
-}
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/event_response_builder/BuildEventResponse.ts b/packages/dashboard.power.stream/src/pages/tts_configs/event_response_builder/BuildEventResponse.ts
deleted file mode 100644
index 66b2d25aaa..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/event_response_builder/BuildEventResponse.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { EventResponse } from "@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventResponse";
-import { EventResponseType } from "./EventResponseType";
-
-export function buildEventResponse(ttsModelTokens: string[], eventResponseType: EventResponseType) :EventResponse {
- let eventResponse : EventResponse = {};
-
- switch (eventResponseType) {
- case EventResponseType.TtsSingleVoice:
- let ttsModelToken = '';
- if (ttsModelTokens.length > 0) {
- ttsModelToken = ttsModelTokens[0];
- }
- eventResponse.tts_single_voice = {
- tts_model_token: ttsModelToken,
- }
- break;
- case EventResponseType.TtsRandomVoice:
- eventResponse.tts_random_voice= {
- tts_model_tokens: [...ttsModelTokens],
- }
- break;
- }
-
- return eventResponse;
-}
\ No newline at end of file
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/event_response_builder/EventResponseComponent.tsx b/packages/dashboard.power.stream/src/pages/tts_configs/event_response_builder/EventResponseComponent.tsx
deleted file mode 100644
index 6bdd77afb5..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/event_response_builder/EventResponseComponent.tsx
+++ /dev/null
@@ -1,155 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
-import { TtsSingleVoiceForm } from './TtsSingleVoiceForm';
-import { EventResponseType } from './EventResponseType';
-import { EventResponse } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventResponse';
-import { buildEventResponse } from './BuildEventResponse';
-import { TtsRandomVoiceForm } from './TtsRandomVoiceForm';
-
-// NB: This was created for "Edit" view, but we've forced it to work for "Create" view by hardcoding
-// values that don't actually come from the server.
-
-interface EventResponseComponentProps {
- // Original response from server.
- // (also immutable for sanity and because we may lose info on UI changes)
- serverEventResponse: EventResponse,
-
- // Updates sent back up the tree
- updateModifiedEventResponse: (response: EventResponse) => void,
-
- // FakeYou voices
- allTtsModels: TtsModelListItem[],
- allTtsModelsByToken: Map,
-};
-
-function EventResponseComponent(props: EventResponseComponentProps) {
- // ========== Core UI flow ==========
-
- const [eventResponseType, setEventResponseType] = useState(EventResponseType.TtsSingleVoice);
-
- // ========== Cached Values for Editing ==========
-
- const [selectedTtsModelTokens, setSelectedTtsModelTokens] = 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 newEventResponseType = EventResponseType.TtsSingleVoice;
- let modelTokens : string[] = [];
-
- if (!!props.serverEventResponse.tts_single_voice) {
- newEventResponseType = EventResponseType.TtsSingleVoice;
-
- if (!!props.serverEventResponse.tts_single_voice.tts_model_token) {
- modelTokens.push(props.serverEventResponse.tts_single_voice.tts_model_token)
- }
-
- } else if (!!props.serverEventResponse.tts_random_voice) {
- newEventResponseType = EventResponseType.TtsRandomVoice;
-
- if (!!props.serverEventResponse.tts_random_voice.tts_model_tokens &&
- props.serverEventResponse.tts_random_voice.tts_model_tokens.length > 0)
- {
- modelTokens = [...props.serverEventResponse.tts_random_voice.tts_model_tokens];
- }
- }
-
- setEventResponseType(newEventResponseType);
- setSelectedTtsModelTokens(modelTokens);
-
- }, [props.serverEventResponse]);
-
- const handleChangedEventResponseType = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- const newResponseType = value as EventResponseType;
-
- const newEventResponse = buildEventResponse(selectedTtsModelTokens, newResponseType);
-
- setEventResponseType(newResponseType);
- props.updateModifiedEventResponse(newEventResponse);
-
- return true;
- }
-
- const handleChangedTtsModelToken = (token: string) => {
- // We may be only updating to a single token, but we want to be able to navigate back to
- // the "random voices" view, etc.
- let updatedTokens = [...selectedTtsModelTokens];
-
- if (updatedTokens.length > 0) {
- updatedTokens[0] = token;
- } else if (updatedTokens.length === 0) {
- updatedTokens.push(token);
- }
-
- const newEventResponse = buildEventResponse(updatedTokens, eventResponseType);
-
- setSelectedTtsModelTokens(updatedTokens);
- props.updateModifiedEventResponse(newEventResponse);
- }
-
- const handleChangedTtsModelTokens = (tokens: string[]) => {
- let updatedTokens = [...tokens];
-
- const newEventResponse = buildEventResponse(updatedTokens, eventResponseType);
-
- setSelectedTtsModelTokens(updatedTokens);
- props.updateModifiedEventResponse(newEventResponse);
- }
-
- let responseForm = <>>
-
- switch (eventResponseType) {
- case EventResponseType.TtsSingleVoice:
- responseForm =
- break;
- case EventResponseType.TtsRandomVoice:
- responseForm =
- break;
- }
-
- return (
- <>
- 3) Pick how to respond
-
-
-
Response Type
-
-
-
- Respond with a TTS voice
- Respond with a Random TTS voice from list
-
-
-
-
-
-
-
-
- 4) Configure the response
-
- {responseForm}
-
-
-
-
- >
- )
-}
-
-export { EventResponseComponent }
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/event_response_builder/EventResponseType.ts b/packages/dashboard.power.stream/src/pages/tts_configs/event_response_builder/EventResponseType.ts
deleted file mode 100644
index 4131886efa..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/event_response_builder/EventResponseType.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-
-export enum EventResponseType {
- TtsSingleVoice = "tts_single_voice",
- TtsRandomVoice = "tts_random_voice",
-}
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/event_response_builder/TtsRandomVoiceForm.tsx b/packages/dashboard.power.stream/src/pages/tts_configs/event_response_builder/TtsRandomVoiceForm.tsx
deleted file mode 100644
index 41b72bbc79..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/event_response_builder/TtsRandomVoiceForm.tsx
+++ /dev/null
@@ -1,206 +0,0 @@
-import React, { useEffect } from 'react';
-import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
-import { FakeYouExternalLink } from '@storyteller/components/src/elements/FakeYouExternalLink';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faPlus, faTrash } from '@fortawesome/free-solid-svg-icons';
-
-interface TtsRandomVoiceFormProps {
- selectedTtsModelTokens: string[],
- updateSelectedTtsModelTokens: (tokens: string[]) => void,
-
- // FakeYou voices
- allTtsModels: TtsModelListItem[],
- allTtsModelsByToken: Map,
-};
-
-function TtsRandomVoiceForm(props: TtsRandomVoiceFormProps) {
- // 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(() => {
- }, [props.selectedTtsModelTokens]);
-
- const handleAddVoice = (ev: React.FormEvent) : boolean => {
- ev.preventDefault();
-
- let newModelTokens = [...props.selectedTtsModelTokens];
-
- newModelTokens.push('');
-
- props.updateSelectedTtsModelTokens(newModelTokens);
-
- return true;
- }
-
- const handleModelSelect = (ttsModelToken: string, voiceIndex: number) => {
- let newModelTokens = [...props.selectedTtsModelTokens];
-
- if (voiceIndex >= newModelTokens.length) {
- return;
- }
-
- newModelTokens[voiceIndex] = ttsModelToken;
- props.updateSelectedTtsModelTokens(newModelTokens);
- }
-
- const handleModelDelete = (voiceIndex: number) => {
- let newModelTokens = [...props.selectedTtsModelTokens];
-
- if (newModelTokens.length <= 1 || voiceIndex >= newModelTokens.length) {
- // We must have at least one item in the list
- return;
- }
-
- newModelTokens.splice(voiceIndex, 1);
-
- props.updateSelectedTtsModelTokens(newModelTokens);
- }
-
- const deleteDisabled = props.selectedTtsModelTokens.length < 2;
-
- let selectBoxes : JSX.Element[] = [];
-
- // No tokens - show one box (unselected)
- // One or more tokens - show boxes for each (with selections)
-
- props.selectedTtsModelTokens.forEach((token, index) => {
- selectBoxes.push(
-
- )
- });
-
- if (selectBoxes.length === 0) {
- selectBoxes.push(
-
- )
- }
-
- return (
- <>
- {selectBoxes}
-
-
- Add Additional Voice
-
-
-
-
-
-
- Check out the voices at FakeYou to search for voices, experiment with them,
- and see how they sound. Sorry that this list is a nightmare to navigate. It will improve over time.
-
-
- >
- )
-}
-
-interface VoiceDropdownProps {
- // Actual token or empty string
- selectedTtsModelToken: string,
-
- // Position in the list, 0-indexed
- voiceIndex: number,
-
- // Callbacks
- handleModelSelect: (token: string, index: number) => void,
- handleModelDelete: (index: number) => void,
-
- // Whether to disable the delete button
- deleteDisabled: boolean,
-
- // FakeYou voices
- allTtsModels: TtsModelListItem[],
- allTtsModelsByToken: Map,
-}
-
-function VoiceDropdown(props: VoiceDropdownProps) {
-
- const handleSelectChange = (ev: React.FormEvent) : boolean => {
- const maybeModelToken = (ev.target as HTMLSelectElement).value;
-
- if (!maybeModelToken) {
- // Clear selection
- props.handleModelSelect(maybeModelToken, props.voiceIndex);
- return true;
- }
-
- if (!!maybeModelToken && !props.allTtsModelsByToken.has(maybeModelToken)) {
- return false;
- }
-
- props.handleModelSelect(maybeModelToken, props.voiceIndex);
- return true;
- }
-
- const handleRemoveClick = (ev: React.FormEvent) : boolean => {
- props.handleModelDelete(props.voiceIndex);
- return true;
- }
-
- return (
-
-
{props.voiceIndex+1}. TTS Voice Model (used as a Random Voice)
-
-
-
-
-
-
-
- Select a voice...
- {props.allTtsModels.map(ttsModel => {
- return (
- {ttsModel.title}
- );
- })}
-
-
-
-
-
- Remove
-
-
-
-
-
-
- );
-}
-
-export { TtsRandomVoiceForm }
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/event_response_builder/TtsSingleVoiceForm.tsx b/packages/dashboard.power.stream/src/pages/tts_configs/event_response_builder/TtsSingleVoiceForm.tsx
deleted file mode 100644
index e3a3fbdd4d..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/event_response_builder/TtsSingleVoiceForm.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
-import { FakeYouExternalLink } from '@storyteller/components/src/elements/FakeYouExternalLink';
-
-interface TtsSingleVoiceFormProps {
- selectedTtsModelTokens: string[],
- updateSelectedTtsModelToken: (token: string) => void,
-
- // FakeYou voices
- allTtsModels: TtsModelListItem[],
- allTtsModelsByToken: Map,
-};
-
-function TtsSingleVoiceForm(props: TtsSingleVoiceFormProps) {
- const [ttsModelToken, setTtsModelToken] = 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(() => {
- if (props.selectedTtsModelTokens.length > 0) {
- setTtsModelToken(props.selectedTtsModelTokens[0]);
- }
- }, [props.selectedTtsModelTokens]);
-
- const handleModelSelect = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLSelectElement).value;
-
- if (!!value && !props.allTtsModelsByToken.has(value)) {
- return false;
- }
-
- setTtsModelToken(value);
- props.updateSelectedTtsModelToken(value);
- return true;
- }
-
- return (
- <>
-
-
TTS Voice Model
-
-
-
- Select a voice...
- {props.allTtsModels.map(ttsModel => {
- return (
- {ttsModel.title}
- );
- })}
-
-
-
-
-
-
-
- We apologize that this list is a nightmare to navigate right now. It will improve over time.
- In the mean time, check out the voices at FakeYou to
- find the voices you want, experiment with them, and see which ones work best for your stream. The
- names of the voices at FakeYou are the same as in this dropdown, so you can search at FakeYou, then
- add them here when you're done looking.
-
- >
- )
-}
-
-export { TtsSingleVoiceForm }
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/rule_cards/ReorderableTwitchEventRuleElement.tsx b/packages/dashboard.power.stream/src/pages/tts_configs/rule_cards/ReorderableTwitchEventRuleElement.tsx
deleted file mode 100644
index 7c9cf14fa8..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/rule_cards/ReorderableTwitchEventRuleElement.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-import React from 'react';
-import { TwitchEventRule } from '@storyteller/components/src/api/storyteller/twitch_event_rules/ListTwitchEventRules';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faArrowDown, faArrowUp, faExternalLinkAlt, } from '@fortawesome/free-solid-svg-icons';
-import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
-
-interface Props {
- rule: TwitchEventRule,
-
- // Index of the rule in the parents' list
- ruleIndex: number,
-
- // Update callbacks
- handleMoveUp: (index: number) => void,
- handleMoveDown: (index: number) => void,
-
- // FakeYou voices
- allTtsModelsByToken: Map,
-}
-
-function ReorderableTwitchEventRuleElement(props: Props) {
- let subtitle = <>>;
- let description = <>>;
-
- if (props.rule.event_match_predicate.bits_cheermote_name_exact_match !== undefined) {
- subtitle = (
- <>Cheermote name matches "{props.rule.event_match_predicate.bits_cheermote_name_exact_match.cheermote_name}">
- );
- } else if (props.rule.event_match_predicate.bits_cheermote_prefix_spend_threshold !== undefined) {
- subtitle = (
- <>
- Cheermote prefix is "{props.rule.event_match_predicate.bits_cheermote_prefix_spend_threshold.cheermote_prefix}"
- and spend at least {props.rule.event_match_predicate.bits_cheermote_prefix_spend_threshold.minimum_bits_spent} bits
- >
- );
- } else if (props.rule.event_match_predicate.bits_spend_threshold !== undefined) {
- subtitle = (
- <>
- Spend at least {props.rule.event_match_predicate.bits_spend_threshold.minimum_bits_spent} bits
- >
- );
- } else if (props.rule.event_match_predicate.channel_points_reward_name_exact_match !== undefined) {
- subtitle = (
- <>
- Reward name matches "{props.rule.event_match_predicate.channel_points_reward_name_exact_match.reward_name}"
- >
- );
- }
-
- if (props.rule.event_response.tts_single_voice !== undefined) {
- const token = props.rule.event_response.tts_single_voice.tts_model_token;
-
- let model = props.allTtsModelsByToken.get(token);
- let modelName = token;
-
- if (!!model) {
- modelName = model.title;
- }
-
- let link = `https://fakeyou.com/tts/${token}`;
-
- description = (
- <>
- TTS with voice: {modelName}
- >
- );
- } else if (props.rule.event_response.tts_random_voice !== undefined) {
- const tokens = props.rule.event_response.tts_random_voice.tts_model_tokens;
-
- let modelNameAndLinks = tokens.map((token, index) => {
- let model = props.allTtsModelsByToken.get(token);
- let link = `https://fakeyou.com/tts/${token}`;
-
- let modelName = token;
- if (!!model) {
- modelName = model.title;
- }
-
- return (
-
- {modelName}
-
- );
- });
-
- description = (
- <>
- TTS with a random voice from the following {modelNameAndLinks.length} voice(s):
-
- >
- );
- }
-
- return (
-
-
-
-
- {subtitle}
-
-
- {description}
-
-
-
-
-
- props.handleMoveUp(props.ruleIndex)} className="button is-ghost">
- Up
-
-
-
-
-
- props.handleMoveDown(props.ruleIndex)} className="button is-ghost">
- Down
-
-
-
-
-
-
-
- )
-}
-
-export { ReorderableTwitchEventRuleElement }
\ No newline at end of file
diff --git a/packages/dashboard.power.stream/src/pages/tts_configs/rule_cards/TwitchEventRuleElement.tsx b/packages/dashboard.power.stream/src/pages/tts_configs/rule_cards/TwitchEventRuleElement.tsx
deleted file mode 100644
index 174e721bb1..0000000000
--- a/packages/dashboard.power.stream/src/pages/tts_configs/rule_cards/TwitchEventRuleElement.tsx
+++ /dev/null
@@ -1,134 +0,0 @@
-import React from 'react';
-import { TwitchEventRule } from '@storyteller/components/src/api/storyteller/twitch_event_rules/ListTwitchEventRules';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faEdit, faExternalLinkAlt, faTrash } from '@fortawesome/free-solid-svg-icons';
-import { Link } from 'react-router-dom';
-import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
-
-interface Props {
- rule: TwitchEventRule,
- allTtsModelsByToken: Map,
- hideButtons?: boolean,
-}
-
-function TwitchEventRuleElement(props: Props) {
- const hideButtons = !!props.hideButtons;
-
- let subtitle = <>>;
- let description = <>>;
-
- if (props.rule.event_match_predicate.bits_cheermote_name_exact_match !== undefined) {
- subtitle = (
- <>Cheermote name matches "{props.rule.event_match_predicate.bits_cheermote_name_exact_match.cheermote_name}">
- );
- } else if (props.rule.event_match_predicate.bits_cheermote_prefix_spend_threshold !== undefined) {
- subtitle = (
- <>
- Cheermote prefix is "{props.rule.event_match_predicate.bits_cheermote_prefix_spend_threshold.cheermote_prefix}"
- and spend at least {props.rule.event_match_predicate.bits_cheermote_prefix_spend_threshold.minimum_bits_spent} bits
- >
- );
- } else if (props.rule.event_match_predicate.bits_spend_threshold !== undefined) {
- subtitle = (
- <>
- Spend at least {props.rule.event_match_predicate.bits_spend_threshold.minimum_bits_spent} bits
- >
- );
- } else if (props.rule.event_match_predicate.channel_points_reward_name_exact_match !== undefined) {
- subtitle = (
- <>
- Reward name matches "{props.rule.event_match_predicate.channel_points_reward_name_exact_match.reward_name}"
- >
- );
- }
-
- if (props.rule.event_response.tts_single_voice !== undefined) {
- const token = props.rule.event_response.tts_single_voice.tts_model_token;
-
- let model = props.allTtsModelsByToken.get(token);
- let modelName = token;
-
- if (!!model) {
- modelName = model.title;
- }
-
- let link = `https://fakeyou.com/tts/${token}`;
-
- description = (
- <>
- TTS with voice: {modelName}
- >
- );
- } else if (props.rule.event_response.tts_random_voice !== undefined) {
- const tokens = props.rule.event_response.tts_random_voice.tts_model_tokens;
-
- let modelNameAndLinks = tokens.map((token, index) => {
- let model = props.allTtsModelsByToken.get(token);
- let link = `https://fakeyou.com/tts/${token}`;
-
- let modelName = token;
- if (!!model) {
- modelName = model.title;
- }
-
- return (
-
- {modelName}
-
- );
- });
-
- description = (
- <>
- TTS with a random voice from the following {modelNameAndLinks.length} voice(s):
-
- >
- );
- }
-
- const editUrl = `/tts_configs/edit/${props.rule.token}`;
- const deleteUrl = `/tts_configs/delete/${props.rule.token}`;
-
- let buttons = <>>;
- if (!hideButtons) {
- buttons = (
- <>
-
-
-
-
- Edit
-
-
-
-
-
-
- Delete
-
-
-
-
- >
- )
- }
-
- return (
-
-
-
-
- {subtitle}
-
-
- {description}
-
-
- {buttons}
-
-
-
- )
-}
-
-export { TwitchEventRuleElement }
\ No newline at end of file
diff --git a/packages/dashboard.power.stream/src/react-app-env.d.ts b/packages/dashboard.power.stream/src/react-app-env.d.ts
deleted file mode 100644
index 6431bc5fc6..0000000000
--- a/packages/dashboard.power.stream/src/react-app-env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-///
diff --git a/packages/dashboard.power.stream/src/reportWebVitals.ts b/packages/dashboard.power.stream/src/reportWebVitals.ts
deleted file mode 100644
index 49a2a16e0f..0000000000
--- a/packages/dashboard.power.stream/src/reportWebVitals.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { ReportHandler } from 'web-vitals';
-
-const reportWebVitals = (onPerfEntry?: ReportHandler) => {
- if (onPerfEntry && onPerfEntry instanceof Function) {
- import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
- getCLS(onPerfEntry);
- getFID(onPerfEntry);
- getFCP(onPerfEntry);
- getLCP(onPerfEntry);
- getTTFB(onPerfEntry);
- });
- }
-};
-
-export default reportWebVitals;
diff --git a/packages/dashboard.power.stream/src/setupTests.ts b/packages/dashboard.power.stream/src/setupTests.ts
deleted file mode 100644
index 8f2609b7b3..0000000000
--- a/packages/dashboard.power.stream/src/setupTests.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-// jest-dom adds custom jest matchers for asserting on DOM nodes.
-// allows you to do things like:
-// expect(element).toHaveTextContent(/react/i)
-// learn more: https://github.com/testing-library/jest-dom
-import '@testing-library/jest-dom';
diff --git a/packages/dashboard.power.stream/src/twitch/CheerUtil.ts b/packages/dashboard.power.stream/src/twitch/CheerUtil.ts
deleted file mode 100644
index c582542193..0000000000
--- a/packages/dashboard.power.stream/src/twitch/CheerUtil.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-
-const CHEER_REGEX = /^([A-Za-z]+)(\d+)?$/;
-
-interface ParsedCheer {
- cheerPrefix?: string,
- bitValue?: number,
-}
-
-export class CheerUtil {
-
- public static parseCheerString(cheerString: string) : ParsedCheer {
- const matches = cheerString.trim().match(CHEER_REGEX)
-
- let cheerPrefix = undefined;
- let bitValue = undefined;
-
- if (!!matches && matches.length > 1) {
- cheerPrefix = matches[1];
- if (matches.length === 3 && matches[2] !== undefined) {
- // NB(1): The second match group can be 'undefined' if no number is present. (Optional matching.)
- let maybeBits = parseInt(matches[2]);
- if (!isNaN(maybeBits) && maybeBits > 0) {
- bitValue = maybeBits;
- }
- }
- }
-
- return {
- cheerPrefix: cheerPrefix,
- bitValue: bitValue,
- };
- }
-
- public static joinCheerAndPrefix(cheerPrefix?: string, bitValue?: number) : string {
- return `${cheerPrefix || ''}${bitValue || ''}`;
- }
-}
\ No newline at end of file
diff --git a/packages/dashboard.power.stream/src/twitch/Cheers.ts b/packages/dashboard.power.stream/src/twitch/Cheers.ts
deleted file mode 100644
index ea0901fe74..0000000000
--- a/packages/dashboard.power.stream/src/twitch/Cheers.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-
-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[] = [
- "Cheer",
- "DoodleCheer",
- "BibleThump",
- "cheerwhal",
- "Corgo",
- "uni",
- "ShowLove",
- "Party",
- "SeemsGood",
- "Pride",
- "Kappa",
- "FrankerZ",
- "HeyGuys",
- "DansGame",
- "EleGiggle",
- "TriHard",
- "Kreygasm",
- "4Head",
- "SwiftRage",
- "NotLikeThis",
- "FailFish",
- "VoHiYo",
- "PJSalt",
- "MrDestructoid",
- "bday",
- "RIPCheer",
- "Shamrock",
- "BitBoss",
- "Streamlabs",
- "Muxy",
- "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];
diff --git a/packages/dashboard.power.stream/tsconfig.json b/packages/dashboard.power.stream/tsconfig.json
deleted file mode 100644
index a273b0cfc0..0000000000
--- a/packages/dashboard.power.stream/tsconfig.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "compilerOptions": {
- "target": "es5",
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
- ],
- "allowJs": true,
- "skipLibCheck": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "strict": true,
- "forceConsistentCasingInFileNames": true,
- "noFallthroughCasesInSwitch": true,
- "module": "esnext",
- "moduleResolution": "node",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "jsx": "react-jsx"
- },
- "include": [
- "src"
- ]
-}
diff --git a/packages/fakeyou.com/src/v2/view/pages/media/MediaPage.tsx b/packages/fakeyou.com/src/v2/view/pages/media/MediaPage.tsx
index 8f903f679f..b0421bd5cd 100644
--- a/packages/fakeyou.com/src/v2/view/pages/media/MediaPage.tsx
+++ b/packages/fakeyou.com/src/v2/view/pages/media/MediaPage.tsx
@@ -555,7 +555,7 @@ export default function MediaPage() {
}}
/>
) : null}
- { canAccessStudio && mediaFile?.media_type === MediaFileType.Video ? (
+ { canAccessStudio() && mediaFile?.media_type === MediaFileType.Video ? (
{
- const { isFound, match } = getLoader(
- webpackConfig,
- loaderByName("babel-loader")
- );
- if (isFound) {
- const include = Array.isArray(match.loader.include)
- ? match.loader.include
- : [match.loader.include];
-
- match.loader.include = include.concat(packages);
- }
- return webpackConfig;
- },
- },
-};
diff --git a/packages/power.stream-dashboard/package.json b/packages/power.stream-dashboard/package.json
deleted file mode 100644
index 9f7b057ac8..0000000000
--- a/packages/power.stream-dashboard/package.json
+++ /dev/null
@@ -1,56 +0,0 @@
-{
- "name": "@storyteller/power.stream-dashboard",
- "version": "0.1.0",
- "private": true,
- "dependencies": {
- "@craco/craco": "^6.4.3",
- "@fortawesome/fontawesome-svg-core": "^6.2.1",
- "@fortawesome/free-brands-svg-icons": "^6.2.1",
- "@fortawesome/free-solid-svg-icons": "^6.2.1",
- "@fortawesome/react-fontawesome": "^0.2.0",
- "@storyteller/components": "0.1.0",
- "@testing-library/jest-dom": "^5.16.1",
- "@testing-library/react": "^9.5.0",
- "@testing-library/user-event": "^13.5.0",
- "@types/jest": "^27.4.0",
- "@types/node": "^16.11.21",
- "@types/react": "^16.9.36",
- "@types/react-dom": "^16.9.8",
- "bulma": "^0.9.0",
- "craco": "^0.0.3",
- "react": "^16.13.1",
- "react-autocomplete": "^1.8.1",
- "react-dom": "^16.13.1",
- "react-router-dom": "^5.2.0",
- "react-scripts": "5.0.0",
- "react-wavesurfer.js": "^0.0.5",
- "typescript": "^4.5.5",
- "wavesurfer.js": "^6.2.0",
- "web-vitals": "^2.1.4"
- },
- "scripts": {
- "start": "craco start",
- "start-w": "set BROWSER=none && craco start",
- "build": "craco build",
- "test": "craco test",
- "eject": "craco eject"
- },
- "eslintConfig": {
- "extends": [
- "react-app",
- "react-app/jest"
- ]
- },
- "browserslist": {
- "production": [
- ">0.2%",
- "not dead",
- "not op_mini all"
- ],
- "development": [
- "last 1 chrome version",
- "last 1 firefox version",
- "last 1 safari version"
- ]
- }
-}
diff --git a/packages/power.stream-dashboard/public/assets/bits-icon.png b/packages/power.stream-dashboard/public/assets/bits-icon.png
deleted file mode 100644
index ee34dee7c7..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/bits-icon.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/channel-points-icon.png b/packages/power.stream-dashboard/public/assets/channel-points-icon.png
deleted file mode 100644
index 776faa183b..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/channel-points-icon.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/channel-points-icon.svg b/packages/power.stream-dashboard/public/assets/channel-points-icon.svg
deleted file mode 100644
index 68e2112891..0000000000
--- a/packages/power.stream-dashboard/public/assets/channel-points-icon.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/packages/power.stream-dashboard/public/assets/circle-1.png b/packages/power.stream-dashboard/public/assets/circle-1.png
deleted file mode 100644
index a283ec849d..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/circle-1.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/circle-2.png b/packages/power.stream-dashboard/public/assets/circle-2.png
deleted file mode 100644
index c75c2eb035..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/circle-2.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/cta-img.png b/packages/power.stream-dashboard/public/assets/cta-img.png
deleted file mode 100644
index b231b23a2b..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/cta-img.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/hanashi-mc.png b/packages/power.stream-dashboard/public/assets/hanashi-mc.png
deleted file mode 100644
index 5116162823..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/hanashi-mc.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/hero-audio-wave.png b/packages/power.stream-dashboard/public/assets/hero-audio-wave.png
deleted file mode 100644
index 4b35790786..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/hero-audio-wave.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/hero-bg-floor.png b/packages/power.stream-dashboard/public/assets/hero-bg-floor.png
deleted file mode 100644
index 12a16a61b5..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/hero-bg-floor.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/hero-kitsune.png b/packages/power.stream-dashboard/public/assets/hero-kitsune.png
deleted file mode 100644
index e47ba9b110..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/hero-kitsune.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/munis copy.svg b/packages/power.stream-dashboard/public/assets/munis copy.svg
deleted file mode 100644
index 57a3b0c548..0000000000
--- a/packages/power.stream-dashboard/public/assets/munis copy.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/packages/power.stream-dashboard/public/assets/munis.svg b/packages/power.stream-dashboard/public/assets/munis.svg
deleted file mode 100644
index 57a3b0c548..0000000000
--- a/packages/power.stream-dashboard/public/assets/munis.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/packages/power.stream-dashboard/public/assets/obs-icon.png b/packages/power.stream-dashboard/public/assets/obs-icon.png
deleted file mode 100644
index 49d44e144e..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/obs-icon.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/plus copy.svg b/packages/power.stream-dashboard/public/assets/plus copy.svg
deleted file mode 100644
index f060693945..0000000000
--- a/packages/power.stream-dashboard/public/assets/plus copy.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/packages/power.stream-dashboard/public/assets/plus.svg b/packages/power.stream-dashboard/public/assets/plus.svg
deleted file mode 100644
index f060693945..0000000000
--- a/packages/power.stream-dashboard/public/assets/plus.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/packages/power.stream-dashboard/public/assets/powerstream-logo.png b/packages/power.stream-dashboard/public/assets/powerstream-logo.png
deleted file mode 100644
index 7bc285ebe5..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/powerstream-logo.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/preview/monokuma.wav b/packages/power.stream-dashboard/public/assets/preview/monokuma.wav
deleted file mode 100644
index 5ce286edac..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/preview/monokuma.wav and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/preview/son-goku.wav b/packages/power.stream-dashboard/public/assets/preview/son-goku.wav
deleted file mode 100644
index 4b1f55dd6e..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/preview/son-goku.wav and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/preview/stan-lee.wav b/packages/power.stream-dashboard/public/assets/preview/stan-lee.wav
deleted file mode 100644
index 5537f691ed..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/preview/stan-lee.wav and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/preview/tracer.wav b/packages/power.stream-dashboard/public/assets/preview/tracer.wav
deleted file mode 100644
index 898010cb28..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/preview/tracer.wav and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/sonic-float.png b/packages/power.stream-dashboard/public/assets/sonic-float.png
deleted file mode 100644
index 09a235e97f..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/sonic-float.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/storyteller-logo.png b/packages/power.stream-dashboard/public/assets/storyteller-logo.png
deleted file mode 100644
index 6504997d28..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/storyteller-logo.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/title-shape.png b/packages/power.stream-dashboard/public/assets/title-shape.png
deleted file mode 100644
index d3b05b7586..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/title-shape.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/tracer-float.png b/packages/power.stream-dashboard/public/assets/tracer-float.png
deleted file mode 100644
index b5968b0e9c..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/tracer-float.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/twitch-icon.png b/packages/power.stream-dashboard/public/assets/twitch-icon.png
deleted file mode 100644
index ed6fd3fc5d..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/twitch-icon.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/assets/voices-icon.png b/packages/power.stream-dashboard/public/assets/voices-icon.png
deleted file mode 100644
index bf434e1bba..0000000000
Binary files a/packages/power.stream-dashboard/public/assets/voices-icon.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/favicon.ico b/packages/power.stream-dashboard/public/favicon.ico
deleted file mode 100644
index a11777cc47..0000000000
Binary files a/packages/power.stream-dashboard/public/favicon.ico and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/index.html b/packages/power.stream-dashboard/public/index.html
deleted file mode 100644
index 451613e776..0000000000
--- a/packages/power.stream-dashboard/public/index.html
+++ /dev/null
@@ -1,108 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- PowerStream - Stream TTS
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- You need to enable JavaScript to run this app.
-
-
-
-
-
diff --git a/packages/power.stream-dashboard/public/logo192.png b/packages/power.stream-dashboard/public/logo192.png
deleted file mode 100644
index fc44b0a379..0000000000
Binary files a/packages/power.stream-dashboard/public/logo192.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/logo512.png b/packages/power.stream-dashboard/public/logo512.png
deleted file mode 100644
index a4e47a6545..0000000000
Binary files a/packages/power.stream-dashboard/public/logo512.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/manifest.json b/packages/power.stream-dashboard/public/manifest.json
deleted file mode 100644
index 4a14ea6044..0000000000
--- a/packages/power.stream-dashboard/public/manifest.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "short_name": "Storyteller",
- "name": "Stream tech",
- "icons": [
- {
- "src": "favicon.ico",
- "sizes": "64x64 32x32 24x24 16x16",
- "type": "image/x-icon"
- },
- {
- "src": "logo192.png",
- "type": "image/png",
- "sizes": "192x192"
- },
- {
- "src": "logo512.png",
- "type": "image/png",
- "sizes": "512x512"
- }
- ],
- "start_url": ".",
- "display": "standalone",
- "theme_color": "#000000",
- "background_color": "#ffffff"
-}
diff --git a/packages/power.stream-dashboard/public/mascot/kitsune_pose5_black_2000.webp b/packages/power.stream-dashboard/public/mascot/kitsune_pose5_black_2000.webp
deleted file mode 100644
index e24d31683a..0000000000
Binary files a/packages/power.stream-dashboard/public/mascot/kitsune_pose5_black_2000.webp and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/mascot/kitsune_pose6_black_2000.webp b/packages/power.stream-dashboard/public/mascot/kitsune_pose6_black_2000.webp
deleted file mode 100644
index eaadc8d66a..0000000000
Binary files a/packages/power.stream-dashboard/public/mascot/kitsune_pose6_black_2000.webp and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/mascot/kitsune_pose8_black_2000.webp b/packages/power.stream-dashboard/public/mascot/kitsune_pose8_black_2000.webp
deleted file mode 100644
index 9928cda0b8..0000000000
Binary files a/packages/power.stream-dashboard/public/mascot/kitsune_pose8_black_2000.webp and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/obs-tutorial/obs-step1.webp b/packages/power.stream-dashboard/public/obs-tutorial/obs-step1.webp
deleted file mode 100644
index 821cc7eac7..0000000000
Binary files a/packages/power.stream-dashboard/public/obs-tutorial/obs-step1.webp and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/obs-tutorial/obs-step2.webp b/packages/power.stream-dashboard/public/obs-tutorial/obs-step2.webp
deleted file mode 100644
index f726ee2660..0000000000
Binary files a/packages/power.stream-dashboard/public/obs-tutorial/obs-step2.webp and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/obs-tutorial/obs-step3.webp b/packages/power.stream-dashboard/public/obs-tutorial/obs-step3.webp
deleted file mode 100644
index ff92432f9f..0000000000
Binary files a/packages/power.stream-dashboard/public/obs-tutorial/obs-step3.webp and /dev/null differ
diff --git a/packages/power.stream-dashboard/public/robots.txt b/packages/power.stream-dashboard/public/robots.txt
deleted file mode 100644
index e9e57dc4d4..0000000000
--- a/packages/power.stream-dashboard/public/robots.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-# https://www.robotstxt.org/robotstxt.html
-User-agent: *
-Disallow:
diff --git a/packages/power.stream-dashboard/public/storyteller-nav-logo-mascot-pink.png b/packages/power.stream-dashboard/public/storyteller-nav-logo-mascot-pink.png
deleted file mode 100644
index 0057c9bf51..0000000000
Binary files a/packages/power.stream-dashboard/public/storyteller-nav-logo-mascot-pink.png and /dev/null differ
diff --git a/packages/power.stream-dashboard/src/App.css b/packages/power.stream-dashboard/src/App.css
deleted file mode 100644
index 74b5e05345..0000000000
--- a/packages/power.stream-dashboard/src/App.css
+++ /dev/null
@@ -1,38 +0,0 @@
-.App {
- text-align: center;
-}
-
-.App-logo {
- height: 40vmin;
- pointer-events: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .App-logo {
- animation: App-logo-spin infinite 20s linear;
- }
-}
-
-.App-header {
- background-color: #282c34;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: calc(10px + 2vmin);
- color: white;
-}
-
-.App-link {
- color: #61dafb;
-}
-
-@keyframes App-logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
diff --git a/packages/power.stream-dashboard/src/App.scss b/packages/power.stream-dashboard/src/App.scss
deleted file mode 100644
index d0b5aed855..0000000000
--- a/packages/power.stream-dashboard/src/App.scss
+++ /dev/null
@@ -1,927 +0,0 @@
-//@import-normalize; /* bring in normalize.css styles */
-
-// $page-width: 700px;
-$primary: #fc6b68;
-$secondary: #72434d;
-$destructive: #6d333a;
-$white: #fff;
-$light-text-color: #777;
-$button-text-color: #666;
-
-$body-font-family: "Fira Sans", serif;
-$nav-font-family: "Fira Sans", sans-serif;
-$footer-font-family: "Fira Sans Condensed", sans-serif;
-$textarea-font-family: "Fira Sans", sans-serif;
-$headings-font-family: "Merriweather", serif;
-
-// body {
-// font-family: $body-font-family;
-// font-weight: 300; // For Fira Sans
-// }
-
-// .mainwrap {
-// max-width: 1000px;
-// margin: auto;
-// }
-
-#root {
- height: 100%;
-}
-
-.git-sha {
- color: #999;
- font-size: 0.8rem;
-}
-
-html {
- height: 100%;
-}
-
-body {
- height: 100%;
- font-family: "Fira Sans", sans-serif;
- color: #fff;
-}
-
-hr {
- border: 0;
- border-top: 1px solid rgba($white, 1);
- opacity: 0.1;
-}
-
-select {
- cursor: pointer;
-}
-
-button:focus,
-button:active,
-button:hover,
-label:focus,
-.btn:active,
-.btn.focus {
- outline: 0px !important;
- -webkit-appearance: none;
- box-shadow: none !important;
-}
-
-a {
- font-weight: 400;
- color: #fc6b68;
- text-decoration: none;
- cursor: pointer;
- transition: 0.1s;
-}
-
-.text-link {
- color: #fc6b68;
-}
-
-a:hover {
- color: #ff8d8b;
-}
-
-h1 {
- font-weight: 700;
-}
-
-h3 {
- color: rgba(255, 255, 255, 0.8);
-}
-
-p {
- font-weight: 300;
- margin-bottom: 0;
- color: rgba(255, 255, 255, 0.85);
-}
-
-ul {
- margin-bottom: 0;
-}
-
-.fw-semibold {
- font-weight: 500;
-}
-
-.pre-heading {
- font-size: 18px;
- color: #fc6b68;
- text-transform: uppercase;
-}
-
-.rotateimg180 {
- -webkit-transform: rotate(180deg);
- -moz-transform: rotate(180deg);
- -ms-transform: rotate(180deg);
- -o-transform: rotate(180deg);
- transform: rotate(180deg);
-}
-
-.noselect {
- -webkit-touch-callout: none; /* iOS Safari */
- -webkit-user-select: none; /* Safari */
- -khtml-user-select: none; /* Konqueror HTML */
- -moz-user-select: none; /* Old versions of Firefox */
- -ms-user-select: none; /* Internet Explorer/Edge */
- user-select: none; /* Non-prefixed version, currently
- supported by Chrome, Edge, Opera and Firefox */
-}
-
-#particles-js {
- position: absolute;
- width: 100%;
- height: 100%;
- top: 0;
- pointer-events: none;
-}
-
-.container {
- max-width: 1200px;
-}
-
-.container,
-.container-fluid,
-.container-lg,
-.container-md,
-.container-sm,
-.container-xl,
-.container-xxl {
- width: 100%;
- padding-right: var(--bs-gutter-x, 1rem);
- padding-left: var(--bs-gutter-x, 1rem);
-}
-
-/*margin top/bottom for panels*/
-.py-6 {
- padding-top: 1.6rem !important;
- padding-bottom: 0rem !important;
-}
-
-.navbar {
- z-index: 100;
- width: 100%;
- transition: all 0.1s;
- border-color: rgba($white, 0.07) !important;
- border-bottom: 0px solid;
- padding-top: 10px;
- padding-bottom: 10px;
-}
-
-.navbar-scrolled {
- background-color: darken(rgba(42, 21, 34, 1), 7.5%);
- border-color: rgba($white, 0.07) !important;
- border-bottom: 1px solid;
-}
-
-.navbar-fixed {
- position: fixed;
-}
-
-.navbar-dark .navbar-toggler {
- border: none;
-}
-
-.nav-bg-dark {
- background-color: #17101c !important;
-}
-
-.m-page-top {
- margin-top: 100px;
-}
-
-.offcanvas {
- width: 100vw;
- height: 100%;
-}
-
-.offcanvas-header {
- padding-top: 10px;
- padding-bottom: 10px;
-}
-
-.nav-link {
- font-size: 15px;
- font-weight: 500;
- padding-left: 16px !important;
- padding-right: 16px !important;
-}
-
-.navbar-brand {
- padding-top: 0;
-}
-
-.dropdown-item {
- font-size: 15px;
- font-weight: 600;
-}
-
-.dropdown-item:active {
- background-color: #fc6b68;
-}
-
-.dropdown-menu {
- border-color: #ffffff12 !important;
- border: 1px solid;
- border-radius: 8px;
-}
-
-.lead {
- font-size: 16px;
-}
-
-.parent {
- position: relative;
-}
-
-span.word {
- position: relative;
- z-index: 1;
-}
-
-span.word:after {
- background-color: #fc6b68;
- content: " ";
- height: 15%;
- position: absolute;
- left: 0;
- margin-left: 0rem;
- top: 74%;
- width: calc(100%);
- z-index: -1;
-}
-
-.bg-gradient {
- background: darken(rgb(13, 16, 25), 3%) !important;
- background: linear-gradient(
- 325deg,
- darken(rgba(13, 16, 25, 1), 3%) 0%,
- darken(rgba(42, 21, 34, 1), 3%) 55%,
- darken(rgba(16, 14, 26, 1), 3%) 100%
- ) !important;
- position: relative;
- overflow: hidden;
- min-height: 100%;
-}
-
-.bg-title {
- background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='1920' height='300' preserveAspectRatio='none' viewBox='0 0 1920 300'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1028%26quot%3b)' fill='none'%3e%3cpath d='M1170 175L183 174' stroke-width='8' stroke='url(%23SvgjsLinearGradient1029)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1456 93L968 92' stroke-width='10' stroke='url(%23SvgjsLinearGradient1029)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1459 102L2490 101' stroke-width='6' stroke='url(%23SvgjsLinearGradient1029)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M766 41L192 40' stroke-width='6' stroke='url(%23SvgjsLinearGradient1030)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M261 212L-475 211' stroke-width='10' stroke='url(%23SvgjsLinearGradient1030)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M251 192L1533 191' stroke-width='6' stroke='url(%23SvgjsLinearGradient1029)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M172 211L936 210' stroke-width='8' stroke='url(%23SvgjsLinearGradient1030)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M1070 207L1854 206' stroke-width='10' stroke='url(%23SvgjsLinearGradient1029)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1028'%3e%3crect width='1920' height='300' fill='white'%3e%3c/rect%3e%3c/mask%3e%3clinearGradient x1='100%25' y1='0%25' x2='0%25' y2='0%25' id='SvgjsLinearGradient1029'%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0.05 )' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3clinearGradient x1='0%25' y1='0%25' x2='100%25' y2='0%25' id='SvgjsLinearGradient1030'%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0.05)' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e");
- background-size: auto;
- background-repeat: no-repeat;
-}
-
-.bg-hero {
- background: darken(rgb(13, 16, 25), 3%);
- background: linear-gradient(
- 180deg,
- darken(rgb(13, 16, 25), 2%) 0%,
- rgba(48, 23, 32, 1) 100%
- );
-}
-
-.btn {
- border-radius: 5px;
- outline: none;
- font-size: 15px;
- font-weight: 600;
- padding: 10px 24px;
- z-index: 1;
-
- a {
- color: white !important;
- }
-}
-
-.btn-primary,
-.btn-primary:active,
-.btn-primary:focus {
- background-color: #fc6b68;
- border: 2.5px solid;
- border-color: #fc6b68;
- transition: 0.2s;
-}
-
-.btn-primary:hover {
- background-color: #ff8d8b;
- border: 2.5px solid;
- border-color: #ff8d8b;
-}
-
-.btn-secondary,
-.btn-secondary:focus,
-.btn-secondary:active {
- background-color: $secondary;
- color: $white;
- border: 2.5px solid;
- border-color: $secondary;
- transition: 0.2s;
-}
-
-.btn-secondary:hover {
- background-color: lighten($secondary, 10%);
- color: $white;
- border: 2.5px solid;
- border-color: lighten($secondary, 10%);
-}
-
-.btn-destructive,
-.btn-destructive:active,
-.btn-destructive:focus {
- background-color: $destructive;
- color: $white;
- border: 2.5px solid;
- border-color: $destructive;
- transition: 0.2s;
-}
-
-.btn-destructive:hover {
- background-color: lighten($destructive, 8%);
- color: $white;
- border: 2.5px solid;
- border-color: lighten($destructive, 8%);
-}
-
-.btn-hero {
- padding-left: 35px !important;
- padding-right: 35px !important;
-}
-
-.nav-login {
- font-size: 15px;
- font-weight: 500;
- color: #fff;
- text-decoration: none;
- transition: 0.1s;
-}
-
-.nav-login:hover {
- color: rgba(255, 255, 255, 0.6);
- text-decoration: none;
- transition: 0.1s;
-}
-
-.red-line {
- border-left: 3px solid $primary;
- height: 100%;
- top: 0;
-}
-
-.icon-red {
- color: $primary;
-}
-
-.alert {
- border-radius: 5px;
- font-size: 14px;
-}
-
-.hero-section {
- height: 100vh;
- width: 100%;
- min-height: 600px;
- max-height: 1080px;
- text-align: center;
- overflow: hidden;
- /* background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='1920' height='300' preserveAspectRatio='none' viewBox='0 0 1920 300'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1327%26quot%3b)' fill='none'%3e%3cpath d='M486 46L1358 45' stroke-width='10' stroke='url(%23SvgjsLinearGradient1328)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M312 288L-212 287' stroke-width='6' stroke='url(%23SvgjsLinearGradient1328)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M1583 12L2334 11' stroke-width='8' stroke='url(%23SvgjsLinearGradient1329)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1219 65L1985 64' stroke-width='10' stroke='url(%23SvgjsLinearGradient1329)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1877 57L2438 56' stroke-width='8' stroke='url(%23SvgjsLinearGradient1328)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M347 173L-797 172' stroke-width='10' stroke='url(%23SvgjsLinearGradient1328)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M762 234L-332 233' stroke-width='6' stroke='url(%23SvgjsLinearGradient1329)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M621 133L2031 132' stroke-width='8' stroke='url(%23SvgjsLinearGradient1328)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1327'%3e%3crect width='1920' height='300' fill='white'%3e%3c/rect%3e%3c/mask%3e%3clinearGradient x1='0%25' y1='0%25' x2='100%25' y2='0%25' id='SvgjsLinearGradient1328'%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0.05)' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3clinearGradient x1='100%25' y1='0%25' x2='0%25' y2='0%25' id='SvgjsLinearGradient1329'%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0.05)' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e"); */
- background-position: 0 120px;
- background-repeat: no-repeat;
- background-size: 100%;
-}
-
-.hero-inner {
- padding-top: 100px;
- height: 100%;
-}
-
-.hero-text {
- max-width: 800px;
-}
-
-.hero-img-section {
- display: flex;
- position: relative;
- justify-content: center;
- height: 100%;
- width: 100%;
- pointer-events: none;
-}
-
-.hero-img {
- position: absolute;
- bottom: 0;
- max-height: 110%;
- max-width: 140%;
- z-index: 1;
-}
-
-.hero-floor {
- position: absolute;
- bottom: 0;
- width: 100%;
- height: 100%;
- max-height: 100%;
- min-height: 50%;
-}
-
-@keyframes animatedBackground {
- from {
- background-position: 0px 0;
- }
- to {
- background-position: -1448px 0;
- }
-}
-
-#hero-audio-wave {
- position: absolute;
- bottom: 0;
- margin-bottom: 12rem;
- opacity: 15%;
- width: 115%;
- min-height: 150px;
- background-image: url("../public/assets/hero-audio-wave.png");
- background-repeat: repeat-x;
- animation: animatedBackground 20s linear infinite;
- mix-blend-mode: screen;
-}
-
-.panel {
- position: relative;
- width: 100%;
- background-color: #2a1c26;
- border-color: rgba(255, 255, 255, 0.06) !important;
- border: 2px solid;
- border-radius: 8px;
- backdrop-filter: blur(10px);
- -webkit-backdrop-filter: blur(10px);
- // -webkit-box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
- // box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
-}
-
-.panel-outline {
- border-color: rgba(252, 107, 104, 0.5) !important;
-}
-
-.hero-floating-panel {
- text-align: left;
- position: absolute;
- bottom: 180px;
- z-index: 3;
- width: auto;
- height: auto;
- padding: 1rem 1.25rem;
- pointer-events: none;
- background-color: lighten(#30232a, 5%);
- border-color: rgba(255, 255, 255, 0.06) !important;
-}
-
-.hero-floating-panel-left {
- margin-left: -800px;
- margin-bottom: 25px;
-}
-
-.hero-floating-panel-right {
- margin-right: -760px;
- margin-bottom: 100px;
-}
-
-.hero-floating-panel-text {
- font-size: 14px;
- color: rgba(255, 255, 255, 0.8);
-}
-
-.panel-title {
- position: static;
- text-align: center;
- text-shadow: 0px 3px 26px #00000060;
- margin-top: -35px;
-}
-
-.section {
- padding-top: 4rem;
- padding-bottom: 4rem;
-}
-
-.features-section {
- width: 100%;
- text-align: center;
- margin-top: 50px;
- margin-bottom: -60px;
-}
-
-.features-panel {
- padding: 1rem 2rem 2rem 2rem;
- position: relative;
- border-radius: 8px;
-}
-
-.features-panel-sides {
- margin-top: 40px;
- margin-bottom: 40px;
-}
-
-.features-icon {
- position: absolute;
- transform: translate(-50%, -50%);
- top: 0;
-}
-
-.features-title {
- margin-top: 60px;
-}
-
-.features-bg,
-.insights-bg,
-.community-bg {
- background-size: cover;
-}
-
-.voices-section {
- width: 100%;
- text-align: center;
- margin-top: 50px;
-}
-
-.insights-section {
- max-width: 1000px;
-}
-
-.insights-panel {
- padding: 32px;
- position: relative;
- border-radius: 8px;
-}
-
-.insights-circle-1 {
- position: absolute;
- top: 0;
- right: 0;
- margin-right: -40px;
- margin-top: 30px;
- width: 70px;
-}
-
-.insights-circle-2 {
- position: absolute;
- bottom: 0;
- right: 0;
- width: 46px;
- margin-bottom: -25px;
- margin-right: 50px;
-}
-
-.questions-panel {
- max-width: 700px;
- border-radius: 8px;
-}
-
-.cta-panel {
- background-color: #cf4c54;
- width: 100%;
- border-radius: 8px;
- padding: 3rem 1rem;
- position: relative;
- -webkit-box-shadow: 0px -10px 50px 5px #fc6b68;
- box-shadow: 0px -10px 50px 5px rgba(190, 71, 69, 0.2);
-}
-
-.cta-img {
- position: absolute;
- max-width: 600px;
- left: 50%;
- top: 50%;
- transform: translate(-50%, -55%);
- pointer-events: none;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -o-user-select: none;
- user-select: none;
-}
-
-.cta-right {
- padding-top: 150px;
-}
-
-.red-glow {
- filter: drop-shadow(0px 10px 30px #cf4c5582);
- -webkit-filter: drop-shadow(0px 10px 30px #cf4c5582);
-}
-
-.sub-title {
- font-weight: 500;
- padding-bottom: 10px;
-}
-
-.input-icon .form-control,
-.input-icon .form-select {
- padding-left: 2.375rem;
-}
-
-.input-icon .form-control-feedback {
- position: absolute;
- z-index: 2;
- display: block;
- width: 2.375rem;
- height: 2.375rem;
- line-height: 2.375rem;
- text-align: center;
- pointer-events: none;
- color: #443a40;
-}
-
-.form-control {
- border-width: 1px;
- border-color: #fff;
-}
-
-.form-control:focus,
-.form-select:focus {
- border-color: #fc6b68;
- outline: 0;
- box-shadow: 0 0 0 0.1rem #fc6b68;
-}
-
-.footer-link {
- color: #fc6b68;
- font-weight: 400;
- text-decoration: none;
- cursor: pointer;
- transition: 0.1s;
-}
-
-.footer-link:hover {
- color: #ff8d8b;
-}
-
-.social-icon {
- font-size: 24px;
- color: #fff;
- text-decoration: none;
- cursor: pointer;
- transition: 0.1s;
-}
-
-.social-icon:hover {
- color: rgba(255, 255, 255, 0.601);
-}
-
-.form-check-input:checked {
- background-color: #fc6b68;
- border-color: #fc6b68;
-}
-
-.form-check-input:focus {
- box-shadow: none;
-}
-
-.login-panel {
- max-width: 800px !important;
-}
-
-.faq-panel {
- max-width: 800px;
-}
-
-.accordion-item {
- background-color: #2a1c26;
- border-color: rgba(255, 255, 255, 0.06) !important;
- border: 2px solid;
- border-radius: 5px;
-}
-
-.accordion-button {
- background-color: rgba(255, 255, 255, 0) !important;
- color: rgba(255, 255, 255, 0.8);
- font-size: 20px;
- line-height: 1.5;
-}
-
-.accordion-button:hover {
- color: #fff;
-}
-
-.accordion-button::after {
- // background-image: url(assets/plus.svg);
- opacity: 0.5;
- background-position: center;
-}
-
-.accordion-button:not(.collapsed)::after {
- // background-image: url(assets/munis.svg);
- opacity: 0.5;
-}
-
-.accordion-body {
- color: rgba(255, 255, 255, 0.8);
- font-weight: 300;
-}
-
-.accordion-button:not(.collapsed) {
- color: #fff;
- box-shadow: none;
-}
-
-.nav-pills {
- backdrop-filter: blur(20px);
- -webkit-backdrop-filter: blur(20px);
- width: 100% !important;
-}
-
-.nav-link:focus,
-.nav-link:hover {
- color: #fc6b68;
-}
-
-.nav-pills .nav-link {
- padding: 16px 24px !important;
- background-color: rgba(255, 255, 255, 0.1);
- border-radius: 0;
- border-color: rgba(255, 255, 255, 0.1) !important;
- border-bottom: 5px solid;
- color: #fff !important;
-}
-
-.nav-pills .nav-link.active,
-.nav-pills .show > .nav-link {
- background-color: rgba(255, 255, 255, 0.15);
- border-color: #fc6b68 !important;
- border-bottom: 5px solid;
-}
-
-.modal {
- background-color: rgba(19, 15, 27, 0.5);
-}
-
-.modal-content {
- background-color: #342b35;
- border-color: #4b3c46 !important;
- border: 2px solid;
- border-radius: 8px;
- backdrop-filter: blur(20px);
- -webkit-backdrop-filter: blur(20px);
- -webkit-box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
- box-shadow: 0px 0px 30px 0px rgba(0, 0, 0, 0.5);
-}
-
-.modal-header {
- border-color: rgba(255, 255, 255, 0.1) !important;
-}
-
-.modal-footer {
- border-color: rgba(255, 255, 255, 0.1) !important;
-}
-
-.modal-close-btn {
- background: none !important;
- border-radius: 5px;
- outline: none;
- border: none;
- font-size: 22px;
- color: rgba(255, 255, 255, 0.8);
- transition: 0.1s;
-}
-
-.modal-close-btn:hover {
- color: rgba(255, 255, 255, 0.4);
-}
-
-.voice-preview-text {
- font-size: 12px;
- text-align: left;
- white-space: nowrap;
-}
-
-.btn-voice-preview {
- padding: 0;
- margin: 0;
- height: 30px;
- width: 30px;
- min-width: 30px;
-}
-
-.twitch-color {
- color: #9146ff;
-}
-
-.purple-glow {
- filter: drop-shadow(0px 10px 30px #9046ff9f);
- -webkit-filter: drop-shadow(0px 10px 30px #9046ff9f);
-}
-
-.twitch-btn,
-.twitch-btn:focus,
-.twitch-btn:active {
- background-color: #9146ff;
- border-color: #9146ff;
-
- &:hover {
- background-color: lighten(#9146ff, 5%);
- border-color: lighten(#9146ff, 5%);
- }
-}
-
-.dashboard-icon {
- font-size: 50px;
-}
-
-.text-red {
- color: $primary;
-}
-
-@media (min-width: 768px) {
- .container-panel {
- width: 100%;
- padding-right: var(--bs-gutter-x, 1rem);
- padding-left: var(--bs-gutter-x, 1rem);
- margin-right: auto;
- margin-left: auto;
- max-width: 1080px;
- border-radius: 8px;
- }
-
- .panel {
- border-radius: 8px;
- }
-
- .features-panel-sides {
- margin-top: 50px;
- margin-bottom: -50px;
- }
-
- .features-section {
- margin-top: 90px;
- margin-bottom: 50px;
- }
-
- .section {
- padding-top: 8.5rem;
- padding-bottom: 8.5rem;
- }
-
- .cta-right {
- padding-top: 0px;
- }
-
- .hero-img {
- position: absolute;
- bottom: 0;
- max-height: 110%;
- max-width: 100%;
- z-index: 2;
- }
-
- .voice-preview-text {
- font-size: 14px;
- }
-
- .lead {
- font-size: 18px;
- }
-}
-
-@media (min-width: 992px) {
- .panel-title {
- position: absolute;
- text-align: left;
- top: 0;
- margin-top: -25px;
- }
- .p-lg-4 {
- padding: 2rem !important;
- }
-
- .nav-bg-dark {
- background-color: #17101c00 !important;
- }
-
- .features-bg {
- background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='1920' height='560' preserveAspectRatio='none' viewBox='0 0 1920 560'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1087%26quot%3b)' fill='none'%3e%3cpath d='M860 39L1510 38' stroke-width='8' stroke='url(%23SvgjsLinearGradient1088)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M356 89L-827 88' stroke-width='6' stroke='url(%23SvgjsLinearGradient1088)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M19 415L-1000 414' stroke-width='6' stroke='url(%23SvgjsLinearGradient1089)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M207 30L-1043 29' stroke-width='10' stroke='url(%23SvgjsLinearGradient1089)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1333 95L2384 94' stroke-width='10' stroke='url(%23SvgjsLinearGradient1088)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M1471 510L2543 509' stroke-width='8' stroke='url(%23SvgjsLinearGradient1088)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M1789 194L985 193' stroke-width='10' stroke='url(%23SvgjsLinearGradient1089)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1512 92L2891 91' stroke-width='6' stroke='url(%23SvgjsLinearGradient1089)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M67 447L-1110 446' stroke-width='10' stroke='url(%23SvgjsLinearGradient1089)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1209 230L309 229' stroke-width='8' stroke='url(%23SvgjsLinearGradient1088)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M760 527L-35 526' stroke-width='6' stroke='url(%23SvgjsLinearGradient1088)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M52 257L-1362 256' stroke-width='8' stroke='url(%23SvgjsLinearGradient1088)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M1269 105L2420 104' stroke-width='10' stroke='url(%23SvgjsLinearGradient1088)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M1535 531L2296 530' stroke-width='10' stroke='url(%23SvgjsLinearGradient1088)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1087'%3e%3crect width='1920' height='560' fill='white'%3e%3c/rect%3e%3c/mask%3e%3clinearGradient x1='0%25' y1='0%25' x2='100%25' y2='0%25' id='SvgjsLinearGradient1088'%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0.05)' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3clinearGradient x1='100%25' y1='0%25' x2='0%25' y2='0%25' id='SvgjsLinearGradient1089'%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0.05)' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e");
- background-size: 100%;
- background-position: 0 250px;
- background-repeat: no-repeat;
- }
-
- .voices-bg {
- background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='1440' height='560' preserveAspectRatio='none' viewBox='0 0 1440 560'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1000%26quot%3b)' fill='none'%3e%3cpath d='M -470.4799303954471%2c120 C -422.48%2c165.4 -326.48%2c348 -230.47993039544713%2c347 C -134.48%2c346 -86.48%2c124.8 9.520069604552873%2c115 C 105.52%2c105.2 153.52%2c266.2 249.52006960455287%2c298 C 345.52%2c329.8 393.52%2c259.4 489.5200696045529%2c274 C 585.52%2c288.6 633.52%2c379.2 729.5200696045529%2c371 C 825.52%2c362.8 873.52%2c233.4 969.5200696045529%2c233 C 1065.52%2c232.6 1115.42%2c379.8 1209.5200696045529%2c369 C 1303.62%2c358.2 1393.9%2c217 1440%2c179' stroke='rgba(247%2c 84%2c 92%2c 0.08)' stroke-width='2'%3e%3c/path%3e%3cpath d='M -173.39417306836023%2c447 C -125.39%2c383.6 -29.39%2c161.8 66.60582693163977%2c130 C 162.61%2c98.2 210.61%2c278 306.60582693163974%2c288 C 402.61%2c298 450.61%2c166.8 546.6058269316397%2c180 C 642.61%2c193.2 690.61%2c337.2 786.6058269316397%2c354 C 882.61%2c370.8 930.61%2c255 1026.6058269316397%2c264 C 1122.61%2c273 1170.61%2c427.4 1266.6058269316397%2c399 C 1362.61%2c370.6 1471.93%2c142.4 1506.6058269316397%2c122 C 1541.28%2c101.6 1453.32%2c262 1440%2c297' stroke='rgba(247%2c 84%2c 92%2c 0.08)' stroke-width='2'%3e%3c/path%3e%3cpath d='M -250.53447353119677%2c366 C -202.53%2c330 -106.53%2c202 -10.53447353119676%2c186 C 85.47%2c170 133.47%2c300 229.46552646880323%2c286 C 325.47%2c272 373.47%2c109.8 469.46552646880326%2c116 C 565.47%2c122.2 613.47%2c296.4 709.4655264688032%2c317 C 805.47%2c337.6 853.47%2c207.4 949.4655264688032%2c219 C 1045.47%2c230.6 1093.47%2c376.8 1189.4655264688033%2c375 C 1285.47%2c373.2 1379.36%2c196.8 1429.4655264688033%2c210 C 1479.57%2c223.2 1437.89%2c394.8 1440%2c441' stroke='rgba(247%2c 84%2c 92%2c 0.08)' stroke-width='2'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1000'%3e%3crect width='1440' height='560' fill='white'%3e%3c/rect%3e%3c/mask%3e%3c/defs%3e%3c/svg%3e");
- background-size: 100%;
- background-position: 0 55%;
- background-repeat: no-repeat;
- }
-
- .community-bg {
- background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='1920' height='500' preserveAspectRatio='none' viewBox='0 0 1920 500'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1540%26quot%3b)' fill='none'%3e%3cpath d='M863 371L-379 370' stroke-width='6' stroke='url(%23SvgjsLinearGradient1541)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1822 20L2738 19' stroke-width='6' stroke='url(%23SvgjsLinearGradient1541)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M835 407L161 406' stroke-width='8' stroke='url(%23SvgjsLinearGradient1542)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M1461 280L514 279' stroke-width='8' stroke='url(%23SvgjsLinearGradient1542)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M362 385L-283 384' stroke-width='6' stroke='url(%23SvgjsLinearGradient1541)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1321 113L2663 112' stroke-width='6' stroke='url(%23SvgjsLinearGradient1541)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1358 249L54 248' stroke-width='6' stroke='url(%23SvgjsLinearGradient1541)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M66 37L-666 36' stroke-width='6' stroke='url(%23SvgjsLinearGradient1541)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1746 269L363 268' stroke-width='8' stroke='url(%23SvgjsLinearGradient1541)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1317 204L759 203' stroke-width='6' stroke='url(%23SvgjsLinearGradient1542)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M1211 23L121 22' stroke-width='6' stroke='url(%23SvgjsLinearGradient1542)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M793 423L1418 422' stroke-width='10' stroke='url(%23SvgjsLinearGradient1541)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M23 167L-1078 166' stroke-width='10' stroke='url(%23SvgjsLinearGradient1542)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1540'%3e%3crect width='1920' height='500' fill='white'%3e%3c/rect%3e%3c/mask%3e%3clinearGradient x1='100%25' y1='0%25' x2='0%25' y2='0%25' id='SvgjsLinearGradient1541'%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0.05)' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3clinearGradient x1='0%25' y1='0%25' x2='100%25' y2='0%25' id='SvgjsLinearGradient1542'%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0.05)' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e");
- background-size: 100%;
- background-position: center;
- background-repeat: no-repeat;
- }
-
- .insights-bg {
- background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='1920' height='300' preserveAspectRatio='none' viewBox='0 0 1920 300'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1056%26quot%3b)' fill='none'%3e%3cpath d='M0 174.34L10 149.93L20 119.89L30 125.05L40 99.87L50 67.35L60 49.02L70 34.79L80 52.92L90 38.38L100 42.07L110 42.37L120 34.03L130 50.65L140 27.52L150 20.16L160 8.73L170 44.68L180 24.6L190 1.43L200 14.65L210 43.32L220 77.48L230 99.7L240 122.99L250 146.47L260 128.54L270 102.41L280 86.49L290 102.37L300 81.48L310 62.07L320 99.29L330 75.32L340 66.93L350 83.9L360 94.48L370 62.31L380 45.19L390 27.62L400 34.54L410 41.5L420 39.2L430 56.28L440 80.96L450 58.21L460 65.28L470 93.02L480 88.25L490 107.02L500 81.06L510 84.94L520 75.08L530 40.79L540 36.65L550 64.85L560 37.29L570 68.66L580 34.8L590 57.92L600 74.39L610 55.74L620 52.09L630 70.43L640 49.43L650 71.76L660 73.23L670 77.8L680 106.27L690 85.99L700 89.94L710 112.08L720 108.17L730 127.84L740 120.67L750 102.28L760 114.63L770 111.79L780 96.92L790 65.31L800 90.52L810 76.31L820 72.63L830 57.46L840 24.02L850 27.97L860 8.13L870 23.64L880 36.56L890 43.68L900 10.09L910 18.34L920 36.36L930 60.86L940 87.39L950 93.49L960 60.46L970 87.07L980 71.1L990 102.44L1000 107.22L1010 112.7L1020 83.31L1030 74.86L1040 73.89L1050 48.95L1060 69.19L1070 33.87L1080 52.24L1090 73.78L1100 55.45L1110 69.19L1120 64.06L1130 44.4L1140 9.34L1150 29.02L1160 26.59L1170 36.77L1180 4.52L1190 25.85L1200 39.02L1210 34.71L1220 19.43L1230 35.84L1240 49.16L1250 40.48L1260 8.93L1270 39.55L1280 58.26L1290 82.39L1300 88.4L1310 100.17L1320 106.31L1330 134.08L1340 124.71L1350 101.84L1360 119.43L1370 95.32L1380 74.16L1390 82.91L1400 77.55L1410 103.99L1420 118.62L1430 85.24L1440 100.32L1450 135.6L1460 100.46L1470 88.25L1480 55.22L1490 81.67L1500 86.48L1510 52.36L1520 70.19L1530 73.09L1540 74.23L1550 52.86L1560 85.95L1570 65.98L1580 37.35L1590 7.69L1600 14.92L1610 6.87L1620 9.55L1630 25.73L1640 23.25L1650 36.63L1660 27.09L1670 5.76L1680 32.72L1690 3.81L1700 27.06L1710 11.67L1720 26.98L1730 51.06L1740 18.55L1750 32.88L1760 59.11L1770 87.91L1780 66.47L1790 39.44L1800 42.68L1810 73.69L1820 36.63L1830 60.41L1840 70.38L1850 88.57L1860 58.79L1870 84.45L1880 89.09L1890 63.22L1900 26.84L1910 60.71L1920 23.99' stroke='rgba(207%2c 76%2c 84%2c 0.1)' stroke-width='2'%3e%3c/path%3e%3cpath d='M0 174.34L10 149.93L20 119.89L30 125.05L40 99.87L50 67.35L60 49.02L70 34.79L80 52.92L90 38.38L100 42.07L110 42.37L120 34.03L130 50.65L140 27.52L150 20.16L160 8.73L170 44.68L180 24.6L190 1.43L200 14.65L210 43.32L220 77.48L230 99.7L240 122.99L250 146.47L260 128.54L270 102.41L280 86.49L290 102.37L300 81.48L310 62.07L320 99.29L330 75.32L340 66.93L350 83.9L360 94.48L370 62.31L380 45.19L390 27.62L400 34.54L410 41.5L420 39.2L430 56.28L440 80.96L450 58.21L460 65.28L470 93.02L480 88.25L490 107.02L500 81.06L510 84.94L520 75.08L530 40.79L540 36.65L550 64.85L560 37.29L570 68.66L580 34.8L590 57.92L600 74.39L610 55.74L620 52.09L630 70.43L640 49.43L650 71.76L660 73.23L670 77.8L680 106.27L690 85.99L700 89.94L710 112.08L720 108.17L730 127.84L740 120.67L750 102.28L760 114.63L770 111.79L780 96.92L790 65.31L800 90.52L810 76.31L820 72.63L830 57.46L840 24.02L850 27.97L860 8.13L870 23.64L880 36.56L890 43.68L900 10.09L910 18.34L920 36.36L930 60.86L940 87.39L950 93.49L960 60.46L970 87.07L980 71.1L990 102.44L1000 107.22L1010 112.7L1020 83.31L1030 74.86L1040 73.89L1050 48.95L1060 69.19L1070 33.87L1080 52.24L1090 73.78L1100 55.45L1110 69.19L1120 64.06L1130 44.4L1140 9.34L1150 29.02L1160 26.59L1170 36.77L1180 4.52L1190 25.85L1200 39.02L1210 34.71L1220 19.43L1230 35.84L1240 49.16L1250 40.48L1260 8.93L1270 39.55L1280 58.26L1290 82.39L1300 88.4L1310 100.17L1320 106.31L1330 134.08L1340 124.71L1350 101.84L1360 119.43L1370 95.32L1380 74.16L1390 82.91L1400 77.55L1410 103.99L1420 118.62L1430 85.24L1440 100.32L1450 135.6L1460 100.46L1470 88.25L1480 55.22L1490 81.67L1500 86.48L1510 52.36L1520 70.19L1530 73.09L1540 74.23L1550 52.86L1560 85.95L1570 65.98L1580 37.35L1590 7.69L1600 14.92L1610 6.87L1620 9.55L1630 25.73L1640 23.25L1650 36.63L1660 27.09L1670 5.76L1680 32.72L1690 3.81L1700 27.06L1710 11.67L1720 26.98L1730 51.06L1740 18.55L1750 32.88L1760 59.11L1770 87.91L1780 66.47L1790 39.44L1800 42.68L1810 73.69L1820 36.63L1830 60.41L1840 70.38L1850 88.57L1860 58.79L1870 84.45L1880 89.09L1890 63.22L1900 26.84L1910 60.71L1920 23.99L1920 300L0 300z' fill='url(%23SvgjsLinearGradient1057)'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1056'%3e%3crect width='1920' height='300' fill='white'%3e%3c/rect%3e%3c/mask%3e%3clinearGradient x1='50%25' y1='0%25' x2='50%25' y2='100%25' id='SvgjsLinearGradient1057'%3e%3cstop stop-opacity='0.65' stop-color='rgba(207%2c 76%2c 84%2c 0.05)' offset='0'%3e%3c/stop%3e%3cstop stop-opacity='0' stop-color='rgba(255%2c 255%2c 255%2c 0.05)' offset='0.8'%3e%3c/stop%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e");
- background-size: 100%;
- background-position: bottom;
- background-repeat: no-repeat;
- }
-
- .voice-preview-text {
- font-size: 16px;
- }
-}
diff --git a/packages/power.stream-dashboard/src/App.test.tsx b/packages/power.stream-dashboard/src/App.test.tsx
deleted file mode 100644
index bfe47f2987..0000000000
--- a/packages/power.stream-dashboard/src/App.test.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-//import { render, screen } from '@testing-library/react';
-import App from './App';
-
-test('renders learn react link', () => {
- // NB(2022-02-01): This broke when converting to a monorepo.
- // I hadn't been using the tests anyhow.
- //render( );
- //const linkElement = screen.getByText(/learn react/i);
- //expect(linkElement).toBeInTheDocument();
-});
diff --git a/packages/power.stream-dashboard/src/App.tsx b/packages/power.stream-dashboard/src/App.tsx
deleted file mode 100644
index c30a87260e..0000000000
--- a/packages/power.stream-dashboard/src/App.tsx
+++ /dev/null
@@ -1,216 +0,0 @@
-// import "bulma/css/bulma.css";
-import "./App.scss";
-
-import React from "react";
-import { Link } from "react-router-dom";
-import { BrowserRouter, Route, Switch } from "react-router-dom";
-// import { GitSha } from "@storyteller/components/src/elements/GitSha";
-import {
- ListTtsModels,
- TtsModelListItem,
-} from "@storyteller/components/src/api/tts/ListTtsModels";
-import StreamPage from "./pages/stream/StreamPage";
-import i18n from "i18next";
-import { ComingSoonPage } from "./pages/coming-soon/ComingSoonPage";
-import { LoginPage } from "./pages/login/LoginPage";
-import { SignupPage } from "./pages/signup/SignupPage";
-import { TtsConfigsIndexPage } from "./pages/tts_configs/TtsConfigsIndexPage";
-import {
- DetectLocale,
- DetectLocaleIsOk,
-} from "@storyteller/components/src/api/locale/DetectLocale";
-import { DashboardPage } from "./pages/dashboard/DashboardPage";
-import { STORYTELLER_MERGED_TRANSLATIONS } from "./_i18n/StorytellerTranslations";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import { TopNav } from "./layout/TopNav";
-import { initReactI18next } from "react-i18next";
-import { TtsConfigsCreateRulePage } from "./pages/tts_configs/TtsConfigsCreateRulePage";
-import { TtsConfigsDeleteRulePage } from "./pages/tts_configs/TtsConfigsDeleteRulePage";
-import { TtsConfigsEditRulePage } from "./pages/tts_configs/TtsConfigsEditRulePage";
-import { TtsConfigsReorderPage } from "./pages/tts_configs/TtsConfigsReorderPage";
-import { ObsConfigsPage } from "./pages/obs_configs/ObsConfigsPage";
-
-i18n
- .use(initReactI18next) // passes i18n down to react-i18next
- .init({
- // the translations
- // (tip move them in a JSON file and import them,
- // or even better, manage them via a UI: https://react.i18next.com/guides/multiple-translation-files#manage-your-translations-with-a-management-gui)
- resources: STORYTELLER_MERGED_TRANSLATIONS,
- //lng: 'en', // if you're using a language detector, do not define the lng option
- fallbackLng: "en",
-
- interpolation: {
- escapeValue: false, // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
- },
- });
-
-interface Props {}
-
-interface State {
- sessionWrapper: SessionWrapper;
- allTtsModels: TtsModelListItem[];
- allTtsModelsByToken: Map;
-}
-
-// Root element is a non-functional component for easier global lifecycle management
-class App extends React.Component {
- constructor(props: Props) {
- super(props);
- this.state = {
- sessionWrapper: SessionWrapper.emptySession(),
- allTtsModels: [],
- allTtsModelsByToken: new Map(),
- };
- }
-
- async componentDidMount() {
- await this.querySession();
- await this.queryLanguage();
- await this.queryTtsModels();
- setInterval(async () => {
- await this.querySession();
- }, 60000);
- }
-
- querySession = async () => {
- const sessionWrapper = await SessionWrapper.lookupSession();
- this.setState({
- sessionWrapper: sessionWrapper,
- });
- };
-
- queryLanguage = async () => {
- let locale = await DetectLocale();
- if (DetectLocaleIsOk(locale)) {
- // TODO
- }
- };
-
- queryTtsModels = async () => {
- if (this.state.allTtsModels.length > 0) {
- return; // Already queried.
- }
- const models = await ListTtsModels();
- if (models) {
- let allTtsModelsByToken = new Map();
-
- models.forEach((item) => {
- allTtsModelsByToken.set(item.model_token, item);
- });
-
- //dynamicallyCategorizeModels(models);
- this.setState({
- allTtsModels: models,
- allTtsModelsByToken: allTtsModelsByToken,
- });
- //if (!maybeSelectedTtsModel && models.length > 0) {
- // let model = models[0];
- // const featuredModels = models.filter(m => m.is_front_page_featured);
- // if (featuredModels.length > 0) {
- // // Random featured model
- // model = featuredModels[
- // Math.floor(Math.random()*featuredModels.length)
- // ];
- // }
- // setMaybeSelectedTtsModel(model);
- //}
- }
- };
-
- public render() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
-}
-
-export default App;
diff --git a/packages/power.stream-dashboard/src/_i18n/StorytellerTranslations.ts b/packages/power.stream-dashboard/src/_i18n/StorytellerTranslations.ts
deleted file mode 100644
index 044cd67185..0000000000
--- a/packages/power.stream-dashboard/src/_i18n/StorytellerTranslations.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import { COMMON_TRANSLATIONS, MergeDeepDictionary } from '@storyteller/components/src/_i18n/CommonTranslations';
-
-// Use \u{00a0} = character literal
-
-const STORYTELLER_TRANSLATIONS : any = {
- // English: 46.6% Twitch (#1), 30+% FakeYou (#1)
- en: {
- translation: {
- commonUi: {
- },
- pages: {
- },
- },
- },
- // Spanish: 10.5% Twitch (#2), 20+% FakeYou (#2)
- es: {
- translation: {
- commonUi: {
- },
- pages: {
- },
- },
- },
-
- // ---------- OTHER LANGUAGES ----------
-
- // German: 6.5% Twitch (#4)
- de: {
- translation: {
- }
- },
- // French: 5.6% Twitch (#6)
- fr: {
- translation: {
- }
- },
- // Indonesian 2% FakeYou
- id: {
- translation: {
- }
- },
- // Japanese: 2.5% Twitch (#8)
- ja: {
- translation: {
- },
- },
- // Korean: 5.4% Twitch (#7)
- ko: {
- translation: {
- }
- },
- // Portuguese: 6.2% Twitch (#5)
- pt: {
- translation: {
- },
- },
- // 6.5% Twitch (#3)
- ru: {
- translation: {
- }
- },
- // Turkish: 4% FakeYou (#3)
- tr: {
- translation: {
- }
- },
-}
-
-// TODO: Type these as i18next dictionaries
-const STORYTELLER_MERGED_TRANSLATIONS : any = MergeDeepDictionary(STORYTELLER_TRANSLATIONS, COMMON_TRANSLATIONS);
-
-export { STORYTELLER_MERGED_TRANSLATIONS }
diff --git a/packages/power.stream-dashboard/src/index.css b/packages/power.stream-dashboard/src/index.css
deleted file mode 100644
index d942c42f27..0000000000
--- a/packages/power.stream-dashboard/src/index.css
+++ /dev/null
@@ -1,13 +0,0 @@
-body {
- margin: 0;
- /*font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
- sans-serif;*/
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
- monospace;
-}
diff --git a/packages/power.stream-dashboard/src/index.tsx b/packages/power.stream-dashboard/src/index.tsx
deleted file mode 100644
index ef2edf8ea3..0000000000
--- a/packages/power.stream-dashboard/src/index.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import './index.css';
-import App from './App';
-import reportWebVitals from './reportWebVitals';
-
-ReactDOM.render(
-
-
- ,
- document.getElementById('root')
-);
-
-// If you want to start measuring performance in your app, pass a function
-// to log results (for example: reportWebVitals(console.log))
-// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals();
diff --git a/packages/power.stream-dashboard/src/layout/MustBeLoggedInView.tsx b/packages/power.stream-dashboard/src/layout/MustBeLoggedInView.tsx
deleted file mode 100644
index c53794944f..0000000000
--- a/packages/power.stream-dashboard/src/layout/MustBeLoggedInView.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { Link } from "react-router-dom";
-
-function MustBeLoggedInView() {
- return (
-
-
-
You must to be logged in to view.
-
-
- Sign Up
-
-
- Login
-
-
-
- );
-}
-
-export { MustBeLoggedInView };
diff --git a/packages/power.stream-dashboard/src/layout/TopNav.tsx b/packages/power.stream-dashboard/src/layout/TopNav.tsx
deleted file mode 100644
index 5c21704168..0000000000
--- a/packages/power.stream-dashboard/src/layout/TopNav.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import React, { useState, useEffect } from "react";
-import { Link } from "react-router-dom";
-import { TopNavSessionButton } from "./TopNavSessionButton";
-
-interface Props {
- sessionWrapper: SessionWrapper;
- querySessionCallback: () => void;
-}
-
-function TopNav(props: Props) {
- const [scroll, setScroll] = useState(false);
- useEffect(() => {
- window.addEventListener("scroll", () => {
- setScroll(window.scrollY > 50);
- });
- }, []);
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- TTS Configs
-
-
-
-
- OBS Configs
-
-
-
- {/*
-
- Login
-
-
- Sign up
-
-
*/}
-
-
-
-
-
-
-
- >
- );
-}
-
-export { TopNav };
diff --git a/packages/power.stream-dashboard/src/layout/TopNavSessionButton.tsx b/packages/power.stream-dashboard/src/layout/TopNavSessionButton.tsx
deleted file mode 100644
index 6759e7c2c2..0000000000
--- a/packages/power.stream-dashboard/src/layout/TopNavSessionButton.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import React from "react";
-import { Link, useHistory } from "react-router-dom";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import { Logout } from "@storyteller/components/src/api/session/Logout";
-import { t } from "i18next";
-import { Gravatar } from "@storyteller/components/src/elements/Gravatar";
-
-interface Props {
- sessionWrapper: SessionWrapper;
- enableAlpha: boolean;
- querySessionAction: () => void;
-}
-
-function TopNavSessionButton(props: Props) {
- let history = useHistory();
-
- if (!props.enableAlpha) {
- return ;
- }
-
- const logoutHandler = async () => {
- await Logout();
- props.querySessionAction();
- history.push("/");
- };
-
- let loggedIn = props.sessionWrapper.isLoggedIn();
- let displayName = props.sessionWrapper.getDisplayName();
- let gravatarHash = props.sessionWrapper.getEmailGravatarHash();
- let gravatar = ;
-
- if (displayName === undefined) {
- displayName = "My Account";
- }
-
- if (gravatarHash !== undefined) {
- gravatar = ;
- }
-
- let sessionLink = ;
- let logoutLink = ;
-
- if (loggedIn) {
- let url = `https://fakeyou.com/profile/${displayName}`;
- sessionLink = (
-
- {" "}
- {gravatar} {displayName} (FakeYou)
-
- );
- logoutLink = (
- {
- await logoutHandler();
- }}
- >
- {t("common.logout")}
-
- );
- } else {
- sessionLink = (
- <>
-
- Login
-
-
- Sign Up
-
- >
- );
- }
-
- return (
-
- {sessionLink}
- {logoutLink}
-
- );
-}
-
-export { TopNavSessionButton };
diff --git a/packages/power.stream-dashboard/src/logo.svg b/packages/power.stream-dashboard/src/logo.svg
deleted file mode 100644
index 9dfc1c058c..0000000000
--- a/packages/power.stream-dashboard/src/logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/power.stream-dashboard/src/pages/coming-soon/ComingSoonPage.tsx b/packages/power.stream-dashboard/src/pages/coming-soon/ComingSoonPage.tsx
deleted file mode 100644
index 664ad64c51..0000000000
--- a/packages/power.stream-dashboard/src/pages/coming-soon/ComingSoonPage.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import React from 'react';
-
-function ComingSoonPage() {
- return (
-
-
-
-
- Coming Soon
-
-
- We're almost done!
-
-
-
We'll be launching the following features shortly:
-
-
- Audience rewards
-
- Stream TTS
- Deepfake Emotes (TTS + Green Screen W2L)
-
-
- Voice and video
-
- Real time voice conversion (change your voice to someone else's)
- VoxelCam 3D volumetric camera (a webcam, but 3D, and directly injectable
- into games)
-
-
- Big Secret Project
-
-
- {/*Storyteller Engine
-
- This is a high quality cinematic game engine.
- You don't need to own an expensive computer. It can run in the cloud.
- Real time 3D motion capture of one or more streamers / actors.
- Think VTubing, but Star Wars quality.
- Dedicated control plane for camera, lighting, environmental control OPs.
- Embed rich content: games, browser panes, video feeds
- Let your audience control your world
-
- */}
-
-
- {/*
You'll be able to make films, Hollywood-quality streams, and so much more!
*/}
-
-
-
-
-
-
- )
-}
-
-export { ComingSoonPage }
\ No newline at end of file
diff --git a/packages/power.stream-dashboard/src/pages/dashboard/DashboardPage.tsx b/packages/power.stream-dashboard/src/pages/dashboard/DashboardPage.tsx
deleted file mode 100644
index 17a9b1be6b..0000000000
--- a/packages/power.stream-dashboard/src/pages/dashboard/DashboardPage.tsx
+++ /dev/null
@@ -1,153 +0,0 @@
-import React, { useCallback, useEffect, useState } from "react";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faVideo, faVolumeUp } from "@fortawesome/free-solid-svg-icons";
-import {
- faDiscord,
- faTwitch,
- faTwitter,
-} from "@fortawesome/free-brands-svg-icons";
-import { Link } from "react-router-dom";
-import { MustBeLoggedInView } from "../../layout/MustBeLoggedInView";
-import { StorytellerUrlConfig } from "@storyteller/components/src/urls/StorytellerUrlConfig";
-import {
- CheckTwitchOauth,
- CheckTwitchOauthIsError,
- CheckTwitchOauthIsOk,
-} from "@storyteller/components/src/api/storyteller/twitch_oauth/CheckTwitchOauth";
-
-interface Props {
- sessionWrapper: SessionWrapper;
-}
-
-function DashboardPage(props: Props) {
- const oauthUrl = new StorytellerUrlConfig().twitchOauthEnrollRedirect();
-
- const [oauthTokenFound, setOauthTokenFound] = useState(false);
-
- const checkTwitchOauth = useCallback(async () => {
- const result = await CheckTwitchOauth();
-
- if (CheckTwitchOauthIsOk(result)) {
- setOauthTokenFound(result.oauth_token_found);
- } else if (CheckTwitchOauthIsError(result)) {
- }
- }, []);
-
- useEffect(() => {
- checkTwitchOauth();
- }, [checkTwitchOauth]);
-
- let twitchText = "Link Your Twitch";
-
- if (oauthTokenFound) {
- twitchText = "Re-link Your Twitch";
- }
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return (
- <>
-
- >
- );
- }
-
- return (
-
-
-
-
- Dashboard
-
-
-
-
-
-
-
{twitchText}
-
- Morbi dapibus commodo porta. Sed faucibus tristique orci in
- tristique. Praesent quam nunc, fermentum eu feugiat sit.
-
-
- {twitchText}
-
-
-
-
-
-
-
Configure TTS
-
- Morbi dapibus commodo porta. Sed faucibus tristique orci in
- tristique. Praesent quam nunc, fermentum eu feugiat sit.
-
-
- Setup TTS
-
-
-
-
-
-
-
OBS Setup
-
- Morbi dapibus commodo porta. Sed faucibus tristique orci in
- tristique. Praesent quam nunc, fermentum eu feugiat sit.
-
-
- Setup OBS
-
-
-
-
-
-
-
-
-
-
-
-
-
- Got questions?
-
-
-
-
- Join Our Community
-
-
- We're happy to answer your questions if you have any.
-
- Please join us in Discord so that we know what you're thinking.
-
-
-
-
- Follow on Twitter
-
-
-
- Join our Discord
-
-
-
-
-
- );
-}
-
-export { DashboardPage };
diff --git a/packages/power.stream-dashboard/src/pages/index/IndexPage.tsx b/packages/power.stream-dashboard/src/pages/index/IndexPage.tsx
deleted file mode 100644
index deca123aca..0000000000
--- a/packages/power.stream-dashboard/src/pages/index/IndexPage.tsx
+++ /dev/null
@@ -1,408 +0,0 @@
-import React from "react";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import {
- faArrowRight,
- faHeadphones,
- faVolumeUp,
-} from "@fortawesome/free-solid-svg-icons";
-import { faDiscord } from "@fortawesome/free-brands-svg-icons";
-import { VoicePreviewPlayer } from "./VoicePreviewPlayer";
-import { wavesurferConfigs } from "./wsConfig";
-
-interface Props {
- sessionWrapper: SessionWrapper;
-}
-
-let wavesurfers = wavesurferConfigs.map((config) => {
- let ws = (
-
- );
- return ws;
-});
-
-function IndexPage(props: Props) {
- return (
-
-
-
-
-
- Text to Speech For Your Stream
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
- eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
-
-
- Sign Up Now
-
-
-
-
- Join Discord
-
-
-
-
-
-
-
-
-
- {
-
-
-
- Tracer (Overwatch)
-
-
- “Look out world! Tracer's here.”
-
-
- }
-
-
-
- Sonic the Hedgehog
-
-
- "This is what speed looks like."
-
-
-
-
-
-
-
-
-
-
-
Features
-
-
-
- TTS Features
-
-
-
-
-
-
-
Channel Points
-
- Morbi dapibus commodo porta. Sed faucibus tristique orci in
- tristique. Praesent quam nunc, fermentum eu feugiat sit.
-
-
- Sign up now
-
-
-
-
-
-
-
-
Over 2000 voices!
-
- Morbi dapibus commodo porta. Sed faucibus tristique orci in
- tristique. Praesent quam nunc, fermentum eu feugiat sit.
-
-
- See all the voices on FakeYou
-
-
-
-
-
-
-
-
Bits
-
- Morbi dapibus commodo porta. Sed faucibus tristique orci in
- tristique. Praesent quam nunc, fermentum eu feugiat sit.
-
-
- Sign up now
-
-
-
-
-
-
-
-
-
-
-
-
-
-
TTS Voices
-
-
-
- Voice Previews
-
-
-
{wavesurfers}
-
-
- These are just a few samples of the voices available! Listen to
- 2000+ more usable voices on
- our TTS platform FakeYou.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Statistics
-
-
-
- Our Insights
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
- eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
-
-
-
-
+2000
-
TTS Voices in the library (and growing)
-
-
+10K
-
Daily generated TTS or something
-
-
+1.1M
-
Some kind of statistic here
-
-
-
-
-
-
-
-
-
-
-
-
-
- Questions and Answers
-
-
-
-
- Frequently Asked Questions
-
-
- FAQ
-
-
-
-
-
-
-
- What is Storyteller TTS?
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
- do eiusmod tempor incididunt ut labore et dolore magna
- aliqua. Ut enim ad minim veniam, quis nostrud exercitation
- ullamco laboris nisi ut aliquip ex ea commodo consequat.
- Duis aute irure dolor in reprehenderit in voluptate velit
- esse cillum dolore eu fugiat nulla pariatur.
-
-
-
-
-
-
- How do I use Storyteller TTS?
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
- do eiusmod tempor incididunt ut labore et dolore magna
- aliqua. Ut enim ad minim veniam, quis nostrud exercitation
- ullamco laboris nisi ut aliquip ex ea commodo consequat.
- Duis aute irure dolor in reprehenderit in voluptate velit
- esse cillum dolore eu fugiat nulla pariatur.
-
-
-
-
-
-
- What is this question?
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
- do eiusmod tempor incididunt ut labore et dolore magna
- aliqua. Ut enim ad minim veniam, quis nostrud exercitation
- ullamco laboris nisi ut aliquip ex ea commodo consequat.
- Duis aute irure dolor in reprehenderit in voluptate velit
- esse cillum dolore eu fugiat nulla pariatur.
-
-
-
-
-
-
-
-
-
-
-
-
-
- Still got questions?
-
-
-
-
- Join Our Community
-
-
- We'd love to chat with you!
-
- Please join us in Discord so that we know what you're thinking.
-
-
-
- Follow on Twitter
-
-
- Join our Discord
-
-
-
-
-
-
-
-
-
-
-
-
-
- Get started with Storyteller Stream!
-
-
- Sign Up
-
- Join Discord
-
-
-
-
-
-
-
- );
-}
-
-export { IndexPage };
diff --git a/packages/power.stream-dashboard/src/pages/index/VoicePreviewPlayer.tsx b/packages/power.stream-dashboard/src/pages/index/VoicePreviewPlayer.tsx
deleted file mode 100644
index 068268ac03..0000000000
--- a/packages/power.stream-dashboard/src/pages/index/VoicePreviewPlayer.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import React, { useState, useCallback } from "react";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import {
- faMicrophone,
- faPause,
- faPlay,
-} from "@fortawesome/free-solid-svg-icons";
-import Wavesurfer from "react-wavesurfer.js";
-
-interface Props {
- filename: string;
- title: string;
-}
-
-function VoicePreviewPlayer(props: Props) {
- const [isPlaying, setIsPlaying] = useState(false);
- const [position, setPosition] = useState(0);
-
- const handleTogglePlay = useCallback(() => {
- if (!isPlaying) {
- setIsPlaying(true);
- } else {
- setIsPlaying(false);
- }
- }, [isPlaying]);
-
- const handleFinish = useCallback(() => {
- setIsPlaying(false);
- setPosition(0);
- return [setIsPlaying, setPosition];
- }, []);
-
- let playButtonText = ;
-
- if (isPlaying) {
- playButtonText = (
- <>
-
- >
- );
- }
-
- return (
-
-
-
- handleTogglePlay()}
- >
- {playButtonText}
-
-
-
- {props.title}
-
-
-
-
-
-
-
- );
-}
-
-export { VoicePreviewPlayer };
diff --git a/packages/power.stream-dashboard/src/pages/index/subpages/LoggedInIndex.tsx b/packages/power.stream-dashboard/src/pages/index/subpages/LoggedInIndex.tsx
deleted file mode 100644
index f048e71c47..0000000000
--- a/packages/power.stream-dashboard/src/pages/index/subpages/LoggedInIndex.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { Link } from 'react-router-dom';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faCogs, faVideo } from '@fortawesome/free-solid-svg-icons';
-import { faDiscord, faTwitch } from '@fortawesome/free-brands-svg-icons';
-import { DiscordLink } from '@storyteller/components/src/elements/DiscordLink';
-import { StorytellerUrlConfig } from '@storyteller/components/src/urls/StorytellerUrlConfig';
-import { CheckTwitchOauth, CheckTwitchOauthIsError, CheckTwitchOauthIsOk } from '@storyteller/components/src/api/storyteller/twitch_oauth/CheckTwitchOauth';
-
-function LoggedInIndex() {
- const oauthUrl = new StorytellerUrlConfig().twitchOauthEnrollRedirect();
-
- const [oauthTokenFound, setOauthTokenFound] = useState(false);
-
- const checkTwitchOauth = useCallback(async () => {
- const result = await CheckTwitchOauth();
-
- if (CheckTwitchOauthIsOk(result)) {
- setOauthTokenFound(result.oauth_token_found);
- } else if (CheckTwitchOauthIsError(result)) {
- }
- }, []);
-
- useEffect(() => {
- checkTwitchOauth();
- }, [checkTwitchOauth])
-
- let linkText = <>Link Your Twitch > ;
-
- if (oauthTokenFound) {
- linkText = <>Re-link Your Twitch > ;
- }
-
-
- return (
-
-
-
-
-
- {linkText}
-
-
-
-
-
- Configure TTS
-
-
-
-
-
- Configure OBS
-
-
-
-
-
-
-
-
-
Join us in Discord!
-
- We'd like to chat with you.
-
-
- so that we know
- what you're thinking.
-
-
-
-
-
-
- )
-}
-
-export { LoggedInIndex }
\ No newline at end of file
diff --git a/packages/power.stream-dashboard/src/pages/index/subpages/LoggedOutIndex.tsx b/packages/power.stream-dashboard/src/pages/index/subpages/LoggedOutIndex.tsx
deleted file mode 100644
index 329c20bdd9..0000000000
--- a/packages/power.stream-dashboard/src/pages/index/subpages/LoggedOutIndex.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faRocket, faSignInAlt } from '@fortawesome/free-solid-svg-icons';
-
-function LoggedOutIndex() {
- return (
-
-
-
-
-
You'll need an account to continue…
- (If you have a FakeYou.com account, it'll work here too.)
-
-
- Sign Up
-
-
-
-
-
- Log In
-
-
-
-
-
-
-
- )
-}
-
-export { LoggedOutIndex }
\ No newline at end of file
diff --git a/packages/power.stream-dashboard/src/pages/index/wsConfig.ts b/packages/power.stream-dashboard/src/pages/index/wsConfig.ts
deleted file mode 100644
index 8825e7e7f7..0000000000
--- a/packages/power.stream-dashboard/src/pages/index/wsConfig.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-export let wavesurferConfigs = [
- {
- filename: "/assets/preview/tracer.wav",
- title: "Tracer",
- },
- {
- filename: "/assets/preview/son-goku.wav",
- title: "Son Goku",
- },
- {
- filename: "/assets/preview/stan-lee.wav",
- title: "Stan Lee",
- },
- {
- filename: "/assets/preview/monokuma.wav",
- title: "Monokuma",
- },
- {
- filename: "/assets/preview/monokuma.wav",
- title: "Monokuma",
- },
- {
- filename: "/assets/preview/monokuma.wav",
- title: "Monokuma",
- },
- {
- filename: "/assets/preview/monokuma.wav",
- title: "Monokuma",
- },
- {
- filename: "/assets/preview/monokuma.wav",
- title: "Monokuma",
- },
- {
- filename: "/assets/preview/monokuma.wav",
- title: "Monokuma",
- },
- {
- filename: "/assets/preview/monokuma.wav",
- title: "Monokuma",
- },
- {
- filename: "/assets/preview/monokuma.wav",
- title: "Monokuma",
- },
- {
- filename: "/assets/preview/monokuma.wav",
- title: "Monokuma",
- },
-];
diff --git a/packages/power.stream-dashboard/src/pages/login/LoginPage.tsx b/packages/power.stream-dashboard/src/pages/login/LoginPage.tsx
deleted file mode 100644
index a742598fa6..0000000000
--- a/packages/power.stream-dashboard/src/pages/login/LoginPage.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import React, { useState } from "react";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faUser, faKey } from "@fortawesome/free-solid-svg-icons";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import { Link, useHistory } from "react-router-dom";
-import {
- CreateSession,
- CreateSessionIsError,
- CreateSessionIsSuccess,
-} from "@storyteller/components/src/api/session/CreateSession";
-
-interface Props {
- sessionWrapper: SessionWrapper;
- querySessionAction: () => void;
-}
-
-function LoginPage(props: Props) {
- let history = useHistory();
-
- const [password, setPassword] = useState("");
- const [usernameOrEmail, setUsernameOrEmail] = useState("");
- const [errorMessage, setErrorMessage] = useState("");
-
- if (props.sessionWrapper.isLoggedIn()) {
- history.push("/");
- }
-
- const handleUsernameOrEmailChange = (
- ev: React.FormEvent
- ) => {
- ev.preventDefault();
- const usernameOrEmailValue = (ev.target as HTMLInputElement).value;
- setUsernameOrEmail(usernameOrEmailValue);
- setErrorMessage("");
- return false;
- };
-
- const handlePasswordChange = (ev: React.FormEvent) => {
- ev.preventDefault();
- const passwordValue = (ev.target as HTMLInputElement).value;
- setPassword(passwordValue);
- setErrorMessage("");
- return false;
- };
-
- const handleFormSubmit = async (
- ev: React.FormEvent
- ): Promise => {
- ev.preventDefault();
-
- const request = {
- username_or_email: usernameOrEmail,
- password: password,
- };
-
- const response = await CreateSession(request);
-
- if (CreateSessionIsError(response)) {
- setErrorMessage(response.error_message);
- } else if (CreateSessionIsSuccess(response)) {
- console.log("querying new session");
- props.querySessionAction();
- history.push("/");
- }
-
- return false;
- };
-
- let errorWarning = ;
- if (errorMessage) {
- errorWarning = (
-
- Login Error: {errorMessage}
-
- );
- }
-
- return (
-
-
-
-
-
Login
-
- You can login with your{" "}
-
- Fakeyou.com
- {" "}
- account.
-
-
-
-
-
-
- );
-}
-
-export { LoginPage };
diff --git a/packages/power.stream-dashboard/src/pages/obs_configs/ObsConfigsPage.tsx b/packages/power.stream-dashboard/src/pages/obs_configs/ObsConfigsPage.tsx
deleted file mode 100644
index add2bb19a0..0000000000
--- a/packages/power.stream-dashboard/src/pages/obs_configs/ObsConfigsPage.tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-import React, { useCallback, useEffect, useState } from "react";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faDiscord, faTwitch } from "@fortawesome/free-brands-svg-icons";
-import {
- CheckTwitchOauth,
- CheckTwitchOauthIsError,
- CheckTwitchOauthIsOk,
-} from "@storyteller/components/src/api/storyteller/twitch_oauth/CheckTwitchOauth";
-import { StorytellerUrlConfig } from "@storyteller/components/src/urls/StorytellerUrlConfig";
-import { faExternalLinkAlt, faPlus } from "@fortawesome/free-solid-svg-icons";
-import { MustBeLoggedInView } from "../../layout/MustBeLoggedInView";
-
-interface Props {
- sessionWrapper: SessionWrapper;
-}
-
-function ObsConfigsPage(props: Props) {
- const [maybeTwitchUsername, setMaybeTwitchUsername] = useState("");
-
- const checkTwitchOauth = useCallback(async () => {
- const result = await CheckTwitchOauth();
-
- if (CheckTwitchOauthIsOk(result)) {
- setMaybeTwitchUsername(result.maybe_twitch_username || "");
- } else if (CheckTwitchOauthIsError(result)) {
- }
- }, []);
-
- useEffect(() => {
- checkTwitchOauth();
- }, [checkTwitchOauth]);
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return (
- <>
-
- >
- );
- }
-
- const username = !!maybeTwitchUsername
- ? maybeTwitchUsername
- : "YOUR_USERNAME";
-
- const obsUrl = new StorytellerUrlConfig().obsPageFortwitch(username);
-
- return (
-
-
-
- OBS Setup
-
- Set up your OBS
-
-
-
-
Your OBS Browser Source
-
-
- Once you've linked Twitch with Storyteller, you can add the
- following page as an OBS Browser Source :
-
-
-
-
-
Setup Instructions
-
-
-
- If you're not familiar with OBS or you need a refresher, here's a
- quick glance at the setup.
-
-
-
- Step 1) Click to add a new
- Browser Source
-
-
-
-
-
- The bottom of OBS should have a plus icon (
- ) to add new sources. You'll want
- to add a new Browser Source .
-
-
-
- Step 2) Paste the URL into the configuration window
-
-
-
-
-
- Remember, the URL is {obsUrl}
-
-
-
Step 3) Activate the Page
-
-
-
-
- Right click the layer and select “Interact” .
- This will let you click the button and activate the service.
-
-
-
- Good to go!
-
-
-
- You can now hide the layer and make it invisible. The audio levels
- are also independently adjustable.
-
-
-
- Have fun! We'll be back soon with some even more jaw-dropping
- utilities.
-
-
-
-
-
- );
-}
-
-export { ObsConfigsPage };
diff --git a/packages/power.stream-dashboard/src/pages/signup/SignupPage.tsx b/packages/power.stream-dashboard/src/pages/signup/SignupPage.tsx
deleted file mode 100644
index ae724525f9..0000000000
--- a/packages/power.stream-dashboard/src/pages/signup/SignupPage.tsx
+++ /dev/null
@@ -1,376 +0,0 @@
-import React, { useState } from "react";
-import {
- CreateAccount,
- CreateAccountIsError,
- CreateAccountIsSuccess,
-} from "@storyteller/components/src/api/user/CreateAccount";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Link } from "react-router-dom";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import {
- iconEmailField,
- iconUser,
-} from "@storyteller/components/src/icons/SemanticIcons";
-import { faKey } from "@fortawesome/free-solid-svg-icons";
-
-enum FieldTriState {
- EMPTY_FALSE,
- FALSE,
- TRUE,
-}
-
-interface Props {
- sessionWrapper: SessionWrapper;
- querySessionCallback: () => void;
-}
-
-function SignupPage(props: Props) {
- const [username, setUsername] = useState("");
- const [usernameValid, setUsernameValid] = useState(FieldTriState.EMPTY_FALSE);
- const [usernameInvalidReason, setUsernameInvalidReason] = useState("");
-
- const [email, setEmail] = useState("");
- const [emailValid, setEmailValid] = useState(FieldTriState.EMPTY_FALSE);
- const [emailInvalidReason, setEmailInvalidReason] = useState("");
-
- const [password, setPassword] = useState("");
- const [passwordValid, setPasswordValid] = useState(FieldTriState.EMPTY_FALSE);
- const [passwordInvalidReason, setPasswordInvalidReason] = useState("");
-
- const [passwordConfirmation, setPasswordConfirmation] = useState("");
- const [passwordConfirmationValid, setPasswordConfirmationValid] = useState(
- FieldTriState.EMPTY_FALSE
- );
- const [
- passwordConfirmationInvalidReason,
- setPasswordConfirmationInvalidReason,
- ] = useState("");
-
- const handleUsernameChange = (ev: React.FormEvent) => {
- ev.preventDefault();
-
- const newUsername = (ev.target as HTMLInputElement).value;
-
- let usernameValid = FieldTriState.EMPTY_FALSE;
- let usernameInvalidReason = "";
-
- if (newUsername.length > 1) {
- if (newUsername.length < 3) {
- usernameValid = FieldTriState.FALSE;
- usernameInvalidReason = "username is too short";
- } else if (newUsername.length > 15) {
- usernameValid = FieldTriState.FALSE;
- usernameInvalidReason = "username is too long";
- } else {
- usernameValid = FieldTriState.TRUE;
- }
- }
-
- setUsername(newUsername);
- setUsernameValid(usernameValid);
- setUsernameInvalidReason(usernameInvalidReason);
-
- return false;
- };
-
- const handleEmailChange = (ev: React.FormEvent) => {
- ev.preventDefault();
-
- const newEmail = (ev.target as HTMLInputElement).value;
-
- let emailValid = FieldTriState.EMPTY_FALSE;
- let emailInvalidReason = "";
-
- if (newEmail.length > 1) {
- if (newEmail.length < 3) {
- emailValid = FieldTriState.FALSE;
- emailInvalidReason = "email is too short";
- } else if (!newEmail.includes("@")) {
- emailValid = FieldTriState.FALSE;
- emailInvalidReason = "email is invalid";
- } else {
- emailValid = FieldTriState.TRUE;
- }
- }
-
- setEmail(newEmail);
- setEmailValid(emailValid);
- setEmailInvalidReason(emailInvalidReason);
-
- return false;
- };
-
- const handlePasswordChange = (ev: React.FormEvent) => {
- ev.preventDefault();
-
- const newPassword = (ev.target as HTMLInputElement).value;
-
- let passwordValid = FieldTriState.EMPTY_FALSE;
- let passwordInvalidReason = "";
- let passwordConfirmationValid = FieldTriState.EMPTY_FALSE;
- let passwordConfirmationInvalidReason = "";
-
- if (newPassword.length > 1) {
- if (newPassword.length < 5) {
- passwordValid = FieldTriState.FALSE;
- passwordInvalidReason = "password is too short";
- } else {
- passwordValid = FieldTriState.TRUE;
- }
-
- if (newPassword !== passwordConfirmation) {
- passwordConfirmationValid = FieldTriState.FALSE;
- passwordConfirmationInvalidReason = "passwords do not match";
- } else {
- passwordConfirmationValid = FieldTriState.TRUE;
- passwordConfirmationInvalidReason = "";
- }
- }
-
- setPassword(newPassword);
- setPasswordValid(passwordValid);
- setPasswordInvalidReason(passwordInvalidReason);
- setPasswordConfirmationValid(passwordConfirmationValid);
- setPasswordConfirmationInvalidReason(passwordConfirmationInvalidReason);
-
- return false;
- };
-
- const handlePasswordConfirmationChange = (
- ev: React.FormEvent
- ) => {
- ev.preventDefault();
-
- const newPasswordConfirmation = (ev.target as HTMLInputElement).value;
-
- let passwordConfirmationValid = FieldTriState.EMPTY_FALSE;
- let passwordConfirmationInvalidReason = "";
-
- if (newPasswordConfirmation.length > 1) {
- if (newPasswordConfirmation !== password) {
- passwordConfirmationValid = FieldTriState.FALSE;
- passwordConfirmationInvalidReason = "passwords do not match";
- } else {
- passwordConfirmationValid = FieldTriState.TRUE;
- passwordConfirmationInvalidReason = "";
- }
- }
-
- setPasswordConfirmation(newPasswordConfirmation);
- setPasswordConfirmationValid(passwordConfirmationValid);
- setPasswordConfirmationInvalidReason(passwordConfirmationInvalidReason);
-
- return false;
- };
-
- const handleFormSubmit = async (
- ev: React.FormEvent
- ): Promise => {
- ev.preventDefault();
-
- if (
- !usernameValid ||
- !emailValid ||
- !passwordValid ||
- !passwordConfirmationValid
- ) {
- return false;
- }
-
- const request = {
- username: username,
- email_address: email,
- password: password,
- password_confirmation: passwordConfirmation,
- };
-
- const response = await CreateAccount(request);
-
- if (CreateAccountIsError(response)) {
- if ("email_address" in response.error_fields) {
- setEmailValid(FieldTriState.FALSE);
- setEmailInvalidReason(response.error_fields["email_address"] || "");
- }
- if ("username" in response.error_fields) {
- setUsernameValid(FieldTriState.FALSE);
- setUsernameInvalidReason(response.error_fields["username"] || "");
- }
- } else if (CreateAccountIsSuccess(response)) {
- console.log("querying new session");
- props.querySessionCallback();
-
- // TODO: Switch to functional component.
- window.location.href = "/";
- }
-
- return false;
- };
-
- if (props.sessionWrapper.isLoggedIn()) {
- return Invalid view for logged in users.
;
- }
-
- let usernameInputClass = "form-control";
- let usernameHelpClass = "form-text text-red mt-2";
- switch (usernameValid) {
- case FieldTriState.EMPTY_FALSE:
- break;
- case FieldTriState.FALSE:
- usernameInputClass += " is-danger";
- usernameHelpClass += " is-danger";
- break;
- case FieldTriState.TRUE:
- usernameInputClass += " is-success";
- usernameHelpClass += " is-success";
- break;
- }
-
- let emailInputClass = "form-control";
- let emailHelpClass = "form-text text-red mt-2";
- switch (emailValid) {
- case FieldTriState.EMPTY_FALSE:
- break;
- case FieldTriState.FALSE:
- emailInputClass += " is-danger";
- emailHelpClass += " is-danger";
- break;
- case FieldTriState.TRUE:
- emailInputClass += " is-success";
- emailHelpClass += " is-success";
- break;
- }
-
- let passwordInputClass = "form-control";
- let passwordHelpClass = "form-text text-red mt-2";
- switch (passwordValid) {
- case FieldTriState.EMPTY_FALSE:
- break;
- case FieldTriState.FALSE:
- passwordInputClass += " is-danger";
- passwordHelpClass += " is-danger";
- break;
- case FieldTriState.TRUE:
- passwordInputClass += " is-success";
- passwordHelpClass += " is-success";
- break;
- }
-
- let passwordConfirmationInputClass = "form-control";
- let passwordConfirmationHelpClass = "form-text text-red mt-2";
- switch (passwordConfirmationValid) {
- case FieldTriState.EMPTY_FALSE:
- break;
- case FieldTriState.FALSE:
- passwordConfirmationInputClass += " is-danger";
- passwordConfirmationHelpClass += " is-danger";
- break;
- case FieldTriState.TRUE:
- passwordConfirmationInputClass += " is-success";
- passwordConfirmationHelpClass += " is-success";
- break;
- }
-
- return (
-
- );
-}
-
-export { SignupPage };
diff --git a/packages/power.stream-dashboard/src/pages/stream/StreamPage.tsx b/packages/power.stream-dashboard/src/pages/stream/StreamPage.tsx
deleted file mode 100644
index cd52946b0d..0000000000
--- a/packages/power.stream-dashboard/src/pages/stream/StreamPage.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import { faTwitch } from '@fortawesome/free-brands-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import React, { useState } from 'react';
-import { useHistory, withRouter } from 'react-router-dom';
-
-function StreamPage() {
- const history = useHistory();
-
- const [twitchUsername, setTwitchUsername] = useState('')
-
- const handleTwitchUsernameChange = (ev: React.FormEvent) => {
- ev.preventDefault();
- const value = (ev.target as HTMLInputElement).value;
- setTwitchUsername(value);
- return false;
- };
-
- const openObs = (ev: React.FormEvent) => {
- ev.preventDefault();
- let url = `/obs/${twitchUsername}`;
- history.push(url);
- return false;
- };
-
- const buttonDisabled = twitchUsername.trim().length === 0;
-
- return (
-
-
-
-
- Stream TTS
-
-
- Early Alpha Preview
-
-
-
This is an early demo of our Stream TTS, powered by FakeYou.
- It does not currently offer customization, though our plans are to
- allow for a high degree of configurability: set your own voices, rewards, etc.
-
-
You don't need to set up any software on your end. Simply point OBS or your
- broadcast software the URL below:
-
-
-
-
- 1. Connect with Twitch
-
-
-
-
-
-
-
-
-
-
-
- 2. Open OBS Page
-
-
-
-
-
- )
-}
-
-export default withRouter(StreamPage);
\ No newline at end of file
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/TtsConfigsCreateRulePage.tsx b/packages/power.stream-dashboard/src/pages/tts_configs/TtsConfigsCreateRulePage.tsx
deleted file mode 100644
index be890893d8..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/TtsConfigsCreateRulePage.tsx
+++ /dev/null
@@ -1,180 +0,0 @@
-import React, { useState } from "react";
-import { v4 as uuidv4 } from "uuid";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
-import { TwitchEventRule } from "@storyteller/components/src/api/storyteller/twitch_event_rules/GetTwitchEventRule";
-import { Link, useHistory, useParams } from "react-router-dom";
-import {
- TwitchEventCategory,
- TWITCH_EVENT_CATEGORY_BY_STRING,
-} from "@storyteller/components/src/api/storyteller/twitch_event_rules/shared/TwitchEventCategory";
-import { EventResponse } from "@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventResponse";
-import { EventMatchPredicate } from "@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventMatchPredicate";
-import {
- CreateTwitchEventRule,
- CreateTwitchEventRuleRequest,
-} from "@storyteller/components/src/api/storyteller/twitch_event_rules/CreateTwitchEventRule";
-import { EventMatchPredicateBuilderComponent } from "./event_match_predicate_builder/EventMatchPredicateBuilderComponent";
-import { EventResponseComponent } from "./event_response_builder/EventResponseComponent";
-import { TwitchEventRuleElement } from "./rule_cards/TwitchEventRuleElement";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faAngleLeft, faSave } from "@fortawesome/free-solid-svg-icons";
-import { MustBeLoggedInView } from "../../layout/MustBeLoggedInView";
-
-interface Props {
- sessionWrapper: SessionWrapper;
- allTtsModels: TtsModelListItem[];
- allTtsModelsByToken: Map;
-}
-
-function TtsConfigsCreateRulePage(props: Props) {
- const { event_category } = useParams() as { event_category: string };
-
- const history = useHistory();
-
- // TODO: Use centralized configs
- const indexLink = "/tts_configs";
-
- // ========== In-Progress Model Edits ==========
-
- // Sent to sub-components to initialize UI
- // NB: This is a HACK. The subcomponent views were originally created for the "Edit" UI
- // and this had to be done to get them to work for "Create" UI. Sending empty dictionaries
- // or statically initialized values froze the UI.
- const [serverEventMatchPredicate] = useState({});
- const [serverEventResponse] = useState({});
-
- const [modifiedEventMatchPredicate, setModifiedEventMatchPredicate] =
- useState({});
- const [modifiedEventResponse, setModifiedEventResponse] =
- useState({});
- const [ruleIsDisabled] = useState(false);
-
- let maybeTwitchEventCategory =
- TWITCH_EVENT_CATEGORY_BY_STRING.get(event_category);
-
- if (maybeTwitchEventCategory === undefined) {
- history.push(indexLink);
- return <>>;
- }
-
- // NB: To satisfy the type system and not be a union on 'undefined'
- const twitchEventCategory = maybeTwitchEventCategory;
-
- const updateModifiedEventMatchPredicate = (
- predicate: EventMatchPredicate
- ) => {
- setModifiedEventMatchPredicate(predicate);
- };
-
- const updateModifiedEventResponse = (response: EventResponse) => {
- setModifiedEventResponse(response);
- };
-
- const handleFormSubmit = async (
- ev: React.FormEvent
- ): Promise => {
- ev.preventDefault();
-
- // TODO: Check for errors.
-
- let newEventMatchPredicate = modifiedEventMatchPredicate;
- let newEventResponse = modifiedEventResponse;
-
- // TODO: UUID idempotency token that updates on every model change.
-
- const request: CreateTwitchEventRuleRequest = {
- idempotency_token: uuidv4(),
- event_category: twitchEventCategory,
- event_match_predicate: newEventMatchPredicate,
- event_response: newEventResponse,
- rule_is_disabled: ruleIsDisabled,
- user_specified_rule_order: 1000,
- };
-
- const result = await CreateTwitchEventRule(request);
- if (result.success) {
- history.push(indexLink);
- }
-
- return false;
- };
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return (
- <>
-
- >
- );
- }
-
- // NB: This is a hypothetical version of what we'll update to
- let renderRule: TwitchEventRule = {
- // Not yet saved, so fake values
- token: "virtual",
- user_specified_rule_order: 1000,
- created_at: new Date(),
- updated_at: new Date(),
-
- // Updated in UI
- event_category: twitchEventCategory,
- event_match_predicate: modifiedEventMatchPredicate,
- event_response: modifiedEventResponse,
- rule_is_disabled: ruleIsDisabled,
- };
-
- const titlesByCategory = new Map([
- [TwitchEventCategory.Bits, "Create New Bits Rule"],
- [TwitchEventCategory.ChannelPoints, "Create New Channel Points Rule"],
- ]);
-
- const pageTitle = titlesByCategory.get(twitchEventCategory);
-
- return (
- <>
-
-
- {pageTitle}
-
-
-
-
-
-
-
-
- Cancel / Go Back
-
-
- >
- );
-}
-
-export { TtsConfigsCreateRulePage };
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/TtsConfigsDeleteRulePage.tsx b/packages/power.stream-dashboard/src/pages/tts_configs/TtsConfigsDeleteRulePage.tsx
deleted file mode 100644
index d9af3f53ae..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/TtsConfigsDeleteRulePage.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import React, { useCallback, useEffect, useState } from "react";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import {
- GetTwitchEventRule,
- GetTwitchEventRuleIsError,
- GetTwitchEventRuleIsOk,
- TwitchEventRule,
-} from "@storyteller/components/src/api/storyteller/twitch_event_rules/GetTwitchEventRule";
-import { DeleteTwitchEventRule } from "@storyteller/components/src/api/storyteller/twitch_event_rules/DeleteTwitchEventRule";
-import { TwitchEventRuleElement } from "./rule_cards/TwitchEventRuleElement";
-import { Link, useHistory, useParams } from "react-router-dom";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faAngleLeft, faTrash } from "@fortawesome/free-solid-svg-icons";
-import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
-import { MustBeLoggedInView } from "../../layout/MustBeLoggedInView";
-
-interface Props {
- sessionWrapper: SessionWrapper;
- allTtsModelsByToken: Map;
-}
-
-function TtsConfigsDeleteRulePage(props: Props) {
- const { token }: { token: string } = useParams();
-
- const history = useHistory();
-
- const [twitchEventRule, setTwitchEventRule] = useState<
- TwitchEventRule | undefined
- >(undefined);
-
- const getTwitchEventRule = useCallback(async (token: string) => {
- const response = await GetTwitchEventRule(token);
-
- if (GetTwitchEventRuleIsOk(response)) {
- setTwitchEventRule(response.twitch_event_rule);
- } else if (GetTwitchEventRuleIsError(response)) {
- // TODO
- }
- }, []);
-
- useEffect(() => {
- getTwitchEventRule(token);
- }, [getTwitchEventRule, token]);
-
- const indexLink = "/tts_configs";
-
- const handleDeleteFormSubmit = async (
- ev: React.FormEvent
- ): Promise => {
- ev.preventDefault();
-
- const result = await DeleteTwitchEventRule(token);
- if (result.success) {
- history.push(indexLink);
- }
-
- return false;
- };
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return (
- <>
-
- >
- );
- }
-
- if (twitchEventRule === undefined) {
- return Loading... ;
- }
-
- return (
- <>
-
-
- Delete Rule?
-
-
-
-
-
-
-
-
-
-
-
-
- Cancel / Go Back
-
-
- >
- );
-}
-
-export { TtsConfigsDeleteRulePage };
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/TtsConfigsEditRulePage.tsx b/packages/power.stream-dashboard/src/pages/tts_configs/TtsConfigsEditRulePage.tsx
deleted file mode 100644
index 4f242d73fd..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/TtsConfigsEditRulePage.tsx
+++ /dev/null
@@ -1,196 +0,0 @@
-import React, { useCallback, useEffect, useState } from "react";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import {
- GetTwitchEventRule,
- GetTwitchEventRuleIsError,
- GetTwitchEventRuleIsOk,
- TwitchEventRule,
-} from "@storyteller/components/src/api/storyteller/twitch_event_rules/GetTwitchEventRule";
-import {
- EditTwitchEventRule,
- EditTwitchEventRuleRequest,
-} from "@storyteller/components/src/api/storyteller/twitch_event_rules/EditTwitchEventRule";
-import { TwitchEventRuleElement } from "./rule_cards/TwitchEventRuleElement";
-import { Link, useHistory, useParams } from "react-router-dom";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faAngleLeft, faSave } from "@fortawesome/free-solid-svg-icons";
-import { EventMatchPredicate } from "@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventMatchPredicate";
-import { EventResponse } from "@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventResponse";
-import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
-import { EventResponseComponent } from "./event_response_builder/EventResponseComponent";
-import { EventMatchPredicateBuilderComponent } from "./event_match_predicate_builder/EventMatchPredicateBuilderComponent";
-import { MustBeLoggedInView } from "../../layout/MustBeLoggedInView";
-
-interface Props {
- sessionWrapper: SessionWrapper;
- allTtsModels: TtsModelListItem[];
- allTtsModelsByToken: Map;
-}
-
-function TtsConfigsEditRulePage(props: Props) {
- const { token }: { token: string } = useParams();
-
- // TODO: Use centralized URL configs
- const indexLink = "/tts_configs";
-
- const history = useHistory();
-
- // ========== Initial Load of Server State ==========
-
- const [twitchEventRule, setTwitchEventRule] = useState<
- TwitchEventRule | undefined
- >(undefined);
-
- // Sent to sub-components to initialize UI
- const [serverEventMatchPredicate, setServerEventMatchPredicate] =
- useState({});
- const [serverEventResponse, setServerEventResponse] = useState(
- {}
- );
-
- // ========== In-Progress Model Edits ==========
-
- const [modifiedEventMatchPredicate, setModifiedEventMatchPredicate] =
- useState({});
- const [modifiedEventResponse, setModifiedEventResponse] =
- useState({});
- const [ruleIsDisabled, setRuleIsDisabled] = useState(false);
-
- const getTwitchEventRule = useCallback(async (token: string) => {
- const response = await GetTwitchEventRule(token);
-
- if (GetTwitchEventRuleIsOk(response)) {
- // Source of truth
- setTwitchEventRule(response.twitch_event_rule);
- setServerEventMatchPredicate(
- response.twitch_event_rule.event_match_predicate
- );
- setServerEventResponse(response.twitch_event_rule.event_response);
-
- // In-progress modifications
- setModifiedEventMatchPredicate(
- response.twitch_event_rule.event_match_predicate
- );
- setModifiedEventResponse(response.twitch_event_rule.event_response);
- setRuleIsDisabled(response.twitch_event_rule.rule_is_disabled);
- } else if (GetTwitchEventRuleIsError(response)) {
- // TODO
- }
- }, []);
-
- useEffect(() => {
- getTwitchEventRule(token);
- }, [getTwitchEventRule, token]);
-
- const updateModifiedEventMatchPredicate = (
- predicate: EventMatchPredicate
- ) => {
- setModifiedEventMatchPredicate(predicate);
- };
-
- const updateModifiedEventResponse = (response: EventResponse) => {
- setModifiedEventResponse(response);
- };
-
- const handleFormSubmit = async (
- ev: React.FormEvent
- ): Promise => {
- ev.preventDefault();
-
- // TODO: Check for errors.
-
- let newEventMatchPredicate = modifiedEventMatchPredicate;
- let newEventResponse = modifiedEventResponse;
-
- const request: EditTwitchEventRuleRequest = {
- event_match_predicate: newEventMatchPredicate,
- event_response: newEventResponse,
- rule_is_disabled: ruleIsDisabled,
- };
-
- const result = await EditTwitchEventRule(token, request);
- if (result.success) {
- history.push(indexLink);
- }
-
- return false;
- };
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return (
- <>
-
- >
- );
- }
-
- if (twitchEventRule === undefined) {
- return Loading... ;
- }
-
- // NB: This is a hypothetical version of what we'll update to
- let renderRule: TwitchEventRule = {
- // Unchanged
- token: twitchEventRule.token,
- event_category: twitchEventRule.event_category,
- user_specified_rule_order: twitchEventRule.user_specified_rule_order,
- created_at: twitchEventRule.created_at,
- updated_at: twitchEventRule.updated_at,
-
- // Updated in UI
- event_match_predicate: modifiedEventMatchPredicate,
- event_response: modifiedEventResponse,
- rule_is_disabled: ruleIsDisabled,
- };
-
- return (
- <>
-
-
- Edit Rule
-
-
-
-
-
-
-
-
- Cancel / Go Back
-
-
- >
- );
-}
-
-export { TtsConfigsEditRulePage };
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/TtsConfigsIndexPage.tsx b/packages/power.stream-dashboard/src/pages/tts_configs/TtsConfigsIndexPage.tsx
deleted file mode 100644
index 84b32da00c..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/TtsConfigsIndexPage.tsx
+++ /dev/null
@@ -1,238 +0,0 @@
-import React, { useCallback, useEffect, useState } from "react";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import {
- ListTwitchEventRules,
- ListTwitchEventRulesIsError,
- ListTwitchEventRulesIsOk,
- TwitchEventRule,
-} from "@storyteller/components/src/api/storyteller/twitch_event_rules/ListTwitchEventRules";
-import { TwitchEventRuleElement } from "./rule_cards/TwitchEventRuleElement";
-import { TwitchEventCategory } from "@storyteller/components/src/api/storyteller/twitch_event_rules/shared/TwitchEventCategory";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import {
- faBox,
- faGem,
- faPlus,
- faSort,
-} from "@fortawesome/free-solid-svg-icons";
-import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
-import { Link } from "react-router-dom";
-import { MustBeLoggedInView } from "../../layout/MustBeLoggedInView";
-
-interface Props {
- sessionWrapper: SessionWrapper;
- allTtsModelsByToken: Map;
-}
-
-function TtsConfigsIndexPage(props: Props) {
- const [twitchEventRules, setTwitchEventRules] = useState(
- []
- );
-
- const listTwitchEventRules = useCallback(async () => {
- const response = await ListTwitchEventRules();
-
- if (ListTwitchEventRulesIsOk(response)) {
- setTwitchEventRules(response.twitch_event_rules);
- } else if (ListTwitchEventRulesIsError(response)) {
- // TODO
- }
- }, []);
-
- useEffect(() => {
- listTwitchEventRules();
- }, [listTwitchEventRules]);
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return (
- <>
-
- >
- );
- }
-
- const cheerEventRules = twitchEventRules.filter(
- (rule) => rule.event_category === TwitchEventCategory.Bits
- );
-
- const channelPointsEventRules = twitchEventRules.filter(
- (rule) => rule.event_category === TwitchEventCategory.ChannelPoints
- );
-
- return (
- <>
-
-
- Stream Setup
-
-
Configure TTS (and soon much more!)
-
- You can create rules for matching different categories of events:
- cheers (bits), channel point rewards, etc. When an event occurs in
- your stream, we compare it against the rules in its category. The
- first matched rule (from the top) is selected, and the action that it
- specifies is taken. For now this is limited to FakeYou text to speech,
- but we'll be adding lots of new capabilities soon.
-
-
-
-
-
- Bits / Cheers
-
-
- These settings are best for small and medium-sized channels.
-
-
-
-
- Create New Bits / Cheer Rule
-
-
-
- Reorder Rules
-
-
- {cheerEventRules.map((rule) => {
- return (
-
- );
- })}
-
-
-
-
-
- Channel Points / Rewards
-
-
These settings are best for small channels.
-
-
-
-
- Create Channel Points Rule
-
-
-
-
- Reorder Rules
-
-
-
- {channelPointsEventRules.map((rule) => {
- return (
-
- );
- })}
-
- {/*
-
- {" "}
- Coming Soon…
-
-
-
-
-
-
- {" "}
- Subs, Resubs, and Gifted Subs{" "}
-
-
- These settings are great for all channel sizes.
-
-
-
-
-
-
- {" "}
- Donation Services (Streamlabs,
- StreamElements, etc.){" "}
-
-
- These settings are great for large channels, but work for channels
- of all sizes.
-
-
-
-
-
-
-
- {" "}
- Text /slash{" "}
- Commands
-
-
- These settings are great for very small channels.
-
-
-
-
-
-
-
- {" "}
- User-generated Animated Deepfake
- Rewards{" "}
-
-
- A lip-synced, green screened “Famous Person” {" "}
- pops up and says something.
-
-
-
-
-
-
-
- {" "}
- Multi Voice and Sound Effects{" "}
-
-
- User-selected voices can be strung together in a single reward,
- interspersed with sound effects.
-
-
-
-
-
-
-
- {" "}
- Suggestions?{" "}
-
-
Let us know what to build!
-
- Can you think of anything we haven't provided?{" "}
- {" "}
- so that we can build it!
-
-
*/}
-
- >
- );
-}
-
-export { TtsConfigsIndexPage };
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/TtsConfigsReorderPage.tsx b/packages/power.stream-dashboard/src/pages/tts_configs/TtsConfigsReorderPage.tsx
deleted file mode 100644
index 766b5f3534..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/TtsConfigsReorderPage.tsx
+++ /dev/null
@@ -1,193 +0,0 @@
-import React, { useCallback, useEffect, useState } from "react";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import {
- ListTwitchEventRules,
- ListTwitchEventRulesIsError,
- ListTwitchEventRulesIsOk,
- TwitchEventRule,
-} from "@storyteller/components/src/api/storyteller/twitch_event_rules/ListTwitchEventRules";
-import {
- ReorderTwitchEventRules,
- ReorderTwitchEventRulesRequest,
-} from "@storyteller/components/src/api/storyteller/twitch_event_rules/ReorderTwitchEventRules";
-import {
- TwitchEventCategory,
- TWITCH_EVENT_CATEGORY_BY_STRING,
-} from "@storyteller/components/src/api/storyteller/twitch_event_rules/shared/TwitchEventCategory";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faSave } from "@fortawesome/free-solid-svg-icons";
-import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
-import { useHistory, useParams } from "react-router-dom";
-import { ReorderableTwitchEventRuleElement } from "./rule_cards/ReorderableTwitchEventRuleElement";
-import { MustBeLoggedInView } from "../../layout/MustBeLoggedInView";
-
-interface Props {
- sessionWrapper: SessionWrapper;
- allTtsModelsByToken: Map;
-}
-
-function TtsConfigsReorderPage(props: Props) {
- const { event_category } = useParams() as { event_category: string };
-
- const history = useHistory();
-
- // TODO: Use centralized configs
- const indexLink = "/tts_configs";
-
- const [twitchEventRules, setTwitchEventRules] = useState(
- []
- );
-
- const listTwitchEventRules = useCallback(
- async (twitchEventCategory: TwitchEventCategory) => {
- const response = await ListTwitchEventRules();
-
- if (ListTwitchEventRulesIsOk(response)) {
- const allRules = response.twitch_event_rules;
- const releventRules = allRules.filter(
- (rule) => rule.event_category === twitchEventCategory
- );
-
- setTwitchEventRules(releventRules);
- } else if (ListTwitchEventRulesIsError(response)) {
- // TODO
- }
- },
- []
- );
-
- useEffect(() => {
- console.log("useEffect");
- let maybeTwitchEventCategory =
- TWITCH_EVENT_CATEGORY_BY_STRING.get(event_category);
-
- if (maybeTwitchEventCategory === undefined) {
- history.push(indexLink);
- } else {
- listTwitchEventRules(maybeTwitchEventCategory);
- }
- }, [listTwitchEventRules, event_category, history]);
-
- const handleMoveUp = (ruleIndex: number) => {
- let newEventRules = [...twitchEventRules];
-
- if (
- ruleIndex >= newEventRules.length || // Walk off end
- ruleIndex === 0
- ) {
- // Can't move up.
- return false;
- }
-
- let aboveIndex = ruleIndex - 1;
- let aboveRule = newEventRules[aboveIndex];
-
- newEventRules[aboveIndex] = newEventRules[ruleIndex];
- newEventRules[ruleIndex] = aboveRule;
-
- setTwitchEventRules(newEventRules);
- };
-
- const handleMoveDown = (ruleIndex: number) => {
- let newEventRules = [...twitchEventRules];
-
- if (
- ruleIndex < 0 || // Invalid
- ruleIndex >= newEventRules.length - 1
- ) {
- // Can't move down.
- return false;
- }
-
- let belowIndex = ruleIndex + 1;
-
- console.log(ruleIndex, belowIndex);
-
- let belowRule = newEventRules[belowIndex];
-
- newEventRules[belowIndex] = newEventRules[ruleIndex];
- newEventRules[ruleIndex] = belowRule;
-
- setTwitchEventRules(newEventRules);
- };
-
- const handleSaveOrderings = async (
- ev: React.FormEvent
- ): Promise => {
- const pairs = twitchEventRules.map((rule, index) => {
- return {
- rule_token: rule.token,
- position: index,
- };
- });
-
- const request: ReorderTwitchEventRulesRequest = {
- rule_token_position_pairs: pairs,
- };
-
- const result = await ReorderTwitchEventRules(request);
- if (result.success) {
- history.push(indexLink);
- }
-
- return false;
- };
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return (
- <>
-
- >
- );
- }
-
- return (
- <>
-
-
- Reorder Rules
-
-
- {" "}
- This affects match priority. Rules at the top come first.{" "}
-
-
-
-
-
- You can create rules for matching{" "}
- different categories of events : cheers (bits), channel point
- rewards, etc. When an event occurs in your stream, we compare it
- against the rules in its category.{" "}
- The first matched rule (from the top) is selected,
- and the action that it specifies is taken. For now this is limited to
- FakeYou text to speech, but we'll be adding lots of new capabilities
- soon.
-
-
- {twitchEventRules.map((rule, index) => {
- return (
-
- );
- })}
-
-
-
- Save Orderings
-
-
- >
- );
-}
-
-export { TtsConfigsReorderPage };
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/CheerState.ts b/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/CheerState.ts
deleted file mode 100644
index 05f5ee932b..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/CheerState.ts
+++ /dev/null
@@ -1,247 +0,0 @@
-import { EventMatchPredicate } from "@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventMatchPredicate";
-import { CHEER_LOOKUP_MAP, CHEER_PREFIX_TO_STRING_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,
- bits?: number,
-}
-
-export interface CheerStateCustom {
- // NB: This can contain a numeric suffix independent of bits.
- // eg. third party cheers from 7tv.app: Sus11, Sus12, 1984, 4Weird.
- cheerFull?: string,
- bits?: number,
-}
-
-// Due to the complexity of the forms, this is used as the source of truth while editing.
-// Each type of predicate can serialize and deserialize into the predicates.
-export type CheerState = CheerStateOfficial | CheerStateCustom;
-
-export function CheerStateIsOfficial(cheerState: CheerState): cheerState is CheerStateOfficial {
- return cheerState.hasOwnProperty('cheerPrefix');
-}
-
-export function CheerStateIsCustom(cheerState: CheerState): cheerState is CheerStateCustom {
- return cheerState.hasOwnProperty('cheerFull');
-}
-
-// Convert "exact match" strings into CheerState
-export function convertExactMatchToCheerState(freeformText: string) : CheerState {
- const trimmed = freeformText.trim();
-
- let { cheerPrefix, bitValue } = CheerUtil.parseCheerString(trimmed);
-
- let maybeCheer = CHEER_LOOKUP_MAP.get(cheerPrefix || '');
-
- if (maybeCheer !== undefined) {
- return {
- cheerPrefix: maybeCheer,
- bits: bitValue,
- }
- }
-
- // NB: In this case, bits value may be wrong.
- // eg. "1984"
- return {
- cheerFull: freeformText,
- bits: bitValue,
- };
-}
-
-// Convert "prefix + bits" into CheerState
-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: maybeBits,
- }
- }
-
- // NB: In this case, bits value may be wrong.
- // eg. "1984"
- return {
- cheerFull: freeformText,
- 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 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,
- }
- } else {
- return {};
- }
-}
-
-// Turn a cheer state into a predicate
-export function cheerStateToPredicate(
- cheerState: CheerState,
- bitsRuleType: BitsRuleType) : EventMatchPredicate
-{
- let predicate : EventMatchPredicate = {};
-
- switch (bitsRuleType) {
- case BitsRuleType.BitsCheermoteNameExactMatch:
- let cheermoteName = '';
- if (CheerStateIsOfficial(cheerState)) {
- let cheerPrefix = '';
- let bitsValue = '';
- if (!!cheerState.cheerPrefix) {
- cheerPrefix = CHEER_PREFIX_TO_STRING_MAP.get(cheerState.cheerPrefix) || '';
- }
- if (!!cheerState.bits && !isNaN(cheerState.bits) && cheerState.bits > 1) {
- bitsValue = cheerState.bits.toString();
- }
-
- if (!!cheerPrefix) {
- // Don't bother setting unless we have a prefix
- cheermoteName = `${cheerPrefix}${bitsValue}`
- }
-
- } else if (CheerStateIsCustom(cheerState)) {
- // Cheer name is the full value, eg. 'ZomboCom1000'
- cheermoteName = cheerState.cheerFull || '';
- }
-
- predicate.bits_cheermote_name_exact_match = {
- cheermote_name: cheermoteName,
- }
- break;
-
- case BitsRuleType.BitsCheermotePrefixSpendThreshold:
- let newBitsValue = 1;
- if (!!cheerState.bits && !isNaN(cheerState.bits) && cheerState.bits > 1) {
- newBitsValue = cheerState.bits;
- }
-
- let newPrefix = '';
- if (CheerStateIsOfficial(cheerState)) {
- if (!!cheerState.cheerPrefix) {
- newPrefix = CHEER_PREFIX_TO_STRING_MAP.get(cheerState.cheerPrefix) || '';
- }
- } else if (CheerStateIsCustom(cheerState)) {
- newPrefix = cheerState.cheerFull || '';
- }
-
- predicate.bits_cheermote_prefix_spend_threshold = {
- cheermote_prefix: newPrefix,
- minimum_bits_spent: newBitsValue,
- }
- break;
-
- case BitsRuleType.BitsSpendThreshold:
- let newBits = 1;
- if (!!cheerState.bits && !isNaN(cheerState.bits) && cheerState.bits > 1) {
- newBits = cheerState.bits;
- }
- predicate.bits_spend_threshold = {
- minimum_bits_spent: newBits,
- }
- break;
- }
-
- return predicate;
-}
-
-
-/*
-
-Truth table-ish breakdown (incomplete).
-
-"Corgo" is an official cheer
-"Zombo", "Sus12", and "1984" are custom cheers
-
-------------------------------------------------------------------------------------------------
-
-Backend Re-serialize to backend
-
-Bits 1000 => CheerState { bits: 1000 }
-
-ExactMatch Corgo100 => CheerState { official: Corgo, bits: 100 } => ExactMatch Corgo100
-ExactMatch Zombo100 => CheerState { full: Zombo100, bits: 100 } => ExactMatch Zombo100 (from "full")
-
-PrefixBits (Corgo, 100) => CheerState { official: Corgo, bits: 100 } => PrefixBits (Corgo, 100)
-PrefixBits (Corgo, 123) => CheerState { official: Corgo, bits: 123 } => PrefixBits (Corgo, 123)
-PrefixBits (Zombo, 123) => CheerState { full: Zombo, bits: 123 } => PrefixBits (Zombo, 123)
-
-------------------------------------------------------------------------------------------------
-
-Backend To another [view type] w/ UI state
-
-Bits 1000 => CheerState { bits: 1000 } => [ExactMatch] PrefixSelect: (n/a) BitSelect: 1000 FreeForm: (n/a)
-Bits 1234 => CheerState { bits: 1234 } => [ExactMatch] PrefixSelect: (n/a) BitSelect: (n/a) FreeForm: (n/a) [CANNOT POPULATE]
-
-Bits 1000 => CheerState { bits: 1000 } => [PrefixBits] PefixSelect: (n/a) FreeForm: (n/a) Bits: 1000
-Bits 1234 => CheerState { bits: 1234 } => [PrefixBits] PefixSelect: (n/a) FreeForm: (n/a) Bits: 1234
-
-ExactMatch Corgo100 => CheerState { official: Corgo, bits: 100 } => [ExactMatch] PrefixSelect: Corgo BitSelect: 100 FreeForm: Corgo100
-ExactMatch Zombo100 => CheerState { full: Zombo100, bits: 100 } => [ExactMatch] PrefixSelect: (n/a) BitSelect: 100 FreeForm: Zombo100
-ExactMatch Corgo100 => CheerState { official: Corgo, bits: 100 } => [Bits] BitsSelect: 100 BitsFreeForm: 100
-*) ExactMatch Corgo100 => CheerState { official: Corgo, bits: 100 } => [PrefixBits] PrefixSelect: Corgo FreeForm: Corgo Bits: 100
-*) ExactMatch Zombo100 => CheerState { full: Zombo100, bits: 100 } => [PrefixBits] PrefixSelect: (n/a) FreeForm: Zombo100 Bits: 100
-
-PrefixBits (Corgo, 100) => CheerState { official: Corgo, bits: 100 } => PrefixBits (Corgo, 100)
-PrefixBits (Corgo, 123) => CheerState { official: Corgo, bits: 123 } => PrefixBits (Corgo, 123)
-PrefixBits (Zombo, 123) => CheerState { full: Zombo, bits: 123 } => PrefixBits (Zombo, 123)
-
-# Notably, prefix is usually always "full":
-PrefixBits (1884, 1000) => CheerState { full: 1984, bits: 1000 } => [PrefixBits] PrefixSelect: (n/a) FreeForm: 1984 Bits: 1000
-PrefixBits (Sus12, 1000) => CheerState { full: Sus12, bits: 1000 } => [PrefixBits] PrefixSelect: (n/a) FreeForm: Sus12 Bits: 1000
-PrefixBits (Corgo1000, 5000) => CheerState { full: Corgo1000, bits: 5000} => [PrefixBits] PrefixSelect: (n/a) FreeForm: Corgo1000 Bits: 5000
-
-PrefixBits (1884, 1000) => CheerState { full: 1984, bits: 1000 } => [ExactMatch] PrefixSelect: (n/a) FreeForm: 1984 Bits: 1000
-PrefixBits (Sus12, 1000) => CheerState { full: Sus12, bits: 1000 } => [ExactMatch] PrefixSelect: (n/a) FreeForm: Sus12 Bits: 1000
-PrefixBits (Corgo1000, 5000) => CheerState { full: Corgo1000, bits: 5000} => [ExactMatch] PrefixSelect: (Corgo) FreeForm: Corgo1000 Bits: 1000 (*ignore bits*)
-
-
-# Unless it is *only* an official prefix
-PrefixBits (Corgo, 5000) => CheerState { official: Corgo, bits: 5000} => [PrefixBits] PrefixSelect: Corgo FreeForm: Corgo Bits: 5000
-PrefixBits (Corgo, 5000) => CheerState { official: Corgo, bits: 5000} => [ExactMatch] PrefixSelect: Corgo FreeForm: Corgo5000
-
-
-------------------------------------------------------------------------------------------------
-
-CheerState UI CheerState
-
-CheerState { official: Corgo, bits: 5000} => [Bits] BitsSelect: 5000, BitsFreeForm: 5000 => CheerState { official: (retain), bits: 5000 }
-CheerState { official: Corgo, bits: 5000} => [PrefixBits] PrefixSelect: Corgo FreeForm: Corgo Bits: 5000 => CheerState { official: Corgo, bits: 5000 }
-CheerState { official: Corgo, bits: 5000} => [ExactMatch] PrefixSelect: Corgo FreeForm: Corgo5000 => CheerState { official: Corgo, bits: 5000 } [split freeform]
-
-CheerState { full: Zombo, bits: 5000} => [Bits] BitsSelect: 5000, BitsFreeForm: 5000
-CheerState { full: Zombo, bits: 5000} => [PrefixBits] PrefixSelect: (n/a) FreeForm: Zombo Bits: 5000
-CheerState { full: Zombo, bits: 5000} => [ExactMatch] PrefixSelect: (n/a) FreeForm: Zombo5000
-
-CheerState { full: Zombo, bits: 5000} => [Bits] BitsSelect: 5000, BitsFreeForm: 5000
-CheerState { full: Zombo, bits: 5000} => [Bits] BitsSelect: 5000, BitsFreeForm: 5000
-
-CheerState { full: 1984, bits: 5000} => [PrefixBits] PrefixSelect: Corgo FreeForm: Corgo Bits: 5000
-CheerState { full: Sus12, bits: 5000} => [ExactMatch] PrefixSelect: Corgo FreeForm: Corgo5000
-CheerState { full: Corgo1000, bits: 5000} => [ExactMatch] PrefixSelect: Corgo FreeForm: Corgo5000
-
-
-*/
\ No newline at end of file
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/EventMatchPredicateBuilderComponent.tsx b/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/EventMatchPredicateBuilderComponent.tsx
deleted file mode 100644
index e52c8588a1..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/EventMatchPredicateBuilderComponent.tsx
+++ /dev/null
@@ -1,352 +0,0 @@
-import React, { useEffect, useState } from "react";
-import { BitsRuleType } from "./types/BitsRuleType";
-import { TwitchEventCategory } from "@storyteller/components/src/api/storyteller/twitch_event_rules/shared/TwitchEventCategory";
-import { EventMatchPredicate } from "@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventMatchPredicate";
-import { BitsCheermoteNameExactMatchForm } from "./subforms/BitsCheermoteNameExactMatchForm";
-import { BitsCheermotePrefixSpendThresholdForm } from "./subforms/BitsCheermotePrefixSpendThresholdForm";
-import { BitsSpendThresholdForm } from "./subforms/BitsSpendThresholdForm";
-import { ChannelPointsRewardNameExactMatchForm } from "./subforms/ChannelPointsRewardNameExactMatchForm";
-import { ChannelPointsRuleType } from "./types/ChannelPointsRuleType";
-import { CheerUtil } from "../../../twitch/CheerUtil";
-import {
- CheerState,
- CheerStateIsCustom,
- CheerStateIsOfficial,
- cheerStateToPredicate,
- predicateToCheerState,
-} from "./CheerState";
-import { CHEER_LOOKUP_MAP } from "../../../twitch/Cheers";
-
-// NB: This was created for "Edit" view, but we've forced it to work for "Create" view by hardcoding
-// values that don't actually come from the server.
-
-interface EventMatchPredicateBuilderComponentProps {
- // CANNOT BE CHANGED AFTER CREATION
- twitchEventCategory: TwitchEventCategory;
-
- // Original event match predicate from the server
- // (also immutable for sanity and because we lose information on UI changes)
- serverEventMatchPredicate: EventMatchPredicate;
-
- // Updates sent back up the tree
- updateModifiedEventMatchPredicate: (predicate: EventMatchPredicate) => void;
-}
-
-function EventMatchPredicateBuilderComponent(
- props: EventMatchPredicateBuilderComponentProps
-) {
- // ========== Core UI flow ==========
-
- const [bitsRuleType, setBitsRuleType] = useState(
- BitsRuleType.BitsCheermoteNameExactMatch
- );
- const [channelPointsRuleType, setChannelPointsRuleType] =
- useState(
- ChannelPointsRuleType.ChannelPointsRewardNameExactMatch
- );
-
- // ========== Cached Values for Editing ==========
-
- // A very complicated container that juggles bits and cheermote state.
- // The state transitions are complicated, and I've tried to document as best as possible.
- // TODO: This really needs some testing.
- const [cheerState, setCheerState] = useState({});
-
- // Used in:
- // ChannelPointsRewardNameExactMatch
- const [rewardName, setRewardName] = 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 newBitsRuleType = BitsRuleType.BitsCheermoteNameExactMatch;
- let newChannelPointsRuleType =
- ChannelPointsRuleType.ChannelPointsRewardNameExactMatch;
-
- let newCheerState = predicateToCheerState(props.serverEventMatchPredicate);
-
- switch (props.twitchEventCategory) {
- case TwitchEventCategory.Bits:
- if (!!props.serverEventMatchPredicate.bits_cheermote_name_exact_match) {
- newBitsRuleType = BitsRuleType.BitsCheermoteNameExactMatch;
- } else if (
- !!props.serverEventMatchPredicate
- .bits_cheermote_prefix_spend_threshold
- ) {
- newBitsRuleType = BitsRuleType.BitsCheermotePrefixSpendThreshold;
- } else if (!!props.serverEventMatchPredicate.bits_spend_threshold) {
- newBitsRuleType = BitsRuleType.BitsSpendThreshold;
- }
- break;
- case TwitchEventCategory.ChannelPoints: // NB: Only one rule type
- if (
- !!props.serverEventMatchPredicate
- .channel_points_reward_name_exact_match
- ) {
- setRewardName(
- props.serverEventMatchPredicate
- .channel_points_reward_name_exact_match.reward_name
- );
- newChannelPointsRuleType =
- ChannelPointsRuleType.ChannelPointsRewardNameExactMatch;
- }
- break;
- case TwitchEventCategory.ChatCommand: // TODO: Not yet supported
- default:
- break;
- }
-
- setCheerState(newCheerState);
-
- setBitsRuleType(newBitsRuleType);
- setChannelPointsRuleType(newChannelPointsRuleType);
- }, [props.twitchEventCategory, props.serverEventMatchPredicate]);
-
- // TODO: We need to recalculate the model.
- const handleChangedBitsRuleType = (
- ev: React.FormEvent
- ): boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- const newRuleType = value as BitsRuleType;
-
- let predicate = cheerStateToPredicate(cheerState, newRuleType);
-
- setBitsRuleType(newRuleType);
- props.updateModifiedEventMatchPredicate(predicate);
-
- return true;
- };
-
- const handleChangedChannelPointsRuleType = (
- ev: React.FormEvent
- ): boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- const ruleType = value as ChannelPointsRuleType;
- setChannelPointsRuleType(ruleType);
- return true;
- };
-
- const handleChangedCheerPrefix = (newPrefix: string) => {
- // 1) Update Cheer State
- // 2) Update NewEventMatchPredicate
- let newCheerState: CheerState = {};
-
- let maybeCheerPrefix = CHEER_LOOKUP_MAP.get(newPrefix || "");
-
- if (!!maybeCheerPrefix) {
- newCheerState = {
- cheerPrefix: maybeCheerPrefix,
- bits: cheerState.bits, // Unchanged
- };
- } else {
- newCheerState = {
- cheerFull: newPrefix,
- bits: cheerState.bits, // Unchanged
- };
- }
-
- let predicate = cheerStateToPredicate(newCheerState, bitsRuleType);
-
- setCheerState(newCheerState);
- props.updateModifiedEventMatchPredicate(predicate);
- };
-
- const handleChangedCheerName = (name: string) => {
- // 1) Update Cheer State
- // 2) Update NewEventMatchPredicate
- let newCheerState: CheerState = {};
-
- let { cheerPrefix, bitValue } = CheerUtil.parseCheerString(name);
-
- let newBitValue = bitValue;
- let maybeCheerPrefix = CHEER_LOOKUP_MAP.get(cheerPrefix || "");
-
- if (!!maybeCheerPrefix) {
- // Official prefix.
- newCheerState = {
- cheerPrefix: maybeCheerPrefix,
- bits: newBitValue,
- };
- } else if (!!cheerPrefix) {
- // Custom prefix.
-
- // Test if hypothetically we should retain an independent of name bit value.
- // Hypothetically, Sus13 -> Sus12, but the bit value is 5000 in a separate field.
- // We don't want to repalace with 13.
- if (CheerStateIsCustom(cheerState)) {
- let { cheerPrefix, bitValue } = CheerUtil.parseCheerString(
- cheerState.cheerFull || ""
- );
-
- if (
- !!cheerPrefix &&
- !!bitValue && // We have a complete name
- bitValue !== cheerState.bits
- ) {
- // The bit value is not independent of the cheer name, as in eg. (Foobar1000, 1000)
- // Keep the old value.
- newBitValue = cheerState.bits;
- }
- }
-
- newCheerState = {
- cheerFull: name, // Should always be the full value
- bits: newBitValue,
- };
- }
-
- let predicate = cheerStateToPredicate(newCheerState, bitsRuleType);
-
- setCheerState(newCheerState);
- props.updateModifiedEventMatchPredicate(predicate);
- };
-
- const handleChangedMinimumBitsSpent = (minimumSpent: number) => {
- // 1) Update Cheer State
- // 2) Update NewEventMatchPredicate
- let newCheerState = {};
-
- if (CheerStateIsOfficial(cheerState)) {
- newCheerState = {
- cheerPrefix: cheerState.cheerPrefix, // Unchanged
- bits: minimumSpent,
- };
- } else if (CheerStateIsCustom(cheerState)) {
- // Test if the cheer name includes numbers, eg. 'Sus12', or '1984', that differ from bit spend
- let { cheerPrefix, bitValue } = CheerUtil.parseCheerString(
- cheerState.cheerFull || ""
- );
- let cheerFull = cheerState.cheerFull;
-
- if (!!cheerPrefix && !!bitValue && bitValue === cheerState.bits) {
- // Let's replace the cheer.
- cheerFull = CheerUtil.joinCheerAndPrefix(cheerPrefix, minimumSpent);
- }
-
- newCheerState = {
- cheerFull: cheerFull,
- bits: minimumSpent,
- };
- } else {
- newCheerState = {
- bits: minimumSpent,
- };
- }
-
- let predicate = cheerStateToPredicate(newCheerState, bitsRuleType);
-
- setCheerState(newCheerState);
- props.updateModifiedEventMatchPredicate(predicate);
- };
-
- // TODO
- const handleChangedRewardName = (name: string) => {
- let predicate: EventMatchPredicate = {};
-
- switch (channelPointsRuleType) {
- case ChannelPointsRuleType.ChannelPointsRewardNameExactMatch:
- predicate.channel_points_reward_name_exact_match = {
- reward_name: name, // New value
- };
- break;
- }
-
- props.updateModifiedEventMatchPredicate(predicate);
- };
-
- let ruleTypeSelect = <>>;
- let matchingRulesForm = <>>;
-
- if (props.twitchEventCategory === TwitchEventCategory.Bits) {
- ruleTypeSelect = (
- <>
-
-
- Bits Spend Threshold
-
-
- Cheermote Name (Exact Match)
-
-
- Cheermote Prefix and Bits Spend Threshold
-
-
- >
- );
-
- switch (bitsRuleType) {
- case BitsRuleType.BitsCheermoteNameExactMatch:
- matchingRulesForm = (
-
- );
- break;
- case BitsRuleType.BitsCheermotePrefixSpendThreshold:
- matchingRulesForm = (
-
- );
- break;
- case BitsRuleType.BitsSpendThreshold:
- matchingRulesForm = (
-
- );
- break;
- }
- } else if (props.twitchEventCategory === TwitchEventCategory.ChannelPoints) {
- ruleTypeSelect = (
- <>
-
-
- Channel Points Reward Name (exact match)
-
-
- >
- );
-
- matchingRulesForm = (
-
- );
- }
-
- return (
- <>
-
-
-
1) Pick what to match on
-
{ruleTypeSelect}
-
-
-
-
2) Configure the matching
-
{matchingRulesForm}
-
-
- >
- );
-}
-
-export { EventMatchPredicateBuilderComponent };
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermoteNameExactMatchForm.tsx b/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermoteNameExactMatchForm.tsx
deleted file mode 100644
index 50e643007d..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermoteNameExactMatchForm.tsx
+++ /dev/null
@@ -1,182 +0,0 @@
-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,
- 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 {
- cheerState: CheerState;
- updateCheerName: (cheerNameOrPrefix: string) => void;
-}
-
-function BitsCheermoteNameExactMatchForm(
- props: BitsCheermoteNameExactMatchProps
-) {
- // The two dropdowns
- const [cheerPrefix, setCheerPrefix] = useState("");
- const [bitsValue, setBitsValue] = useState(1);
- // The freeform text input
- 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 newPrefix = "";
- let newFullName = "";
- let newBits = 1;
- let hasBits = false;
-
- if (
- !!props.cheerState.bits &&
- !isNaN(props.cheerState.bits) &&
- props.cheerState.bits > 0
- ) {
- newBits = props.cheerState.bits;
- hasBits = true;
- }
-
- if (CheerStateIsOfficial(props.cheerState)) {
- if (!!props.cheerState.cheerPrefix) {
- newPrefix =
- CHEER_PREFIX_TO_STRING_MAP.get(props.cheerState.cheerPrefix) || "";
- }
-
- if (hasBits) {
- newFullName = `${newPrefix}${newBits}`;
- } else {
- newFullName = newPrefix;
- }
- } else if (CheerStateIsCustom(props.cheerState)) {
- newFullName = props.cheerState.cheerFull || "";
- }
-
- setCheerPrefix(newPrefix);
- setBitsValue(newBits);
- setManualCheerValue(newFullName);
- }, [props.cheerState]);
-
- const updateCheerPrefix = (
- ev: React.FormEvent
- ): boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- setCheerPrefix(value);
- recalcuateFieldValue(value, bitsValue);
- return true;
- };
-
- const updateBitsValue = (ev: React.FormEvent): boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- const numericValue = parseInt(value);
- if (!isNaN(numericValue)) {
- // NB: The form shouldn't yield NaN, but just in case.
- setBitsValue(numericValue);
- recalcuateFieldValue(cheerPrefix, numericValue);
- }
- return true;
- };
-
- const updateTextCheerValue = (
- ev: React.FormEvent
- ): boolean => {
- const value = (ev.target as HTMLInputElement).value;
-
- // Cheer name is the full value, eg. 'Corgo100'
- const matches = value.trim().match(CHEER_REGEX);
-
- if (!!matches && matches.length > 1) {
- setCheerPrefix(matches[1]); // First match group
- if (matches.length === 3 && matches[2] !== undefined) {
- // NB: Second match group can be 'undefined' if no number is present. (Zero-width matching?)
- const maybeBits = parseInt(matches[2]);
- if (!isNaN(maybeBits)) {
- setBitsValue(maybeBits); // Second match group
- }
- }
- }
-
- setManualCheerValue(value);
- props.updateCheerName(value);
- return true;
- };
-
- // When the dropdowns are used, replace any manual text entry.
- const recalcuateFieldValue = (prefix: string, bits: number) => {
- const cheerValue = `${prefix}${bits}`;
- setManualCheerValue(cheerValue);
- props.updateCheerName(cheerValue);
- };
-
- return (
- <>
-
-
- Pick the cheer
-
-
- Select cheer...
-
- {CHEER_PREFIXES.map((cheerPrefix) => {
- return (
-
- {cheerPrefix}
-
- );
- })}
-
-
-
- Then the bit value
-
- {CHEER_BIT_LEVELS.map((level) => {
- return (
-
- {level}
-
- );
- })}
-
-
-
-
- To match against this (or set something custom)
-
-
-
-
-
-
-
-
-
- >
- );
-}
-
-export { BitsCheermoteNameExactMatchForm };
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermotePrefixSpendThresholdForm.tsx b/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermotePrefixSpendThresholdForm.tsx
deleted file mode 100644
index 5ad8bbc8b6..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermotePrefixSpendThresholdForm.tsx
+++ /dev/null
@@ -1,203 +0,0 @@
-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,
- CHEER_PREFIX_TO_STRING_MAP,
-} from "../../../../twitch/Cheers";
-import {
- CheerState,
- CheerStateIsCustom,
- CheerStateIsOfficial,
-} from "../CheerState";
-
-interface BitsCheermotePrefixSpendThresholdProps {
- cheerState: CheerState;
-
- updateCheerPrefix: (cheerNameOrPrefix: string) => void;
- updateMinimumBitsSpent: (minimumSpent: number) => void;
-}
-
-function BitsCheermotePrefixSpendThresholdForm(
- props: BitsCheermotePrefixSpendThresholdProps
-) {
- const [cheerPrefix, setCheerPrefix] = useState("");
- const [customCheerPrefix, setCustomCheerPrefix] = useState("");
-
- 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)
- // 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 newPrefix = "";
- let newCustomPrefix = "";
- let newBits = 1;
- let newCustomBits = 1;
-
- if (
- !!props.cheerState.bits &&
- !isNaN(props.cheerState.bits) &&
- props.cheerState.bits > 0
- ) {
- newBits = props.cheerState.bits;
- newCustomBits = props.cheerState.bits;
- }
-
- 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) || "";
- }
- } else if (CheerStateIsCustom(props.cheerState)) {
- newCustomPrefix = props.cheerState.cheerFull || "";
- }
-
- setCheerPrefix(newPrefix);
- setCustomCheerPrefix(newCustomPrefix);
- setBitsValue(newBits);
- setCustomBitsValue(newCustomBits);
- }, [props.cheerState]);
-
- const updateCheerPrefix = (
- ev: React.FormEvent
- ): boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- setCheerPrefix(value);
- setCustomCheerPrefix(value);
- props.updateCheerPrefix(value);
- return true;
- };
-
- const updateTextCheerValue = (
- ev: React.FormEvent
- ): boolean => {
- const value = (ev.target as HTMLInputElement).value;
- setCheerPrefix(value);
- setCustomCheerPrefix(value);
- props.updateCheerPrefix(value);
- return true;
- };
-
- const handleBitSelect = (ev: React.FormEvent): boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- const numericValue = parseInt(value);
- setBitsValue(numericValue);
- setCustomBitsValue(numericValue);
- props.updateMinimumBitsSpent(numericValue);
- return true;
- };
- const handleCustomBitsValueUpdate = (
- ev: React.FormEvent
- ): boolean => {
- const value = (ev.target as HTMLInputElement).value;
- const numericValue = parseInt(value);
- setCustomBitsValue(numericValue);
- props.updateMinimumBitsSpent(numericValue);
- return true;
- };
-
- return (
- <>
-
- This rule type requires both the cheermote to match and
- the bits spent to be equal or greater than the amount specified.
-
-
-
-
Cheermote matching
-
- Pick the cheer prefix
-
-
- Select cheer...
-
- {CHEER_PREFIXES.map((cheerPrefix) => {
- return (
-
- {cheerPrefix}
-
- );
- })}
-
-
-
-
- To match against this (or set something custom)
-
-
-
-
-
-
-
-
-
- The "Cheer prefix" can be an actual cheer prefix, such as "SeemsGood",
- but it can also be an arbitrary string value. So if you make it
- "ManBearPig", that will be searched for in cheers. Cheermotes will be
- removed from the words used in TTS, but currently custom strings will
- not (though this may change in the future).
-
-
-
Bits spent
-
-
- Preset thresholds
-
- {CHEER_BIT_LEVELS.map((level) => {
- return (
-
- Spend at least {level} bits
-
- );
- })}
-
-
-
-
Or a custom minimum amount.
-
-
-
-
-
-
-
-
- Remember that all Cheermotes in a single message add up, eg.{" "}
- “Cheer100 + Cheer100 + Party100” is 300 bits. A
- custom amount should give you flexibility to charge, say, 300 or 500
- bits. Or 420. Whatever you want.
-
-
- >
- );
-}
-
-export { BitsCheermotePrefixSpendThresholdForm };
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsSpendThresholdForm.tsx b/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsSpendThresholdForm.tsx
deleted file mode 100644
index 4681b6a7c8..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsSpendThresholdForm.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-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 {
- cheerState: CheerState;
- updateMinimumBitsSpent: (minimumSpent: number) => void;
-}
-
-function BitsSpendThresholdForm(props: BitsSpendThresholdFormProps) {
- // The dropdown
- const [bitsValue, setBitsValue] = useState(1);
- // The freeform text input
- 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 (
- !!props.cheerState.bits &&
- !isNaN(props.cheerState.bits) &&
- props.cheerState.bits > 0
- ) {
- setBitsValue(props.cheerState.bits);
- setCustomAmount(props.cheerState.bits);
- }
- }, [props.cheerState]);
-
- const handleBitSelect = (ev: React.FormEvent): boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- const numericValue = parseInt(value);
-
- if (!isNaN(numericValue) && numericValue > 0) {
- // Only update on valid positive integers
- setBitsValue(numericValue);
- setCustomAmount(numericValue);
- props.updateMinimumBitsSpent(numericValue);
- }
-
- return true;
- };
-
- const handleCustomAmountUpdate = (
- ev: React.FormEvent
- ): boolean => {
- const value = (ev.target as HTMLInputElement).value;
- const numericValue = parseInt(value);
-
- if (!isNaN(numericValue) && numericValue > 0) {
- // Only update on valid positive integers
- setBitsValue(numericValue);
- setCustomAmount(numericValue);
- props.updateMinimumBitsSpent(numericValue);
- }
-
- return true;
- };
-
- return (
- <>
-
-
- Preset thresholds
-
-
- Choose or type...
-
- {CHEER_BIT_LEVELS.map((level) => {
- return (
-
- Spend at least {level} bits
-
- );
- })}
-
-
-
-
Or a custom minimum amount.
-
-
-
-
-
-
-
- Remember that all Cheermotes in a single message add up, eg.{" "}
- “Cheer100 + Cheer100 + Party100” is 300 bits. A
- custom amount should give you flexibility to charge, say, 300 or 500
- bits. Or 420. Whatever you want.
-
-
-
- >
- );
-}
-
-export { BitsSpendThresholdForm };
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/subforms/ChannelPointsRewardNameExactMatchForm.tsx b/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/subforms/ChannelPointsRewardNameExactMatchForm.tsx
deleted file mode 100644
index d603b95cbd..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/subforms/ChannelPointsRewardNameExactMatchForm.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import React, { useEffect, useState } from "react";
-
-interface ChannelPointsRewardNameExactMatchProps {
- rewardName: string;
- updateRewardName: (name: string) => void;
-}
-
-function ChannelPointsRewardNameExactMatchForm(
- props: ChannelPointsRewardNameExactMatchProps
-) {
- const [rewardName, setRewardName] = 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(() => {
- setRewardName(props.rewardName);
- }, [props.rewardName]);
-
- const updateRewardName = (ev: React.FormEvent): boolean => {
- const value = (ev.target as HTMLInputElement).value;
- setRewardName(value);
- props.updateRewardName(value);
- return true;
- };
-
- return (
- <>
-
-
Match the following reward name
-
-
-
-
- >
- );
-}
-
-export { ChannelPointsRewardNameExactMatchForm };
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/types/BitsRuleType.ts b/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/types/BitsRuleType.ts
deleted file mode 100644
index b89d958ade..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/types/BitsRuleType.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-
-// For bits / cheermotes
-export enum BitsRuleType {
- BitsCheermoteNameExactMatch = "bits_cheermote_name_exact_match",
- BitsCheermotePrefixSpendThreshold = "bits_cheermote_prefix_spend_threshold",
- BitsSpendThreshold = "bits_spend_threshold",
-}
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/types/ChannelPointsRuleType.ts b/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/types/ChannelPointsRuleType.ts
deleted file mode 100644
index d4379b3d7b..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/event_match_predicate_builder/types/ChannelPointsRuleType.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-
-// For channel points
-export enum ChannelPointsRuleType {
- ChannelPointsRewardNameExactMatch = 'channel_points_reward_name_exact_match',
-}
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/event_response_builder/BuildEventResponse.ts b/packages/power.stream-dashboard/src/pages/tts_configs/event_response_builder/BuildEventResponse.ts
deleted file mode 100644
index 66b2d25aaa..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/event_response_builder/BuildEventResponse.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { EventResponse } from "@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventResponse";
-import { EventResponseType } from "./EventResponseType";
-
-export function buildEventResponse(ttsModelTokens: string[], eventResponseType: EventResponseType) :EventResponse {
- let eventResponse : EventResponse = {};
-
- switch (eventResponseType) {
- case EventResponseType.TtsSingleVoice:
- let ttsModelToken = '';
- if (ttsModelTokens.length > 0) {
- ttsModelToken = ttsModelTokens[0];
- }
- eventResponse.tts_single_voice = {
- tts_model_token: ttsModelToken,
- }
- break;
- case EventResponseType.TtsRandomVoice:
- eventResponse.tts_random_voice= {
- tts_model_tokens: [...ttsModelTokens],
- }
- break;
- }
-
- return eventResponse;
-}
\ No newline at end of file
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/event_response_builder/EventResponseComponent.tsx b/packages/power.stream-dashboard/src/pages/tts_configs/event_response_builder/EventResponseComponent.tsx
deleted file mode 100644
index 461680c164..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/event_response_builder/EventResponseComponent.tsx
+++ /dev/null
@@ -1,177 +0,0 @@
-import React, { useEffect, useState } from "react";
-import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
-import { TtsSingleVoiceForm } from "./TtsSingleVoiceForm";
-import { EventResponseType } from "./EventResponseType";
-import { EventResponse } from "@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventResponse";
-import { buildEventResponse } from "./BuildEventResponse";
-import { TtsRandomVoiceForm } from "./TtsRandomVoiceForm";
-
-// NB: This was created for "Edit" view, but we've forced it to work for "Create" view by hardcoding
-// values that don't actually come from the server.
-
-interface EventResponseComponentProps {
- // Original response from server.
- // (also immutable for sanity and because we may lose info on UI changes)
- serverEventResponse: EventResponse;
-
- // Updates sent back up the tree
- updateModifiedEventResponse: (response: EventResponse) => void;
-
- // FakeYou voices
- allTtsModels: TtsModelListItem[];
- allTtsModelsByToken: Map;
-}
-
-function EventResponseComponent(props: EventResponseComponentProps) {
- // ========== Core UI flow ==========
-
- const [eventResponseType, setEventResponseType] = useState(
- EventResponseType.TtsSingleVoice
- );
-
- // ========== Cached Values for Editing ==========
-
- const [selectedTtsModelTokens, setSelectedTtsModelTokens] = useState<
- string[]
- >([]);
-
- // 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 newEventResponseType = EventResponseType.TtsSingleVoice;
- let modelTokens: string[] = [];
-
- if (!!props.serverEventResponse.tts_single_voice) {
- newEventResponseType = EventResponseType.TtsSingleVoice;
-
- if (!!props.serverEventResponse.tts_single_voice.tts_model_token) {
- modelTokens.push(
- props.serverEventResponse.tts_single_voice.tts_model_token
- );
- }
- } else if (!!props.serverEventResponse.tts_random_voice) {
- newEventResponseType = EventResponseType.TtsRandomVoice;
-
- if (
- !!props.serverEventResponse.tts_random_voice.tts_model_tokens &&
- props.serverEventResponse.tts_random_voice.tts_model_tokens.length > 0
- ) {
- modelTokens = [
- ...props.serverEventResponse.tts_random_voice.tts_model_tokens,
- ];
- }
- }
-
- setEventResponseType(newEventResponseType);
- setSelectedTtsModelTokens(modelTokens);
- }, [props.serverEventResponse]);
-
- const handleChangedEventResponseType = (
- ev: React.FormEvent
- ): boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- const newResponseType = value as EventResponseType;
-
- const newEventResponse = buildEventResponse(
- selectedTtsModelTokens,
- newResponseType
- );
-
- setEventResponseType(newResponseType);
- props.updateModifiedEventResponse(newEventResponse);
-
- return true;
- };
-
- const handleChangedTtsModelToken = (token: string) => {
- // We may be only updating to a single token, but we want to be able to navigate back to
- // the "random voices" view, etc.
- let updatedTokens = [...selectedTtsModelTokens];
-
- if (updatedTokens.length > 0) {
- updatedTokens[0] = token;
- } else if (updatedTokens.length === 0) {
- updatedTokens.push(token);
- }
-
- const newEventResponse = buildEventResponse(
- updatedTokens,
- eventResponseType
- );
-
- setSelectedTtsModelTokens(updatedTokens);
- props.updateModifiedEventResponse(newEventResponse);
- };
-
- const handleChangedTtsModelTokens = (tokens: string[]) => {
- let updatedTokens = [...tokens];
-
- const newEventResponse = buildEventResponse(
- updatedTokens,
- eventResponseType
- );
-
- setSelectedTtsModelTokens(updatedTokens);
- props.updateModifiedEventResponse(newEventResponse);
- };
-
- let responseForm = <>>;
-
- switch (eventResponseType) {
- case EventResponseType.TtsSingleVoice:
- responseForm = (
-
- );
- break;
- case EventResponseType.TtsRandomVoice:
- responseForm = (
-
- );
- break;
- }
-
- return (
- <>
-
-
3) Pick how to respond
-
-
-
Response Type
-
-
-
- Respond with a TTS voice
-
-
- Respond with a Random TTS voice from list
-
-
-
-
-
-
-
-
4) Configure the response
- {responseForm}
-
- >
- );
-}
-
-export { EventResponseComponent };
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/event_response_builder/EventResponseType.ts b/packages/power.stream-dashboard/src/pages/tts_configs/event_response_builder/EventResponseType.ts
deleted file mode 100644
index 4131886efa..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/event_response_builder/EventResponseType.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-
-export enum EventResponseType {
- TtsSingleVoice = "tts_single_voice",
- TtsRandomVoice = "tts_random_voice",
-}
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/event_response_builder/TtsRandomVoiceForm.tsx b/packages/power.stream-dashboard/src/pages/tts_configs/event_response_builder/TtsRandomVoiceForm.tsx
deleted file mode 100644
index 25224663db..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/event_response_builder/TtsRandomVoiceForm.tsx
+++ /dev/null
@@ -1,209 +0,0 @@
-import React, { useEffect } from "react";
-import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
-import { FakeYouExternalLink } from "@storyteller/components/src/elements/FakeYouExternalLink";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
-
-interface TtsRandomVoiceFormProps {
- selectedTtsModelTokens: string[];
- updateSelectedTtsModelTokens: (tokens: string[]) => void;
-
- // FakeYou voices
- allTtsModels: TtsModelListItem[];
- allTtsModelsByToken: Map;
-}
-
-function TtsRandomVoiceForm(props: TtsRandomVoiceFormProps) {
- // 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(() => {}, [props.selectedTtsModelTokens]);
-
- const handleAddVoice = (ev: React.FormEvent): boolean => {
- ev.preventDefault();
-
- let newModelTokens = [...props.selectedTtsModelTokens];
-
- newModelTokens.push("");
-
- props.updateSelectedTtsModelTokens(newModelTokens);
-
- return true;
- };
-
- const handleModelSelect = (ttsModelToken: string, voiceIndex: number) => {
- let newModelTokens = [...props.selectedTtsModelTokens];
-
- if (voiceIndex >= newModelTokens.length) {
- return;
- }
-
- newModelTokens[voiceIndex] = ttsModelToken;
- props.updateSelectedTtsModelTokens(newModelTokens);
- };
-
- const handleModelDelete = (voiceIndex: number) => {
- let newModelTokens = [...props.selectedTtsModelTokens];
-
- if (newModelTokens.length <= 1 || voiceIndex >= newModelTokens.length) {
- // We must have at least one item in the list
- return;
- }
-
- newModelTokens.splice(voiceIndex, 1);
-
- props.updateSelectedTtsModelTokens(newModelTokens);
- };
-
- const deleteDisabled = props.selectedTtsModelTokens.length < 2;
-
- let selectBoxes: JSX.Element[] = [];
-
- // No tokens - show one box (unselected)
- // One or more tokens - show boxes for each (with selections)
-
- props.selectedTtsModelTokens.forEach((token, index) => {
- selectBoxes.push(
-
- );
- });
-
- if (selectBoxes.length === 0) {
- selectBoxes.push(
-
- );
- }
-
- return (
- <>
- {selectBoxes}
-
-
-
- Add Additional Voice
-
-
-
-
- Check out the voices at FakeYou
- {" "}
- to search for voices, experiment with them, and see how they sound.
- Sorry that this list is a nightmare to navigate. It will improve over
- time.
-
- >
- );
-}
-
-interface VoiceDropdownProps {
- // Actual token or empty string
- selectedTtsModelToken: string;
-
- // Position in the list, 0-indexed
- voiceIndex: number;
-
- // Callbacks
- handleModelSelect: (token: string, index: number) => void;
- handleModelDelete: (index: number) => void;
-
- // Whether to disable the delete button
- deleteDisabled: boolean;
-
- // FakeYou voices
- allTtsModels: TtsModelListItem[];
- allTtsModelsByToken: Map;
-}
-
-function VoiceDropdown(props: VoiceDropdownProps) {
- const handleSelectChange = (
- ev: React.FormEvent
- ): boolean => {
- const maybeModelToken = (ev.target as HTMLSelectElement).value;
-
- if (!maybeModelToken) {
- // Clear selection
- props.handleModelSelect(maybeModelToken, props.voiceIndex);
- return true;
- }
-
- if (!!maybeModelToken && !props.allTtsModelsByToken.has(maybeModelToken)) {
- return false;
- }
-
- props.handleModelSelect(maybeModelToken, props.voiceIndex);
- return true;
- };
-
- const handleRemoveClick = (
- ev: React.FormEvent
- ): boolean => {
- props.handleModelDelete(props.voiceIndex);
- return true;
- };
-
- return (
-
-
- {props.voiceIndex + 1}. TTS Voice Model (used as a Random Voice)
-
-
-
-
-
-
- Select a voice...
-
- {props.allTtsModels.map((ttsModel) => {
- return (
-
- {ttsModel.title}
-
- );
- })}
-
-
-
-
-
-
- Remove
-
-
-
-
-
-
- );
-}
-
-export { TtsRandomVoiceForm };
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/event_response_builder/TtsSingleVoiceForm.tsx b/packages/power.stream-dashboard/src/pages/tts_configs/event_response_builder/TtsSingleVoiceForm.tsx
deleted file mode 100644
index 749ca9fe53..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/event_response_builder/TtsSingleVoiceForm.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import React, { useEffect, useState } from "react";
-import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
-import { FakeYouExternalLink } from "@storyteller/components/src/elements/FakeYouExternalLink";
-
-interface TtsSingleVoiceFormProps {
- selectedTtsModelTokens: string[];
- updateSelectedTtsModelToken: (token: string) => void;
-
- // FakeYou voices
- allTtsModels: TtsModelListItem[];
- allTtsModelsByToken: Map;
-}
-
-function TtsSingleVoiceForm(props: TtsSingleVoiceFormProps) {
- const [ttsModelToken, setTtsModelToken] = 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(() => {
- if (props.selectedTtsModelTokens.length > 0) {
- setTtsModelToken(props.selectedTtsModelTokens[0]);
- }
- }, [props.selectedTtsModelTokens]);
-
- const handleModelSelect = (
- ev: React.FormEvent
- ): boolean => {
- const value = (ev.target as HTMLSelectElement).value;
-
- if (!!value && !props.allTtsModelsByToken.has(value)) {
- return false;
- }
-
- setTtsModelToken(value);
- props.updateSelectedTtsModelToken(value);
- return true;
- };
-
- return (
- <>
-
-
- TTS Voice Model
-
- Select a voice...
- {props.allTtsModels.map((ttsModel) => {
- return (
-
- {ttsModel.title}
-
- );
- })}
-
-
-
- We apologize that this list is a nightmare to navigate right now. It
- will improve over time. In the mean time,{" "}
-
- check out the voices at FakeYou
- {" "}
- to find the voices you want, experiment with them, and see which ones
- work best for your stream. The names of the voices at FakeYou are the
- same as in this dropdown, so you can search at FakeYou, then add them
- here when you're done looking.
-
-
- >
- );
-}
-
-export { TtsSingleVoiceForm };
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/rule_cards/ReorderableTwitchEventRuleElement.tsx b/packages/power.stream-dashboard/src/pages/tts_configs/rule_cards/ReorderableTwitchEventRuleElement.tsx
deleted file mode 100644
index b8a942cb6a..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/rule_cards/ReorderableTwitchEventRuleElement.tsx
+++ /dev/null
@@ -1,180 +0,0 @@
-import React from "react";
-import { TwitchEventRule } from "@storyteller/components/src/api/storyteller/twitch_event_rules/ListTwitchEventRules";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import {
- faArrowDown,
- faArrowUp,
- faExternalLinkAlt,
-} from "@fortawesome/free-solid-svg-icons";
-import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
-
-interface Props {
- rule: TwitchEventRule;
-
- // Index of the rule in the parents' list
- ruleIndex: number;
-
- // Update callbacks
- handleMoveUp: (index: number) => void;
- handleMoveDown: (index: number) => void;
-
- // FakeYou voices
- allTtsModelsByToken: Map;
-}
-
-function ReorderableTwitchEventRuleElement(props: Props) {
- let subtitle = <>>;
- let description = <>>;
-
- if (
- props.rule.event_match_predicate.bits_cheermote_name_exact_match !==
- undefined
- ) {
- subtitle = (
- <>
- Cheermote name matches "
- {
- props.rule.event_match_predicate.bits_cheermote_name_exact_match
- .cheermote_name
- }
- "
- >
- );
- } else if (
- props.rule.event_match_predicate.bits_cheermote_prefix_spend_threshold !==
- undefined
- ) {
- subtitle = (
- <>
- Cheermote prefix is "
- {
- props.rule.event_match_predicate.bits_cheermote_prefix_spend_threshold
- .cheermote_prefix
- }
- " and spend at least{" "}
- {
- props.rule.event_match_predicate.bits_cheermote_prefix_spend_threshold
- .minimum_bits_spent
- }{" "}
- bits
- >
- );
- } else if (
- props.rule.event_match_predicate.bits_spend_threshold !== undefined
- ) {
- subtitle = (
- <>
- Spend at least{" "}
- {
- props.rule.event_match_predicate.bits_spend_threshold
- .minimum_bits_spent
- }{" "}
- bits
- >
- );
- } else if (
- props.rule.event_match_predicate.channel_points_reward_name_exact_match !==
- undefined
- ) {
- subtitle = (
- <>
- Reward name matches "
- {
- props.rule.event_match_predicate
- .channel_points_reward_name_exact_match.reward_name
- }
- "
- >
- );
- }
-
- if (props.rule.event_response.tts_single_voice !== undefined) {
- const token = props.rule.event_response.tts_single_voice.tts_model_token;
-
- let model = props.allTtsModelsByToken.get(token);
- let modelName = token;
-
- if (!!model) {
- modelName = model.title;
- }
-
- let link = `https://fakeyou.com/tts/${token}`;
-
- description = (
- <>
-
- TTS with voice:
-
- {modelName}{" "}
-
-
-
-
- >
- );
- } else if (props.rule.event_response.tts_random_voice !== undefined) {
- const tokens = props.rule.event_response.tts_random_voice.tts_model_tokens;
-
- let modelNameAndLinks = tokens.map((token, index) => {
- let model = props.allTtsModelsByToken.get(token);
- let link = `https://fakeyou.com/tts/${token}`;
-
- let modelName = token;
- if (!!model) {
- modelName = model.title;
- }
-
- return (
-
- {modelName}{" "}
-
-
-
-
- );
- });
-
- description = (
- <>
-
-
- TTS with a random voice from the following{" "}
- {modelNameAndLinks.length} voice(s):
-
-
-
- >
- );
- }
-
- return (
-
-
-
-
{subtitle}
-
{description}
-
-
- props.handleMoveUp(props.ruleIndex)}
- className="btn btn-secondary w-100"
- >
-
- Up
-
-
- props.handleMoveDown(props.ruleIndex)}
- className="btn btn-secondary w-100"
- >
-
- Down
-
-
-
-
-
- );
-}
-
-export { ReorderableTwitchEventRuleElement };
diff --git a/packages/power.stream-dashboard/src/pages/tts_configs/rule_cards/TwitchEventRuleElement.tsx b/packages/power.stream-dashboard/src/pages/tts_configs/rule_cards/TwitchEventRuleElement.tsx
deleted file mode 100644
index 281f553a55..0000000000
--- a/packages/power.stream-dashboard/src/pages/tts_configs/rule_cards/TwitchEventRuleElement.tsx
+++ /dev/null
@@ -1,180 +0,0 @@
-import React from "react";
-import { TwitchEventRule } from "@storyteller/components/src/api/storyteller/twitch_event_rules/ListTwitchEventRules";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import {
- faEdit,
- faExternalLinkAlt,
- faTrash,
-} from "@fortawesome/free-solid-svg-icons";
-import { Link } from "react-router-dom";
-import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
-
-interface Props {
- rule: TwitchEventRule;
- allTtsModelsByToken: Map;
- hideButtons?: boolean;
-}
-
-function TwitchEventRuleElement(props: Props) {
- const hideButtons = !!props.hideButtons;
-
- let subtitle = <>>;
- let description = <>>;
-
- if (
- props.rule.event_match_predicate.bits_cheermote_name_exact_match !==
- undefined
- ) {
- subtitle = (
- <>
- Cheermote name matches "
- {
- props.rule.event_match_predicate.bits_cheermote_name_exact_match
- .cheermote_name
- }
- "
- >
- );
- } else if (
- props.rule.event_match_predicate.bits_cheermote_prefix_spend_threshold !==
- undefined
- ) {
- subtitle = (
- <>
- Cheermote prefix is "
- {
- props.rule.event_match_predicate.bits_cheermote_prefix_spend_threshold
- .cheermote_prefix
- }
- " and spend at least{" "}
- {
- props.rule.event_match_predicate.bits_cheermote_prefix_spend_threshold
- .minimum_bits_spent
- }{" "}
- bits
- >
- );
- } else if (
- props.rule.event_match_predicate.bits_spend_threshold !== undefined
- ) {
- subtitle = (
- <>
- Spend at least{" "}
- {
- props.rule.event_match_predicate.bits_spend_threshold
- .minimum_bits_spent
- }{" "}
- bits
- >
- );
- } else if (
- props.rule.event_match_predicate.channel_points_reward_name_exact_match !==
- undefined
- ) {
- subtitle = (
- <>
- Reward name matches "
- {
- props.rule.event_match_predicate
- .channel_points_reward_name_exact_match.reward_name
- }
- "
- >
- );
- }
-
- if (props.rule.event_response.tts_single_voice !== undefined) {
- const token = props.rule.event_response.tts_single_voice.tts_model_token;
-
- let model = props.allTtsModelsByToken.get(token);
- let modelName = token;
-
- if (!!model) {
- modelName = model.title;
- }
-
- let link = `https://fakeyou.com/tts/${token}`;
-
- description = (
- <>
-
- TTS with voice:
-
- {modelName}{" "}
-
-
-
-
- >
- );
- } else if (props.rule.event_response.tts_random_voice !== undefined) {
- const tokens = props.rule.event_response.tts_random_voice.tts_model_tokens;
-
- let modelNameAndLinks = tokens.map((token, index) => {
- let model = props.allTtsModelsByToken.get(token);
- let link = `https://fakeyou.com/tts/${token}`;
-
- let modelName = token;
- if (!!model) {
- modelName = model.title;
- }
-
- return (
-
- {modelName}{" "}
-
-
-
-
- );
- });
-
- description = (
- <>
-
-
- TTS with a random voice from the following{" "}
- {modelNameAndLinks.length} voice(s):
-
-
-
- >
- );
- }
-
- const editUrl = `/tts_configs/edit/${props.rule.token}`;
- const deleteUrl = `/tts_configs/delete/${props.rule.token}`;
-
- let buttons = undefined;
- if (!hideButtons) {
- buttons = (
- <>
-
-
-
- Edit
-
-
-
- Delete
-
-
- >
- );
- }
-
- return (
-
-
-
-
{subtitle}
-
{description}
-
- {buttons}
-
-
-
- );
-}
-
-export { TwitchEventRuleElement };
diff --git a/packages/power.stream-dashboard/src/react-app-env.d.ts b/packages/power.stream-dashboard/src/react-app-env.d.ts
deleted file mode 100644
index 6431bc5fc6..0000000000
--- a/packages/power.stream-dashboard/src/react-app-env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-///
diff --git a/packages/power.stream-dashboard/src/reportWebVitals.ts b/packages/power.stream-dashboard/src/reportWebVitals.ts
deleted file mode 100644
index 49a2a16e0f..0000000000
--- a/packages/power.stream-dashboard/src/reportWebVitals.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { ReportHandler } from 'web-vitals';
-
-const reportWebVitals = (onPerfEntry?: ReportHandler) => {
- if (onPerfEntry && onPerfEntry instanceof Function) {
- import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
- getCLS(onPerfEntry);
- getFID(onPerfEntry);
- getFCP(onPerfEntry);
- getLCP(onPerfEntry);
- getTTFB(onPerfEntry);
- });
- }
-};
-
-export default reportWebVitals;
diff --git a/packages/power.stream-dashboard/src/setupTests.ts b/packages/power.stream-dashboard/src/setupTests.ts
deleted file mode 100644
index 8f2609b7b3..0000000000
--- a/packages/power.stream-dashboard/src/setupTests.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-// jest-dom adds custom jest matchers for asserting on DOM nodes.
-// allows you to do things like:
-// expect(element).toHaveTextContent(/react/i)
-// learn more: https://github.com/testing-library/jest-dom
-import '@testing-library/jest-dom';
diff --git a/packages/power.stream-dashboard/src/twitch/CheerUtil.ts b/packages/power.stream-dashboard/src/twitch/CheerUtil.ts
deleted file mode 100644
index c582542193..0000000000
--- a/packages/power.stream-dashboard/src/twitch/CheerUtil.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-
-const CHEER_REGEX = /^([A-Za-z]+)(\d+)?$/;
-
-interface ParsedCheer {
- cheerPrefix?: string,
- bitValue?: number,
-}
-
-export class CheerUtil {
-
- public static parseCheerString(cheerString: string) : ParsedCheer {
- const matches = cheerString.trim().match(CHEER_REGEX)
-
- let cheerPrefix = undefined;
- let bitValue = undefined;
-
- if (!!matches && matches.length > 1) {
- cheerPrefix = matches[1];
- if (matches.length === 3 && matches[2] !== undefined) {
- // NB(1): The second match group can be 'undefined' if no number is present. (Optional matching.)
- let maybeBits = parseInt(matches[2]);
- if (!isNaN(maybeBits) && maybeBits > 0) {
- bitValue = maybeBits;
- }
- }
- }
-
- return {
- cheerPrefix: cheerPrefix,
- bitValue: bitValue,
- };
- }
-
- public static joinCheerAndPrefix(cheerPrefix?: string, bitValue?: number) : string {
- return `${cheerPrefix || ''}${bitValue || ''}`;
- }
-}
\ No newline at end of file
diff --git a/packages/power.stream-dashboard/src/twitch/Cheers.ts b/packages/power.stream-dashboard/src/twitch/Cheers.ts
deleted file mode 100644
index ea0901fe74..0000000000
--- a/packages/power.stream-dashboard/src/twitch/Cheers.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-
-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[] = [
- "Cheer",
- "DoodleCheer",
- "BibleThump",
- "cheerwhal",
- "Corgo",
- "uni",
- "ShowLove",
- "Party",
- "SeemsGood",
- "Pride",
- "Kappa",
- "FrankerZ",
- "HeyGuys",
- "DansGame",
- "EleGiggle",
- "TriHard",
- "Kreygasm",
- "4Head",
- "SwiftRage",
- "NotLikeThis",
- "FailFish",
- "VoHiYo",
- "PJSalt",
- "MrDestructoid",
- "bday",
- "RIPCheer",
- "Shamrock",
- "BitBoss",
- "Streamlabs",
- "Muxy",
- "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];
diff --git a/packages/power.stream-dashboard/tsconfig.json b/packages/power.stream-dashboard/tsconfig.json
deleted file mode 100644
index a273b0cfc0..0000000000
--- a/packages/power.stream-dashboard/tsconfig.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "compilerOptions": {
- "target": "es5",
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
- ],
- "allowJs": true,
- "skipLibCheck": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "strict": true,
- "forceConsistentCasingInFileNames": true,
- "noFallthroughCasesInSwitch": true,
- "module": "esnext",
- "moduleResolution": "node",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "jsx": "react-jsx"
- },
- "include": [
- "src"
- ]
-}
diff --git a/packages/power.stream-obs/.gitignore b/packages/power.stream-obs/.gitignore
deleted file mode 100644
index 4d29575de8..0000000000
--- a/packages/power.stream-obs/.gitignore
+++ /dev/null
@@ -1,23 +0,0 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-/.pnp
-.pnp.js
-
-# testing
-/coverage
-
-# production
-/build
-
-# misc
-.DS_Store
-.env.local
-.env.development.local
-.env.test.local
-.env.production.local
-
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
diff --git a/packages/power.stream-obs/README.md b/packages/power.stream-obs/README.md
deleted file mode 100644
index b87cb00449..0000000000
--- a/packages/power.stream-obs/README.md
+++ /dev/null
@@ -1,46 +0,0 @@
-# Getting Started with Create React App
-
-This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
-
-## Available Scripts
-
-In the project directory, you can run:
-
-### `npm start`
-
-Runs the app in the development mode.\
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
-
-The page will reload if you make edits.\
-You will also see any lint errors in the console.
-
-### `npm test`
-
-Launches the test runner in the interactive watch mode.\
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
-
-### `npm run build`
-
-Builds the app for production to the `build` folder.\
-It correctly bundles React in production mode and optimizes the build for the best performance.
-
-The build is minified and the filenames include the hashes.\
-Your app is ready to be deployed!
-
-See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
-
-### `npm run eject`
-
-**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
-
-If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
-
-Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
-
-You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
-
-## Learn More
-
-You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
-
-To learn React, check out the [React documentation](https://reactjs.org/).
diff --git a/packages/power.stream-obs/craco.config.js b/packages/power.stream-obs/craco.config.js
deleted file mode 100644
index c17acbd833..0000000000
--- a/packages/power.stream-obs/craco.config.js
+++ /dev/null
@@ -1,24 +0,0 @@
-const path = require("path");
-const { getLoader, loaderByName } = require("@craco/craco");
-
-const packages = [];
-packages.push(path.join(__dirname, "../components"));
-
-module.exports = {
- webpack: {
- configure: (webpackConfig, arg) => {
- const { isFound, match } = getLoader(
- webpackConfig,
- loaderByName("babel-loader")
- );
- if (isFound) {
- const include = Array.isArray(match.loader.include)
- ? match.loader.include
- : [match.loader.include];
-
- match.loader.include = include.concat(packages);
- }
- return webpackConfig;
- },
- },
-};
diff --git a/packages/power.stream-obs/package.json b/packages/power.stream-obs/package.json
deleted file mode 100644
index 8f4f4aee24..0000000000
--- a/packages/power.stream-obs/package.json
+++ /dev/null
@@ -1,53 +0,0 @@
-{
- "name": "@storyteller/power.stream-obs",
- "version": "0.1.0",
- "private": true,
- "dependencies": {
- "@craco/craco": "^6.4.3",
- "@fortawesome/fontawesome-svg-core": "^6.2.1",
- "@fortawesome/free-brands-svg-icons": "^6.2.1",
- "@fortawesome/free-solid-svg-icons": "^6.2.1",
- "@fortawesome/react-fontawesome": "^0.2.0",
- "@storyteller/components": "0.1.0",
- "@testing-library/jest-dom": "^5.16.1",
- "@testing-library/react": "^9.5.0",
- "@testing-library/user-event": "^13.5.0",
- "@types/jest": "^27.4.0",
- "@types/node": "^16.11.21",
- "@types/react": "^16.9.36",
- "@types/react-dom": "^16.9.8",
- "bulma": "^0.9.0",
- "craco": "^0.0.3",
- "react": "^16.13.1",
- "react-autocomplete": "^1.8.1",
- "react-dom": "^16.13.1",
- "react-router-dom": "^5.2.0",
- "react-scripts": "5.0.0",
- "typescript": "^4.5.5",
- "web-vitals": "^2.1.4"
- },
- "scripts": {
- "start": "craco start",
- "build": "craco build",
- "test": "craco test",
- "eject": "craco eject"
- },
- "eslintConfig": {
- "extends": [
- "react-app",
- "react-app/jest"
- ]
- },
- "browserslist": {
- "production": [
- ">0.2%",
- "not dead",
- "not op_mini all"
- ],
- "development": [
- "last 1 chrome version",
- "last 1 firefox version",
- "last 1 safari version"
- ]
- }
-}
diff --git a/packages/power.stream-obs/public/favicon.ico b/packages/power.stream-obs/public/favicon.ico
deleted file mode 100644
index a11777cc47..0000000000
Binary files a/packages/power.stream-obs/public/favicon.ico and /dev/null differ
diff --git a/packages/power.stream-obs/public/index.html b/packages/power.stream-obs/public/index.html
deleted file mode 100644
index 4724be42aa..0000000000
--- a/packages/power.stream-obs/public/index.html
+++ /dev/null
@@ -1,68 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Storyteller. Stream tech.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- You need to enable JavaScript to run this app.
-
-
-
-
diff --git a/packages/power.stream-obs/public/logo192.png b/packages/power.stream-obs/public/logo192.png
deleted file mode 100644
index fc44b0a379..0000000000
Binary files a/packages/power.stream-obs/public/logo192.png and /dev/null differ
diff --git a/packages/power.stream-obs/public/logo512.png b/packages/power.stream-obs/public/logo512.png
deleted file mode 100644
index a4e47a6545..0000000000
Binary files a/packages/power.stream-obs/public/logo512.png and /dev/null differ
diff --git a/packages/power.stream-obs/public/manifest.json b/packages/power.stream-obs/public/manifest.json
deleted file mode 100644
index 4a14ea6044..0000000000
--- a/packages/power.stream-obs/public/manifest.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "short_name": "Storyteller",
- "name": "Stream tech",
- "icons": [
- {
- "src": "favicon.ico",
- "sizes": "64x64 32x32 24x24 16x16",
- "type": "image/x-icon"
- },
- {
- "src": "logo192.png",
- "type": "image/png",
- "sizes": "192x192"
- },
- {
- "src": "logo512.png",
- "type": "image/png",
- "sizes": "512x512"
- }
- ],
- "start_url": ".",
- "display": "standalone",
- "theme_color": "#000000",
- "background_color": "#ffffff"
-}
diff --git a/packages/power.stream-obs/public/robots.txt b/packages/power.stream-obs/public/robots.txt
deleted file mode 100644
index e9e57dc4d4..0000000000
--- a/packages/power.stream-obs/public/robots.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-# https://www.robotstxt.org/robotstxt.html
-User-agent: *
-Disallow:
diff --git a/packages/power.stream-obs/src/App.css b/packages/power.stream-obs/src/App.css
deleted file mode 100644
index 74b5e05345..0000000000
--- a/packages/power.stream-obs/src/App.css
+++ /dev/null
@@ -1,38 +0,0 @@
-.App {
- text-align: center;
-}
-
-.App-logo {
- height: 40vmin;
- pointer-events: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .App-logo {
- animation: App-logo-spin infinite 20s linear;
- }
-}
-
-.App-header {
- background-color: #282c34;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: calc(10px + 2vmin);
- color: white;
-}
-
-.App-link {
- color: #61dafb;
-}
-
-@keyframes App-logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
diff --git a/packages/power.stream-obs/src/App.scss b/packages/power.stream-obs/src/App.scss
deleted file mode 100644
index 6b3abd16c7..0000000000
--- a/packages/power.stream-obs/src/App.scss
+++ /dev/null
@@ -1,22 +0,0 @@
-//@import-normalize; /* bring in normalize.css styles */
-
-$page-width: 700px;
-
-$light-text-color: #777;
-$button-text-color: #666;
-
-$body-font-family: 'Fira Sans', serif;
-$nav-font-family: 'Fira Sans', sans-serif;
-$footer-font-family: 'Fira Sans Condensed', sans-serif;
-$textarea-font-family: 'Fira Sans', sans-serif;
-$headings-font-family: 'Merriweather', serif;
-
-body {
- font-family: $body-font-family;
- font-weight: 300; // For Fira Sans
-}
-
-.mainwrap {
- max-width: 1000px;
- margin: auto;
-}
\ No newline at end of file
diff --git a/packages/power.stream-obs/src/App.test.tsx b/packages/power.stream-obs/src/App.test.tsx
deleted file mode 100644
index bfe47f2987..0000000000
--- a/packages/power.stream-obs/src/App.test.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-//import { render, screen } from '@testing-library/react';
-import App from './App';
-
-test('renders learn react link', () => {
- // NB(2022-02-01): This broke when converting to a monorepo.
- // I hadn't been using the tests anyhow.
- //render( );
- //const linkElement = screen.getByText(/learn react/i);
- //expect(linkElement).toBeInTheDocument();
-});
diff --git a/packages/power.stream-obs/src/App.tsx b/packages/power.stream-obs/src/App.tsx
deleted file mode 100644
index ce1df2340e..0000000000
--- a/packages/power.stream-obs/src/App.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import 'bulma/css/bulma.css'
-
-import React from 'react';
-import { BrowserRouter, Route } from 'react-router-dom';
-import { ObsLayerPage } from './pages/obs-layer/ObsLayerPage';
-
-
-function App () {
- return (
-
-
-
-
-
-
-
-
-
Invalid Page for OBS
-
-
-
-
-
-
- );
-}
-
-export default App;
diff --git a/packages/power.stream-obs/src/index.css b/packages/power.stream-obs/src/index.css
deleted file mode 100644
index d942c42f27..0000000000
--- a/packages/power.stream-obs/src/index.css
+++ /dev/null
@@ -1,13 +0,0 @@
-body {
- margin: 0;
- /*font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
- sans-serif;*/
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
- monospace;
-}
diff --git a/packages/power.stream-obs/src/index.tsx b/packages/power.stream-obs/src/index.tsx
deleted file mode 100644
index ef2edf8ea3..0000000000
--- a/packages/power.stream-obs/src/index.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import './index.css';
-import App from './App';
-import reportWebVitals from './reportWebVitals';
-
-ReactDOM.render(
-
-
- ,
- document.getElementById('root')
-);
-
-// If you want to start measuring performance in your app, pass a function
-// to log results (for example: reportWebVitals(console.log))
-// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals();
diff --git a/packages/power.stream-obs/src/logo.svg b/packages/power.stream-obs/src/logo.svg
deleted file mode 100644
index 9dfc1c058c..0000000000
--- a/packages/power.stream-obs/src/logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/power.stream-obs/src/pages/obs-layer/ObsLayerPage.tsx b/packages/power.stream-obs/src/pages/obs-layer/ObsLayerPage.tsx
deleted file mode 100644
index 204448da43..0000000000
--- a/packages/power.stream-obs/src/pages/obs-layer/ObsLayerPage.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import React, { useEffect, useRef, useState } from 'react';
-import { Howl } from 'howler';
-import { useParams } from 'react-router-dom';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faVolumeUp } from '@fortawesome/free-solid-svg-icons';
-import WebSocketProtocol from './player/WebSocketProtocol';
-
-/*
-NB: Debugging with CORS and self signed certs in local dev is a nightmare. Use this (Linux):
-
- chromium-browser --disable-web-security --user-data-dir="~/chrome"
-
-*/
-
-function ObsLayerPage() {
- const { username } : { username : string } = useParams();
-
- // FIXME: We're going to remove the "click to activate" button by switching this to true
- // Ultimately all of this UI should be pulled out, because very few people are using this
- // through their browsers.
- const [interfaceHidden, setInterfaceHidden]= useState(true);
-
- const webSocketProtocolRef = useRef(new WebSocketProtocol(username));
-
- useEffect(() => {
- webSocketProtocolRef.current.start();
-
- // Click anywhere on the page to activate sounds.
- document.documentElement.addEventListener("mousedown", () => {
- console.log('play dummy sound');
- let sound = new Howl({
- src: ['foo']
- });
- sound.play();
- })
-
- }, [username]);
-
- if (interfaceHidden) {
- return <>>;
- }
-
- return (
-
-
-
-
- Paste this page's URL into OBS
-
- Twitch Username : {username}
-
- setInterfaceHidden(true)}
- >
- Click this to activate audio and hide UI
-
-
-
-
- )
-}
-
-export { ObsLayerPage }
\ No newline at end of file
diff --git a/packages/power.stream-obs/src/pages/obs-layer/player/QueuePlayer.tsx b/packages/power.stream-obs/src/pages/obs-layer/player/QueuePlayer.tsx
deleted file mode 100644
index e77012602f..0000000000
--- a/packages/power.stream-obs/src/pages/obs-layer/player/QueuePlayer.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-
-class QueuePlayer {
-
-}
-
-export default QueuePlayer;
diff --git a/packages/power.stream-obs/src/pages/obs-layer/player/WebSocketProtocol.ts b/packages/power.stream-obs/src/pages/obs-layer/player/WebSocketProtocol.ts
deleted file mode 100644
index 95dac42bab..0000000000
--- a/packages/power.stream-obs/src/pages/obs-layer/player/WebSocketProtocol.ts
+++ /dev/null
@@ -1,162 +0,0 @@
-// This class encodes our websocket protocol and sequences playing of clips.
-
-import { Howl } from 'howler';
-import MediaQueue from "./composition/MediaQueue";
-import ObsWebSocket from "./composition/ObsWebSocket";
-
-// If a new socket is unhealthy after this many seconds, refresh the page.
-const NEW_SOCKET_TTL_MILLIS = 30_000;
-
-// If a previously working socket is unhealthy after this many seconds, refresh the page.
-const WORKING_SOCKET_TTL_MILLIS = 60_000;
-
-class WebSocketProtocol {
- twitchUsername: string;
-
- mediaQueue: MediaQueue;
-
- webSocket?: ObsWebSocket;
-
- currentSound?: Howl;
-
- // Keep track of last successful ping delta to force page refreshes.
- createdTime: Date;
- lastSuccessfulPingTime?: Date;
-
- constructor(twitchUsername: string) {
- this.twitchUsername = twitchUsername;
- this.mediaQueue = new MediaQueue();
- this.createdTime = new Date();
- }
-
- start() {
- this.newWebsocket();
- this.beginPollingMediaQueue();
- this.beginPollingAvailableAudioToPlay();
- this.beginPollingWebsocket();
- this.beginHealthCheckingPage();
- }
-
- private newWebsocket() {
- console.warn('creating new websocket');
- if (this.webSocket) {
- this.webSocket.close();
- }
- const that = this;
- this.webSocket = new ObsWebSocket(
- this.twitchUsername,
- (ev: Event) => that.onWebsocketError(ev),
- (ev: MessageEvent) => that.onWebsocketMessage(ev),
- );
- }
-
- private beginPollingMediaQueue() {
- const that = this;
- setInterval(async () => {
- await that.mediaQueue.queryAllPendingJobs();
- }, 1000);
- }
-
- private beginPollingAvailableAudioToPlay() {
- const that = this;
- setInterval(() => {
- that.playNextAvailableAudio();
- }, 1000);
- }
-
- private beginPollingWebsocket() {
- // NB: This has a direct bearing on how fast the backend responds.
- // Increasing the delay will slow down the flow of events.
- const that = this;
- setInterval(async () => {
- if (!!that.webSocket) {
- that.webSocket.send('ping');
- }
- }, 1000);
- }
-
- private beginHealthCheckingPage() {
- // HACK: We're getting a lot of reports that OBS will stop playing audio
- // after 10-30 minutes. We'll force a page reload if the socket hasn't
- // reported any data to us. Not sure if this is a proper fix yet.
- const that = this;
- setInterval(() => {
- const now = new Date();
- let delta = -1;
- let ttl = 0;
-
- if (that.lastSuccessfulPingTime !== undefined) {
- delta = now.getTime() - that.lastSuccessfulPingTime.getTime();
- ttl = WORKING_SOCKET_TTL_MILLIS;
- } else {
- delta = now.getTime() - that.createdTime.getTime();
- ttl = NEW_SOCKET_TTL_MILLIS;
- }
-
- if (delta > ttl) {
- console.error('Socket appears unhealthy! TTL elapsed. Reloading page.');
- window.location.reload();
- }
- }, 5000);
- }
-
- private onWebsocketError(event: Event) {
- const that = this;
- setTimeout(() => that.newWebsocket(), 1000);
- }
-
- private onWebsocketMessage(event: MessageEvent) {
- const result = JSON.parse(event.data);
-
- console.log('message', result);
-
- this.lastSuccessfulPingTime = new Date();
-
- if (result['response_type'] === 'TtsEvent') {
- this.handleTtsWebsocketMessage(result);
- }
- }
-
- private handleTtsWebsocketMessage(result: any) {
- if ('tts_job_tokens' in result) {
- result['tts_job_tokens'].forEach((jobToken : string) => {
- this.mediaQueue.addNewTtsJobToken(jobToken);
- });
- }
- }
-
- private isCurrentlyPlayingAudio() {
- return this.currentSound !== undefined && this.currentSound.playing();
- }
-
- private playNextAvailableAudio() {
- if (this.isCurrentlyPlayingAudio()) {
- return; // Not ready.
- }
-
- const maybePlayableAudio = this.mediaQueue.getNextAvailable();
- if (!maybePlayableAudio) {
- return; // No audio available.
- }
-
- const that = this;
-
- const sound = new Howl({
- src: [maybePlayableAudio.getUrl()],
- autoplay: false,
- loop: false,
- onend: function(soundId: number) {
- that.currentSound = undefined;
- that.playNextAvailableAudio(); // eagerly play the next one.
- },
- });
-
- this.currentSound = sound;
-
- sound.once('load', function() {
- sound.play();
- });
- }
-}
-
-export default WebSocketProtocol;
diff --git a/packages/power.stream-obs/src/pages/obs-layer/player/composition/MediaQueue.ts b/packages/power.stream-obs/src/pages/obs-layer/player/composition/MediaQueue.ts
deleted file mode 100644
index fb8004465d..0000000000
--- a/packages/power.stream-obs/src/pages/obs-layer/player/composition/MediaQueue.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
-import { GetTtsInferenceJobStatus, GetTtsInferenceJobStatusIsOk, TtsInferenceJobStatus } from "@storyteller/components/src/api/jobs/GetTtsInferenceJobStatus";
-import { JobState, jobStateCanChange, jobStateFromString } from "@storyteller/components/src/jobs/JobStates";
-import { TtsInferenceJob } from "@storyteller/components/src/jobs/TtsInferenceJobs";
-import PlayableAudioFile from "./PlayableAudioFile";
-
-enum PollingOutcome {
- KeepPolling,
- RemoveFromPendingQueue,
-}
-
-class MediaQueue {
-
- // These are newly enqueued TTS jobs.
- private enqueuedJobQueue: TtsInferenceJob[];
-
- // This is audio that is ready to play!
- private readyToPlayAudioQueue: PlayableAudioFile[];
-
- constructor() {
- this.enqueuedJobQueue = [];
- this.readyToPlayAudioQueue = [];
- }
-
- // Add a new TTS job
- addNewTtsJobToken(jobToken: string) {
- const newJob = new TtsInferenceJob(jobToken);
- this.enqueuedJobQueue.push(newJob);
- }
-
- // Get the next audio file that is available (if it exists)
- getNextAvailable() : PlayableAudioFile | undefined {
- return this.readyToPlayAudioQueue.shift();
- }
-
- // Query all jobs *ONCE*
- async queryAllPendingJobs() {
- let removeJobTokens = new Set();
-
- for (let job of this.enqueuedJobQueue) {
- if (jobStateCanChange(job.jobState)) {
- let pollingOutcome = await this.queryPendingJobForStatus(job.jobToken);
-
- if (pollingOutcome === PollingOutcome.RemoveFromPendingQueue) {
- removeJobTokens.add(job.jobToken);
- }
- }
- }
-
- this.enqueuedJobQueue =
- this.enqueuedJobQueue.filter(job => !removeJobTokens.has(job.jobToken));
- }
-
- private async queryPendingJobForStatus(jobToken: string) : Promise {
- const response = await GetTtsInferenceJobStatus(jobToken);
- if (GetTtsInferenceJobStatusIsOk(response)) {
-
- const jobState = jobStateFromString(response.state.status);
-
- switch (jobState) {
- case JobState.UNKNOWN:
- case JobState.PENDING:
- case JobState.STARTED:
- case JobState.ATTEMPT_FAILED:
- return PollingOutcome.KeepPolling;
-
- case JobState.COMPLETE_SUCCESS:
- this.addReadyToPlayJob(response.state);
- return PollingOutcome.RemoveFromPendingQueue;
-
- case JobState.COMPLETE_FAILURE:
- case JobState.DEAD:
- return PollingOutcome.RemoveFromPendingQueue;
- }
- }
-
- return PollingOutcome.KeepPolling;
- }
-
- private addReadyToPlayJob(jobStatusResponse: TtsInferenceJobStatus) {
- const wavPath = jobStatusResponse.maybe_public_bucket_wav_audio_path;
-
- if (!wavPath) {
- return;
- }
-
- const wavUrl = new BucketConfig().getGcsUrl(wavPath);
- const playable = new PlayableAudioFile(wavUrl);
-
- this.readyToPlayAudioQueue.push(playable);
- }
-}
-
-export default MediaQueue;
diff --git a/packages/power.stream-obs/src/pages/obs-layer/player/composition/ObsWebSocket.ts b/packages/power.stream-obs/src/pages/obs-layer/player/composition/ObsWebSocket.ts
deleted file mode 100644
index 1670b13b75..0000000000
--- a/packages/power.stream-obs/src/pages/obs-layer/player/composition/ObsWebSocket.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { ApiConfig } from "@storyteller/components";
-
-class ObsWebSocket {
-
- private webSocket: WebSocket;
-
- private onErrorCallback?: (event: Event) => void;
- private onMessageCallback?: (event: MessageEvent) => void;
-
- constructor(
- twitchUsername: string,
- onError?: (event: Event) => void,
- onMessage?: (event: MessageEvent) => void,
- ) {
- const url = new ApiConfig().obsEventsWebsocket(twitchUsername);
- const webSocket = new WebSocket(url);
-
- if (!!onError) {
- this.onErrorCallback = onError;
- }
-
- if (!!onMessage) {
- this.onMessageCallback = onMessage;
- }
-
- const that = this;
-
- webSocket.onopen = function (event: Event) { that.onOpen(event); };
- webSocket.onerror = function(event: Event) { that.onError(event); };
- webSocket.onmessage = function (event: MessageEvent) { that.onMessage(event); }
-
- this.webSocket = webSocket;
- }
-
- send(data: string) {
- this.webSocket.send(data);
- }
-
- close() {
- this.onErrorCallback = undefined;
- this.onMessageCallback = undefined;
- this.webSocket.close();
- }
-
- // ========== Handlers ==========
-
- private onOpen(event: Event) {
- }
-
- private onMessage(event: MessageEvent) {
- if (!!this.onMessageCallback) {
- this.onMessageCallback(event);
- }
- }
-
- private onError(event: Event) {
- console.log('error with websocket: ', event);
- if (!!this.onErrorCallback) {
- this.onErrorCallback(event);
- }
- }
-}
-
-export default ObsWebSocket;
\ No newline at end of file
diff --git a/packages/power.stream-obs/src/pages/obs-layer/player/composition/PlayableAudioFile.ts b/packages/power.stream-obs/src/pages/obs-layer/player/composition/PlayableAudioFile.ts
deleted file mode 100644
index 7e4d3951f6..0000000000
--- a/packages/power.stream-obs/src/pages/obs-layer/player/composition/PlayableAudioFile.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-
-// TODO: Add twitch user data, and lots of other stats that might be useful
-// at audio play-time.
-class PlayableAudioFile {
-
- private mediaUrl: string;
-
- constructor(mediaUrl: string) {
- this.mediaUrl = mediaUrl;
- }
-
- getUrl() : string {
- return this.mediaUrl;
- }
-}
-
-export default PlayableAudioFile;
diff --git a/packages/power.stream-obs/src/react-app-env.d.ts b/packages/power.stream-obs/src/react-app-env.d.ts
deleted file mode 100644
index 6431bc5fc6..0000000000
--- a/packages/power.stream-obs/src/react-app-env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-///
diff --git a/packages/power.stream-obs/src/reportWebVitals.ts b/packages/power.stream-obs/src/reportWebVitals.ts
deleted file mode 100644
index 49a2a16e0f..0000000000
--- a/packages/power.stream-obs/src/reportWebVitals.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { ReportHandler } from 'web-vitals';
-
-const reportWebVitals = (onPerfEntry?: ReportHandler) => {
- if (onPerfEntry && onPerfEntry instanceof Function) {
- import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
- getCLS(onPerfEntry);
- getFID(onPerfEntry);
- getFCP(onPerfEntry);
- getLCP(onPerfEntry);
- getTTFB(onPerfEntry);
- });
- }
-};
-
-export default reportWebVitals;
diff --git a/packages/power.stream-obs/src/setupTests.ts b/packages/power.stream-obs/src/setupTests.ts
deleted file mode 100644
index 8f2609b7b3..0000000000
--- a/packages/power.stream-obs/src/setupTests.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-// jest-dom adds custom jest matchers for asserting on DOM nodes.
-// allows you to do things like:
-// expect(element).toHaveTextContent(/react/i)
-// learn more: https://github.com/testing-library/jest-dom
-import '@testing-library/jest-dom';
diff --git a/packages/power.stream-obs/tsconfig.json b/packages/power.stream-obs/tsconfig.json
deleted file mode 100644
index a273b0cfc0..0000000000
--- a/packages/power.stream-obs/tsconfig.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "compilerOptions": {
- "target": "es5",
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
- ],
- "allowJs": true,
- "skipLibCheck": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "strict": true,
- "forceConsistentCasingInFileNames": true,
- "noFallthroughCasesInSwitch": true,
- "module": "esnext",
- "moduleResolution": "node",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "jsx": "react-jsx"
- },
- "include": [
- "src"
- ]
-}
diff --git a/packages/power.stream/.env b/packages/power.stream/.env
deleted file mode 100644
index 7428382201..0000000000
--- a/packages/power.stream/.env
+++ /dev/null
@@ -1,3 +0,0 @@
-BROWSER=none
-HTTPS=true
-PORT=7001
\ No newline at end of file
diff --git a/packages/power.stream/.gitignore b/packages/power.stream/.gitignore
deleted file mode 100644
index 8b182cf848..0000000000
--- a/packages/power.stream/.gitignore
+++ /dev/null
@@ -1,25 +0,0 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-/.pnp
-.pnp.js
-
-# testing
-/coverage
-
-# production
-/build
-
-# misc
-.DS_Store
-.env.local
-.env.development.local
-.env.test.local
-.env.production.local
-
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-
-.env
diff --git a/packages/power.stream/README.md b/packages/power.stream/README.md
deleted file mode 100644
index b87cb00449..0000000000
--- a/packages/power.stream/README.md
+++ /dev/null
@@ -1,46 +0,0 @@
-# Getting Started with Create React App
-
-This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
-
-## Available Scripts
-
-In the project directory, you can run:
-
-### `npm start`
-
-Runs the app in the development mode.\
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
-
-The page will reload if you make edits.\
-You will also see any lint errors in the console.
-
-### `npm test`
-
-Launches the test runner in the interactive watch mode.\
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
-
-### `npm run build`
-
-Builds the app for production to the `build` folder.\
-It correctly bundles React in production mode and optimizes the build for the best performance.
-
-The build is minified and the filenames include the hashes.\
-Your app is ready to be deployed!
-
-See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
-
-### `npm run eject`
-
-**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
-
-If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
-
-Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
-
-You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
-
-## Learn More
-
-You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
-
-To learn React, check out the [React documentation](https://reactjs.org/).
diff --git a/packages/power.stream/craco.config.js b/packages/power.stream/craco.config.js
deleted file mode 100644
index c17acbd833..0000000000
--- a/packages/power.stream/craco.config.js
+++ /dev/null
@@ -1,24 +0,0 @@
-const path = require("path");
-const { getLoader, loaderByName } = require("@craco/craco");
-
-const packages = [];
-packages.push(path.join(__dirname, "../components"));
-
-module.exports = {
- webpack: {
- configure: (webpackConfig, arg) => {
- const { isFound, match } = getLoader(
- webpackConfig,
- loaderByName("babel-loader")
- );
- if (isFound) {
- const include = Array.isArray(match.loader.include)
- ? match.loader.include
- : [match.loader.include];
-
- match.loader.include = include.concat(packages);
- }
- return webpackConfig;
- },
- },
-};
diff --git a/packages/power.stream/package.json b/packages/power.stream/package.json
deleted file mode 100644
index 0f052cfcc4..0000000000
--- a/packages/power.stream/package.json
+++ /dev/null
@@ -1,56 +0,0 @@
-{
- "name": "@storyteller/power.stream",
- "version": "0.1.0",
- "private": true,
- "dependencies": {
- "@craco/craco": "^6.4.3",
- "@fortawesome/fontawesome-svg-core": "^6.2.1",
- "@fortawesome/free-brands-svg-icons": "^6.2.1",
- "@fortawesome/free-solid-svg-icons": "^6.2.1",
- "@fortawesome/react-fontawesome": "^0.2.0",
- "@storyteller/components": "0.1.0",
- "@testing-library/jest-dom": "^5.16.1",
- "@testing-library/react": "^9.5.0",
- "@testing-library/user-event": "^13.5.0",
- "@types/jest": "^27.4.0",
- "@types/node": "^16.11.21",
- "@types/react": "^16.9.36",
- "@types/react-dom": "^16.9.8",
- "bulma": "^0.9.0",
- "craco": "^0.0.3",
- "react": "^16.13.1",
- "react-autocomplete": "^1.8.1",
- "react-dom": "^16.13.1",
- "react-router-dom": "^5.2.0",
- "react-scripts": "5.0.0",
- "react-wavesurfer.js": "^0.0.8",
- "typescript": "^4.5.5",
- "wavesurfer.js": "^6.2.0",
- "web-vitals": "^2.1.4"
- },
- "scripts": {
- "start": "craco start",
- "start-w": "set BROWSER=none && craco start",
- "build": "craco build",
- "test": "craco test",
- "eject": "craco eject"
- },
- "eslintConfig": {
- "extends": [
- "react-app",
- "react-app/jest"
- ]
- },
- "browserslist": {
- "production": [
- ">0.2%",
- "not dead",
- "not op_mini all"
- ],
- "development": [
- "last 1 chrome version",
- "last 1 firefox version",
- "last 1 safari version"
- ]
- }
-}
diff --git a/packages/power.stream/public/android-chrome-192x192.png b/packages/power.stream/public/android-chrome-192x192.png
deleted file mode 100644
index 5012fb5b32..0000000000
Binary files a/packages/power.stream/public/android-chrome-192x192.png and /dev/null differ
diff --git a/packages/power.stream/public/android-chrome-512x512.png b/packages/power.stream/public/android-chrome-512x512.png
deleted file mode 100644
index ca9fab527a..0000000000
Binary files a/packages/power.stream/public/android-chrome-512x512.png and /dev/null differ
diff --git a/packages/power.stream/public/apple-touch-icon.png b/packages/power.stream/public/apple-touch-icon.png
deleted file mode 100644
index 1adfe32697..0000000000
Binary files a/packages/power.stream/public/apple-touch-icon.png and /dev/null differ
diff --git a/packages/power.stream/public/assets/bits-icon.png b/packages/power.stream/public/assets/bits-icon.png
deleted file mode 100644
index ee34dee7c7..0000000000
Binary files a/packages/power.stream/public/assets/bits-icon.png and /dev/null differ
diff --git a/packages/power.stream/public/assets/channel-points-icon.png b/packages/power.stream/public/assets/channel-points-icon.png
deleted file mode 100644
index 776faa183b..0000000000
Binary files a/packages/power.stream/public/assets/channel-points-icon.png and /dev/null differ
diff --git a/packages/power.stream/public/assets/channel-points-icon.svg b/packages/power.stream/public/assets/channel-points-icon.svg
deleted file mode 100644
index 68e2112891..0000000000
--- a/packages/power.stream/public/assets/channel-points-icon.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/packages/power.stream/public/assets/circle-1.png b/packages/power.stream/public/assets/circle-1.png
deleted file mode 100644
index a283ec849d..0000000000
Binary files a/packages/power.stream/public/assets/circle-1.png and /dev/null differ
diff --git a/packages/power.stream/public/assets/circle-2.png b/packages/power.stream/public/assets/circle-2.png
deleted file mode 100644
index c75c2eb035..0000000000
Binary files a/packages/power.stream/public/assets/circle-2.png and /dev/null differ
diff --git a/packages/power.stream/public/assets/cta-img.png b/packages/power.stream/public/assets/cta-img.png
deleted file mode 100644
index b231b23a2b..0000000000
Binary files a/packages/power.stream/public/assets/cta-img.png and /dev/null differ
diff --git a/packages/power.stream/public/assets/hanashi-mc.png b/packages/power.stream/public/assets/hanashi-mc.png
deleted file mode 100644
index 5116162823..0000000000
Binary files a/packages/power.stream/public/assets/hanashi-mc.png and /dev/null differ
diff --git a/packages/power.stream/public/assets/hero-audio-wave.png b/packages/power.stream/public/assets/hero-audio-wave.png
deleted file mode 100644
index 4b35790786..0000000000
Binary files a/packages/power.stream/public/assets/hero-audio-wave.png and /dev/null differ
diff --git a/packages/power.stream/public/assets/hero-bg-floor.png b/packages/power.stream/public/assets/hero-bg-floor.png
deleted file mode 100644
index 12a16a61b5..0000000000
Binary files a/packages/power.stream/public/assets/hero-bg-floor.png and /dev/null differ
diff --git a/packages/power.stream/public/assets/hero-kitsune.png b/packages/power.stream/public/assets/hero-kitsune.png
deleted file mode 100644
index e47ba9b110..0000000000
Binary files a/packages/power.stream/public/assets/hero-kitsune.png and /dev/null differ
diff --git a/packages/power.stream/public/assets/munis copy.svg b/packages/power.stream/public/assets/munis copy.svg
deleted file mode 100644
index 57a3b0c548..0000000000
--- a/packages/power.stream/public/assets/munis copy.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/packages/power.stream/public/assets/munis.svg b/packages/power.stream/public/assets/munis.svg
deleted file mode 100644
index 57a3b0c548..0000000000
--- a/packages/power.stream/public/assets/munis.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/packages/power.stream/public/assets/obs-icon.png b/packages/power.stream/public/assets/obs-icon.png
deleted file mode 100644
index 49d44e144e..0000000000
Binary files a/packages/power.stream/public/assets/obs-icon.png and /dev/null differ
diff --git a/packages/power.stream/public/assets/plus copy.svg b/packages/power.stream/public/assets/plus copy.svg
deleted file mode 100644
index f060693945..0000000000
--- a/packages/power.stream/public/assets/plus copy.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/packages/power.stream/public/assets/plus.svg b/packages/power.stream/public/assets/plus.svg
deleted file mode 100644
index f060693945..0000000000
--- a/packages/power.stream/public/assets/plus.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/packages/power.stream/public/assets/powerstream-logo.png b/packages/power.stream/public/assets/powerstream-logo.png
deleted file mode 100644
index 7bc285ebe5..0000000000
Binary files a/packages/power.stream/public/assets/powerstream-logo.png and /dev/null differ
diff --git a/packages/power.stream/public/assets/preview/monokuma.wav b/packages/power.stream/public/assets/preview/monokuma.wav
deleted file mode 100644
index 5ce286edac..0000000000
Binary files a/packages/power.stream/public/assets/preview/monokuma.wav and /dev/null differ
diff --git a/packages/power.stream/public/assets/preview/son-goku.wav b/packages/power.stream/public/assets/preview/son-goku.wav
deleted file mode 100644
index 4b1f55dd6e..0000000000
Binary files a/packages/power.stream/public/assets/preview/son-goku.wav and /dev/null differ
diff --git a/packages/power.stream/public/assets/preview/stan-lee.wav b/packages/power.stream/public/assets/preview/stan-lee.wav
deleted file mode 100644
index 5537f691ed..0000000000
Binary files a/packages/power.stream/public/assets/preview/stan-lee.wav and /dev/null differ
diff --git a/packages/power.stream/public/assets/preview/tracer.wav b/packages/power.stream/public/assets/preview/tracer.wav
deleted file mode 100644
index 898010cb28..0000000000
Binary files a/packages/power.stream/public/assets/preview/tracer.wav and /dev/null differ
diff --git a/packages/power.stream/public/assets/sonic-float.png b/packages/power.stream/public/assets/sonic-float.png
deleted file mode 100644
index 09a235e97f..0000000000
Binary files a/packages/power.stream/public/assets/sonic-float.png and /dev/null differ
diff --git a/packages/power.stream/public/assets/storyteller-logo.png b/packages/power.stream/public/assets/storyteller-logo.png
deleted file mode 100644
index 6504997d28..0000000000
Binary files a/packages/power.stream/public/assets/storyteller-logo.png and /dev/null differ
diff --git a/packages/power.stream/public/assets/title-shape.png b/packages/power.stream/public/assets/title-shape.png
deleted file mode 100644
index d3b05b7586..0000000000
Binary files a/packages/power.stream/public/assets/title-shape.png and /dev/null differ
diff --git a/packages/power.stream/public/assets/tracer-float.png b/packages/power.stream/public/assets/tracer-float.png
deleted file mode 100644
index b5968b0e9c..0000000000
Binary files a/packages/power.stream/public/assets/tracer-float.png and /dev/null differ
diff --git a/packages/power.stream/public/assets/twitch-icon.png b/packages/power.stream/public/assets/twitch-icon.png
deleted file mode 100644
index ed6fd3fc5d..0000000000
Binary files a/packages/power.stream/public/assets/twitch-icon.png and /dev/null differ
diff --git a/packages/power.stream/public/assets/voices-icon.png b/packages/power.stream/public/assets/voices-icon.png
deleted file mode 100644
index bf434e1bba..0000000000
Binary files a/packages/power.stream/public/assets/voices-icon.png and /dev/null differ
diff --git a/packages/power.stream/public/browserconfig.xml b/packages/power.stream/public/browserconfig.xml
deleted file mode 100644
index b3930d0f04..0000000000
--- a/packages/power.stream/public/browserconfig.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
- #da532c
-
-
-
diff --git a/packages/power.stream/public/favicon-16x16.png b/packages/power.stream/public/favicon-16x16.png
deleted file mode 100644
index cb64005ecd..0000000000
Binary files a/packages/power.stream/public/favicon-16x16.png and /dev/null differ
diff --git a/packages/power.stream/public/favicon-32x32.png b/packages/power.stream/public/favicon-32x32.png
deleted file mode 100644
index 7a4ec4546a..0000000000
Binary files a/packages/power.stream/public/favicon-32x32.png and /dev/null differ
diff --git a/packages/power.stream/public/favicon.ico b/packages/power.stream/public/favicon.ico
deleted file mode 100644
index 5f84a16186..0000000000
Binary files a/packages/power.stream/public/favicon.ico and /dev/null differ
diff --git a/packages/power.stream/public/index.html b/packages/power.stream/public/index.html
deleted file mode 100644
index 2277508c22..0000000000
--- a/packages/power.stream/public/index.html
+++ /dev/null
@@ -1,108 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- PowerStream - AI Power for Your Stream
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- You need to enable JavaScript to run this app.
-
-
-
-
-
diff --git a/packages/power.stream/public/logo192.png b/packages/power.stream/public/logo192.png
deleted file mode 100644
index fc44b0a379..0000000000
Binary files a/packages/power.stream/public/logo192.png and /dev/null differ
diff --git a/packages/power.stream/public/logo512.png b/packages/power.stream/public/logo512.png
deleted file mode 100644
index a4e47a6545..0000000000
Binary files a/packages/power.stream/public/logo512.png and /dev/null differ
diff --git a/packages/power.stream/public/manifest.json b/packages/power.stream/public/manifest.json
deleted file mode 100644
index 4a14ea6044..0000000000
--- a/packages/power.stream/public/manifest.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "short_name": "Storyteller",
- "name": "Stream tech",
- "icons": [
- {
- "src": "favicon.ico",
- "sizes": "64x64 32x32 24x24 16x16",
- "type": "image/x-icon"
- },
- {
- "src": "logo192.png",
- "type": "image/png",
- "sizes": "192x192"
- },
- {
- "src": "logo512.png",
- "type": "image/png",
- "sizes": "512x512"
- }
- ],
- "start_url": ".",
- "display": "standalone",
- "theme_color": "#000000",
- "background_color": "#ffffff"
-}
diff --git a/packages/power.stream/public/mascot/kitsune_pose5_black_2000.webp b/packages/power.stream/public/mascot/kitsune_pose5_black_2000.webp
deleted file mode 100644
index e24d31683a..0000000000
Binary files a/packages/power.stream/public/mascot/kitsune_pose5_black_2000.webp and /dev/null differ
diff --git a/packages/power.stream/public/mascot/kitsune_pose6_black_2000.webp b/packages/power.stream/public/mascot/kitsune_pose6_black_2000.webp
deleted file mode 100644
index eaadc8d66a..0000000000
Binary files a/packages/power.stream/public/mascot/kitsune_pose6_black_2000.webp and /dev/null differ
diff --git a/packages/power.stream/public/mascot/kitsune_pose8_black_2000.webp b/packages/power.stream/public/mascot/kitsune_pose8_black_2000.webp
deleted file mode 100644
index 9928cda0b8..0000000000
Binary files a/packages/power.stream/public/mascot/kitsune_pose8_black_2000.webp and /dev/null differ
diff --git a/packages/power.stream/public/mstile-150x150.png b/packages/power.stream/public/mstile-150x150.png
deleted file mode 100644
index 1c0c5c8f4d..0000000000
Binary files a/packages/power.stream/public/mstile-150x150.png and /dev/null differ
diff --git a/packages/power.stream/public/obs-tutorial/obs-step1.webp b/packages/power.stream/public/obs-tutorial/obs-step1.webp
deleted file mode 100644
index 821cc7eac7..0000000000
Binary files a/packages/power.stream/public/obs-tutorial/obs-step1.webp and /dev/null differ
diff --git a/packages/power.stream/public/obs-tutorial/obs-step2.webp b/packages/power.stream/public/obs-tutorial/obs-step2.webp
deleted file mode 100644
index f726ee2660..0000000000
Binary files a/packages/power.stream/public/obs-tutorial/obs-step2.webp and /dev/null differ
diff --git a/packages/power.stream/public/obs-tutorial/obs-step3.webp b/packages/power.stream/public/obs-tutorial/obs-step3.webp
deleted file mode 100644
index ff92432f9f..0000000000
Binary files a/packages/power.stream/public/obs-tutorial/obs-step3.webp and /dev/null differ
diff --git a/packages/power.stream/public/robots.txt b/packages/power.stream/public/robots.txt
deleted file mode 100644
index e9e57dc4d4..0000000000
--- a/packages/power.stream/public/robots.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-# https://www.robotstxt.org/robotstxt.html
-User-agent: *
-Disallow:
diff --git a/packages/power.stream/public/safari-pinned-tab.svg b/packages/power.stream/public/safari-pinned-tab.svg
deleted file mode 100644
index 27e2232857..0000000000
--- a/packages/power.stream/public/safari-pinned-tab.svg
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-Created by potrace 1.14, written by Peter Selinger 2001-2017
-
-
-
-
-
-
diff --git a/packages/power.stream/public/site.webmanifest b/packages/power.stream/public/site.webmanifest
deleted file mode 100644
index 9c0cbad73a..0000000000
--- a/packages/power.stream/public/site.webmanifest
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "name": "PowerStream",
- "short_name": "PowerStream",
- "icons": [
- {
- "src": "/android-chrome-192x192.png",
- "sizes": "192x192",
- "type": "image/png"
- },
- {
- "src": "/android-chrome-512x512.png",
- "sizes": "512x512",
- "type": "image/png"
- }
- ],
- "theme_color": "#209cee",
- "background_color": "#2f2f4a",
- "display": "standalone"
-}
diff --git a/packages/power.stream/public/storyteller-nav-logo-mascot-pink.png b/packages/power.stream/public/storyteller-nav-logo-mascot-pink.png
deleted file mode 100644
index 0057c9bf51..0000000000
Binary files a/packages/power.stream/public/storyteller-nav-logo-mascot-pink.png and /dev/null differ
diff --git a/packages/power.stream/src/App.css b/packages/power.stream/src/App.css
deleted file mode 100644
index 74b5e05345..0000000000
--- a/packages/power.stream/src/App.css
+++ /dev/null
@@ -1,38 +0,0 @@
-.App {
- text-align: center;
-}
-
-.App-logo {
- height: 40vmin;
- pointer-events: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .App-logo {
- animation: App-logo-spin infinite 20s linear;
- }
-}
-
-.App-header {
- background-color: #282c34;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: calc(10px + 2vmin);
- color: white;
-}
-
-.App-link {
- color: #61dafb;
-}
-
-@keyframes App-logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
diff --git a/packages/power.stream/src/App.scss b/packages/power.stream/src/App.scss
deleted file mode 100644
index 4d8ca624c5..0000000000
--- a/packages/power.stream/src/App.scss
+++ /dev/null
@@ -1,923 +0,0 @@
-//@import-normalize; /* bring in normalize.css styles */
-
-// $page-width: 700px;
-$primary: #fc6b68;
-$secondary: #72434d;
-$destructive: #6d333a;
-$white: #fff;
-$light-text-color: #777;
-$button-text-color: #666;
-
-$body-font-family: "Fira Sans", serif;
-$nav-font-family: "Fira Sans", sans-serif;
-$footer-font-family: "Fira Sans Condensed", sans-serif;
-$textarea-font-family: "Fira Sans", sans-serif;
-$headings-font-family: "Merriweather", serif;
-
-// body {
-// font-family: $body-font-family;
-// font-weight: 300; // For Fira Sans
-// }
-
-// .mainwrap {
-// max-width: 1000px;
-// margin: auto;
-// }
-
-#root {
- height: 100%;
-}
-
-.git-sha {
- color: #999;
- font-size: 0.8rem;
-}
-
-html {
- height: 100%;
- scroll-behavior: smooth;
- overflow-anchor: none;
-}
-
-body {
- height: 100%;
- font-family: "Fira Sans", sans-serif;
- color: #fff;
- overflow-anchor: none;
-}
-
-hr {
- border: 0;
- border-top: 1px solid rgba($white, 1);
- opacity: 0.1;
-}
-
-select {
- cursor: pointer;
-}
-
-button:focus,
-button:active,
-button:hover,
-label:focus,
-.btn:active,
-.btn.focus {
- outline: 0px !important;
- -webkit-appearance: none;
- box-shadow: none !important;
-}
-
-a {
- font-weight: 400;
- color: #fc6b68;
- text-decoration: none;
- cursor: pointer;
- transition: 0.1s;
-}
-
-.text-link {
- color: #fc6b68;
-}
-
-a:hover {
- color: #ff8d8b;
-}
-
-h1 {
- font-weight: 700;
-}
-
-h3 {
- color: rgba(255, 255, 255, 0.8);
-}
-
-p {
- font-weight: 300;
- margin-bottom: 0;
- color: rgba(255, 255, 255, 0.85);
-}
-
-.fw-semibold {
- font-weight: 500;
-}
-
-.pre-heading {
- font-size: 18px;
- color: #fc6b68;
- text-transform: uppercase;
-}
-
-.rotateimg180 {
- -webkit-transform: rotate(180deg);
- -moz-transform: rotate(180deg);
- -ms-transform: rotate(180deg);
- -o-transform: rotate(180deg);
- transform: rotate(180deg);
-}
-
-.noselect {
- -webkit-touch-callout: none; /* iOS Safari */
- -webkit-user-select: none; /* Safari */
- -khtml-user-select: none; /* Konqueror HTML */
- -moz-user-select: none; /* Old versions of Firefox */
- -ms-user-select: none; /* Internet Explorer/Edge */
- user-select: none; /* Non-prefixed version, currently
- supported by Chrome, Edge, Opera and Firefox */
-}
-
-#particles-js {
- position: absolute;
- width: 100%;
- height: 100%;
- top: 0;
- pointer-events: none;
-}
-
-.container {
- max-width: 1200px;
-}
-
-.container,
-.container-fluid,
-.container-lg,
-.container-md,
-.container-sm,
-.container-xl,
-.container-xxl {
- width: 100%;
- padding-right: var(--bs-gutter-x, 1rem);
- padding-left: var(--bs-gutter-x, 1rem);
-}
-
-/*margin top/bottom for panels*/
-.py-6 {
- padding-top: 1.6rem !important;
- padding-bottom: 0rem !important;
-}
-
-.navbar {
- z-index: 100;
- width: 100%;
- transition: all 0.1s;
- border-color: rgba($white, 0.07) !important;
- border-bottom: 0px solid;
- padding-top: 10px;
- padding-bottom: 10px;
-}
-
-.navbar-scrolled {
- background-color: darken(rgba(42, 21, 34, 1), 7.5%);
- border-color: rgba($white, 0.07) !important;
- border-bottom: 1px solid;
-}
-
-.navbar-fixed {
- position: fixed;
-}
-
-.navbar-dark .navbar-toggler {
- border: none;
-}
-
-.nav-bg-dark {
- background-color: #17101c !important;
-}
-
-.m-page-top {
- margin-top: 100px;
-}
-
-.offcanvas {
- width: 100vw;
- height: 100%;
-}
-
-.offcanvas-header {
- padding-top: 10px;
- padding-bottom: 10px;
-}
-
-.nav-link {
- font-size: 15px;
- font-weight: 500;
- padding-left: 16px !important;
- padding-right: 16px !important;
-}
-
-.navbar-brand {
- padding-top: 0;
-}
-
-.dropdown-item {
- font-size: 15px;
- font-weight: 600;
-}
-
-.dropdown-item:active {
- background-color: #fc6b68;
-}
-
-.dropdown-menu {
- border-color: #ffffff12 !important;
- border: 1px solid;
- border-radius: 8px;
-}
-
-.lead {
- font-size: 16px;
-}
-
-.parent {
- position: relative;
-}
-
-span.word {
- position: relative;
- z-index: 1;
-}
-
-span.word:after {
- background-color: #fc6b68;
- content: " ";
- height: 15%;
- position: absolute;
- left: 0;
- margin-left: 0rem;
- top: 74%;
- width: calc(100%);
- z-index: -1;
-}
-
-.bg-gradient {
- background: darken(rgb(13, 16, 25), 3%) !important;
- background: linear-gradient(
- 325deg,
- darken(rgba(13, 16, 25, 1), 3%) 0%,
- darken(rgba(42, 21, 34, 1), 3%) 55%,
- darken(rgba(16, 14, 26, 1), 3%) 100%
- ) !important;
- position: relative;
- overflow: hidden;
- min-height: 100%;
-}
-
-.bg-title {
- background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='1920' height='300' preserveAspectRatio='none' viewBox='0 0 1920 300'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1028%26quot%3b)' fill='none'%3e%3cpath d='M1170 175L183 174' stroke-width='8' stroke='url(%23SvgjsLinearGradient1029)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1456 93L968 92' stroke-width='10' stroke='url(%23SvgjsLinearGradient1029)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1459 102L2490 101' stroke-width='6' stroke='url(%23SvgjsLinearGradient1029)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M766 41L192 40' stroke-width='6' stroke='url(%23SvgjsLinearGradient1030)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M261 212L-475 211' stroke-width='10' stroke='url(%23SvgjsLinearGradient1030)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M251 192L1533 191' stroke-width='6' stroke='url(%23SvgjsLinearGradient1029)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M172 211L936 210' stroke-width='8' stroke='url(%23SvgjsLinearGradient1030)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M1070 207L1854 206' stroke-width='10' stroke='url(%23SvgjsLinearGradient1029)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1028'%3e%3crect width='1920' height='300' fill='white'%3e%3c/rect%3e%3c/mask%3e%3clinearGradient x1='100%25' y1='0%25' x2='0%25' y2='0%25' id='SvgjsLinearGradient1029'%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0.05 )' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3clinearGradient x1='0%25' y1='0%25' x2='100%25' y2='0%25' id='SvgjsLinearGradient1030'%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0.05)' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e");
- background-size: auto;
- background-repeat: no-repeat;
-}
-
-.bg-hero {
- background: darken(rgb(13, 16, 25), 3%);
- background: linear-gradient(
- 180deg,
- darken(rgb(13, 16, 25), 2%) 0%,
- rgba(48, 23, 32, 1) 100%
- );
-}
-
-.btn {
- border-radius: 5px;
- outline: none;
- font-size: 15px;
- font-weight: 600;
- padding: 10px 24px;
- z-index: 1;
-
- a {
- color: white !important;
- }
-}
-
-.btn-primary,
-.btn-primary:active,
-.btn-primary:focus {
- background-color: #fc6b68;
- border: 2.5px solid;
- border-color: #fc6b68;
- transition: 0.2s;
-}
-
-.btn-primary:hover {
- background-color: #ff8d8b;
- border: 2.5px solid;
- border-color: #ff8d8b;
-}
-
-.btn-secondary,
-.btn-secondary:focus,
-.btn-secondary:active {
- background-color: $secondary;
- color: $white;
- border: 2.5px solid;
- border-color: $secondary;
- transition: 0.2s;
-}
-
-.btn-secondary:hover {
- background-color: lighten($secondary, 10%);
- color: $white;
- border: 2.5px solid;
- border-color: lighten($secondary, 10%);
-}
-
-.btn-destructive,
-.btn-destructive:active,
-.btn-destructive:focus {
- background-color: $destructive;
- color: $white;
- border: 2.5px solid;
- border-color: $destructive;
- transition: 0.2s;
-}
-
-.btn-destructive:hover {
- background-color: lighten($destructive, 8%);
- color: $white;
- border: 2.5px solid;
- border-color: lighten($destructive, 8%);
-}
-
-.btn-cta,
-.btn-cta:focus,
-.btn-cta:active {
- background-color: $white;
- color: darken($secondary, 10%);
- border: 2.5px solid;
- border-color: $white;
- transition: 0.2s;
-}
-
-.btn-cta:hover {
- background-color: darken($white, 20%);
- color: darken($secondary, 10%);
- border: 2.5px solid;
- border-color: darken($white, 20%);
-}
-
-.btn-hero {
- padding-left: 35px !important;
- padding-right: 35px !important;
-}
-
-.nav-login {
- font-size: 15px;
- font-weight: 500;
- color: #fff;
- text-decoration: none;
- transition: 0.1s;
-}
-
-.nav-login:hover {
- color: rgba(255, 255, 255, 0.6);
- text-decoration: none;
- transition: 0.1s;
-}
-
-.red-line {
- border-left: 3px solid #fc6b68;
- height: 100%;
- top: 0;
-}
-
-.icon-red {
- color: #fc6b68;
-}
-
-.alert {
- border-radius: 5px;
- font-size: 14px;
-}
-
-.hero-section {
- height: 100vh;
- width: 100%;
- min-height: 600px;
- max-height: 1080px;
- text-align: center;
- overflow: hidden;
- /* background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='1920' height='300' preserveAspectRatio='none' viewBox='0 0 1920 300'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1327%26quot%3b)' fill='none'%3e%3cpath d='M486 46L1358 45' stroke-width='10' stroke='url(%23SvgjsLinearGradient1328)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M312 288L-212 287' stroke-width='6' stroke='url(%23SvgjsLinearGradient1328)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M1583 12L2334 11' stroke-width='8' stroke='url(%23SvgjsLinearGradient1329)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1219 65L1985 64' stroke-width='10' stroke='url(%23SvgjsLinearGradient1329)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1877 57L2438 56' stroke-width='8' stroke='url(%23SvgjsLinearGradient1328)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M347 173L-797 172' stroke-width='10' stroke='url(%23SvgjsLinearGradient1328)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M762 234L-332 233' stroke-width='6' stroke='url(%23SvgjsLinearGradient1329)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M621 133L2031 132' stroke-width='8' stroke='url(%23SvgjsLinearGradient1328)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1327'%3e%3crect width='1920' height='300' fill='white'%3e%3c/rect%3e%3c/mask%3e%3clinearGradient x1='0%25' y1='0%25' x2='100%25' y2='0%25' id='SvgjsLinearGradient1328'%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0.05)' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3clinearGradient x1='100%25' y1='0%25' x2='0%25' y2='0%25' id='SvgjsLinearGradient1329'%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0.05)' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e"); */
- background-position: 0 120px;
- background-repeat: no-repeat;
- background-size: 100%;
-}
-
-.hero-inner {
- padding-top: 100px;
- height: 100%;
-}
-
-.hero-text {
- max-width: 800px;
-}
-
-.hero-img-section {
- display: flex;
- position: relative;
- justify-content: center;
- height: 100%;
- width: 100%;
- pointer-events: none;
-}
-
-.hero-img {
- position: absolute;
- bottom: 0;
- max-height: 110%;
- max-width: 140%;
- z-index: 1;
-}
-
-.hero-floor {
- position: absolute;
- bottom: 0;
- width: 100%;
- height: 100%;
- max-height: 100%;
- min-height: 50%;
-}
-
-@keyframes animatedBackground {
- from {
- background-position: 0px 0;
- }
- to {
- background-position: -1448px 0;
- }
-}
-
-#hero-audio-wave {
- position: absolute;
- bottom: 0;
- margin-bottom: 12rem;
- opacity: 15%;
- width: 115%;
- min-height: 150px;
- background-image: url("../public/assets/hero-audio-wave.png");
- background-repeat: repeat-x;
- animation: animatedBackground 20s linear infinite;
- mix-blend-mode: screen;
-}
-
-.panel {
- position: relative;
- width: 100%;
- background-color: #2a1c26;
- border-color: rgba(255, 255, 255, 0.06) !important;
- border: 2px solid;
- border-radius: 8px;
- backdrop-filter: blur(10px);
- -webkit-backdrop-filter: blur(10px);
- // -webkit-box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
- // box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
-}
-
-.panel-outline {
- border-color: rgba(252, 107, 104, 0.5) !important;
-}
-
-.hero-floating-panel {
- text-align: left;
- position: absolute;
- bottom: 180px;
- z-index: 3;
- width: auto;
- height: auto;
- padding: 1rem 1.25rem;
- pointer-events: none;
- background-color: lighten(#30232a, 5%);
- border-color: rgba(255, 255, 255, 0.06) !important;
-}
-
-.hero-floating-panel-left {
- margin-left: -800px;
- margin-bottom: 25px;
-}
-
-.hero-floating-panel-right {
- margin-right: -760px;
- margin-bottom: 100px;
-}
-
-.hero-floating-panel-text {
- font-size: 14px;
- color: rgba(255, 255, 255, 0.8);
-}
-
-.panel-title {
- position: static;
- text-align: center;
- text-shadow: 0px 3px 26px #00000060;
- margin-top: -35px;
-}
-
-.section {
- padding-top: 4rem;
- padding-bottom: 4rem;
-}
-
-.features-section {
- width: 100%;
- text-align: center;
- margin-top: 50px;
- margin-bottom: -60px;
-}
-
-.features-panel {
- padding: 1rem 2rem 2rem 2rem;
- position: relative;
- border-radius: 8px;
-}
-
-.features-panel-sides {
- margin-top: 40px;
- margin-bottom: 40px;
-}
-
-.features-icon {
- position: absolute;
- transform: translate(-50%, -50%);
- top: 0;
-}
-
-.features-title {
- margin-top: 60px;
-}
-
-.features-bg,
-.insights-bg,
-.community-bg {
- background-size: cover;
-}
-
-.voices-section {
- width: 100%;
- text-align: center;
- margin-top: 50px;
-}
-
-.insights-section {
- max-width: 1000px;
-}
-
-.insights-panel {
- padding: 32px;
- position: relative;
- border-radius: 8px;
-}
-
-.insights-circle-1 {
- position: absolute;
- top: 0;
- right: 0;
- margin-right: -40px;
- margin-top: 30px;
- width: 70px;
-}
-
-.insights-circle-2 {
- position: absolute;
- bottom: 0;
- right: 0;
- width: 46px;
- margin-bottom: -25px;
- margin-right: 50px;
-}
-
-.questions-panel {
- max-width: 700px;
- border-radius: 8px;
-}
-
-.cta-panel {
- background-color: #cf4c54;
- width: 100%;
- border-radius: 8px;
- padding: 3rem 1rem;
- position: relative;
- -webkit-box-shadow: 0px -10px 50px 5px #fc6b68;
- box-shadow: 0px -10px 50px 5px rgba(190, 71, 69, 0.2);
-}
-
-.cta-img {
- position: absolute;
- max-width: 600px;
- left: 50%;
- top: 50%;
- transform: translate(-50%, -55%);
- pointer-events: none;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -o-user-select: none;
- user-select: none;
-}
-
-.cta-right {
- padding-top: 150px;
-}
-
-.red-glow {
- filter: drop-shadow(0px 10px 30px #cf4c5582);
- -webkit-filter: drop-shadow(0px 10px 30px #cf4c5582);
-}
-
-.sub-title {
- font-weight: 500;
- padding-bottom: 10px;
-}
-
-.input-icon .form-control,
-.input-icon .form-select {
- padding-left: 2.375rem;
-}
-
-.input-icon .form-control-feedback {
- position: absolute;
- z-index: 2;
- display: block;
- width: 2.375rem;
- height: 2.375rem;
- line-height: 2.375rem;
- text-align: center;
- pointer-events: none;
- color: #443a40;
-}
-
-.form-control {
- border-width: 1px;
- border-color: #fff;
-}
-
-.form-control:focus,
-.form-select:focus {
- border-color: #fc6b68;
- outline: 0;
- box-shadow: 0 0 0 0.1rem #fc6b68;
-}
-
-footer {
- background-color: transparent;
-
- li {
- text-decoration: none;
- list-style: none;
- }
-}
-
-.footer-link {
- color: #fc6b68;
- font-weight: 400;
- text-decoration: none;
- cursor: pointer;
- transition: 0.1s;
-}
-
-.footer-link:hover {
- color: #ff8d8b;
-}
-
-.social-icon {
- font-size: 24px;
- color: #fff;
- text-decoration: none;
- cursor: pointer;
- transition: 0.1s;
-}
-
-.social-icon:hover {
- color: rgba(255, 255, 255, 0.601);
-}
-
-.form-check-input:checked {
- background-color: #fc6b68;
- border-color: #fc6b68;
-}
-
-.form-check-input:focus {
- box-shadow: none;
-}
-
-.login-panel {
- max-width: 800px !important;
-}
-
-.faq-panel {
- max-width: 800px;
-}
-
-.accordion-item {
- background-color: #2a1c26;
- border-color: rgba(255, 255, 255, 0.06) !important;
- border: 2px solid;
- border-radius: 5px;
-}
-
-.accordion-button {
- background-color: rgba(255, 255, 255, 0) !important;
- color: rgba(255, 255, 255, 0.8);
- font-size: 20px;
- line-height: 1.5;
-}
-
-.accordion-button:hover {
- color: #fff;
-}
-
-.accordion-button::after {
- background-image: url(../public/assets/plus.svg);
- opacity: 0.5;
- background-position: center;
-}
-
-.accordion-button:not(.collapsed)::after {
- background-image: url(../public/assets/munis.svg);
- opacity: 0.5;
-}
-
-.accordion-body {
- color: rgba(255, 255, 255, 0.8);
- font-weight: 300;
-}
-
-.accordion-button:not(.collapsed) {
- color: #fff;
- box-shadow: none;
-}
-
-.nav-pills {
- backdrop-filter: blur(20px);
- -webkit-backdrop-filter: blur(20px);
- width: 100% !important;
-}
-
-.nav-link:focus,
-.nav-link:hover {
- color: #fc6b68;
-}
-
-.nav-pills .nav-link {
- padding: 16px 24px !important;
- background-color: rgba(255, 255, 255, 0.1);
- border-radius: 0;
- border-color: rgba(255, 255, 255, 0.1) !important;
- border-bottom: 5px solid;
- color: #fff !important;
-}
-
-.nav-pills .nav-link.active,
-.nav-pills .show > .nav-link {
- background-color: rgba(255, 255, 255, 0.15);
- border-color: #fc6b68 !important;
- border-bottom: 5px solid;
-}
-
-.modal {
- background-color: rgba(19, 15, 27, 0.5);
-}
-
-.modal-content {
- background-color: #342b35;
- border-color: #4b3c46 !important;
- border: 2px solid;
- border-radius: 8px;
- backdrop-filter: blur(20px);
- -webkit-backdrop-filter: blur(20px);
- -webkit-box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
- box-shadow: 0px 0px 30px 0px rgba(0, 0, 0, 0.5);
-}
-
-.modal-header {
- border-color: rgba(255, 255, 255, 0.1) !important;
-}
-
-.modal-footer {
- border-color: rgba(255, 255, 255, 0.1) !important;
-}
-
-.modal-close-btn {
- background: none !important;
- border-radius: 5px;
- outline: none;
- border: none;
- font-size: 22px;
- color: rgba(255, 255, 255, 0.8);
- transition: 0.1s;
-}
-
-.modal-close-btn:hover {
- color: rgba(255, 255, 255, 0.4);
-}
-
-.voice-preview-text {
- font-size: 12px;
- text-align: left;
- white-space: nowrap;
-}
-
-.btn-voice-preview {
- padding: 0;
- margin: 0;
- height: 30px;
- width: 30px;
- min-width: 30px;
-}
-
-@media (min-width: 768px) {
- .container-panel {
- width: 100%;
- padding-right: var(--bs-gutter-x, 1rem);
- padding-left: var(--bs-gutter-x, 1rem);
- margin-right: auto;
- margin-left: auto;
- max-width: 1080px;
- border-radius: 8px;
- }
-
- .panel {
- border-radius: 8px;
- }
-
- .features-panel-sides {
- margin-top: 50px;
- margin-bottom: -50px;
- }
-
- .features-section {
- margin-top: 90px;
- margin-bottom: 50px;
- }
-
- .section {
- padding-top: 8.5rem;
- padding-bottom: 8.5rem;
- }
-
- .cta-right {
- padding-top: 0px;
- }
-
- .hero-img {
- position: absolute;
- bottom: 0;
- max-height: 110%;
- max-width: 100%;
- z-index: 2;
- }
-
- .voice-preview-text {
- font-size: 14px;
- }
-
- .lead {
- font-size: 18px;
- }
-}
-
-@media (min-width: 992px) {
- .panel-title {
- position: absolute;
- text-align: left;
- top: 0;
- margin-top: -25px;
- }
- .p-lg-4 {
- padding: 2rem !important;
- }
-
- .nav-bg-dark {
- background-color: #17101c00 !important;
- }
-
- .features-bg {
- background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='1920' height='560' preserveAspectRatio='none' viewBox='0 0 1920 560'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1087%26quot%3b)' fill='none'%3e%3cpath d='M860 39L1510 38' stroke-width='8' stroke='url(%23SvgjsLinearGradient1088)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M356 89L-827 88' stroke-width='6' stroke='url(%23SvgjsLinearGradient1088)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M19 415L-1000 414' stroke-width='6' stroke='url(%23SvgjsLinearGradient1089)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M207 30L-1043 29' stroke-width='10' stroke='url(%23SvgjsLinearGradient1089)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1333 95L2384 94' stroke-width='10' stroke='url(%23SvgjsLinearGradient1088)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M1471 510L2543 509' stroke-width='8' stroke='url(%23SvgjsLinearGradient1088)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M1789 194L985 193' stroke-width='10' stroke='url(%23SvgjsLinearGradient1089)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1512 92L2891 91' stroke-width='6' stroke='url(%23SvgjsLinearGradient1089)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M67 447L-1110 446' stroke-width='10' stroke='url(%23SvgjsLinearGradient1089)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1209 230L309 229' stroke-width='8' stroke='url(%23SvgjsLinearGradient1088)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M760 527L-35 526' stroke-width='6' stroke='url(%23SvgjsLinearGradient1088)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M52 257L-1362 256' stroke-width='8' stroke='url(%23SvgjsLinearGradient1088)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M1269 105L2420 104' stroke-width='10' stroke='url(%23SvgjsLinearGradient1088)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M1535 531L2296 530' stroke-width='10' stroke='url(%23SvgjsLinearGradient1088)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1087'%3e%3crect width='1920' height='560' fill='white'%3e%3c/rect%3e%3c/mask%3e%3clinearGradient x1='0%25' y1='0%25' x2='100%25' y2='0%25' id='SvgjsLinearGradient1088'%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0.05)' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3clinearGradient x1='100%25' y1='0%25' x2='0%25' y2='0%25' id='SvgjsLinearGradient1089'%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0.05)' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e");
- background-size: 100%;
- background-position: 0 250px;
- background-repeat: no-repeat;
- }
-
- .voices-bg {
- background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='1440' height='560' preserveAspectRatio='none' viewBox='0 0 1440 560'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1000%26quot%3b)' fill='none'%3e%3cpath d='M -470.4799303954471%2c120 C -422.48%2c165.4 -326.48%2c348 -230.47993039544713%2c347 C -134.48%2c346 -86.48%2c124.8 9.520069604552873%2c115 C 105.52%2c105.2 153.52%2c266.2 249.52006960455287%2c298 C 345.52%2c329.8 393.52%2c259.4 489.5200696045529%2c274 C 585.52%2c288.6 633.52%2c379.2 729.5200696045529%2c371 C 825.52%2c362.8 873.52%2c233.4 969.5200696045529%2c233 C 1065.52%2c232.6 1115.42%2c379.8 1209.5200696045529%2c369 C 1303.62%2c358.2 1393.9%2c217 1440%2c179' stroke='rgba(247%2c 84%2c 92%2c 0.08)' stroke-width='2'%3e%3c/path%3e%3cpath d='M -173.39417306836023%2c447 C -125.39%2c383.6 -29.39%2c161.8 66.60582693163977%2c130 C 162.61%2c98.2 210.61%2c278 306.60582693163974%2c288 C 402.61%2c298 450.61%2c166.8 546.6058269316397%2c180 C 642.61%2c193.2 690.61%2c337.2 786.6058269316397%2c354 C 882.61%2c370.8 930.61%2c255 1026.6058269316397%2c264 C 1122.61%2c273 1170.61%2c427.4 1266.6058269316397%2c399 C 1362.61%2c370.6 1471.93%2c142.4 1506.6058269316397%2c122 C 1541.28%2c101.6 1453.32%2c262 1440%2c297' stroke='rgba(247%2c 84%2c 92%2c 0.08)' stroke-width='2'%3e%3c/path%3e%3cpath d='M -250.53447353119677%2c366 C -202.53%2c330 -106.53%2c202 -10.53447353119676%2c186 C 85.47%2c170 133.47%2c300 229.46552646880323%2c286 C 325.47%2c272 373.47%2c109.8 469.46552646880326%2c116 C 565.47%2c122.2 613.47%2c296.4 709.4655264688032%2c317 C 805.47%2c337.6 853.47%2c207.4 949.4655264688032%2c219 C 1045.47%2c230.6 1093.47%2c376.8 1189.4655264688033%2c375 C 1285.47%2c373.2 1379.36%2c196.8 1429.4655264688033%2c210 C 1479.57%2c223.2 1437.89%2c394.8 1440%2c441' stroke='rgba(247%2c 84%2c 92%2c 0.08)' stroke-width='2'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1000'%3e%3crect width='1440' height='560' fill='white'%3e%3c/rect%3e%3c/mask%3e%3c/defs%3e%3c/svg%3e");
- background-size: 100%;
- background-position: 0 55%;
- background-repeat: no-repeat;
- }
-
- .community-bg {
- background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='1920' height='500' preserveAspectRatio='none' viewBox='0 0 1920 500'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1540%26quot%3b)' fill='none'%3e%3cpath d='M863 371L-379 370' stroke-width='6' stroke='url(%23SvgjsLinearGradient1541)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1822 20L2738 19' stroke-width='6' stroke='url(%23SvgjsLinearGradient1541)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M835 407L161 406' stroke-width='8' stroke='url(%23SvgjsLinearGradient1542)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M1461 280L514 279' stroke-width='8' stroke='url(%23SvgjsLinearGradient1542)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M362 385L-283 384' stroke-width='6' stroke='url(%23SvgjsLinearGradient1541)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1321 113L2663 112' stroke-width='6' stroke='url(%23SvgjsLinearGradient1541)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1358 249L54 248' stroke-width='6' stroke='url(%23SvgjsLinearGradient1541)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M66 37L-666 36' stroke-width='6' stroke='url(%23SvgjsLinearGradient1541)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1746 269L363 268' stroke-width='8' stroke='url(%23SvgjsLinearGradient1541)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M1317 204L759 203' stroke-width='6' stroke='url(%23SvgjsLinearGradient1542)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M1211 23L121 22' stroke-width='6' stroke='url(%23SvgjsLinearGradient1542)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3cpath d='M793 423L1418 422' stroke-width='10' stroke='url(%23SvgjsLinearGradient1541)' stroke-linecap='round' class='Left'%3e%3c/path%3e%3cpath d='M23 167L-1078 166' stroke-width='10' stroke='url(%23SvgjsLinearGradient1542)' stroke-linecap='round' class='Right'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1540'%3e%3crect width='1920' height='500' fill='white'%3e%3c/rect%3e%3c/mask%3e%3clinearGradient x1='100%25' y1='0%25' x2='0%25' y2='0%25' id='SvgjsLinearGradient1541'%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0.05)' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3clinearGradient x1='0%25' y1='0%25' x2='100%25' y2='0%25' id='SvgjsLinearGradient1542'%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(247%2c 84%2c 92%2c 0.05)' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e");
- background-size: 100%;
- background-position: center;
- background-repeat: no-repeat;
- }
-
- .insights-bg {
- background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='1920' height='300' preserveAspectRatio='none' viewBox='0 0 1920 300'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1056%26quot%3b)' fill='none'%3e%3cpath d='M0 174.34L10 149.93L20 119.89L30 125.05L40 99.87L50 67.35L60 49.02L70 34.79L80 52.92L90 38.38L100 42.07L110 42.37L120 34.03L130 50.65L140 27.52L150 20.16L160 8.73L170 44.68L180 24.6L190 1.43L200 14.65L210 43.32L220 77.48L230 99.7L240 122.99L250 146.47L260 128.54L270 102.41L280 86.49L290 102.37L300 81.48L310 62.07L320 99.29L330 75.32L340 66.93L350 83.9L360 94.48L370 62.31L380 45.19L390 27.62L400 34.54L410 41.5L420 39.2L430 56.28L440 80.96L450 58.21L460 65.28L470 93.02L480 88.25L490 107.02L500 81.06L510 84.94L520 75.08L530 40.79L540 36.65L550 64.85L560 37.29L570 68.66L580 34.8L590 57.92L600 74.39L610 55.74L620 52.09L630 70.43L640 49.43L650 71.76L660 73.23L670 77.8L680 106.27L690 85.99L700 89.94L710 112.08L720 108.17L730 127.84L740 120.67L750 102.28L760 114.63L770 111.79L780 96.92L790 65.31L800 90.52L810 76.31L820 72.63L830 57.46L840 24.02L850 27.97L860 8.13L870 23.64L880 36.56L890 43.68L900 10.09L910 18.34L920 36.36L930 60.86L940 87.39L950 93.49L960 60.46L970 87.07L980 71.1L990 102.44L1000 107.22L1010 112.7L1020 83.31L1030 74.86L1040 73.89L1050 48.95L1060 69.19L1070 33.87L1080 52.24L1090 73.78L1100 55.45L1110 69.19L1120 64.06L1130 44.4L1140 9.34L1150 29.02L1160 26.59L1170 36.77L1180 4.52L1190 25.85L1200 39.02L1210 34.71L1220 19.43L1230 35.84L1240 49.16L1250 40.48L1260 8.93L1270 39.55L1280 58.26L1290 82.39L1300 88.4L1310 100.17L1320 106.31L1330 134.08L1340 124.71L1350 101.84L1360 119.43L1370 95.32L1380 74.16L1390 82.91L1400 77.55L1410 103.99L1420 118.62L1430 85.24L1440 100.32L1450 135.6L1460 100.46L1470 88.25L1480 55.22L1490 81.67L1500 86.48L1510 52.36L1520 70.19L1530 73.09L1540 74.23L1550 52.86L1560 85.95L1570 65.98L1580 37.35L1590 7.69L1600 14.92L1610 6.87L1620 9.55L1630 25.73L1640 23.25L1650 36.63L1660 27.09L1670 5.76L1680 32.72L1690 3.81L1700 27.06L1710 11.67L1720 26.98L1730 51.06L1740 18.55L1750 32.88L1760 59.11L1770 87.91L1780 66.47L1790 39.44L1800 42.68L1810 73.69L1820 36.63L1830 60.41L1840 70.38L1850 88.57L1860 58.79L1870 84.45L1880 89.09L1890 63.22L1900 26.84L1910 60.71L1920 23.99' stroke='rgba(207%2c 76%2c 84%2c 0.1)' stroke-width='2'%3e%3c/path%3e%3cpath d='M0 174.34L10 149.93L20 119.89L30 125.05L40 99.87L50 67.35L60 49.02L70 34.79L80 52.92L90 38.38L100 42.07L110 42.37L120 34.03L130 50.65L140 27.52L150 20.16L160 8.73L170 44.68L180 24.6L190 1.43L200 14.65L210 43.32L220 77.48L230 99.7L240 122.99L250 146.47L260 128.54L270 102.41L280 86.49L290 102.37L300 81.48L310 62.07L320 99.29L330 75.32L340 66.93L350 83.9L360 94.48L370 62.31L380 45.19L390 27.62L400 34.54L410 41.5L420 39.2L430 56.28L440 80.96L450 58.21L460 65.28L470 93.02L480 88.25L490 107.02L500 81.06L510 84.94L520 75.08L530 40.79L540 36.65L550 64.85L560 37.29L570 68.66L580 34.8L590 57.92L600 74.39L610 55.74L620 52.09L630 70.43L640 49.43L650 71.76L660 73.23L670 77.8L680 106.27L690 85.99L700 89.94L710 112.08L720 108.17L730 127.84L740 120.67L750 102.28L760 114.63L770 111.79L780 96.92L790 65.31L800 90.52L810 76.31L820 72.63L830 57.46L840 24.02L850 27.97L860 8.13L870 23.64L880 36.56L890 43.68L900 10.09L910 18.34L920 36.36L930 60.86L940 87.39L950 93.49L960 60.46L970 87.07L980 71.1L990 102.44L1000 107.22L1010 112.7L1020 83.31L1030 74.86L1040 73.89L1050 48.95L1060 69.19L1070 33.87L1080 52.24L1090 73.78L1100 55.45L1110 69.19L1120 64.06L1130 44.4L1140 9.34L1150 29.02L1160 26.59L1170 36.77L1180 4.52L1190 25.85L1200 39.02L1210 34.71L1220 19.43L1230 35.84L1240 49.16L1250 40.48L1260 8.93L1270 39.55L1280 58.26L1290 82.39L1300 88.4L1310 100.17L1320 106.31L1330 134.08L1340 124.71L1350 101.84L1360 119.43L1370 95.32L1380 74.16L1390 82.91L1400 77.55L1410 103.99L1420 118.62L1430 85.24L1440 100.32L1450 135.6L1460 100.46L1470 88.25L1480 55.22L1490 81.67L1500 86.48L1510 52.36L1520 70.19L1530 73.09L1540 74.23L1550 52.86L1560 85.95L1570 65.98L1580 37.35L1590 7.69L1600 14.92L1610 6.87L1620 9.55L1630 25.73L1640 23.25L1650 36.63L1660 27.09L1670 5.76L1680 32.72L1690 3.81L1700 27.06L1710 11.67L1720 26.98L1730 51.06L1740 18.55L1750 32.88L1760 59.11L1770 87.91L1780 66.47L1790 39.44L1800 42.68L1810 73.69L1820 36.63L1830 60.41L1840 70.38L1850 88.57L1860 58.79L1870 84.45L1880 89.09L1890 63.22L1900 26.84L1910 60.71L1920 23.99L1920 300L0 300z' fill='url(%23SvgjsLinearGradient1057)'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1056'%3e%3crect width='1920' height='300' fill='white'%3e%3c/rect%3e%3c/mask%3e%3clinearGradient x1='50%25' y1='0%25' x2='50%25' y2='100%25' id='SvgjsLinearGradient1057'%3e%3cstop stop-opacity='0.65' stop-color='rgba(207%2c 76%2c 84%2c 0.05)' offset='0'%3e%3c/stop%3e%3cstop stop-opacity='0' stop-color='rgba(255%2c 255%2c 255%2c 0.05)' offset='0.8'%3e%3c/stop%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e");
- background-size: 100%;
- background-position: bottom;
- background-repeat: no-repeat;
- }
-
- .voice-preview-text {
- font-size: 16px;
- }
-}
diff --git a/packages/power.stream/src/App.test.tsx b/packages/power.stream/src/App.test.tsx
deleted file mode 100644
index bfe47f2987..0000000000
--- a/packages/power.stream/src/App.test.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-//import { render, screen } from '@testing-library/react';
-import App from './App';
-
-test('renders learn react link', () => {
- // NB(2022-02-01): This broke when converting to a monorepo.
- // I hadn't been using the tests anyhow.
- //render( );
- //const linkElement = screen.getByText(/learn react/i);
- //expect(linkElement).toBeInTheDocument();
-});
diff --git a/packages/power.stream/src/App.tsx b/packages/power.stream/src/App.tsx
deleted file mode 100644
index cd2e73f1fb..0000000000
--- a/packages/power.stream/src/App.tsx
+++ /dev/null
@@ -1,243 +0,0 @@
-// import "bulma/css/bulma.css";
-import "./App.scss";
-
-import React from "react";
-import { BrowserRouter, Route, Switch } from "react-router-dom";
-import {
- ListTtsModels,
- TtsModelListItem,
-} from "@storyteller/components/src/api/tts/ListTtsModels";
-import StreamPage from "./pages/stream/StreamPage";
-import i18n from "i18next";
-import { ComingSoonPage } from "./pages/coming-soon/ComingSoonPage";
-import { LoginPage } from "./pages/login/LoginPage";
-import { SignupPage } from "./pages/signup/SignupPage";
-import { TtsConfigsIndexPage } from "./pages/tts_configs/TtsConfigsIndexPage";
-import {
- DetectLocale,
- DetectLocaleIsOk,
-} from "@storyteller/components/src/api/locale/DetectLocale";
-import { IndexPage } from "./pages/index/IndexPage";
-import { STORYTELLER_MERGED_TRANSLATIONS } from "./_i18n/StorytellerTranslations";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import { TopNav } from "./layout/TopNav";
-import { initReactI18next } from "react-i18next";
-import { TtsConfigsCreateRulePage } from "./pages/tts_configs/TtsConfigsCreateRulePage";
-import { TtsConfigsDeleteRulePage } from "./pages/tts_configs/TtsConfigsDeleteRulePage";
-import { TtsConfigsEditRulePage } from "./pages/tts_configs/TtsConfigsEditRulePage";
-import { TtsConfigsReorderPage } from "./pages/tts_configs/TtsConfigsReorderPage";
-import { ObsConfigsPage } from "./pages/obs_configs/ObsConfigsPage";
-import { FooterNav } from "./layout/FooterNav";
-
-i18n
- .use(initReactI18next) // passes i18n down to react-i18next
- .init({
- // the translations
- // (tip move them in a JSON file and import them,
- // or even better, manage them via a UI: https://react.i18next.com/guides/multiple-translation-files#manage-your-translations-with-a-management-gui)
- resources: STORYTELLER_MERGED_TRANSLATIONS,
- //lng: 'en', // if you're using a language detector, do not define the lng option
- fallbackLng: "en",
-
- interpolation: {
- escapeValue: false, // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
- },
- });
-
-interface Props {}
-
-interface State {
- sessionWrapper: SessionWrapper;
- allTtsModels: TtsModelListItem[];
- allTtsModelsByToken: Map;
-}
-
-// Root element is a non-functional component for easier global lifecycle management
-class App extends React.Component {
- constructor(props: Props) {
- super(props);
- this.state = {
- sessionWrapper: SessionWrapper.emptySession(),
- allTtsModels: [],
- allTtsModelsByToken: new Map(),
- };
- }
-
- async componentDidMount() {
- await this.querySession();
- await this.queryLanguage();
- await this.queryTtsModels();
- setInterval(async () => {
- await this.querySession();
- }, 60000);
- }
-
- querySession = async () => {
- const sessionWrapper = await SessionWrapper.lookupSession();
- this.setState({
- sessionWrapper: sessionWrapper,
- });
- };
-
- queryLanguage = async () => {
- let locale = await DetectLocale();
- if (DetectLocaleIsOk(locale)) {
- // TODO
- }
- };
-
- queryTtsModels = async () => {
- if (this.state.allTtsModels.length > 0) {
- return; // Already queried.
- }
- const models = await ListTtsModels();
- if (models) {
- let allTtsModelsByToken = new Map();
-
- models.forEach((item) => {
- allTtsModelsByToken.set(item.model_token, item);
- });
-
- //dynamicallyCategorizeModels(models);
- this.setState({
- allTtsModels: models,
- allTtsModelsByToken: allTtsModelsByToken,
- });
- //if (!maybeSelectedTtsModel && models.length > 0) {
- // let model = models[0];
- // const featuredModels = models.filter(m => m.is_front_page_featured);
- // if (featuredModels.length > 0) {
- // // Random featured model
- // model = featuredModels[
- // Math.floor(Math.random()*featuredModels.length)
- // ];
- // }
- // setMaybeSelectedTtsModel(model);
- //}
- }
- };
-
- public render() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/*
-
-
-
- © 2022 PowerStream.
-
- */}
-
-
-
-
-
- );
- }
-}
-
-export default App;
diff --git a/packages/power.stream/src/_i18n/StorytellerTranslations.ts b/packages/power.stream/src/_i18n/StorytellerTranslations.ts
deleted file mode 100644
index 044cd67185..0000000000
--- a/packages/power.stream/src/_i18n/StorytellerTranslations.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import { COMMON_TRANSLATIONS, MergeDeepDictionary } from '@storyteller/components/src/_i18n/CommonTranslations';
-
-// Use \u{00a0} = character literal
-
-const STORYTELLER_TRANSLATIONS : any = {
- // English: 46.6% Twitch (#1), 30+% FakeYou (#1)
- en: {
- translation: {
- commonUi: {
- },
- pages: {
- },
- },
- },
- // Spanish: 10.5% Twitch (#2), 20+% FakeYou (#2)
- es: {
- translation: {
- commonUi: {
- },
- pages: {
- },
- },
- },
-
- // ---------- OTHER LANGUAGES ----------
-
- // German: 6.5% Twitch (#4)
- de: {
- translation: {
- }
- },
- // French: 5.6% Twitch (#6)
- fr: {
- translation: {
- }
- },
- // Indonesian 2% FakeYou
- id: {
- translation: {
- }
- },
- // Japanese: 2.5% Twitch (#8)
- ja: {
- translation: {
- },
- },
- // Korean: 5.4% Twitch (#7)
- ko: {
- translation: {
- }
- },
- // Portuguese: 6.2% Twitch (#5)
- pt: {
- translation: {
- },
- },
- // 6.5% Twitch (#3)
- ru: {
- translation: {
- }
- },
- // Turkish: 4% FakeYou (#3)
- tr: {
- translation: {
- }
- },
-}
-
-// TODO: Type these as i18next dictionaries
-const STORYTELLER_MERGED_TRANSLATIONS : any = MergeDeepDictionary(STORYTELLER_TRANSLATIONS, COMMON_TRANSLATIONS);
-
-export { STORYTELLER_MERGED_TRANSLATIONS }
diff --git a/packages/power.stream/src/index.css b/packages/power.stream/src/index.css
deleted file mode 100644
index d942c42f27..0000000000
--- a/packages/power.stream/src/index.css
+++ /dev/null
@@ -1,13 +0,0 @@
-body {
- margin: 0;
- /*font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
- sans-serif;*/
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
- monospace;
-}
diff --git a/packages/power.stream/src/index.tsx b/packages/power.stream/src/index.tsx
deleted file mode 100644
index ef2edf8ea3..0000000000
--- a/packages/power.stream/src/index.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import './index.css';
-import App from './App';
-import reportWebVitals from './reportWebVitals';
-
-ReactDOM.render(
-
-
- ,
- document.getElementById('root')
-);
-
-// If you want to start measuring performance in your app, pass a function
-// to log results (for example: reportWebVitals(console.log))
-// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals();
diff --git a/packages/power.stream/src/layout/FooterNav.tsx b/packages/power.stream/src/layout/FooterNav.tsx
deleted file mode 100644
index a27cfd044e..0000000000
--- a/packages/power.stream/src/layout/FooterNav.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-import { GitSha } from "@storyteller/components/src/elements/GitSha";
-import { Link } from "react-router-dom";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import {
- faTwitch,
- faDiscord,
- faTwitter,
- faTiktok,
-} from "@fortawesome/free-brands-svg-icons";
-import { ThirdPartyLinks } from "@storyteller/components/src/constants/ThirdPartyLinks";
-
-interface Props {}
-
-function FooterNav(props: Props) {
- return (
-
-
-
- );
-}
-
-export { FooterNav };
diff --git a/packages/power.stream/src/layout/TopNav.tsx b/packages/power.stream/src/layout/TopNav.tsx
deleted file mode 100644
index b159543f77..0000000000
--- a/packages/power.stream/src/layout/TopNav.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import React, { useState, useEffect } from "react";
-
-interface Props {
- sessionWrapper: SessionWrapper;
- querySessionCallback: () => void;
-}
-
-function TopNav(props: Props) {
- const [scroll, setScroll] = useState(false);
- useEffect(() => {
- window.addEventListener("scroll", () => {
- setScroll(window.scrollY > 50);
- });
- }, []);
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
-
-export { TopNav };
diff --git a/packages/power.stream/src/layout/TopNavSessionButton.tsx b/packages/power.stream/src/layout/TopNavSessionButton.tsx
deleted file mode 100644
index 26872802ab..0000000000
--- a/packages/power.stream/src/layout/TopNavSessionButton.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import React from 'react';
-import { Link, useHistory } from 'react-router-dom';
-import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
-import { Logout } from '@storyteller/components/src/api/session/Logout';
-import { t } from 'i18next';
-import { Gravatar } from '@storyteller/components/src/elements/Gravatar';
-
-interface Props {
- sessionWrapper: SessionWrapper,
- enableAlpha: boolean,
- querySessionAction: () => void,
- closeHamburgerAction: () => void,
-}
-
-function TopNavSessionButton(props: Props) {
- let history = useHistory();
-
- if (!props.enableAlpha) {
- return
- }
-
- const logoutHandler = async () => {
- await Logout();
- props.querySessionAction();
- history.push('/');
- }
-
- let loggedIn = props.sessionWrapper.isLoggedIn();
- let displayName = props.sessionWrapper.getDisplayName();
- let gravatarHash = props.sessionWrapper.getEmailGravatarHash();
- let gravatar = ;
-
- if (displayName === undefined) {
- displayName = 'My Account';
- }
-
- if (gravatarHash !== undefined) {
- gravatar =
- }
-
- let sessionLink = ;
- let logoutLink = ;
-
- if (loggedIn) {
- let url = `https://fakeyou.com/profile/${displayName}`;
- sessionLink = (
- props.closeHamburgerAction()}
- className="button is-alert is-inverted is-pulled-right"
- > {gravatar} {displayName} (FakeYou)
- );
- logoutLink = {
- await logoutHandler();
- props.closeHamburgerAction();
- }}
- >{t('common.logout')} ;
- } else {
- sessionLink = (
- props.closeHamburgerAction()}
- >{t('common.signUpLogin')}
- );
- }
-
- return (
-
- {logoutLink}
- {sessionLink}
-
- );
-}
-
-export { TopNavSessionButton };
diff --git a/packages/power.stream/src/logo.svg b/packages/power.stream/src/logo.svg
deleted file mode 100644
index 9dfc1c058c..0000000000
--- a/packages/power.stream/src/logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/power.stream/src/pages/coming-soon/ComingSoonPage.tsx b/packages/power.stream/src/pages/coming-soon/ComingSoonPage.tsx
deleted file mode 100644
index 664ad64c51..0000000000
--- a/packages/power.stream/src/pages/coming-soon/ComingSoonPage.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import React from 'react';
-
-function ComingSoonPage() {
- return (
-
-
-
-
- Coming Soon
-
-
- We're almost done!
-
-
-
We'll be launching the following features shortly:
-
-
- Audience rewards
-
- Stream TTS
- Deepfake Emotes (TTS + Green Screen W2L)
-
-
- Voice and video
-
- Real time voice conversion (change your voice to someone else's)
- VoxelCam 3D volumetric camera (a webcam, but 3D, and directly injectable
- into games)
-
-
- Big Secret Project
-
-
- {/*Storyteller Engine
-
- This is a high quality cinematic game engine.
- You don't need to own an expensive computer. It can run in the cloud.
- Real time 3D motion capture of one or more streamers / actors.
- Think VTubing, but Star Wars quality.
- Dedicated control plane for camera, lighting, environmental control OPs.
- Embed rich content: games, browser panes, video feeds
- Let your audience control your world
-
- */}
-
-
- {/*
You'll be able to make films, Hollywood-quality streams, and so much more!
*/}
-
-
-
-
-
-
- )
-}
-
-export { ComingSoonPage }
\ No newline at end of file
diff --git a/packages/power.stream/src/pages/index/IndexPage.tsx b/packages/power.stream/src/pages/index/IndexPage.tsx
deleted file mode 100644
index a555f999b8..0000000000
--- a/packages/power.stream/src/pages/index/IndexPage.tsx
+++ /dev/null
@@ -1,434 +0,0 @@
-import React from "react";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import {
- faArrowRight,
- faHeadphones,
- faVolumeUp,
-} from "@fortawesome/free-solid-svg-icons";
-import { faDiscord, faTwitter } from "@fortawesome/free-brands-svg-icons";
-import { VoicePreviewPlayer } from "./VoicePreviewPlayer";
-import { wavesurferConfigs } from "./wsConfig";
-
-interface Props {
- sessionWrapper: SessionWrapper;
-}
-
-let wavesurfers = wavesurferConfigs.map((config) => {
- let ws = (
-
- );
- return ws;
-});
-
-function IndexPage(props: Props) {
- return (
-
-
-
-
-
- AI Power for
- Your Stream
-
-
- We use advanced AI to enhance your stream, making it easier for
- you to engage your audience and get paid for the work you love.
-
-
-
-
-
-
-
-
-
- {
-
-
-
- Tracer (Overwatch)
-
-
- “Look out world! Tracer's here.”
-
-
- }
-
-
-
- Sonic the Hedgehog
-
-
- "This is what speed looks like."
-
-
-
-
-
-
-
-
-
-
-
Features
-
-
-
- TTS Features
-
-
-
-
-
-
-
Channel Points
-
- Your viewers can use their hard-earned channel points to
- donate and have their messages read aloud. This adds a fun
- new way for your community to engage with your streams and
- show their support.
-
-
- Sign up now
-
-
-
-
-
-
-
-
Over 2000 voices!
-
- We're proud to offer over 2000 text-to-speech voices for you
- to choose from. Whether you're looking for something funny,
- serious, or somewhere in between, we've got you covered.
-
-
- See all the voices on FakeYou
-
-
-
-
-
-
-
-
Bits
-
- Your viewers can use Twitch Bits to donate and have their
- messages read aloud. This makes it easy for anyone to get
- involved and support your stream.
-
-
-
-
- Sign up now
-
-
-
-
-
-
-
-
-
-
-
-
-
-
TTS Voices
-
-
-
- Voice Previews
-
-
-
{wavesurfers}
-
-
- These are just a few samples of the voices available! Listen to
- 2000+ more usable voices on
- our TTS platform FakeYou.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
It's Obvious
-
-
-
- Better Streams Grow
-
-
- We empower streamers to make incredible content. Use the power
- of AI to do the hard work so you can focus on what matters.
-
-
-
-
-
2000+
-
AI models and features to enrich your stream.
-
-
40%
-
Revenue boost by engaging your audience.
-
-
1+
-
human centuries of content we’ve helped enrich.
-
-
-
-
-
-
-
-
-
-
-
-
-
- Questions and Answers
-
-
-
-
- Frequently Asked Questions
-
-
- FAQ
-
-
-
-
-
-
-
- PowerStream TTS?
-
-
-
-
- PowerStream TTS lets your audience inject AI TTS sound into
- your stream. You can engage with them directly and earn
- money from every use.
-
-
-
-
-
-
- PowerStream Autopilot?
-
-
-
-
- Coming soon. Use our advanced AI body double to run your
- stream while you’re away. It can fill in for bathroom
- breaks, or run 24/7, directly engaging your audience with
- custom-tailored content and your own unique personality.
-
-
-
-
-
-
- PowerStream Transformer?
-
-
-
-
- Coming soon. Your community can donate to change your face
- and voice to that of popular characters and celebrities.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Get started with Power Stream!
-
-
-
-
-
-
- );
-}
-
-export { IndexPage };
diff --git a/packages/power.stream/src/pages/index/VoicePreviewPlayer.tsx b/packages/power.stream/src/pages/index/VoicePreviewPlayer.tsx
deleted file mode 100644
index 068268ac03..0000000000
--- a/packages/power.stream/src/pages/index/VoicePreviewPlayer.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import React, { useState, useCallback } from "react";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import {
- faMicrophone,
- faPause,
- faPlay,
-} from "@fortawesome/free-solid-svg-icons";
-import Wavesurfer from "react-wavesurfer.js";
-
-interface Props {
- filename: string;
- title: string;
-}
-
-function VoicePreviewPlayer(props: Props) {
- const [isPlaying, setIsPlaying] = useState(false);
- const [position, setPosition] = useState(0);
-
- const handleTogglePlay = useCallback(() => {
- if (!isPlaying) {
- setIsPlaying(true);
- } else {
- setIsPlaying(false);
- }
- }, [isPlaying]);
-
- const handleFinish = useCallback(() => {
- setIsPlaying(false);
- setPosition(0);
- return [setIsPlaying, setPosition];
- }, []);
-
- let playButtonText = ;
-
- if (isPlaying) {
- playButtonText = (
- <>
-
- >
- );
- }
-
- return (
-
-
-
- handleTogglePlay()}
- >
- {playButtonText}
-
-
-
- {props.title}
-
-
-
-
-
-
-
- );
-}
-
-export { VoicePreviewPlayer };
diff --git a/packages/power.stream/src/pages/index/subpages/LoggedInIndex.tsx b/packages/power.stream/src/pages/index/subpages/LoggedInIndex.tsx
deleted file mode 100644
index f048e71c47..0000000000
--- a/packages/power.stream/src/pages/index/subpages/LoggedInIndex.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { Link } from 'react-router-dom';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faCogs, faVideo } from '@fortawesome/free-solid-svg-icons';
-import { faDiscord, faTwitch } from '@fortawesome/free-brands-svg-icons';
-import { DiscordLink } from '@storyteller/components/src/elements/DiscordLink';
-import { StorytellerUrlConfig } from '@storyteller/components/src/urls/StorytellerUrlConfig';
-import { CheckTwitchOauth, CheckTwitchOauthIsError, CheckTwitchOauthIsOk } from '@storyteller/components/src/api/storyteller/twitch_oauth/CheckTwitchOauth';
-
-function LoggedInIndex() {
- const oauthUrl = new StorytellerUrlConfig().twitchOauthEnrollRedirect();
-
- const [oauthTokenFound, setOauthTokenFound] = useState(false);
-
- const checkTwitchOauth = useCallback(async () => {
- const result = await CheckTwitchOauth();
-
- if (CheckTwitchOauthIsOk(result)) {
- setOauthTokenFound(result.oauth_token_found);
- } else if (CheckTwitchOauthIsError(result)) {
- }
- }, []);
-
- useEffect(() => {
- checkTwitchOauth();
- }, [checkTwitchOauth])
-
- let linkText = <>Link Your Twitch > ;
-
- if (oauthTokenFound) {
- linkText = <>Re-link Your Twitch > ;
- }
-
-
- return (
-
-
-
-
-
- {linkText}
-
-
-
-
-
- Configure TTS
-
-
-
-
-
- Configure OBS
-
-
-
-
-
-
-
-
-
Join us in Discord!
-
- We'd like to chat with you.
-
-
- so that we know
- what you're thinking.
-
-
-
-
-
-
- )
-}
-
-export { LoggedInIndex }
\ No newline at end of file
diff --git a/packages/power.stream/src/pages/index/subpages/LoggedOutIndex.tsx b/packages/power.stream/src/pages/index/subpages/LoggedOutIndex.tsx
deleted file mode 100644
index 329c20bdd9..0000000000
--- a/packages/power.stream/src/pages/index/subpages/LoggedOutIndex.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faRocket, faSignInAlt } from '@fortawesome/free-solid-svg-icons';
-
-function LoggedOutIndex() {
- return (
-
-
-
-
-
You'll need an account to continue…
- (If you have a FakeYou.com account, it'll work here too.)
-
-
- Sign Up
-
-
-
-
-
- Log In
-
-
-
-
-
-
-
- )
-}
-
-export { LoggedOutIndex }
\ No newline at end of file
diff --git a/packages/power.stream/src/pages/index/wsConfig.ts b/packages/power.stream/src/pages/index/wsConfig.ts
deleted file mode 100644
index 7fe936eea8..0000000000
--- a/packages/power.stream/src/pages/index/wsConfig.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-export let wavesurferConfigs = [
- {
- filename: "/assets/preview/tracer.wav",
- title: "Tracer",
- },
- {
- filename: "/assets/preview/son-goku.wav",
- title: "Son Goku",
- },
- {
- filename: "/assets/preview/stan-lee.wav",
- title: "Stan Lee",
- },
- {
- filename: "/assets/preview/monokuma.wav",
- title: "Monokuma",
- },
- // {
- // filename: "/assets/preview/monokuma.wav",
- // title: "Monokuma",
- // },
- // {
- // filename: "/assets/preview/monokuma.wav",
- // title: "Monokuma",
- // },
- // {
- // filename: "/assets/preview/monokuma.wav",
- // title: "Monokuma",
- // },
- // {
- // filename: "/assets/preview/monokuma.wav",
- // title: "Monokuma",
- // },
- // {
- // filename: "/assets/preview/monokuma.wav",
- // title: "Monokuma",
- // },
- // {
- // filename: "/assets/preview/monokuma.wav",
- // title: "Monokuma",
- // },
- // {
- // filename: "/assets/preview/monokuma.wav",
- // title: "Monokuma",
- // },
- // {
- // filename: "/assets/preview/monokuma.wav",
- // title: "Monokuma",
- // },
-];
diff --git a/packages/power.stream/src/pages/login/LoginPage.tsx b/packages/power.stream/src/pages/login/LoginPage.tsx
deleted file mode 100644
index 941dcf6956..0000000000
--- a/packages/power.stream/src/pages/login/LoginPage.tsx
+++ /dev/null
@@ -1,195 +0,0 @@
-import React, { useState } from "react";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faUser, faKey } from "@fortawesome/free-solid-svg-icons";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import { Link, useHistory } from "react-router-dom";
-import {
- iconPasswordField,
- iconUser,
-} from "@storyteller/components/src/icons/SemanticIcons";
-import {
- CreateSession,
- CreateSessionIsError,
- CreateSessionIsSuccess,
-} from "@storyteller/components/src/api/session/CreateSession";
-
-interface Props {
- sessionWrapper: SessionWrapper;
- querySessionAction: () => void;
-}
-
-function LoginPage(props: Props) {
- let history = useHistory();
-
- const [password, setPassword] = useState("");
- const [usernameOrEmail, setUsernameOrEmail] = useState("");
- const [errorMessage, setErrorMessage] = useState("");
-
- if (props.sessionWrapper.isLoggedIn()) {
- history.push("/");
- }
-
- const handleUsernameOrEmailChange = (
- ev: React.FormEvent
- ) => {
- ev.preventDefault();
- const usernameOrEmailValue = (ev.target as HTMLInputElement).value;
- setUsernameOrEmail(usernameOrEmailValue);
- setErrorMessage("");
- return false;
- };
-
- const handlePasswordChange = (ev: React.FormEvent) => {
- ev.preventDefault();
- const passwordValue = (ev.target as HTMLInputElement).value;
- setPassword(passwordValue);
- setErrorMessage("");
- return false;
- };
-
- const handleFormSubmit = async (
- ev: React.FormEvent
- ): Promise => {
- ev.preventDefault();
-
- const request = {
- username_or_email: usernameOrEmail,
- password: password,
- };
-
- const response = await CreateSession(request);
-
- if (CreateSessionIsError(response)) {
- setErrorMessage(response.error_message);
- } else if (CreateSessionIsSuccess(response)) {
- console.log("querying new session");
- props.querySessionAction();
- history.push("/");
- }
-
- return false;
- };
-
- let errorWarning = ;
- if (errorMessage) {
- errorWarning = (
-
- Login Error: {errorMessage}
-
- );
- }
-
- return (
-
-
-
-
Login
-
- (If you have a FakeYou.com account, it'll work here too.)
-
-
- {errorWarning}
-
-
-
-
-
-
-
- );
-}
-
-export { LoginPage };
diff --git a/packages/power.stream/src/pages/obs_configs/ObsConfigsPage.tsx b/packages/power.stream/src/pages/obs_configs/ObsConfigsPage.tsx
deleted file mode 100644
index 721bc9a30f..0000000000
--- a/packages/power.stream/src/pages/obs_configs/ObsConfigsPage.tsx
+++ /dev/null
@@ -1,151 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faDiscord, faTwitch } from '@fortawesome/free-brands-svg-icons';
-import { CheckTwitchOauth, CheckTwitchOauthIsError, CheckTwitchOauthIsOk } from '@storyteller/components/src/api/storyteller/twitch_oauth/CheckTwitchOauth';
-import { StorytellerUrlConfig } from '@storyteller/components/src/urls/StorytellerUrlConfig';
-import { faExternalLinkAlt, faPlus } from '@fortawesome/free-solid-svg-icons';
-
-interface Props {
- sessionWrapper: SessionWrapper,
-}
-
-function ObsConfigsPage(props: Props) {
- const [maybeTwitchUsername, setMaybeTwitchUsername] = useState("");
-
- const checkTwitchOauth = useCallback(async () => {
- const result = await CheckTwitchOauth();
-
- if (CheckTwitchOauthIsOk(result)) {
- setMaybeTwitchUsername(result.maybe_twitch_username || "");
- } else if (CheckTwitchOauthIsError(result)) {
- }
- }, []);
-
- useEffect(() => {
- checkTwitchOauth();
- }, [checkTwitchOauth])
-
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return Must Log In ;
- }
-
- const username = !!maybeTwitchUsername ? maybeTwitchUsername : 'YOUR_USERNAME';
-
- const obsUrl = new StorytellerUrlConfig().obsPageFortwitch(username);
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- OBS Setup
-
-
- Ready to Broadcast!
-
-
-
-
-
-
-
-
-
-
-
-
Your OBS Browser Source
-
-
Once you've linked Twitch with Storyteller, you can add the following page as an OBS Browser Source :
-
-
-
-
-
-
-
-
- Open In New Tab
-
-
-
-
-
-
-
Setup Instructions
-
-
If you're not familiar with OBS or you need a refresher, here's a quick glance at the setup.
-
-
-
-
Step 1) Click to add a new Browser Source
-
-
-
-
The bottom of OBS should have a plus icon ( ) to add new sources.
- You'll want to add a new Browser Source .
-
-
-
-
Step 2) Paste the URL into the configuration window
-
-
-
-
-
-
Remember, the URL is {obsUrl}
-
-
-
-
Step 3) Activate the Page
-
-
-
-
-
-
-
Right click the layer and select “Interact” .
- This will let you click the button and activate the service.
-
-
-
-
-
Good to go!
-
-
You can now hide the layer and make it invisible. The audio levels are also independently adjustable.
-
-
-
-
Have fun! We'll be back soon with some even more jaw-dropping utilities.
-
-
-
-
- )
-}
-
-export { ObsConfigsPage }
\ No newline at end of file
diff --git a/packages/power.stream/src/pages/signup/SignupPage.tsx b/packages/power.stream/src/pages/signup/SignupPage.tsx
deleted file mode 100644
index ec81752c69..0000000000
--- a/packages/power.stream/src/pages/signup/SignupPage.tsx
+++ /dev/null
@@ -1,331 +0,0 @@
-import React, { useState } from 'react';
-import { CreateAccount, CreateAccountIsError, CreateAccountIsSuccess } from '@storyteller/components/src/api/user/CreateAccount';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Link } from 'react-router-dom';
-import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
-import { iconEmailField, iconPasswordField, iconUser } from '@storyteller/components/src/icons/SemanticIcons';
-
-enum FieldTriState {
- EMPTY_FALSE,
- FALSE,
- TRUE,
-}
-
-interface Props {
- sessionWrapper: SessionWrapper,
- querySessionCallback : () => void,
-}
-
-function SignupPage(props: Props) {
-
- const [username, setUsername] = useState('');
- const [usernameValid, setUsernameValid] = useState(FieldTriState.EMPTY_FALSE);
- const [usernameInvalidReason, setUsernameInvalidReason] = useState('');
-
- const [email, setEmail] = useState('');
- const [emailValid, setEmailValid] = useState(FieldTriState.EMPTY_FALSE);
- const [emailInvalidReason, setEmailInvalidReason] = useState('');
-
- const [password, setPassword] = useState('');
- const [passwordValid, setPasswordValid] = useState(FieldTriState.EMPTY_FALSE);
- const [passwordInvalidReason, setPasswordInvalidReason] = useState('');
-
- const [passwordConfirmation, setPasswordConfirmation] = useState('');
- const [passwordConfirmationValid, setPasswordConfirmationValid] = useState(FieldTriState.EMPTY_FALSE);
- const [passwordConfirmationInvalidReason, setPasswordConfirmationInvalidReason] = useState('');
-
- const handleUsernameChange = (ev: React.FormEvent) => {
- ev.preventDefault();
-
- const newUsername = (ev.target as HTMLInputElement).value;
-
- let usernameValid = FieldTriState.EMPTY_FALSE;
- let usernameInvalidReason = "";
-
- if (newUsername.length > 1) {
- if (newUsername.length < 3) {
- usernameValid = FieldTriState.FALSE;
- usernameInvalidReason = "username is too short";
- } else if (newUsername.length > 15) {
- usernameValid = FieldTriState.FALSE;
- usernameInvalidReason = "username is too long";
- } else {
- usernameValid = FieldTriState.TRUE;
- }
- }
-
- setUsername(newUsername);
- setUsernameValid(usernameValid);
- setUsernameInvalidReason(usernameInvalidReason);
-
- return false;
- }
-
- const handleEmailChange = (ev: React.FormEvent) => {
- ev.preventDefault();
-
- const newEmail = (ev.target as HTMLInputElement).value;
-
- let emailValid = FieldTriState.EMPTY_FALSE;
- let emailInvalidReason = "";
-
- if (newEmail.length > 1) {
- if (newEmail.length < 3) {
- emailValid = FieldTriState.FALSE;
- emailInvalidReason = "email is too short";
- } else if (!newEmail.includes("@")) {
- emailValid = FieldTriState.FALSE;
- emailInvalidReason = "email is invalid";
- } else {
- emailValid = FieldTriState.TRUE;
- }
- }
-
- setEmail(newEmail);
- setEmailValid(emailValid);
- setEmailInvalidReason(emailInvalidReason);
-
- return false;
- }
-
- const handlePasswordChange = (ev: React.FormEvent) => {
- ev.preventDefault();
-
- const newPassword = (ev.target as HTMLInputElement).value;
-
- let passwordValid = FieldTriState.EMPTY_FALSE;
- let passwordInvalidReason = "";
- let passwordConfirmationValid = FieldTriState.EMPTY_FALSE;
- let passwordConfirmationInvalidReason = "";
-
- if (newPassword.length > 1) {
- if (newPassword.length < 5) {
- passwordValid = FieldTriState.FALSE;
- passwordInvalidReason = "password is too short";
- } else {
- passwordValid = FieldTriState.TRUE;
- }
-
- if (newPassword !== passwordConfirmation) {
- passwordConfirmationValid = FieldTriState.FALSE;
- passwordConfirmationInvalidReason = "passwords do not match";
- } else {
- passwordConfirmationValid = FieldTriState.TRUE;
- passwordConfirmationInvalidReason = "";
-
- }
- }
-
- setPassword(newPassword);
- setPasswordValid(passwordValid);
- setPasswordInvalidReason(passwordInvalidReason);
- setPasswordConfirmationValid(passwordConfirmationValid);
- setPasswordConfirmationInvalidReason(passwordConfirmationInvalidReason)
-
- return false;
- }
-
- const handlePasswordConfirmationChange = (ev: React.FormEvent) => {
- ev.preventDefault();
-
- const newPasswordConfirmation = (ev.target as HTMLInputElement).value;
-
- let passwordConfirmationValid = FieldTriState.EMPTY_FALSE;
- let passwordConfirmationInvalidReason = "";
-
- if (newPasswordConfirmation.length > 1) {
-
- if (newPasswordConfirmation !== password) {
- passwordConfirmationValid = FieldTriState.FALSE;
- passwordConfirmationInvalidReason = "passwords do not match";
- } else {
- passwordConfirmationValid = FieldTriState.TRUE;
- passwordConfirmationInvalidReason = "";
-
- }
- }
-
- setPasswordConfirmation(newPasswordConfirmation);
- setPasswordConfirmationValid(passwordConfirmationValid);
- setPasswordConfirmationInvalidReason(passwordConfirmationInvalidReason);
-
- return false;
- }
-
- const handleFormSubmit = async (ev: React.FormEvent) : Promise => {
- ev.preventDefault();
-
- if (!usernameValid ||
- !emailValid ||
- !passwordValid ||
- !passwordConfirmationValid) {
- return false;
- }
-
- const request = {
- username: username,
- email_address: email,
- password: password,
- password_confirmation: passwordConfirmation,
- };
-
- const response = await CreateAccount(request);
-
- if (CreateAccountIsError(response)) {
- if ('email_address' in response.error_fields) {
- setEmailValid(FieldTriState.FALSE);
- setEmailInvalidReason(response.error_fields['email_address'] || "");
- }
- if ('username' in response.error_fields) {
- setUsernameValid(FieldTriState.FALSE);
- setUsernameInvalidReason(response.error_fields['username'] || "");
- }
- } else if (CreateAccountIsSuccess(response)) {
- console.log('querying new session');
- props.querySessionCallback();
-
- // TODO: Switch to functional component.
- window.location.href = '/';
- }
-
- return false;
- }
-
-
- if (props.sessionWrapper.isLoggedIn()) {
- return Invalid view for logged in users.
;
- }
-
- let usernameInputClass = "input";
- let usernameHelpClass = "help";
- switch (usernameValid) {
- case FieldTriState.EMPTY_FALSE:
- break;
- case FieldTriState.FALSE:
- usernameInputClass += " is-danger";
- usernameHelpClass += " is-danger";
- break;
- case FieldTriState.TRUE:
- usernameInputClass += " is-success";
- usernameHelpClass += " is-success";
- break;
- }
-
- let emailInputClass = "input";
- let emailHelpClass = "help";
- switch (emailValid) {
- case FieldTriState.EMPTY_FALSE:
- break;
- case FieldTriState.FALSE:
- emailInputClass += " is-danger";
- emailHelpClass += " is-danger";
- break;
- case FieldTriState.TRUE:
- emailInputClass += " is-success";
- emailHelpClass += " is-success";
- break;
- }
-
- let passwordInputClass = "input";
- let passwordHelpClass = "help";
- switch (passwordValid) {
- case FieldTriState.EMPTY_FALSE:
- break;
- case FieldTriState.FALSE:
- passwordInputClass += " is-danger";
- passwordHelpClass += " is-danger";
- break;
- case FieldTriState.TRUE:
- passwordInputClass += " is-success";
- passwordHelpClass += " is-success";
- break;
- }
-
- let passwordConfirmationInputClass = "input";
- let passwordConfirmationHelpClass = "help";
- switch (passwordConfirmationValid) {
- case FieldTriState.EMPTY_FALSE:
- break;
- case FieldTriState.FALSE:
- passwordConfirmationInputClass += " is-danger";
- passwordConfirmationHelpClass += " is-danger";
- break;
- case FieldTriState.TRUE:
- passwordConfirmationInputClass += " is-success";
- passwordConfirmationHelpClass += " is-success";
- break;
- }
-
- return (
-
-
Sign Up
-
-
-
-
-
-
Already have an account? Log in instead!
-
-
- )
-}
-
-export { SignupPage };
\ No newline at end of file
diff --git a/packages/power.stream/src/pages/stream/StreamPage.tsx b/packages/power.stream/src/pages/stream/StreamPage.tsx
deleted file mode 100644
index cd52946b0d..0000000000
--- a/packages/power.stream/src/pages/stream/StreamPage.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import { faTwitch } from '@fortawesome/free-brands-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import React, { useState } from 'react';
-import { useHistory, withRouter } from 'react-router-dom';
-
-function StreamPage() {
- const history = useHistory();
-
- const [twitchUsername, setTwitchUsername] = useState('')
-
- const handleTwitchUsernameChange = (ev: React.FormEvent) => {
- ev.preventDefault();
- const value = (ev.target as HTMLInputElement).value;
- setTwitchUsername(value);
- return false;
- };
-
- const openObs = (ev: React.FormEvent) => {
- ev.preventDefault();
- let url = `/obs/${twitchUsername}`;
- history.push(url);
- return false;
- };
-
- const buttonDisabled = twitchUsername.trim().length === 0;
-
- return (
-
-
-
-
- Stream TTS
-
-
- Early Alpha Preview
-
-
-
This is an early demo of our Stream TTS, powered by FakeYou.
- It does not currently offer customization, though our plans are to
- allow for a high degree of configurability: set your own voices, rewards, etc.
-
-
You don't need to set up any software on your end. Simply point OBS or your
- broadcast software the URL below:
-
-
-
-
- 1. Connect with Twitch
-
-
-
-
-
-
-
-
-
-
-
- 2. Open OBS Page
-
-
-
-
-
- )
-}
-
-export default withRouter(StreamPage);
\ No newline at end of file
diff --git a/packages/power.stream/src/pages/tts_configs/TtsConfigsCreateRulePage.tsx b/packages/power.stream/src/pages/tts_configs/TtsConfigsCreateRulePage.tsx
deleted file mode 100644
index 0adc6bee8c..0000000000
--- a/packages/power.stream/src/pages/tts_configs/TtsConfigsCreateRulePage.tsx
+++ /dev/null
@@ -1,167 +0,0 @@
-import React, { useState } from 'react';
-import { v4 as uuidv4 } from 'uuid';
-import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
-import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
-import { TwitchEventRule } from '@storyteller/components/src/api/storyteller/twitch_event_rules/GetTwitchEventRule';
-import { Link, useHistory, useParams } from 'react-router-dom';
-import { TwitchEventCategory, TWITCH_EVENT_CATEGORY_BY_STRING } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/TwitchEventCategory';
-import { EventResponse } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventResponse';
-import { EventMatchPredicate } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventMatchPredicate';
-import { CreateTwitchEventRule, CreateTwitchEventRuleRequest } from '@storyteller/components/src/api/storyteller/twitch_event_rules/CreateTwitchEventRule';
-import { EventMatchPredicateBuilderComponent } from './event_match_predicate_builder/EventMatchPredicateBuilderComponent';
-import { EventResponseComponent } from './event_response_builder/EventResponseComponent';
-import { TwitchEventRuleElement } from './rule_cards/TwitchEventRuleElement';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faAngleLeft, faSave } from '@fortawesome/free-solid-svg-icons';
-
-interface Props {
- sessionWrapper: SessionWrapper,
- allTtsModels: TtsModelListItem[],
- allTtsModelsByToken: Map,
-}
-
-function TtsConfigsCreateRulePage(props: Props) {
- const { event_category } = useParams() as { event_category : string };
-
- const history = useHistory();
-
- // TODO: Use centralized configs
- const indexLink = '/tts_configs';
-
- // ========== In-Progress Model Edits ==========
-
- // Sent to sub-components to initialize UI
- // NB: This is a HACK. The subcomponent views were originally created for the "Edit" UI
- // and this had to be done to get them to work for "Create" UI. Sending empty dictionaries
- // or statically initialized values froze the UI.
- const [serverEventMatchPredicate] = useState({});
- const [serverEventResponse] = useState({});
-
- const [modifiedEventMatchPredicate, setModifiedEventMatchPredicate] = useState({});
- const [modifiedEventResponse, setModifiedEventResponse] = useState({});
- const [ruleIsDisabled] = useState(false);
-
- let maybeTwitchEventCategory = TWITCH_EVENT_CATEGORY_BY_STRING.get(event_category);
-
- if (maybeTwitchEventCategory === undefined) {
- history.push(indexLink);
- return <>>;
- }
-
- // NB: To satisfy the type system and not be a union on 'undefined'
- const twitchEventCategory = maybeTwitchEventCategory;
-
- const updateModifiedEventMatchPredicate = (predicate: EventMatchPredicate) => {
- setModifiedEventMatchPredicate(predicate);
- }
-
- const updateModifiedEventResponse = (response: EventResponse) => {
- setModifiedEventResponse(response);
- }
-
- const handleFormSubmit = async (ev: React.FormEvent) : Promise => {
- ev.preventDefault();
-
- // TODO: Check for errors.
-
- let newEventMatchPredicate = modifiedEventMatchPredicate;
- let newEventResponse = modifiedEventResponse;
-
- // TODO: UUID idempotency token that updates on every model change.
-
- const request : CreateTwitchEventRuleRequest = {
- idempotency_token: uuidv4(),
- event_category: twitchEventCategory,
- event_match_predicate: newEventMatchPredicate,
- event_response: newEventResponse,
- rule_is_disabled: ruleIsDisabled,
- user_specified_rule_order: 1000,
- };
-
- const result = await CreateTwitchEventRule(request);
- if (result.success) {
- history.push(indexLink);
- }
-
- return false;
- }
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return Must Log In ;
- }
-
- // NB: This is a hypothetical version of what we'll update to
- let renderRule : TwitchEventRule = {
- // Not yet saved, so fake values
- token: 'virtual',
- user_specified_rule_order: 1000,
- created_at: new Date(),
- updated_at: new Date(),
-
- // Updated in UI
- event_category: twitchEventCategory,
- event_match_predicate: modifiedEventMatchPredicate,
- event_response: modifiedEventResponse,
- rule_is_disabled: ruleIsDisabled,
- };
-
- const titlesByCategory = new Map([
- [TwitchEventCategory.Bits, 'Create New Bits / Cheers Rule'],
- [TwitchEventCategory.ChannelPoints, 'Create New Channel Points Rule'],
- ]);
-
- const pageTitle = titlesByCategory.get(twitchEventCategory);
-
- return (
- <>
-
-
{pageTitle}
-
-
-
-
-
-
-
-
-
-
- Cancel / Go Back
-
-
- >
- )
-}
-
-export { TtsConfigsCreateRulePage }
\ No newline at end of file
diff --git a/packages/power.stream/src/pages/tts_configs/TtsConfigsDeleteRulePage.tsx b/packages/power.stream/src/pages/tts_configs/TtsConfigsDeleteRulePage.tsx
deleted file mode 100644
index 6e6bdbe1e5..0000000000
--- a/packages/power.stream/src/pages/tts_configs/TtsConfigsDeleteRulePage.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
-import { GetTwitchEventRule, GetTwitchEventRuleIsError, GetTwitchEventRuleIsOk, TwitchEventRule } from '@storyteller/components/src/api/storyteller/twitch_event_rules/GetTwitchEventRule';
-import { DeleteTwitchEventRule } from '@storyteller/components/src/api/storyteller/twitch_event_rules/DeleteTwitchEventRule';
-import { TwitchEventRuleElement } from './rule_cards/TwitchEventRuleElement';
-import { Link, useHistory, useParams } from 'react-router-dom';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faAngleLeft, faTrash } from '@fortawesome/free-solid-svg-icons';
-import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
-
-interface Props {
- sessionWrapper: SessionWrapper,
- allTtsModelsByToken: Map,
-}
-
-function TtsConfigsDeleteRulePage(props: Props) {
- const { token } : { token : string } = useParams();
-
- const history = useHistory();
-
- const [twitchEventRule, setTwitchEventRule] = useState(undefined);
-
- const getTwitchEventRule = useCallback(async (token: string) => {
- const response = await GetTwitchEventRule(token);
-
- if (GetTwitchEventRuleIsOk(response)) {
- setTwitchEventRule(response.twitch_event_rule);
- } else if (GetTwitchEventRuleIsError(response)) {
- // TODO
- }
- }, []);
-
- useEffect(() => {
- getTwitchEventRule(token);
- }, [getTwitchEventRule, token]);
-
- const indexLink = '/tts_configs';
-
- const handleDeleteFormSubmit = async (ev: React.FormEvent) : Promise => {
- ev.preventDefault();
-
- const result = await DeleteTwitchEventRule(token);
- if (result.success) {
- history.push(indexLink);
- }
-
- return false;
- }
-
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return Must Log In ;
- }
-
- if (twitchEventRule === undefined) {
- return Loading... ;
- }
-
- return (
- <>
-
-
Delete Rule ?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Cancel / Go Back
-
-
- >
- )
-}
-
-export { TtsConfigsDeleteRulePage }
\ No newline at end of file
diff --git a/packages/power.stream/src/pages/tts_configs/TtsConfigsEditRulePage.tsx b/packages/power.stream/src/pages/tts_configs/TtsConfigsEditRulePage.tsx
deleted file mode 100644
index d880de1ff0..0000000000
--- a/packages/power.stream/src/pages/tts_configs/TtsConfigsEditRulePage.tsx
+++ /dev/null
@@ -1,171 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
-import { GetTwitchEventRule, GetTwitchEventRuleIsError, GetTwitchEventRuleIsOk, TwitchEventRule } from '@storyteller/components/src/api/storyteller/twitch_event_rules/GetTwitchEventRule';
-import { EditTwitchEventRule, EditTwitchEventRuleRequest } from '@storyteller/components/src/api/storyteller/twitch_event_rules/EditTwitchEventRule';
-import { TwitchEventRuleElement } from './rule_cards/TwitchEventRuleElement';
-import { Link, useHistory, useParams } from 'react-router-dom';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faAngleLeft, faSave } from '@fortawesome/free-solid-svg-icons';
-import { EventMatchPredicate } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventMatchPredicate';
-import { EventResponse } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventResponse';
-import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
-import { EventResponseComponent } from './event_response_builder/EventResponseComponent';
-import { EventMatchPredicateBuilderComponent } from './event_match_predicate_builder/EventMatchPredicateBuilderComponent';
-
-interface Props {
- sessionWrapper: SessionWrapper,
- allTtsModels: TtsModelListItem[],
- allTtsModelsByToken: Map,
-}
-
-function TtsConfigsEditRulePage(props: Props) {
- const { token } : { token : string } = useParams();
-
- // TODO: Use centralized URL configs
- const indexLink = '/tts_configs';
-
- const history = useHistory();
-
- // ========== Initial Load of Server State ==========
-
- const [twitchEventRule, setTwitchEventRule] = useState(undefined);
-
- // Sent to sub-components to initialize UI
- const [serverEventMatchPredicate, setServerEventMatchPredicate] = useState({});
- const [serverEventResponse, setServerEventResponse] = useState({});
-
- // ========== In-Progress Model Edits ==========
-
- const [modifiedEventMatchPredicate, setModifiedEventMatchPredicate] = useState({});
- const [modifiedEventResponse, setModifiedEventResponse] = useState({});
- const [ruleIsDisabled, setRuleIsDisabled] = useState(false);
-
- const getTwitchEventRule = useCallback(async (token: string) => {
- const response = await GetTwitchEventRule(token);
-
- if (GetTwitchEventRuleIsOk(response)) {
- // Source of truth
- setTwitchEventRule(response.twitch_event_rule);
- setServerEventMatchPredicate(response.twitch_event_rule.event_match_predicate);
- setServerEventResponse(response.twitch_event_rule.event_response);
-
- // In-progress modifications
- setModifiedEventMatchPredicate(response.twitch_event_rule.event_match_predicate);
- setModifiedEventResponse(response.twitch_event_rule.event_response);
- setRuleIsDisabled(response.twitch_event_rule.rule_is_disabled);
-
- } else if (GetTwitchEventRuleIsError(response)) {
- // TODO
- }
- }, []);
-
- useEffect(() => {
- getTwitchEventRule(token);
- }, [getTwitchEventRule, token]);
-
- const updateModifiedEventMatchPredicate = (predicate: EventMatchPredicate) => {
- setModifiedEventMatchPredicate(predicate);
- }
-
- const updateModifiedEventResponse = (response: EventResponse) => {
- setModifiedEventResponse(response);
- }
-
- const handleFormSubmit = async (ev: React.FormEvent) : Promise => {
- ev.preventDefault();
-
- // TODO: Check for errors.
-
- let newEventMatchPredicate = modifiedEventMatchPredicate;
- let newEventResponse = modifiedEventResponse;
-
- const request : EditTwitchEventRuleRequest = {
- event_match_predicate: newEventMatchPredicate,
- event_response: newEventResponse,
- rule_is_disabled: ruleIsDisabled,
- };
-
- const result = await EditTwitchEventRule(token, request);
- if (result.success) {
- history.push(indexLink);
- }
-
- return false;
- }
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return Must Log In ;
- }
-
- if (twitchEventRule === undefined) {
- return Loading... ;
- }
-
- // NB: This is a hypothetical version of what we'll update to
- let renderRule : TwitchEventRule = {
- // Unchanged
- token: twitchEventRule.token,
- event_category: twitchEventRule.event_category,
- user_specified_rule_order: twitchEventRule.user_specified_rule_order,
- created_at: twitchEventRule.created_at,
- updated_at: twitchEventRule.updated_at,
-
- // Updated in UI
- event_match_predicate: modifiedEventMatchPredicate,
- event_response: modifiedEventResponse,
- rule_is_disabled: ruleIsDisabled,
- };
-
- return (
- <>
-
-
Edit Rule
-
-
-
-
-
-
-
-
-
-
- Cancel / Go Back
-
-
- >
- )
-}
-
-export { TtsConfigsEditRulePage }
\ No newline at end of file
diff --git a/packages/power.stream/src/pages/tts_configs/TtsConfigsIndexPage.tsx b/packages/power.stream/src/pages/tts_configs/TtsConfigsIndexPage.tsx
deleted file mode 100644
index c68c83137e..0000000000
--- a/packages/power.stream/src/pages/tts_configs/TtsConfigsIndexPage.tsx
+++ /dev/null
@@ -1,270 +0,0 @@
-import React, { useCallback, useEffect, useState } from "react";
-import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
-import {
- ListTwitchEventRules,
- ListTwitchEventRulesIsError,
- ListTwitchEventRulesIsOk,
- TwitchEventRule,
-} from "@storyteller/components/src/api/storyteller/twitch_event_rules/ListTwitchEventRules";
-import { TwitchEventRuleElement } from "./rule_cards/TwitchEventRuleElement";
-import { TwitchEventCategory } from "@storyteller/components/src/api/storyteller/twitch_event_rules/shared/TwitchEventCategory";
-import { DiscordLink } from "@storyteller/components/src/elements/DiscordLink";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import {
- faBox,
- faDonate,
- faFilm,
- faGem,
- faHeart,
- faLightbulb,
- faMeteor,
- faPlus,
- faSort,
- faTerminal,
- faUsers,
-} from "@fortawesome/free-solid-svg-icons";
-import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
-import { Link } from "react-router-dom";
-
-interface Props {
- sessionWrapper: SessionWrapper;
- allTtsModelsByToken: Map;
-}
-
-function TtsConfigsIndexPage(props: Props) {
- const [twitchEventRules, setTwitchEventRules] = useState(
- []
- );
-
- const listTwitchEventRules = useCallback(async () => {
- const response = await ListTwitchEventRules();
-
- if (ListTwitchEventRulesIsOk(response)) {
- setTwitchEventRules(response.twitch_event_rules);
- } else if (ListTwitchEventRulesIsError(response)) {
- // TODO
- }
- }, []);
-
- useEffect(() => {
- listTwitchEventRules();
- }, [listTwitchEventRules]);
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return Must Log In ;
- }
-
- const cheerEventRules = twitchEventRules.filter(
- (rule) => rule.event_category === TwitchEventCategory.Bits
- );
-
- const channelPointsEventRules = twitchEventRules.filter(
- (rule) => rule.event_category === TwitchEventCategory.ChannelPoints
- );
-
- return (
- <>
-
-
- Stream Setup
-
-
Configure TTS (and soon much more!)
-
- You can create rules for matching different categories of events:
- cheers (bits), channel point rewards, etc. When an event occurs in
- your stream, we compare it against the rules in its category. The
- first matched rule (from the top) is selected, and the action that it
- specifies is taken. For now this is limited to FakeYou text to speech,
- but we'll be adding lots of new capabilities soon.
-
-
-
-
-
- Bits / Cheers
-
-
- These settings are best for small and medium-sized channels.
-
-
-
-
- Create New Bits / Cheer Rule
-
-
-
- Reorder Rules
-
-
- {cheerEventRules.map((rule) => {
- return (
-
- );
- })}
-
-
-
-
-
- Create New Bits / Cheer Rule
-
-
-
-
-
-
- Reorder Rules
-
-
-
-
-
-
-
- {" "}
- Channel Points / Rewards{" "}
-
-
- These settings are best for small channels.
-
- {channelPointsEventRules.map((rule) => {
- return (
-
- );
- })}
-
-
-
-
- Create Channel Points Rule
-
-
-
-
-
-
- Reorder Rules
-
-
-
-
-
-
-
- {" "}
- Coming Soon…
-
-
-
-
-
-
- {" "}
- Subs, Resubs, and Gifted Subs{" "}
-
-
- These settings are great for all channel sizes.
-
-
-
-
-
-
- {" "}
- Donation Services (Streamlabs,
- StreamElements, etc.){" "}
-
-
- These settings are great for large channels, but work for channels
- of all sizes.
-
-
-
-
-
-
-
- {" "}
- Text /slash{" "}
- Commands
-
-
- These settings are great for very small channels.
-
-
-
-
-
-
-
- {" "}
- User-generated Animated Deepfake
- Rewards{" "}
-
-
- A lip-synced, green screened “Famous Person” {" "}
- pops up and says something.
-
-
-
-
-
-
-
- {" "}
- Multi Voice and Sound Effects{" "}
-
-
- User-selected voices can be strung together in a single reward,
- interspersed with sound effects.
-
-
-
-
-
-
-
- {" "}
- Suggestions?{" "}
-
-
Let us know what to build!
-
- Can you think of anything we haven't provided?{" "}
- {" "}
- so that we can build it!
-
-
-
-
-
- >
- );
-}
-
-export { TtsConfigsIndexPage };
diff --git a/packages/power.stream/src/pages/tts_configs/TtsConfigsReorderPage.tsx b/packages/power.stream/src/pages/tts_configs/TtsConfigsReorderPage.tsx
deleted file mode 100644
index 3417747217..0000000000
--- a/packages/power.stream/src/pages/tts_configs/TtsConfigsReorderPage.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
-import { ListTwitchEventRules, ListTwitchEventRulesIsError, ListTwitchEventRulesIsOk, TwitchEventRule } from '@storyteller/components/src/api/storyteller/twitch_event_rules/ListTwitchEventRules';
-import { ReorderTwitchEventRules, ReorderTwitchEventRulesRequest } from '@storyteller/components/src/api/storyteller/twitch_event_rules/ReorderTwitchEventRules';
-import { TwitchEventCategory, TWITCH_EVENT_CATEGORY_BY_STRING } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/TwitchEventCategory';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faSave } from '@fortawesome/free-solid-svg-icons';
-import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
-import { useHistory, useParams } from 'react-router-dom';
-import { ReorderableTwitchEventRuleElement } from './rule_cards/ReorderableTwitchEventRuleElement';
-
-interface Props {
- sessionWrapper: SessionWrapper,
- allTtsModelsByToken: Map,
-}
-
-function TtsConfigsReorderPage(props: Props) {
- const { event_category } = useParams() as { event_category : string };
-
- const history = useHistory();
-
- // TODO: Use centralized configs
- const indexLink = '/tts_configs';
-
- const [twitchEventRules, setTwitchEventRules] = useState([]);
-
- const listTwitchEventRules = useCallback(async (twitchEventCategory: TwitchEventCategory) => {
- const response = await ListTwitchEventRules();
-
- if (ListTwitchEventRulesIsOk(response)) {
- const allRules = response.twitch_event_rules;
- const releventRules = allRules.filter(rule => rule.event_category === twitchEventCategory);
-
- setTwitchEventRules(releventRules);
-
- } else if (ListTwitchEventRulesIsError(response)) {
- // TODO
- }
- }, []);
-
- useEffect(() => {
- console.log('useEffect');
- let maybeTwitchEventCategory = TWITCH_EVENT_CATEGORY_BY_STRING.get(event_category);
-
- if (maybeTwitchEventCategory === undefined) {
- history.push(indexLink);
- } else {
- listTwitchEventRules(maybeTwitchEventCategory);
- }
-
- }, [listTwitchEventRules, event_category, history]);
-
- const handleMoveUp = (ruleIndex: number) => {
- let newEventRules = [...twitchEventRules];
-
- if (ruleIndex >= newEventRules.length || // Walk off end
- ruleIndex === 0) { // Can't move up.
- return false;
- }
-
- let aboveIndex = ruleIndex - 1;
- let aboveRule = newEventRules[aboveIndex];
-
- newEventRules[aboveIndex] = newEventRules[ruleIndex];
- newEventRules[ruleIndex] = aboveRule;
-
- setTwitchEventRules(newEventRules);
- }
-
- const handleMoveDown = (ruleIndex: number) => {
- let newEventRules = [...twitchEventRules];
-
- if (ruleIndex < 0 || // Invalid
- ruleIndex >= newEventRules.length - 1) { // Can't move down.
- return false;
- }
-
- let belowIndex = ruleIndex + 1;
-
- console.log(ruleIndex, belowIndex);
-
- let belowRule = newEventRules[belowIndex];
-
- newEventRules[belowIndex] = newEventRules[ruleIndex];
- newEventRules[ruleIndex] = belowRule;
-
- setTwitchEventRules(newEventRules);
- }
-
- const handleSaveOrderings = async (ev: React.FormEvent) : Promise => {
-
- const pairs = twitchEventRules.map((rule, index) => {
- return {
- rule_token: rule.token,
- position: index,
- }
- })
-
- const request : ReorderTwitchEventRulesRequest = {
- rule_token_position_pairs: pairs,
- };
-
- const result = await ReorderTwitchEventRules(request);
- if (result.success) {
- history.push(indexLink);
- }
-
- return false;
- }
-
- if (!props.sessionWrapper.isLoggedIn()) {
- return Must Log In ;
- }
-
- return (
- <>
-
-
Reorder Rules
- This affects match priority. Rules at the top come first.
-
-
-
-
-
- You can create rules for matching different categories of events : cheers (bits),
- channel point rewards, etc.
-
- When an event occurs in your stream, we compare it against the rules in its
- category. The first matched rule (from the top) is selected, and the action
- that it specifies is taken. For now this is limited to FakeYou text to speech, but we'll be
- adding lots of new capabilities soon.
-
-
-
- {twitchEventRules.map((rule, index) => {
- return
- })}
-
-
-
- Save Orderings
-
- >
- )
-}
-
-export { TtsConfigsReorderPage }
\ No newline at end of file
diff --git a/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/CheerState.ts b/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/CheerState.ts
deleted file mode 100644
index 05f5ee932b..0000000000
--- a/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/CheerState.ts
+++ /dev/null
@@ -1,247 +0,0 @@
-import { EventMatchPredicate } from "@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventMatchPredicate";
-import { CHEER_LOOKUP_MAP, CHEER_PREFIX_TO_STRING_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,
- bits?: number,
-}
-
-export interface CheerStateCustom {
- // NB: This can contain a numeric suffix independent of bits.
- // eg. third party cheers from 7tv.app: Sus11, Sus12, 1984, 4Weird.
- cheerFull?: string,
- bits?: number,
-}
-
-// Due to the complexity of the forms, this is used as the source of truth while editing.
-// Each type of predicate can serialize and deserialize into the predicates.
-export type CheerState = CheerStateOfficial | CheerStateCustom;
-
-export function CheerStateIsOfficial(cheerState: CheerState): cheerState is CheerStateOfficial {
- return cheerState.hasOwnProperty('cheerPrefix');
-}
-
-export function CheerStateIsCustom(cheerState: CheerState): cheerState is CheerStateCustom {
- return cheerState.hasOwnProperty('cheerFull');
-}
-
-// Convert "exact match" strings into CheerState
-export function convertExactMatchToCheerState(freeformText: string) : CheerState {
- const trimmed = freeformText.trim();
-
- let { cheerPrefix, bitValue } = CheerUtil.parseCheerString(trimmed);
-
- let maybeCheer = CHEER_LOOKUP_MAP.get(cheerPrefix || '');
-
- if (maybeCheer !== undefined) {
- return {
- cheerPrefix: maybeCheer,
- bits: bitValue,
- }
- }
-
- // NB: In this case, bits value may be wrong.
- // eg. "1984"
- return {
- cheerFull: freeformText,
- bits: bitValue,
- };
-}
-
-// Convert "prefix + bits" into CheerState
-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: maybeBits,
- }
- }
-
- // NB: In this case, bits value may be wrong.
- // eg. "1984"
- return {
- cheerFull: freeformText,
- 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 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,
- }
- } else {
- return {};
- }
-}
-
-// Turn a cheer state into a predicate
-export function cheerStateToPredicate(
- cheerState: CheerState,
- bitsRuleType: BitsRuleType) : EventMatchPredicate
-{
- let predicate : EventMatchPredicate = {};
-
- switch (bitsRuleType) {
- case BitsRuleType.BitsCheermoteNameExactMatch:
- let cheermoteName = '';
- if (CheerStateIsOfficial(cheerState)) {
- let cheerPrefix = '';
- let bitsValue = '';
- if (!!cheerState.cheerPrefix) {
- cheerPrefix = CHEER_PREFIX_TO_STRING_MAP.get(cheerState.cheerPrefix) || '';
- }
- if (!!cheerState.bits && !isNaN(cheerState.bits) && cheerState.bits > 1) {
- bitsValue = cheerState.bits.toString();
- }
-
- if (!!cheerPrefix) {
- // Don't bother setting unless we have a prefix
- cheermoteName = `${cheerPrefix}${bitsValue}`
- }
-
- } else if (CheerStateIsCustom(cheerState)) {
- // Cheer name is the full value, eg. 'ZomboCom1000'
- cheermoteName = cheerState.cheerFull || '';
- }
-
- predicate.bits_cheermote_name_exact_match = {
- cheermote_name: cheermoteName,
- }
- break;
-
- case BitsRuleType.BitsCheermotePrefixSpendThreshold:
- let newBitsValue = 1;
- if (!!cheerState.bits && !isNaN(cheerState.bits) && cheerState.bits > 1) {
- newBitsValue = cheerState.bits;
- }
-
- let newPrefix = '';
- if (CheerStateIsOfficial(cheerState)) {
- if (!!cheerState.cheerPrefix) {
- newPrefix = CHEER_PREFIX_TO_STRING_MAP.get(cheerState.cheerPrefix) || '';
- }
- } else if (CheerStateIsCustom(cheerState)) {
- newPrefix = cheerState.cheerFull || '';
- }
-
- predicate.bits_cheermote_prefix_spend_threshold = {
- cheermote_prefix: newPrefix,
- minimum_bits_spent: newBitsValue,
- }
- break;
-
- case BitsRuleType.BitsSpendThreshold:
- let newBits = 1;
- if (!!cheerState.bits && !isNaN(cheerState.bits) && cheerState.bits > 1) {
- newBits = cheerState.bits;
- }
- predicate.bits_spend_threshold = {
- minimum_bits_spent: newBits,
- }
- break;
- }
-
- return predicate;
-}
-
-
-/*
-
-Truth table-ish breakdown (incomplete).
-
-"Corgo" is an official cheer
-"Zombo", "Sus12", and "1984" are custom cheers
-
-------------------------------------------------------------------------------------------------
-
-Backend Re-serialize to backend
-
-Bits 1000 => CheerState { bits: 1000 }
-
-ExactMatch Corgo100 => CheerState { official: Corgo, bits: 100 } => ExactMatch Corgo100
-ExactMatch Zombo100 => CheerState { full: Zombo100, bits: 100 } => ExactMatch Zombo100 (from "full")
-
-PrefixBits (Corgo, 100) => CheerState { official: Corgo, bits: 100 } => PrefixBits (Corgo, 100)
-PrefixBits (Corgo, 123) => CheerState { official: Corgo, bits: 123 } => PrefixBits (Corgo, 123)
-PrefixBits (Zombo, 123) => CheerState { full: Zombo, bits: 123 } => PrefixBits (Zombo, 123)
-
-------------------------------------------------------------------------------------------------
-
-Backend To another [view type] w/ UI state
-
-Bits 1000 => CheerState { bits: 1000 } => [ExactMatch] PrefixSelect: (n/a) BitSelect: 1000 FreeForm: (n/a)
-Bits 1234 => CheerState { bits: 1234 } => [ExactMatch] PrefixSelect: (n/a) BitSelect: (n/a) FreeForm: (n/a) [CANNOT POPULATE]
-
-Bits 1000 => CheerState { bits: 1000 } => [PrefixBits] PefixSelect: (n/a) FreeForm: (n/a) Bits: 1000
-Bits 1234 => CheerState { bits: 1234 } => [PrefixBits] PefixSelect: (n/a) FreeForm: (n/a) Bits: 1234
-
-ExactMatch Corgo100 => CheerState { official: Corgo, bits: 100 } => [ExactMatch] PrefixSelect: Corgo BitSelect: 100 FreeForm: Corgo100
-ExactMatch Zombo100 => CheerState { full: Zombo100, bits: 100 } => [ExactMatch] PrefixSelect: (n/a) BitSelect: 100 FreeForm: Zombo100
-ExactMatch Corgo100 => CheerState { official: Corgo, bits: 100 } => [Bits] BitsSelect: 100 BitsFreeForm: 100
-*) ExactMatch Corgo100 => CheerState { official: Corgo, bits: 100 } => [PrefixBits] PrefixSelect: Corgo FreeForm: Corgo Bits: 100
-*) ExactMatch Zombo100 => CheerState { full: Zombo100, bits: 100 } => [PrefixBits] PrefixSelect: (n/a) FreeForm: Zombo100 Bits: 100
-
-PrefixBits (Corgo, 100) => CheerState { official: Corgo, bits: 100 } => PrefixBits (Corgo, 100)
-PrefixBits (Corgo, 123) => CheerState { official: Corgo, bits: 123 } => PrefixBits (Corgo, 123)
-PrefixBits (Zombo, 123) => CheerState { full: Zombo, bits: 123 } => PrefixBits (Zombo, 123)
-
-# Notably, prefix is usually always "full":
-PrefixBits (1884, 1000) => CheerState { full: 1984, bits: 1000 } => [PrefixBits] PrefixSelect: (n/a) FreeForm: 1984 Bits: 1000
-PrefixBits (Sus12, 1000) => CheerState { full: Sus12, bits: 1000 } => [PrefixBits] PrefixSelect: (n/a) FreeForm: Sus12 Bits: 1000
-PrefixBits (Corgo1000, 5000) => CheerState { full: Corgo1000, bits: 5000} => [PrefixBits] PrefixSelect: (n/a) FreeForm: Corgo1000 Bits: 5000
-
-PrefixBits (1884, 1000) => CheerState { full: 1984, bits: 1000 } => [ExactMatch] PrefixSelect: (n/a) FreeForm: 1984 Bits: 1000
-PrefixBits (Sus12, 1000) => CheerState { full: Sus12, bits: 1000 } => [ExactMatch] PrefixSelect: (n/a) FreeForm: Sus12 Bits: 1000
-PrefixBits (Corgo1000, 5000) => CheerState { full: Corgo1000, bits: 5000} => [ExactMatch] PrefixSelect: (Corgo) FreeForm: Corgo1000 Bits: 1000 (*ignore bits*)
-
-
-# Unless it is *only* an official prefix
-PrefixBits (Corgo, 5000) => CheerState { official: Corgo, bits: 5000} => [PrefixBits] PrefixSelect: Corgo FreeForm: Corgo Bits: 5000
-PrefixBits (Corgo, 5000) => CheerState { official: Corgo, bits: 5000} => [ExactMatch] PrefixSelect: Corgo FreeForm: Corgo5000
-
-
-------------------------------------------------------------------------------------------------
-
-CheerState UI CheerState
-
-CheerState { official: Corgo, bits: 5000} => [Bits] BitsSelect: 5000, BitsFreeForm: 5000 => CheerState { official: (retain), bits: 5000 }
-CheerState { official: Corgo, bits: 5000} => [PrefixBits] PrefixSelect: Corgo FreeForm: Corgo Bits: 5000 => CheerState { official: Corgo, bits: 5000 }
-CheerState { official: Corgo, bits: 5000} => [ExactMatch] PrefixSelect: Corgo FreeForm: Corgo5000 => CheerState { official: Corgo, bits: 5000 } [split freeform]
-
-CheerState { full: Zombo, bits: 5000} => [Bits] BitsSelect: 5000, BitsFreeForm: 5000
-CheerState { full: Zombo, bits: 5000} => [PrefixBits] PrefixSelect: (n/a) FreeForm: Zombo Bits: 5000
-CheerState { full: Zombo, bits: 5000} => [ExactMatch] PrefixSelect: (n/a) FreeForm: Zombo5000
-
-CheerState { full: Zombo, bits: 5000} => [Bits] BitsSelect: 5000, BitsFreeForm: 5000
-CheerState { full: Zombo, bits: 5000} => [Bits] BitsSelect: 5000, BitsFreeForm: 5000
-
-CheerState { full: 1984, bits: 5000} => [PrefixBits] PrefixSelect: Corgo FreeForm: Corgo Bits: 5000
-CheerState { full: Sus12, bits: 5000} => [ExactMatch] PrefixSelect: Corgo FreeForm: Corgo5000
-CheerState { full: Corgo1000, bits: 5000} => [ExactMatch] PrefixSelect: Corgo FreeForm: Corgo5000
-
-
-*/
\ No newline at end of file
diff --git a/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/EventMatchPredicateBuilderComponent.tsx b/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/EventMatchPredicateBuilderComponent.tsx
deleted file mode 100644
index ff14e57b9c..0000000000
--- a/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/EventMatchPredicateBuilderComponent.tsx
+++ /dev/null
@@ -1,311 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { BitsRuleType } from './types/BitsRuleType';
-import { TwitchEventCategory } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/TwitchEventCategory';
-import { EventMatchPredicate } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventMatchPredicate';
-import { BitsCheermoteNameExactMatchForm } from './subforms/BitsCheermoteNameExactMatchForm';
-import { BitsCheermotePrefixSpendThresholdForm } from './subforms/BitsCheermotePrefixSpendThresholdForm';
-import { BitsSpendThresholdForm } from './subforms/BitsSpendThresholdForm';
-import { ChannelPointsRewardNameExactMatchForm } from './subforms/ChannelPointsRewardNameExactMatchForm';
-import { ChannelPointsRuleType } from './types/ChannelPointsRuleType';
-import { CheerUtil } from '../../../twitch/CheerUtil';
-import { CheerState, CheerStateIsCustom, CheerStateIsOfficial, cheerStateToPredicate, predicateToCheerState } from './CheerState';
-import { CHEER_LOOKUP_MAP } from '../../../twitch/Cheers';
-
-// NB: This was created for "Edit" view, but we've forced it to work for "Create" view by hardcoding
-// values that don't actually come from the server.
-
-interface EventMatchPredicateBuilderComponentProps {
- // CANNOT BE CHANGED AFTER CREATION
- twitchEventCategory: TwitchEventCategory,
-
- // Original event match predicate from the server
- // (also immutable for sanity and because we lose information on UI changes)
- serverEventMatchPredicate: EventMatchPredicate,
-
- // Updates sent back up the tree
- updateModifiedEventMatchPredicate: (predicate: EventMatchPredicate) => void,
-};
-
-function EventMatchPredicateBuilderComponent(props: EventMatchPredicateBuilderComponentProps) {
- // ========== Core UI flow ==========
-
- const [bitsRuleType, setBitsRuleType] = useState(BitsRuleType.BitsCheermoteNameExactMatch);
- const [channelPointsRuleType, setChannelPointsRuleType] = useState(ChannelPointsRuleType.ChannelPointsRewardNameExactMatch);
-
- // ========== Cached Values for Editing ==========
-
- // A very complicated container that juggles bits and cheermote state.
- // The state transitions are complicated, and I've tried to document as best as possible.
- // TODO: This really needs some testing.
- const [cheerState, setCheerState] = useState({});
-
- // Used in:
- // ChannelPointsRewardNameExactMatch
- const [rewardName, setRewardName] = 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 newBitsRuleType = BitsRuleType.BitsCheermoteNameExactMatch;
- let newChannelPointsRuleType = ChannelPointsRuleType.ChannelPointsRewardNameExactMatch;
-
- let newCheerState = predicateToCheerState(props.serverEventMatchPredicate);
-
- switch (props.twitchEventCategory) {
- case TwitchEventCategory.Bits:
- if (!!props.serverEventMatchPredicate.bits_cheermote_name_exact_match) {
- newBitsRuleType = BitsRuleType.BitsCheermoteNameExactMatch;
- } else if (!!props.serverEventMatchPredicate.bits_cheermote_prefix_spend_threshold) {
- newBitsRuleType = BitsRuleType.BitsCheermotePrefixSpendThreshold;
- } else if (!!props.serverEventMatchPredicate.bits_spend_threshold) {
- newBitsRuleType = BitsRuleType.BitsSpendThreshold;
- }
- break;
- case TwitchEventCategory.ChannelPoints: // NB: Only one rule type
- if (!!props.serverEventMatchPredicate.channel_points_reward_name_exact_match) {
- setRewardName(props.serverEventMatchPredicate.channel_points_reward_name_exact_match.reward_name);
- newChannelPointsRuleType = ChannelPointsRuleType.ChannelPointsRewardNameExactMatch;
- }
- break;
- case TwitchEventCategory.ChatCommand: // TODO: Not yet supported
- default:
- break;
- }
-
- setCheerState(newCheerState);
-
- setBitsRuleType(newBitsRuleType);
- setChannelPointsRuleType(newChannelPointsRuleType);
-
- }, [props.twitchEventCategory, props.serverEventMatchPredicate]);
-
- // TODO: We need to recalculate the model.
- const handleChangedBitsRuleType = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- const newRuleType = value as BitsRuleType;
-
- let predicate = cheerStateToPredicate(cheerState, newRuleType);
-
- setBitsRuleType(newRuleType);
- props.updateModifiedEventMatchPredicate(predicate);
-
- return true;
- }
-
- const handleChangedChannelPointsRuleType = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- const ruleType = value as ChannelPointsRuleType;
- setChannelPointsRuleType(ruleType);
- return true;
- }
-
- const handleChangedCheerPrefix = (newPrefix: string) => {
- // 1) Update Cheer State
- // 2) Update NewEventMatchPredicate
- let newCheerState : CheerState = {};
-
- let maybeCheerPrefix = CHEER_LOOKUP_MAP.get(newPrefix || '');
-
- if (!!maybeCheerPrefix) {
- newCheerState = {
- cheerPrefix: maybeCheerPrefix,
- bits: cheerState.bits, // Unchanged
- }
- } else {
- newCheerState = {
- cheerFull: newPrefix,
- bits: cheerState.bits, // Unchanged
- }
- }
-
- let predicate = cheerStateToPredicate(newCheerState, bitsRuleType);
-
- setCheerState(newCheerState);
- props.updateModifiedEventMatchPredicate(predicate);
- }
-
- const handleChangedCheerName = (name: string) => {
- // 1) Update Cheer State
- // 2) Update NewEventMatchPredicate
- let newCheerState : CheerState = {};
-
- let { cheerPrefix, bitValue } = CheerUtil.parseCheerString(name);
-
- let newBitValue = bitValue;
- let maybeCheerPrefix = CHEER_LOOKUP_MAP.get(cheerPrefix || '');
-
- if (!!maybeCheerPrefix) {
- // Official prefix.
- newCheerState = {
- cheerPrefix: maybeCheerPrefix,
- bits: newBitValue,
- }
- } else if (!!cheerPrefix) {
- // Custom prefix.
-
- // Test if hypothetically we should retain an independent of name bit value.
- // Hypothetically, Sus13 -> Sus12, but the bit value is 5000 in a separate field.
- // We don't want to repalace with 13.
- if (CheerStateIsCustom(cheerState)) {
- let { cheerPrefix, bitValue } = CheerUtil.parseCheerString(cheerState.cheerFull || '');
-
- if ((!!cheerPrefix && !!bitValue) // We have a complete name
- && (bitValue !== cheerState.bits)) // The bit value is not independent of the cheer name, as in eg. (Foobar1000, 1000)
- {
- // Keep the old value.
- newBitValue = cheerState.bits;
- }
- }
-
- newCheerState = {
- cheerFull: name, // Should always be the full value
- bits: newBitValue,
- }
- }
-
- let predicate = cheerStateToPredicate(newCheerState, bitsRuleType);
-
- setCheerState(newCheerState);
- props.updateModifiedEventMatchPredicate(predicate);
- }
-
- const handleChangedMinimumBitsSpent = (minimumSpent: number) => {
- // 1) Update Cheer State
- // 2) Update NewEventMatchPredicate
- let newCheerState = {};
-
- if (CheerStateIsOfficial(cheerState)) {
- newCheerState = {
- cheerPrefix: cheerState.cheerPrefix, // Unchanged
- bits: minimumSpent,
- }
- } else if (CheerStateIsCustom(cheerState)) {
- // Test if the cheer name includes numbers, eg. 'Sus12', or '1984', that differ from bit spend
- let { cheerPrefix, bitValue } = CheerUtil.parseCheerString(cheerState.cheerFull || '');
- let cheerFull = cheerState.cheerFull;
-
- if (!!cheerPrefix && !!bitValue && bitValue === cheerState.bits) {
- // Let's replace the cheer.
- cheerFull = CheerUtil.joinCheerAndPrefix(cheerPrefix, minimumSpent);
- }
-
- newCheerState = {
- cheerFull: cheerFull,
- bits: minimumSpent,
- }
-
- } else {
- newCheerState = {
- bits: minimumSpent,
- }
- }
-
- let predicate = cheerStateToPredicate(newCheerState, bitsRuleType);
-
- setCheerState(newCheerState);
- props.updateModifiedEventMatchPredicate(predicate);
- }
-
- // TODO
- const handleChangedRewardName = (name: string) => {
- let predicate : EventMatchPredicate = {};
-
- switch (channelPointsRuleType) {
- case ChannelPointsRuleType.ChannelPointsRewardNameExactMatch:
- predicate.channel_points_reward_name_exact_match = {
- reward_name: name, // New value
- }
- break;
- }
-
- props.updateModifiedEventMatchPredicate(predicate);
- }
-
- let ruleTypeSelect = <>>
- let matchingRulesForm = <>>
-
- if (props.twitchEventCategory === TwitchEventCategory.Bits) {
- ruleTypeSelect = (
- <>
-
- Bits Spend Threshold
- Cheermote Name (Exact Match)
- Cheermote Prefix and Bits Spend Threshold
-
- >
- );
-
- switch (bitsRuleType) {
- case BitsRuleType.BitsCheermoteNameExactMatch:
- matchingRulesForm = ;
- break;
- case BitsRuleType.BitsCheermotePrefixSpendThreshold:
- matchingRulesForm =
- break;
- case BitsRuleType.BitsSpendThreshold:
- matchingRulesForm = ;
- break;
- }
-
- } else if (props.twitchEventCategory === TwitchEventCategory.ChannelPoints) {
- ruleTypeSelect = (
- <>
-
- Channel Points Reward Name (exact match)
-
- >
- );
-
- matchingRulesForm = ;
- }
-
- return (
- <>
- 1) Pick what to match on
-
-
-
-
-
-
- 2) Configure the matching
-
- {matchingRulesForm}
- >
- )
-}
-
-export { EventMatchPredicateBuilderComponent }
diff --git a/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermoteNameExactMatchForm.tsx b/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermoteNameExactMatchForm.tsx
deleted file mode 100644
index 0ff460078f..0000000000
--- a/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermoteNameExactMatchForm.tsx
+++ /dev/null
@@ -1,169 +0,0 @@
-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, 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 {
- cheerState: CheerState,
- updateCheerName: (cheerNameOrPrefix: string) => void,
-};
-
-function BitsCheermoteNameExactMatchForm(props: BitsCheermoteNameExactMatchProps) {
- // The two dropdowns
- const [cheerPrefix, setCheerPrefix] = useState('');
- const [bitsValue, setBitsValue] = useState(1);
- // The freeform text input
- 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 newPrefix = '';
- let newFullName = '';
- let newBits = 1;
- let hasBits = false;
-
- if (!!props.cheerState.bits && !isNaN(props.cheerState.bits) && props.cheerState.bits > 0) {
- newBits = props.cheerState.bits;
- hasBits = true;
- }
-
- if (CheerStateIsOfficial(props.cheerState)) {
- if (!!props.cheerState.cheerPrefix) {
- newPrefix = CHEER_PREFIX_TO_STRING_MAP.get((props.cheerState.cheerPrefix)) || '';
- }
-
- if (hasBits) {
- newFullName = `${newPrefix}${newBits}`;
- } else {
- newFullName = newPrefix;
- }
-
- } else if (CheerStateIsCustom(props.cheerState)) {
- newFullName = props.cheerState.cheerFull || '';
- }
-
- setCheerPrefix(newPrefix);
- setBitsValue(newBits);
- setManualCheerValue(newFullName);
-
- }, [props.cheerState]);
-
- const updateCheerPrefix = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- setCheerPrefix(value);
- recalcuateFieldValue(value, bitsValue);
- return true;
- }
-
- const updateBitsValue = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- const numericValue = parseInt(value);
- if (!isNaN(numericValue)) {
- // NB: The form shouldn't yield NaN, but just in case.
- setBitsValue(numericValue);
- recalcuateFieldValue(cheerPrefix, numericValue);
- }
- return true;
- }
-
- const updateTextCheerValue = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLInputElement).value;
-
- // Cheer name is the full value, eg. 'Corgo100'
- const matches = value.trim().match(CHEER_REGEX)
-
- if (!!matches && matches.length > 1) {
- setCheerPrefix(matches[1]); // First match group
- if (matches.length === 3 && matches[2] !== undefined) {
- // NB: Second match group can be 'undefined' if no number is present. (Zero-width matching?)
- const maybeBits = parseInt(matches[2]);
- if (!isNaN(maybeBits)) {
- setBitsValue(maybeBits); // Second match group
- }
- }
- }
-
- setManualCheerValue(value);
- props.updateCheerName(value);
- return true;
- }
-
- // When the dropdowns are used, replace any manual text entry.
- const recalcuateFieldValue = (prefix: string, bits: number) => {
- const cheerValue = `${prefix}${bits}`;
- setManualCheerValue(cheerValue);
- props.updateCheerName(cheerValue);
- }
-
- return (
- <>
-
-
-
Pick the cheer
-
-
- Select cheer...
- {CHEER_PREFIXES.map(cheerPrefix => {
- return (
- {cheerPrefix}
- );
- })}
-
-
-
-
-
Then the bit value
-
-
-
- {CHEER_BIT_LEVELS.map(level => {
- return (
- {level}
- );
- })}
-
-
-
-
-
-
To match against this (or set something custom)
-
-
-
-
-
-
-
-
- >
- )
-}
-
-export { BitsCheermoteNameExactMatchForm }
diff --git a/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermotePrefixSpendThresholdForm.tsx b/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermotePrefixSpendThresholdForm.tsx
deleted file mode 100644
index 362eca4550..0000000000
--- a/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsCheermotePrefixSpendThresholdForm.tsx
+++ /dev/null
@@ -1,195 +0,0 @@
-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, CHEER_PREFIX_TO_STRING_MAP } from '../../../../twitch/Cheers';
-import { CheerState, CheerStateIsCustom, CheerStateIsOfficial } from '../CheerState';
-
-interface BitsCheermotePrefixSpendThresholdProps {
- cheerState: CheerState,
-
- updateCheerPrefix: (cheerNameOrPrefix: string) => void,
- updateMinimumBitsSpent: (minimumSpent: number) => void,
-};
-
-function BitsCheermotePrefixSpendThresholdForm(props: BitsCheermotePrefixSpendThresholdProps) {
- const [cheerPrefix, setCheerPrefix] = useState('');
- const [customCheerPrefix, setCustomCheerPrefix] = useState('');
-
- 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)
- // 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 newPrefix = '';
- let newCustomPrefix = '';
- let newBits = 1;
- let newCustomBits = 1;
-
- if (!!props.cheerState.bits && !isNaN(props.cheerState.bits) && props.cheerState.bits > 0) {
- newBits = props.cheerState.bits;
- newCustomBits = props.cheerState.bits;
- }
-
- 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)) || '';
- }
- } else if (CheerStateIsCustom(props.cheerState)) {
- newCustomPrefix = props.cheerState.cheerFull || '';
- }
-
- setCheerPrefix(newPrefix);
- setCustomCheerPrefix(newCustomPrefix);
- setBitsValue(newBits);
- setCustomBitsValue(newCustomBits);
-
- }, [props.cheerState]);
-
- const updateCheerPrefix = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- setCheerPrefix(value);
- setCustomCheerPrefix(value);
- props.updateCheerPrefix(value);
- return true;
- }
-
- const updateTextCheerValue = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLInputElement).value;
- setCheerPrefix(value);
- setCustomCheerPrefix(value);
- props.updateCheerPrefix(value);
- return true;
- }
-
- const handleBitSelect = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- const numericValue = parseInt(value);
- setBitsValue(numericValue);
- setCustomBitsValue(numericValue);
- props.updateMinimumBitsSpent(numericValue);
- return true;
- }
- const handleCustomBitsValueUpdate = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLInputElement).value;
- const numericValue = parseInt(value);
- setCustomBitsValue(numericValue);
- props.updateMinimumBitsSpent(numericValue);
- return true;
- }
-
- return (
- <>
-
-
- This rule type requires both the cheermote
- to match and the bits spent to be equal or greater than the amount specified.
-
-
-
- Cheermote matching
-
-
-
-
Pick the cheer prefix
-
-
- Select cheer...
- {CHEER_PREFIXES.map(cheerPrefix => {
- return (
- {cheerPrefix}
- );
- })}
-
-
-
-
-
To match against this (or set something custom)
-
-
-
-
-
-
-
-
-
-
-
- The "Cheer prefix" can be an actual cheer prefix, such as "SeemsGood", but it can also be an arbitrary
- string value. So if you make it "ManBearPig", that will be searched for in cheers.
-
- Cheermotes will be removed from the words used in TTS, but currently custom strings will not (though
- this may change in the future).
-
-
-
- Bits spent
-
-
-
-
Preset thresholds
-
-
-
- {CHEER_BIT_LEVELS.map(level => {
- return (
- Spend at least {level} bits
- );
- })}
-
-
-
-
-
-
Or a custom minimum amount.
-
-
-
-
-
-
-
-
-
-
- Remember that all Cheermotes in a single message add up,
- eg. “Cheer100 + Cheer100 + Party100” is 300 bits.
- A custom amount should give you flexibility to charge, say, 300 or 500 bits.
- Or 420. Whatever you want.
-
-
- >
- )
-}
-
-export { BitsCheermotePrefixSpendThresholdForm }
diff --git a/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsSpendThresholdForm.tsx b/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsSpendThresholdForm.tsx
deleted file mode 100644
index 0474a4754b..0000000000
--- a/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/BitsSpendThresholdForm.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-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 {
- cheerState: CheerState,
- updateMinimumBitsSpent: (minimumSpent: number) => void,
-};
-
-function BitsSpendThresholdForm(props: BitsSpendThresholdFormProps) {
- // The dropdown
- const [bitsValue, setBitsValue] = useState(1);
- // The freeform text input
- 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 (!!props.cheerState.bits && !isNaN(props.cheerState.bits) && props.cheerState.bits > 0) {
- setBitsValue(props.cheerState.bits);
- setCustomAmount(props.cheerState.bits);
- }
- }, [props.cheerState]);
-
- const handleBitSelect = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- const numericValue = parseInt(value);
-
- if (!isNaN(numericValue) && numericValue > 0) {
- // Only update on valid positive integers
- setBitsValue(numericValue);
- setCustomAmount(numericValue);
- props.updateMinimumBitsSpent(numericValue);
- }
-
- return true;
- }
-
- const handleCustomAmountUpdate = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLInputElement).value;
- const numericValue = parseInt(value);
-
- if (!isNaN(numericValue) && numericValue > 0) {
- // Only update on valid positive integers
- setBitsValue(numericValue);
- setCustomAmount(numericValue);
- props.updateMinimumBitsSpent(numericValue);
- }
-
- return true;
- }
-
- return (
- <>
-
-
-
Preset thresholds
-
-
-
- Choose or type...
- {CHEER_BIT_LEVELS.map(level => {
- return (
- Spend at least {level} bits
- );
- })}
-
-
-
-
-
-
Or a custom minimum amount.
-
-
-
-
-
-
-
-
-
-
- Remember that all Cheermotes in a single message add up,
- eg. “Cheer100 + Cheer100 + Party100” is 300 bits.
- A custom amount should give you flexibility to charge, say, 300 or 500 bits.
- Or 420. Whatever you want.
-
-
- >
- )
-}
-
-export { BitsSpendThresholdForm }
diff --git a/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/ChannelPointsRewardNameExactMatchForm.tsx b/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/ChannelPointsRewardNameExactMatchForm.tsx
deleted file mode 100644
index d489c6024e..0000000000
--- a/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/subforms/ChannelPointsRewardNameExactMatchForm.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React, { useEffect, useState } from 'react';
-
-interface ChannelPointsRewardNameExactMatchProps {
- rewardName: string,
- updateRewardName: (name: string) => void,
-};
-
-function ChannelPointsRewardNameExactMatchForm(props: ChannelPointsRewardNameExactMatchProps) {
- const [rewardName, setRewardName] = 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(() => {
- setRewardName(props.rewardName);
- }, [props.rewardName]);
-
- const updateRewardName = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLInputElement).value;
- setRewardName(value);
- props.updateRewardName(value);
- return true;
- }
-
- return (
- <>
-
-
Match the following reward name
-
-
-
-
- >
- )
-}
-
-export { ChannelPointsRewardNameExactMatchForm }
diff --git a/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/types/BitsRuleType.ts b/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/types/BitsRuleType.ts
deleted file mode 100644
index b89d958ade..0000000000
--- a/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/types/BitsRuleType.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-
-// For bits / cheermotes
-export enum BitsRuleType {
- BitsCheermoteNameExactMatch = "bits_cheermote_name_exact_match",
- BitsCheermotePrefixSpendThreshold = "bits_cheermote_prefix_spend_threshold",
- BitsSpendThreshold = "bits_spend_threshold",
-}
diff --git a/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/types/ChannelPointsRuleType.ts b/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/types/ChannelPointsRuleType.ts
deleted file mode 100644
index d4379b3d7b..0000000000
--- a/packages/power.stream/src/pages/tts_configs/event_match_predicate_builder/types/ChannelPointsRuleType.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-
-// For channel points
-export enum ChannelPointsRuleType {
- ChannelPointsRewardNameExactMatch = 'channel_points_reward_name_exact_match',
-}
diff --git a/packages/power.stream/src/pages/tts_configs/event_response_builder/BuildEventResponse.ts b/packages/power.stream/src/pages/tts_configs/event_response_builder/BuildEventResponse.ts
deleted file mode 100644
index 66b2d25aaa..0000000000
--- a/packages/power.stream/src/pages/tts_configs/event_response_builder/BuildEventResponse.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { EventResponse } from "@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventResponse";
-import { EventResponseType } from "./EventResponseType";
-
-export function buildEventResponse(ttsModelTokens: string[], eventResponseType: EventResponseType) :EventResponse {
- let eventResponse : EventResponse = {};
-
- switch (eventResponseType) {
- case EventResponseType.TtsSingleVoice:
- let ttsModelToken = '';
- if (ttsModelTokens.length > 0) {
- ttsModelToken = ttsModelTokens[0];
- }
- eventResponse.tts_single_voice = {
- tts_model_token: ttsModelToken,
- }
- break;
- case EventResponseType.TtsRandomVoice:
- eventResponse.tts_random_voice= {
- tts_model_tokens: [...ttsModelTokens],
- }
- break;
- }
-
- return eventResponse;
-}
\ No newline at end of file
diff --git a/packages/power.stream/src/pages/tts_configs/event_response_builder/EventResponseComponent.tsx b/packages/power.stream/src/pages/tts_configs/event_response_builder/EventResponseComponent.tsx
deleted file mode 100644
index 6bdd77afb5..0000000000
--- a/packages/power.stream/src/pages/tts_configs/event_response_builder/EventResponseComponent.tsx
+++ /dev/null
@@ -1,155 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
-import { TtsSingleVoiceForm } from './TtsSingleVoiceForm';
-import { EventResponseType } from './EventResponseType';
-import { EventResponse } from '@storyteller/components/src/api/storyteller/twitch_event_rules/shared/EventResponse';
-import { buildEventResponse } from './BuildEventResponse';
-import { TtsRandomVoiceForm } from './TtsRandomVoiceForm';
-
-// NB: This was created for "Edit" view, but we've forced it to work for "Create" view by hardcoding
-// values that don't actually come from the server.
-
-interface EventResponseComponentProps {
- // Original response from server.
- // (also immutable for sanity and because we may lose info on UI changes)
- serverEventResponse: EventResponse,
-
- // Updates sent back up the tree
- updateModifiedEventResponse: (response: EventResponse) => void,
-
- // FakeYou voices
- allTtsModels: TtsModelListItem[],
- allTtsModelsByToken: Map,
-};
-
-function EventResponseComponent(props: EventResponseComponentProps) {
- // ========== Core UI flow ==========
-
- const [eventResponseType, setEventResponseType] = useState(EventResponseType.TtsSingleVoice);
-
- // ========== Cached Values for Editing ==========
-
- const [selectedTtsModelTokens, setSelectedTtsModelTokens] = 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 newEventResponseType = EventResponseType.TtsSingleVoice;
- let modelTokens : string[] = [];
-
- if (!!props.serverEventResponse.tts_single_voice) {
- newEventResponseType = EventResponseType.TtsSingleVoice;
-
- if (!!props.serverEventResponse.tts_single_voice.tts_model_token) {
- modelTokens.push(props.serverEventResponse.tts_single_voice.tts_model_token)
- }
-
- } else if (!!props.serverEventResponse.tts_random_voice) {
- newEventResponseType = EventResponseType.TtsRandomVoice;
-
- if (!!props.serverEventResponse.tts_random_voice.tts_model_tokens &&
- props.serverEventResponse.tts_random_voice.tts_model_tokens.length > 0)
- {
- modelTokens = [...props.serverEventResponse.tts_random_voice.tts_model_tokens];
- }
- }
-
- setEventResponseType(newEventResponseType);
- setSelectedTtsModelTokens(modelTokens);
-
- }, [props.serverEventResponse]);
-
- const handleChangedEventResponseType = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLSelectElement).value;
- const newResponseType = value as EventResponseType;
-
- const newEventResponse = buildEventResponse(selectedTtsModelTokens, newResponseType);
-
- setEventResponseType(newResponseType);
- props.updateModifiedEventResponse(newEventResponse);
-
- return true;
- }
-
- const handleChangedTtsModelToken = (token: string) => {
- // We may be only updating to a single token, but we want to be able to navigate back to
- // the "random voices" view, etc.
- let updatedTokens = [...selectedTtsModelTokens];
-
- if (updatedTokens.length > 0) {
- updatedTokens[0] = token;
- } else if (updatedTokens.length === 0) {
- updatedTokens.push(token);
- }
-
- const newEventResponse = buildEventResponse(updatedTokens, eventResponseType);
-
- setSelectedTtsModelTokens(updatedTokens);
- props.updateModifiedEventResponse(newEventResponse);
- }
-
- const handleChangedTtsModelTokens = (tokens: string[]) => {
- let updatedTokens = [...tokens];
-
- const newEventResponse = buildEventResponse(updatedTokens, eventResponseType);
-
- setSelectedTtsModelTokens(updatedTokens);
- props.updateModifiedEventResponse(newEventResponse);
- }
-
- let responseForm = <>>
-
- switch (eventResponseType) {
- case EventResponseType.TtsSingleVoice:
- responseForm =
- break;
- case EventResponseType.TtsRandomVoice:
- responseForm =
- break;
- }
-
- return (
- <>
- 3) Pick how to respond
-
-
-
Response Type
-
-
-
- Respond with a TTS voice
- Respond with a Random TTS voice from list
-
-
-
-
-
-
-
-
- 4) Configure the response
-
- {responseForm}
-
-
-
-
- >
- )
-}
-
-export { EventResponseComponent }
diff --git a/packages/power.stream/src/pages/tts_configs/event_response_builder/EventResponseType.ts b/packages/power.stream/src/pages/tts_configs/event_response_builder/EventResponseType.ts
deleted file mode 100644
index 4131886efa..0000000000
--- a/packages/power.stream/src/pages/tts_configs/event_response_builder/EventResponseType.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-
-export enum EventResponseType {
- TtsSingleVoice = "tts_single_voice",
- TtsRandomVoice = "tts_random_voice",
-}
diff --git a/packages/power.stream/src/pages/tts_configs/event_response_builder/TtsRandomVoiceForm.tsx b/packages/power.stream/src/pages/tts_configs/event_response_builder/TtsRandomVoiceForm.tsx
deleted file mode 100644
index 41b72bbc79..0000000000
--- a/packages/power.stream/src/pages/tts_configs/event_response_builder/TtsRandomVoiceForm.tsx
+++ /dev/null
@@ -1,206 +0,0 @@
-import React, { useEffect } from 'react';
-import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
-import { FakeYouExternalLink } from '@storyteller/components/src/elements/FakeYouExternalLink';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faPlus, faTrash } from '@fortawesome/free-solid-svg-icons';
-
-interface TtsRandomVoiceFormProps {
- selectedTtsModelTokens: string[],
- updateSelectedTtsModelTokens: (tokens: string[]) => void,
-
- // FakeYou voices
- allTtsModels: TtsModelListItem[],
- allTtsModelsByToken: Map,
-};
-
-function TtsRandomVoiceForm(props: TtsRandomVoiceFormProps) {
- // 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(() => {
- }, [props.selectedTtsModelTokens]);
-
- const handleAddVoice = (ev: React.FormEvent) : boolean => {
- ev.preventDefault();
-
- let newModelTokens = [...props.selectedTtsModelTokens];
-
- newModelTokens.push('');
-
- props.updateSelectedTtsModelTokens(newModelTokens);
-
- return true;
- }
-
- const handleModelSelect = (ttsModelToken: string, voiceIndex: number) => {
- let newModelTokens = [...props.selectedTtsModelTokens];
-
- if (voiceIndex >= newModelTokens.length) {
- return;
- }
-
- newModelTokens[voiceIndex] = ttsModelToken;
- props.updateSelectedTtsModelTokens(newModelTokens);
- }
-
- const handleModelDelete = (voiceIndex: number) => {
- let newModelTokens = [...props.selectedTtsModelTokens];
-
- if (newModelTokens.length <= 1 || voiceIndex >= newModelTokens.length) {
- // We must have at least one item in the list
- return;
- }
-
- newModelTokens.splice(voiceIndex, 1);
-
- props.updateSelectedTtsModelTokens(newModelTokens);
- }
-
- const deleteDisabled = props.selectedTtsModelTokens.length < 2;
-
- let selectBoxes : JSX.Element[] = [];
-
- // No tokens - show one box (unselected)
- // One or more tokens - show boxes for each (with selections)
-
- props.selectedTtsModelTokens.forEach((token, index) => {
- selectBoxes.push(
-
- )
- });
-
- if (selectBoxes.length === 0) {
- selectBoxes.push(
-
- )
- }
-
- return (
- <>
- {selectBoxes}
-
-
- Add Additional Voice
-
-
-
-
-
-
- Check out the voices at FakeYou to search for voices, experiment with them,
- and see how they sound. Sorry that this list is a nightmare to navigate. It will improve over time.
-
-
- >
- )
-}
-
-interface VoiceDropdownProps {
- // Actual token or empty string
- selectedTtsModelToken: string,
-
- // Position in the list, 0-indexed
- voiceIndex: number,
-
- // Callbacks
- handleModelSelect: (token: string, index: number) => void,
- handleModelDelete: (index: number) => void,
-
- // Whether to disable the delete button
- deleteDisabled: boolean,
-
- // FakeYou voices
- allTtsModels: TtsModelListItem[],
- allTtsModelsByToken: Map,
-}
-
-function VoiceDropdown(props: VoiceDropdownProps) {
-
- const handleSelectChange = (ev: React.FormEvent) : boolean => {
- const maybeModelToken = (ev.target as HTMLSelectElement).value;
-
- if (!maybeModelToken) {
- // Clear selection
- props.handleModelSelect(maybeModelToken, props.voiceIndex);
- return true;
- }
-
- if (!!maybeModelToken && !props.allTtsModelsByToken.has(maybeModelToken)) {
- return false;
- }
-
- props.handleModelSelect(maybeModelToken, props.voiceIndex);
- return true;
- }
-
- const handleRemoveClick = (ev: React.FormEvent) : boolean => {
- props.handleModelDelete(props.voiceIndex);
- return true;
- }
-
- return (
-
-
{props.voiceIndex+1}. TTS Voice Model (used as a Random Voice)
-
-
-
-
-
-
-
- Select a voice...
- {props.allTtsModels.map(ttsModel => {
- return (
- {ttsModel.title}
- );
- })}
-
-
-
-
-
- Remove
-
-
-
-
-
-
- );
-}
-
-export { TtsRandomVoiceForm }
diff --git a/packages/power.stream/src/pages/tts_configs/event_response_builder/TtsSingleVoiceForm.tsx b/packages/power.stream/src/pages/tts_configs/event_response_builder/TtsSingleVoiceForm.tsx
deleted file mode 100644
index e3a3fbdd4d..0000000000
--- a/packages/power.stream/src/pages/tts_configs/event_response_builder/TtsSingleVoiceForm.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
-import { FakeYouExternalLink } from '@storyteller/components/src/elements/FakeYouExternalLink';
-
-interface TtsSingleVoiceFormProps {
- selectedTtsModelTokens: string[],
- updateSelectedTtsModelToken: (token: string) => void,
-
- // FakeYou voices
- allTtsModels: TtsModelListItem[],
- allTtsModelsByToken: Map,
-};
-
-function TtsSingleVoiceForm(props: TtsSingleVoiceFormProps) {
- const [ttsModelToken, setTtsModelToken] = 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(() => {
- if (props.selectedTtsModelTokens.length > 0) {
- setTtsModelToken(props.selectedTtsModelTokens[0]);
- }
- }, [props.selectedTtsModelTokens]);
-
- const handleModelSelect = (ev: React.FormEvent) : boolean => {
- const value = (ev.target as HTMLSelectElement).value;
-
- if (!!value && !props.allTtsModelsByToken.has(value)) {
- return false;
- }
-
- setTtsModelToken(value);
- props.updateSelectedTtsModelToken(value);
- return true;
- }
-
- return (
- <>
-
-
TTS Voice Model
-
-
-
- Select a voice...
- {props.allTtsModels.map(ttsModel => {
- return (
- {ttsModel.title}
- );
- })}
-
-
-
-
-
-
-
- We apologize that this list is a nightmare to navigate right now. It will improve over time.
- In the mean time, check out the voices at FakeYou to
- find the voices you want, experiment with them, and see which ones work best for your stream. The
- names of the voices at FakeYou are the same as in this dropdown, so you can search at FakeYou, then
- add them here when you're done looking.
-
- >
- )
-}
-
-export { TtsSingleVoiceForm }
diff --git a/packages/power.stream/src/pages/tts_configs/rule_cards/ReorderableTwitchEventRuleElement.tsx b/packages/power.stream/src/pages/tts_configs/rule_cards/ReorderableTwitchEventRuleElement.tsx
deleted file mode 100644
index 7c9cf14fa8..0000000000
--- a/packages/power.stream/src/pages/tts_configs/rule_cards/ReorderableTwitchEventRuleElement.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-import React from 'react';
-import { TwitchEventRule } from '@storyteller/components/src/api/storyteller/twitch_event_rules/ListTwitchEventRules';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faArrowDown, faArrowUp, faExternalLinkAlt, } from '@fortawesome/free-solid-svg-icons';
-import { TtsModelListItem } from '@storyteller/components/src/api/tts/ListTtsModels';
-
-interface Props {
- rule: TwitchEventRule,
-
- // Index of the rule in the parents' list
- ruleIndex: number,
-
- // Update callbacks
- handleMoveUp: (index: number) => void,
- handleMoveDown: (index: number) => void,
-
- // FakeYou voices
- allTtsModelsByToken: Map,
-}
-
-function ReorderableTwitchEventRuleElement(props: Props) {
- let subtitle = <>>;
- let description = <>>;
-
- if (props.rule.event_match_predicate.bits_cheermote_name_exact_match !== undefined) {
- subtitle = (
- <>Cheermote name matches "{props.rule.event_match_predicate.bits_cheermote_name_exact_match.cheermote_name}">
- );
- } else if (props.rule.event_match_predicate.bits_cheermote_prefix_spend_threshold !== undefined) {
- subtitle = (
- <>
- Cheermote prefix is "{props.rule.event_match_predicate.bits_cheermote_prefix_spend_threshold.cheermote_prefix}"
- and spend at least {props.rule.event_match_predicate.bits_cheermote_prefix_spend_threshold.minimum_bits_spent} bits
- >
- );
- } else if (props.rule.event_match_predicate.bits_spend_threshold !== undefined) {
- subtitle = (
- <>
- Spend at least {props.rule.event_match_predicate.bits_spend_threshold.minimum_bits_spent} bits
- >
- );
- } else if (props.rule.event_match_predicate.channel_points_reward_name_exact_match !== undefined) {
- subtitle = (
- <>
- Reward name matches "{props.rule.event_match_predicate.channel_points_reward_name_exact_match.reward_name}"
- >
- );
- }
-
- if (props.rule.event_response.tts_single_voice !== undefined) {
- const token = props.rule.event_response.tts_single_voice.tts_model_token;
-
- let model = props.allTtsModelsByToken.get(token);
- let modelName = token;
-
- if (!!model) {
- modelName = model.title;
- }
-
- let link = `https://fakeyou.com/tts/${token}`;
-
- description = (
- <>
- TTS with voice: {modelName}
- >
- );
- } else if (props.rule.event_response.tts_random_voice !== undefined) {
- const tokens = props.rule.event_response.tts_random_voice.tts_model_tokens;
-
- let modelNameAndLinks = tokens.map((token, index) => {
- let model = props.allTtsModelsByToken.get(token);
- let link = `https://fakeyou.com/tts/${token}`;
-
- let modelName = token;
- if (!!model) {
- modelName = model.title;
- }
-
- return (
-
- {modelName}
-
- );
- });
-
- description = (
- <>
- TTS with a random voice from the following {modelNameAndLinks.length} voice(s):
-
- >
- );
- }
-
- return (
-
-
-
-
- {subtitle}
-
-
- {description}
-
-
-
-
-
- props.handleMoveUp(props.ruleIndex)} className="button is-ghost">
- Up
-
-
-
-
-
- props.handleMoveDown(props.ruleIndex)} className="button is-ghost">
- Down
-
-
-
-
-
-
-
- )
-}
-
-export { ReorderableTwitchEventRuleElement }
\ No newline at end of file
diff --git a/packages/power.stream/src/pages/tts_configs/rule_cards/TwitchEventRuleElement.tsx b/packages/power.stream/src/pages/tts_configs/rule_cards/TwitchEventRuleElement.tsx
deleted file mode 100644
index 46f9f1e01e..0000000000
--- a/packages/power.stream/src/pages/tts_configs/rule_cards/TwitchEventRuleElement.tsx
+++ /dev/null
@@ -1,178 +0,0 @@
-import React from "react";
-import { TwitchEventRule } from "@storyteller/components/src/api/storyteller/twitch_event_rules/ListTwitchEventRules";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import {
- faEdit,
- faExternalLinkAlt,
- faTrash,
-} from "@fortawesome/free-solid-svg-icons";
-import { Link } from "react-router-dom";
-import { TtsModelListItem } from "@storyteller/components/src/api/tts/ListTtsModels";
-
-interface Props {
- rule: TwitchEventRule;
- allTtsModelsByToken: Map;
- hideButtons?: boolean;
-}
-
-function TwitchEventRuleElement(props: Props) {
- const hideButtons = !!props.hideButtons;
-
- let subtitle = <>>;
- let description = <>>;
-
- if (
- props.rule.event_match_predicate.bits_cheermote_name_exact_match !==
- undefined
- ) {
- subtitle = (
- <>
- Cheermote name matches "
- {
- props.rule.event_match_predicate.bits_cheermote_name_exact_match
- .cheermote_name
- }
- "
- >
- );
- } else if (
- props.rule.event_match_predicate.bits_cheermote_prefix_spend_threshold !==
- undefined
- ) {
- subtitle = (
- <>
- Cheermote prefix is "
- {
- props.rule.event_match_predicate.bits_cheermote_prefix_spend_threshold
- .cheermote_prefix
- }
- " and spend at least{" "}
- {
- props.rule.event_match_predicate.bits_cheermote_prefix_spend_threshold
- .minimum_bits_spent
- }{" "}
- bits
- >
- );
- } else if (
- props.rule.event_match_predicate.bits_spend_threshold !== undefined
- ) {
- subtitle = (
- <>
- Spend at least{" "}
- {
- props.rule.event_match_predicate.bits_spend_threshold
- .minimum_bits_spent
- }{" "}
- bits
- >
- );
- } else if (
- props.rule.event_match_predicate.channel_points_reward_name_exact_match !==
- undefined
- ) {
- subtitle = (
- <>
- Reward name matches "
- {
- props.rule.event_match_predicate
- .channel_points_reward_name_exact_match.reward_name
- }
- "
- >
- );
- }
-
- if (props.rule.event_response.tts_single_voice !== undefined) {
- const token = props.rule.event_response.tts_single_voice.tts_model_token;
-
- let model = props.allTtsModelsByToken.get(token);
- let modelName = token;
-
- if (!!model) {
- modelName = model.title;
- }
-
- let link = `https://fakeyou.com/tts/${token}`;
-
- description = (
- <>
-
- TTS with voice:
-
- {modelName}{" "}
-
-
-
-
- >
- );
- } else if (props.rule.event_response.tts_random_voice !== undefined) {
- const tokens = props.rule.event_response.tts_random_voice.tts_model_tokens;
-
- let modelNameAndLinks = tokens.map((token, index) => {
- let model = props.allTtsModelsByToken.get(token);
- let link = `https://fakeyou.com/tts/${token}`;
-
- let modelName = token;
- if (!!model) {
- modelName = model.title;
- }
-
- return (
-
- {modelName}{" "}
-
-
-
-
- );
- });
-
- description = (
- <>
-
- TTS with a random voice from the following {modelNameAndLinks.length}{" "}
- voice(s):
-
-
- >
- );
- }
-
- const editUrl = `/tts_configs/edit/${props.rule.token}`;
- const deleteUrl = `/tts_configs/delete/${props.rule.token}`;
-
- let buttons = <>>;
- if (!hideButtons) {
- buttons = (
- <>
-
-
-
- Edit
-
-
-
- Delete
-
-
- >
- );
- }
-
- return (
-
-
-
-
{subtitle}
-
{description}
-
- {buttons}
-
-
-
- );
-}
-
-export { TwitchEventRuleElement };
diff --git a/packages/power.stream/src/react-app-env.d.ts b/packages/power.stream/src/react-app-env.d.ts
deleted file mode 100644
index 6431bc5fc6..0000000000
--- a/packages/power.stream/src/react-app-env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-///
diff --git a/packages/power.stream/src/reportWebVitals.ts b/packages/power.stream/src/reportWebVitals.ts
deleted file mode 100644
index 49a2a16e0f..0000000000
--- a/packages/power.stream/src/reportWebVitals.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { ReportHandler } from 'web-vitals';
-
-const reportWebVitals = (onPerfEntry?: ReportHandler) => {
- if (onPerfEntry && onPerfEntry instanceof Function) {
- import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
- getCLS(onPerfEntry);
- getFID(onPerfEntry);
- getFCP(onPerfEntry);
- getLCP(onPerfEntry);
- getTTFB(onPerfEntry);
- });
- }
-};
-
-export default reportWebVitals;
diff --git a/packages/power.stream/src/setupTests.ts b/packages/power.stream/src/setupTests.ts
deleted file mode 100644
index 8f2609b7b3..0000000000
--- a/packages/power.stream/src/setupTests.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-// jest-dom adds custom jest matchers for asserting on DOM nodes.
-// allows you to do things like:
-// expect(element).toHaveTextContent(/react/i)
-// learn more: https://github.com/testing-library/jest-dom
-import '@testing-library/jest-dom';
diff --git a/packages/power.stream/src/twitch/CheerUtil.ts b/packages/power.stream/src/twitch/CheerUtil.ts
deleted file mode 100644
index c582542193..0000000000
--- a/packages/power.stream/src/twitch/CheerUtil.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-
-const CHEER_REGEX = /^([A-Za-z]+)(\d+)?$/;
-
-interface ParsedCheer {
- cheerPrefix?: string,
- bitValue?: number,
-}
-
-export class CheerUtil {
-
- public static parseCheerString(cheerString: string) : ParsedCheer {
- const matches = cheerString.trim().match(CHEER_REGEX)
-
- let cheerPrefix = undefined;
- let bitValue = undefined;
-
- if (!!matches && matches.length > 1) {
- cheerPrefix = matches[1];
- if (matches.length === 3 && matches[2] !== undefined) {
- // NB(1): The second match group can be 'undefined' if no number is present. (Optional matching.)
- let maybeBits = parseInt(matches[2]);
- if (!isNaN(maybeBits) && maybeBits > 0) {
- bitValue = maybeBits;
- }
- }
- }
-
- return {
- cheerPrefix: cheerPrefix,
- bitValue: bitValue,
- };
- }
-
- public static joinCheerAndPrefix(cheerPrefix?: string, bitValue?: number) : string {
- return `${cheerPrefix || ''}${bitValue || ''}`;
- }
-}
\ No newline at end of file
diff --git a/packages/power.stream/src/twitch/Cheers.ts b/packages/power.stream/src/twitch/Cheers.ts
deleted file mode 100644
index ea0901fe74..0000000000
--- a/packages/power.stream/src/twitch/Cheers.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-
-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[] = [
- "Cheer",
- "DoodleCheer",
- "BibleThump",
- "cheerwhal",
- "Corgo",
- "uni",
- "ShowLove",
- "Party",
- "SeemsGood",
- "Pride",
- "Kappa",
- "FrankerZ",
- "HeyGuys",
- "DansGame",
- "EleGiggle",
- "TriHard",
- "Kreygasm",
- "4Head",
- "SwiftRage",
- "NotLikeThis",
- "FailFish",
- "VoHiYo",
- "PJSalt",
- "MrDestructoid",
- "bday",
- "RIPCheer",
- "Shamrock",
- "BitBoss",
- "Streamlabs",
- "Muxy",
- "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];
diff --git a/packages/power.stream/tsconfig.json b/packages/power.stream/tsconfig.json
deleted file mode 100644
index a273b0cfc0..0000000000
--- a/packages/power.stream/tsconfig.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "compilerOptions": {
- "target": "es5",
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
- ],
- "allowJs": true,
- "skipLibCheck": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "strict": true,
- "forceConsistentCasingInFileNames": true,
- "noFallthroughCasesInSwitch": true,
- "module": "esnext",
- "moduleResolution": "node",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "jsx": "react-jsx"
- },
- "include": [
- "src"
- ]
-}