added chat provider and app override
This commit is contained in:
parent
9accc44f4f
commit
d7e963b1ec
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -1,37 +1,61 @@
|
|||
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'}>
|
||||
<Button onClick={() => onClick()} variant="dark" size="large-square">
|
||||
{children}
|
||||
</Button>
|
||||
<span>{label}</span>
|
||||
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>
|
||||
<span>{label}</span>
|
||||
|
||||
<style jsx>{`
|
||||
.tray-button {
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
<style jsx>{`
|
||||
.tray-button {
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tray-button.orange :global(.button) {
|
||||
color: var(--secondary-dark);
|
||||
}
|
||||
.tray-button.orange :global(.button) {
|
||||
color: var(--secondary-dark);
|
||||
}
|
||||
|
||||
span {
|
||||
color: white;
|
||||
font-weight: var(--weight-medium);
|
||||
font-size: 12px;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
.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);
|
||||
font-size: 12px;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
TrayButton.propTypes = {
|
||||
children: PropTypes.node,
|
||||
onClick: PropTypes.func,
|
||||
orange: PropTypes.bool,
|
||||
bubble: PropTypes.bool,
|
||||
label: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { AppWithChat as default } from './App';
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue