added chat provider and app override

This commit is contained in:
J Taylor 2021-06-23 14:42:49 +01:00
parent 9accc44f4f
commit d7e963b1ec
13 changed files with 3137 additions and 28 deletions

View File

@ -29,3 +29,7 @@ These examples re-use some common components, contexts, hooks and libraries. The
### [🤙 Basic call](./basic-call)
The basic call demo (derived from our prebuilt UI codebase) demonstrates how to create a video and audio call using Call Object mode.
### [💬 Text chat](./text-chat)
Send messages to other participants using sendAppMessage

View File

@ -15,6 +15,7 @@ function App({ Component, pageProps }) {
<Component
asides={App.asides}
customTrayComponent={App.customTrayComponent}
customAppComponent={App.customAppComponent}
{...pageProps}
/>
</>
@ -33,5 +34,6 @@ App.propTypes = {
App.asides = [];
App.customTrayComponent = null;
App.customAppComponent = null;
export default App;

View File

@ -22,6 +22,7 @@ export default function Index({
isConfigured = false,
asides,
customTrayComponent,
customAppComponent,
}) {
const [roomName, setRoomName] = useState('');
const [fetchingToken, setFetchingToken] = useState(false);
@ -103,7 +104,7 @@ export default function Index({
<TracksProvider>
<MediaDeviceProvider>
<WaitingRoomProvider>
<App />
{customAppComponent || <App />}
</WaitingRoomProvider>
</MediaDeviceProvider>
</TracksProvider>
@ -118,6 +119,7 @@ Index.propTypes = {
domain: PropTypes.string,
asides: PropTypes.arrayOf(PropTypes.func),
customTrayComponent: PropTypes.node,
customAppComponent: PropTypes.node,
};
export async function getStaticProps() {

View File

@ -1,9 +1,18 @@
import React from 'react';
import Button from '@dailyjs/shared/components/Button';
import classNames from 'classnames';
import PropTypes from 'prop-types';
export const TrayButton = ({ children, label, onClick, orange = false }) => (
<div className={orange ? 'tray-button orange' : 'tray-button'}>
export const TrayButton = ({
children,
label,
onClick,
bubble = false,
orange = false,
}) => {
const cx = classNames('tray-button', { orange, bubble });
return (
<div className={cx}>
<Button onClick={() => onClick()} variant="dark" size="large-square">
{children}
</Button>
@ -13,12 +22,25 @@ export const TrayButton = ({ children, label, onClick, orange = false }) => (
.tray-button {
text-align: center;
user-select: none;
position: relative;
}
.tray-button.orange :global(.button) {
color: var(--secondary-dark);
}
.tray-button.bubble::after {
position: absolute;
content: '';
top: 10px;
right: 10px;
width: 9px;
height: 9px;
background: var(--green-default);
border-radius: 50%;
z-index: 99;
}
span {
color: white;
font-weight: var(--weight-medium);
@ -27,11 +49,13 @@ export const TrayButton = ({ children, label, onClick, orange = false }) => (
`}</style>
</div>
);
};
TrayButton.propTypes = {
children: PropTypes.node,
onClick: PropTypes.func,
orange: PropTypes.bool,
bubble: PropTypes.bool,
label: PropTypes.string.isRequired,
};

View File

@ -6,8 +6,9 @@
"dependencies": {
"@daily-co/daily-js": "^0.12.0",
"classnames": "^2.3.1",
"no-scroll": "^2.1.1",
"debounce": "^1.2.1",
"nanoid": "^3.1.23",
"no-scroll": "^2.1.1",
"prop-types": "^15.7.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",

View File

@ -0,0 +1,13 @@
import React from 'react';
import App from '@dailyjs/basic-call/components/App';
import { ChatProvider } from '../../contexts/ChatProvider';
// Extend our basic call app component with the chat context
export const AppWithChat = () => (
<ChatProvider>
<App />
</ChatProvider>
);
export default AppWithChat;

View File

@ -0,0 +1 @@
export { AppWithChat as default } from './App';

View File

@ -1,18 +1,50 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import Aside from '@dailyjs/shared/components/Aside';
import { useUIState } from '@dailyjs/shared/contexts/UIStateProvider';
import { useChat } from '../../contexts/ChatProvider';
export const CHAT_ASIDE = 'chat';
export const ChatAside = () => {
const { showAside, setShowAside } = useUIState();
const { sendMessage, chatHistory, setHasNewMessages } = useChat();
const [newMessage, setNewMessage] = useState('');
useEffect(() => {
// Clear out any new message otifications if we're showing the chat screen
if (showAside === CHAT_ASIDE) {
setHasNewMessages(false);
}
}, [showAside, chatHistory.length, setHasNewMessages]);
if (!showAside || showAside !== CHAT_ASIDE) {
return null;
}
return (
<Aside onClose={() => setShowAside(false)}>Hello I am teh chat aside</Aside>
<Aside onClose={() => setShowAside(false)}>
{chatHistory.map((chatItem) => (
<div key={chatItem.id}>
{chatItem.sender} - {chatItem.message}
</div>
))}
<hr />
<input
type="text"
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
/>
<button
type="button"
disabled={!newMessage}
onClick={() => {
sendMessage(newMessage);
setNewMessage('');
}}
>
Send a test message
</button>
</Aside>
);
};

View File

@ -3,14 +3,22 @@ import React from 'react';
import { TrayButton } from '@dailyjs/shared/components/Tray';
import { useUIState } from '@dailyjs/shared/contexts/UIStateProvider';
import { ReactComponent as IconChat } from '@dailyjs/shared/icons/chat-md.svg';
import { useChat } from '../../contexts/ChatProvider';
import { CHAT_ASIDE } from '../ChatAside/ChatAside';
export const Tray = () => {
const { toggleAside } = useUIState();
const { hasNewMessages } = useChat();
return (
<>
<TrayButton label="Chat" onClick={() => toggleAside(CHAT_ASIDE)}>
<TrayButton
label="Chat"
bubble={hasNewMessages}
onClick={() => {
toggleAside(CHAT_ASIDE);
}}
>
<IconChat />
</TrayButton>
</>

View File

@ -0,0 +1,92 @@
import React, {
createContext,
useCallback,
useContext,
useEffect,
useState,
} from 'react';
import { useCallState } from '@dailyjs/shared/contexts/CallProvider';
import { nanoid } from 'nanoid';
import PropTypes from 'prop-types';
export const ChatContext = createContext();
export const ChatProvider = ({ children }) => {
const { callObject } = useCallState();
const [chatHistory, setChatHistory] = useState([]);
const [hasNewMessages, setHasNewMessages] = useState(false);
const handleNewMessage = useCallback(
(e) => {
const participants = callObject.participants();
const sender = participants[e.fromId].user_name
? participants[e.fromId].user_name
: 'Guest';
setChatHistory((oldState) => [
...oldState,
{ sender, message: e.data.message, id: nanoid() },
]);
setHasNewMessages(true);
// Play notification here...
},
[callObject]
);
const sendMessage = useCallback(
(message) => {
if (!callObject) {
return false;
}
console.log('💬 Sending app message');
callObject.sendAppMessage({ message }, '*');
// Get the sender (local participant) name
const sender = callObject.participants().local.user_name
? callObject.participants().local.user_name
: 'Guest';
// Update local chat history
return setChatHistory((oldState) => [
...oldState,
{ sender, message, id: nanoid() },
]);
},
[callObject]
);
useEffect(() => {
if (!callObject) {
return false;
}
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,
hasNewMessages,
setHasNewMessages,
}}
>
{children}
</ChatContext.Provider>
);
};
ChatProvider.propTypes = {
children: PropTypes.node,
};
export const useChat = () => useContext(ChatContext);

View File

@ -1,10 +1,12 @@
import React from 'react';
import App from '@dailyjs/basic-call/pages/_app';
import AppWithChat from '../components/App';
import ChatAside from '../components/ChatAside/ChatAside';
import ChatAside from '../components/ChatAside';
import Tray from '../components/Tray';
App.asides = [ChatAside];
App.customAppComponent = <AppWithChat />;
App.customTrayComponent = <Tray />;
export default App;

2923
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2302,6 +2302,11 @@ nanoid@^3.1.22:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844"
integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==
nanoid@^3.1.23:
version "3.1.23"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81"
integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==
native-url@0.3.4:
version "0.3.4"
resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.3.4.tgz#29c943172aed86c63cee62c8c04db7f5756661f8"