diff --git a/dailyjs/flying-emojis/.babelrc b/dailyjs/flying-emojis/.babelrc new file mode 100644 index 0000000..a6f4434 --- /dev/null +++ b/dailyjs/flying-emojis/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["next/babel"], + "plugins": ["inline-react-svg"] +} diff --git a/dailyjs/flying-emojis/README.md b/dailyjs/flying-emojis/README.md new file mode 100644 index 0000000..6703492 --- /dev/null +++ b/dailyjs/flying-emojis/README.md @@ -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) diff --git a/dailyjs/flying-emojis/components/App/App.js b/dailyjs/flying-emojis/components/App/App.js new file mode 100644 index 0000000..e006832 --- /dev/null +++ b/dailyjs/flying-emojis/components/App/App.js @@ -0,0 +1,12 @@ +import React from 'react'; +import App from '@dailyjs/basic-call/components/App'; +import FlyingEmojiOverlay from '../FlyingEmojis/FlyingEmojisOverlay'; + +export const AppWithEmojis = () => ( + <> + + + +); + +export default AppWithEmojis; diff --git a/dailyjs/flying-emojis/components/App/index.js b/dailyjs/flying-emojis/components/App/index.js new file mode 100644 index 0000000..8887a4b --- /dev/null +++ b/dailyjs/flying-emojis/components/App/index.js @@ -0,0 +1 @@ +export { AppWithEmojis as default } from './App'; diff --git a/dailyjs/flying-emojis/components/FlyingEmojis/FlyingEmojisOverlay.js b/dailyjs/flying-emojis/components/FlyingEmojis/FlyingEmojisOverlay.js new file mode 100644 index 0000000..8060e59 --- /dev/null +++ b/dailyjs/flying-emojis/components/FlyingEmojis/FlyingEmojisOverlay.js @@ -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 ( +
+ +
+ ); +}; + +export default FlyingEmojisOverlay; diff --git a/dailyjs/flying-emojis/components/FlyingEmojis/index.js b/dailyjs/flying-emojis/components/FlyingEmojis/index.js new file mode 100644 index 0000000..6e98cc3 --- /dev/null +++ b/dailyjs/flying-emojis/components/FlyingEmojis/index.js @@ -0,0 +1,2 @@ +export { FlyingEmojisOverlay } from './FlyingEmojisOverlay'; +export { FlyingEmojisOverlay as default } from './FlyingEmojisOverlay'; diff --git a/dailyjs/flying-emojis/components/Tray/Tray.js b/dailyjs/flying-emojis/components/Tray/Tray.js new file mode 100644 index 0000000..7b0edda --- /dev/null +++ b/dailyjs/flying-emojis/components/Tray/Tray.js @@ -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 ( +
+ {showEmojis && ( +
+ + +
+ )} + setShowEmojis(!showEmojis)}> + + + +
+ ); +}; + +export default Tray; diff --git a/dailyjs/flying-emojis/components/Tray/index.js b/dailyjs/flying-emojis/components/Tray/index.js new file mode 100644 index 0000000..100bcc8 --- /dev/null +++ b/dailyjs/flying-emojis/components/Tray/index.js @@ -0,0 +1 @@ +export { Tray as default } from './Tray'; diff --git a/dailyjs/flying-emojis/image.png b/dailyjs/flying-emojis/image.png new file mode 100644 index 0000000..cca9672 Binary files /dev/null and b/dailyjs/flying-emojis/image.png differ diff --git a/dailyjs/flying-emojis/next.config.js b/dailyjs/flying-emojis/next.config.js new file mode 100644 index 0000000..9a0a6ee --- /dev/null +++ b/dailyjs/flying-emojis/next.config.js @@ -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, + }, +}); diff --git a/dailyjs/flying-emojis/package.json b/dailyjs/flying-emojis/package.json new file mode 100644 index 0000000..a084b01 --- /dev/null +++ b/dailyjs/flying-emojis/package.json @@ -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" + } +} diff --git a/dailyjs/flying-emojis/pages/_app.js b/dailyjs/flying-emojis/pages/_app.js new file mode 100644 index 0000000..16a9742 --- /dev/null +++ b/dailyjs/flying-emojis/pages/_app.js @@ -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 = ; +App.customTrayComponent = ; + +export default App; diff --git a/dailyjs/flying-emojis/pages/api b/dailyjs/flying-emojis/pages/api new file mode 120000 index 0000000..999f604 --- /dev/null +++ b/dailyjs/flying-emojis/pages/api @@ -0,0 +1 @@ +../../basic-call/pages/api \ No newline at end of file diff --git a/dailyjs/flying-emojis/pages/index.js b/dailyjs/flying-emojis/pages/index.js new file mode 100644 index 0000000..d25e77e --- /dev/null +++ b/dailyjs/flying-emojis/pages/index.js @@ -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; diff --git a/dailyjs/flying-emojis/public b/dailyjs/flying-emojis/public new file mode 120000 index 0000000..33a6e67 --- /dev/null +++ b/dailyjs/flying-emojis/public @@ -0,0 +1 @@ +../basic-call/public \ No newline at end of file diff --git a/dailyjs/shared/icons/star-md.svg b/dailyjs/shared/icons/star-md.svg new file mode 100644 index 0000000..68c299b --- /dev/null +++ b/dailyjs/shared/icons/star-md.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index deb3d12..6c4dc2f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"