Compare commits

..

28 Commits

Author SHA1 Message Date
Janis
ef73abe6a5 Update CNAME 2025-01-24 13:49:59 +01:00
Janis
247df0d9c5 Create CNAME 2025-01-24 13:46:06 +01:00
DerTyp7
fdda04a346 deploy: aaa770be2b 2025-01-24 12:44:49 +00:00
Janis
ee156b8e63 Create CNAME 2024-02-05 19:49:56 +01:00
DerTyp7
561ad3a056 deploy: d37c7326c3 2024-02-05 16:35:43 +00:00
Janis
2afb852e99 Create CNAME 2024-01-28 12:42:53 +01:00
DerTyp7
5b40e5df32 deploy: 43f3c4f4ac 2024-01-25 20:46:57 +00:00
Janis
6ad495429f Update CNAME 2024-01-25 00:39:04 +01:00
Janis
390ff3007e Update CNAME 2024-01-25 00:25:11 +01:00
Janis
5832d00a4d Update CNAME 2024-01-25 00:22:50 +01:00
Janis
2f02d0ccf2 Update CNAME 2024-01-25 00:22:13 +01:00
Janis
178ea1aaf0 Update CNAME 2024-01-25 00:21:35 +01:00
Janis
189dd73f12 Create CNAME 2024-01-25 00:20:24 +01:00
Janis
83837b61b2 Delete CNAME 2024-01-24 20:32:25 +01:00
Janis
299312663f Create CNAME 2024-01-24 20:26:50 +01:00
DerTyp7
333e5c1010 deploy: 34ffedc294 2023-11-12 22:13:41 +00:00
DerTyp7
7f159df428 deploy: 442e8f05e6 2023-11-12 18:58:04 +00:00
DerTyp7
2e6591fdfd deploy: f0761c40b7 2023-11-12 18:27:04 +00:00
DerTyp7
6bb27d0423 deploy: 4eb61d3b96 2023-11-12 18:20:38 +00:00
DerTyp7
8dd8b94490 deploy: 1a5b231d1e 2023-11-12 18:01:42 +00:00
DerTyp7
c43c6e3982 deploy: 232bca2bbc 2023-11-12 17:52:58 +00:00
DerTyp7
f8db280142 deploy: 0b74390d2e 2023-11-12 17:50:52 +00:00
DerTyp7
59ccbaaf0b deploy: c5009271c4 2023-11-12 17:44:28 +00:00
DerTyp7
6f7c467aef deploy: 8b23a8cca7 2023-11-12 17:42:08 +00:00
DerTyp7
469aba4d46 deploy: 1a5f0b8e6e 2023-11-12 17:36:26 +00:00
DerTyp7
32fa12ed2f deploy: b1da52ad1d 2023-11-12 17:27:32 +00:00
DerTyp7
b7d13e86e2 deploy: 59594125a5 2023-10-07 15:21:43 +00:00
DerTyp7
1eb9dbc3b7 deploy: 295153e702 2023-10-07 13:33:17 +00:00
28 changed files with 89 additions and 4380 deletions

View File

@@ -1,11 +0,0 @@
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"],
rules: {
"react-refresh/only-export-components": "warn",
"react-hooks/exhaustive-deps": "ignore",
},
};

View File

@@ -1,27 +0,0 @@
---
name: Bug report
about: Something isn't working for you
title: ''
labels: bug
assignees: ''
---
> Screenshots are welcome
## Describe the bug
A clear and concise description of what the bug is.
## To Reproduce
Example or explanation to reproduce the issue.
## Expected behavior
A clear and concise description of what you expected to happen.
**Do you use the local or online version?**
- [x] Online version with URL: `https://dertyp7.github.io/ts5-obs-overlay`
- [ ] The version named below locally downloaded on my device
**Your environment:**
- OS: [e.g. Windows 11]
- OBS version: [e.g. 29.1.3]
- Overlay version [e.g. v1.1.0]

View File

@@ -1,11 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "monthly"
target-branch: "dev"

View File

@@ -1,31 +0,0 @@
name: Deploy to GitHub Pages on Release
on:
release:
types:
- created
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '18'
- name: Install dependencies
run: npm install
- name: Build
run: npm run build
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3.9.3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist

