(feat): improve mobile
Some checks failed
/ release (push) Has been cancelled
/ build-web (push) Has been cancelled
/ build-desktop (linux) (push) Has been cancelled
/ build-mobile (push) Has been cancelled

(feat): add camera sharing
(fix): vite tauri connection
(fix): methanium/ui theme not applied to call popout
This commit is contained in:
Alois 2026-08-01 01:25:41 +02:00
commit 62aaa7c01d
Signed by: alois
SSH key fingerprint: SHA256:GBzT2DXvAuGV9XIV5W3WrzVpjU54FThmxHXdbz95J24
31 changed files with 1915 additions and 859 deletions

View file

@ -0,0 +1,171 @@
import { useEffect, useState } from "react";
import {
Button,
Popover,
PopoverContent,
PopoverTrigger,
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@methanium/ui";
import { MonitorDot, ScreenShare } from "lucide-react";
import { toast } from "@tensamin/shared/log";
import {
startScreenShare,
stopCameraShare,
stopScreenShare,
useCall,
} from "../../store";
import { getMediaShareAdapter, type MediaShareKind } from "../../mediaShare";
import MediaShareDialog from "../mediaShareDialog";
export default function MediaShareButton({
className,
iconSize,
tooltip,
defaultPortal,
}: {
className?: string;
iconSize?: number;
tooltip?: string;
defaultPortal?: boolean;
}) {
const isScreensharing = useCall((state) => state.screenShareEnabled);
const cameraEnabled = useCall((state) => state.cameraEnabled);
const screenRef = useCall((state) => state.screenRef);
const [portalContainer, setPortalContainer] = useState<HTMLElement>();
const [dialogKind, setDialogKind] = useState<MediaShareKind | null>(null);
const [menuOpen, setMenuOpen] = useState(false);
useEffect(() => {
if (!defaultPortal) setPortalContainer(screenRef?.current ?? undefined);
}, [screenRef, defaultPortal]);
async function beginScreenShare() {
try {
const capabilities = await getMediaShareAdapter().getCapabilities();
if (capabilities.screenPicker === "sources") {
setDialogKind("screen");
} else {
await startScreenShare({ includeAudio: true });
}
} catch (error) {
console.error("Failed to start screen sharing", error);
toast(
"error",
error instanceof Error
? error.message
: "Failed to start screen sharing.",
);
}
}
async function stop(kind: MediaShareKind) {
try {
await (kind === "screen" ? stopScreenShare() : stopCameraShare());
} catch (error) {
console.error(`Failed to stop ${kind} sharing`, error);
toast("error", `Failed to stop ${kind} sharing.`);
}
}
const trigger = (
<Button
variant={isScreensharing || cameraEnabled ? "subtleDefault" : "default"}
className="w-full! h-full!"
>
{isScreensharing || cameraEnabled ? (
<MonitorDot
style={{ scale: (iconSize ? iconSize + 100 : 100) + "%" }}
/>
) : (
<ScreenShare
style={{ scale: (iconSize ? iconSize + 100 : 100) + "%" }}
/>
)}
</Button>
);
return (
<>
<Tooltip>
<Popover onOpenChange={setMenuOpen} open={menuOpen}>
<PopoverTrigger
render={({ onClick }) =>
tooltip ? (
<TooltipTrigger
render={({ ref }) => (
<span
ref={ref as React.Ref<HTMLSpanElement>}
onClick={onClick}
className={className}
>
{trigger}
</span>
)}
/>
) : (
<span className={className} onClick={onClick}>
{trigger}
</span>
)
}
/>
<PopoverContent
className="flex w-48 flex-col gap-2"
portalProps={{
container: defaultPortal ? undefined : portalContainer,
}}
>
<Button
disabled={isScreensharing}
onClick={() => {
setMenuOpen(false);
void beginScreenShare();
}}
>
Share screen
</Button>
<Button
disabled={cameraEnabled}
onClick={() => {
setMenuOpen(false);
setDialogKind("camera");
}}
>
Share camera
</Button>
{isScreensharing ? (
<Button variant="destructive" onClick={() => void stop("screen")}>
Stop screen sharing
</Button>
) : null}
{cameraEnabled ? (
<Button variant="destructive" onClick={() => void stop("camera")}>
Stop camera
</Button>
) : null}
</PopoverContent>
</Popover>
{tooltip ? (
<TooltipContent
portalProps={{
container: defaultPortal ? undefined : portalContainer,
}}
>
{tooltip}
</TooltipContent>
) : null}
</Tooltip>
{dialogKind ? (
<MediaShareDialog
kind={dialogKind}
open
onOpenChange={(open) => !open && setDialogKind(null)}
portalContainer={portalContainer}
/>
) : null}
</>
);
}

View file

@ -1,175 +0,0 @@
import { useEffect, useState } from "react";
import {
Button,
Popover,
PopoverContent,
PopoverTrigger,
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@methanium/ui";
import { MonitorDot, ScreenShare } from "lucide-react";
import { toast } from "@tensamin/shared/log";
import { isTauri } from "@tauri-apps/api/core";
import { setScreenShareEnabled, useCall } from "../../store";
import ScreenShareDialog from "../screenshareDialog";
export default function ScreenshareButton({
className,
iconSize,
tooltip,
defaultPortal,
}: {
className?: string;
iconSize?: number;
tooltip?: string;
defaultPortal?: boolean;
}) {
const isScreensharing = useCall((state) => state.screenShareEnabled);
const screenRef = useCall((state) => state.screenRef);
const [portalContainer, setPortalContainer] = useState<HTMLElement>();
const [dialogOpen, setDialogOpen] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
useEffect(() => {
if (defaultPortal) return;
setPortalContainer(screenRef?.current ?? undefined);
}, [screenRef, defaultPortal]);
async function startWebShare() {
try {
await setScreenShareEnabled(true, {
audio: true,
systemAudio: "include",
surfaceSwitching: "include",
video: true,
});
} catch (error) {
console.error("Failed to start web screen share", error);
toast(
"error",
error instanceof Error
? error.message
: "Failed to start screen sharing.",
);
}
}
async function stopShare() {
try {
await setScreenShareEnabled(false);
} catch (error) {
console.error("Failed to stop screen share", error);
toast("error", "Failed to stop screen sharing.");
}
}
return (
<>
<Tooltip>
<Popover onOpenChange={setMenuOpen} open={menuOpen}>
<PopoverTrigger
render={({ onClick: popoverClick }) =>
tooltip ? (
<TooltipTrigger
render={({ ref, onClick: tooltipClick }) => (
<Button
ref={ref as React.Ref<HTMLButtonElement>}
onClick={(event) => {
popoverClick?.(event);
tooltipClick?.(event);
}}
variant={isScreensharing ? "subtleDefault" : "default"}
className={className}
>
{isScreensharing ? (
<MonitorDot
style={{
scale: (iconSize ? iconSize + 100 : 100) + "%",
}}
/>
) : (
<ScreenShare
style={{
scale: (iconSize ? iconSize + 100 : 100) + "%",
}}
/>
)}
</Button>
)}
/>
) : (
<Button
variant={isScreensharing ? "subtleDefault" : "default"}
className={className}
onClick={popoverClick}
>
{isScreensharing ? (
<MonitorDot
style={{ scale: (iconSize ? iconSize + 100 : 100) + "%" }}
/>
) : (
<ScreenShare
style={{ scale: (iconSize ? iconSize + 100 : 100) + "%" }}
/>
)}
</Button>
)
}
/>
<PopoverContent
className="flex w-40 flex-col gap-2"
portalProps={{
container: defaultPortal ? undefined : portalContainer,
}}
>
<Button
disabled={isScreensharing}
onClick={() => {
setMenuOpen(false);
if (isTauri()) {
setDialogOpen(true);
return;
}
void startWebShare();
}}
>
Start screenshare
</Button>
<Button
variant="destructive"
disabled={!isScreensharing}
onClick={() => {
setMenuOpen(false);
void stopShare();
}}
>
Stop screenshare
</Button>
<Button variant="outline">Change quality</Button>
</PopoverContent>
</Popover>
{tooltip && (
<TooltipContent
portalProps={{
container: defaultPortal ? undefined : portalContainer,
}}
>
{tooltip}
</TooltipContent>
)}
</Tooltip>
{isTauri() && (
<ScreenShareDialog
open={dialogOpen}
onOpenChange={setDialogOpen}
isScreensharing={isScreensharing}
portalContainer={portalContainer}
/>
)}
</>
);
}