Skip to content

Commit

Permalink
Fce 646/prettier fishjam chat (#153)
Browse files Browse the repository at this point in the history
## Description

Add prettier version of fishjam chat.

## Motivation and Context

We'd like to have a version that is slick enough to show it to the
world.

## Types of changes

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to
      not work as expected)
  • Loading branch information
czerwiukk authored Oct 24, 2024
1 parent 1b65f51 commit 2417cae
Show file tree
Hide file tree
Showing 40 changed files with 2,310 additions and 464 deletions.
31 changes: 3 additions & 28 deletions examples/react-client/fishjam-chat/README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,5 @@
# React + TypeScript + Vite
# Fishjam Chat

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
This is an example app demonstrating how to build a simple conferencing react app using Fishjam.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:

- Configure the top-level `parserOptions` property like this:

```js
export default {
// other rules...
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
project: ["./tsconfig.json", "./tsconfig.node.json", "./tsconfig.app.json"],
tsconfigRootDir: __dirname,
},
};
```

- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
Install dependencies with `yarn`, run with `yarn dev`.
20 changes: 20 additions & 0 deletions examples/react-client/fishjam-chat/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.cjs",
"css": "src/index.css",
"baseColor": "stone",
"cssVariables": false,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}
20 changes: 19 additions & 1 deletion examples/react-client/fishjam-chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,34 @@
"dependencies": {
"@fishjam-cloud/react-client": "workspace:*",
"@mediapipe/tasks-vision": "^0.10.17",
"@radix-ui/react-accordion": "^1.2.1",
"@radix-ui/react-aspect-ratio": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.1",
"axios": "^1.7.7",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"lucide-react": "^0.453.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@types/node": "^22.7.5",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react-swc": "^3.7.1",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.1",
"postcss": "^8.4.47",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.13",
"typescript": "^5.6.2",
"vite": "^5.4.8"
Expand Down
99 changes: 11 additions & 88 deletions examples/react-client/fishjam-chat/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,95 +1,18 @@
import { DevicePicker } from "./components/DevicePicker";
import { RoomConnector } from "./components/RoomConnector";
import { Tile } from "./components/Tile.tsx";
import { useInitializeDevices, usePeers } from "@fishjam-cloud/react-client";
import { Fragment, useEffect } from "react";
import AudioPlayer from "./components/AudioPlayer.tsx";
import { JoinRoomCard } from "./components/JoinRoomCard";
import { RoomView } from "./components/RoomView";
import { useStatus } from "@fishjam-cloud/react-client";

function App() {
const { localPeer, peers } = usePeers();

const { initializeDevices } = useInitializeDevices();

useEffect(() => {
initializeDevices();
}, [initializeDevices]);
const peerStatus = useStatus();
const isConnected = peerStatus === "connected";

return (
<main className="flex h-screen w-screen">
<section className="h-full w-1/3 space-y-8 overflow-auto bg-zinc-200 p-4">
<h1 className="text-xl">FishjamChat</h1>

<RoomConnector />

<DevicePicker />
</section>

<div className="h-full w-full p-4">
<section className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
{localPeer && (
<>
<Tile
id={localPeer.id}
name="You"
videoTrack={localPeer.cameraTrack}
audioTrack={localPeer.microphoneTrack}
/>
{localPeer.screenShareVideoTrack && (
<Tile
id="Your screen share"
name="Your screen share"
videoTrack={localPeer.screenShareVideoTrack}
audioTrack={localPeer.screenShareAudioTrack}
/>
)}
</>
)}

{peers.map(
({
id,
cameraTrack,
microphoneTrack,
screenShareVideoTrack,
screenShareAudioTrack,
metadata,
}) => {
const label = metadata?.peer?.displayName ?? id;

return (
<Fragment key={id}>
<Tile
id={id}
name={label}
videoTrack={cameraTrack}
audioTrack={microphoneTrack}
/>

{screenShareVideoTrack && (
<Tile
id={id}
name={`Screen share: ${label}`}
videoTrack={screenShareVideoTrack}
audioTrack={screenShareAudioTrack}
/>
)}
</Fragment>
);
},
)}

{peers.map(({ id, microphoneTrack, screenShareAudioTrack }) => (
<Fragment key={id}>
{microphoneTrack?.stream && (
<AudioPlayer stream={microphoneTrack.stream} />
)}
{screenShareAudioTrack?.stream && (
<AudioPlayer stream={screenShareAudioTrack.stream} />
)}
</Fragment>
))}
</section>
</div>
<main className="flex h-screen w-screen bg-stone-100">
{isConnected ? (
<RoomView />
) : (
<JoinRoomCard className="m-auto w-full max-w-md" />
)}
</main>
);
}
Expand Down
12 changes: 12 additions & 0 deletions examples/react-client/fishjam-chat/src/components/AudioTracks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import AudioPlayer from "./AudioPlayer";
import { Track } from "@fishjam-cloud/react-client";

type Props = {
audioTracks: Track[] | undefined;
};

export function AudioTracks({ audioTracks }: Props) {
return audioTracks?.map((audioTrack) => (
<AudioPlayer stream={audioTrack.stream} key={audioTrack.trackId} />
));
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,25 @@
import { useEffect, useRef } from "react";

type Props = {
track?: MediaStreamTrack | null;
stream?: MediaStream | null;
};

export const AudioVisualizer = ({ track }: Props) => {
export const AudioVisualizer = ({ stream }: Props) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const idRef = useRef<number | null>(null);
const clearCanvasRef = useRef<() => void>(() => {});

useEffect(() => {
if (!track) {
clearCanvasRef.current();
return;
}
if (!stream) return;
if (!canvasRef.current) return;

const audioContext = new AudioContext();

const stream = new MediaStream([track]);
if (stream.getAudioTracks().length === 0) return;
const mediaStreamSource = audioContext.createMediaStreamSource(stream);
const analyser = audioContext.createAnalyser();

const canvas = canvasRef.current;
const canvasContext: CanvasRenderingContext2D = canvas.getContext("2d")!;

clearCanvasRef.current = () =>
canvasContext.clearRect(0, 0, canvas.width, canvas.height);

mediaStreamSource.connect(analyser);

analyser.fftSize = 64;
Expand All @@ -44,7 +36,7 @@ export const AudioVisualizer = ({ track }: Props) => {
return;
}

clearCanvasRef.current();
canvasContext.clearRect(0, 0, canvas.width, canvas.height);

const barWidth = canvas.width / bufferLength;

Expand Down Expand Up @@ -73,7 +65,7 @@ export const AudioVisualizer = ({ track }: Props) => {
cancelAnimationFrame(idRef.current);
}
};
}, [track]);
}, [stream]);

