Merge pull request #88 from DerTyp7/dev

Implement generator to simplify customization
This commit is contained in:
Janis
2023-11-12 18:24:08 +01:00
committed by GitHub
15 changed files with 721 additions and 322 deletions

View File

@@ -1,14 +1,11 @@
module.exports = {
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
plugins: ['react-refresh'],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended"],
parser: "@typescript-eslint/parser",
parserOptions: { ecmaVersion: "latest", sourceType: "module" },
plugins: ["react-refresh"],
rules: {
'react-refresh/only-export-components': 'warn',
"react-refresh/only-export-components": "warn",
"react-hooks/exhaustive-deps": "ignore",
},
}
};

View File

@@ -1,7 +1,7 @@
{
"tabWidth": 2,
"useTabs": false,
"printWidth": 120,
"printWidth": 180,
"singleQuote": false,
"semi": true
}

116
README.md
View File

@@ -1,107 +1,59 @@
# Teamspeak5-OBS-Overlay
# TeamSpeak5-OBS-Overlay
This is an overlay for OBS to show the current talking clients in your Teamspeak 5 Channel.
This App uses the new "Remote Apps" feature of Teamspeak 5.
This is an overlay for OBS to show the current talking clients in your TeamSpeak5 Channel.
This App uses the new "Remote Apps" feature of TeamSpeak5.
This overlay uses the [Teamspeak 5 Remote App API](https://github.com/DerTyp7/react-ts5-remote-app-api).
This overlay uses the [TeamSpeak5 Remote App API](https://github.com/DerTyp7/react-ts5-remote-app-api).
![image](https://github.com/DerTyp7/ts5-obs-overlay/assets/76851529/d0ab06f2-1a36-479d-826f-bd4bd3d405b7)
- [Teamspeak5-OBS-Overlay](#teamspeak5-obs-overlay)
- [TeamSpeak5-OBS-Overlay](#teamspeak5-obs-overlay)
- [Usage](#usage)
- [Quick instructions (online usage only)](#quick-instructions-online-usage-only)
- [Instructions](#instructions)
- [Settings (Parameters)](#settings-parameters)
- [Are you using the **online** version (recommended version)?](#are-you-using-the-online-version-recommended-version)
- [Are you using the **offline** version?](#are-you-using-the-offline-version)
- [Adding Parameters](#adding-parameters)
- [Setup (Developer)](#setup-developer)
- [Quick instructions](#quick-instructions)
- [Detailed instructions](#detailed-instructions)
- [Common Issues](#common-issues)
- [The overlay is empty, but i'm connected to a Teamspeak 5 server](#the-overlay-is-empty-but-im-connected-to-a-teamspeak-5-server)
- [The overlay is empty, but i'm connected to a TeamSpeak5 server](#the-overlay-is-empty-but-im-connected-to-a-teamspeak5-server)
- [OBS doesn't show the latest version of the overlay](#obs-doesnt-show-the-latest-version-of-the-overlay)
- [Setup (Developer)](#setup-developer)
## Usage
### Quick instructions (online usage only)
### Quick instructions
1. Go into the **Teamspeak 5 Settings** and enable "**Remote Apps**"
2. Add a **new Browser Source** to your **OBS** Scene and enter `https://dertyp7.github.io/ts5-obs-overlay/` as URL
3. Set the **width and height** to your desired size (e.g. 1920x1080 OR 1280x720)
4. You should now receive a **notification in Teamspeak** 5 that the app is allowed to connect to your Teamspeak 5 client. **Allow it**.
1. Open this link in your Browser: [https://dertyp7.github.io/ts5-obs-overlay/generate](https://dertyp7.github.io/ts5-obs-overlay/generate)
2. Follow the instructions on the website
3. Accept overlay inside TeamSpeak5
![image](https://github.com/DerTyp7/ts5-obs-overlay/assets/76851529/40faa435-e128-415f-98eb-a9e8809e8f65)
### Instructions
### Detailed instructions
1. Go into the Teamspeak 5 Settings and enable "Remote Apps"
Try this instruction if you have problems with the quick instructions above.
1. Open this link in your Browser: [https://dertyp7.github.io/ts5-obs-overlay/generate](https://dertyp7.github.io/ts5-obs-overlay/generate)
2. Follow the instructions on the website
3. Go into the TeamSpeak5 Settings and enable "Remote Apps"
![image](https://github.com/DerTyp7/ts5-obs-overlay/assets/76851529/b31bc553-fde2-46ab-b07c-d3c81339cc7d)
2. Add a new Browser Source to your OBS Scene
4. Add a new Browser Source to your OBS Scene
![image](https://github.com/DerTyp7/ts5-obs-overlay/assets/76851529/0198b468-bb96-4b65-bdd4-3d6bb3ef7d25)
![image](https://github.com/DerTyp7/ts5-obs-overlay/assets/76851529/58ad399f-5344-456f-b243-6e267b489fd5)
3. Configure **Browser Source** (Use **ONE** of the following methods)
5. Enter the in step 1 generated URL into the URL field of the Browser Source
![image](https://github.com/DerTyp7/ts5-obs-overlay/assets/76851529/50b755f9-d4b4-469f-9136-e2b18f226547)
1. **Online Usage (recommended):** Enter **`https://dertyp7.github.io/ts5-obs-overlay/`** as URL
2. **Offline Usage (ignore this if you use the online usage above):**
1. Download the `ts5-overlay-{version}.html` of the latest release from [here](https://github.com/DerTyp7/ts5-obs-overlay/releases/latest)
![image](https://github.com/DerTyp7/ts5-obs-overlay/assets/76851529/04dc3a66-c493-429b-b4ae-44bade473ad6)
1.1. (optional) You can rename the file. Just remember using the new file name in the future instead of `ts5-overlay-{version}.html`
2. Tick the checkbox "Local File" and select the downloaded `ts5-overlay-{version}.html`
6. Set the width and height to your desired size. Recommended is a ratio of 1:1 or 1:2 (e.g. 1500x3000 OR 1000x2000)
4. Set the width and height to your desired size. Recommended is a ratio of 1:1 or 1:2 (e.g. 1500x3000 OR 1000x2000)
5. You should now receive a notification in Teamspeak 5 that the app is allowed to connect to your Teamspeak 5 client. Allow it. (If you don't get a notification, restart Teamspeak 5 and OBS -> try again)
7. You should now receive a notification in TeamSpeak5 that the app is allowed to connect to your TeamSpeak5 client. Allow it. (If you don't get a notification, restart TeamSpeak5 and OBS -> try again)
![image](https://github.com/DerTyp7/ts5-obs-overlay/assets/76851529/40faa435-e128-415f-98eb-a9e8809e8f65)
## Settings (Parameters)
You can customize the overlay by **adding parameters** to the URL of the **Browser Source**.
### Are you using the **online** version (recommended version)?
1. Open your Browser Source settings
2. Start adding parameters like discribed in [Adding Parameters](#adding-parameters)
### Are you using the **offline** version?
1. Open your Browser Source settings
2. **Untick** the checkbox "Local File"
3. Add `file://` to the beginning of the URL
![image](https://github.com/DerTyp7/ts5-obs-overlay/assets/76851529/87985b4c-4397-4681-9635-239d1e382c24)
4. Start adding parameters like discribed in [Adding Parameters](#adding-parameters)
### Adding Parameters
Start by adding a `?` to the end of the URL and then add the parameters.
To add multiple parameters, you have to seperate them with a `&`.
Like this:
**Online:** _`https://dertyp7.github.io/ts5-obs-overlay/?parameter1=value1&parameter2=value2`_
**Offline:** _`file://C:/Users/.../ts5-overlay-{version}.html?parameter1=value1&parameter2=value2`_
Real example:
**Online:** _`https://dertyp7.github.io/ts5-obs-overlay/?remoteAppPort=5899&hideNonTalking=true&clientLimit=5&showChannelName=true`_
**Offline:** _`file://C:/Users/.../ts5-overlay-{version}.html?remoteAppPort=5899&hideNonTalking=true&clientLimit=5&showChannelName=true`_
This is a **list** of **all available parameters** (all parameters are optional):
| Parameter | Description | Type | Default |
| ----------------- | ---------------------------------------- | ------- | --------------- |
| `remoteAppPort` | The port of the Teamspeak 5 remote app | number | `5899` |
| `hideNonTalking` | Hide all non-talking clients | boolean | `false` |
| `clientLimit` | Count of how many client should be shown | number | `0` (unlimited) |
| `showChannelName` | Display the channel name | boolean | `false` |
## Setup (Developer)
1. Clone this repository
2. Run `npm install`
3. To start the development server run `npm run dev`
## Common Issues
### The overlay is empty, but i'm connected to a Teamspeak 5 server
### The overlay is empty, but i'm connected to a TeamSpeak5 server
**Fix 1**
Make sure you accepted the notifiaction in your Teamspeak Client.
Make sure you accepted the notification in your TeamSpeak Client.
**Fix 2**
Sadly TeamSpeak5 does not give us any information about the current active server tab.
@@ -118,3 +70,13 @@ Possible fixes:
This can happen if the OBS Browser Source is caching the overlay.
To fix this, open the Browser Source settings and click on "Refresh cache of current page".
## Setup (Developer)
1. Clone this repository
2. Run `npm install`
3. To start the development server run `npm run dev`
> **Note:** Pull requests are welcome, but please be consistent with the code style.
> This project uses [Prettier](https://prettier.io/) to format the code.
> Pull requests always in the `dev` branch.

270
package-lock.json generated
View File

@@ -1,33 +1,32 @@
{
"name": "ts5-obs-overlay",
"version": "1.2.4",
"version": "2.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ts5-obs-overlay",
"version": "1.2.4",
"version": "2.0.0",
"dependencies": {
"@types/node": "^20.8.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.14.2",
"react-router-dom": "^6.18.0",
"react-ts5-remote-app-api": "^1.1.1",
"sass": "^1.68.0"
},
"devDependencies": {
"@types/jest": "^29.5.2",
"@types/react": "^18.2.23",
"@types/react-dom": "^18.2.7",
"@types/react": "^18.2.35",
"@types/react-dom": "^18.2.14",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.2.1",
"@typescript-eslint/parser": "^6.9.1",
"@vitejs/plugin-react-swc": "^3.0.0",
"eslint": "^8.45.0",
"eslint": "^8.53.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"typescript": "^5.1.6",
"vite": "^4.4.7",
"vite-plugin-singlefile": "^0.13.5"
"typescript": "^5.2.2",
"vite": "^4.5.0"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -533,18 +532,18 @@
}
},
"node_modules/@eslint-community/regexpp": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz",
"integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==",
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
"integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
"dev": true,
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
"node_modules/@eslint/eslintrc": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz",
"integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz",
"integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
@@ -565,21 +564,21 @@
}
},
"node_modules/@eslint/js": {
"version": "8.44.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz",
"integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==",
"version": "8.53.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz",
"integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
"integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==",
"version": "0.11.13",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
"integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
"dev": true,
"dependencies": {
"@humanwhocodes/object-schema": "^1.2.1",
"@humanwhocodes/object-schema": "^2.0.1",
"debug": "^4.1.1",
"minimatch": "^3.0.5"
},
@@ -601,9 +600,9 @@
}
},
"node_modules/@humanwhocodes/object-schema": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
"dev": true
},
"node_modules/@jest/expect-utils": {
@@ -696,11 +695,11 @@
}
},
"node_modules/@remix-run/router": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.2.tgz",
"integrity": "sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A==",
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.11.0.tgz",
"integrity": "sha512-BHdhcWgeiudl91HvVa2wxqZjSHbheSgIiDvxrF1VjFzBzpTtuDPkOdOi3Iqvc08kXtFkLjhbS+ML9aM8mJS+wQ==",
"engines": {
"node": ">=14"
"node": ">=14.0.0"
}
},
"node_modules/@rollup/plugin-typescript": {
@@ -1026,9 +1025,9 @@
"devOptional": true
},
"node_modules/@types/react": {
"version": "18.2.23",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.23.tgz",
"integrity": "sha512-qHLW6n1q2+7KyBEYnrZpcsAmU/iiCh9WGCKgXvMxx89+TYdJWRjZohVIo9XTcoLhfX3+/hP0Pbulu3bCZQ9PSA==",
"version": "18.2.37",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.37.tgz",
"integrity": "sha512-RGAYMi2bhRgEXT3f4B92WTohopH6bIXw05FuGlmJEnv/omEn190+QYEIYxIAuIBdKgboYYdVved2p1AxZVQnaw==",
"devOptional": true,
"dependencies": {
"@types/prop-types": "*",
@@ -1037,9 +1036,9 @@
}
},
"node_modules/@types/react-dom": {
"version": "18.2.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz",
"integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==",
"version": "18.2.15",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.15.tgz",
"integrity": "sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg==",
"dev": true,
"dependencies": {
"@types/react": "*"
@@ -1114,15 +1113,15 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.2.1.tgz",
"integrity": "sha512-Ld+uL1kYFU8e6btqBFpsHkwQ35rw30IWpdQxgOqOh4NfxSDH6uCkah1ks8R/RgQqI5hHPXMaLy9fbFseIe+dIg==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.10.0.tgz",
"integrity": "sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "6.2.1",
"@typescript-eslint/types": "6.2.1",
"@typescript-eslint/typescript-estree": "6.2.1",
"@typescript-eslint/visitor-keys": "6.2.1",
"@typescript-eslint/scope-manager": "6.10.0",
"@typescript-eslint/types": "6.10.0",
"@typescript-eslint/typescript-estree": "6.10.0",
"@typescript-eslint/visitor-keys": "6.10.0",
"debug": "^4.3.4"
},
"engines": {
@@ -1142,13 +1141,13 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.2.1.tgz",
"integrity": "sha512-UCqBF9WFqv64xNsIEPfBtenbfodPXsJ3nPAr55mGPkQIkiQvgoWNo+astj9ZUfJfVKiYgAZDMnM6dIpsxUMp3Q==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.10.0.tgz",
"integrity": "sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.2.1",
"@typescript-eslint/visitor-keys": "6.2.1"
"@typescript-eslint/types": "6.10.0",
"@typescript-eslint/visitor-keys": "6.10.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -1159,9 +1158,9 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.2.1.tgz",
"integrity": "sha512-528bGcoelrpw+sETlyM91k51Arl2ajbNT9L4JwoXE2dvRe1yd8Q64E4OL7vHYw31mlnVsf+BeeLyAZUEQtqahQ==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.10.0.tgz",
"integrity": "sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -1172,13 +1171,13 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.2.1.tgz",
"integrity": "sha512-G+UJeQx9AKBHRQBpmvr8T/3K5bJa485eu+4tQBxFq0KoT22+jJyzo1B50JDT9QdC1DEmWQfdKsa8ybiNWYsi0Q==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.10.0.tgz",
"integrity": "sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.2.1",
"@typescript-eslint/visitor-keys": "6.2.1",
"@typescript-eslint/types": "6.10.0",
"@typescript-eslint/visitor-keys": "6.10.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -1199,12 +1198,12 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.2.1.tgz",
"integrity": "sha512-iTN6w3k2JEZ7cyVdZJTVJx2Lv7t6zFA8DCrJEHD2mwfc16AEvvBWVhbFh34XyG2NORCd0viIgQY1+u7kPI0WpA==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.10.0.tgz",
"integrity": "sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.2.1",
"@typescript-eslint/types": "6.10.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
@@ -1341,6 +1340,12 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
},
"node_modules/@vitejs/plugin-react-swc": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.3.2.tgz",
@@ -1728,27 +1733,28 @@
}
},
"node_modules/eslint": {
"version": "8.45.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.45.0.tgz",
"integrity": "sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==",
"version": "8.53.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz",
"integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.4.0",
"@eslint/eslintrc": "^2.1.0",
"@eslint/js": "8.44.0",
"@humanwhocodes/config-array": "^0.11.10",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.3",
"@eslint/js": "8.53.0",
"@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"ajv": "^6.10.0",
"@ungap/structured-clone": "^1.2.0",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.3.2",
"doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^7.2.0",
"eslint-visitor-keys": "^3.4.1",
"espree": "^9.6.0",
"eslint-scope": "^7.2.2",
"eslint-visitor-keys": "^3.4.3",
"espree": "^9.6.1",
"esquery": "^1.4.2",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@@ -1802,22 +1808,10 @@
"eslint": ">=7"
}
},
"node_modules/eslint-visitor-keys": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
"integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint/node_modules/eslint-scope": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz",
"integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==",
"node_modules/eslint-scope": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
"integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
"dev": true,
"dependencies": {
"esrecurse": "^4.3.0",
@@ -1830,19 +1824,22 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint/node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"node_modules/eslint-visitor-keys": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"engines": {
"node": ">=4.0"
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/espree": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz",
"integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==",
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
"integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
"dev": true,
"dependencies": {
"acorn": "^8.9.0",
@@ -1868,15 +1865,6 @@
"node": ">=0.10"
}
},
"node_modules/esquery/node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
"engines": {
"node": ">=4.0"
}
},
"node_modules/esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
@@ -1889,7 +1877,7 @@
"node": ">=4.0"
}
},
"node_modules/esrecurse/node_modules/estraverse": {
"node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
@@ -2099,9 +2087,9 @@
}
},
"node_modules/globals": {
"version": "13.20.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
"integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
"version": "13.23.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz",
"integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==",
"dev": true,
"dependencies": {
"type-fest": "^0.20.2"
@@ -2713,9 +2701,9 @@
}
},
"node_modules/punycode": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"engines": {
"node": ">=6"
@@ -2771,29 +2759,29 @@
"dev": true
},
"node_modules/react-router": {
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.2.tgz",
"integrity": "sha512-09Zss2dE2z+T1D03IheqAFtK4UzQyX8nFPWx6jkwdYzGLXd5ie06A6ezS2fO6zJfEb/SpG6UocN2O1hfD+2urQ==",
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.18.0.tgz",
"integrity": "sha512-vk2y7Dsy8wI02eRRaRmOs9g2o+aE72YCx5q9VasT1N9v+lrdB79tIqrjMfByHiY5+6aYkH2rUa5X839nwWGPDg==",
"dependencies": {
"@remix-run/router": "1.7.2"
"@remix-run/router": "1.11.0"
},
"engines": {
"node": ">=14"
"node": ">=14.0.0"
},
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/react-router-dom": {
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.2.tgz",
"integrity": "sha512-5pWX0jdKR48XFZBuJqHosX3AAHjRAzygouMTyimnBPOLdY3WjzUSKhus2FVMihUFWzeLebDgr4r8UeQFAct7Bg==",
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.18.0.tgz",
"integrity": "sha512-Ubrue4+Ercc/BoDkFQfc6og5zRQ4A8YxSO3Knsne+eRbZ+IepAsK249XBH/XaFuOYOYr3L3r13CXTLvYt5JDjw==",
"dependencies": {
"@remix-run/router": "1.7.2",
"react-router": "6.14.2"
"@remix-run/router": "1.11.0",
"react-router": "6.18.0"
},
"engines": {
"node": ">=14"
"node": ">=14.0.0"
},
"peerDependencies": {
"react": ">=16.8",
@@ -2890,9 +2878,9 @@
}
},
"node_modules/rollup": {
"version": "3.26.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.2.tgz",
"integrity": "sha512-6umBIGVz93er97pMgQO08LuH3m6PUb3jlDUUGFsNJB6VgTCUaDFpupf5JfU30529m/UKOgmiX+uY6Sx8cOYpLA==",
"version": "3.29.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
"integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
"devOptional": true,
"bin": {
"rollup": "dist/bin/rollup"
@@ -3184,9 +3172,9 @@
}
},
"node_modules/typescript": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
"integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -3210,14 +3198,14 @@
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="
},
"node_modules/vite": {
"version": "4.4.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.7.tgz",
"integrity": "sha512-6pYf9QJ1mHylfVh39HpuSfMPojPSKVxZvnclX1K1FyZ1PXDOcLBibdq5t1qxJSnL63ca8Wf4zts6mD8u8oc9Fw==",
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz",
"integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==",
"dev": true,
"dependencies": {
"esbuild": "^0.18.10",
"postcss": "^8.4.26",
"rollup": "^3.25.2"
"postcss": "^8.4.27",
"rollup": "^3.27.1"
},
"bin": {
"vite": "bin/vite.js"
@@ -3264,22 +3252,6 @@
}
}
},
"node_modules/vite-plugin-singlefile": {
"version": "0.13.5",
"resolved": "https://registry.npmjs.org/vite-plugin-singlefile/-/vite-plugin-singlefile-0.13.5.tgz",
"integrity": "sha512-y/aRGh8qHmw2f1IhaI/C6PJAaov47ESYDvUv1am1YHMhpY+19B5k5Odp8P+tgs+zhfvak6QB1ykrALQErEAo7g==",
"dev": true,
"dependencies": {
"micromatch": "^4.0.5"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"peerDependencies": {
"rollup": ">=2.79.0",
"vite": ">=3.2.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@@ -1,7 +1,7 @@
{
"name": "ts5-obs-overlay",
"private": false,
"version": "1.3.1",
"version": "2.0.0",
"description": "Overlay for OBS to show the current talking clients in your Teamspeak 5 Channel",
"author": "DerTyp7",
"homepage": "https://dertyp7.github.io/ts5-obs-overlay",
@@ -33,22 +33,21 @@
"@types/node": "^20.8.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.14.2",
"react-router-dom": "^6.18.0",
"react-ts5-remote-app-api": "^1.1.1",
"sass": "^1.68.0"
},
"devDependencies": {
"@types/jest": "^29.5.2",
"@types/react": "^18.2.23",
"@types/react-dom": "^18.2.7",
"@types/react": "^18.2.35",
"@types/react-dom": "^18.2.14",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.2.1",
"@typescript-eslint/parser": "^6.9.1",
"@vitejs/plugin-react-swc": "^3.0.0",
"eslint": "^8.45.0",
"eslint": "^8.53.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"typescript": "^5.1.6",
"vite": "^4.4.7",
"vite-plugin-singlefile": "^0.13.5"
"typescript": "^5.2.2",
"vite": "^4.5.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

@@ -1,37 +1,27 @@
import "@styles/App.scss";
import { useSearchParams } from "react-router-dom";
import useTSRemoteApp, { IClient } from "react-ts5-remote-app-api";
import { Navigate, Route, Routes, useSearchParams } from "react-router-dom";
import Viewer from "./Viewer";
import Generator from "./Generator";
export default function App() {
const [searchParams] = useSearchParams();
const { clients, activeConnectionId, currentChannel } = useTSRemoteApp({
remoteAppPort: parseInt(searchParams.get("remoteAppPort") ?? "5899"),
auth: {
identifier: "de.tealfire.obs",
version: "1.3.1",
name: "TS5 OBS Overlay",
description: "A OBS overlay for TS5 by DerTyp7",
},
logging: true,
});
return (
<div className="App">
<Viewer
showChannelName={searchParams.get("showChannelName") === "true"}
hideNonTalking={searchParams.get("hideNonTalking") === "true"}
clientLimit={searchParams.get("clientLimit") ? parseInt(searchParams.get("clientLimit") ?? "0") : 0}
clients={
clients.map((client) => {
if (client.channel?.id === currentChannel?.id && client.channel.connection.id === activeConnectionId) {
return client;
}
}) as IClient[]
<Routes>
<Route
path="/"
element={
<Viewer
remoteAppPort={parseInt(searchParams.get("remoteAppPort") ?? "5899")}
showChannelName={searchParams.get("showChannelName") === "true"}
hideNonTalking={searchParams.get("hideNonTalking") === "true"}
clientLimit={searchParams.get("clientLimit") ? parseInt(searchParams.get("clientLimit") ?? "0") : 0}
/>
}
channel={currentChannel}
/>
</div>
<Route path="/generate" element={<Generator />} />
<Route path="*" element={<Navigate to="/generate" replace />} />
</Routes>
);
}

122
src/Generator.tsx Normal file
View File

@@ -0,0 +1,122 @@
import React, { ChangeEvent, useRef, useState, useEffect } from "react";
import "@styles/Generator.scss";
import Viewer from "./Viewer";
export default function Generator() {
// State variables
const [outputUrl, setOutputUrl] = useState(() => new URL(window.location.href).toString());
const copiedTooltipRef = useRef<HTMLDivElement>(null);
const [remoteAppPort, setRemoteAppPort] = useState(5899);
const [showChannelName, setShowChannelName] = useState(true);
const [hideNonTalking, setHideNonTalking] = useState(false);
const [clientLimit, setClientLimit] = useState(0);
// Effect to generate URL when dependencies change
useEffect(() => {
generateUrl();
}, [remoteAppPort, showChannelName, hideNonTalking, clientLimit]);
// Function to generate the output URL
function generateUrl() {
const url = new URL(window.location.href.replace("/generate", ""));
url.searchParams.set("remoteAppPort", remoteAppPort.toString());
url.searchParams.set("showChannelName", showChannelName.toString());
url.searchParams.set("hideNonTalking", hideNonTalking.toString());
url.searchParams.set("clientLimit", clientLimit.toString());
setOutputUrl(url.toString());
}
// Function to copy URL to clipboard
function copy() {
navigator.clipboard.writeText(outputUrl);
if (copiedTooltipRef.current) {
copiedTooltipRef.current.style.animation = "tooltipAnimation 200ms";
copiedTooltipRef.current.style.opacity = "1";
setTimeout(() => {
if (copiedTooltipRef.current) {
copiedTooltipRef.current.style.opacity = "0";
copiedTooltipRef.current.style.animation = "";
}
}, 1000);
}
}
return (
<div className="generator">
{/* Header */}
<div className="headline">
<h1>TS5-OBS-Overlay Generator</h1>
<h4>by DerTyp7</h4>
</div>
{/* Instructions */}
<div className="instructions">
<p>1. Customize your settings</p>
<p>2. Copy the generated URL</p>
<p>3. Paste the URL into the BrowserSource URL field in OBS</p>
<a href="#">Click here for detailed instructions</a>
</div>
{/* Output Section */}
<div className="output">
<p className="url">
<code>{outputUrl}</code>
</p>
<button onClick={copy} className="copy">
Copy
</button>
<div ref={copiedTooltipRef} className="copiedTooltip">
Copied!
</div>
</div>
{/* Generator Content */}
<div className="generatorContent">
{/* Configurations */}
<div className="configurations">
<h2>Configurations</h2>
<div className="options">
{/* Option Sections */}
<section>
{/* Show Channel Name Option */}
<div className="option" onClick={() => setShowChannelName(!showChannelName)}>
<input type="checkbox" checked={showChannelName} />
<label>Show channel name</label>
</div>
{/* Hide Non-Talking Clients Option */}
<div className="option" onClick={() => setHideNonTalking(!hideNonTalking)}>
<input type="checkbox" checked={hideNonTalking} />
<label>Hide non talking clients</label>
</div>
</section>
<section>
{/* Client Limit Option */}
<div className="option">
<input type="number" value={clientLimit} min={0} onChange={(e: ChangeEvent<HTMLInputElement>) => setClientLimit(parseInt(e.target.value))} />
<label>Client Limit</label>
</div>
{/* RemoteApp-Port Option */}
<div className="option">
<input type="number" value={remoteAppPort} min={0} onChange={(e: ChangeEvent<HTMLInputElement>) => setRemoteAppPort(parseInt(e.target.value))} />
<label>RemoteApp-Port</label>
</div>
</section>
</div>
</div>
{/* Preview */}
<div className="preview">
<Viewer remoteAppPort={remoteAppPort} showChannelName={showChannelName} hideNonTalking={hideNonTalking} clientLimit={clientLimit} />
</div>
</div>
</div>
);
}

View File

@@ -1,27 +1,42 @@
import "@styles/Viewer.scss";
import { IChannel, IClient } from "react-ts5-remote-app-api";
import useTSRemoteApp, { IClient } from "react-ts5-remote-app-api";
export default function Viewer({
clients,
channel,
remoteAppPort = 5899,
showChannelName = false,
hideNonTalking = false,
clientLimit = 0,
}: {
clients: IClient[] | undefined;
channel: IChannel | undefined;
remoteAppPort?: number;
showChannelName?: boolean;
hideNonTalking?: boolean;
clientLimit?: number;
}) {
const { clients, activeConnectionId, currentChannel } = useTSRemoteApp({
remoteAppPort: remoteAppPort,
auth: {
identifier: "de.tealfire.obs",
version: "2.0.0",
name: "TS5 OBS Overlay",
description: "A OBS overlay for TS5 by DerTyp7",
},
logging: true,
});
const currentClients = clients.map((client) => {
if (client.channel?.id === currentChannel?.id && client.channel.connection.id === activeConnectionId) {
return client;
}
}) as IClient[];
return (
<div className="viewer">
{showChannelName ? (
<div className="channelNameContainer">
<h3>{channel?.properties.name}</h3>
<h1>{currentChannel?.properties.name}</h1>
</div>
) : null}
{clients?.map((client, i) => {
{currentClients?.map((client, i) => {
//* Client limit
if (clientLimit != 0 && i >= clientLimit) {
return null;
@@ -29,10 +44,7 @@ export default function Viewer({
if (client) {
//* Non-talking client
if (
hideNonTalking &&
(client.properties.inputMuted || client.properties.outputMuted || client.talkStatus == 0)
) {
if (hideNonTalking && (client.properties.inputMuted || client.properties.outputMuted || client.talkStatus == 0)) {
return null;
}
@@ -73,16 +85,7 @@ export default function Viewer({
d="M88.62,54.15V64A24.69,24.69,0,0,1,64,88.62a25.26,25.26,0,0,1-8.38-1.46l-7.39,7.39A34,34,0,0,0,64,98.46,34.5,34.5,0,0,0,98.46,64V54.15a4.92,4.92,0,1,1,9.85,0V64a44.31,44.31,0,0,1-39.38,44v10.15H88.62a4.92,4.92,0,0,1,0,9.85H39.38a4.92,4.92,0,1,1,0-9.85H59.08V108A43.3,43.3,0,0,1,41,101.77L21.46,121.31a2.46,2.46,0,0,1-3.54,0L11.62,115a2.46,2.46,0,0,1,0-3.54l94.92-94.92a2.46,2.46,0,0,1,3.54,0l6.31,6.31a2.46,2.46,0,0,1,0,3.54ZM22.92,80.46A43.3,43.3,0,0,1,19.69,64V54.15a4.92,4.92,0,1,1,9.85,0V64a35.94,35.94,0,0,0,1.15,8.69ZM39.38,64V24.62a24.62,24.62,0,0,1,47.77-8.38Z"
fill="#d8d8d8"
/>
<rect
x="-5.93"
y="61.89"
width="139.87"
height="14.02"
rx="2.87"
ry="2.87"
transform="translate(-29.97 65.43) rotate(-45)"
fill="#c9070a"
/>
<rect x="-5.93" y="61.89" width="139.87" height="14.02" rx="2.87" ry="2.87" transform="translate(-29.97 65.43) rotate(-45)" fill="#c9070a" />
</g>
</svg>
) : client.properties.outputMuted ? (

View File

@@ -1,10 +1,10 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App.tsx";
import "@styles/index.scss";
import { BrowserRouter } from "react-router-dom";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
ReactDOM.createRoot(document.getElementById("root")!).render(
<BrowserRouter>
<App />
</BrowserRouter>

259
src/styles/Generator.scss Normal file
View File

@@ -0,0 +1,259 @@
// Breakpoints
$breakpoint-1: 1200px;
$breakpoint-2: 790px;
$breakpoint-3: 600px;
// Tooltip animation keyframes
@keyframes tooltipAnimation {
0% {
opacity: 0;
transform: translateY(-10px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.generator {
background-color: #232528;
color: #fff;
width: 100%;
display: flex;
height: 100%;
flex-direction: column;
align-items: center;
gap: 50px;
padding: 50px 0;
.headline {
text-align: center;
letter-spacing: 1.8px;
}
.instructions {
border-bottom: 2px solid #75797773;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 10px 50px;
flex-wrap: wrap;
padding-bottom: 10px;
p,
a {
font-size: 0.8rem;
}
p {
color: #b4b4b4;
}
}
.output {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
column-gap: 20px;
position: relative;
height: 50px;
.url {
white-space: nowrap;
overflow: auto;
font-weight: bold;
width: 500px;
padding: 5px 10px;
background-color: #313136;
border-radius: 5px;
text-align: center;
}
.copy {
padding: 5px 10px;
border: 2px solid #31f39973;
transition: all 100ms ease-in-out;
&:hover {
border-color: #42d486;
background-color: transparent;
color: #fff;
}
}
.copiedTooltip {
opacity: 0;
position: absolute;
right: -90px;
background-color: #31f399;
color: #202024;
padding: 5px 10px;
border-radius: 5px;
font-size: 0.8rem;
font-weight: bold;
}
}
.generatorContent {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 0 50px;
gap: 30px;
width: 100%;
.configurations {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
flex: 1;
gap: 50px;
.options {
display: flex;
flex-direction: row;
align-items: center;
gap: 100px;
user-select: none;
section {
display: flex;
flex-direction: column;
align-items: center;
justify-content: left;
gap: 10px;
}
.option {
display: flex;
flex-direction: row;
align-items: center;
justify-content: left;
column-gap: 10px;
width: 100%;
input {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border: 2px solid #31f399;
border-radius: 5px;
background-color: #202024;
outline: none;
transition: all 200ms ease-in-out;
position: relative;
color: #fff;
text-align: center;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
// Cross when checked styles
&:checked {
&:after {
content: "";
position: absolute;
width: 10px;
height: 2px;
background-color: #31f399;
transform: rotate(45deg);
}
&:before {
content: "";
position: absolute;
width: 10px;
height: 2px;
background-color: #31f399;
transform: rotate(-45deg);
}
&:after,
&:before {
top: 7px;
left: 3px;
}
}
cursor: pointer;
}
input[type="number"] {
width: 50px;
height: 25px;
cursor: text;
-moz-appearance: textfield;
appearance: textfield;
}
label {
cursor: pointer;
}
}
}
}
// Preview styles
.preview {
flex: 1;
border: 2px solid #31f39973;
// Viewer styles (see src/styles/Viewer.scss)
.viewer {
background-image: url("/images/viewer_example_background.png");
background-repeat: no-repeat;
background-size: cover;
min-height: 500px;
}
}
}
// Responsive styles
@media screen and (max-width: $breakpoint-1) {
.generatorContent {
flex-direction: column;
.preview {
width: 80%;
}
}
}
@media screen and (max-width: $breakpoint-2) {
.output {
.url {
width: 300px;
}
}
.generatorContent {
.preview {
width: 100%;
}
}
}
@media screen and (max-width: $breakpoint-3) {
.output {
.url {
width: 200px;
}
.generatorContent {
.configurations {
.options {
flex-direction: column;
gap: 30px;
}
}
}
}
}
}

View File

@@ -1,22 +1,28 @@
//* Viewer styles
// this file contains styles for the viewer component
// styles for the viewer component should not be modified somewhere else
.viewer {
display: flex;
flex-direction: column;
gap: 0 0;
padding: 1rem;
padding: 0.5rem;
h1,
p {
background-color: #2f313680;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 20ch;
user-select: none;
}
.channelNameContainer {
display: flex;
align-items: center;
margin-bottom: 20px;
h3 {
font-size: 1.7rem;
font-weight: 500;
margin: 0;
background-color: rgba(47, 49, 54, 0.5);
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
color: white;
}
}
.client {
@@ -26,23 +32,16 @@
align-items: center;
margin: 0.5rem 0;
// icon styles
svg {
width: 2.8rem;
height: 2.8rem;
width: 2.1rem;
aspect-ratio: 1/1;
margin-right: 0.5rem;
}
p {
margin: 0;
color: white;
background-color: rgba(47, 49, 54, 0.5);
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
// ellipsis after 22 characters
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 20ch;
// client name styles
p {
font-size: 1.4rem;
}
}
}

View File

@@ -1,5 +1,101 @@
// Reset styles for all elements
* {
font-family: Arial, Helvetica, sans-serif;
font-weight: bold;
font-size: 3rem;
font-size: 1rem;
margin: 0;
padding: 0;
box-sizing: border-box;
}
// Set up basic styles for the entire page
body,
html {
min-height: 100vh;
min-width: 100%;
display: flex;
overflow-x: hidden;
color: #fff;
}
// Ensure the root element takes up the full viewport
#root {
height: 100%;
min-width: 100%;
}
// Headline styles
h1 {
font-size: 1.8rem;
}
h2 {
font-size: 1.5rem;
}
h3 {
font-size: 1.2rem;
}
h4 {
font-size: 1.1rem;
}
// Common styles for heading elements
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: bold;
}
// Text styles
a {
color: #3abe78;
font-weight: bold;
text-decoration: none;
transition: all 100ms ease-in-out;
&:hover {
color: #31f399;
}
}
// Button styles
button {
background-color: #202024;
color: #fff;
font-weight: bold;
border: 2px solid #31f399;
border-radius: 5px;
padding: 10px 20px;
cursor: pointer;
transition: all 300ms ease-in-out;
&:hover {
background-color: #42d486;
color: #202024;
}
}
// Custom dark-themed scrollbar
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-track {
background: #363638;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: #31f39973;
border-radius: 10px;
&:hover {
background: #48ee95;
}
}

View File

@@ -18,7 +18,8 @@
},
/* Bundler mode */
"moduleResolution": "bundler",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true,
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,

View File

@@ -1,6 +1,5 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import { viteSingleFile } from "vite-plugin-singlefile"
import path from 'path';
// https://vitejs.dev/config/
@@ -21,5 +20,5 @@ export default defineConfig({
outDir: 'dist',
emptyOutDir: true,
},
plugins: [react(), viteSingleFile({ useRecommendedBuildConfig: false })],
plugins: [react()],
})