initial commit

This commit is contained in:
Jon 2021-07-13 17:42:29 +01:00
parent 5d68418d81
commit 30e77c985c
17 changed files with 347 additions and 1 deletions

View File

@ -0,0 +1,4 @@
{
"presets": ["next/babel"],
"plugins": ["inline-react-svg"]
}

View File

@ -0,0 +1,41 @@
# Text Chat
![Text Chat](./image.png)
### Live example
**[See it in action here ➡️](https://dailyjs-text-chat.vercel.app)**
---
## What does this demo do?
- Use [sendAppMessage](https://docs.daily.co/reference#%EF%B8%8F-sendappmessage) to send messages
- Listen for incoming messages using the call object `app-message` event
- Extend the basic call demo with a chat provider and aside
- Show a notification bubble on chat tray button when a new message is received
- Demonstrate how to play a sound whenever a message is received
Please note: this demo is not currently mobile optimised
### Getting started
```
# set both DAILY_API_KEY and DAILY_DOMAIN
mv env.example .env.local
yarn
yarn workspace @dailyjs/text-chat dev
```
## How does this example work?
In this example we extend the [basic call demo](../basic-call) with the ability to send chat messages.
We pass a custom tray object, a custom app object (wrapping the original in a new `ChatProvider`) as well as add our `ChatAside` panel. We also symlink both the `public` and `pages/api` folders from the basic call.
In a real world use case you would likely want to implement serverside logic so that participants joining a call can retrieve previously sent messages. This round trip could be done inside of the Chat context.
## Deploy your own on Vercel
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/daily-co/clone-flow?repository-url=https%3A%2F%2Fgithub.com%2Fdaily-demos%2Fexamples.git&env=DAILY_DOMAIN%2CDAILY_API_KEY&envDescription=Your%20Daily%20domain%20and%20API%20key%20can%20be%20found%20on%20your%20account%20dashboard&envLink=https%3A%2F%2Fdashboard.daily.co&project-name=daily-examples&repo-name=daily-examples)

View File

@ -0,0 +1,12 @@
import React from 'react';
import App from '@dailyjs/basic-call/components/App';
import FlyingEmojiOverlay from '../FlyingEmojis/FlyingEmojisOverlay';
export const AppWithEmojis = () => (
<>
<FlyingEmojiOverlay />
<App />
</>
);
export default AppWithEmojis;

View File

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

View File

@ -0,0 +1,153 @@
import React, { useEffect, useRef, useCallback } from 'react';
import { useCallState } from '@dailyjs/shared/contexts/CallProvider';
const EMOJI_MAP = {
fire: '🔥',
squid: '🦑',
};
export const FlyingEmojisOverlay = () => {
const { callObject } = useCallState();
const overlayRef = useRef();
// -- Handlers
const handleRemoveFlyingEmoji = useCallback((node) => {
if (!overlayRef.current) return;
overlayRef.current.removeChild(node);
}, []);
const handleNewFlyingEmoji = useCallback(
(emoji) => {
if (!overlayRef.current) {
return;
}
console.log(`⭐ New flying emoji: ${emoji}`);
const node = document.createElement('div');
node.appendChild(document.createTextNode(EMOJI_MAP[emoji]));
node.className =
Math.random() * 1 > 0.5 ? 'emoji wiggle-1' : 'emoji wiggle-2';
node.style.transform = `rotate(${-30 + Math.random() * 60}deg)`;
node.style.left = `${Math.random() * 100}%`;
node.src = '';
overlayRef.current.appendChild(node);
node.addEventListener('animationend', (e) =>
handleRemoveFlyingEmoji(e.target)
);
},
[handleRemoveFlyingEmoji]
);
// -- Effects
// Listen for new app messages and show new flying emojis
useEffect(() => {
if (!callObject) {
return false;
}
console.log(`⭐ Listening for flying emojis...`);
callObject.on('app-message', handleNewFlyingEmoji);
return () => callObject.off('app-message', handleNewFlyingEmoji);
}, [callObject, handleNewFlyingEmoji]);
// Listen to window events to show local user emojis
useEffect(() => {
if (!callObject) {
return false;
}
function handleIncomingEmoji(e) {
const { emoji } = e.detail;
console.log(`⭐ Sending flying emoji: ${emoji}`);
if (emoji) {
callObject.sendAppMessage({ emoji }, '*');
handleNewFlyingEmoji(emoji);
}
}
window.addEventListener('reaction_added', handleIncomingEmoji);
return () =>
window.removeEventListener('reaction_added', handleIncomingEmoji);
}, [callObject, handleNewFlyingEmoji]);
// Remove all event listeners on unmount to prevent console warnings
useEffect(
() => () =>
overlayRef.current.childNodes.forEach((n) =>
n.removeEventListener('animationend', handleRemoveFlyingEmoji)
),
[handleRemoveFlyingEmoji]
);
return (
<div className="flying-emojis" ref={overlayRef}>
<style jsx>{`
.flying-emojis {
position: fixed;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
overflow: hidden;
pointer-events: none;
user-select: none;
z-index: 99;
}
.flying-emojis :global(.emoji) {
position: absolute;
bottom: 0px;
left: 50%;
font-size: 48px;
line-height: 1;
width: 48px;
height: 48px;
}
.flying-emojis :global(.emoji.wiggle-1) {
animation: emerge 3s forwards,
wiggle-1 1s ease-in-out infinite alternate;
}
.flying-emojis :global(.emoji.wiggle-2) {
animation: emerge 3s forwards,
wiggle-2 1s ease-in-out infinite alternate;
}
@keyframes emerge {
to {
bottom: 300px;
opacity: 0;
}
}
@keyframes wiggle-1 {
from {
margin-left: -50px;
}
to {
margin-left: 50px;
}
}
@keyframes wiggle-2 {
from {
margin-left: 50px;
}
to {
margin-left: -50px;
}
}
`}</style>
</div>
);
};
export default FlyingEmojisOverlay;

View File

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

View File

@ -0,0 +1,44 @@
import React, { useState } from 'react';
import Button from '@dailyjs/shared/components/Button';
import { TrayButton } from '@dailyjs/shared/components/Tray';
import { ReactComponent as IconStar } from '@dailyjs/shared/icons/star-md.svg';
export const Tray = () => {
const [showEmojis, setShowEmojis] = useState(false);
function sendEmoji(emoji) {
// Dispatch custom event here so the local user can see their own emoji
window.dispatchEvent(
new CustomEvent('reaction_added', { detail: { emoji } })
);
setShowEmojis(false);
}
return (
<div>
{showEmojis && (
<div className="emojis">
<Button onClick={() => sendEmoji('fire')}>A</Button>
<Button onClick={() => sendEmoji('squid')}>B</Button>
</div>
)}
<TrayButton label="Emoji" onClick={() => setShowEmojis(!showEmojis)}>
<IconStar />
</TrayButton>
<style jsx>{`
position: relative;
.emojis {
position: absolute;
display: flex;
top: -50px;
z-index: 99;
}
`}</style>
</div>
);
};
export default Tray;

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

View File

@ -0,0 +1,13 @@
const withPlugins = require('next-compose-plugins');
const withTM = require('next-transpile-modules')([
'@dailyjs/shared',
'@dailyjs/basic-call',
]);
const packageJson = require('./package.json');
module.exports = withPlugins([withTM], {
env: {
PROJECT_TITLE: packageJson.description,
},
});

View File

@ -0,0 +1,25 @@
{
"name": "@dailyjs/flying-emojis",
"description": "Basic Call + Flying Emojis",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@dailyjs/basic-call": "*",
"@dailyjs/shared": "*",
"next": "^11.0.0",
"pluralize": "^8.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"babel-plugin-module-resolver": "^4.1.0",
"next-compose-plugins": "^2.2.1",
"next-transpile-modules": "^8.0.0"
}
}

