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