added RoomContainer abstraction
This commit is contained in:
parent
8461c3e028
commit
db91e7f84e
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import ExpiryTimer from '@dailyjs/shared/components/ExpiryTimer';
|
||||
import { useCallState } from '@dailyjs/shared/contexts/CallProvider';
|
||||
import { useCallUI } from '@dailyjs/shared/hooks/useCallUI';
|
||||
|
|
|
|||
|
|
@ -1,64 +1,16 @@
|
|||
import React from 'react';
|
||||
import { Audio } from '@dailyjs/shared/components/Audio';
|
||||
import { BasicTray } from '@dailyjs/shared/components/Tray';
|
||||
import {
|
||||
WaitingRoomModal,
|
||||
WaitingRoomNotification,
|
||||
} from '@dailyjs/shared/components/WaitingRoom';
|
||||
import { useParticipants } from '@dailyjs/shared/contexts/ParticipantsProvider';
|
||||
import { useWaitingRoom } from '@dailyjs/shared/contexts/WaitingRoomProvider';
|
||||
import useJoinSound from '@dailyjs/shared/hooks/useJoinSound';
|
||||
|
||||
import { VideoGrid } from '../VideoGrid';
|
||||
import { Header } from './Header';
|
||||
import RoomContainer from './RoomContainer';
|
||||
|
||||
export const Room = () => {
|
||||
const { setShowModal, showModal } = useWaitingRoom();
|
||||
const { localParticipant } = useParticipants();
|
||||
|
||||
useJoinSound();
|
||||
|
||||
return (
|
||||
<div className="room">
|
||||
<Header />
|
||||
|
||||
<main>
|
||||
<VideoGrid />
|
||||
</main>
|
||||
|
||||
{/* Show waiting room notification & modal if call owner */}
|
||||
{localParticipant?.isOwner && (
|
||||
<>
|
||||
<WaitingRoomNotification />
|
||||
{showModal && (
|
||||
<WaitingRoomModal onClose={() => setShowModal(false)} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<BasicTray />
|
||||
<Audio />
|
||||
|
||||
<style jsx>{`
|
||||
.room {
|
||||
flex-flow: column nowrap;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1 1 auto;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-height: 0px;
|
||||
height: 100%;
|
||||
padding: var(--spacing-xxxs);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export const Room = () => (
|
||||
<RoomContainer>
|
||||
<Header />
|
||||
<main>
|
||||
<VideoGrid />
|
||||
</main>
|
||||
</RoomContainer>
|
||||
);
|
||||
|
||||
export default Room;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { Audio } from '@dailyjs/shared/components/Audio';
|
||||
import { BasicTray } from '@dailyjs/shared/components/Tray';
|
||||
import { useParticipants } from '@dailyjs/shared/contexts/ParticipantsProvider';
|
||||
import useJoinSound from '@dailyjs/shared/hooks/useJoinSound';
|
||||
import PropTypes from 'prop-types';
|
||||
import WaitingRoom from '../WaitingRoom';
|
||||
|
||||
const RoomContainer = ({ children }) => {
|
||||
const { localParticipant } = useParticipants();
|
||||
const isOwner = !!localParticipant?.isOwner;
|
||||
|
||||
useJoinSound();
|
||||
|
||||
const roomComponents = useMemo(
|
||||
() => (
|
||||
<>
|
||||
{/* Show waiting room notification & modal if call owner */}
|
||||
{isOwner && <WaitingRoom />}
|
||||
|
||||
{/* Tray buttons */}
|
||||
<BasicTray />
|
||||
|
||||
{/* Audio tags */}
|
||||
<Audio />
|
||||
</>
|
||||
),
|
||||
[isOwner]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="room">
|
||||
{children}
|
||||
{roomComponents}
|
||||
|
||||
<style jsx>{`
|
||||
.room {
|
||||
flex-flow: column nowrap;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.room > :global(main) {
|
||||
flex: 1 1 auto;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-height: 0px;
|
||||
height: 100%;
|
||||
padding: var(--spacing-xxxs);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
RoomContainer.propTypes = {
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
export default React.memo(RoomContainer, () => true);
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
WaitingRoomModal,
|
||||
WaitingRoomNotification,
|
||||
} from '@dailyjs/shared/components/WaitingRoom';
|
||||
import { useWaitingRoom } from '@dailyjs/shared/contexts/WaitingRoomProvider';
|
||||
|
||||
export const WaitingRoom = () => {
|
||||
const { setShowModal, showModal } = useWaitingRoom();
|
||||
return (
|
||||
<>
|
||||
<WaitingRoomNotification />
|
||||
{showModal && <WaitingRoomModal onClose={() => setShowModal(false)} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default WaitingRoom;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { WaitingRoom as default } from './WaitingRoom';
|
||||
|
|
@ -15,6 +15,7 @@ export default async function handler(req, res) {
|
|||
properties: {
|
||||
exp: Math.round(Date.now() / 1000) + (expiryMinutes || 5) * 60, // expire in x minutes
|
||||
eject_at_room_exp: true,
|
||||
enable_knocking: privacy !== 'public',
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export const FlyingEmojisOverlay = () => {
|
|||
// Remove all event listeners on unmount to prevent console warnings
|
||||
useEffect(
|
||||
() => () =>
|
||||
overlayRef.current.childNodes.forEach((n) =>
|
||||
overlayRef.current?.childNodes.forEach((n) =>
|
||||
n.removeEventListener('animationend', handleRemoveFlyingEmoji)
|
||||
),
|
||||
[handleRemoveFlyingEmoji]
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
// Note: I am here because next-transpile-modules requires a mainfile
|
||||
|
|
@ -1 +1,9 @@
|
|||
# Live Fitness
|
||||
|
||||
Brings together
|
||||
|
||||
- Live streaming
|
||||
- Recording
|
||||
- Flying emojis
|
||||
- Pagination / track management
|
||||
- Text chat
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
import React from 'react';
|
||||
|
||||
import App from '@dailyjs/basic-call/components/App';
|
||||
import FlyingEmojiOverlay from '@dailyjs/flying-emojis/components/FlyingEmojis';
|
||||
import { ChatProvider } from '@dailyjs/text-chat/contexts/ChatProvider';
|
||||
|
||||
export const LiveFitnessApp = () => (
|
||||
<>
|
||||
<ChatProvider>
|
||||
<FlyingEmojiOverlay />
|
||||
<App />
|
||||
</ChatProvider>
|
||||
</>
|
||||
);
|
||||
|
||||
export default LiveFitnessApp;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { LiveFitnessApp as default } from './App';
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
import FlyingEmojiTrayButton from '@dailyjs/flying-emojis/components/Tray';
|
||||
import ChatTrayButton from '@dailyjs/text-chat/components/Tray';
|
||||
|
||||
export const Tray = () => (
|
||||
<>
|
||||
<FlyingEmojiTrayButton />
|
||||
<ChatTrayButton />
|
||||
</>
|
||||
);
|
||||
|
||||
export default Tray;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { Tray as default } from './Tray';
|
||||
|
|
@ -2,6 +2,8 @@ const withPlugins = require('next-compose-plugins');
|
|||
const withTM = require('next-transpile-modules')([
|
||||
'@dailyjs/shared',
|
||||
'@dailyjs/basic-call',
|
||||
'@dailyjs/flying-emojis',
|
||||
'@dailyjs/text-chat',
|
||||
]);
|
||||
|
||||
const packageJson = require('./package.json');
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
"dependencies": {
|
||||
"@dailyjs/shared": "*",
|
||||
"@dailyjs/basic-call": "*",
|
||||
"@dailyjs/flying-emojis": "*",
|
||||
"@dailyjs/text-chat": "*",
|
||||
"next": "^11.0.0",
|
||||
"pluralize": "^8.0.0",
|
||||
"react": "^17.0.2",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import App from '@dailyjs/basic-call/components/App';
|
||||
import Loader from '@dailyjs/shared/components/Loader';
|
||||
import { CallProvider } from '@dailyjs/shared/contexts/CallProvider';
|
||||
import { MediaDeviceProvider } from '@dailyjs/shared/contexts/MediaDeviceProvider';
|
||||
import { ParticipantsProvider } from '@dailyjs/shared/contexts/ParticipantsProvider';
|
||||
|
|
@ -10,12 +8,19 @@ import { WaitingRoomProvider } from '@dailyjs/shared/contexts/WaitingRoomProvide
|
|||
import getDemoProps from '@dailyjs/shared/lib/demoProps';
|
||||
import { useRouter } from 'next/router';
|
||||
import PropTypes from 'prop-types';
|
||||
import App from '../components/App';
|
||||
|
||||
/**
|
||||
* Room page
|
||||
* ---
|
||||
*/
|
||||
export default function Room({ room, instructor, domain }) {
|
||||
export default function Room({
|
||||
room,
|
||||
instructor,
|
||||
domain,
|
||||
customTrayComponent,
|
||||
asides,
|
||||
}) {
|
||||
const [token, setToken] = useState();
|
||||
const [tokenError, setTokenError] = useState();
|
||||
|
||||
|
|
@ -65,11 +70,15 @@ export default function Room({ room, instructor, domain }) {
|
|||
return <div>Fetching token...</div>;
|
||||
}
|
||||
|
||||
if (tokenError) {
|
||||
return <div>Token error</div>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main call UI
|
||||
*/
|
||||
return (
|
||||
<UIStateProvider>
|
||||
<UIStateProvider customTrayComponent={customTrayComponent} asides={asides}>
|
||||
<CallProvider domain={domain} room={room} token={token}>
|
||||
<ParticipantsProvider>
|
||||
<TracksProvider>
|
||||
|
|
@ -89,6 +98,8 @@ Room.propTypes = {
|
|||
room: PropTypes.string.isRequired,
|
||||
domain: PropTypes.string.isRequired,
|
||||
instructor: PropTypes.bool,
|
||||
customTrayComponent: PropTypes.node,
|
||||
asides: PropTypes.arrayOf(PropTypes.func),
|
||||
};
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
|
|
|
|||
|
|
@ -1,30 +1,9 @@
|
|||
import React from 'react';
|
||||
import GlobalHead from '@dailyjs/shared/components/GlobalHead';
|
||||
import GlobalStyle from '@dailyjs/shared/components/GlobalStyle';
|
||||
import Head from 'next/head';
|
||||
import PropTypes from 'prop-types';
|
||||
import App from '@dailyjs/basic-call/pages/_app';
|
||||
import ChatAside from '@dailyjs/text-chat/components/ChatAside';
|
||||
import Tray from '../components/Tray';
|
||||
|
||||
function App({ Component, pageProps }) {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Daily - {process.env.PROJECT_TITLE}</title>
|
||||
</Head>
|
||||
<GlobalHead />
|
||||
<GlobalStyle />
|
||||
<Component {...pageProps} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
App.defaultProps = {
|
||||
Component: null,
|
||||
pageProps: {},
|
||||
};
|
||||
|
||||
App.propTypes = {
|
||||
Component: PropTypes.elementType,
|
||||
pageProps: PropTypes.object,
|
||||
};
|
||||
App.customTrayComponent = <Tray />;
|
||||
App.asides = [ChatAside];
|
||||
|
||||
export default App;
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import App from '@dailyjs/basic-call/components/App';
|
||||
import { LiveStreamingProvider } from '../../contexts/LiveStreamingProvider';
|
||||
|
||||
// Extend our basic call app component with the live streaming context
|
||||
export const AppWithLiveStreaming = () => (
|
||||
<LiveStreamingProvider>
|
||||
<App />
|
||||
</LiveStreamingProvider>
|
||||
);
|
||||
|
||||
export default AppWithLiveStreaming;
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { AppWithLiveStreaming as default } from './App';
|
||||
|
|
@ -12,15 +12,13 @@ export const Tray = () => {
|
|||
const { isStreaming } = useLiveStreaming();
|
||||
|
||||
return (
|
||||
<>
|
||||
<TrayButton
|
||||
label={isStreaming ? 'Live' : 'Stream'}
|
||||
orange={isStreaming}
|
||||
onClick={() => openModal(LIVE_STREAMING_MODAL)}
|
||||
>
|
||||
<IconStream />
|
||||
</TrayButton>
|
||||
</>
|
||||
<TrayButton
|
||||
label={isStreaming ? 'Live' : 'Stream'}
|
||||
orange={isStreaming}
|
||||
onClick={() => openModal(LIVE_STREAMING_MODAL)}
|
||||
>
|
||||
<IconStream />
|
||||
</TrayButton>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -32,15 +32,13 @@ export const Tray = () => {
|
|||
].includes(recordingState);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TrayButton
|
||||
label={isRecording ? 'Recording' : 'Record'}
|
||||
orange={isRecording}
|
||||
onClick={() => openModal(RECORDING_MODAL)}
|
||||
>
|
||||
<IconRecord />
|
||||
</TrayButton>
|
||||
</>
|
||||
<TrayButton
|
||||
label={isRecording ? 'Recording' : 'Record'}
|
||||
orange={isRecording}
|
||||
onClick={() => openModal(RECORDING_MODAL)}
|
||||
>
|
||||
<IconRecord />
|
||||
</TrayButton>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export const MessageCard = ({
|
|||
hideBack = false,
|
||||
onBack,
|
||||
}) => (
|
||||
<Card>
|
||||
<Card className={error && 'error'}>
|
||||
{header && <CardHeader>{header}</CardHeader>}
|
||||
{children && <CardBody>{children}</CardBody>}
|
||||
{!hideBack && (
|
||||
|
|
@ -27,13 +27,11 @@ export const MessageCard = ({
|
|||
)}
|
||||
</CardFooter>
|
||||
)}
|
||||
{error && (
|
||||
<style jsx>{`
|
||||
.card {
|
||||
border: 3px solid var(--red-default);
|
||||
}
|
||||
`}</style>
|
||||
)}
|
||||
<style jsx>{`
|
||||
:global(.card.error) {
|
||||
border: 3px solid var(--red-default);
|
||||
}
|
||||
`}</style>
|
||||
</Card>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
import React from 'react';
|
||||
import Button from '@dailyjs/shared/components/Button';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const TrayButton = ({ children, label, onClick, orange = false }) => (
|
||||
<div className={orange ? 'tray-button orange' : 'tray-button'}>
|
||||
<Button onClick={() => onClick()} variant="dark" size="large-square">
|
||||
{children}
|
||||
</Button>
|
||||
<span>{label}</span>
|
||||
|
||||
<style jsx>{`
|
||||
.tray-button {
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tray-button.orange :global(.button) {
|
||||
color: var(--secondary-dark);
|
||||
}
|
||||
|
||||
span {
|
||||
color: white;
|
||||
font-weight: var(--weight-medium);
|
||||
font-size: 12px;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
|
||||
TrayButton.propTypes = {
|
||||
children: PropTypes.node,
|
||||
onClick: PropTypes.func,
|
||||
orange: PropTypes.bool,
|
||||
label: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default TrayButton;
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import { TrayButton } from '@dailyjs/shared/components/Tray';
|
||||
import { useAudioLevel } from '@dailyjs/shared/hooks/useAudioLevel';
|
||||
import { ReactComponent as IconMicOff } from '@dailyjs/shared/icons/mic-off-md.svg';
|
||||
import { ReactComponent as IconMicOn } from '@dailyjs/shared/icons/mic-on-md.svg';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const TrayMicButton = ({ isMuted, onClick }) => {
|
||||
const audioLevel = useAudioLevel('local');
|
||||
|
||||
return (
|
||||
<TrayButton label="Mic" onClick={onClick} orange={isMuted}>
|
||||
{isMuted ? <IconMicOff /> : <IconMicOn />}
|
||||
</TrayButton>
|
||||
);
|
||||
};
|
||||
|
||||
TrayMicButton.propTypes = {
|
||||
isMuted: PropTypes.bool,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default TrayMicButton;
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const useAudioLevel = (sessionId) => {
|
||||
const [audioLevel, setAudioLevel] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!sessionId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const i = setInterval(async () => {
|
||||
try {
|
||||
if (!(window.rtcpeers && window.rtcpeers.sfu)) {
|
||||
return;
|
||||
}
|
||||
const consumer =
|
||||
window.rtcpeers.sfu.consumers[`${sessionId}/cam-audio`];
|
||||
if (!(consumer && consumer.getStats)) {
|
||||
return;
|
||||
}
|
||||
const level = Array.from((await consumer.getStats()).values()).find(
|
||||
(s) => 'audioLevel' in s
|
||||
).audioLevel;
|
||||
setAudioLevel(level);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
return () => clearInterval(i);
|
||||
}, [sessionId]);
|
||||
|
||||
return audioLevel;
|
||||
};
|
||||
|
||||
export default useAudioLevel;
|
||||
|
|
@ -11,17 +11,15 @@ export const Tray = () => {
|
|||
const { hasNewMessages } = useChat();
|
||||
|
||||
return (
|
||||
<>
|
||||
<TrayButton
|
||||
label="Chat"
|
||||
bubble={hasNewMessages}
|
||||
onClick={() => {
|
||||
toggleAside(CHAT_ASIDE);
|
||||
}}
|
||||
>
|
||||
<IconChat />
|
||||
</TrayButton>
|
||||
</>
|
||||
<TrayButton
|
||||
label="Chat"
|
||||
bubble={hasNewMessages}
|
||||
onClick={() => {
|
||||
toggleAside(CHAT_ASIDE);
|
||||
}}
|
||||
>
|
||||
<IconChat />
|
||||
</TrayButton>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
// Note: I am here because next-transpile-modules requires a mainfile
|
||||
Loading…
Reference in New Issue