return <canvas ref={canvasRef} width={200} height={50} />;
};
Expand Down
51 changes: 41 additions & 10 deletions examples/react-client/fishjam-chat/src/components/BlurToggle.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import { TrackMiddleware, useCamera } from "@fishjam-cloud/react-client";
import { useCallback } from "react";
import {
createContext,
FC,
PropsWithChildren,
useCallback,
useContext,
} from "react";
import { BlurProcessor } from "../utils/blur/BlurProcessor";
import { Button } from "./Button";
import { Button, type ButtonProps } from "./ui/button";
import { Stars } from "lucide-react";
import { cn } from "@/lib/utils";

export function BlurToggle() {
const BlurContext = createContext<{
toggleBlur: () => Promise<void>;
isBlurEnabled: boolean;
} | null>(null);

export const BlurProvider: FC<PropsWithChildren> = ({ children }) => {
const camera = useCamera();

const blurMiddleware: TrackMiddleware = useCallback(
Expand All @@ -19,16 +32,34 @@ export function BlurToggle() {
[],
);

const isMiddlewareSet = camera.currentMiddleware === blurMiddleware;
const isBlurEnabled = camera.currentMiddleware === blurMiddleware;

const toggleBlur = () =>
camera.setTrackMiddleware(isBlurEnabled ? null : blurMiddleware);

return (
<BlurContext.Provider value={{ toggleBlur, isBlurEnabled }}>
{children}
</BlurContext.Provider>
);
};

const toggleBlurMiddleware = () =>
camera.setTrackMiddleware(isMiddlewareSet ? null : blurMiddleware);
export const BlurToggleButton: FC<
ButtonProps & React.RefAttributes<HTMLButtonElement>
> = (props) => {
const blurCtx = useContext(BlurContext);

const title = isMiddlewareSet ? "Clear blur" : "Blur camera";
if (!blurCtx) throw Error("BlurToggle must be used within BlurProvider");

return (
<Button className="w-full" onClick={toggleBlurMiddleware}>
{title}
<Button
{...props}
variant={blurCtx.isBlurEnabled ? "default" : "outline"}
onClick={blurCtx.toggleBlur}
className={cn("space-x-2", props.className)}
>
<Stars size={20} strokeWidth={"1.5px"} />
<span>{blurCtx.isBlurEnabled ? "Disable" : "Enable"} Blur</span>
</Button>
);
}
};
10 changes: 0 additions & 10 deletions examples/react-client/fishjam-chat/src/components/Button.tsx

This file was deleted.

Loading

0 comments on commit 2417cae

Please sign in to comment.