Initial
10
.dockerignore
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
.git/
|
||||||
|
.cache/
|
||||||
|
node_modules/
|
||||||
|
dashboard/
|
||||||
|
extension/
|
||||||
|
graphics/
|
||||||
|
/streamdeck-plugin
|
||||||
|
.gitignore
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
13
.eslintignore
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
.eslintrc.js
|
||||||
|
.eslintrc.*.js
|
||||||
|
webpack.config.mjs
|
||||||
|
vetur.config.js
|
||||||
|
dashboard/**/*
|
||||||
|
extension/**/*
|
||||||
|
graphics/**/*
|
||||||
|
node_modules/**/*
|
||||||
|
player-templates/**/*
|
||||||
|
schemas/**/*
|
||||||
|
streamdeck-plugin/**/*
|
||||||
|
src/types/schemas/**/*
|
||||||
|
src/types/augment-window.d.ts
|
70
.eslintrc.browser.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
parser: 'vue-eslint-parser',
|
||||||
|
parserOptions: {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
project: path.join(__dirname, 'tsconfig.browser.json'),
|
||||||
|
extraFileExtensions: ['.vue'],
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
nodecg: 'readonly',
|
||||||
|
NodeCG: 'readonly',
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
'@typescript-eslint',
|
||||||
|
],
|
||||||
|
extends: [
|
||||||
|
'plugin:vue/essential',
|
||||||
|
'airbnb-base',
|
||||||
|
'airbnb-typescript/base',
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:import/typescript',
|
||||||
|
],
|
||||||
|
settings: {
|
||||||
|
'import/resolver': {
|
||||||
|
typescript: {
|
||||||
|
// This is needed to properly resolve paths.
|
||||||
|
project: path.join(__dirname, 'tsconfig.browser.json'),
|
||||||
|
},
|
||||||
|
webpack: {
|
||||||
|
config: path.join(__dirname, 'webpack.config.mjs'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'import/extensions': ['.js', '.jsx', '.ts', '.tsx'],
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// Everything is compiled for the browser so dev dependencies are fine.
|
||||||
|
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
|
||||||
|
// max-len set to ignore "import" lines (as they usually get long and messy).
|
||||||
|
'max-len': ['error', { code: 100, ignorePattern: '^import\\s.+\\sfrom\\s.+;' }],
|
||||||
|
// I mainly have this off as it ruins auto import sorting in VSCode.
|
||||||
|
'object-curly-newline': 'off',
|
||||||
|
'@typescript-eslint/lines-between-class-members': 'off',
|
||||||
|
'vue/html-self-closing': ['error'],
|
||||||
|
'class-methods-use-this': 'off',
|
||||||
|
'no-param-reassign': ['error', {
|
||||||
|
props: true,
|
||||||
|
ignorePropertyModificationsFor: [
|
||||||
|
'state', // for vuex state
|
||||||
|
'acc', // for reduce accumulators
|
||||||
|
'e', // for e.returnvalue
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
'import/extensions': ['error', 'ignorePackages', {
|
||||||
|
js: 'never',
|
||||||
|
jsx: 'never',
|
||||||
|
ts: 'never',
|
||||||
|
tsx: 'never',
|
||||||
|
}],
|
||||||
|
|
||||||
|
'no-restricted-syntax': 'off',
|
||||||
|
'vue/multi-word-component-names': 'off', // Check about this once things are all using decorators!
|
||||||
|
}
|
||||||
|
};
|
60
.eslintrc.extension.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
project: path.join(__dirname, 'tsconfig.extension.json'),
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
'@typescript-eslint',
|
||||||
|
],
|
||||||
|
extends: [
|
||||||
|
'airbnb-base',
|
||||||
|
'airbnb-typescript/base',
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:import/typescript',
|
||||||
|
],
|
||||||
|
settings: {
|
||||||
|
'import/resolver': {
|
||||||
|
typescript: {
|
||||||
|
// This is needed to properly resolve paths.
|
||||||
|
project: path.join(__dirname, 'tsconfig.extension.json'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'import/extensions': ['.js', '.jsx', '.ts', '.tsx'],
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/lines-between-class-members': 'off',
|
||||||
|
// max-len set to ignore "import" lines (as they usually get long and messy).
|
||||||
|
'max-len': ['error', { code: 100, ignorePattern: '^import\\s.+\\sfrom\\s.+;' }],
|
||||||
|
// I mainly have this off as it ruins auto import sorting in VSCode.
|
||||||
|
'object-curly-newline': 'off',
|
||||||
|
'import/extensions': ['error', 'ignorePackages', {
|
||||||
|
js: 'never',
|
||||||
|
jsx: 'never',
|
||||||
|
ts: 'never',
|
||||||
|
tsx: 'never',
|
||||||
|
}],
|
||||||
|
|
||||||
|
'no-restricted-syntax': 'off',
|
||||||
|
'no-await-in-loop': 'off',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Overrides for types.
|
||||||
|
overrides: [{
|
||||||
|
files: ['**/*.d.ts'],
|
||||||
|
rules: {
|
||||||
|
// @typescript-eslint/no-unused-vars does not work with type definitions
|
||||||
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
|
// Sometimes eslint complains about this for types (usually when using namespaces).
|
||||||
|
'import/prefer-default-export': 'off',
|
||||||
|
// Types are only used for development (usually!) so dev dependencies are fine.
|
||||||
|
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
};
|
4
.eslintrc.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
// This is here just to make sure ESLint doesn't check NodeCG's own configuration.
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
};
|
136
.gitignore
vendored
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# Built files
|
||||||
|
/dashboard/
|
||||||
|
/extension/
|
||||||
|
/graphics/
|
||||||
|
|
||||||
|
streamdeck-plugin/com.esamarathon.streamdeck.sdPlugin
|
||||||
|
streamdeck-plugin/DistributionTool.exe
|
||||||
|
*.psd
|
||||||
|
/boxart/
|
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "shared"]
|
||||||
|
path = shared
|
||||||
|
url = https://github.com/esamarathon/esa-layouts-shared
|
7
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"eslint.workingDirectories": [
|
||||||
|
"shared",
|
||||||
|
"streamdeck-plugin"
|
||||||
|
],
|
||||||
|
"typescript.tsdk": "node_modules\\typescript\\lib"
|
||||||
|
}
|
25
Dockerfile
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# NEEDS SOME IMPROVEMENTS, MAINLY TO DO WITH THE
|
||||||
|
# SPEEDCONTROL BRANCH USED AND CONFIG SETTINGS
|
||||||
|
|
||||||
|
FROM node:10
|
||||||
|
WORKDIR /home/node/app
|
||||||
|
RUN chown -R node:node /home/node/app
|
||||||
|
# Install some packages to install NodeCG.
|
||||||
|
RUN npm install bower -g && npm install nodecg-cli -g
|
||||||
|
USER node
|
||||||
|
RUN nodecg setup
|
||||||
|
# Install latest nodecg-speedcontrol.
|
||||||
|
RUN nodecg install speedcontrol/nodecg-speedcontrol
|
||||||
|
# Copy over this bundle's files and fully build it.
|
||||||
|
WORKDIR /home/node/app/bundles/esa-layouts
|
||||||
|
USER root
|
||||||
|
RUN chown -R node:node /home/node/app/bundles/esa-layouts
|
||||||
|
USER node
|
||||||
|
COPY --chown=node:node package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY --chown=node:node . .
|
||||||
|
RUN npm run build
|
||||||
|
# Run NodeCG.
|
||||||
|
WORKDIR /home/node/app
|
||||||
|
EXPOSE 9090
|
||||||
|
CMD [ "nodecg", "start" ]
|
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 European Speedrunner Assembly
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
113
README.md
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# esa-layouts
|
||||||
|
|
||||||
|
> The on-screen graphics used during European Speedrunner Assembly's "marathon" events.
|
||||||
|
|
||||||
|
*This is a bundle for [NodeCG](https://nodecg.dev); if you do not understand what that is, we advise you read their website first for more information.*
|
||||||
|
|
||||||
|
***This documentation isn't fully complete and may have errors, but intends to be as correct as possible.***
|
||||||
|
|
||||||
|
This is a [NodeCG](https://nodecg.dev) v1.8.1 bundle. You will need to have NodeCG v1.8.1 or above installed to run it. It also requires you to install the [nodecg-speedcontrol](https://github.com/speedcontrol/nodecg-speedcontrol) bundle (of which you may also need to install the latest changes instead of the most stable release).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
You will need [Node.js](https://nodejs.org) (16.x LTS tested) and [git](https://git-scm.com/) installed to install NodeCG, then see the [NodeCG documentation](https://www.nodecg.dev/docs/installing) on how to install that. I also suggest installing `nodecg-cli`; information on that is also on the documentation just linked (**the guide below will assume you have done this!**). You may also need to install the appropriate build tools for whichever platform you are running on; for example if you are on Windows you can either install it while installing Node.js, or using [windows-build-tools](https://github.com/felixrieseberg/windows-build-tools).
|
||||||
|
|
||||||
|
Next, clone the `build` branch of this repository into the NodeCG `bundles` folder and install the dependencies:
|
||||||
|
> ```
|
||||||
|
> cd bundles
|
||||||
|
> git clone https://github.com/esamarathon/esa-layouts.git --branch build
|
||||||
|
> cd esa-layouts
|
||||||
|
> npm install --production
|
||||||
|
> ```
|
||||||
|
|
||||||
|
You will probably also want a default configuration you can fill in, which can be created using:
|
||||||
|
> `nodecg defaultconfig`.
|
||||||
|
|
||||||
|
Then, to get the most recent changes for [nodecg-speedcontrol](https://github.com/speedcontrol/nodecg-speedcontrol), clone the `build` branch and install dependencies, similar to above:
|
||||||
|
> ```
|
||||||
|
> cd ..
|
||||||
|
> git clone https://github.com/speedcontrol/nodecg-speedcontrol.git --branch build
|
||||||
|
> cd nodecg-speedcontrol
|
||||||
|
> npm install --production
|
||||||
|
> ```
|
||||||
|
|
||||||
|
In addition, to have the `videos` assets automatically audio normalised, you must have `python` (v3), `ffmpeg`, and [`ffmpeg-normalize`](https://github.com/slhck/ffmpeg-normalize) available in your system's `PATH`. If you don't have all of these, the check will fail and videos will just not be touched. For Windows, `python` (v3) should be automatically installed when you install Node.js built tools, if you chose to do that.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
*Not everything you can set is documented here; if you're an advanced user we advise you take a look at the included [configschema.json](configschema.json) file.*
|
||||||
|
|
||||||
|
This bundle heavily relies on the [obs-websocket](https://github.com/Palakis/obs-websocket) plugin, so make sure you have this installed (custom address/port and password can be specified in the bundle's config if needed).
|
||||||
|
|
||||||
|
This bundle also heavily relies on information from a RabbitMQ server, and an instance of our fork of the [GamesDoneQuick donation tracker](https://github.com/esamarathon/donation-tracker).
|
||||||
|
|
||||||
|
### Stream Deck Plugin
|
||||||
|
|
||||||
|
Included with this bundle is a plugin for the Elgato Stream Deck software that can be used by various crew members during events. Once you have the Stream Deck software installed, you can install the plugin by running the file `com.esamarathon.streamdeck.streamDeckPlugin` in the `streamdeck-plugin/Release` directory. Currently, you need to set the actions up yourself in the software, so it can easily be customised on the fly.
|
||||||
|
|
||||||
|
### FlagCarrier Configuration
|
||||||
|
|
||||||
|
You will need to install the [speedcontrol-flagcarrier](https://github.com/speedcontrol/speedcontrol-flagcarrier) bundle to use this part, along with using one of the FlagCarrier applications to set them.
|
||||||
|
|
||||||
|
**Hosts (the ones on camera):**
|
||||||
|
- group_id: `hosts`
|
||||||
|
- positions: `left,midleft,middle,midright,right`
|
||||||
|
|
||||||
|
### Text-To-Speech Donations
|
||||||
|
|
||||||
|
This can be enabled via the config, controlled via Stream Deck buttons available in the included extension, and the graphic files `tts.html` will play them when requested. You will need to set a specific URL for the `voiceAPI` setting in the config though, so unless you know this it's somewhat useless, sorry.
|
||||||
|
|
||||||
|
### Music Player
|
||||||
|
|
||||||
|
This bundle can interface with [foobar2000](https://www.foobar2000.org/) using the [beefweb](https://github.com/hyperblast/beefweb) plugin. Set up foobar2000 however you want it to play music (we use a long playlist on shuffle, and set a fade in/out on pause), make sure the correct username/password are set in the configuration fiole, and this bundle with automatically play music when needed. It will only play if the scene name ends in `[M]`, for example, `Intermission [M]`.
|
||||||
|
|
||||||
|
## Other Information
|
||||||
|
|
||||||
|
### Events Used For
|
||||||
|
|
||||||
|
Here's a list of events this bundle has been used at so far, most recent first.
|
||||||
|
|
||||||
|
* UKSG Autumn 2021
|
||||||
|
* ESA Summer 2021
|
||||||
|
* UKSG Summer 2021
|
||||||
|
* UKSG Spring 2021
|
||||||
|
* ESA Winter 2021
|
||||||
|
* UKSG Winter 2021
|
||||||
|
* UKSG Autumn 2020
|
||||||
|
* ESA Summer 2020
|
||||||
|
* UKSG Summer 2020
|
||||||
|
* ESA Corona Relief
|
||||||
|
* ESA Together
|
||||||
|
* UKSG Spring 2020
|
||||||
|
* ESA Winter 2020
|
||||||
|
* UKSG Winter 2020
|
||||||
|
* ESA @ Malmö Vinterspelen 2019
|
||||||
|
* ESA @ DreamHack Winter 2019
|
||||||
|
* UKSG Autumn 2019
|
||||||
|
* ESA Summer 2019 (including some streams on [SpeedGaming](https://www.twitch.tv/speedgaming) during the event).
|
||||||
|
* UKSG Summer 2019
|
||||||
|
* All BSG's from BSG @Home 2020 onwards (Aug 2020)
|
||||||
|
* All Hekathon events from 2021 onwards
|
||||||
|
|
||||||
|
### Previous Bundles
|
||||||
|
|
||||||
|
Here's a list of previous bundles that used to fulfil the purpose of this one, when we kept making new repositories for most of them.
|
||||||
|
|
||||||
|
* [esaw19-layouts](https://github.com/esamarathon/esaw19-layouts)
|
||||||
|
* ESA Winter 2019
|
||||||
|
* ESA @ TwitchCon Europe 2019
|
||||||
|
* [esas18-layouts](https://github.com/esamarathon/esas18-layouts)
|
||||||
|
* ESA Summer 2018
|
||||||
|
* UKSG Fall 2018
|
||||||
|
* ESA Movember
|
||||||
|
* UKSG Winter 2019
|
||||||
|
* UKSG Spring 2019
|
||||||
|
* [esaw18-layouts](https://github.com/esamarathon/esaw18-layouts)
|
||||||
|
* ESA Winter 2018
|
||||||
|
* [esa17-layouts](https://github.com/esamarathon/esa17-layouts)
|
||||||
|
* ESA 2017
|
||||||
|
|
||||||
|
### Credits
|
||||||
|
|
||||||
|
* Country flags sourced from [speedrun.com](https://www.speedrun.com/).
|
||||||
|
* [clip.ts](src/graphics/_misc/clip.ts), modified from a version originally written by [Hoishin](https://github.com/hoishin).
|
748
configschema.json
Normal file
@ -0,0 +1,748 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"definitions": {
|
||||||
|
"bidwarBias": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"bidId": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 0,
|
||||||
|
"$comment": "ID of relevant bid in the tracker."
|
||||||
|
},
|
||||||
|
"option1Id": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 0,
|
||||||
|
"$comment": "ID of option of team 1 in the tracker on the above bid."
|
||||||
|
},
|
||||||
|
"option2Id": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 0,
|
||||||
|
"$comment": "ID of option of team 2 in the tracker on the above bid."
|
||||||
|
},
|
||||||
|
"optionTitle": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "Commentary Bias",
|
||||||
|
"$comment": "String to be used on layout to describe the visual bar."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"bidId",
|
||||||
|
"option1Id",
|
||||||
|
"option2Id",
|
||||||
|
"optionTitle"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"useTestData": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"event": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"theme": {
|
||||||
|
"$comment": "Theme to be used in the graphical overlays; will use default if none supplied.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"shorts": {
|
||||||
|
"$comment": "This/these must match the tracker, if that feature is enabled.",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"uniqueItems": true,
|
||||||
|
"minItems": 1,
|
||||||
|
"maxItems": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": "EVENT_SHORT"
|
||||||
|
},
|
||||||
|
"thisEvent": {
|
||||||
|
"$comment": "If the 'event' has multiple tracker events, this a 1-indexed value of which one is applicable to this stream from the shorts array.",
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 2,
|
||||||
|
"default": 1
|
||||||
|
},
|
||||||
|
"online": {
|
||||||
|
"$comment": "If this event is ran online and has no on-site presence.",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$comment": "If set to 'partial', will only do basic changes.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"partial",
|
||||||
|
"full"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"fallbackTwitchTitle": {
|
||||||
|
"$comment": "Set the fallback Twitch title for this event; {{total}} and {{run}} can be used as placeholders (see source code).",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"shorts",
|
||||||
|
"thisEvent",
|
||||||
|
"online"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"omnibar": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"miniCredits": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"header": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "Thanks to all these people!"
|
||||||
|
},
|
||||||
|
"screeners": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tech": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"header"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"miniCredits"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"streamdeck": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"port": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 9091
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "DEFAULT_KEY"
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"port",
|
||||||
|
"key",
|
||||||
|
"debug"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"rabbitmq": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"protocol": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "amqps"
|
||||||
|
},
|
||||||
|
"hostname": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "mq.esamarathon.com"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "USERNAME"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "PASSWORD"
|
||||||
|
},
|
||||||
|
"vhost": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "esa_prod"
|
||||||
|
},
|
||||||
|
"queuePrepend": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"protocol",
|
||||||
|
"hostname",
|
||||||
|
"username",
|
||||||
|
"password",
|
||||||
|
"vhost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"obs": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"address": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "localhost:4444"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"canvasResolution": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"width": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 1920
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 1080
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"width",
|
||||||
|
"height"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"names": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"scenes": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"commercials": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "Intermission (commercials)"
|
||||||
|
},
|
||||||
|
"gameLayout": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "Game Layout"
|
||||||
|
},
|
||||||
|
"readerIntroduction": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "Reader Introduction"
|
||||||
|
},
|
||||||
|
"intermission": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "Intermission"
|
||||||
|
},
|
||||||
|
"intermissionPlayer": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "Intermission Player"
|
||||||
|
},
|
||||||
|
"countdown": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "Countdown"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"commercials",
|
||||||
|
"gameLayout",
|
||||||
|
"readerIntroduction",
|
||||||
|
"intermission",
|
||||||
|
"intermissionPlayer",
|
||||||
|
"countdown"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sources": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"gameSources": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"uniqueItems": true,
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": "Game Source"
|
||||||
|
},
|
||||||
|
"cameraSources": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"uniqueItems": true,
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": "Camera Source"
|
||||||
|
},
|
||||||
|
"cameraSourceCrowd": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"twitchSources": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"uniqueItems": true,
|
||||||
|
"minItems": 1,
|
||||||
|
"maxItems": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": "Twitch Source"
|
||||||
|
},
|
||||||
|
"videoPlayer": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "Video Player Source"
|
||||||
|
},
|
||||||
|
"donationSound": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "Donation Sound"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"gameSources",
|
||||||
|
"cameraSources",
|
||||||
|
"twitchSources",
|
||||||
|
"videoPlayer",
|
||||||
|
"donationSound"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"groups": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"gameCaptures": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"uniqueItems": true,
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": "Game Capture"
|
||||||
|
},
|
||||||
|
"cameraCaptures": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"uniqueItems": true,
|
||||||
|
"minItems": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": "Camera Capture"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"gameCaptures",
|
||||||
|
"cameraCaptures"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"scenes",
|
||||||
|
"sources",
|
||||||
|
"groups"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"address",
|
||||||
|
"password",
|
||||||
|
"canvasResolution",
|
||||||
|
"names"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"music": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"address": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "localhost:8880"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"default": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"address",
|
||||||
|
"username",
|
||||||
|
"password"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"x32": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"ip": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "10.20.30.42"
|
||||||
|
},
|
||||||
|
"localPort": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 52361
|
||||||
|
},
|
||||||
|
"xr18": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"ip",
|
||||||
|
"localPort",
|
||||||
|
"xr18"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"xkeys": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tracker": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"address": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "donations.esamarathon.com"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "USERNAME"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "PASSWORD"
|
||||||
|
},
|
||||||
|
"prizesUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "prizes.esamarathon.com"
|
||||||
|
},
|
||||||
|
"commentaryBias": {
|
||||||
|
"$ref": "#/definitions/bidwarBias"
|
||||||
|
},
|
||||||
|
"otherBidwarBias": {
|
||||||
|
"$ref": "#/definitions/bidwarBias"
|
||||||
|
},
|
||||||
|
"donationTotalInTitle": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"address",
|
||||||
|
"username",
|
||||||
|
"password",
|
||||||
|
"prizesUrl",
|
||||||
|
"commentaryBias",
|
||||||
|
"otherBidwarBias",
|
||||||
|
"donationTotalInTitle"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tts": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"voiceAPI": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "URL"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "TOKEN"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"voiceAPI",
|
||||||
|
"key"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"flagcarrier": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"allowedDevices": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"uniqueItems": true,
|
||||||
|
"minItems": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "stream1"
|
||||||
|
},
|
||||||
|
"availableButtons": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"name": "PC 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"name": "PC 2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"name": "Console 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4",
|
||||||
|
"name": "Console 2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"group",
|
||||||
|
"availableButtons"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"offsite": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"address": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "https://app.esamarathon.com/offsite/api"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "SECRET_KEY"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"address",
|
||||||
|
"key"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"server": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"address": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "https://register.esamarathon.com/api"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "SECRET_KEY"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"address",
|
||||||
|
"key"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "BOT_TOKEN"
|
||||||
|
},
|
||||||
|
"textChannelId": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "TEXT_CHANNEL_ID"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"token",
|
||||||
|
"textChannelId"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"streamlabsCharity": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"apiUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "API_URL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"apiUrl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"therungg": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"useTestData",
|
||||||
|
"event",
|
||||||
|
"omnibar",
|
||||||
|
"streamdeck",
|
||||||
|
"rabbitmq",
|
||||||
|
"obs",
|
||||||
|
"music",
|
||||||
|
"x32",
|
||||||
|
"xkeys",
|
||||||
|
"tracker",
|
||||||
|
"tts",
|
||||||
|
"flagcarrier",
|
||||||
|
"offsite",
|
||||||
|
"server",
|
||||||
|
"discord",
|
||||||
|
"streamlabsCharity",
|
||||||
|
"therungg"
|
||||||
|
]
|
||||||
|
}
|
1
obs-assets/BlankTransition.avs
Normal file
@ -0,0 +1 @@
|
|||||||
|
BlankClip(length=72,width=16,height=9,pixel_type="RGB32",fps=60,audio_rate=0)
|
BIN
obs-assets/BlankTransition.mov
Normal file
BIN
obs-assets/DonationSound.mp3
Normal file
BIN
obs-assets/Stinger.webm
Normal file
1
obs-assets/encode.bat
Normal file
@ -0,0 +1 @@
|
|||||||
|
ffmpeg -i "BlankTransition.avs" -codec:v qtrle "BlankTransition.mov"
|
10571
package-lock.json
generated
Normal file
384
package.json
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
{
|
||||||
|
"name": "esa-layouts",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "The on-screen graphics used during European Speedrunner Assembly's \"marathon\" events.",
|
||||||
|
"homepage": "https://github.com/esamarathon/esa-layouts#readme",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/esamarathon/esa-layouts/issues"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/esamarathon/esa-layouts.git"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "zoton2",
|
||||||
|
"contributors": [
|
||||||
|
"BtbN"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"autofix": "run-s autofix:*",
|
||||||
|
"autofix:browser": "eslint --fix --ext .ts,.vue src/dashboard && eslint --fix --ext .ts,.vue src/graphics && eslint --fix --ext .ts,.vue src/browser_shared",
|
||||||
|
"autofix:extension": "eslint --fix --ext .ts src/extension && eslint --fix --ext .d.ts src/types",
|
||||||
|
"build": "run-s build:*",
|
||||||
|
"build:browser": "cross-env NODE_ENV=production webpack",
|
||||||
|
"build:extension": "tsc -b tsconfig.extension.json",
|
||||||
|
"clean": "trash node_modules/.cache && trash dashboard && trash graphics && trash extension",
|
||||||
|
"schema-types": "nodecg schema-types",
|
||||||
|
"start": "node ../..",
|
||||||
|
"watch": "run-p watch:*",
|
||||||
|
"watch:browser": "webpack -w",
|
||||||
|
"watch:extension": "tsc -b tsconfig.extension.json -w",
|
||||||
|
"postinstall": "cd shared && node postinstall.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@esamarathon/mq-events": "^1.0.1",
|
||||||
|
"clone": "^2.1.2",
|
||||||
|
"discord.js": "^13.17.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"module-alias": "^2.2.3",
|
||||||
|
"moment": "^2.30.1",
|
||||||
|
"needle": "^3.3.1",
|
||||||
|
"sharp": "^0.33.2",
|
||||||
|
"socket.io-client": "^4.7.4",
|
||||||
|
"speedcontrol-util": "github:speedcontrol/speedcontrol-util.git#build",
|
||||||
|
"streamdeck-util": "^0.4.0",
|
||||||
|
"uuid": "^9.0.1",
|
||||||
|
"ws": "^8.16.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@fontsource/barlow-condensed": "^4.5.9",
|
||||||
|
"@fontsource/montserrat": "^4.5.14",
|
||||||
|
"@fontsource/roboto": "^4.5.8",
|
||||||
|
"@mdi/font": "^7.4.47",
|
||||||
|
"@nodecg/types": "^2.1.12",
|
||||||
|
"@types/async": "^3.2.24",
|
||||||
|
"@types/clone": "^2.1.4",
|
||||||
|
"@types/lodash": "^4.14.202",
|
||||||
|
"@types/module-alias": "^2.0.4",
|
||||||
|
"@types/needle": "^3.3.0",
|
||||||
|
"@types/node": "^18.19.8",
|
||||||
|
"@types/sharp": "^0.31.1",
|
||||||
|
"@types/uuid": "^9.0.7",
|
||||||
|
"@types/webpack-env": "^1.18.4",
|
||||||
|
"@types/ws": "^8.5.10",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||||
|
"@typescript-eslint/parser": "^5.62.0",
|
||||||
|
"@vue/component-compiler-utils": "^3.3.0",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"css-loader": "^6.9.1",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
|
"deepmerge": "^4.3.1",
|
||||||
|
"eslint": "^8.56.0",
|
||||||
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
|
"eslint-config-airbnb-typescript": "^16.2.0",
|
||||||
|
"eslint-import-resolver-typescript": "^2.7.1",
|
||||||
|
"eslint-import-resolver-webpack": "^0.13.8",
|
||||||
|
"eslint-plugin-import": "^2.29.1",
|
||||||
|
"eslint-plugin-vue": "^8.7.1",
|
||||||
|
"file-loader": "^6.2.0",
|
||||||
|
"fitty": "^2.4.2",
|
||||||
|
"fork-ts-checker-webpack-plugin": "^6.5.3",
|
||||||
|
"globby": "^12.2.0",
|
||||||
|
"gsap": "^3.12.5",
|
||||||
|
"html-webpack-plugin": "^5.6.0",
|
||||||
|
"mini-css-extract-plugin": "^2.7.7",
|
||||||
|
"nodecg-cli": "^8.6.8",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"sass": "~1.32",
|
||||||
|
"sass-loader": "^12.6.0",
|
||||||
|
"style-loader": "^3.3.4",
|
||||||
|
"trash-cli": "^5.0.0",
|
||||||
|
"ts-essentials": "^9.4.1",
|
||||||
|
"ts-loader": "^9.5.1",
|
||||||
|
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
||||||
|
"typescript": "^4.9.5",
|
||||||
|
"vue": "^2.7.15",
|
||||||
|
"vue-class-component": "^7.2.6",
|
||||||
|
"vue-eslint-parser": "^8.3.0",
|
||||||
|
"vue-hot-reload-api": "^2.3.4",
|
||||||
|
"vue-loader": "^15.11.1",
|
||||||
|
"vue-property-decorator": "^9.1.2",
|
||||||
|
"vue-router": "^3.6.5",
|
||||||
|
"vue-style-loader": "^4.1.3",
|
||||||
|
"vue-template-compiler": "^2.7.16",
|
||||||
|
"vuedraggable": "^2.24.3",
|
||||||
|
"vuetify": "^2.7.1",
|
||||||
|
"vuetify-loader": "^1.9.2",
|
||||||
|
"vuex": "^3.6.2",
|
||||||
|
"vuex-class": "^0.3.2",
|
||||||
|
"vuex-class-state2way": "^1.0.1",
|
||||||
|
"vuex-module-decorators": "^1.2.0",
|
||||||
|
"webpack": "^5.89.0",
|
||||||
|
"webpack-cli": "^4.10.0",
|
||||||
|
"webpack-livereload-plugin": "^3.0.2"
|
||||||
|
},
|
||||||
|
"nodecg": {
|
||||||
|
"compatibleRange": "^1.9.0||^2",
|
||||||
|
"bundleDependencies": {
|
||||||
|
"nodecg-speedcontrol": "^2.4.0"
|
||||||
|
},
|
||||||
|
"dashboardPanels": [
|
||||||
|
{
|
||||||
|
"name": "game-layout-override",
|
||||||
|
"title": "Game Layout Override",
|
||||||
|
"width": 2,
|
||||||
|
"file": "game-layout-override.html",
|
||||||
|
"workspace": "ESA",
|
||||||
|
"headerColor": "#c49215"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "media-box-control",
|
||||||
|
"title": "Media Box Control",
|
||||||
|
"width": 3,
|
||||||
|
"file": "media-box-control.html",
|
||||||
|
"workspace": "Z2 - ESA Advanced",
|
||||||
|
"headerColor": "#c49215"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "intermission-slide-control",
|
||||||
|
"title": "Intermission Slide Control",
|
||||||
|
"width": 3,
|
||||||
|
"file": "intermission-slide-control.html",
|
||||||
|
"workspace": "Z2 - ESA Advanced",
|
||||||
|
"headerColor": "#c49215"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "commentators",
|
||||||
|
"title": "Commentators",
|
||||||
|
"width": 2,
|
||||||
|
"file": "commentators.html",
|
||||||
|
"headerColor": "#c49215"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tts-control",
|
||||||
|
"title": "Text-To-Speech Control",
|
||||||
|
"width": 3,
|
||||||
|
"file": "tts-control.html",
|
||||||
|
"workspace": "Z8 - N/A",
|
||||||
|
"headerColor": "#c49215"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "intermission-player-control",
|
||||||
|
"title": "Intermission Player Control",
|
||||||
|
"width": 3,
|
||||||
|
"file": "intermission-player-control.html",
|
||||||
|
"workspace": "Z2 - ESA Advanced",
|
||||||
|
"headerColor": "#c49215"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "upcoming-run-control",
|
||||||
|
"title": "Upcoming Run Control",
|
||||||
|
"width": 3,
|
||||||
|
"file": "upcoming-run-control.html",
|
||||||
|
"workspace": "ESA",
|
||||||
|
"headerColor": "#c49215"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "obs-control",
|
||||||
|
"title": "OBS Control",
|
||||||
|
"width": 3,
|
||||||
|
"file": "obs-control.html",
|
||||||
|
"headerColor": "#c49215"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "donation-reader-control",
|
||||||
|
"title": "Donation Reader Control",
|
||||||
|
"width": 2,
|
||||||
|
"file": "donation-reader-control.html",
|
||||||
|
"headerColor": "#c49215"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "countdown-control",
|
||||||
|
"title": "Countdown Control",
|
||||||
|
"width": 3,
|
||||||
|
"file": "countdown-control.html",
|
||||||
|
"workspace": "Z2 - ESA Advanced",
|
||||||
|
"headerColor": "#c49215"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "omnibar-ticker-control",
|
||||||
|
"title": "Omnibar Ticker Control",
|
||||||
|
"width": 3,
|
||||||
|
"file": "omnibar-ticker-control.html",
|
||||||
|
"workspace": "Z2 - ESA Advanced",
|
||||||
|
"headerColor": "#c49215"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "donation-alert-control",
|
||||||
|
"title": "Donation Alert Control",
|
||||||
|
"width": 3,
|
||||||
|
"file": "donation-alert-control.html",
|
||||||
|
"workspace": "Z2 - ESA Advanced",
|
||||||
|
"headerColor": "#c49215"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "donation-total-milestones",
|
||||||
|
"title": "Donation Total Milestones",
|
||||||
|
"width": 4,
|
||||||
|
"file": "donation-total-milestones.html",
|
||||||
|
"workspace": "ESA",
|
||||||
|
"headerColor": "#c49215"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bigbutton-tag-scan-control",
|
||||||
|
"title": "Big Button Tag Scan Control",
|
||||||
|
"width": 3,
|
||||||
|
"file": "bigbutton-tag-scan-control.html",
|
||||||
|
"headerColor": "#c49215"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bids",
|
||||||
|
"title": "Bids",
|
||||||
|
"width": 3,
|
||||||
|
"file": "bids.html",
|
||||||
|
"workspace": "ESA",
|
||||||
|
"headerColor": "#c49215"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rabbitmq-test",
|
||||||
|
"title": "RabbitMQ Test",
|
||||||
|
"width": 3,
|
||||||
|
"file": "rabbitmq-test.html",
|
||||||
|
"workspace": "Z9 - Debug",
|
||||||
|
"headerColor": "#c49215"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"graphics": [
|
||||||
|
{
|
||||||
|
"file": "transition.html",
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "omnibar.html",
|
||||||
|
"width": 1920,
|
||||||
|
"height": 80,
|
||||||
|
"singleInstance": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "countdown.html",
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "intermission.html",
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "intermission-hosts.html",
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "intermission-player.html",
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "reader-introduction.html",
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "game-layout.html",
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "unread-donations.html",
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "tts-player.html",
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "player-hud.html",
|
||||||
|
"width": 800,
|
||||||
|
"height": 480
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "media-box-only.html",
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mount": [
|
||||||
|
{
|
||||||
|
"directory": "shared/flags",
|
||||||
|
"endpoint": "flags"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"directory": "boxart",
|
||||||
|
"endpoint": "boxart"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"assetCategories": [
|
||||||
|
{
|
||||||
|
"name": "media-box-images",
|
||||||
|
"title": "Media Box Images",
|
||||||
|
"allowedTypes": [
|
||||||
|
"jpg",
|
||||||
|
"jpeg",
|
||||||
|
"png",
|
||||||
|
"svg",
|
||||||
|
"webp",
|
||||||
|
"gif"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "videos",
|
||||||
|
"title": "Videos",
|
||||||
|
"allowedTypes": [
|
||||||
|
"mp4",
|
||||||
|
"webm",
|
||||||
|
"MP4",
|
||||||
|
"WEBM"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "intermission-slides",
|
||||||
|
"title": "Intermission Slide Images/Videos",
|
||||||
|
"allowedTypes": [
|
||||||
|
"jpg",
|
||||||
|
"jpeg",
|
||||||
|
"png",
|
||||||
|
"svg",
|
||||||
|
"mp4",
|
||||||
|
"webm",
|
||||||
|
"webp",
|
||||||
|
"gif"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reader-introduction-images",
|
||||||
|
"title": "Reader Introduction Images",
|
||||||
|
"allowedTypes": [
|
||||||
|
"jpg",
|
||||||
|
"jpeg",
|
||||||
|
"png",
|
||||||
|
"svg",
|
||||||
|
"webp",
|
||||||
|
"gif"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "donation-alert-assets",
|
||||||
|
"title": "Donation Alert Assets",
|
||||||
|
"allowedTypes": [
|
||||||
|
"jpg",
|
||||||
|
"jpeg",
|
||||||
|
"png",
|
||||||
|
"svg",
|
||||||
|
"webp",
|
||||||
|
"gif",
|
||||||
|
"mp3",
|
||||||
|
"wav"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
BIN
player-templates/16x9-1player-2cams.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
player-templates/16x9-1player-largecam.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
player-templates/16x9-1player.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
player-templates/16x9-2player.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
player-templates/3DS-1player.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
player-templates/4x3-1player.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
player-templates/4x3-2player-extraspace.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
player-templates/4x3-2player.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
player-templates/5x2-1player.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
player-templates/DS-1player.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
player-templates/GBA-1player.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
player-templates/GameBoy-1player.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
player-templates/GameBoy-2player-extraspace.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
player-templates/game-only/3DS-1player.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
player-templates/game-only/4x3-2player-extraspace-p1.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
player-templates/game-only/4x3-2player-extraspace-p2.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
player-templates/game-only/DS-1player.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
player-templates/game-only/GameBoy-2player-extraspace-p1.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
player-templates/game-only/GameBoy-2player-extraspace-p2.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
player-templates/game-only/pokemon-gba-blackout-p1.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
player-templates/game-only/pokemon-gba-blackout-p2.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
player-templates/game-only/sm64-psp-2p-p1.png
Normal file
After Width: | Height: | Size: 522 KiB |
BIN
player-templates/game-only/sm64-psp-2p-p2.png
Normal file
After Width: | Height: | Size: 581 KiB |
6606
pnpm-lock.yaml
generated
Normal file
76
schemas/bids.json
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"game": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"endTime": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"war": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"allowUserOptions": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"parent": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"parent",
|
||||||
|
"name",
|
||||||
|
"total"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"goal": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"total",
|
||||||
|
"war",
|
||||||
|
"allowUserOptions",
|
||||||
|
"options"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
}
|
104
schemas/bigbuttonPlayerMap.json
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"$comment": "Copied from @esamarathon/mq-events/definitions/FlagCarrier/TagScanned.json due to NodeCG issues.",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"flagcarrier": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"group",
|
||||||
|
"time",
|
||||||
|
"uid"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ID of the terminal that scanned the tag (BRB1, ...)",
|
||||||
|
"default": "unset"
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Group of the terminal that scanned the tag",
|
||||||
|
"default": "unset"
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"iso",
|
||||||
|
"unix"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"iso": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Timestamp representation in the ISO 8601 format",
|
||||||
|
"format": "date-time",
|
||||||
|
"examples": [
|
||||||
|
"2019-09-03T19:55:18.430Z"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unix": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Timestamp representation in seconds since the Unix epoch, including a fractional millisecond part",
|
||||||
|
"examples": [
|
||||||
|
1567540518.430
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"description": "Timestamp of when the tag was scanned"
|
||||||
|
},
|
||||||
|
"uid": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "NFC tag UID as hex string"
|
||||||
|
},
|
||||||
|
"validSignature": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Indicates if tag had a valid signature"
|
||||||
|
},
|
||||||
|
"pubKey": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Base64 encoded ed25519 public key used to verify the tag"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"displayName"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "UserTool ID of the user who scanned the tag (if known)"
|
||||||
|
},
|
||||||
|
"displayName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "UserTool display name of the user who scanned the tag",
|
||||||
|
"default": "*unset*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"raw": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Raw dump of scanned tags Key->Value data",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"flagcarrier",
|
||||||
|
"user",
|
||||||
|
"raw"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
schemas/bundles.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
8
schemas/capturePositions.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "../shared/schemas/capturePositions.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
7
schemas/commentators.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
8
schemas/countdown.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "../shared/schemas/countdown.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
19
schemas/currentRunDelay.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"audio": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"audio",
|
||||||
|
"video"
|
||||||
|
]
|
||||||
|
}
|
8
schemas/delayedTimer.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "../node_modules/speedcontrol-util/schemas/timer.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
53
schemas/donationAlerts.json
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"threshold": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"sound": {
|
||||||
|
"description": "This stores a reference based on the 'name' of the asset.",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"volume": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"graphic": {
|
||||||
|
"description": "This stores a reference based on the 'name' of the asset.",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"graphicDisplayTime": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"threshold",
|
||||||
|
"sound",
|
||||||
|
"volume",
|
||||||
|
"graphic",
|
||||||
|
"graphicDisplayTime"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
}
|
12
schemas/donationReader.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": null
|
||||||
|
}
|
5
schemas/donationTotal.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"type": "number",
|
||||||
|
"default": 0
|
||||||
|
}
|
31
schemas/donationTotalMilestones.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"addition": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"amount": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"enabled"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
}
|
32
schemas/donationsToRead.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"amount": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"amount",
|
||||||
|
"timestamp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
}
|
38
schemas/gameLayouts.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"available": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"code": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"selected": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"crowdCamera": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"available",
|
||||||
|
"crowdCamera"
|
||||||
|
]
|
||||||
|
}
|
52
schemas/graphics%3Ainstances.json
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"ipv4": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"bundleName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"bundleVersion": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"bundleGit": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"pathName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"singleInstance": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"socketId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"open": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"potentiallyOutOfDate": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"ipv4",
|
||||||
|
"timestamp",
|
||||||
|
"bundleName",
|
||||||
|
"bundleVersion",
|
||||||
|
"pathName",
|
||||||
|
"singleInstance",
|
||||||
|
"socketId",
|
||||||
|
"open",
|
||||||
|
"potentiallyOutOfDate"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
80
schemas/intermissionSlides.json
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"definitions": {
|
||||||
|
"types": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"UpcomingRuns",
|
||||||
|
"RandomBid",
|
||||||
|
"RandomPrize",
|
||||||
|
"Media"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"rotation": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/definitions/types"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mediaUUID": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"id",
|
||||||
|
"mediaUUID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"current": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/definitions/types"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mediaUUID": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"bidId": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"prizeId": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"id",
|
||||||
|
"mediaUUID"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"rotation",
|
||||||
|
"current"
|
||||||
|
]
|
||||||
|
}
|
8
schemas/mediaBox.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "../shared/schemas/mediaBox.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
8
schemas/musicData.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "../shared/schemas/musicData.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
5
schemas/nameCycle.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"type": "number",
|
||||||
|
"default": 0
|
||||||
|
}
|
41
schemas/notableDonations.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"event": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"_id": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"donor_visiblename": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"amount": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"comment_state": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"time_received": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"event",
|
||||||
|
"_id",
|
||||||
|
"donor_visiblename",
|
||||||
|
"amount",
|
||||||
|
"comment_state",
|
||||||
|
"comment",
|
||||||
|
"time_received"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
}
|
48
schemas/obsData.json
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"connected": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"scene": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sceneList": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"transitioning": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"streaming": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"gameLayoutScreenshot": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"disableTransitioning": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"transitionTimestamp": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"connected",
|
||||||
|
"sceneList",
|
||||||
|
"transitioning",
|
||||||
|
"streaming",
|
||||||
|
"disableTransitioning",
|
||||||
|
"transitionTimestamp"
|
||||||
|
]
|
||||||
|
}
|
203
schemas/omnibar.json
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"definitions": {
|
||||||
|
"props": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"types": {
|
||||||
|
"rotation": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"GenericMsg",
|
||||||
|
"UpcomingRun",
|
||||||
|
"Prize",
|
||||||
|
"Bid",
|
||||||
|
"Milestone"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"alerts": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Tweet",
|
||||||
|
"CrowdControl",
|
||||||
|
"MiniCredits"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pins": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Bid",
|
||||||
|
"Milestone"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"rotation": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/definitions/types/rotation"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"props": {
|
||||||
|
"$ref": "#/definitions/props"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"alertQueue": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/definitions/types/alerts"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"current": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/types/rotation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/types/alerts"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"props": {
|
||||||
|
"$ref": "#/definitions/props"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
"lastId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"pin": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/definitions/types/pins"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
"miniCredits": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"runSubs": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$comment": "TODO: Update MQ event!",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"runCheers": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"runDonations": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"runSubs",
|
||||||
|
"runCheers",
|
||||||
|
"runDonations"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"rotation",
|
||||||
|
"alertQueue",
|
||||||
|
"current",
|
||||||
|
"pin",
|
||||||
|
"miniCredits"
|
||||||
|
]
|
||||||
|
}
|
26
schemas/otherStreamData.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"show": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"runData": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "../node_modules/speedcontrol-util/schemas/reused/RunData.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"show",
|
||||||
|
"runData"
|
||||||
|
]
|
||||||
|
}
|
8
schemas/prizes.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "../shared/schemas/prizes.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
27
schemas/readerIntroduction.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"current": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"RunInfo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"current"
|
||||||
|
]
|
||||||
|
}
|
5
schemas/serverTimestamp.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"type": "number",
|
||||||
|
"default": 0
|
||||||
|
}
|
63
schemas/soundCues.json
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
|
||||||
|
"definitions": {
|
||||||
|
"cueFile": {
|
||||||
|
"type": ["object", "null"],
|
||||||
|
"properties": {
|
||||||
|
"sum": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"base": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"ext": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"volume": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"channels": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"bundleName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"defaultVolume": {
|
||||||
|
"type": ["number", "null"]
|
||||||
|
},
|
||||||
|
"file": {
|
||||||
|
"$ref": "#/definitions/cueFile"
|
||||||
|
},
|
||||||
|
"defaultFile": {
|
||||||
|
"$ref": "#/definitions/cueFile"
|
||||||
|
},
|
||||||
|
"assignable": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name", "volume", "file", "assignable"]
|
||||||
|
}
|
||||||
|
}
|
10
schemas/streamDeckData.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"playerHUDTriggerType": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
schemas/taskmasterTimestamps.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"start": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"start",
|
||||||
|
"end"
|
||||||
|
]
|
||||||
|
}
|
33
schemas/ttsVoices.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"available": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"code",
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"selected": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"available"
|
||||||
|
]
|
||||||
|
}
|
12
schemas/upcomingRunID.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": null
|
||||||
|
}
|
63
schemas/videoPlayer.json
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"playlist": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"sum": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"length": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"commercial": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"length",
|
||||||
|
"commercial"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"current": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
"playing": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"estimatedFinishTimestamp": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"plays": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"playlist",
|
||||||
|
"current",
|
||||||
|
"playing",
|
||||||
|
"estimatedFinishTimestamp",
|
||||||
|
"plays"
|
||||||
|
]
|
||||||
|
}
|
8
shared/.eslintignore
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
postinstall.js
|
||||||
|
.eslintrc.*.js
|
||||||
|
.eslintrc.js
|
||||||
|
types/schemas/**/*
|
||||||
|
flags/**/*
|
||||||
|
node_modules/**/*
|
||||||
|
schemas/**/*
|
||||||
|
dist
|
72
shared/.eslintrc.browser.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
parser: 'vue-eslint-parser',
|
||||||
|
parserOptions: {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
project: path.join(__dirname, 'tsconfig.browser.json'),
|
||||||
|
extraFileExtensions: ['.vue'],
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
nodecg: 'readonly',
|
||||||
|
NodeCG: 'readonly',
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
'@typescript-eslint',
|
||||||
|
],
|
||||||
|
extends: [
|
||||||
|
'plugin:vue/essential',
|
||||||
|
'airbnb-base',
|
||||||
|
'airbnb-typescript/base',
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:import/typescript',
|
||||||
|
],
|
||||||
|
settings: {
|
||||||
|
'import/resolver': {
|
||||||
|
typescript: {
|
||||||
|
// This is needed to properly resolve paths(?)
|
||||||
|
// Left commented out, might need again in the future.
|
||||||
|
// project: 'tsconfig.browser.json',
|
||||||
|
},
|
||||||
|
// This may be wanted/needed in the future.
|
||||||
|
/* webpack: {
|
||||||
|
config: path.join(__dirname, '../webpack.config.js'),
|
||||||
|
}, */
|
||||||
|
},
|
||||||
|
'import/extensions': ['.js', '.jsx', '.ts', '.tsx'],
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'import/no-extraneous-dependencies': ['error', {
|
||||||
|
// Everything is compiled for the browser so dev dependencies are fine.
|
||||||
|
devDependencies: true,
|
||||||
|
packageDir: [path.join(__dirname, '.'), path.join(__dirname, '..')],
|
||||||
|
}],
|
||||||
|
// max-len set to ignore "import" lines (as they usually get long and messy).
|
||||||
|
'max-len': ['error', { code: 100, ignorePattern: '^import\\s.+\\sfrom\\s.+;$' }],
|
||||||
|
// I mainly have this off as it ruins auto import sorting in VSCode.
|
||||||
|
'object-curly-newline': 'off',
|
||||||
|
'@typescript-eslint/lines-between-class-members': 'off',
|
||||||
|
'vue/html-self-closing': ['error'],
|
||||||
|
'class-methods-use-this': 'off',
|
||||||
|
'no-param-reassign': ['error', {
|
||||||
|
props: true,
|
||||||
|
ignorePropertyModificationsFor: [
|
||||||
|
'state', // for vuex state
|
||||||
|
'acc', // for reduce accumulators
|
||||||
|
'e', // for e.returnvalue
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
'import/extensions': ['error', 'ignorePackages', {
|
||||||
|
js: 'never',
|
||||||
|
jsx: 'never',
|
||||||
|
ts: 'never',
|
||||||
|
tsx: 'never',
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
};
|
66
shared/.eslintrc.extension.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
project: path.join(__dirname, 'tsconfig.extension.json'),
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
'@typescript-eslint',
|
||||||
|
],
|
||||||
|
extends: [
|
||||||
|
'airbnb-base',
|
||||||
|
'airbnb-typescript/base',
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:import/typescript',
|
||||||
|
],
|
||||||
|
settings: {
|
||||||
|
'import/resolver': {
|
||||||
|
typescript: {
|
||||||
|
// This is needed to properly resolve paths(?)
|
||||||
|
// Left commented out, might need again in the future.
|
||||||
|
// project: 'tsconfig.extension.json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'import/extensions': ['.js', '.jsx', '.ts', '.tsx'],
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/lines-between-class-members': 'off',
|
||||||
|
// max-len set to ignore "import" lines (as they usually get long and messy).
|
||||||
|
'max-len': ['error', { code: 100, ignorePattern: '^import\\s.+\\sfrom\\s.+;' }],
|
||||||
|
// I mainly have this off as it ruins auto import sorting in VSCode.
|
||||||
|
'object-curly-newline': 'off',
|
||||||
|
'import/extensions': ['error', 'ignorePackages', {
|
||||||
|
js: 'never',
|
||||||
|
jsx: 'never',
|
||||||
|
ts: 'never',
|
||||||
|
tsx: 'never',
|
||||||
|
}],
|
||||||
|
|
||||||
|
'import/no-extraneous-dependencies': ['error', {
|
||||||
|
packageDir: [path.join(__dirname, '.'), path.join(__dirname, '..')],
|
||||||
|
}],
|
||||||
|
'class-methods-use-this': 'off',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Overrides for types.
|
||||||
|
overrides: [{
|
||||||
|
files: ['**/*.d.ts'],
|
||||||
|
rules: {
|
||||||
|
// @typescript-eslint/no-unused-vars does not work with type definitions
|
||||||
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
|
// Sometimes eslint complains about this for types (usually when using namespaces).
|
||||||
|
'import/prefer-default-export': 'off',
|
||||||
|
// Types are only used for development (usually!) so dev dependencies are fine.
|
||||||
|
'import/no-extraneous-dependencies': ['error', {
|
||||||
|
devDependencies: true,
|
||||||
|
packageDir: [path.join(__dirname, '.'), path.join(__dirname, '..')],
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
};
|
4
shared/.eslintrc.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
// This is here just to make sure ESLint doesn't check any deeper.
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
};
|
126
shared/.gitignore
vendored
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
21
shared/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 European Speedrunner Assembly
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
90
shared/README.md
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
# esa-layouts-shared
|
||||||
|
|
||||||
|
A repository which houses several elements that are used by mutiple [NodeCG](https://nodecg.dev) based bundles for our layouts, [esa-layouts](https://github.com/esamarathon/esa-layouts) for example.
|
||||||
|
|
||||||
|
**This repository is purposefully designed for our use, and can have breaking changes without prior notice. We advise you don't directly use it in any projects.**
|
||||||
|
|
||||||
|
## Basic notes for setup/structure
|
||||||
|
|
||||||
|
- This repository is to be used as a submodule, directly in the root of the NodeCG bundle (usually in a directory named `shared`).
|
||||||
|
- It requires the bundle to have some specific dependencies and structure; not going to note it all here because it's basically just the stuff from [zoton2/nodecg-vue-ts-template](https://github.com/zoton2/nodecg-vue-ts-template) which we tend to base bundles using.
|
||||||
|
- The bundle should have a `postinstall` in the `package.json` file:
|
||||||
|
- ```
|
||||||
|
"postinstall": "cd shared && node postinstall.js"
|
||||||
|
```
|
||||||
|
- You may want to add a `path` to your `tsconfig.*.json` files for ease of development:
|
||||||
|
- ```
|
||||||
|
"paths": {
|
||||||
|
"@shared/*": [
|
||||||
|
"shared/*"
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- To make sure the above part works, you will also want to add this line in your `extension/index.ts` file:
|
||||||
|
- ```
|
||||||
|
require('module-alias').addAlias('@shared', require('path').join(__dirname, '../shared'));
|
||||||
|
```
|
||||||
|
- You will want to add these paths to your `tsconfig.browser.json` in the `include` array:
|
||||||
|
- ```
|
||||||
|
"include": [
|
||||||
|
// esa-layouts-shared
|
||||||
|
"shared/browser_shared/**/*.ts",
|
||||||
|
"shared/browser_shared/**/*.vue",
|
||||||
|
"shared/dashboard/**/*.ts",
|
||||||
|
"shared/dashboard/**/*.vue",
|
||||||
|
"shared/graphics/**/*.ts",
|
||||||
|
"shared/graphics/**/*.vue",
|
||||||
|
"shared/types/**/*.d.ts"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
- You will want to add this path to yoiur `tsconfig.extension.json` in the `include` array:
|
||||||
|
- ```
|
||||||
|
"include": [
|
||||||
|
`"shared/types/**/*.d.ts"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
- You will want to add these to your `tsconfig.extension.json` as `references`:
|
||||||
|
- ```
|
||||||
|
"references": [
|
||||||
|
{ "path": "./shared/extension/audio-normaliser" },
|
||||||
|
{ "path": "./shared/extension/countdown" },
|
||||||
|
{ "path": "./shared/extension/mediabox" },
|
||||||
|
{ "path": "./shared/extension/music" },
|
||||||
|
{ "path": "./shared/extension/obs" },
|
||||||
|
{ "path": "./shared/extension/rabbitmq" },
|
||||||
|
{ "path": "./shared/extension/video-player" },
|
||||||
|
{ "path": "./shared/extension/x32" },
|
||||||
|
{ "path": "./shared/extension/xkeys-esa" }
|
||||||
|
]
|
||||||
|
```
|
||||||
|
- You will want to add these entries in your `vetur.config.js` in the `projects` section:
|
||||||
|
- ```
|
||||||
|
projects: [
|
||||||
|
// esa-layouts-shared
|
||||||
|
{
|
||||||
|
root: './shared/dashboard',
|
||||||
|
package: '../../package.json',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: './shared/graphics',
|
||||||
|
package: '../../package.json',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: './shared/browser_shared',
|
||||||
|
package: '../../package.json',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
- You will want to add this entry in your `.vscode/settings.json` file in the `eslint.workingDirectories` section:
|
||||||
|
- ```
|
||||||
|
"eslint.workingDirectories": [
|
||||||
|
"shared"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
- You will want to change the Webpack `resolve.alias.vue` config to make sure it resolves to the one in the bundle:
|
||||||
|
- ```
|
||||||
|
alias: {
|
||||||
|
// vue: 'vue/dist/vue.esm.js',
|
||||||
|
vue: path.resolve(__dirname, 'node_modules/vue/dist/vue.esm.js'),
|
||||||
|
},
|
||||||
|
```
|
5
shared/browser_shared/.eslintrc.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
extends: ['../.eslintrc.browser.js'],
|
||||||
|
};
|
28
shared/browser_shared/helpers.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Checks if number needs a 0 adding to the start and does so if needed.
|
||||||
|
* @param num Number which you want to turn into a padded string.
|
||||||
|
*/
|
||||||
|
export function padTimeNumber(num: number): string {
|
||||||
|
return num.toString().padStart(2, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts milliseconds into a time string (HH:MM:SS).
|
||||||
|
* @param ms Milliseconds you wish to convert.
|
||||||
|
*/
|
||||||
|
export function msToTimeStr(ms: number): string {
|
||||||
|
const seconds = Math.floor((ms / 1000) % 60);
|
||||||
|
const minutes = Math.floor((ms / (1000 * 60)) % 60);
|
||||||
|
const hours = Math.floor(ms / (1000 * 60 * 60));
|
||||||
|
return `${padTimeNumber(hours)
|
||||||
|
}:${padTimeNumber(minutes)
|
||||||
|
}:${padTimeNumber(seconds)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple formatter for displaying USD amounts.
|
||||||
|
* @param amount Amount as a integer/float.
|
||||||
|
*/
|
||||||
|
export function formatUSD(amount: number): string {
|
||||||
|
return `$${amount.toFixed(2)}`;
|
||||||
|
}
|
64
shared/browser_shared/replicant_store.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import type NodeCGTypes from '@nodecg/types';
|
||||||
|
import clone from 'clone';
|
||||||
|
import Vue from 'vue';
|
||||||
|
import type { Store } from 'vuex';
|
||||||
|
import { namespace } from 'vuex-class';
|
||||||
|
import { getModule, Module, Mutation, VuexModule } from 'vuex-module-decorators';
|
||||||
|
import type { Countdown, MediaBox, Prizes } from '../types/schemas';
|
||||||
|
|
||||||
|
// Declaring replicants.
|
||||||
|
export const reps: {
|
||||||
|
assetsMediaBoxImages: NodeCGTypes.ClientReplicant<NodeCGTypes.AssetFile[]>;
|
||||||
|
countdown: NodeCGTypes.ClientReplicant<Countdown>;
|
||||||
|
mediaBox: NodeCGTypes.ClientReplicant<MediaBox>;
|
||||||
|
prizes: NodeCGTypes.ClientReplicant<Prizes>;
|
||||||
|
[k: string]: NodeCGTypes.ClientReplicant<unknown>;
|
||||||
|
} = {
|
||||||
|
assetsMediaBoxImages: nodecg.Replicant('assets:media-box-images'),
|
||||||
|
countdown: nodecg.Replicant('countdown'),
|
||||||
|
mediaBox: nodecg.Replicant('mediaBox'),
|
||||||
|
prizes: nodecg.Replicant('prizes'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// All the replicant types.
|
||||||
|
export interface ReplicantTypes {
|
||||||
|
assetsMediaBoxImages: NodeCGTypes.AssetFile[];
|
||||||
|
countdown: Countdown;
|
||||||
|
mediaBox: MediaBox;
|
||||||
|
prizes: Prizes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Module({ name: 'ReplicantModule', namespaced: true })
|
||||||
|
export class ReplicantModule extends VuexModule {
|
||||||
|
// Replicant values are stored here.
|
||||||
|
reps: { [k: string]: unknown } = {};
|
||||||
|
|
||||||
|
// This sets the state object above when a replicant sends an update.
|
||||||
|
@Mutation
|
||||||
|
setState({ name, val }: { name: string, val: unknown }): void {
|
||||||
|
Vue.set(this.reps, name, clone(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a generic mutation to update a named replicant.
|
||||||
|
@Mutation
|
||||||
|
setReplicant<K>({ name, val }: { name: string, val: K }): void {
|
||||||
|
Vue.set(this.reps, name, clone(val)); // Also update local copy, although no schema validation!
|
||||||
|
reps[name].value = clone(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-mutable-exports
|
||||||
|
export let replicantModule!: ReplicantModule;
|
||||||
|
export const replicantNS = namespace('ReplicantModule');
|
||||||
|
|
||||||
|
export async function setUpReplicants(store: Store<unknown>): Promise<void> {
|
||||||
|
// Listens for each declared replicants "change" event, and updates the state.
|
||||||
|
Object.keys(reps).forEach((name) => {
|
||||||
|
reps[name].on('change', (val) => {
|
||||||
|
store.commit('ReplicantModule/setState', { name, val });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// We should make sure the replicant are ready to be read, needs to be done in browser context.
|
||||||
|
await NodeCG.waitForReplicants(...Object.keys(reps).map((key) => reps[key]));
|
||||||
|
replicantModule = getModule(ReplicantModule, store);
|
||||||
|
}
|
3
shared/browser_shared/tsconfig.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.browser.json"
|
||||||
|
}
|
11
shared/dashboard/.eslintrc.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
extends: ['../.eslintrc.browser.js'],
|
||||||
|
overrides: [{
|
||||||
|
files: ['**/index.ts'],
|
||||||
|
rules: {
|
||||||
|
'import/newline-after-import': 'off',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
};
|
45
shared/dashboard/countdown/App.vue
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<v-app>
|
||||||
|
<div>
|
||||||
|
Current Countdown: {{ currentCountdown }}
|
||||||
|
</div>
|
||||||
|
<v-time-picker
|
||||||
|
v-model="entry"
|
||||||
|
format="24hr"
|
||||||
|
full-width
|
||||||
|
/>
|
||||||
|
<v-btn @click="change()">
|
||||||
|
Apply
|
||||||
|
</v-btn>
|
||||||
|
</v-app>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Vue, Component } from 'vue-property-decorator';
|
||||||
|
import clone from 'clone';
|
||||||
|
import { msToTimeStr } from '../../browser_shared/helpers';
|
||||||
|
import { Countdown } from '../../types/schemas';
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class extends Vue {
|
||||||
|
countdown: Countdown | null = null;
|
||||||
|
entry = '';
|
||||||
|
|
||||||
|
get currentCountdown(): string {
|
||||||
|
const seconds = Math.round((this.countdown?.remaining ?? 0) / 1000);
|
||||||
|
return msToTimeStr(seconds * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
change(): void {
|
||||||
|
nodecg.sendMessage('startCountdown', this.entry);
|
||||||
|
this.entry = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
created(): void {
|
||||||
|
// Simple replicant cloning to avoid having to use a whole Vuex store.
|
||||||
|
nodecg.Replicant<Countdown>('countdown').on('change', (val) => {
|
||||||
|
Vue.set(this, 'countdown', clone(val));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
2
shared/dashboard/countdown/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import App from './App.vue';
|
||||||
|
export default App;
|
71
shared/dashboard/mediabox/App.vue
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<v-app>
|
||||||
|
<available-images />
|
||||||
|
<available-prizes v-if="prizes" :style="{ 'margin-top': '20px' }" />
|
||||||
|
<div :style="{ 'margin-top': '20px' }">
|
||||||
|
<v-toolbar-title>
|
||||||
|
Custom Text Element
|
||||||
|
</v-toolbar-title>
|
||||||
|
<div>
|
||||||
|
<draggable
|
||||||
|
:list="['text']"
|
||||||
|
:group="{ name: 'media', pull: 'clone', put: false }"
|
||||||
|
:sort="false"
|
||||||
|
:clone="cloneText"
|
||||||
|
>
|
||||||
|
<media-card key="text" :style="{ 'font-weight': '500' }">
|
||||||
|
Drag to rotation to configure a custom text element.
|
||||||
|
</media-card>
|
||||||
|
</draggable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<rotation :style="{ 'margin-top': '20px' }" />
|
||||||
|
|
||||||
|
<!-- Save Button -->
|
||||||
|
<v-btn
|
||||||
|
:loading="disableSave"
|
||||||
|
:disabled="disableSave"
|
||||||
|
:style="{ 'margin-top': '20px' }"
|
||||||
|
@click="save()"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<current-media-info :style="{ 'margin-top': '10px' }" />
|
||||||
|
</v-app>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||||
|
import Draggable from 'vuedraggable';
|
||||||
|
import { Action, State } from 'vuex-class';
|
||||||
|
import { MediaBox } from '../../types';
|
||||||
|
import AvailableImages from './components/AvailableImages.vue';
|
||||||
|
import AvailablePrizes from './components/AvailablePrizes.vue';
|
||||||
|
import CurrentMediaInfo from './components/CurrentMediaInfo.vue';
|
||||||
|
import MediaCard from './components/MediaCard.vue';
|
||||||
|
import Rotation from './components/Rotation.vue';
|
||||||
|
import { clone } from './components/shared';
|
||||||
|
import { Save, store } from './store';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
store,
|
||||||
|
components: {
|
||||||
|
Draggable,
|
||||||
|
AvailableImages,
|
||||||
|
AvailablePrizes,
|
||||||
|
Rotation,
|
||||||
|
CurrentMediaInfo,
|
||||||
|
MediaCard,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class extends Vue {
|
||||||
|
@Prop({ type: Boolean, default: true }) prizes!: boolean;
|
||||||
|
@State disableSave!: boolean;
|
||||||
|
@Action save!: Save;
|
||||||
|
|
||||||
|
cloneText(): MediaBox.RotationElem {
|
||||||
|
return clone('text', undefined, 'Your text here');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
44
shared/dashboard/mediabox/components/ApplicableIcon.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<v-tooltip
|
||||||
|
v-if="isApplicable === true"
|
||||||
|
right
|
||||||
|
>
|
||||||
|
<template v-slot:activator="{ on }">
|
||||||
|
<v-icon v-on="on">
|
||||||
|
mdi-check
|
||||||
|
</v-icon>
|
||||||
|
</template>
|
||||||
|
<span>Applicable</span>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip
|
||||||
|
v-else-if="isApplicable === false"
|
||||||
|
right
|
||||||
|
>
|
||||||
|
<template v-slot:activator="{ on }">
|
||||||
|
<v-icon v-on="on">
|
||||||
|
mdi-close
|
||||||
|
</v-icon>
|
||||||
|
</template>
|
||||||
|
<span>Not Currently Applicable</span>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip
|
||||||
|
v-else
|
||||||
|
right
|
||||||
|
>
|
||||||
|
<template v-slot:activator="{ on }">
|
||||||
|
<v-icon v-on="on">
|
||||||
|
mdi-help
|
||||||
|
</v-icon>
|
||||||
|
</template>
|
||||||
|
<span>Unknown</span>
|
||||||
|
</v-tooltip>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class extends Vue {
|
||||||
|
@Prop({ type: Boolean, required: false }) isApplicable!: boolean | undefined;
|
||||||
|
}
|
||||||
|
</script>
|
63
shared/dashboard/mediabox/components/AvailableImages.vue
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<v-toolbar-title>
|
||||||
|
Available Images
|
||||||
|
</v-toolbar-title>
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
'max-height': '400px',
|
||||||
|
'overflow-y': 'auto',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<media-card
|
||||||
|
v-if="!images.length"
|
||||||
|
:style="{
|
||||||
|
'font-style': 'italic',
|
||||||
|
'white-space': 'unset',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
Add images under "Assets" > "{{ bundleName }}" > "Media Box Images".
|
||||||
|
</media-card>
|
||||||
|
<draggable
|
||||||
|
v-else
|
||||||
|
:list="images"
|
||||||
|
:group="{ name: 'media', pull: 'clone', put: false }"
|
||||||
|
:sort="false"
|
||||||
|
:clone="clone"
|
||||||
|
>
|
||||||
|
<media-card
|
||||||
|
v-for="image in images"
|
||||||
|
:key="image.sum"
|
||||||
|
:title="image.name"
|
||||||
|
>
|
||||||
|
{{ image.name }}
|
||||||
|
</media-card>
|
||||||
|
</draggable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type NodeCGTypes from '@nodecg/types';
|
||||||
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
|
import Draggable from 'vuedraggable';
|
||||||
|
import { State } from 'vuex-class';
|
||||||
|
import { MediaBox } from '../../../types';
|
||||||
|
import MediaCard from './MediaCard.vue';
|
||||||
|
import { clone } from './shared';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
Draggable,
|
||||||
|
MediaCard,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class extends Vue {
|
||||||
|
@State images!: NodeCGTypes.AssetFile[];
|
||||||
|
bundleName = nodecg.bundleName;
|
||||||
|
|
||||||
|
clone(original: NodeCGTypes.AssetFile): MediaBox.RotationElem {
|
||||||
|
return clone('image', original.sum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
95
shared/dashboard/mediabox/components/AvailablePrizes.vue
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<v-toolbar-title>
|
||||||
|
Available Prizes
|
||||||
|
</v-toolbar-title>
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
'max-height': '400px',
|
||||||
|
'overflow-y': 'auto',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<media-card
|
||||||
|
v-if="!prizes.length"
|
||||||
|
:style="{ 'font-style': 'italic' }"
|
||||||
|
>
|
||||||
|
No prizes available from the tracker.
|
||||||
|
</media-card>
|
||||||
|
<!-- All Prizes -->
|
||||||
|
<draggable
|
||||||
|
v-else
|
||||||
|
:list="prizes"
|
||||||
|
:group="{ name: 'media', pull: 'clone', put: false }"
|
||||||
|
:sort="false"
|
||||||
|
:clone="clone"
|
||||||
|
>
|
||||||
|
<media-card
|
||||||
|
v-for="prize in prizes"
|
||||||
|
:key="prize.id"
|
||||||
|
class="d-flex"
|
||||||
|
>
|
||||||
|
<applicable-icon :is-applicable="isPrizeApplicable(prize)" />
|
||||||
|
<div
|
||||||
|
class="flex-grow-1"
|
||||||
|
:title="prize.name"
|
||||||
|
>
|
||||||
|
{{ prize.name }}
|
||||||
|
</div>
|
||||||
|
</media-card>
|
||||||
|
</draggable>
|
||||||
|
|
||||||
|
<!-- Generic Prize Slide -->
|
||||||
|
<draggable
|
||||||
|
:list="['generic_prize']"
|
||||||
|
:group="{ name: 'media', pull: 'clone', put: false }"
|
||||||
|
:sort="false"
|
||||||
|
:clone="cloneGeneric"
|
||||||
|
>
|
||||||
|
<media-card
|
||||||
|
key="generic_prize"
|
||||||
|
class="d-flex"
|
||||||
|
:style="{ 'font-weight': '500' }"
|
||||||
|
>
|
||||||
|
<applicable-icon :is-applicable="!!prizes.filter((p) => isPrizeApplicable(p)).length" />
|
||||||
|
<div
|
||||||
|
class="flex-grow-1"
|
||||||
|
title="Generic Prize Slide"
|
||||||
|
>
|
||||||
|
Generic Prize Slide
|
||||||
|
</div>
|
||||||
|
</media-card>
|
||||||
|
</draggable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Vue, Component } from 'vue-property-decorator';
|
||||||
|
import { State } from 'vuex-class';
|
||||||
|
import Draggable from 'vuedraggable';
|
||||||
|
import { Tracker, MediaBox } from '../../../types';
|
||||||
|
import { Prizes } from '../../../types/schemas';
|
||||||
|
import { clone, isPrizeApplicable } from './shared';
|
||||||
|
import MediaCard from './MediaCard.vue';
|
||||||
|
import ApplicableIcon from './ApplicableIcon.vue';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
Draggable,
|
||||||
|
MediaCard,
|
||||||
|
ApplicableIcon,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class extends Vue {
|
||||||
|
@State prizes!: Prizes;
|
||||||
|
isPrizeApplicable = isPrizeApplicable;
|
||||||
|
|
||||||
|
clone(original: Tracker.FormattedPrize): MediaBox.RotationElem {
|
||||||
|
return clone('prize', original.id.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
cloneGeneric(): MediaBox.RotationElem {
|
||||||
|
return clone('prize_generic');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
76
shared/dashboard/mediabox/components/CurrentMediaInfo.vue
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="Status">
|
||||||
|
<span
|
||||||
|
v-if="!settings.current"
|
||||||
|
:style="{ 'font-style': 'italic' }"
|
||||||
|
>
|
||||||
|
No media currently displaying.
|
||||||
|
</span>
|
||||||
|
<template v-else-if="settings.current">
|
||||||
|
<span class="font-weight-bold">Current:</span>
|
||||||
|
<template v-if="isAlertType(settings.current.type)">
|
||||||
|
<span :style="{ 'text-transform': 'capitalize' }">
|
||||||
|
{{ settings.current.type }}
|
||||||
|
</span> Alert
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ getMediaDetails(settings.current).name }}
|
||||||
|
</template>
|
||||||
|
<br>
|
||||||
|
<template v-if="!isAlertType(settings.current.type)">
|
||||||
|
(position {{ position(settings.current) }}/{{ settings.rotationApplicable.length }},
|
||||||
|
</template>
|
||||||
|
<span v-else>(</span>{{
|
||||||
|
timeRemaining(settings.current) }}/{{ mediaLength(settings.current) }}s left)
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="settings.paused"
|
||||||
|
class="Status"
|
||||||
|
>
|
||||||
|
<span class="font-weight-bold">Paused:</span> {{ getMediaDetails(settings.paused).name }}
|
||||||
|
<br>(position {{ position(settings.paused) }}/{{ settings.rotationApplicable.length }},
|
||||||
|
{{ timeRemaining(settings.paused) }}/{{ mediaLength(settings.paused) }}s left)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
|
import { State } from 'vuex-class';
|
||||||
|
import { MediaBox } from '../../../types';
|
||||||
|
import { MediaBox as MediaBoxRep } from '../../../types/schemas';
|
||||||
|
import { getMediaDetails, isAlertType } from './shared';
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class extends Vue {
|
||||||
|
@State settings!: MediaBoxRep;
|
||||||
|
getMediaDetails = getMediaDetails;
|
||||||
|
isAlertType = isAlertType;
|
||||||
|
|
||||||
|
mediaLength(media: MediaBox.ActiveElem): number {
|
||||||
|
if (media && isAlertType(media.type)) {
|
||||||
|
return 15; // Alerts have a hardcoded 15 second length for now.
|
||||||
|
}
|
||||||
|
return this.settings.rotationApplicable
|
||||||
|
.find((i) => i.id === media?.id)?.seconds || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeRemaining(media: MediaBox.ActiveElem): number {
|
||||||
|
return Math.round(this.mediaLength(media) - ((media?.timeElapsed || 0) / 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
position(media: MediaBox.ActiveElem): number {
|
||||||
|
const index = media?.index;
|
||||||
|
return typeof index === 'number' ? index + 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.Status {
|
||||||
|
text-align: center;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
20
shared/dashboard/mediabox/components/MediaCard.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<v-card
|
||||||
|
:style="{
|
||||||
|
'text-align': 'center',
|
||||||
|
padding: '5px',
|
||||||
|
'margin-top': '10px',
|
||||||
|
'white-space': 'nowrap',
|
||||||
|
'overflow': 'hidden',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Vue, Component } from 'vue-property-decorator';
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class extends Vue {}
|
||||||
|
</script>
|
181
shared/dashboard/mediabox/components/Rotation.vue
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!-- Dialog for editing custom text -->
|
||||||
|
<v-dialog class="Dialog" v-model="dialog" persistent>
|
||||||
|
<v-card>
|
||||||
|
<div class="pa-4 pb-0">
|
||||||
|
Text entered here can include Markdown for styling purposes.
|
||||||
|
</div>
|
||||||
|
<v-card-text class="pa-4 pb-0">
|
||||||
|
<v-form>
|
||||||
|
<v-textarea
|
||||||
|
v-model="editedText"
|
||||||
|
label="Text"
|
||||||
|
autocomplete="off"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
/>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn @click="save">Save</v-btn>
|
||||||
|
<v-btn @click="dialog = false; editedText = ''; editingElem = ''">Cancel</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
<v-toolbar-title>
|
||||||
|
Rotation
|
||||||
|
</v-toolbar-title>
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
'max-height': '400px',
|
||||||
|
'overflow-y': 'auto',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<media-card
|
||||||
|
v-if="!newRotation.length"
|
||||||
|
:style="{ 'font-style': 'italic' }"
|
||||||
|
>
|
||||||
|
Drag elements from above to here to configure.
|
||||||
|
</media-card>
|
||||||
|
<draggable
|
||||||
|
v-model="newRotation"
|
||||||
|
group="media"
|
||||||
|
>
|
||||||
|
<media-card
|
||||||
|
v-for="(media, i) in newRotation"
|
||||||
|
:key="media.id"
|
||||||
|
class="d-flex"
|
||||||
|
>
|
||||||
|
<applicable-icon :is-applicable="isApplicable(media)" />
|
||||||
|
<div
|
||||||
|
class="d-flex align-center justify-center flex-grow-1"
|
||||||
|
:title="getMediaDetails(media).name"
|
||||||
|
:style="{
|
||||||
|
'overflow': 'hidden',
|
||||||
|
'font-weight': media.type === 'prize_generic' ? '500' : undefined,
|
||||||
|
'font-style': !getMediaDetails(media).name ? 'italic' : undefined,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ getMediaDetails(media).name || 'Could not find media name.' }}
|
||||||
|
</div>
|
||||||
|
<div class="d-flex">
|
||||||
|
<v-tooltip left>
|
||||||
|
<template v-slot:activator="{ on }">
|
||||||
|
<div v-on="on">
|
||||||
|
<v-checkbox
|
||||||
|
v-on="on"
|
||||||
|
v-model="media.showOnIntermission"
|
||||||
|
dense
|
||||||
|
class="ma-0 pa-0"
|
||||||
|
hide-details
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<span>Show On Intermission</span>
|
||||||
|
</v-tooltip>
|
||||||
|
<v-text-field
|
||||||
|
v-model="media.seconds"
|
||||||
|
class="pa-0 ma-0"
|
||||||
|
type="number"
|
||||||
|
hide-details
|
||||||
|
dense
|
||||||
|
:style="{ 'width': '40px !important' }"
|
||||||
|
@input="parseSeconds(i)"
|
||||||
|
/>
|
||||||
|
<v-icon
|
||||||
|
v-if="media.type === 'text'"
|
||||||
|
@click="editingElem = media.id; editedText = media.text || ''; dialog = true"
|
||||||
|
>
|
||||||
|
mdi-pencil
|
||||||
|
</v-icon>
|
||||||
|
<v-icon @click="remove(i)">
|
||||||
|
mdi-delete
|
||||||
|
</v-icon>
|
||||||
|
</div>
|
||||||
|
</media-card>
|
||||||
|
</draggable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type NodeCGTypes from '@nodecg/types';
|
||||||
|
import clone from 'clone';
|
||||||
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
|
import Draggable from 'vuedraggable';
|
||||||
|
import { State } from 'vuex-class';
|
||||||
|
import { State2Way } from 'vuex-class-state2way';
|
||||||
|
import { MediaBox } from '../../../types';
|
||||||
|
import { MediaBox as MediaBoxRep, Prizes } from '../../../types/schemas';
|
||||||
|
import ApplicableIcon from './ApplicableIcon.vue';
|
||||||
|
import MediaCard from './MediaCard.vue';
|
||||||
|
import { getMediaDetails, isPrizeApplicable } from './shared';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
Draggable,
|
||||||
|
MediaCard,
|
||||||
|
ApplicableIcon,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class extends Vue {
|
||||||
|
@State images!: NodeCGTypes.AssetFile[];
|
||||||
|
@State prizes!: Prizes;
|
||||||
|
@State settings!: MediaBoxRep;
|
||||||
|
@State2Way('updateNewRotation', 'newRotation') newRotation!: MediaBox.RotationElem[];
|
||||||
|
getMediaDetails = getMediaDetails;
|
||||||
|
isPrizeApplicable = isPrizeApplicable;
|
||||||
|
dialog = false;
|
||||||
|
editingElem = '';
|
||||||
|
editedText = '';
|
||||||
|
|
||||||
|
created(): void {
|
||||||
|
this.newRotation = clone(this.settings.rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
isApplicable(media: MediaBox.RotationElem): boolean | undefined {
|
||||||
|
// TODO: Check if on intermission on the dashboard size.
|
||||||
|
// We should probably just be loading in the server applicable rotation here.
|
||||||
|
if (!media.showOnIntermission) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
// Only applicable if the asset actually exists.
|
||||||
|
if (media.type === 'image') {
|
||||||
|
return !!this.images.find((i) => i.sum === media.mediaUUID);
|
||||||
|
}
|
||||||
|
// Generic prize element only applicable if there are applicable prizes to fill it with.
|
||||||
|
if (media.type === 'prize_generic') {
|
||||||
|
return !!this.prizes.filter((p) => isPrizeApplicable(p)).length;
|
||||||
|
}
|
||||||
|
// Check if prize is applicable using other function.
|
||||||
|
if (media.type === 'prize') {
|
||||||
|
return isPrizeApplicable(this.prizes.find((p) => p.id.toString() === media.mediaUUID));
|
||||||
|
}
|
||||||
|
// Text is always applicable.
|
||||||
|
if (media.type === 'text') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
parseSeconds(i: number): void {
|
||||||
|
this.newRotation[i].seconds = Number(this.newRotation[i].seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
save(): void {
|
||||||
|
const index = this.newRotation.findIndex((v) => v.id === this.editingElem);
|
||||||
|
if (index >= 0) {
|
||||||
|
this.newRotation[index].text = this.editedText;
|
||||||
|
}
|
||||||
|
this.editedText = '';
|
||||||
|
this.editingElem = '';
|
||||||
|
this.dialog = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(i: number): void {
|
||||||
|
this.newRotation.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
71
shared/dashboard/mediabox/components/shared.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import type NodeCGTypes from '@nodecg/types';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { MediaBox, Tracker } from '../../../types';
|
||||||
|
import { store } from '../store';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the supplied type is that of an alert.
|
||||||
|
* @param type Type of alert
|
||||||
|
*/
|
||||||
|
export function isAlertType(type: MediaBox.Types): boolean {
|
||||||
|
return ['donation', 'subscription', 'cheer', 'merch', 'therungg'].includes(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns details about a piece of media from rotation if found.
|
||||||
|
* @param media Media from rotation you wish to query information on.
|
||||||
|
*/
|
||||||
|
export function getMediaDetails(
|
||||||
|
media: MediaBox.RotationElem | NonNullable<MediaBox.ActiveElem>,
|
||||||
|
): { name?: string } {
|
||||||
|
let details: NodeCGTypes.AssetFile | Tracker.FormattedPrize | undefined;
|
||||||
|
if (media.type === 'prize_generic') {
|
||||||
|
return {
|
||||||
|
name: 'Generic Prize Slide',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (media.type === 'image') {
|
||||||
|
details = store.state.images.find((l) => l.sum === media.mediaUUID);
|
||||||
|
} else if (media.type === 'prize') {
|
||||||
|
details = store.state.prizes.find((p) => p.id.toString() === media.mediaUUID);
|
||||||
|
} else if (media.type === 'text') {
|
||||||
|
return {
|
||||||
|
// This cast type is technically wrong but works OK in this context.
|
||||||
|
name: (media as MediaBox.RotationElem).text
|
||||||
|
? (media as MediaBox.RotationElem).text
|
||||||
|
: 'Custom Text',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return details ? {
|
||||||
|
name: details.name,
|
||||||
|
} : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by VueDraggble to properly clone items.
|
||||||
|
* @param type Type of item to be cloned.
|
||||||
|
* @param mediaUUID UUID of media, sum of image, ID of prize etc.
|
||||||
|
*/
|
||||||
|
export function clone(
|
||||||
|
type: 'image' | 'prize' | 'prize_generic' | 'text',
|
||||||
|
mediaUUID?: string,
|
||||||
|
text?: string,
|
||||||
|
): MediaBox.RotationElem {
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
id: uuid(),
|
||||||
|
mediaUUID: mediaUUID || '-1',
|
||||||
|
text,
|
||||||
|
seconds: 60,
|
||||||
|
showOnIntermission: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if a prize should be shown or not.
|
||||||
|
* @param prize Formatted prize object from the tracker.
|
||||||
|
*/
|
||||||
|
export function isPrizeApplicable(prize?: Tracker.FormattedPrize): boolean {
|
||||||
|
return !!(prize && prize.startTime && prize.endTime
|
||||||
|
&& Date.now() > prize.startTime && Date.now() < prize.endTime);
|
||||||
|
}
|
3
shared/dashboard/mediabox/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import App from './App.vue';
|
||||||
|
export { setUpReplicants } from './store';
|
||||||
|
export default App;
|
70
shared/dashboard/mediabox/store.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import type NodeCGTypes from '@nodecg/types';
|
||||||
|
import clone from 'clone';
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Vuex, { Store } from 'vuex';
|
||||||
|
import type { MediaBox } from '../../types';
|
||||||
|
import type { MediaBox as MediaBoxRep, Prizes } from '../../types/schemas';
|
||||||
|
|
||||||
|
Vue.use(Vuex);
|
||||||
|
|
||||||
|
// Replicants and their types
|
||||||
|
const reps: {
|
||||||
|
images: NodeCGTypes.ClientReplicant<NodeCGTypes.AssetFile[]>;
|
||||||
|
prizes: NodeCGTypes.ClientReplicant<Prizes>;
|
||||||
|
settings: NodeCGTypes.ClientReplicant<MediaBoxRep>;
|
||||||
|
[k: string]: NodeCGTypes.ClientReplicant<unknown>;
|
||||||
|
} = {
|
||||||
|
images: nodecg.Replicant('assets:media-box-images'),
|
||||||
|
prizes: nodecg.Replicant('prizes'),
|
||||||
|
settings: nodecg.Replicant('mediaBox'),
|
||||||
|
};
|
||||||
|
|
||||||
|
interface StateTypes {
|
||||||
|
images: NodeCGTypes.AssetFile[];
|
||||||
|
prizes: Prizes;
|
||||||
|
disableSave: boolean;
|
||||||
|
newRotation: MediaBox.RotationElem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types for mutations/actions below
|
||||||
|
export type UpdateNewRotation = (arr: MediaBox.RotationElem[]) => void;
|
||||||
|
export type Save = () => void;
|
||||||
|
|
||||||
|
export const store = new Vuex.Store({
|
||||||
|
state: {
|
||||||
|
images: [],
|
||||||
|
prizes: [],
|
||||||
|
disableSave: false,
|
||||||
|
newRotation: [],
|
||||||
|
} as StateTypes,
|
||||||
|
mutations: {
|
||||||
|
setState(state, { name, val }): void {
|
||||||
|
Vue.set(state, name, val);
|
||||||
|
},
|
||||||
|
updateNewRotation(state, arr): void {
|
||||||
|
Vue.set(state, 'newRotation', arr);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
async save({ state }): Promise<void> {
|
||||||
|
Vue.set(state, 'disableSave', true);
|
||||||
|
if (typeof reps.settings.value !== 'undefined') {
|
||||||
|
reps.settings.value.rotation = clone(state.newRotation);
|
||||||
|
}
|
||||||
|
await new Promise((res) => { setTimeout(res, 1000); }); // Fake 1s wait
|
||||||
|
Vue.set(state, 'disableSave', false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(reps).forEach((key) => {
|
||||||
|
reps[key].on('change', (val) => {
|
||||||
|
store.commit('setState', { name: key, val: clone(val) });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export const setUpReplicants = async (): Promise<Store<StateTypes>> => {
|
||||||
|
await NodeCG.waitForReplicants(...Object.keys(reps).map((key) => reps[key]));
|
||||||
|
return store;
|
||||||
|
};
|
||||||
|
export default setUpReplicants;
|
93
shared/dashboard/rabbitmq/App.vue
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<v-app
|
||||||
|
v-if="!useTestData"
|
||||||
|
:style="{ 'font-style': 'italic' }"
|
||||||
|
>
|
||||||
|
Not using test data.
|
||||||
|
</v-app>
|
||||||
|
<v-app
|
||||||
|
v-else-if="!enabled"
|
||||||
|
:style="{ 'font-style': 'italic' }"
|
||||||
|
>
|
||||||
|
RabbitMQ not enabled.
|
||||||
|
</v-app>
|
||||||
|
<v-app v-else>
|
||||||
|
<v-btn @click="donation">
|
||||||
|
Donation
|
||||||
|
</v-btn>
|
||||||
|
<v-btn @click="subscription">
|
||||||
|
Subscription
|
||||||
|
</v-btn>
|
||||||
|
<v-btn @click="cheer">
|
||||||
|
Cheer
|
||||||
|
</v-btn>
|
||||||
|
<v-btn @click="tweet">
|
||||||
|
Tweet
|
||||||
|
</v-btn>
|
||||||
|
<v-btn @click="crowdControl">
|
||||||
|
Crowd Control
|
||||||
|
</v-btn>
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<span title="ExampleUser1, he/him, exampleuser1, DE">Scan Tag 1:</span>
|
||||||
|
<v-btn @click="scanTag(1, '1')">B.1</v-btn>
|
||||||
|
<v-btn @click="scanTag(1, '2')">B.2</v-btn>
|
||||||
|
<v-btn @click="scanTag(1, '3')">B.3</v-btn>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<span title="ExampleUser2, she/her, exampleuser2, SE">Scan Tag 2:</span>
|
||||||
|
<v-btn @click="scanTag(2, '1')">B.1</v-btn>
|
||||||
|
<v-btn @click="scanTag(2, '2')">B.2</v-btn>
|
||||||
|
<v-btn @click="scanTag(2, '3')">B.3</v-btn>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<span title="ExampleUser3, they/them, exampleuser3, FI">Scan Tag 3:</span>
|
||||||
|
<v-btn @click="scanTag(3, '1')">B.1</v-btn>
|
||||||
|
<v-btn @click="scanTag(3, '2')">B.2</v-btn>
|
||||||
|
<v-btn @click="scanTag(3, '3')">B.3</v-btn>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
<span title="ExampleUser, no pronouns, no Twitch, no country">Scan Tag 4:</span>
|
||||||
|
<v-btn @click="scanTag(4, '1')">B.1</v-btn>
|
||||||
|
<v-btn @click="scanTag(4, '2')">B.2</v-btn>
|
||||||
|
<v-btn @click="scanTag(4, '3')">B.3</v-btn>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-center">
|
||||||
|
Press Button:
|
||||||
|
<v-btn @click="pressBtn(1)">B.1</v-btn>
|
||||||
|
<v-btn @click="pressBtn(2)">B.2</v-btn>
|
||||||
|
<v-btn @click="pressBtn(3)">B.3</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-app>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Vue, Component, Prop } from 'vue-property-decorator';
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class extends Vue {
|
||||||
|
@Prop(Boolean) enabled!: boolean;
|
||||||
|
@Prop(Boolean) useTestData!: boolean;
|
||||||
|
|
||||||
|
donation(): void {
|
||||||
|
nodecg.sendMessage('testRabbitMQ', { msgType: 'donationFullyProcessed' });
|
||||||
|
}
|
||||||
|
subscription(): void {
|
||||||
|
nodecg.sendMessage('testRabbitMQ', { msgType: 'newScreenedSub' });
|
||||||
|
}
|
||||||
|
cheer(): void {
|
||||||
|
nodecg.sendMessage('testRabbitMQ', { msgType: 'newScreenedCheer' });
|
||||||
|
}
|
||||||
|
tweet(): void {
|
||||||
|
nodecg.sendMessage('testRabbitMQ', { msgType: 'newScreenedTweet' });
|
||||||
|
}
|
||||||
|
crowdControl(): void {
|
||||||
|
nodecg.sendMessage('testRabbitMQ', { msgType: 'newScreenedCrowdControl' });
|
||||||
|
}
|
||||||
|
scanTag(tag: number, id: string): void {
|
||||||
|
nodecg.sendMessage('testRabbitMQ', { msgType: 'bigbuttonTagScanned', data: { tag, id } });
|
||||||
|
}
|
||||||
|
pressBtn(id: number): void {
|
||||||
|
nodecg.sendMessage('testRabbitMQ', { msgType: 'bigbuttonPressed', data: { id } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|