24
.gitignore vendored
View File

@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

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

1
CNAME Normal file
View File

@@ -0,0 +1 @@
teamspeak-overlay.tealfire.de

View File

@@ -1,82 +0,0 @@
# TeamSpeak5-OBS-Overlay
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 [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)
- [Usage](#usage)
- [Quick instructions](#quick-instructions)
- [Detailed instructions](#detailed-instructions)
- [Common Issues](#common-issues)
- [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
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)
### Detailed instructions
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)
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)
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)
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)
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)
## Common Issues
### The overlay is empty, but i'm connected to a TeamSpeak5 server
**Fix 1**
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.
So we try currently use a workaround, where the active server tab is determined by looking on which server the your hardware input was unmuted the latest, since the non-active server tabs in TS5 usually mute the clients microphone.
However this workaround is not 100% accurate and can fail in some cases.
Possible fixes:
- Unmute and mute yourself in the active server tab (Just a normal unmute and mute, not the hardware mute)
- Reconnect to the TS5 server while the overlay is open
### OBS doesn't show the latest version of 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".
## 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.

60
assets/index-DXas0TfE.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

@@ -3,15 +3,14 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="An OBS overlay for TeamSpeak5, so your audience can see who is talking. https://github.com/DerTyp7/ts5-obs-overlay"
/>
<meta name="description" content="An OBS overlay for TeamSpeak5, so your audience can see who is talking. https://github.com/DerTyp7/ts5-obs-overlay" />
<meta name="author" content="DerTyp7" />
<link rel="icon" href="./logo.svg" type="image/x-icon" />
<title>TS5-OBS-Overlay</title>
<script type="module" crossorigin src="./assets/index-DXas0TfE.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-MKACXyL3.css">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

23
logo.svg Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="307.000000pt" height="339.000000pt" viewBox="0 0 307.000000 339.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,339.000000) scale(0.100000,-0.100000)"
fill="#008080" stroke="none">
<path d="M1297 3382 c-3 -4 -7 -61 -11 -127 -20 -341 -120 -616 -370 -1010
-158 -251 -306 -436 -331 -415 -7 6 -18 52 -24 103 -22 170 -77 377 -101 377
-5 0 -21 -38 -35 -86 -27 -89 -79 -213 -212 -504 -201 -438 -244 -657 -180
-901 52 -198 102 -290 232 -427 137 -145 345 -310 474 -376 l34 -18 -5 24 c-3
12 -18 63 -32 113 -64 213 -19 418 154 701 106 173 164 314 194 474 16 82 30
120 45 120 13 0 55 -59 94 -131 45 -83 81 -198 102 -329 9 -57 18 -106 20
-108 7 -7 122 231 144 298 34 104 49 202 63 403 6 103 15 186 20 185 4 -2 79
-72 167 -155 257 -247 392 -414 501 -625 97 -187 102 -205 107 -413 6 -223 -1
-265 -73 -418 -31 -65 -53 -121 -50 -124 7 -7 188 122 295 212 210 176 368
383 455 598 149 370 117 760 -98 1193 -110 220 -245 417 -434 633 l-39 45 -7
-183 c-5 -106 -14 -203 -22 -230 -14 -46 -80 -144 -99 -145 -14 -1 -59 92
-101 209 -121 342 -331 651 -579 854 -130 107 -287 203 -298 183z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

3303
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,53 +0,0 @@
{
"name": "ts5-obs-overlay",
"private": false,
"version": "2.0.3",
"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/#",
"repository": {
"type": "git",
"url": "https://github.com/DerTyp7/ts5-obs-overlay"
},
"keywords": [
"ts5",
"teamspeak",
"overlay",
"remote app",
"obs",
"typescript",
"react",
"vite"
],
"bugs": {
"url": "https://github.com/DerTyp7/ts5-obs-overlay/issues"
},
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@types/node": "^20.8.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"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.35",
"@types/react-dom": "^18.2.14",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.9.1",
"@vitejs/plugin-react-swc": "^3.0.0",
"eslint": "^8.53.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"typescript": "^5.2.2",
"vite": "^4.5.0"
}
}

