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)
|
### [🤙 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.
|
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
|
<Component
|
||||||
asides={App.asides}
|
asides={App.asides}
|
||||||
customTrayComponent={App.customTrayComponent}
|
customTrayComponent={App.customTrayComponent}
|
||||||
|
customAppComponent={App.customAppComponent}
|
||||||
{...pageProps}
|
{...pageProps}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
@ -33,5 +34,6 @@ App.propTypes = {
|
||||||
|
|
||||||
App.asides = [];
|
App.asides = [];
|
||||||
App.customTrayComponent = null;
|
App.customTrayComponent = null;
|
||||||
|
App.customAppComponent = null;
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ export default function Index({
|
||||||
isConfigured = false,
|
isConfigured = false,
|
||||||
asides,
|
asides,
|
||||||
customTrayComponent,
|
customTrayComponent,
|
||||||
|
customAppComponent,
|
||||||
}) {
|
}) {
|
||||||
const [roomName, setRoomName] = useState('');
|
const [roomName, setRoomName] = useState('');
|
||||||
const [fetchingToken, setFetchingToken] = useState(false);
|
const [fetchingToken, setFetchingToken] = useState(false);
|
||||||
|
|
@ -103,7 +104,7 @@ export default function Index({
|
||||||
<TracksProvider>
|
<TracksProvider>
|
||||||
<MediaDeviceProvider>
|
<MediaDeviceProvider>
|
||||||
<WaitingRoomProvider>
|
<WaitingRoomProvider>
|
||||||
<App />
|
{customAppComponent || <App />}
|
||||||
</WaitingRoomProvider>
|
</WaitingRoomProvider>
|
||||||
</MediaDeviceProvider>
|
</MediaDeviceProvider>
|
||||||
</TracksProvider>
|
</TracksProvider>
|
||||||
|
|
@ -118,6 +119,7 @@ Index.propTypes = {
|
||||||
domain: PropTypes.string,
|
domain: PropTypes.string,
|
||||||
asides: PropTypes.arrayOf(PropTypes.func),
|
asides: PropTypes.arrayOf(PropTypes.func),
|
||||||
customTrayComponent: PropTypes.node,
|
customTrayComponent: PropTypes.node,
|
||||||
|
customAppComponent: PropTypes.node,
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function getStaticProps() {
|
export async function getStaticProps() {
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,61 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Button from '@dailyjs/shared/components/Button';
|
import Button from '@dailyjs/shared/components/Button';
|
||||||
|
import classNames from 'classnames';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
export const TrayButton = ({ children, label, onClick, orange = false }) => (
|
export const TrayButton = ({
|
||||||
<div className={orange ? 'tray-button orange' : 'tray-button'}>
|
children,
|
||||||
<Button onClick={() => onClick()} variant="dark" size="large-square">
|
label,
|
||||||
{children}
|
onClick,
|
||||||
</Button>
|
bubble = false,
|
||||||
<span>{label}</span>
|
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>{`
|
<style jsx>{`
|
||||||
.tray-button {
|
.tray-button {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.tray-button.orange :global(.button) {
|
.tray-button.orange :global(.button) {
|
||||||
color: var(--secondary-dark);
|
color: var(--secondary-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
.tray-button.bubble::after {
|
||||||
color: white;
|
position: absolute;
|
||||||
font-weight: var(--weight-medium);
|
content: '';
|
||||||
font-size: 12px;
|
top: 10px;
|
||||||
}
|
right: 10px;
|
||||||
`}</style>
|
width: 9px;
|
||||||
</div>
|
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 = {
|
TrayButton.propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
orange: PropTypes.bool,
|
orange: PropTypes.bool,
|
||||||
|
bubble: PropTypes.bool,
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@daily-co/daily-js": "^0.12.0",
|
"@daily-co/daily-js": "^0.12.0",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"no-scroll": "^2.1.1",
|
|
||||||
"debounce": "^1.2.1",
|
"debounce": "^1.2.1",
|
||||||
|
"nanoid": "^3.1.23",
|
||||||
|
"no-scroll": "^2.1.1",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^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 Aside from '@dailyjs/shared/components/Aside';
|
||||||
import { useUIState } from '@dailyjs/shared/contexts/UIStateProvider';
|
import { useUIState } from '@dailyjs/shared/contexts/UIStateProvider';
|
||||||
|
import { useChat } from '../../contexts/ChatProvider';
|
||||||
|
|
||||||
export const CHAT_ASIDE = 'chat';
|
export const CHAT_ASIDE = 'chat';
|
||||||
|
|
||||||
export const ChatAside = () => {
|
export const ChatAside = () => {
|
||||||
const { showAside, setShowAside } = useUIState();
|
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) {
|
if (!showAside || showAside !== CHAT_ASIDE) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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 { TrayButton } from '@dailyjs/shared/components/Tray';
|
||||||
import { useUIState } from '@dailyjs/shared/contexts/UIStateProvider';
|
import { useUIState } from '@dailyjs/shared/contexts/UIStateProvider';
|
||||||
import { ReactComponent as IconChat } from '@dailyjs/shared/icons/chat-md.svg';
|
import { ReactComponent as IconChat } from '@dailyjs/shared/icons/chat-md.svg';
|
||||||
|
import { useChat } from '../../contexts/ChatProvider';
|
||||||
import { CHAT_ASIDE } from '../ChatAside/ChatAside';
|
import { CHAT_ASIDE } from '../ChatAside/ChatAside';
|
||||||
|
|
||||||
export const Tray = () => {
|
export const Tray = () => {
|
||||||
const { toggleAside } = useUIState();
|
const { toggleAside } = useUIState();
|
||||||
|
const { hasNewMessages } = useChat();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TrayButton label="Chat" onClick={() => toggleAside(CHAT_ASIDE)}>
|
<TrayButton
|
||||||
|
label="Chat"
|
||||||
|
bubble={hasNewMessages}
|
||||||
|
onClick={() => {
|
||||||
|
toggleAside(CHAT_ASIDE);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<IconChat />
|
<IconChat />
|
||||||
</TrayButton>
|
</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 React from 'react';
|
||||||
import App from '@dailyjs/basic-call/pages/_app';
|
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';
|
import Tray from '../components/Tray';
|
||||||
|
|
||||||
App.asides = [ChatAside];
|
App.asides = [ChatAside];
|
||||||
|
App.customAppComponent = <AppWithChat />;
|
||||||
App.customTrayComponent = <Tray />;
|
App.customTrayComponent = <Tray />;
|
||||||
|
|
||||||
export default App;
|
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"
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844"
|
||||||
integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==
|
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:
|
native-url@0.3.4:
|
||||||
version "0.3.4"
|
version "0.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.3.4.tgz#29c943172aed86c63cee62c8c04db7f5756661f8"
|
resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.3.4.tgz#29c943172aed86c63cee62c8c04db7f5756661f8"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue