diff --git a/webstack/libs/applications/src/lib/apps/TwilioScreenshare/TwilioScreenshare.tsx b/webstack/libs/applications/src/lib/apps/TwilioScreenshare/TwilioScreenshare.tsx index d4e511283..6cb7c57cd 100644 --- a/webstack/libs/applications/src/lib/apps/TwilioScreenshare/TwilioScreenshare.tsx +++ b/webstack/libs/applications/src/lib/apps/TwilioScreenshare/TwilioScreenshare.tsx @@ -7,7 +7,7 @@ */ // Chakra and React imports -import { useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { Box, Button, @@ -28,6 +28,7 @@ import { ModalFooter, ModalBody, useToast, + ToastId, } from '@chakra-ui/react'; // Twilio Imports @@ -55,12 +56,14 @@ const screenShareTimeLimit = 60 * 60 * 2000; // 2 hours function AppComponent(props: App): JSX.Element { const s = props.data.state as AppState; - const toast = useToast({ id: `toast-${props._id}` }); - // Current User const { user, accessId } = useUser(); const yours = user?._id === props._createdBy && accessId === s.accessId; + // Other apps + const apps = useAppStore((state) => state.apps); + const otherScreenshares = apps.filter((el) => el.data.type === 'Screenshare' && el._createdBy === user?._id && el._id !== props._id); + // Twilio Store const room = useTwilioStore((state) => state.room); const tracks = useTwilioStore((state) => state.tracks); @@ -77,7 +80,7 @@ function AppComponent(props: App): JSX.Element { // UI const red = useHexColor('red'); const teal = useHexColor('teal'); - const fitApps = useUIStore((state) => state.fitApps); + const fitAppsById = useUIStore((state) => state.fitAppsById); const boardLocked = useUIStore((state) => state.boardLocked); // Electron media sources @@ -95,6 +98,16 @@ function AppComponent(props: App): JSX.Element { // The user that is sharing only sets the selTrack const [selTrack, setSelTrack] = useState(null); + // Toasts + const toast = useToast(); + const toastIdRef = useRef(); + + function closeToast() { + if (toastIdRef.current) { + toast.close(toastIdRef.current); + } + } + useEffect(() => { // If the user changes the dimensions of the shared window, resize the app const updateDimensions = (track: LocalVideoTrack) => { @@ -155,7 +168,29 @@ function AppComponent(props: App): JSX.Element { } }, [room]); + function checkForScreenShare(): boolean { + if (otherScreenshares.length > 0) { + closeToast(); + // Show a notification + toastIdRef.current = toast({ + title: 'You can only have one screenshare at a time.', + status: 'error', + duration: 2000, + onCloseComplete: () => { + deleteApp(props._id); + }, + isClosable: false, + }); + return true; + } + return false; + } + const shareScreen = async () => { + // Lets check if user already has a screen share going + const alreadySharing = checkForScreenShare(); + if (alreadySharing) return; + stopStream(); if (room && videoRef.current) { // Load electron and the IPCRender @@ -193,8 +228,10 @@ function AppComponent(props: App): JSX.Element { await updateState(props._id, { videoId }); setSelTrack(screenTrack); + // Close Toast + closeToast(); // Show a notification - toast({ + toastIdRef.current = toast({ title: 'Screenshare started', status: 'success', duration: 3000, @@ -233,22 +270,24 @@ function AppComponent(props: App): JSX.Element { } }, [stopStreamId]); - const goToScreenshare = () => { + const goToScreenshare = useCallback(() => { if (!boardLocked) { // Close the popups - toast.closeAll(); + closeToast(); // Zoom in - fitApps([props]); + fitAppsById([props._id]); } - }; + }, [props, boardLocked]); useEffect(() => { if (yours) return; tracks.forEach((track) => { if (track.name === s.videoId && videoRef.current && track.kind === 'video') { track.attach(videoRef.current); + // Close other toasts by this app + closeToast(); // Show a notification - toast({ + toastIdRef.current = toast({ title: `${props.data.title} started a screenshare`, description: ( @@ -258,7 +297,7 @@ function AppComponent(props: App): JSX.Element { ), status: 'info', - duration: null, + duration: 5000, isClosable: true, }); } @@ -270,8 +309,10 @@ function AppComponent(props: App): JSX.Element { if (yours) update(props._id, { title: `${user.data.name}` }); return () => { if (yours) { + // Close other toasts by this app + closeToast(); // Show a notification - toast({ + toastIdRef.current = toast({ title: 'Your screenshare has ended', status: 'success', duration: 3000, @@ -279,7 +320,7 @@ function AppComponent(props: App): JSX.Element { }); stopStream(); } - toast.close(`toast-${props._id}`); + closeToast(); }; }, []); @@ -321,8 +362,10 @@ function AppComponent(props: App): JSX.Element { // if (isElectron()) window.electron.send('hide-main-window', {}); // } + // Close other toasts by this app + closeToast(); // Show a notification - toast({ + toastIdRef.current = toast({ title: 'Screenshare started', status: 'success', duration: 3000, diff --git a/webstack/libs/frontend/src/lib/stores/ui.ts b/webstack/libs/frontend/src/lib/stores/ui.ts index e93659f90..c2a4c2e12 100644 --- a/webstack/libs/frontend/src/lib/stores/ui.ts +++ b/webstack/libs/frontend/src/lib/stores/ui.ts @@ -112,6 +112,7 @@ interface UIState { zoomOut: () => void; zoomInDelta: (d: number, cursor?: { x: number; y: number }) => void; zoomOutDelta: (d: number, cursor?: { x: number; y: number }) => void; + fitAppsById: (appId: string[]) => void; fitApps: (apps: App[]) => void; fitAllApps: () => void; fitArea: (x: number, y: number, w: number, h: number) => void; @@ -154,6 +155,13 @@ export const useUIStore = create()((set, get) => ({ viewport: { position: { x: 0, y: 0 }, size: { width: 0, height: 0 } }, setViewport: (position: Omit, size: Omit) => set((state) => ({ ...state, viewport: { position, size } })), boardLocked: false, + fitAppsById: (appIds: string[]) => { + const apps = useAppStore.getState().apps; + const filteredApps = apps.filter((app) => appIds.includes(app._id)); + if (filteredApps.length > 0) { + get().fitApps(filteredApps); + } + }, fitApps: (apps: App[]) => { if (apps.length <= 0) { return;