diff --git a/.env b/.env
index ee3b749..80733c3 100644
--- a/.env
+++ b/.env
@@ -1,2 +1,2 @@
-REACT_APP_basePath="/ketr.ketran"
+VITE_basePath="/ketr.ketran"
NODE_CONFIG_ENV='production'
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index fc0b8c2..6e98000 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -34,13 +34,13 @@ RUN npm run build
# prepare client deps in the image so lint/type-check can run inside the container
# copy client sources and install dependencies during the image build (container-first)
COPY client /client
-WORKDIR /client
+WORKDIR /client
ENV PUBLIC_URL="/ketr.ketran"
-ENV REACT_APP_API_BASE=""
+ENV VITE_API_BASE=""
# prefer npm ci when lockfile present, otherwise fall back to npm install
-RUN rm -f package-lock.json
-RUN npm install --legacy-peer-deps --no-audit --no-fund
-RUN npm run build
+#RUN rm -f package-lock.json
+#RUN npm install --legacy-peer-deps --no-audit --no-fund
+#RUN npm run build
# return to server working dir for default run
WORKDIR /server
diff --git a/client/package.json b/client/package.json
index 72a0be2..e322111 100644
--- a/client/package.json
+++ b/client/package.json
@@ -2,6 +2,7 @@
"name": "peddlers-client",
"version": "1.0.0",
"private": true,
+ "type": "module",
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
@@ -23,34 +24,28 @@
"react-movable": "^3.0.4",
"react-moveable": "^0.31.1",
"react-router-dom": "^6.14.1",
- "react-scripts": "5.0.1",
+ "react-use-websocket": "^4.8.1",
"socket.io-client": "^4.4.1",
"web-vitals": "^2.1.4"
},
"scripts": {
- "start": "HTTPS=true react-scripts start",
- "build": "export $(cat ../.env | xargs) && react-scripts build",
- "test": "export $(cat ../.env | xargs) && react-scripts test",
- "eject": "export $(cat ../.env | xargs) && react-scripts eject",
+ "start": "vite --host",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "test": "vitest",
"type-check": "tsc --project tsconfig.json --noEmit",
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}' --max-warnings=0",
"lint:fix": "eslint 'src/**/*.{js,jsx,ts,tsx}' --fix"
},
- "lint-staged": {
- "src/**/*.{js,jsx,ts,tsx}": [
- "npm run lint:fix"
- ]
- },
- "husky": {
- "hooks": {
- "pre-commit": "lint-staged"
- }
- },
- "eslintConfig": {
- "extends": [
- "react-app",
- "react-app/jest"
- ]
+ "devDependencies": {
+ "@types/react": "^18.2.22",
+ "@types/react-dom": "^18.2.7",
+ "@vitejs/plugin-react-swc": "^3.5.0",
+ "@vitejs/plugin-react": "^4.0.0",
+ "typescript": "^5.4.3",
+ "vite": "^5.0.0",
+ "vite-tsconfig-paths": "^4.2.1",
+ "vitest": "^1.0.0"
},
"browserslist": {
"production": [
@@ -63,10 +58,5 @@
"last 1 firefox version",
"last 1 safari version"
]
- },
- "devDependencies": {
- "@types/react": "^18.2.22",
- "@types/react-dom": "^18.2.7",
- "typescript": "^5.3.3"
}
-}
+}
\ No newline at end of file
diff --git a/client/public/index.html b/client/public/index.html
deleted file mode 100755
index b4a0a4d..0000000
--- a/client/public/index.html
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Peddlers of Ketran
-
-
-
-
-
-
-
diff --git a/client/src/Common.ts b/client/src/Common.ts
index af9aa19..12a985d 100644
--- a/client/src/Common.ts
+++ b/client/src/Common.ts
@@ -15,7 +15,7 @@ function debounce void>(fn: T, ms: number): T {
// the client running in a container to talk to the server by docker service
// name (e.g. http://peddlers-of-ketran:8930) while still working when run on
// the host where PUBLIC_URL may be appropriate.
-const envApiBase = process.env.REACT_APP_API_BASE;
+const envApiBase = process.env.VITE_API_BASE;
const publicBase = process.env.PUBLIC_URL || '';
const base = envApiBase || publicBase;
diff --git a/client/src/MediaControl.css b/client/src/MediaControl.css
index 8ee364a..145e438 100644
--- a/client/src/MediaControl.css
+++ b/client/src/MediaControl.css
@@ -1,11 +1,31 @@
+.Video {
+ opacity: 0;
+ transition: opacity 0.8s ease-in-out;
+}
+
+.Video.fade-in {
+ opacity: 1;
+}
+
+.MediaControlContainer {
+ position: relative; /* CRITICAL: This creates the positioning context */
+ display: inline-block;
+ width: max-content; /* Ensure container sizes to content */
+ height: max-content;
+}
+
.MediaControlSpacer {
display: flex;
+ position: relative;
+ padding: 0;
+ margin: 0;
width: 5rem;
min-width: 5rem;
height: 3.75rem;
min-height: 3.75rem;
background-color: #444;
border-radius: 0.25rem;
+ border: 2px dashed #666; /* Visual indicator for drop zone */
}
.MediaControlSpacer.Medium {
@@ -15,24 +35,26 @@
min-height: 8.625em;
}
-
.MediaControl {
display: flex;
- position: fixed;
- flex-direction: row;
- justify-content: flex-end;
- align-items: center;
+ position: absolute; /* Out of flow */
+ top: 0; /* Start at top of container */
+ left: 0; /* Start at left of container */
width: 5rem;
height: 3.75rem;
min-width: 5rem;
min-height: 3.75rem;
- z-index: 50000;
+ z-index: 1200;
+ border-radius: 0.25rem;
}
.MediaControl .Video {
- position: relative;
+ display: flex;
width: 100%;
height: 100%;
+ position: relative;
+ top: 0;
+ left: 0;
background-color: #444;
border-radius: 0.25rem;
border: 1px solid black;
@@ -45,37 +67,25 @@
min-height: 8.625em;
}
-.MediaControl > div {
- display: flex;
- position: absolute;
- top: 0;
- left: 0;
- display: flex;
- flex-direction: column;
- align-items: center;
- margin-right: 0.25rem;
-}
-
.MediaControl .Controls {
display: flex;
position: absolute;
- left: 0.5em;
- bottom: 0.5em;
- justify-content: flex-end;
+ gap: 0;
+ left: 0;
+ bottom: 0;
+ flex-direction: column;
z-index: 1;
+ align-items: flex-start;
+ justify-content: center
}
.MediaControl.Small .Controls {
- left: 0;
- bottom: unset;
justify-content: center;
}
.MediaControl .Controls > div {
- display: flex;
border-radius: 0.25em;
cursor: pointer;
- padding: 0.25em;
}
.MediaControl .Controls > div:hover {
@@ -89,4 +99,4 @@
.moveable-control-box .moveable-direction {
border: none !important;
-}
\ No newline at end of file
+}
diff --git a/client/src/MediaControl.tsx b/client/src/MediaControl.tsx
index 9394d36..8d077c0 100644
--- a/client/src/MediaControl.tsx
+++ b/client/src/MediaControl.tsx
@@ -1,454 +1,1780 @@
-import React, { useState, useEffect, useRef, useCallback, useContext } from "react";
-import Moveable from "react-moveable";
-
+import React, { useState, useEffect, useRef, useCallback } from "react";
import "./MediaControl.css";
-
import VolumeOff from "@mui/icons-material/VolumeOff";
import VolumeUp from "@mui/icons-material/VolumeUp";
import MicOff from "@mui/icons-material/MicOff";
import Mic from "@mui/icons-material/Mic";
import VideocamOff from "@mui/icons-material/VideocamOff";
import Videocam from "@mui/icons-material/Videocam";
+import Box from "@mui/material/Box";
+import IconButton from "@mui/material/IconButton";
+import useWebSocket, { ReadyState } from "react-use-websocket";
+import { Session } from "./GlobalContext";
+import WebRTCStatus from "./WebRTCStatus";
+import Moveable from "react-moveable";
+import { flushSync } from "react-dom";
-import { GlobalContext } from "./GlobalContext";
const debug = true;
+// When true, do not send host candidates to the signaling server. Keeps TURN relays preferred.
+const FILTER_HOST_CANDIDATES = false; // Temporarily disabled to test direct connections
-/* eslint-disable */
+/* ---------- Synthetic Tracks Helpers ---------- */
-interface VideoProps {
- srcObject: MediaStream | undefined;
- local?: boolean;
- [key: string]: any;
+// Helper to hash a string to a color
+function nameToColor(name: string): string {
+ // Simple hash function (djb2)
+ let hash = 5381;
+ for (let i = 0; i < name.length; i++) {
+ hash = (hash << 5) + hash + name.charCodeAt(i);
+ }
+ // Generate HSL color from hash
+ const hue = Math.abs(hash) % 360;
+ const sat = 60 + (Math.abs(hash) % 30); // 60-89%
+ const light = 45 + (Math.abs(hash) % 30); // 45-74%
+ return `hsl(${hue},${sat}%,${light}%)`;
}
-/* Proxy object so we can pass in srcObject to