mirror of
				https://github.com/DerTyp7/teamspeak-obs-overlay.git
				synced 2025-10-31 05:37:10 +01:00 
			
		
		
		
	Merge pull request #87 from DerTyp7/feature_implement-url-generator
Feature implement URL generator
This commit is contained in:
		| @@ -1,14 +1,11 @@ | |||||||
| module.exports = { | module.exports = { | ||||||
|   env: { browser: true, es2020: true }, |   env: { browser: true, es2020: true }, | ||||||
|   extends: [ |   extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended"], | ||||||
|     'eslint:recommended', |   parser: "@typescript-eslint/parser", | ||||||
|     'plugin:@typescript-eslint/recommended', |   parserOptions: { ecmaVersion: "latest", sourceType: "module" }, | ||||||
|     'plugin:react-hooks/recommended', |   plugins: ["react-refresh"], | ||||||
|   ], |  | ||||||
|   parser: '@typescript-eslint/parser', |  | ||||||
|   parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, |  | ||||||
|   plugins: ['react-refresh'], |  | ||||||
|   rules: { |   rules: { | ||||||
|     'react-refresh/only-export-components': 'warn', |     "react-refresh/only-export-components": "warn", | ||||||
|  |     "react-hooks/exhaustive-deps": "ignore", | ||||||
|   }, |   }, | ||||||
| } | }; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| { | { | ||||||
|   "tabWidth": 2, |   "tabWidth": 2, | ||||||
|   "useTabs": false, |   "useTabs": false, | ||||||
|   "printWidth": 120, |   "printWidth": 180, | ||||||
|   "singleQuote": false, |   "singleQuote": false, | ||||||
|   "semi": true |   "semi": true | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										116
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										116
									
								
								README.md
									
									
									
									
									
								
							| @@ -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 is an overlay for OBS to show the current talking clients in your TeamSpeak5 Channel.   | ||||||
| This App uses the new "Remote Apps" feature of Teamspeak 5. | 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). | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| - [Teamspeak5-OBS-Overlay](#teamspeak5-obs-overlay) | - [TeamSpeak5-OBS-Overlay](#teamspeak5-obs-overlay) | ||||||
|   - [Usage](#usage) |   - [Usage](#usage) | ||||||
|     - [Quick instructions (online usage only)](#quick-instructions-online-usage-only) |     - [Quick instructions](#quick-instructions) | ||||||
|     - [Instructions](#instructions) |     - [Detailed instructions](#detailed-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) |  | ||||||
|   - [Common Issues](#common-issues) |   - [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) |     - [OBS doesn't show the latest version of the overlay](#obs-doesnt-show-the-latest-version-of-the-overlay) | ||||||
|  |   - [Setup (Developer)](#setup-developer) | ||||||
|  |  | ||||||
| ## Usage | ## Usage | ||||||
|  |  | ||||||
| ### Quick instructions (online usage only) | ### Quick instructions | ||||||
|  |  | ||||||
| 1. Go into the **Teamspeak 5 Settings** and enable "**Remote Apps**" | 1. Open this link in your Browser: [https://dertyp7.github.io/ts5-obs-overlay/generate](https://dertyp7.github.io/ts5-obs-overlay/generate) | ||||||
| 2. Add a **new Browser Source** to your **OBS** Scene and enter `https://dertyp7.github.io/ts5-obs-overlay/` as URL | 2. Follow the instructions on the website | ||||||
| 3. Set the **width and height** to your desired size (e.g. 1920x1080 OR 1280x720) | 3. Accept overlay inside TeamSpeak5   | ||||||
| 4. You should now receive a **notification in Teamspeak** 5 that the app is allowed to connect to your Teamspeak 5 client. **Allow it**. |     | ||||||
|  |  | ||||||
| ### 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"   | ||||||
|     |     | ||||||
|  |  | ||||||
| 2. Add a new Browser Source to your OBS Scene   | 4. Add a new Browser Source to your OBS Scene   | ||||||
|       |       | ||||||
|     |     | ||||||
|  |  | ||||||
| 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 | ||||||
|  |     | ||||||
|  |  | ||||||
|    1. **Online Usage (recommended):** Enter **`https://dertyp7.github.io/ts5-obs-overlay/`** as URL | 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) | ||||||
|    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) |  | ||||||
|             |  | ||||||
|          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` |  | ||||||
|  |  | ||||||
| 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)  | 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)   | ||||||
| 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)   |  | ||||||
|     |     | ||||||
|  |  | ||||||
| ## 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   |  | ||||||
|     |  | ||||||
| 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¶meter2=value2`_   |  | ||||||
| **Offline:** _`file://C:/Users/.../ts5-overlay-{version}.html?parameter1=value1¶meter2=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 | ## 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**   | **Fix 1**   | ||||||
| Make sure you accepted the notifiaction in your Teamspeak Client. | Make sure you accepted the notification in your TeamSpeak Client. | ||||||
|  |  | ||||||
| **Fix 2**   | **Fix 2**   | ||||||
| Sadly TeamSpeak5 does not give us any information about the current active server tab.   | 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.   | 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". | 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. | ||||||
|   | |||||||
							
								
								
									
										79
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										79
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -26,8 +26,7 @@ | |||||||
|         "eslint-plugin-react-hooks": "^4.6.0", |         "eslint-plugin-react-hooks": "^4.6.0", | ||||||
|         "eslint-plugin-react-refresh": "^0.4.3", |         "eslint-plugin-react-refresh": "^0.4.3", | ||||||
|         "typescript": "^5.2.2", |         "typescript": "^5.2.2", | ||||||
|         "vite": "^4.5.0", |         "vite": "^4.5.0" | ||||||
|         "vite-plugin-singlefile": "^0.13.5" |  | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/@aashutoshrathi/word-wrap": { |     "node_modules/@aashutoshrathi/word-wrap": { | ||||||
| @@ -1026,9 +1025,9 @@ | |||||||
|       "devOptional": true |       "devOptional": true | ||||||
|     }, |     }, | ||||||
|     "node_modules/@types/react": { |     "node_modules/@types/react": { | ||||||
|       "version": "18.2.35", |       "version": "18.2.37", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.35.tgz", |       "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.37.tgz", | ||||||
|       "integrity": "sha512-LG3xpFZ++rTndV+/XFyX5vUP7NI9yxyk+MQvBDq+CVs8I9DLSc3Ymwb1Vmw5YDoeNeHN4PDZa3HylMKJYT9PNQ==", |       "integrity": "sha512-RGAYMi2bhRgEXT3f4B92WTohopH6bIXw05FuGlmJEnv/omEn190+QYEIYxIAuIBdKgboYYdVved2p1AxZVQnaw==", | ||||||
|       "devOptional": true, |       "devOptional": true, | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@types/prop-types": "*", |         "@types/prop-types": "*", | ||||||
| @@ -1037,9 +1036,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/@types/react-dom": { |     "node_modules/@types/react-dom": { | ||||||
|       "version": "18.2.14", |       "version": "18.2.15", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.14.tgz", |       "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.15.tgz", | ||||||
|       "integrity": "sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==", |       "integrity": "sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@types/react": "*" |         "@types/react": "*" | ||||||
| @@ -1114,15 +1113,15 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/@typescript-eslint/parser": { |     "node_modules/@typescript-eslint/parser": { | ||||||
|       "version": "6.9.1", |       "version": "6.10.0", | ||||||
|       "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz", |       "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.10.0.tgz", | ||||||
|       "integrity": "sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==", |       "integrity": "sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@typescript-eslint/scope-manager": "6.9.1", |         "@typescript-eslint/scope-manager": "6.10.0", | ||||||
|         "@typescript-eslint/types": "6.9.1", |         "@typescript-eslint/types": "6.10.0", | ||||||
|         "@typescript-eslint/typescript-estree": "6.9.1", |         "@typescript-eslint/typescript-estree": "6.10.0", | ||||||
|         "@typescript-eslint/visitor-keys": "6.9.1", |         "@typescript-eslint/visitor-keys": "6.10.0", | ||||||
|         "debug": "^4.3.4" |         "debug": "^4.3.4" | ||||||
|       }, |       }, | ||||||
|       "engines": { |       "engines": { | ||||||
| @@ -1142,13 +1141,13 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { |     "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { | ||||||
|       "version": "6.9.1", |       "version": "6.10.0", | ||||||
|       "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.9.1.tgz", |       "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.10.0.tgz", | ||||||
|       "integrity": "sha512-38IxvKB6NAne3g/+MyXMs2Cda/Sz+CEpmm+KLGEM8hx/CvnSRuw51i8ukfwB/B/sESdeTGet1NH1Wj7I0YXswg==", |       "integrity": "sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@typescript-eslint/types": "6.9.1", |         "@typescript-eslint/types": "6.10.0", | ||||||
|         "@typescript-eslint/visitor-keys": "6.9.1" |         "@typescript-eslint/visitor-keys": "6.10.0" | ||||||
|       }, |       }, | ||||||
|       "engines": { |       "engines": { | ||||||
|         "node": "^16.0.0 || >=18.0.0" |         "node": "^16.0.0 || >=18.0.0" | ||||||
| @@ -1159,9 +1158,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { |     "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { | ||||||
|       "version": "6.9.1", |       "version": "6.10.0", | ||||||
|       "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.1.tgz", |       "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.10.0.tgz", | ||||||
|       "integrity": "sha512-BUGslGOb14zUHOUmDB2FfT6SI1CcZEJYfF3qFwBeUrU6srJfzANonwRYHDpLBuzbq3HaoF2XL2hcr01c8f8OaQ==", |       "integrity": "sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "engines": { |       "engines": { | ||||||
|         "node": "^16.0.0 || >=18.0.0" |         "node": "^16.0.0 || >=18.0.0" | ||||||
| @@ -1172,13 +1171,13 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { |     "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { | ||||||
|       "version": "6.9.1", |       "version": "6.10.0", | ||||||
|       "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.1.tgz", |       "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.10.0.tgz", | ||||||
|       "integrity": "sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw==", |       "integrity": "sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@typescript-eslint/types": "6.9.1", |         "@typescript-eslint/types": "6.10.0", | ||||||
|         "@typescript-eslint/visitor-keys": "6.9.1", |         "@typescript-eslint/visitor-keys": "6.10.0", | ||||||
|         "debug": "^4.3.4", |         "debug": "^4.3.4", | ||||||
|         "globby": "^11.1.0", |         "globby": "^11.1.0", | ||||||
|         "is-glob": "^4.0.3", |         "is-glob": "^4.0.3", | ||||||
| @@ -1199,12 +1198,12 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { |     "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { | ||||||
|       "version": "6.9.1", |       "version": "6.10.0", | ||||||
|       "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.1.tgz", |       "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.10.0.tgz", | ||||||
|       "integrity": "sha512-MUaPUe/QRLEffARsmNfmpghuQkW436DvESW+h+M52w0coICHRfD6Np9/K6PdACwnrq1HmuLl+cSPZaJmeVPkSw==", |       "integrity": "sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@typescript-eslint/types": "6.9.1", |         "@typescript-eslint/types": "6.10.0", | ||||||
|         "eslint-visitor-keys": "^3.4.1" |         "eslint-visitor-keys": "^3.4.1" | ||||||
|       }, |       }, | ||||||
|       "engines": { |       "engines": { | ||||||
| @@ -3253,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": { |     "node_modules/which": { | ||||||
|       "version": "2.0.2", |       "version": "2.0.2", | ||||||
|       "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", |       "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", | ||||||
|   | |||||||
| @@ -48,7 +48,6 @@ | |||||||
|     "eslint-plugin-react-hooks": "^4.6.0", |     "eslint-plugin-react-hooks": "^4.6.0", | ||||||
|     "eslint-plugin-react-refresh": "^0.4.3", |     "eslint-plugin-react-refresh": "^0.4.3", | ||||||
|     "typescript": "^5.2.2", |     "typescript": "^5.2.2", | ||||||
|     "vite": "^4.5.0", |     "vite": "^4.5.0" | ||||||
|     "vite-plugin-singlefile": "^0.13.5" |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								public/images/viewer_example_background.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/images/viewer_example_background.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 2.1 MiB | 
							
								
								
									
										25
									
								
								src/App.tsx
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								src/App.tsx
									
									
									
									
									
								
							| @@ -1,21 +1,11 @@ | |||||||
| import "@styles/App.scss"; | import "@styles/App.scss"; | ||||||
|  |  | ||||||
| import { Route, Routes, useSearchParams } from "react-router-dom"; | import { Navigate, Route, Routes, useSearchParams } from "react-router-dom"; | ||||||
| import useTSRemoteApp, { IClient } from "react-ts5-remote-app-api"; |  | ||||||
| import Viewer from "./Viewer"; | import Viewer from "./Viewer"; | ||||||
|  | import Generator from "./Generator"; | ||||||
|  |  | ||||||
| export default function App() { | export default function App() { | ||||||
|   const [searchParams] = useSearchParams(); |   const [searchParams] = useSearchParams(); | ||||||
|   const { clients, activeConnectionId, currentChannel } = useTSRemoteApp({ |  | ||||||
|     remoteAppPort: parseInt(searchParams.get("remoteAppPort") ?? "5899"), |  | ||||||
|     auth: { |  | ||||||
|       identifier: "de.tealfire.obs", |  | ||||||
|       version: "2.0.0", |  | ||||||
|       name: "TS5 OBS Overlay", |  | ||||||
|       description: "A OBS overlay for TS5 by DerTyp7", |  | ||||||
|     }, |  | ||||||
|     logging: true, |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <Routes> |     <Routes> | ||||||
| @@ -23,20 +13,15 @@ export default function App() { | |||||||
|         path="/" |         path="/" | ||||||
|         element={ |         element={ | ||||||
|           <Viewer |           <Viewer | ||||||
|  |             remoteAppPort={parseInt(searchParams.get("remoteAppPort") ?? "5899")} | ||||||
|             showChannelName={searchParams.get("showChannelName") === "true"} |             showChannelName={searchParams.get("showChannelName") === "true"} | ||||||
|             hideNonTalking={searchParams.get("hideNonTalking") === "true"} |             hideNonTalking={searchParams.get("hideNonTalking") === "true"} | ||||||
|             clientLimit={searchParams.get("clientLimit") ? parseInt(searchParams.get("clientLimit") ?? "0") : 0} |             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[] |  | ||||||
|             } |  | ||||||
|             channel={currentChannel} |  | ||||||
|           /> |           /> | ||||||
|         } |         } | ||||||
|       /> |       /> | ||||||
|  |       <Route path="/generate" element={<Generator />} /> | ||||||
|  |       <Route path="*" element={<Navigate to="/generate" replace />} /> | ||||||
|     </Routes> |     </Routes> | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										122
									
								
								src/Generator.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/Generator.tsx
									
									
									
									
									
										Normal 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> | ||||||
|  |   ); | ||||||
|  | } | ||||||
| @@ -1,27 +1,42 @@ | |||||||
| import "@styles/Viewer.scss"; | 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({ | export default function Viewer({ | ||||||
|   clients, |   remoteAppPort = 5899, | ||||||
|   channel, |  | ||||||
|   showChannelName = false, |   showChannelName = false, | ||||||
|   hideNonTalking = false, |   hideNonTalking = false, | ||||||
|   clientLimit = 0, |   clientLimit = 0, | ||||||
| }: { | }: { | ||||||
|   clients: IClient[] | undefined; |   remoteAppPort?: number; | ||||||
|   channel: IChannel | undefined; |  | ||||||
|   showChannelName?: boolean; |   showChannelName?: boolean; | ||||||
|   hideNonTalking?: boolean; |   hideNonTalking?: boolean; | ||||||
|   clientLimit?: number; |   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 ( |   return ( | ||||||
|     <div className="viewer"> |     <div className="viewer"> | ||||||
|       {showChannelName ? ( |       {showChannelName ? ( | ||||||
|         <div className="channelNameContainer"> |         <div className="channelNameContainer"> | ||||||
|           <h3>{channel?.properties.name}</h3> |           <h1>{currentChannel?.properties.name}</h1> | ||||||
|         </div> |         </div> | ||||||
|       ) : null} |       ) : null} | ||||||
|       {clients?.map((client, i) => { |       {currentClients?.map((client, i) => { | ||||||
|         //* Client limit |         //* Client limit | ||||||
|         if (clientLimit != 0 && i >= clientLimit) { |         if (clientLimit != 0 && i >= clientLimit) { | ||||||
|           return null; |           return null; | ||||||
| @@ -29,10 +44,7 @@ export default function Viewer({ | |||||||
|  |  | ||||||
|         if (client) { |         if (client) { | ||||||
|           //* Non-talking client |           //* Non-talking client | ||||||
|           if ( |           if (hideNonTalking && (client.properties.inputMuted || client.properties.outputMuted || client.talkStatus == 0)) { | ||||||
|             hideNonTalking && |  | ||||||
|             (client.properties.inputMuted || client.properties.outputMuted || client.talkStatus == 0) |  | ||||||
|           ) { |  | ||||||
|             return null; |             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" |                       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" |                       fill="#d8d8d8" | ||||||
|                     /> |                     /> | ||||||
|                     <rect |                     <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" /> | ||||||
|                       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> |                   </g> | ||||||
|                 </svg> |                 </svg> | ||||||
|               ) : client.properties.outputMuted ? ( |               ) : client.properties.outputMuted ? ( | ||||||
|   | |||||||
							
								
								
									
										259
									
								
								src/styles/Generator.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										259
									
								
								src/styles/Generator.scss
									
									
									
									
									
										Normal 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; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -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 { | .viewer { | ||||||
|   display: flex; |   display: flex; | ||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
|   gap: 0 0; |   padding: 0.5rem; | ||||||
|   padding: 1rem; |  | ||||||
|  |   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 { |   .channelNameContainer { | ||||||
|     display: flex; |     display: flex; | ||||||
|     align-items: center; |     align-items: center; | ||||||
|     margin-bottom: 20px; |     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 { |   .client { | ||||||
| @@ -26,23 +32,16 @@ | |||||||
|     align-items: center; |     align-items: center; | ||||||
|     margin: 0.5rem 0; |     margin: 0.5rem 0; | ||||||
|  |  | ||||||
|  |     // icon styles | ||||||
|     svg { |     svg { | ||||||
|       width: 2.8rem; |       width: 2.1rem; | ||||||
|       height: 2.8rem; |       aspect-ratio: 1/1; | ||||||
|       margin-right: 0.5rem; |       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 |     // client name styles | ||||||
|       white-space: nowrap; |     p { | ||||||
|       overflow: hidden; |       font-size: 1.4rem; | ||||||
|       text-overflow: ellipsis; |  | ||||||
|       max-width: 20ch; |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,101 @@ | |||||||
|  | // Reset styles for all elements | ||||||
| * { | * { | ||||||
|   font-family: Arial, Helvetica, sans-serif; |   font-family: Arial, Helvetica, sans-serif; | ||||||
|   font-weight: bold; |   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; | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| import { defineConfig } from 'vite' | import { defineConfig } from 'vite' | ||||||
| import react from '@vitejs/plugin-react-swc' | import react from '@vitejs/plugin-react-swc' | ||||||
| import { viteSingleFile } from "vite-plugin-singlefile" |  | ||||||
| import path from 'path'; | import path from 'path'; | ||||||
|  |  | ||||||
| // https://vitejs.dev/config/ | // https://vitejs.dev/config/ | ||||||
| @@ -21,5 +20,5 @@ export default defineConfig({ | |||||||
|     outDir: 'dist', |     outDir: 'dist', | ||||||
|     emptyOutDir: true, |     emptyOutDir: true, | ||||||
|   }, |   }, | ||||||
|   plugins: [react(), viteSingleFile({ useRecommendedBuildConfig: false })], |   plugins: [react()], | ||||||
| }) | }) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Janis
					Janis