Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"dependencies": {
"@fishjam-cloud/react-client": "^0.17.0",
"@mediapipe/tasks-vision": "^0.10.22-rc.20250304",
"@radix-ui/react-alert-dialog": "^1.1.14",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@swmansion/smelter": "^0.2.1",
Expand Down
33 changes: 33 additions & 0 deletions src/components/BrowserSupportAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ScreenShareOff } from "lucide-react";
import {
AlertDialog,
AlertDialogAction,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "./ui/alert-dialog";

export default function BrowserSupportAlert() {
return (
<AlertDialog defaultOpen={true}>
<AlertDialogContent className="font-june">
<AlertDialogHeader>
<AlertDialogTitle asChild>
<div className="inline-flex gap-2">
<ScreenShareOff /> Unsupported browser
</div>
</AlertDialogTitle>
<AlertDialogDescription>
Gesture recognition won't work on your browser, but is coming soon.
You will still see gestures made by other people.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogAction>OK</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}
4 changes: 2 additions & 2 deletions src/components/PeerTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Loader2 } from "lucide-react";
import { FC, useEffect, useRef } from "react";

export type PeerTileProps = {
stream: MediaStream | null;
stream?: MediaStream | null;
name: string;
showHelp?: boolean;
};
Expand All @@ -12,7 +12,7 @@ export const PeerTile: FC<PeerTileProps> = ({ stream, name }) => {

useEffect(() => {
if (!videoRef.current) return;
videoRef.current.srcObject = stream;
videoRef.current.srcObject = stream ?? null;
}, [stream]);

return (
Expand Down
5 changes: 5 additions & 0 deletions src/components/SmelterProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { FC, PropsWithChildren, useEffect, useState } from "react";
import { SmelterContext } from "../contexts/SmelterContext";
import Smelter from "@swmansion/smelter-web-wasm";
import BrowserSupportAlert from "./BrowserSupportAlert";
import { browserSupported } from "@/lib/utils";

export const SmelterProvider: FC<PropsWithChildren> = ({ children }) => {
const [smelter, setSmelter] = useState<Smelter | null>(null);

useEffect(() => {
if (!browserSupported) return;

const smelter = new Smelter();

let cancel = false;
Expand All @@ -28,6 +32,7 @@ export const SmelterProvider: FC<PropsWithChildren> = ({ children }) => {

return (
<SmelterContext.Provider value={smelter}>
{!browserSupported && <BrowserSupportAlert />}
{children}
</SmelterContext.Provider>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/TitleBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const TitleBar: FC = () => {
</div>
<div className="flex items-center gap-4">
<p className="text-center text-sm sm:text-base lg:text-lg">
Share the with someone to invite them to join!
Share the URL with someone to invite them to join!
</p>
<CopyButton value={window.location.href} onCopy={onCopy}>
<Share2 size={24} />
Expand Down
156 changes: 156 additions & 0 deletions src/components/ui/alert-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import * as React from "react";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";

import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";

function AlertDialog({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
}

function AlertDialogTrigger({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
return (
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
);
}

function AlertDialogPortal({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
return (
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
);
}

const AlertDialogOverlay = React.forwardRef<
HTMLDivElement,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => {
return (
<AlertDialogPrimitive.Overlay
data-slot="alert-dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className,
)}
ref={ref}
{...props}
/>
);
});

function AlertDialogContent({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
return (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
data-slot="alert-dialog-content"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border border-stone-200 bg-white p-6 shadow-lg duration-200 sm:max-w-lg dark:border-stone-800 dark:bg-stone-950",
className,
)}
{...props}
/>
</AlertDialogPortal>
);
}

function AlertDialogHeader({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
);
}

function AlertDialogFooter({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className,
)}
{...props}
/>
);
}

function AlertDialogTitle({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
return (
<AlertDialogPrimitive.Title
data-slot="alert-dialog-title"
className={cn("text-lg font-semibold", className)}
{...props}
/>
);
}

function AlertDialogDescription({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
return (
<AlertDialogPrimitive.Description
data-slot="alert-dialog-description"
className={cn("text-sm text-stone-500 dark:text-stone-400", className)}
{...props}
/>
);
}

function AlertDialogAction({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
return (
<AlertDialogPrimitive.Action
className={cn(buttonVariants(), className)}
{...props}
/>
);
}

function AlertDialogCancel({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
return (
<AlertDialogPrimitive.Cancel
className={cn(buttonVariants({ variant: "outline" }), className)}
{...props}
/>
);
}

export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
};
4 changes: 2 additions & 2 deletions src/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const buttonVariants = cva(
"inline-flex gap-2 items-center justify-center whitespace-nowrap rounded-full text-xs sm:text-sm md:text-base font-june transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
"inline-flex gap-2 items-center justify-center whitespace-nowrap rounded-full font-june transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
Expand All @@ -15,7 +15,7 @@ const buttonVariants = cva(
},
size: {
default: "px-6 h-12",
responsive: "px-2 h-8 sm:px-6 sm:h-12",
responsive: "px-2 h-8 sm:px-6 sm:h-12 text-xs sm:text-sm md:text-base ",
},
},
defaultVariants: {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

export const browserSupported = "MediaStreamTrackProcessor" in window;
4 changes: 2 additions & 2 deletions src/views/RoomView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ export default function RoomView() {
<section
className={`align-center grid w-full flex-1 grid-flow-row grid-cols-1 justify-center md:grid-cols-${cols} gap-4 lg:gap-8`}
>
<PeerTile name="You" stream={stream ?? null} showHelp />
<PeerTile name="You" stream={stream ?? cameraStream} showHelp />
{remotePeers.map((peer) => (
<PeerTile
name={peer.metadata?.peer?.name ?? peer.id}
key={peer.id}
stream={peer.customVideoTracks[0]?.stream}
stream={peer.customVideoTracks[0]?.stream ?? peer.cameraTrack?.stream}
/>
))}
</section>
Expand Down
Loading