View File

@@ -1,26 +0,0 @@
import "@styles/App.scss";
import { Navigate, Route, Routes, useSearchParams } from "react-router-dom";
import Viewer from "./Viewer";
import Generator from "./Generator";
export default function App() {
const [searchParams] = useSearchParams();
return (
<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}
/>
}
/>
<Route path="/generate" element={<Generator />} />
</Routes>
);
}

View File

@@ -1,128 +0,0 @@
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);
url.hash = "";
url.searchParams.set("remoteAppPort", remoteAppPort.toString());
url.searchParams.set("showChannelName", showChannelName.toString());
url.searchParams.set("hideNonTalking", hideNonTalking.toString());
url.searchParams.set("clientLimit", clientLimit.toString());
if (url.hostname === "dertyp7.github.io") {
url.pathname = url.pathname + "ts5-obs-overlay/";
}
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,151 +0,0 @@
import "@styles/Viewer.scss";
import useTSRemoteApp, { IClient } from "react-ts5-remote-app-api";
export default function Viewer({
remoteAppPort = 5899,
showChannelName = false,
hideNonTalking = false,
clientLimit = 0,
}: {
remoteAppPort?: number;
showChannelName?: boolean;
hideNonTalking?: boolean;
clientLimit?: number;
}) {
const { clients, activeConnectionId, currentChannel } = useTSRemoteApp({
remoteAppPort: remoteAppPort,
auth: {
identifier: "de.tealfire.obs",
version: "2.0.3",
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">
<h1>{currentChannel?.properties.name}</h1>
</div>
) : null}
{currentClients?.map((client, i) => {
//* Client limit
if (clientLimit != 0 && i >= clientLimit) {
return null;
}
if (client) {
//* Non-talking client
if (hideNonTalking && (client.properties.inputMuted || client.properties.outputMuted || client.talkStatus == 0)) {
return null;
}
//* Normal client
return (
<div className="client" key={`${client.id}-${client.channel?.connection.id}`}>
{client.properties.outputHardware == false ? (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<title>muted_hardware_output</title>
<g id="muted_hardware_output.svg">
<path
d="M116.62,39a4.78,4.78,0,0,1-1.59.3l-6.29,3.63a45.42,45.42,0,0,1-13.33,57.64,49.4,49.4,0,0,1-5.82,3.62c-1.06.57-2.2.92-3.19,1.49-1.7,1-2.77,2.13-2.77,4.18a4.57,4.57,0,0,0,4.54,4.54,5.33,5.33,0,0,0,1.84-.35,54.49,54.49,0,0,0,26.94-75c-.12,0-.22,0-.34,0M88.18,13.58a4.57,4.57,0,0,0-4.54,4.54c0,2.06,1.06,3.19,2.77,4.18,1,.57,2.13.92,3.19,1.49a49.4,49.4,0,0,1,5.82,3.62,45.68,45.68,0,0,1,8.19,7.78l7-4a2.63,2.63,0,0,1,1.11-.34A54.31,54.31,0,0,0,90,13.94a5.33,5.33,0,0,0-1.84-.35"
fill="#d8d8d8"
/>
<path
d="M59.46,71.4,32.77,86.81l19,19a4.51,4.51,0,0,0,3.19,1.35,4.57,4.57,0,0,0,4.54-4.54V71.4M54.92,20.88a4.51,4.51,0,0,0-3.19,1.35L28.12,45.85H9.54A4.57,4.57,0,0,0,5,50.38V77.62a4.57,4.57,0,0,0,4.54,4.54H22.26l37.2-21.48V25.42a4.57,4.57,0,0,0-4.54-4.54"
fill="#d8d8d8"
/>
<path
d="M85.11,56.6l-7.87,4.54A10,10,0,0,1,77.62,64c0,8.58-8.23,7.09-8.23,12.48A4.53,4.53,0,0,0,73.93,81a4,4,0,0,0,1.77-.35A18.13,18.13,0,0,0,86.69,64a18.34,18.34,0,0,0-1.59-7.4M73.93,47a4.52,4.52,0,0,0-4.54,4.54,3.92,3.92,0,0,0,1.08,2.8l8.76-5.06a16.14,16.14,0,0,0-3.52-1.93A4,4,0,0,0,73.93,47"
fill="#d8d8d8"
/>
<path
d="M100.87,47.49,93,52a27.15,27.15,0,0,1-8.36,33.87A36.79,36.79,0,0,1,79.25,89a4.54,4.54,0,0,0,1.84,8.72,5.24,5.24,0,0,0,1.77-.35,36.34,36.34,0,0,0,18-49.91M81,30.25A4.54,4.54,0,0,0,79.25,39a36.82,36.82,0,0,1,5.39,3.12,27,27,0,0,1,2.86,2.4l8.14-4.7A35.37,35.37,0,0,0,82.86,30.6,5.31,5.31,0,0,0,81,30.25"
fill="#d8d8d8"
/>
<path
d="M126.57,43.71,10.42,110.77a2.88,2.88,0,0,1-3.93-1.05L4.33,106A2.88,2.88,0,0,1,5.38,102L121.53,35A2.88,2.88,0,0,1,125.46,36l2.16,3.75A2.88,2.88,0,0,1,126.57,43.71Z"
fill="#c9070a"
/>
</g>
</svg>
) : client.properties.inputHardware == false ? (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<title>muted_hardware_input</title>
<g id="muted_hardware_input.svg">
<path
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" />
</g>
</svg>
) : client.properties.outputMuted ? (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<title>muted_output</title>
<g id="muted_output">
<path
d="M116.62,39a4.78,4.78,0,0,1-1.59.3l-6.29,3.63a45.42,45.42,0,0,1-13.33,57.64,49.4,49.4,0,0,1-5.82,3.62c-1.06.57-2.2.92-3.19,1.49-1.7,1-2.77,2.13-2.77,4.18a4.57,4.57,0,0,0,4.54,4.54,5.33,5.33,0,0,0,1.84-.35,54.49,54.49,0,0,0,26.94-75c-.12,0-.22,0-.34,0M88.18,13.58a4.57,4.57,0,0,0-4.54,4.54c0,2.06,1.06,3.19,2.77,4.18,1,.57,2.13.92,3.19,1.49a49.4,49.4,0,0,1,5.82,3.62,45.68,45.68,0,0,1,8.19,7.78l7-4a2.63,2.63,0,0,1,1.11-.34A54.31,54.31,0,0,0,90,13.94a5.33,5.33,0,0,0-1.84-.35"
fill="#c9070a"
/>
<path
d="M59.46,71.4,32.77,86.81l19,19a4.51,4.51,0,0,0,3.19,1.35,4.57,4.57,0,0,0,4.54-4.54V71.4M54.92,20.88a4.51,4.51,0,0,0-3.19,1.35L28.11,45.85H9.53A4.57,4.57,0,0,0,5,50.38V77.62a4.57,4.57,0,0,0,4.54,4.54H22.25l37.2-21.48V25.42a4.57,4.57,0,0,0-4.54-4.54"
fill="#c9070a"
/>
<path
d="M85.1,56.6l-7.87,4.54A10,10,0,0,1,77.61,64c0,8.58-8.23,7.09-8.23,12.48A4.53,4.53,0,0,0,73.92,81a4,4,0,0,0,1.77-.35A18.13,18.13,0,0,0,86.69,64a18.34,18.34,0,0,0-1.59-7.4M73.92,47a4.52,4.52,0,0,0-4.54,4.54,3.92,3.92,0,0,0,1.08,2.8l8.76-5.06a16.14,16.14,0,0,0-3.52-1.93A4,4,0,0,0,73.92,47"
fill="#c9070a"
/>
<path
d="M100.87,47.49,93,52a27.15,27.15,0,0,1-8.36,33.87A36.79,36.79,0,0,1,79.24,89a4.54,4.54,0,0,0,1.84,8.72,5.24,5.24,0,0,0,1.77-.35,36.34,36.34,0,0,0,18-49.91M81,30.25A4.54,4.54,0,0,0,79.24,39a36.82,36.82,0,0,1,5.39,3.12,27,27,0,0,1,2.86,2.4l8.14-4.7A35.37,35.37,0,0,0,82.86,30.6,5.31,5.31,0,0,0,81,30.25"
fill="#c9070a"
/>
<path
d="M126.57,43.71,10.42,110.77a2.88,2.88,0,0,1-3.93-1.05L4.33,106A2.88,2.88,0,0,1,5.38,102L121.53,35A2.88,2.88,0,0,1,125.46,36l2.16,3.75A2.88,2.88,0,0,1,126.57,43.71Z"
fill="#c9070a"
/>
</g>
</svg>
) : client.properties.inputMuted ? (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<title>muted_input</title>
<g id="muted_input.svg">
<path
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="#c9070a"
/>
</g>
</svg>
) : client.talkStatus == 1 ? (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<title>player_on_v2</title>
<g id="player_on_v2.svg">
<path d="M64,128a64,64,0,1,1,64-64A64,64,0,0,1,64,128Z" fill="#00b4df" />
</g>
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<title>player_off_v2</title>
<g id="player_off_v2.svg">
<path d="M64,128a64,64,0,1,1,64-64A64,64,0,0,1,64,128Z" fill="#3e4f5e" />
</g>
</svg>
)}
<p>{client.properties.nickname}</p>
</div>
);
} else {
return <div key={Math.random()}></div>;
}
})}
</div>
);
}

