diff --git a/AppExamples/side-pane-example/.gitignore b/AppExamples/side-pane-example/.gitignore new file mode 100644 index 0000000..cf5404a --- /dev/null +++ b/AppExamples/side-pane-example/.gitignore @@ -0,0 +1,26 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +package-lock.json +yarn.lock diff --git a/AppExamples/side-pane-example/README.md b/AppExamples/side-pane-example/README.md new file mode 100644 index 0000000..47faa0d --- /dev/null +++ b/AppExamples/side-pane-example/README.md @@ -0,0 +1,105 @@ +# Basic ECP explicit mode example with React + +## Context + +The aim of this application is to show how to use ECP in a React environment. This very simple application only adds an email input field and an "open" button to open an ECP chat window with the given user. +It uses the ECP explicit rendering mode so it can keep ECP up and running in a "master" window even though the actual chat window (child) is not created yet or has been closed. For demo purposes, again, the master window will remain open at the bottom left of the screen to show that this one is not used and is only meant to keep the ECP logic running. Then, everytime the user clicks on the open button, we create a new div that will embed a new child chat with the given user. + +## HOWTO + +This application has been done in very few steps that we resume below: + +- Create the application using the Create React App script (see reference at the end of this file) +- Add the main ECP container in the index.html file so it's always on screen (you can of course hide in a real application): +```HTML +
+``` +- Load the ECP SDK, in index.tsx, before even rendering the React application: +```Typescript +const loadSdk = ( + ): Promise => { + return new Promise((resolve) => { + const sdkScriptNode = document.createElement('script'); + sdkScriptNode.src = `https://${DEFAULT_ORIGIN}/embed/sdk.js`; + sdkScriptNode.id = 'symphony-ecm-sdk'; + sdkScriptNode.setAttribute('render', 'explicit'); + (window as any).renderRoom = () => { + (window as any).symphony + .render('symphony-ecm', { + // Add initial ECP parameters here + showTitle: false, + ecpLoginPopup: true + }).then(resolve); + }; + sdkScriptNode.setAttribute('data-onload', 'renderRoom'); // Will call window.renderRoom once initialized + document.body.appendChild(sdkScriptNode); + }); + }; + +root.render( + + + +); +``` +Here the `loadSdk` function returns a `Promise` that resolves when ECP is ready to be displayed (When the SDK `render` function resolves). This promise is passed to the `App` component as `Props` in case the app want to know when the chat is ready to be displayed. This is not mandatory and we don't use in our App component in this example. +- Add your logic to know when to open the chat, where and with which options (streamId / userIds) +In our case, it's just a simple input and an open button. The input button lets the user enter an email address, the open button opens the side panel containing the chat. +See the details if needed in the `App` component in `App.tsx`. +- The `Chat` component in the `App.tsx` file holds the logic to open the chat: +```Typescript +export interface ChatProps { + userIds: string[]; +} + +export const Chat = (props: ChatProps) => { + const [chatId, setChatId] = React.useState(`pane-chat-${Date.now()}`); + React.useEffect(() => { + (window as any).symphony.startRoom(props.userIds, `#${chatId}`) + }, [chatId]); + return ( +
+

Symphony chat with {props.userIds.join(',')}

+
+
+ ) +} +``` +As you can see, the component is very simple. All it does is adding a div to the DOM that will contain the ECP chat window. **ECP will keep track of all the containers you pass it, so if you give ECP an existing container, it will not create the iframe but just update its content**. In our case the container is deleted everytime the panel is closed, so we need to create a unique ID so ECP recreated an iframe everytime we open a panel: `pane-chat` suffixed with the timestamp. +Once our div is ready, all we need to do is to tell ECP to `startRoom` with the given userIds: +```Typescript +window.symphony.startRoom(props.userIds, `#${chatId}`) +``` + + +## Getting Started with Create React App + +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +### Available Scripts + +In the project directory, you can run: + +#### `yarn start` + +Runs the app in the development mode.\ +Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.\ +You will also see any lint errors in the console. + +#### `yarn build` + +Builds the app for production to the `build` folder.\ +It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.\ +Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). diff --git a/AppExamples/side-pane-example/package.json b/AppExamples/side-pane-example/package.json new file mode 100644 index 0000000..7c8c55d --- /dev/null +++ b/AppExamples/side-pane-example/package.json @@ -0,0 +1,44 @@ +{ + "name": "side-pane-example", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^13.5.0", + "@types/jest": "^27.5.2", + "@types/node": "^16.18.3", + "@types/react": "^18.0.25", + "@types/react-dom": "^18.0.9", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-scripts": "5.0.1", + "react-side-pane": "^2.4.6", + "typescript": "^4.9.3", + "web-vitals": "^2.1.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/AppExamples/side-pane-example/public/favicon.ico b/AppExamples/side-pane-example/public/favicon.ico new file mode 100644 index 0000000..a11777c Binary files /dev/null and b/AppExamples/side-pane-example/public/favicon.ico differ diff --git a/AppExamples/side-pane-example/public/index.html b/AppExamples/side-pane-example/public/index.html new file mode 100644 index 0000000..1ba9d3a --- /dev/null +++ b/AppExamples/side-pane-example/public/index.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + React App + + + +
+
+ + + diff --git a/AppExamples/side-pane-example/public/logo192.png b/AppExamples/side-pane-example/public/logo192.png new file mode 100644 index 0000000..fc44b0a Binary files /dev/null and b/AppExamples/side-pane-example/public/logo192.png differ diff --git a/AppExamples/side-pane-example/public/logo512.png b/AppExamples/side-pane-example/public/logo512.png new file mode 100644 index 0000000..a4e47a6 Binary files /dev/null and b/AppExamples/side-pane-example/public/logo512.png differ diff --git a/AppExamples/side-pane-example/public/manifest.json b/AppExamples/side-pane-example/public/manifest.json new file mode 100644 index 0000000..080d6c7 --- /dev/null +++ b/AppExamples/side-pane-example/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/AppExamples/side-pane-example/public/robots.txt b/AppExamples/side-pane-example/public/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/AppExamples/side-pane-example/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/AppExamples/side-pane-example/src/App.css b/AppExamples/side-pane-example/src/App.css new file mode 100644 index 0000000..1ee56b6 --- /dev/null +++ b/AppExamples/side-pane-example/src/App.css @@ -0,0 +1,50 @@ +.App { + text-align: center; +} + +.App-logo { + height: 40vmin; + pointer-events: none; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #61dafb; +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.symphony-pane-chat { + height: 100%; + flex: 1; +} + +.panel-container { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; +} diff --git a/AppExamples/side-pane-example/src/App.tsx b/AppExamples/side-pane-example/src/App.tsx new file mode 100644 index 0000000..73e4b8c --- /dev/null +++ b/AppExamples/side-pane-example/src/App.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import logo from './logo.svg'; +import './App.css'; +import { SidePane } from "react-side-pane"; + +export interface AppProps { + chatReady: Promise; +} + +export const App = (props: AppProps) => { + const [open, dispatchOpen] = React.useReducer((_open) => !_open, false); + const [name, setName] = React.useState(''); + const handleNameChange = (event: React.ChangeEvent) => { + setName(event.target.value) + } + return ( +
+
+ logo +

+ Edit src/App.tsx and save to reload. +

+ + Learn React + + + +
+ + <> + + + +
+ ); +} + +export interface ChatProps { + userIds: string[]; +} + +export const Chat = (props: ChatProps) => { + const [chatId, setChatId] = React.useState(`pane-chat-${Date.now()}`); + React.useEffect(() => { + (window as any).symphony.startRoom(props.userIds, `#${chatId}`) + }, [chatId]); + return ( +
+

Symphony chat with {props.userIds.join(',')}

+
+
+ ) +} + +export default App; diff --git a/AppExamples/side-pane-example/src/index.css b/AppExamples/side-pane-example/src/index.css new file mode 100644 index 0000000..aa1d5d5 --- /dev/null +++ b/AppExamples/side-pane-example/src/index.css @@ -0,0 +1,22 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} + + +.ecp-main-hidden-frame { + position: fixed; + height: 33vh; + left: 0px; + bottom: 0; + width: 33vw; +} diff --git a/AppExamples/side-pane-example/src/index.tsx b/AppExamples/side-pane-example/src/index.tsx new file mode 100644 index 0000000..d737899 --- /dev/null +++ b/AppExamples/side-pane-example/src/index.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import App from './App'; +import reportWebVitals from './reportWebVitals'; + +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); + +const DEFAULT_ORIGIN: string = "corporate.symphony.com"; + +const loadSdk = ( + ): Promise => { + return new Promise((resolve) => { + const sdkScriptNode = document.createElement('script'); + sdkScriptNode.src = `https://${DEFAULT_ORIGIN}/embed/sdk.js`; + sdkScriptNode.id = 'symphony-ecm-sdk'; + sdkScriptNode.setAttribute('render', 'explicit'); + (window as any).renderRoom = () => { + (window as any).symphony + .render('symphony-ecm', { + showTitle: false, + ecpLoginPopup: true + }).then(() => {resolve();}); + }; + sdkScriptNode.setAttribute('data-onload', 'renderRoom'); // Will call window.renderRoom once initialized + document.body.appendChild(sdkScriptNode); + }); + }; + +root.render( + + + +); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); diff --git a/AppExamples/side-pane-example/src/logo.svg b/AppExamples/side-pane-example/src/logo.svg new file mode 100644 index 0000000..9dfc1c0 --- /dev/null +++ b/AppExamples/side-pane-example/src/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/AppExamples/side-pane-example/src/react-app-env.d.ts b/AppExamples/side-pane-example/src/react-app-env.d.ts new file mode 100644 index 0000000..6431bc5 --- /dev/null +++ b/AppExamples/side-pane-example/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/AppExamples/side-pane-example/src/reportWebVitals.ts b/AppExamples/side-pane-example/src/reportWebVitals.ts new file mode 100644 index 0000000..49a2a16 --- /dev/null +++ b/AppExamples/side-pane-example/src/reportWebVitals.ts @@ -0,0 +1,15 @@ +import { ReportHandler } from 'web-vitals'; + +const reportWebVitals = (onPerfEntry?: ReportHandler) => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } +}; + +export default reportWebVitals; diff --git a/AppExamples/side-pane-example/src/setupTests.ts b/AppExamples/side-pane-example/src/setupTests.ts new file mode 100644 index 0000000..8f2609b --- /dev/null +++ b/AppExamples/side-pane-example/src/setupTests.ts @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom'; diff --git a/AppExamples/side-pane-example/tsconfig.json b/AppExamples/side-pane-example/tsconfig.json new file mode 100644 index 0000000..a273b0c --- /dev/null +++ b/AppExamples/side-pane-example/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": [ + "src" + ] +}