View File

@ -0,0 +1,9 @@
import React from 'react';
import App from '@dailyjs/basic-call/pages/_app';
import AppWithEmojis from '../components/App';
import Tray from '../components/Tray';
App.customAppComponent = <AppWithEmojis />;
App.customTrayComponent = <Tray />;
export default App;

View File

@ -0,0 +1 @@
../../basic-call/pages/api

View File

@ -0,0 +1,13 @@
import Index from '@dailyjs/basic-call/pages';
import getDemoProps from '@dailyjs/shared/lib/demoProps';
export async function getStaticProps() {
const defaultProps = getDemoProps();
// Pass through domain as prop
return {
props: defaultProps,
};
}
export default Index;

View File

@ -0,0 +1 @@
../basic-call/public

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g stroke-linecap="round" stroke-linejoin="round" stroke-width="2" fill="none" stroke="currentColor"><polygon points="12,2.49 15.09,8.75 22,9.754 17,14.628 18.18,21.51 12,18.262 5.82,21.51 7,14.628 2,9.754 8.91,8.75 "></polygon></g></svg>

After

Width:  |  Height:  |  Size: 321 B

View File

@ -264,6 +264,11 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@popperjs/core@^2.9.2":
version "2.9.2"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353"
integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==
"@rushstack/eslint-patch@^1.0.6":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.0.6.tgz#023d72a5c4531b4ce204528971700a78a85a0c50"
@ -2204,7 +2209,7 @@ lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
loose-envify@^1.1.0, loose-envify@^1.4.0:
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@ -2897,6 +2902,11 @@ react-dom@^17.0.2:
object-assign "^4.1.1"
scheduler "^0.20.2"
react-fast-compare@^3.0.1:
version "3.2.0"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
react-is@17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
@ -2907,6 +2917,14 @@ react-is@^16.8.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-popper@^2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.2.5.tgz#1214ef3cec86330a171671a4fbcbeeb65ee58e96"
integrity sha512-kxGkS80eQGtLl18+uig1UIf9MKixFSyPxglsgLBxlYnyDf65BiY9B3nZSc6C9XUNDgStROB0fMQlTEz1KxGddw==
dependencies:
react-fast-compare "^3.0.1"
warning "^4.0.2"
react-refresh@0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
@ -3616,6 +3634,13 @@ vm-browserify@1.1.2, vm-browserify@^1.0.1:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
warning@^4.0.2:
version "4.0.3"
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
dependencies:
loose-envify "^1.0.0"
watchpack@2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.1.1.tgz#e99630550fca07df9f90a06056987baa40a689c7"