Add chat and screenshare options to the demo
This commit is contained in:
parent
ec770a189a
commit
209b9bd72e
|
|
@ -1,51 +1,13 @@
|
||||||
import React, { useMemo } from 'react';
|
import React from 'react';
|
||||||
import ExpiryTimer from '@custom/shared/components/ExpiryTimer';
|
|
||||||
import { useCallState } from '@custom/shared/contexts/CallProvider';
|
|
||||||
import { useCallUI } from '@custom/shared/hooks/useCallUI';
|
|
||||||
|
|
||||||
import PropTypes from 'prop-types';
|
import App from '@custom/basic-call/components/App';
|
||||||
import Room from '../Call/Room';
|
import { ChatProvider } from '../../contexts/ChatProvider';
|
||||||
import { Asides } from './Asides';
|
|
||||||
import { Modals } from './Modals';
|
|
||||||
|
|
||||||
export const App = ({ customComponentForState }) => {
|
// Extend our basic call app component with the chat context
|
||||||
const { roomExp, state } = useCallState();
|
export const CustomApp = () => (
|
||||||
|
<ChatProvider>
|
||||||
|
<App />
|
||||||
|
</ChatProvider>
|
||||||
|
);
|
||||||
|
|
||||||
const componentForState = useCallUI({
|
export default CustomApp;
|
||||||
state,
|
|
||||||
room: <Room />,
|
|
||||||
...customComponentForState,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Memoize children to avoid unnecassary renders from HOC
|
|
||||||
return useMemo(
|
|
||||||
() => (
|
|
||||||
<>
|
|
||||||
{roomExp && <ExpiryTimer expiry={roomExp} />}
|
|
||||||
<div className="app">
|
|
||||||
{componentForState()}
|
|
||||||
<Modals />
|
|
||||||
<Asides />
|
|
||||||
<style jsx>{`
|
|
||||||
color: white;
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
[componentForState, roomExp]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
App.propTypes = {
|
|
||||||
customComponentForState: PropTypes.any,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import { Aside } from '@custom/shared/components/Aside';
|
||||||
|
import Button from '@custom/shared/components/Button';
|
||||||
|
import { TextInput } from '@custom/shared/components/Input';
|
||||||
|
import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
|
||||||
|
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
||||||
|
import { useChat } from '../../contexts/ChatProvider';
|
||||||
|
import { useMessageSound } from '../../hooks/useMessageSound';
|
||||||
|
|
||||||
|
export const CHAT_ASIDE = 'chat';
|
||||||
|
|
||||||
|
export const ChatAside = () => {
|
||||||
|
const { showAside, setShowAside } = useUIState();
|
||||||
|
const { sendMessage, chatHistory, hasNewMessages, setHasNewMessages } =
|
||||||
|
useChat();
|
||||||
|
const { localParticipant } = useParticipants();
|
||||||
|
const [newMessage, setNewMessage] = useState('');
|
||||||
|
const playMessageSound = useMessageSound();
|
||||||
|
|
||||||
|
const chatWindowRef = useRef();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Clear out any new message notifications if we're showing the chat screen
|
||||||
|
if (showAside === CHAT_ASIDE) {
|
||||||
|
setHasNewMessages(false);
|
||||||
|
}
|
||||||
|
}, [showAside, chatHistory.length, setHasNewMessages]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasNewMessages && showAside !== CHAT_ASIDE) {
|
||||||
|
playMessageSound();
|
||||||
|
}
|
||||||
|
}, [playMessageSound, showAside, hasNewMessages]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (chatWindowRef.current) {
|
||||||
|
chatWindowRef.current.scrollTop = chatWindowRef.current.scrollHeight;
|
||||||
|
}
|
||||||
|
}, [chatHistory?.length]);
|
||||||
|
|
||||||
|
const isLocalUser = (id) => id === localParticipant.user_id;
|
||||||
|
|
||||||
|
if (!showAside || showAside !== CHAT_ASIDE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Aside onClose={() => setShowAside(false)}>
|
||||||
|
<div className="messages-container" ref={chatWindowRef}>
|
||||||
|
{chatHistory.map((chatItem) => (
|
||||||
|
<div
|
||||||
|
className={isLocalUser(chatItem.senderID) ? 'message local' : 'message'}
|
||||||
|
key={chatItem.id}
|
||||||
|
>
|
||||||
|
<span className="content">{chatItem.message}</span>
|
||||||
|
<span className="sender">{chatItem.sender}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<footer className="chat-footer">
|
||||||
|
<TextInput
|
||||||
|
value={newMessage}
|
||||||
|
placeholder="Type message here"
|
||||||
|
variant="transparent"
|
||||||
|
onChange={(e) => setNewMessage(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className="send-button"
|
||||||
|
variant="transparent"
|
||||||
|
disabled={!newMessage}
|
||||||
|
onClick={() => {
|
||||||
|
sendMessage(newMessage);
|
||||||
|
setNewMessage('');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Send
|
||||||
|
</Button>
|
||||||
|
</footer>
|
||||||
|
<style jsx>{`
|
||||||
|
.messages-container {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
margin: var(--spacing-xxs);
|
||||||
|
padding: var(--spacing-xxs);
|
||||||
|
background: var(--gray-wash);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.local {
|
||||||
|
background: var(--gray-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.local .sender {
|
||||||
|
color: var(--primary-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
color: var(--text-mid);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender {
|
||||||
|
font-weight: var(--weight-medium);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-footer {
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: var(--spacing-xxs) 0;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
border-top: 1px solid var(--gray-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-footer :global(.input-container) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-footer :global(.input-container input) {
|
||||||
|
padding-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-footer :global(.send-button) {
|
||||||
|
padding: 0 var(--spacing-xs);
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</Aside>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChatAside;
|
||||||
|
|
@ -18,7 +18,7 @@ import { useDeepCompareMemo } from 'use-deep-compare';
|
||||||
export const VideoGrid = React.memo(
|
export const VideoGrid = React.memo(
|
||||||
() => {
|
() => {
|
||||||
const containerRef = useRef();
|
const containerRef = useRef();
|
||||||
const { participants } = useParticipants();
|
const { allParticipants } = useParticipants();
|
||||||
const [dimensions, setDimensions] = useState({
|
const [dimensions, setDimensions] = useState({
|
||||||
width: 1,
|
width: 1,
|
||||||
height: 1,
|
height: 1,
|
||||||
|
|
@ -48,7 +48,7 @@ export const VideoGrid = React.memo(
|
||||||
// Basic brute-force packing algo
|
// Basic brute-force packing algo
|
||||||
const layout = useMemo(() => {
|
const layout = useMemo(() => {
|
||||||
const aspectRatio = DEFAULT_ASPECT_RATIO;
|
const aspectRatio = DEFAULT_ASPECT_RATIO;
|
||||||
const tileCount = participants.length || 0;
|
const tileCount = allParticipants.length || 0;
|
||||||
const w = dimensions.width;
|
const w = dimensions.width;
|
||||||
const h = dimensions.height;
|
const h = dimensions.height;
|
||||||
|
|
||||||
|
|
@ -87,23 +87,23 @@ export const VideoGrid = React.memo(
|
||||||
}
|
}
|
||||||
|
|
||||||
return bestLayout;
|
return bestLayout;
|
||||||
}, [dimensions, participants]);
|
}, [dimensions, allParticipants]);
|
||||||
|
|
||||||
// Memoize our tile list to avoid unnecassary re-renders
|
// Memoize our tile list to avoid unnecassary re-renders
|
||||||
const tiles = useDeepCompareMemo(
|
const tiles = useDeepCompareMemo(
|
||||||
() =>
|
() =>
|
||||||
participants.map((p) => (
|
allParticipants.map((p) => (
|
||||||
<Tile
|
<Tile
|
||||||
participant={p}
|
participant={p}
|
||||||
key={p.id}
|
key={p.id}
|
||||||
mirrored
|
mirrored={!p.isScreenshare}
|
||||||
style={{ maxWidth: layout.width, maxHeight: layout.height }}
|
style={{ maxWidth: layout.width, maxHeight: layout.height }}
|
||||||
/>
|
/>
|
||||||
)),
|
)),
|
||||||
[layout, participants]
|
[layout, allParticipants]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!participants.length) {
|
if (!allParticipants.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
import Field from '@custom/shared/components/Field';
|
import Field from '@custom/shared/components/Field';
|
||||||
import { TextInput, BooleanInput, SelectInput } from '@custom/shared/components/Input';
|
import { TextInput, BooleanInput, SelectInput } from '@custom/shared/components/Input';
|
||||||
import Well from '@custom/shared/components/Well';
|
import Well from '@custom/shared/components/Well';
|
||||||
|
import { slugify } from '@custom/shared/lib/slugify';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -24,7 +25,7 @@ export const Intro = ({
|
||||||
const [rooms, setRooms] = useState({});
|
const [rooms, setRooms] = useState({});
|
||||||
const [duration, setDuration] = useState("30");
|
const [duration, setDuration] = useState("30");
|
||||||
const [roomName, setRoomName] = useState();
|
const [roomName, setRoomName] = useState();
|
||||||
const [privacy, setPrivacy] = useState(false);
|
const [privacy, setPrivacy] = useState(true);
|
||||||
|
|
||||||
const fetchRooms = async () => {
|
const fetchRooms = async () => {
|
||||||
const res = await fetch('/api/presence', {
|
const res = await fetch('/api/presence', {
|
||||||
|
|
@ -62,12 +63,17 @@ export const Intro = ({
|
||||||
{Object.keys(rooms).map(room => (
|
{Object.keys(rooms).map(room => (
|
||||||
<div className="room" key={room}>
|
<div className="room" key={room}>
|
||||||
<div>
|
<div>
|
||||||
<div className="label">{room}</div>
|
<div className="label">{slugify.revert(room)}</div>
|
||||||
<span>{`${rooms[room].length} people in class`}</span>
|
<span>
|
||||||
|
{`${rooms[room].length} ${rooms[room].length > 1 ? 'people' : 'person'} in class`}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="join-room">
|
<div className="join-room">
|
||||||
<Button variant="dark" size="tiny" onClick={() => onJoin(room, 'join')}>
|
<Button
|
||||||
Join Room
|
variant="gray"
|
||||||
|
size="tiny"
|
||||||
|
onClick={() => onJoin(slugify.convert(room), 'join')}>
|
||||||
|
Join Class
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -88,7 +94,7 @@ export const Intro = ({
|
||||||
<Field label="Give you a class name">
|
<Field label="Give you a class name">
|
||||||
<TextInput
|
<TextInput
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Eg. super-stretch"
|
placeholder="Eg. Super stretchy morning flow"
|
||||||
defaultValue={roomName}
|
defaultValue={roomName}
|
||||||
onChange={(e) => setRoomName(e.target.value)}
|
onChange={(e) => setRoomName(e.target.value)}
|
||||||
required
|
required
|
||||||
|
|
@ -104,13 +110,16 @@ export const Intro = ({
|
||||||
</SelectInput>
|
</SelectInput>
|
||||||
</Field>
|
</Field>
|
||||||
<Field label="Public (anyone can join)">
|
<Field label="Public (anyone can join)">
|
||||||
<BooleanInput value={privacy} onChange={e => setPrivacy(e.target.checked)} />
|
<BooleanInput
|
||||||
|
value={privacy}
|
||||||
|
onChange={e => setPrivacy(e.target.checked)}
|
||||||
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
<CardFooter divider>
|
<CardFooter divider>
|
||||||
<Button
|
<Button
|
||||||
fullWidth
|
fullWidth
|
||||||
onClick={() => onJoin(roomName, 'create', duration, privacy)}
|
onClick={() => onJoin(slugify.convert(roomName), 'create', duration, privacy)}
|
||||||
>
|
>
|
||||||
Create class
|
Create class
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -131,7 +140,7 @@ export const Intro = ({
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 25vw;
|
width: 25vw;
|
||||||
border-bottom: 1px solid var(--gray-light);
|
border-bottom: 1px solid var(--gray-light);
|
||||||
padding-bottom: var(--spacing-xxs);
|
padding: var(--spacing-xxs) 0;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
.room .label {
|
.room .label {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { TrayButton } from '@custom/shared/components/Tray';
|
||||||
|
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
||||||
|
import { ReactComponent as IconChat } from '@custom/shared/icons/chat-md.svg';
|
||||||
|
import { useChat } from '../../contexts/ChatProvider';
|
||||||
|
import { CHAT_ASIDE } from '../Call/ChatAside';
|
||||||
|
|
||||||
|
export const ChatTray = () => {
|
||||||
|
const { toggleAside } = useUIState();
|
||||||
|
const { hasNewMessages } = useChat();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TrayButton
|
||||||
|
label="Chat"
|
||||||
|
bubble={hasNewMessages}
|
||||||
|
onClick={() => toggleAside(CHAT_ASIDE)}
|
||||||
|
>
|
||||||
|
<IconChat />
|
||||||
|
</TrayButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChatTray;
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { TrayButton } from '@custom/shared/components/Tray';
|
||||||
|
import { useCallState } from '@custom/shared/contexts/CallProvider';
|
||||||
|
import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
|
||||||
|
import { ReactComponent as IconShare } from '@custom/shared/icons/share-sm.svg';
|
||||||
|
|
||||||
|
const MAX_SCREEN_SHARES = 2;
|
||||||
|
|
||||||
|
export const ScreenShareTray = () => {
|
||||||
|
const { callObject, enableScreenShare } = useCallState();
|
||||||
|
const { screens, participants } = useParticipants();
|
||||||
|
|
||||||
|
const isSharingScreen = useMemo(
|
||||||
|
() => screens.some((s) => s.isLocal),
|
||||||
|
[screens]
|
||||||
|
);
|
||||||
|
|
||||||
|
const screensLength = useMemo(() => screens.length, [screens]);
|
||||||
|
|
||||||
|
const toggleScreenShare = () =>
|
||||||
|
isSharingScreen ? callObject.stopScreenShare() : callObject.startScreenShare();
|
||||||
|
|
||||||
|
const disabled =
|
||||||
|
participants.length &&
|
||||||
|
screensLength >= MAX_SCREEN_SHARES &&
|
||||||
|
!isSharingScreen;
|
||||||
|
|
||||||
|
if (!enableScreenShare) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TrayButton
|
||||||
|
label={isSharingScreen ? 'Stop': 'Share'}
|
||||||
|
orange={isSharingScreen}
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={toggleScreenShare}
|
||||||
|
>
|
||||||
|
<IconShare />
|
||||||
|
</TrayButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScreenShareTray;
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ChatTray from './Chat';
|
||||||
|
import ScreenShareTray from './ScreenShare';
|
||||||
|
|
||||||
|
export const Tray = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ChatTray />
|
||||||
|
<ScreenShareTray />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Tray;
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
import React, {
|
||||||
|
createContext,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import { useCallState } from '@custom/shared/contexts/CallProvider';
|
||||||
|
import { useSharedState } from '@custom/shared/hooks/useSharedState';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
export const ChatContext = createContext();
|
||||||
|
|
||||||
|
export const ChatProvider = ({ children }) => {
|
||||||
|
const { callObject } = useCallState();
|
||||||
|
const { sharedState, setSharedState } = useSharedState({
|
||||||
|
initialValues: {
|
||||||
|
chatHistory: [],
|
||||||
|
},
|
||||||
|
broadcast: false,
|
||||||
|
});
|
||||||
|
const [hasNewMessages, setHasNewMessages] = useState(false);
|
||||||
|
|
||||||
|
const handleNewMessage = useCallback(
|
||||||
|
(e) => {
|
||||||
|
if (e?.data?.message?.type) return;
|
||||||
|
const participants = callObject.participants();
|
||||||
|
const sender = participants[e.fromId].user_name
|
||||||
|
? participants[e.fromId].user_name
|
||||||
|
: 'Guest';
|
||||||
|
|
||||||
|
setSharedState(values => ({
|
||||||
|
...values,
|
||||||
|
chatHistory: [
|
||||||
|
...values.chatHistory,
|
||||||
|
// nanoid - we use it to generate unique ID string
|
||||||
|
{ sender, senderID: e.fromId, message: e.data.message, id: nanoid() },
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
|
||||||
|
setHasNewMessages(true);
|
||||||
|
},
|
||||||
|
[callObject, setSharedState]
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const sendMessage = useCallback(
|
||||||
|
(message) => {
|
||||||
|
if (!callObject) return;
|
||||||
|
|
||||||
|
console.log('💬 Sending app message');
|
||||||
|
|
||||||
|
callObject.sendAppMessage({ message }, '*');
|
||||||
|
|
||||||
|
const participants = callObject.participants();
|
||||||
|
// Get the sender (local participant) name
|
||||||
|
const sender = participants.local.user_name
|
||||||
|
? participants.local.user_name
|
||||||
|
: 'Guest';
|
||||||
|
const senderID = participants.local.user_id;
|
||||||
|
|
||||||
|
// Update shared state chat history
|
||||||
|
return setSharedState(values => ({
|
||||||
|
...values,
|
||||||
|
chatHistory: [
|
||||||
|
...values.chatHistory,
|
||||||
|
// nanoid - we use it to generate unique ID string
|
||||||
|
{ sender, senderID, message, id: nanoid() }
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
[callObject, setSharedState]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!callObject) return;
|
||||||
|
|
||||||
|
console.log(`💬 Chat provider listening for app messages`);
|
||||||
|
|
||||||
|
callObject.on('app-message', handleNewMessage);
|
||||||
|
|
||||||
|
return () => callObject.off('app-message', handleNewMessage);
|
||||||
|
}, [callObject, handleNewMessage]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChatContext.Provider
|
||||||
|
value={{
|
||||||
|
sendMessage,
|
||||||
|
chatHistory: sharedState.chatHistory,
|
||||||
|
hasNewMessages,
|
||||||
|
setHasNewMessages,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ChatContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ChatProvider.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useChat = () => useContext(ChatContext);
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { useSound } from '@custom/shared/hooks/useSound';
|
||||||
|
import { debounce } from 'debounce';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience hook to play `join.mp3` when participants join the call
|
||||||
|
*/
|
||||||
|
export const useMessageSound = () => {
|
||||||
|
const { load, play } = useSound('assets/message.mp3');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
load();
|
||||||
|
}, [load]);
|
||||||
|
|
||||||
|
return useMemo(() => debounce(() => play(), 5000, true), [play]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useMessageSound;
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
const withPlugins = require('next-compose-plugins');
|
const withPlugins = require('next-compose-plugins');
|
||||||
const withTM = require('next-transpile-modules')(['@custom/shared']);
|
const withTM = require('next-transpile-modules')([
|
||||||
|
'@custom/shared',
|
||||||
|
'@custom/basic-call',
|
||||||
|
]);
|
||||||
|
|
||||||
const packageJson = require('./package.json');
|
const packageJson = require('./package.json');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@ import React from 'react';
|
||||||
import GlobalStyle from '@custom/shared/components/GlobalStyle';
|
import GlobalStyle from '@custom/shared/components/GlobalStyle';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { CustomApp } from '../components/App/App';
|
||||||
|
import ChatAside from '../components/Call/ChatAside';
|
||||||
|
import Tray from '../components/Tray';
|
||||||
|
|
||||||
function App({ Component, pageProps }) {
|
function App({ Component, pageProps }) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -31,9 +34,9 @@ App.propTypes = {
|
||||||
pageProps: PropTypes.object,
|
pageProps: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
App.asides = [];
|
App.asides = [ChatAside];
|
||||||
App.modals = [];
|
App.modals = [];
|
||||||
App.customTrayComponent = null;
|
App.customTrayComponent = <Tray />;
|
||||||
App.customAppComponent = null;
|
App.customAppComponent = <CustomApp />;
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ PersonRow.propTypes = {
|
||||||
export const PeopleAside = () => {
|
export const PeopleAside = () => {
|
||||||
const { callObject } = useCallState();
|
const { callObject } = useCallState();
|
||||||
const { showAside, setShowAside } = useUIState();
|
const { showAside, setShowAside } = useUIState();
|
||||||
const { allParticipants, isOwner } = useParticipants();
|
const { participants, isOwner } = useParticipants();
|
||||||
|
|
||||||
if (!showAside || showAside !== PEOPLE_ASIDE) {
|
if (!showAside || showAside !== PEOPLE_ASIDE) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -131,7 +131,7 @@ export const PeopleAside = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="rows">
|
<div className="rows">
|
||||||
{allParticipants.map((p) => (
|
{participants.map((p) => (
|
||||||
<PersonRow participant={p} key={p.id} isOwner={isOwner} />
|
<PersonRow participant={p} key={p.id} isOwner={isOwner} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,24 @@ export const Button = forwardRef(
|
||||||
.button.dark:disabled {
|
.button.dark:disabled {
|
||||||
opacity: 0.35;
|
opacity: 0.35;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button.gray {
|
||||||
|
background: ${theme.gray.light};
|
||||||
|
color: ${theme.gray.dark};
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
.button.gray:hover,
|
||||||
|
.button.gray:focus,
|
||||||
|
.button.gray:active {
|
||||||
|
background: ${theme.gray.default};
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
.button.gray:focus {
|
||||||
|
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
.button.gray:disabled {
|
||||||
|
opacity: 0.35;
|
||||||
|
}
|
||||||
|
|
||||||
.button.white {
|
.button.white {
|
||||||
background: white;
|
background: white;
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import React, {
|
||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import DailyIframe from '@daily-co/daily-js';
|
||||||
import Bowser from 'bowser';
|
import Bowser from 'bowser';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
|
|
@ -31,6 +32,7 @@ export const CallProvider = ({
|
||||||
token = '',
|
token = '',
|
||||||
subscribeToTracksAutomatically = true,
|
subscribeToTracksAutomatically = true,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [enableScreenShare, setEnableScreenShare] = useState(false);
|
||||||
const [videoQuality, setVideoQuality] = useState(VIDEO_QUALITY_AUTO);
|
const [videoQuality, setVideoQuality] = useState(VIDEO_QUALITY_AUTO);
|
||||||
const [showLocalVideo, setShowLocalVideo] = useState(true);
|
const [showLocalVideo, setShowLocalVideo] = useState(true);
|
||||||
const [preJoinNonAuthorized, setPreJoinNonAuthorized] = useState(false);
|
const [preJoinNonAuthorized, setPreJoinNonAuthorized] = useState(false);
|
||||||
|
|
@ -52,7 +54,12 @@ export const CallProvider = ({
|
||||||
if (!daily) return;
|
if (!daily) return;
|
||||||
const updateRoomConfigState = async () => {
|
const updateRoomConfigState = async () => {
|
||||||
const roomConfig = await daily.room();
|
const roomConfig = await daily.room();
|
||||||
|
const isOob = !!roomConfig.config?.owner_only_broadcast;
|
||||||
|
const owner = roomConfig.tokenConfig?.is_owner;
|
||||||
const config = roomConfig?.config;
|
const config = roomConfig?.config;
|
||||||
|
|
||||||
|
const fullUI = !isOob || (isOob && owner);
|
||||||
|
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
|
|
||||||
if (config.exp) {
|
if (config.exp) {
|
||||||
|
|
@ -76,6 +83,12 @@ export const CallProvider = ({
|
||||||
roomConfig?.tokenConfig?.start_cloud_recording ?? false
|
roomConfig?.tokenConfig?.start_cloud_recording ?? false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
setEnableScreenShare(
|
||||||
|
fullUI &&
|
||||||
|
(roomConfig?.tokenConfig?.enable_screenshare ??
|
||||||
|
roomConfig?.config?.enable_screenshare) &&
|
||||||
|
DailyIframe.supportedBrowser().supportsScreenShare
|
||||||
|
);
|
||||||
};
|
};
|
||||||
updateRoomConfigState();
|
updateRoomConfigState();
|
||||||
}, [state, daily]);
|
}, [state, daily]);
|
||||||
|
|
@ -115,11 +128,13 @@ export const CallProvider = ({
|
||||||
showLocalVideo,
|
showLocalVideo,
|
||||||
roomExp,
|
roomExp,
|
||||||
enableRecording,
|
enableRecording,
|
||||||
|
enableScreenShare,
|
||||||
videoQuality,
|
videoQuality,
|
||||||
setVideoQuality,
|
setVideoQuality,
|
||||||
setBandwidth,
|
setBandwidth,
|
||||||
setRedirectOnLeave,
|
setRedirectOnLeave,
|
||||||
setShowLocalVideo,
|
setShowLocalVideo,
|
||||||
|
setEnableScreenShare,
|
||||||
startCloudRecording,
|
startCloudRecording,
|
||||||
subscribeToTracksAutomatically,
|
subscribeToTracksAutomatically,
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M18 22H6" stroke="currentColor" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M1 2L8 9" stroke="currentColor" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M11 2H23V18H1V12" stroke="currentColor" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M7 2H1V8" stroke="currentColor" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 631 B |
|
|
@ -0,0 +1,17 @@
|
||||||
|
const convert = (keyword) => {
|
||||||
|
return keyword
|
||||||
|
.toString()
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\s+/g, '-')
|
||||||
|
};
|
||||||
|
|
||||||
|
const revert = (keyword) => {
|
||||||
|
return keyword
|
||||||
|
.toString()
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace('-', ' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const slugify = { convert, revert };
|
||||||
Loading…
Reference in New Issue