diff --git a/package-lock.json b/package-lock.json index 3b3b6ca..aec242b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2272,6 +2272,10 @@ "dev": true, "license": "MIT" }, + "node_modules/create-your-own-ui": { + "resolved": "samples/create-your-own-ui", + "link": true + }, "node_modules/cross-spawn": { "version": "7.0.3", "dev": true, @@ -4426,6 +4430,10 @@ "version": "2.1.2", "license": "MIT" }, + "node_modules/multiple-stage-ui": { + "resolved": "samples/multiple-stage", + "link": true + }, "node_modules/mz": { "version": "2.7.0", "license": "MIT", @@ -6572,11 +6580,127 @@ "node": ">=6" } }, + "samples/create-your-own-ui": { + "version": "0.0.0", + "dependencies": { + "@dytesdk/react-ui-kit": "^1.66.3", + "@dytesdk/react-web-core": "^1.36.5", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@dytesdk/ui-kit": "^1.66.3", + "@dytesdk/web-core": "^1.32.1", + "@types/react": "^18.0.24", + "@types/react-dom": "^18.0.8", + "@vitejs/plugin-react": "^2.2.0", + "autoprefixer": "^10.4.16", + "postcss": "^8.4.30", + "prettier": "^3.0.3", + "tailwindcss": "^3.3.3", + "typescript": "^4.6.4", + "vite": "^3.2.3" + } + }, + "samples/create-your-own-ui/node_modules/@dyteinternals/mediasoup-client": { + "version": "3.6.89", + "resolved": "https://registry.npmjs.org/@dyteinternals/mediasoup-client/-/mediasoup-client-3.6.89.tgz", + "integrity": "sha512-T9tKCr3u9W2k7zqUTR9TeJalHm9SAzTRoDXGku/oj1apHUcjdALRKd8VhY03DrtoLQKjT8/L8cwV4Q0J3ViJUw==", + "dependencies": { + "@dyteinternals/awaitqueue": "^3.0.1", + "@types/debug": "^4.1.7", + "bowser": "^2.11.0", + "debug": "^4.3.4", + "events": "^3.3.0", + "h264-profile-level-id": "^1.0.1", + "queue-microtask": "^1.2.3", + "sdp-transform": "^2.14.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mediasoup" + } + }, + "samples/create-your-own-ui/node_modules/@dytesdk/react-ui-kit": { + "version": "1.70.2", + "resolved": "https://registry.npmjs.org/@dytesdk/react-ui-kit/-/react-ui-kit-1.70.2.tgz", + "integrity": "sha512-+d4KPX5FkZg3UmxG1pDz/5sL7GigNpDA93QTniMeA2l1kq7tt84VXv6aIncXXzffvbfJhgjoLz0eawS2+6ERhg==", + "dependencies": { + "@dytesdk/ui-kit": "1.70.2" + }, + "peerDependencies": { + "react": ">=16.8.6", + "react-dom": ">=16.8.6" + } + }, + "samples/create-your-own-ui/node_modules/@dytesdk/react-web-core": { + "version": "1.36.14", + "resolved": "https://registry.npmjs.org/@dytesdk/react-web-core/-/react-web-core-1.36.14.tgz", + "integrity": "sha512-gojpefq2HwMwHkbi+8wIBD7z7EPNKLJBfCJLB37xfJv/GH7cK4CUhrI7XkDXYvmouG9SIBqsUznc1nLV0eVwKQ==", + "dependencies": { + "@dytesdk/web-core": "1.34.7" + }, + "peerDependencies": { + "react": ">=16.8.6" + } + }, + "samples/create-your-own-ui/node_modules/@dytesdk/react-web-core/node_modules/@dytesdk/web-core": { + "version": "1.34.7", + "resolved": "https://registry.npmjs.org/@dytesdk/web-core/-/web-core-1.34.7.tgz", + "integrity": "sha512-loPTuUzldTi4A7OIcUNVoBRXJzTLSw5tB/1xAp3RDJHleriZXM+o8RI2TIdbOb0KiIoyIy0A/ShOdADqmWhG8Q==", + "dependencies": { + "@dyteinternals/mediasoup-client": "3.6.89", + "@protobuf-ts/runtime": "^2.7.0", + "bowser": "^2.11.0", + "sdp-transform": "^2.14.1", + "socket.io-client": "4.6.2", + "uuid": "^8.3.2", + "worker-timers": "7.0.60" + } + }, + "samples/create-your-own-ui/node_modules/@dytesdk/ui-kit": { + "version": "1.70.2", + "resolved": "https://registry.npmjs.org/@dytesdk/ui-kit/-/ui-kit-1.70.2.tgz", + "integrity": "sha512-xli4AEgL++3Ar6usrslBIYc2+W+fX+DKOOKTqArynHoO1BPGTjYZBhZvrl2jZYs0xGhCOEGTAsW0x27WiStarQ==" + }, + "samples/create-your-own-ui/node_modules/@dytesdk/web-core": { + "version": "1.34.8", + "resolved": "https://registry.npmjs.org/@dytesdk/web-core/-/web-core-1.34.8.tgz", + "integrity": "sha512-QPQAxLU36WCHp016PXFcPOcmeD5pm2P9Ul9nIQYlS7oItSO/QeoCNvpNp/k9BN4ld2rQ+6OqW//DgYDgwK/e9Q==", + "dev": true, + "dependencies": { + "@dyteinternals/mediasoup-client": "3.6.89", + "@protobuf-ts/runtime": "^2.7.0", + "bowser": "^2.11.0", + "sdp-transform": "^2.14.1", + "socket.io-client": "4.6.2", + "uuid": "^8.3.2", + "worker-timers": "7.0.60" + } + }, + "samples/create-your-own-ui/node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "samples/default-meeting-ui": { "version": "0.0.0", "dependencies": { - "@dytesdk/react-ui-kit": "^1.54.0", - "@dytesdk/react-web-core": "^1.33.4", + "@dytesdk/react-ui-kit": "^2.0.0", + "@dytesdk/react-web-core": "^2.0.3", "react": "^18.2.0", "react-dom": "^18.2.0" }, @@ -6588,6 +6712,46 @@ "vite": "^3.2.3" } }, + "samples/default-meeting-ui/node_modules/@dytesdk/react-ui-kit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@dytesdk/react-ui-kit/-/react-ui-kit-2.0.0.tgz", + "integrity": "sha512-AISbmVotVSvtFs2w10PlInhPObLKoJsPCGGlmJuxtuRoZyoCTVUkCirohwCTzNwRIAE++PO4O3g0RfmD+pdPEg==", + "dependencies": { + "@dytesdk/ui-kit": "2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.6", + "react-dom": ">=16.8.6" + } + }, + "samples/default-meeting-ui/node_modules/@dytesdk/react-web-core": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dytesdk/react-web-core/-/react-web-core-2.0.3.tgz", + "integrity": "sha512-8gYglD1/iB9EnwDqjZYgwBt5/X5uFS4nqGd8O1Crwp7DKRrmtulZ8KZ5g2de74TYit+iLYlWs0b1HeEj2ZzsKg==", + "dependencies": { + "@dytesdk/web-core": "2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.6" + } + }, + "samples/default-meeting-ui/node_modules/@dytesdk/ui-kit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@dytesdk/ui-kit/-/ui-kit-2.0.0.tgz", + "integrity": "sha512-qOFu09m1wrj6RsWwRjPktDqnuk12Re/dFj4E5HSy9sOvkTYHcv8oVoggvwj35RLoNzgkpHhZN7IBW4zC7T0RSg==" + }, + "samples/default-meeting-ui/node_modules/@dytesdk/web-core": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dytesdk/web-core/-/web-core-2.0.3.tgz", + "integrity": "sha512-w2/HTw45tJkHXDqF1Qhw8yI5B5pjzz7DgQqDUqMBOQ6zvZgXJeJUMqe9pc+o+A/DkBoI3/aABtG2HTSY0BRnDQ==", + "dependencies": { + "@protobuf-ts/runtime": "^2.7.0", + "bowser": "^2.11.0", + "sdp-transform": "^2.14.1", + "uuid": "^8.3.2", + "worker-timers": "7.0.60" + } + }, "samples/facetime": { "version": "0.0.0", "dependencies": { @@ -6778,6 +6942,23 @@ } } }, + "samples/multiple-stage": { + "name": "multiple-stage-ui", + "version": "0.0.0", + "dependencies": { + "@dytesdk/react-ui-kit": "^1.54.0", + "@dytesdk/react-web-core": "^1.33.4", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.24", + "@types/react-dom": "^18.0.8", + "@vitejs/plugin-react": "^2.2.0", + "typescript": "^4.6.4", + "vite": "^3.2.3" + } + }, "samples/simple-group-call": { "version": "0.0.0", "dependencies": { diff --git a/samples/active-speaker-ui/src/components/Controlbar.tsx b/samples/active-speaker-ui/src/components/Controlbar.tsx index 2a97d1d..a0db106 100644 --- a/samples/active-speaker-ui/src/components/Controlbar.tsx +++ b/samples/active-speaker-ui/src/components/Controlbar.tsx @@ -5,33 +5,44 @@ import { DyteStageToggle, DyteMicToggle, DyteCameraToggle, - DyteLeaveButton, DyteParticipantsToggle, DytePollsToggle, DyteChatToggle, DytePluginsToggle, - defaultIconPack, - IconPack, + DyteControlbarButton, } from '@dytesdk/react-ui-kit'; -import { useDyteMeeting } from '@dytesdk/react-web-core'; - -const HAND_RAISE_ICON = - ''; - -const iconPack: IconPack = { - ...defaultIconPack, - join_stage: HAND_RAISE_ICON, -}; +import { useDyteMeeting, useDyteSelector } from '@dytesdk/react-web-core'; +import HOST_PRESET, { iconPack, saveWhiteboard, WHITEBOARD_ID } from '../lib/const'; export default function Controlbar() { const { meeting } = useDyteMeeting(); const size = useMeetingStore((s) => s.size); const isMobile = useMeetingStore((s) => s.isMobile); const states = useMeetingStore((s) => s.states); + const isDarkMode = useMeetingStore((s) => s.darkMode); + const toggleDarkMode = useMeetingStore((s) => s.toggleDarkMode); + const setStates = useMeetingStore((s) => s.setStates); + const whiteboardPlugin = useDyteSelector(m => m.plugins.active.get(WHITEBOARD_ID)) const buttonSize = size === 'lg' ? 'lg' : 'sm'; - const isHost = meeting.self.presetName === 'webinar_presenter'; + const isHost = meeting.self.presetName === HOST_PRESET; + + const leaveMeeting = async () => { + if (whiteboardPlugin?.active) { + await saveWhiteboard(whiteboardPlugin); + } + setStates({ + activeLeaveConfirmation: true, + }) + } + + const LeaveButton = () => { + return ( + + ) + } + if (isMobile) { return ( @@ -73,9 +84,9 @@ export default function Controlbar() { - + @@ -91,17 +102,27 @@ export default function Controlbar() { size={buttonSize} states={states} /> + { + isHost + && { + toggleDarkMode(!isDarkMode); + }} + /> + }
- +
diff --git a/samples/active-speaker-ui/src/components/MainArea.tsx b/samples/active-speaker-ui/src/components/MainArea.tsx index c64631f..58e1055 100644 --- a/samples/active-speaker-ui/src/components/MainArea.tsx +++ b/samples/active-speaker-ui/src/components/MainArea.tsx @@ -3,10 +3,6 @@ import ActiveSpeaker from './ActiveSpeaker'; import ScreenShareView from './ScreenShareView'; import { DyteIcon, - defaultIconPack, - DyteScreenshareView, - DyteNameTag, - DyteAudioVisualizer, DytePluginMain, DyteSimpleGrid, DyteButton, @@ -15,6 +11,7 @@ import { useDyteMeeting, useDyteSelector } from '@dytesdk/react-web-core'; import type { DyteParticipant, DytePlugin, DyteSelf } from '@dytesdk/web-core'; import clsx from 'clsx'; import { useState, useEffect, useRef } from 'react'; +import HOST_PRESET, { iconPack, saveWhiteboard, WHITEBOARD_ID } from '../lib/const'; type ActiveTab = | { type: 'plugin'; plugin: DytePlugin } @@ -36,10 +33,12 @@ function ActiveSpeakerView({ const showTabBar = screenshares.length + plugins.length > 1; const size = useMeetingStore((s) => s.size); - const isImmersiveMode = useMeetingStore((s) => s.isImmersiveMode); + const whiteboardPlugin = useDyteSelector(m => m.plugins.active.get(WHITEBOARD_ID)) const [states, setStates] = useMeetingStore((s) => [s.states, s.setStates]); const activeTab = useDyteSelector((m) => m.meta.selfActiveTab); + const isHost = meeting.self.presetName === HOST_PRESET; + const isDarkMode = useMeetingStore((s) => s.darkMode); useEffect(() => { if (activeTab) { @@ -129,16 +128,58 @@ function ActiveSpeakerView({ setSelectedTab(tab); }; + const setConfig = () => { + const hostId = + isHost + ? meeting.self.id + : meeting.participants.joined.toArray().find(x => x.presetName === HOST_PRESET)?.id; + + whiteboardPlugin?.sendData({ + eventName: 'config', + data: { + eventName: 'config', + follow: hostId, + role: isHost ? 'editor' : 'viewer', + infiniteCanvas: false, + darkMode: isDarkMode, + exportMode: 'pdf', + } + }) + } + + // NOTE(ishita1805): Set whiteboard config on launch + useEffect(() => { + if (!whiteboardPlugin) return; + setConfig(); + whiteboardPlugin.on('ready', setConfig); + return () => { + whiteboardPlugin.off('ready', setConfig); + } + }, [whiteboardPlugin]) + + // NOTE(ishita1805): Update whiteboard config when dark mode is toggled + useEffect(() => { + setConfig(); + }, [isDarkMode]) + + // NOTE(ishita1805): Save whiteboard before closing + const closePlugin = async (plugin: DytePlugin) => { + if (plugin.id === whiteboardPlugin?.id) { + await saveWhiteboard(whiteboardPlugin); + } + plugin.deactivate(); + } + + return (
{showTabBar && ( -
- {/* TODO: handle overflow */} +
{screenshares.map((participant) => ( ))} @@ -157,7 +198,7 @@ function ActiveSpeakerView({ {plugins.map((plugin) => ( ))} @@ -182,7 +223,7 @@ function ActiveSpeakerView({ {plugins.map((plugin) => (
+
+ {plugin.name} +
+ closePlugin(plugin)} /> +
+
{states.activeSidebar && ( @@ -202,7 +253,7 @@ function ActiveSpeakerView({ setStates({ activeSidebar: false, sidebar: undefined }); }} > - + )}
@@ -243,7 +294,7 @@ export default function MainArea() { const setActiveMode = useMeetingStore((s) => s.setIsActiveSpeakerMode); const isMobile = useMeetingStore((s) => s.isMobile); - const isHost = meeting.self.presetName === 'webinar_presenter'; + const isHost = meeting.self.presetName === HOST_PRESET; const isEmptyStage = participants.length === 0; useEffect(() => { diff --git a/samples/active-speaker-ui/src/components/Meeting.tsx b/samples/active-speaker-ui/src/components/Meeting.tsx index 23c2d99..d054d23 100644 --- a/samples/active-speaker-ui/src/components/Meeting.tsx +++ b/samples/active-speaker-ui/src/components/Meeting.tsx @@ -8,6 +8,7 @@ import { DyteNotifications, DyteParticipantsAudio, DyteSetupScreen, + DyteSpinner, DyteWaitingScreen, } from '@dytesdk/react-ui-kit'; import { useDyteMeeting, useDyteSelector } from '@dytesdk/react-web-core'; @@ -100,8 +101,6 @@ export default function Meeting() { case 'joined': children = ; break; - case 'disconnected': - // TODO: show disconnected screen default: children = ; break; diff --git a/samples/active-speaker-ui/src/components/ScreenShareView.tsx b/samples/active-speaker-ui/src/components/ScreenShareView.tsx index 4513194..6a8d50e 100644 --- a/samples/active-speaker-ui/src/components/ScreenShareView.tsx +++ b/samples/active-speaker-ui/src/components/ScreenShareView.tsx @@ -5,10 +5,10 @@ import { DyteIcon, DyteNameTag, DyteScreenshareView, - defaultIconPack, } from '@dytesdk/react-ui-kit'; import { useDyteMeeting } from '@dytesdk/react-web-core'; import type { DyteParticipant, DyteSelf } from '@dytesdk/web-core'; +import { iconPack } from '../lib/const'; export default function ScreenShareView({ participant, @@ -48,7 +48,7 @@ export default function ScreenShareView({ onClick={onMaximise} className="absolute bottom-3 right-3" > - + )} diff --git a/samples/active-speaker-ui/src/components/Sidebar.tsx b/samples/active-speaker-ui/src/components/Sidebar.tsx index 1590f48..c71409a 100644 --- a/samples/active-speaker-ui/src/components/Sidebar.tsx +++ b/samples/active-speaker-ui/src/components/Sidebar.tsx @@ -1,3 +1,4 @@ +import HOST_PRESET from '../lib/const'; import { useMeetingStore } from '../lib/meeting-store'; import ActiveSpeaker from './ActiveSpeaker'; import Grid from './Grid'; @@ -36,7 +37,7 @@ export default function Sidebar() { break; } - const isHost = meeting.self.presetName === 'webinar_presenter'; + const isHost = meeting.self.presetName === HOST_PRESET; if (isHost && !sidebar) { return null; diff --git a/samples/active-speaker-ui/src/index.css b/samples/active-speaker-ui/src/index.css index 2550bd1..ba636b5 100644 --- a/samples/active-speaker-ui/src/index.css +++ b/samples/active-speaker-ui/src/index.css @@ -17,4 +17,24 @@ * { border-color: theme(colors.zinc.700); } + + /* width */ + ::-webkit-scrollbar { + width: 6px; + height: 6px; + border-radius: 10px; + } + + /* Track */ + ::-webkit-scrollbar-track { + border-radius: 10px; + background: theme(colors.zinc.1000); + } + + /* Handle */ + ::-webkit-scrollbar-thumb { + border-radius: 10px; + background: theme(colors.zinc.900); + + } } diff --git a/samples/active-speaker-ui/src/lib/const.ts b/samples/active-speaker-ui/src/lib/const.ts new file mode 100644 index 0000000..2bfaa5e --- /dev/null +++ b/samples/active-speaker-ui/src/lib/const.ts @@ -0,0 +1,25 @@ +import { defaultIconPack } from "@dytesdk/react-ui-kit"; +import type { DytePlugin } from '@dytesdk/web-core'; +const HOST_PRESET = 'group_call_host'; +export const WHITEBOARD_ID = "ae79b269-24ca-4f8a-8112-f96084c8c19a"; +export const iconPack = { + ...defaultIconPack, + 'light': '', + 'dark': '', + join_stage: '', +} +export const saveWhiteboard = async (whiteboardPlugin: DytePlugin | undefined) => { + if (!whiteboardPlugin) return; + await new Promise((resolve) => { + whiteboardPlugin.removeAllListeners('board-saved' as any); + whiteboardPlugin.on('board-saved' as any, (data: any) => { + console.log('board saved:', data); + resolve(true); + }) + whiteboardPlugin.sendData({ + eventName: 'save-board', + data: { eventName: 'save-board' } + }) + }); + } +export default HOST_PRESET; diff --git a/samples/active-speaker-ui/src/lib/meeting-store.ts b/samples/active-speaker-ui/src/lib/meeting-store.ts index 9df0307..7948bae 100644 --- a/samples/active-speaker-ui/src/lib/meeting-store.ts +++ b/samples/active-speaker-ui/src/lib/meeting-store.ts @@ -10,6 +10,8 @@ interface Dimensions { export interface MeetingStore { isImmersiveMode: boolean; + darkMode: boolean; + toggleDarkMode: (val: boolean) => void; setIsImmersiveMode: (val: boolean) => void; toggleImmersiveMode: () => void; @@ -30,6 +32,9 @@ export const useMeetingStore = create((set, get) => ({ isActiveSpeakerMode: false, setIsActiveSpeakerMode: (isActiveSpeakerMode) => set({ isActiveSpeakerMode }), + darkMode: true, + toggleDarkMode: (darkMode) => set({ darkMode }), + isImmersiveMode: false, setIsImmersiveMode: (isImmersiveMode) => set({ isImmersiveMode }), toggleImmersiveMode: () => set({ isImmersiveMode: !get().isImmersiveMode }),