View File

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

View File

@@ -1,259 +0,0 @@
// 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,47 +0,0 @@
//* 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;
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;
}
.client {
display: flex;
flex-direction: row;
gap: 0 0;
align-items: center;
margin: 0.5rem 0;
// icon styles
svg {
width: 2.1rem;
aspect-ratio: 1/1;
margin-right: 0.5rem;
}
// client name styles
p {
font-size: 1.4rem;
}
}
}

View File

@@ -1,101 +0,0 @@
// Reset styles for all elements
* {
font-family: Arial, Helvetica, sans-serif;
font-weight: bold;
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

@@ -1,31 +0,0 @@
export default class Logger {
// Log message to the console
public static log(message: string, data: object | null = null): void {
console.log(`[Log] %c${message}`.trim(), "color: gray", data ?? "");
}
// Log warning to the console
public static warn(message: string, data: object | null = null): void {
console.warn(`%c${message}`.trim(), data ?? "");
}
// Log error to the console
public static error(message: string, data: object | null = null): void {
console.error(`%c${message}`.trim(), data ?? "");
}
// Log message received from the websocket to the console
public static wsReceived(data: object, message: string | undefined = undefined): void {
console.log(`%c[WS Recieved] ${message ?? ""}`.trim(), "color: #8258c7", data);
}
// Log message sent to the websocket to the console
public static wsSent(data: object, message: string | undefined = undefined): void {
console.log(`%c[WS Sent] ${message ?? ""}`.trim(), "color: #4eb570", data);
}
// Log message to the console with a timestamp
public static ts(message: string, data: object | null = null): void {
console.log(`%c[TS] ${message}`.trim(), "color: #2e6bc7", data ?? "");
}
}

1
src/vite-env.d.ts vendored
View File

@@ -1 +0,0 @@
/// <reference types="vite/client" />

View File

@@ -1,37 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@assets/*": ["src/assets/*"],
"@styles/*": ["src/styles/*"],
"@utils/*": ["src/utils/*"],
"@interfaces/*": ["src/interfaces/*"],
"@handlers/*": ["src/handlers/*"]
},
/* Bundler mode */
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true,
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -1,10 +0,0 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -1,24 +0,0 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import path from 'path';
// https://vitejs.dev/config/
export default defineConfig({
base: "./",
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@assets': path.resolve(__dirname, './src/assets'),
'@components': path.resolve(__dirname, './src/components'),
'@handlers': path.resolve(__dirname, './src/handlers'),
'@interfaces': path.resolve(__dirname, './src/interfaces'),
'@utils': path.resolve(__dirname, './src/utils'),
'@styles': path.resolve(__dirname, './src/styles'),
},
},
build: {
outDir: 'dist',
emptyOutDir: true,
},
plugins: [react()],
})