├── .env.development
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── custom.md
│ └── feature_request.md
└── workflows
│ ├── addToProject.yml
│ └── build-deploy.yml
├── .gitignore
├── .prettierrc.json
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── docs
├── 220218_PrototypeFundDemoWeek_Blogpost.md
├── ABOUT.md
├── BMBF-Logo.svg
├── BMBF_Logo_new.jpg
├── BMBF_sponsored_by_en.jpg
├── CODE_OF_CONDUCT.md
├── CONTRIBUTION.md
├── Chatmosphere.png
├── Chatmosphere_PrivateConversation.gif
├── INSTALL.md
├── OKFD-Logo.svg
├── OpenDesignChatmosphere.png
├── PF-Logo.svg
├── chatmosphere.gif
├── github-screenshot.png
└── playwave_chatmosphere.png
├── package-lock.json
├── package.json
├── public
├── .htaccess
├── favicon.ico
├── index.html
├── libs
│ ├── jquery-2.1.1.js
│ ├── jquery-2.1.1.min.js
│ ├── jquery-2.1.1.min.map
│ ├── jquery-3.5.1.min.js
│ ├── lib-jitsi-meet.min-135eed5.js
│ ├── lib-jitsi-meet.min.js
│ ├── lib-jitsi-meet.min.map
│ ├── strophe
│ │ ├── strophe.disco.min.js
│ │ ├── strophe.js
│ │ └── strophe.min.js
│ └── umd
│ │ ├── lib-jitsi-meet.e2ee-worker.js
│ │ ├── lib-jitsi-meet.min.js
│ │ ├── lib-jitsi-meet.min.js.LICENSE.txt
│ │ └── lib-jitsi-meet.min.map
├── manifest.json
└── robots.txt
├── src
├── App.test.tsx
├── App.tsx
├── addons
│ ├── Chat
│ │ └── Chat.tsx
│ ├── Screenshare
│ │ ├── ScreenshareButton
│ │ │ └── ScreenshareButton.tsx
│ │ └── components
│ │ │ └── LocalDesktop.tsx
│ ├── Stage
│ │ ├── Stage.tsx
│ │ └── components
│ │ │ ├── ResizeControl.tsx
│ │ │ ├── StageAudio.tsx
│ │ │ ├── StageButton.tsx
│ │ │ ├── StageUser.tsx
│ │ │ ├── StageUsers.tsx
│ │ │ └── StageVideo.tsx
│ └── addonsStore.ts
├── assets
│ ├── call.svg
│ ├── fonts
│ │ ├── inter-v7
│ │ │ ├── Inter-Regular.woff
│ │ │ ├── Inter-Regular.woff2
│ │ │ ├── LICENSE.txt
│ │ │ └── inter-v7-latin-regular.ttf
│ │ ├── roboto-v20-latin
│ │ │ ├── roboto-v20-latin-500.eot
│ │ │ ├── roboto-v20-latin-500.svg
│ │ │ ├── roboto-v20-latin-500.ttf
│ │ │ ├── roboto-v20-latin-500.woff
│ │ │ ├── roboto-v20-latin-500.woff2
│ │ │ ├── roboto-v20-latin-700.eot
│ │ │ ├── roboto-v20-latin-700.svg
│ │ │ ├── roboto-v20-latin-700.ttf
│ │ │ ├── roboto-v20-latin-700.woff
│ │ │ ├── roboto-v20-latin-700.woff2
│ │ │ ├── roboto-v20-latin-regular.eot
│ │ │ ├── roboto-v20-latin-regular.svg
│ │ │ ├── roboto-v20-latin-regular.ttf
│ │ │ ├── roboto-v20-latin-regular.woff
│ │ │ └── roboto-v20-latin-regular.woff2
│ │ └── space-grotesk-v2
│ │ │ ├── AUTHORS.txt
│ │ │ ├── CONTRIBUTORS.txt
│ │ │ ├── OFL.txt
│ │ │ ├── SpaceGrotesk-Bold.ttf
│ │ │ ├── SpaceGrotesk-Bold.woff
│ │ │ ├── SpaceGrotesk-Bold.woff2
│ │ │ ├── SpaceGrotesk-Medium.ttf
│ │ │ ├── SpaceGrotesk-Medium.woff
│ │ │ ├── SpaceGrotesk-Medium.woff2
│ │ │ ├── SpaceGrotesk-Regular.ttf
│ │ │ ├── SpaceGrotesk-Regular.woff
│ │ │ └── SpaceGrotesk-Regular.woff2
│ ├── icons
│ │ ├── ArrowUp.js
│ │ ├── Camera.js
│ │ ├── CameraOff.js
│ │ ├── ChatFilledIcon.js
│ │ ├── ChatIcon.js
│ │ ├── MicIcon.js
│ │ ├── MicOff.js
│ │ ├── MoreVertical.js
│ │ ├── ScreenShare.js
│ │ ├── SettingsIcon.js
│ │ ├── StageIcon.js
│ │ ├── StageIcon.svg
│ │ ├── UserFilled.js
│ │ └── UserIcon.js
│ ├── kissingCat.svg
│ ├── loveCat.svg
│ ├── muteCatBig.svg
│ ├── muteCatCircle.svg
│ ├── muteCatSmall.svg
│ ├── reloadIcon.svg
│ ├── twitter.svg
│ └── wave.svg
├── components
│ ├── DragWrapper
│ │ └── DragWrapper.tsx
│ ├── Footer
│ │ ├── CallControlBox.tsx
│ │ ├── Footer.tsx
│ │ ├── JoinButton
│ │ │ └── JoinButton.tsx
│ │ ├── MoreTab
│ │ │ └── MoreTab.tsx
│ │ ├── MuteButton
│ │ │ └── MuteButton.tsx
│ │ ├── Settings
│ │ │ ├── Settings.tsx
│ │ │ └── SettingsStore.tsx
│ │ ├── SocialIcons.tsx
│ │ └── VideoButton
│ │ │ └── VideoButton.tsx
│ ├── Header
│ │ └── Header.tsx
│ ├── JitsiConnection
│ │ ├── JitsiConnection.js
│ │ └── jitsiOptions.tsx
│ ├── NameTag
│ │ └── NameTag.tsx
│ ├── PanWrapper
│ │ ├── PanWrapper.tsx
│ │ └── panOptions.ts
│ ├── Room
│ │ └── Room.tsx
│ ├── User
│ │ ├── Localuser
│ │ │ ├── LocalUserContainer.tsx
│ │ │ ├── Localuser.test.js
│ │ │ ├── Localuser.tsx
│ │ │ └── components
│ │ │ │ ├── AudioRadius.tsx
│ │ │ │ ├── LocalAudio.tsx
│ │ │ │ ├── LocalVideo.tsx
│ │ │ │ ├── MuteIndicator.tsx
│ │ │ │ └── NameContainer.tsx
│ │ ├── RemoteUser
│ │ │ ├── AudioTrack.tsx
│ │ │ ├── ConnectedUser.js
│ │ │ ├── DesktopVideo.tsx
│ │ │ ├── MuteIndicator.tsx
│ │ │ ├── Name.tsx
│ │ │ ├── Users.tsx
│ │ │ └── VideoTrack.tsx
│ │ └── components
│ │ │ └── Backdrop
│ │ │ └── UserBackdrop.tsx
│ └── common
│ │ ├── BigHeadline
│ │ └── index.tsx
│ │ ├── Buttons
│ │ ├── Button.tsx
│ │ └── IconLink.tsx
│ │ ├── Card
│ │ └── Card.tsx
│ │ ├── Info
│ │ ├── ErrorHandler.tsx
│ │ ├── Info.tsx
│ │ └── InfoStore.ts
│ │ ├── Input
│ │ └── InputField.tsx
│ │ ├── Menu
│ │ └── Menu.tsx
│ │ └── SubHeadline
│ │ └── index.tsx
├── global.d.ts
├── index.tsx
├── pages
│ ├── Enter
│ │ └── Enter.tsx
│ ├── Home
│ │ ├── Home.tsx
│ │ └── elements
│ │ │ ├── NameInputContainer.tsx
│ │ │ └── NameInputForm.tsx
│ └── Session
│ │ └── Session.tsx
├── react-app-env.d.ts
├── reportWebVitals.ts
├── serverConfig-example.ts
├── serverConfig.ts
├── setupTests.ts
├── store
│ ├── ConferenceStore.tsx
│ ├── ConnectionStore.tsx
│ ├── LocalStore.test.tsx
│ ├── LocalStore.tsx
│ ├── LocalStoreLogic.tsx
│ ├── createConnectionSlice.ts
│ └── index.d.ts
├── stories
│ ├── Button.tsx
│ └── button.css
├── styled.d.ts
├── theme.d.ts
├── theme
│ ├── GlobalStyles
│ │ ├── GlobalStyles.tsx
│ │ └── fonts.ts
│ └── theme.tsx
└── utils
│ ├── LookupTable.ts
│ ├── Portal.tsx
│ ├── TypeDefinitions.ts
│ ├── VectorHelpers.ts
│ ├── hooks
│ └── useDrag.ts
│ ├── secureConferenceName.spec.ts
│ └── secureConferenceName.ts
└── tsconfig.json
/.env.development:
--------------------------------------------------------------------------------
1 | # REACT_APP_SERVICE_URL="your_jitsi_server_url"
2 | # REACT_APP_USE_WEBSOCKET="true"
3 |
4 | HTTPS=true
5 | # PORT=8080
6 | SSL_CRT_FILE=./.cert/cert.pem
7 | SSL_KEY_FILE=./.cert/key.pem
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: chatmosphere
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Custom issue template
3 | about: Describe this issue template's purpose here.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/addToProject.yml:
--------------------------------------------------------------------------------
1 | name: Auto Assign to Project(s)
2 |
3 | on:
4 | issues:
5 | types: [opened, labeled]
6 | pull_request:
7 | types: [opened, labeled]
8 | issue_comment:
9 | types: [created]
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 | assign_one_project:
15 | runs-on: ubuntu-latest
16 | name: Assign to One Project
17 | steps:
18 | - name: Assign NEW issues and NEW pull requests to Chatmosphere App Board
19 | uses: srggrs/assign-one-project-github-action@1.2.1
20 | if: github.event.action == 'opened'
21 | with:
22 | project: 'https://github.com/Chatmosphere/chatmosphere-app/projects/1'
23 | column_name: 'Backlog'
24 |
--------------------------------------------------------------------------------
/.github/workflows/build-deploy.yml:
--------------------------------------------------------------------------------
1 | name: Manual Chatmosphere Deploy
2 | on:
3 | # Manual Trigger for Stage Server
4 | workflow_dispatch:
5 | inputs:
6 | environment:
7 | type: environment
8 | description: Select the environment
9 |
10 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
11 | jobs:
12 | build:
13 | # The type of runner that the job will run on
14 | runs-on: ubuntu-latest
15 | environment:
16 | name: ${{ github.event.inputs.environment }}
17 |
18 | strategy:
19 | matrix:
20 | node-version: [16.x]
21 |
22 | # Steps represent a sequence of tasks that will be executed as part of the job
23 | steps:
24 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
25 | - uses: actions/checkout@v2
26 | - name: Use Node.js ${{ matrix.node-version }}
27 | uses: actions/setup-node@v2
28 | with:
29 | node-version: ${{ matrix.node-version }}
30 | - name: npm ci, build and test
31 | run: |
32 | npm install
33 | npm test
34 | PUBLIC_URL="https://${{ secrets.PUBLIC_URL }}" REACT_APP_SERVICE_URL=${{ secrets.SERVICE_URL }} REACT_APP_USE_WEBSOCKET="true" npm run build
35 | - name: rsync deploy
36 | uses: burnett01/rsync-deployments@4.1
37 | with:
38 | switches: -avzr --delete
39 | path: ./build/
40 | remote_path: /var/www/virtual/${{ secrets.UBERSPACE_USER }}/${{ secrets.PUBLIC_URL }}/
41 | remote_host: ${{ secrets.UBERSPACE_HOST }}
42 | remote_user: ${{ secrets.UBERSPACE_USER }}
43 | remote_key: ${{ secrets.DEPLOY_KEY_PRIVATE }}
44 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | *.local
17 |
18 | npm-debug.log*
19 | yarn-debug.log*
20 | yarn-error.log*
21 | .cert
22 | .env
23 | .eslintcache
24 | app
25 | .env.demo
26 | .env.app
27 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma":"all",
3 | "semi": false
4 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [unreleased]
8 |
9 | ### Added
10 | ### Changed
11 | - adding environmental variables to serverConfig.ts for jitsi server url (serviceUrl) to be either set in .env.local files by yourself or passed on `npm run start` or `npm run build` directly. So either add `SERVICE_URL = "url-of-your-jitsi-installation"` to your .env files (also to .env.development to be run during dev) or pass it like `SERVICE_URL=url-of-your-jitsi-installation npm run start`;
12 | ### Fixed
13 | ### Removed
14 |
15 | ## [0.6] 2021-03-04
16 |
17 | ### Added
18 | - Doc Folder for Documentation
19 | - Added CONTRIBUTION.md
20 | - Added INSTALL.md
21 |
22 | ### Changed
23 | - Adjusted Readme.md to be an Overview
24 | - Added gif of chatmosphere in use
25 |
26 | ### Fixed
27 | - Updated Storybook to last version; they are using an old immer install but since we're not using storybook in our app itself this is no problem.
28 | - Fixed Bug where Session Name with capital letters caused error
29 |
30 | ### Removed
31 | - openmoji npm package because it's huuuge; we added used icons in /assets folder
32 |
33 | ## [0.5.9] 2021-02-06
34 |
35 | ### Added
36 | - Added meta description in index.html (thx @bumi)
37 |
38 | ### Changed
39 | - Certificates are now linked in .env.development so it should also run on windows (Thanks @XristophD)
40 | -
41 |
42 | ### Fixed
43 | -
44 | ### Removed
45 |
46 | ## [0.5.8] - 2021-01-28
47 |
48 | ### Added
49 | - preferred Codec to be H265 in Options; it should switch the videoCodec to H265 instead of VP8, not sure if this is used though; maybe it needs to be set on server
50 |
51 | ### Changed
52 | - AudioRadius is now 650px; if its more it feels quite weird :)
53 |
54 |
55 | ## [0.5.7] - 2021-01.28
56 |
57 | ### Added
58 | - Kitty Icons for Mute
59 | - finished Color Variables for Theming
60 | - commented way to throttle pan and drag updates; thus its not updating the store that fast but with an delay of 200ms; should be checked and uncommented if we like it
61 |
62 | ### Changed
63 | - Input Field under users is now more consistent
64 | - Social Icons have theme colors
65 |
66 | ### Fixed
67 | - Button Story in Storyboard
68 |
69 | ### Removed
70 |
71 | ## [0.5.6] - 2021-01-27
72 |
73 | ### Added
74 | - Added a changelog to track our releases :)
75 |
76 | ### Changed
77 | - bumped version to 0.5.6
78 | - Interim Enter Screen is omitted for now since it's currently more confusing than helpful;
79 | so after Welcome one is direcly in the session/call;
80 | we know some people are irritated that they are directly visible with video; we will target that with the enter screen in future releases
81 |
82 |
83 | ### Fixed
84 | - No Videos where shown after camera permission request; hopefully fixed for now.
85 | - Potential Vulnerability in immer lib - updated to 8.0.1
86 | - Dragging Position, meaning the position of the current user, is now truncated to integer; (float values cannot be displayed by browsers anyways)
87 |
88 | ### Removed
89 | - uncommented the "Video" Button/Control in Footer because its not implemented yet
90 |
91 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
😽 Chatmosphere
3 | The Open Source Videochat for Cozy Talks
4 |
5 |
6 |
7 | 
8 |
9 | **Chatmosphere is an open source project that aims to make video calls informal and natural**. We missed the dynamics of a self-organizing crowd hanging out at one big table together. The big table in a bar, where so many discussions, jokes, comforting talks, utopias and ideas happen. With chatmosphere you can move and zoom in the area and hear people that are located near by louder and have dynamic talks. To learn more about the Chatmosphere project and ideas have a look in our [ABOUT.md](docs/ABOUT.md)
10 |
11 |
12 | ### Helpful Links
13 | * [How to run Chatmosphere](docs/INSTALL.md)
14 | * [Contribution Guideline](docs/CONTRIBUTION.md)
15 | * [Find Support here](https://github.com/Chatmosphere/chatmosphere-app/discussions)
16 | * [Code of Conduct for Excellent Humans](docs/CODE_OF_CONDUCT.md)
17 | * [Roadmap](https://github.com/orgs/Chatmosphere/projects/4)
18 | * [Code License](LICENSE.md)
19 | * [Donations to help us maintaining Chatmospere and run our demo server](https://opencollective.com/chatmosphere)
20 |
21 |
25 |
26 |
27 | ### Funded from September 2020 until February 2021 and September 2021 until February 2021 by
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/docs/ABOUT.md:
--------------------------------------------------------------------------------
1 | # Chatmosphere
2 |
3 | We enable informal conversation dynamics in the digital space.
4 |
5 | ## What it is?
6 |
7 | Chatmosphere is an open source project that aims to improve video call experiences.
8 |
9 | ## Why is it useful?
10 |
11 | Haven't you had this problem, when you and your friends met in a video call and just couldn't figure out who would talk next? Haven't you been bothered when everybody talks at once and you couldn't understand a thing? Or when you wanted to tell your best buddy something and had to wait for the conversation to end or use whatsapp in order to tell her?! We're trying to help you with a focus not just on functionality (code), but also on a pleasing user experience. And hey, it's open source…
12 |
13 | ## Why are we doing this?
14 |
15 | The idea came up during the first SARS-CoV-2 shutdown, when some of us started a [virtual bar](https://www.notion.so/How-it-started-e201ac34fbec4429b5dabe7de9699d1e). We found informal group conversations restricting and hierarchical, as the majority of tools is created to enable (corporate) meeting culture. We missed the dynamics of a self-organizing crowd hanging out at one big table together. The big table in a bar, on which so many discussions, jokes, comforting talks, utopias and ideas happened.
16 |
17 | It's our goal to find interaction patterns that enable informal, unmoderated video calls. It's our hypothesis, that we can prototype these interactions based on spatialized sound.
18 |
19 | ## Code of Conduct
20 |
21 | We're working with the [Citizen Code of Conduct](CODE_OF_CONDUCT.md) in order to make this project a respectful and happy space.
22 |
23 | ## Technologies
24 |
25 | We're using the [jitsi API](https://github.com/jitsi/lib-jitsi-meet/blob/master/doc/API.md), [react](https://reactjs.org/) and [type-script](https://www.typescriptlang.org/).
26 |
27 | ## How you can get started with the project?
28 |
29 | Check out our [README](README.md).
30 |
31 | ## Extended documantation
32 |
33 | For our research and other notes, check out our website: [https://chatmosphere.github.io/](https://chatmosphere.github.io/)
34 |
35 | ## Licensing
36 |
37 | We want everybody to be able to participate in this project. Since jitsi is using Apache 2.0, we're also using this for our code. Until we have a better idea, we use the Beer Ware License for all designs. We know that it would be nice if design-files itself were open-source files – thats an issue still to be tackled since most design software is not open source. Still you're welcome to use the assets and adjust to your likings.
38 |
39 | SPDX-License-Identifier: [Apache-2.0 OR GPL-2.0-or-later](LICENSE.md)
40 |
41 | SPDX-License-Identifier: Beer Ware License for Design Files
42 |
43 |
44 | ----------------------------------------------------------------------------
45 | "THE BEER-WARE LICENSE" (Revision 42):
46 | info@chatmosphere.cc wrote this file. As long as you retain this notice you
47 | can do whatever you want with this stuff. If we meet some day, and you think
48 | this stuff is worth it, you can buy us a beer in return.
49 | ----------------------------------------------------------------------------
50 |
51 |
52 | ## Project status & where do we want to go with this?
53 |
54 | We're currently funded by the [Prototype Fund](https://prototypefund.de/) in order to develop a first prototype of our idea and we'd love to carry on working on this. We hope to find additional funding. So if you know something or someone or have any idea, drop us a line. We'd highly appreciate it.
55 |
56 | ## How can i get in touch?
57 |
58 | If you have any questions or issues regarding this project, feel free to contact us anytime and we'll try to answer as quick as life allows us to. [info@chatmosphere.cc](mailto:info@chatmosphere.cc)
59 |
60 | ## Funded from September 2020 until February 2021 and from September 2021 until February 2022 by
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/docs/BMBF_Logo_new.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/docs/BMBF_Logo_new.jpg
--------------------------------------------------------------------------------
/docs/BMBF_sponsored_by_en.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/docs/BMBF_sponsored_by_en.jpg
--------------------------------------------------------------------------------
/docs/Chatmosphere.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/docs/Chatmosphere.png
--------------------------------------------------------------------------------
/docs/Chatmosphere_PrivateConversation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/docs/Chatmosphere_PrivateConversation.gif
--------------------------------------------------------------------------------
/docs/INSTALL.md:
--------------------------------------------------------------------------------
1 | # 😽 Install Chatmosphere
2 |
3 |
4 | -> If you want to run **your own Jitsi Server** checkout [these instructions](https://www.notion.so/universalinteraction/Documentation-87a86705ea564ead942d3c81d2bb06e6)
5 |
6 | ## ☝️ Download Files
7 | * go to our github repository: [Chatmosphere/chatmosphere-app](https://github.com/Chatmosphere/chatmosphere-app)
8 | * Download Zip or clone repo
9 |
10 | 
11 |
12 | * Either download the ZIP file and unpack it or clone with git
13 | * Then open the folder in your favourite IDE
14 |
15 | ## ✌️ Install the App
16 |
17 | ```shell
18 | # run yarn install or npm install
19 | npm install
20 | ```
21 |
22 | #### Certificates
23 |
24 | WebRTC - the basic technology Jitsi and Chatmosphere are running on, needs HTTPS to run. So we need a local certificate to start the app.;
25 | a nice description to add one can be found here
26 | [https://www.freecodecamp.org/news/how-to-set-up-https-locally-with-create-react-app/](https://www.freecodecamp.org/news/how-to-set-up-https-locally-with-create-react-app/)
27 |
28 | So if you have not done so already for some other project, make your local machine a certification authority to create valid certificates. We're using 🍺 [homebrew](https://brew.sh/) for that.
29 |
30 | ```bash
31 | # Install mkcert tool with homebrew
32 | brew install mkcert
33 |
34 | # Install nss (only needed if you use Firefox)
35 | brew install nss
36 |
37 | # Setup mkcert on your machine (creates a CA)
38 | mkcert -install
39 | ```
40 |
41 | #### Create a Certificate
42 | Then, from the root folder of your downloaded chatmosphere directory that you just downloaded, run:
43 |
44 | ```bash
45 | # Create .cert directory if it doesn't exist
46 | mkdir -p .cert
47 |
48 | # Generate the certificate (ran from the root of this project)
49 | mkcert -key-file ./.cert/key.pem -cert-file ./.cert/cert.pem "localhost"
50 | ```
51 |
52 | ## 😻 Run Chatmosphere
53 |
54 | ```bash
55 | # to start chatmosphere, type
56 | npm start
57 | # or if you prefer yarn
58 | yarn start
59 | ```
60 | Runs the app in the development mode.\
61 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
62 |
63 | The page will reload if you make edits.\
64 | You will also see any lint errors in the console.
65 |
66 | The default setup uses a test session on [https://meet.jit.si/](https://meet.jit.si/) by default; the public jitsi-meet should only be used for testing, not for production. They now offer [hosted services](https://jaas.8x8.vc/) and since they're awesome we hope to make chatmosphere compatible soon.
67 |
68 | To use your very own Jitsi Server, copy `serverConfig-example.ts` and rename it to `serverConfig.ts`
69 | Replace the configuration in the file with your very own Jitsi Server paths:
70 |
71 | ```bash
72 | export const connectionOptions = {
73 | serviceUrl: '//your.domain.com/http-bind',
74 | hosts: {
75 | domain: "your.domain.com",
76 | muc: 'conference.your.domain.com',
77 | // anonymousdomain: ''
78 | },
79 | bosh: '//your.domain.com/http-bind',
80 |
81 | clientNode: 'http://jitsi.org/jitsimeet'
82 | }
83 | ```
84 |
85 | Restart your app *(ctrl+c),* then `npm start` aaaaaand you're done - test it and please tell us how it worked 🖖 😽 🎉
86 |
87 | ---
88 |
89 | ## Other Available Scripts
90 |
91 | ### `npm test` `yarn test`
92 |
93 | Launches the test runner in the interactive watch mode.\
94 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
95 |
--------------------------------------------------------------------------------
/docs/OpenDesignChatmosphere.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/docs/OpenDesignChatmosphere.png
--------------------------------------------------------------------------------
/docs/chatmosphere.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/docs/chatmosphere.gif
--------------------------------------------------------------------------------
/docs/github-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/docs/github-screenshot.png
--------------------------------------------------------------------------------
/docs/playwave_chatmosphere.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/docs/playwave_chatmosphere.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.6.1",
3 | "homepage": "https://app.chatmosphere.cc",
4 | "name": "chatmosphere",
5 | "private": true,
6 | "dependencies": {
7 | "@testing-library/jest-dom": "^5.14.1",
8 | "@testing-library/react": "^12.0.0",
9 | "@testing-library/user-event": "^13.2.1",
10 | "@types/jest": "^27.0.1",
11 | "@types/node": "^16.7.13",
12 | "@types/react": "^17.0.20",
13 | "@types/react-dom": "^17.0.9",
14 | "@types/styled-components": "^5.1.21",
15 | "env-cmd": "^10.1.0",
16 | "immer": "^9.0.12",
17 | "lodash": "^4.17.20",
18 | "polished": "^4.1.4",
19 | "react": "^17.0.2",
20 | "react-dom": "^17.0.2",
21 | "react-feather": "^2.0.9",
22 | "react-icons": "^4.1.0",
23 | "react-router-dom": "^5.2.0",
24 | "react-scripts": "^5.0.0",
25 | "react-zoom-pan-pinch": "^1.6.1",
26 | "simple-zustand-devtools": "^1.0.0",
27 | "styled-components": "^5.3.3",
28 | "typescript": "^4.4.2",
29 | "web-vitals": "^2.1.0",
30 | "zustand": "^3.6.9"
31 | },
32 | "scripts": {
33 | "start": "react-scripts start",
34 | "build": "react-scripts build",
35 | "build:demo": "env-cmd -f .env.demo.local react-scripts build && rm -rf demo && rm -rf app && mv build demo",
36 | "build:app": "env-cmd -f .env.app.local react-scripts build && rm -rf app && rm -rf demo && mv build app",
37 | "test": "react-scripts test",
38 | "eject": "react-scripts eject"
39 | },
40 | "eslintConfig": {
41 | "extends": [
42 | "react-app",
43 | "react-app/jest"
44 | ]
45 | },
46 | "browserslist": {
47 | "production": [
48 | ">0.2%",
49 | "not dead",
50 | "not op_mini all"
51 | ],
52 | "development": [
53 | "last 1 chrome version",
54 | "last 1 firefox version",
55 | "last 1 safari version"
56 | ]
57 | },
58 | "devDependencies": {
59 | "@testing-library/react-hooks": "^7.0.2",
60 | "prettier": "2.2.1"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/public/.htaccess:
--------------------------------------------------------------------------------
1 | Options -MultiViews
2 | RewriteEngine On
3 | RewriteBase /
4 | RewriteCond %{HTTP_HOST} ^chatmosphere.cc/build
5 | RewriteRule (.*) https://stage.chatmosphere.cc/$1 [R=301,L]
6 | RewriteCond %{HTTP_HOST} ^chatmosphere.cc/demo
7 | RewriteRule (.*) https://demo.chatmosphere.cc/$1 [R=301,L]
8 | RewriteCond %{REQUEST_FILENAME} !-f
9 | RewriteRule ^ index.html [QSA,L]
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
17 |
18 |
27 | Chatmosphere
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/libs/strophe/strophe.disco.min.js:
--------------------------------------------------------------------------------
1 | Strophe.addConnectionPlugin("disco",{_connection:null,_identities:[],_features:[],_items:[],init:function(conn){this._connection=conn;this._identities=[];this._features=[];this._items=[];conn.addHandler(this._onDiscoInfo.bind(this),Strophe.NS.DISCO_INFO,"iq","get",null,null);conn.addHandler(this._onDiscoItems.bind(this),Strophe.NS.DISCO_ITEMS,"iq","get",null,null)},addIdentity:function(category,type,name,lang){for(var i=0;i {
6 |
7 | });
8 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Header } from './components/Header/Header'
3 | import styled from 'styled-components'
4 |
5 | import { BrowserRouter as Router, Switch, Route } from "react-router-dom"
6 | import { Home } from "./pages/Home/Home"
7 | import { Enter } from "./pages/Enter/Enter"
8 | import { Session } from "./pages/Session/Session"
9 |
10 | const AppContainer = styled.div`
11 | text-align: center;
12 | position: fixed;
13 | width: 100%;
14 | height: 100%;
15 | cursor: default;
16 | `
17 |
18 |
19 | function App() {
20 | return (
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | {/* TODO: redirect to "/enter" if this the first time the user in this conference */}
30 |
31 |
32 |
33 |
34 | Chatmosphere
35 |
36 |
37 |
38 |
39 |
40 | )
41 | }
42 |
43 | export default App
44 |
--------------------------------------------------------------------------------
/src/addons/Screenshare/ScreenshareButton/ScreenshareButton.tsx:
--------------------------------------------------------------------------------
1 | import { useConferenceStore } from "../../../store/ConferenceStore"
2 | import { useConnectionStore } from "../../../store/ConnectionStore"
3 | import { useLocalStore } from "../../../store/LocalStore"
4 | import { IconButton } from "../../../components/common/Buttons/Button"
5 | import { useCallback, useState } from "react"
6 | import ScreenShareIcon from "../../../assets/icons/ScreenShare"
7 |
8 | // TODO Error when alone in call - not sure why - replaceTrack has some empty object
9 | export const ScreenshareButton = (props) => {
10 | const jsMeet = useConnectionStore((state) => state.jsMeet)
11 | const setLocalTracks = useLocalStore(
12 | useCallback((store) => store.setLocalTracks, []),
13 | )
14 | const conferenceObject = useConferenceStore((state) => state.conferenceObject)
15 | const [isSharing, setIsSharing] = useState(false)
16 |
17 |
18 |
19 | const setNewTracks = (tracks, oldTrack) => {
20 | const newTrack = tracks[0]
21 | // const oldTrack = conferenceObject?.getLocalVideoTrack()
22 | // oldTrack?.dispose()
23 |
24 | let isDesktopTrack = newTrack.videoType === "desktop"
25 | if (isDesktopTrack) {
26 | // add stop listener to automatically activate camera again if stopped by browser controls
27 | // -> but thats why the video track is called two times, manual stop calls it also
28 | newTrack.addEventListener(jsMeet?.events.track.LOCAL_TRACK_STOPPED, () => createVideoTrack())
29 | }
30 | if (oldTrack) {
31 | conferenceObject?.replaceTrack(oldTrack, newTrack).then(() => {
32 | setLocalTracks(tracks)
33 | setIsSharing(isDesktopTrack)
34 | })
35 | .catch(err => console.error(err))
36 | } //else add new Track?
37 | }
38 |
39 | const createVideoTrack = () => {
40 | if (!jsMeet) return // not needed ?
41 |
42 | //delete old Track
43 | const oldTrack = conferenceObject?.getLocalVideoTrack()
44 | const type = oldTrack?.videoType === "desktop" ? "video" : "desktop"
45 | oldTrack?.dispose()
46 |
47 | // create tracks
48 | jsMeet
49 | .createLocalTracks({ devices: [type] })
50 | .then((tracks) => setNewTracks(tracks, oldTrack))
51 | .catch((error) => {
52 | console.log(error)
53 | })
54 | }
55 |
56 | return (
57 | }
62 | label="Screenshare"
63 | />
64 | )
65 | }
66 |
--------------------------------------------------------------------------------
/src/addons/Screenshare/components/LocalDesktop.tsx:
--------------------------------------------------------------------------------
1 | import { memo, useEffect, useRef } from "react"
2 | import styled from "styled-components"
3 | import { useConferenceStore } from "../../../store/ConferenceStore"
4 |
5 | const StyledVideo = styled.video`
6 | width: auto;
7 | height: 200px;
8 | display: block;
9 | `
10 |
11 | const LocalDesktop:React.FC<{track:IVideoTrack}> = memo(({track}) => {
12 | const myRef:any = useRef()
13 | const room = useConferenceStore(store => store.conferenceObject)
14 |
15 | useEffect(() => {
16 | room?.addTrack(track) //TODO should be done in store I think?
17 | .catch(error => console.log(error))//the track might have been added already, handle the promise error
18 | return() => {
19 | // room?.removeTrack(track) // we're replacing, not deleting & adding new one;
20 | }
21 | },[room, track])
22 |
23 | useEffect(()=> {
24 | const el = myRef.current
25 | if(track?.containers?.length === 0) track.attach(el)
26 | return (() => {
27 | track.detach(el)
28 | })
29 | },[track])
30 |
31 |
32 | return
33 | })
34 |
35 | export default LocalDesktop
--------------------------------------------------------------------------------
/src/addons/Stage/Stage.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect } from "react"
2 | import styled from "styled-components"
3 | import { useConferenceStore } from "../../store/ConferenceStore"
4 | import { useConnectionStore } from "../../store/ConnectionStore"
5 | import { useLocalStore } from "../../store/LocalStore"
6 | import { LocalUser } from "./components/StageUser"
7 | import { StageUsers } from "./components/StageUsers"
8 | import { GhostButton} from "../../components/common/Buttons/Button"
9 | import { EyeOff, VolumeX } from "react-feather"
10 |
11 | const Container = styled.div`
12 | position: relative;
13 | display: flex;
14 | flex-direction: column;
15 | gap: 20px;
16 | width: auto;
17 | height: auto;
18 | /* top: 24px;
19 | left: 24px; */
20 | `
21 | const ScrollContainer = styled.div`
22 | position: absolute;
23 | top: 24px;
24 | left: 0;
25 | bottom: 24px;
26 | padding-right: 60px;
27 | padding-left: 24px;
28 | width: 12%;
29 | overflow-y: auto;
30 | overflow-x: visible;
31 | `
32 |
33 | //TODO cleaner way would be to handle the whole stage management here and refactor calculateUsersOnScreen
34 | // Actually that would be much better - no need to loop all users in localstore on each user movement to recalculate who is on stage
35 | // in set last-n we would just set calculated visible user array and stage user array that is set here only on participantpropertychange
36 | // const onParticipantPropertyChange = (e) => {
37 | // useLocalStore.getState().calculateUsersOnScreen()
38 | // }
39 |
40 | const userOnScreenSelector = (store) => store.calculateUsersOnScreen
41 | const ids_OnStageSelector = (store) => store.usersOnStage
42 |
43 | const ConnectedStage = () => {
44 | const stageVisible = useLocalStore(
45 | useCallback((store) => store.stageVisible, []),
46 | )
47 | const stageMute = useLocalStore(useCallback((store) => store.stageMute, []))
48 | const localUserOnStage = useLocalStore((store) => store.onStage)
49 | const conference = useConferenceStore((store) => store.conferenceObject)
50 | const JSMeet = useConnectionStore((store) => store.jsMeet)
51 | const calculateUsersOnScreen = useLocalStore(userOnScreenSelector)
52 | const usersOnStage = useLocalStore(ids_OnStageSelector)
53 |
54 | useEffect(() => {
55 | conference?.on(
56 | JSMeet?.events.conference.PARTICIPANT_PROPERTY_CHANGED,
57 | calculateUsersOnScreen,
58 | )
59 |
60 | return () => {
61 | conference?.off(
62 | JSMeet?.events.conference.PARTICIPANT_PROPERTY_CHANGED,
63 | calculateUsersOnScreen,
64 | )
65 | }
66 | }, [conference, JSMeet, calculateUsersOnScreen])
67 |
68 | return (
69 | <>
70 | {(usersOnStage.length > 0 || localUserOnStage) && (
71 |
72 | {localUserOnStage && }
73 | {stageVisible && }
74 |
75 | )}
76 | >
77 | )
78 | }
79 |
80 | export const Stage = ({ children }) => (
81 |
82 |
83 |
84 | {children}
85 |
86 |
87 | )
88 |
89 |
90 |
91 | const StageControls = () => {
92 | const toggleMute = useLocalStore((store) => store.toggleStageMute)
93 | const toggleStage = useLocalStore((store) => store.toggleStage)
94 | const mute = useLocalStore((store) => store.stageMute)
95 | const visible = useLocalStore((store) => store.stageVisible)
96 |
97 | return (
98 |
99 | {!visible && (
100 | }
108 | />
109 | )}
110 | {visible && (
111 | }
118 | />
119 | )}
120 | {visible && mute && (
121 | }
129 | />
130 | )}
131 | {visible && !mute && (
132 | }
139 | />
140 | )}
141 |
142 | )
143 | }
144 |
145 | const StageControlsBox = styled.div`
146 | display: flex;
147 | flex-direction: row;
148 | `
149 |
150 | const MuteStageButton = styled(GhostButton)``
151 |
152 | const HideStageButton = styled(GhostButton)``
153 |
154 | export default ConnectedStage
155 |
--------------------------------------------------------------------------------
/src/addons/Stage/components/ResizeControl.tsx:
--------------------------------------------------------------------------------
1 | import { MouseEventHandler } from "react"
2 | import { Maximize2 } from "react-feather"
3 | import styled from "styled-components"
4 |
5 | const ResizeContainer = styled.div`
6 | position: absolute;
7 | left: 0;
8 | top: 0;
9 | right: 0;
10 | bottom: 0;
11 | & div {
12 | display: none;
13 | }
14 | &:hover {
15 | background-color: rgba(255, 255, 255, 0.2);
16 | & div {
17 | display: block;
18 | }
19 | }
20 | `
21 |
22 | const ResizeElement = styled.div`
23 | position: absolute;
24 | top: 50%;
25 | left: 50%;
26 | transform: translate(-50%, -50%);
27 | background-color: rgba(0, 0, 0, 0.5);
28 | padding: 15px;
29 | border-radius: 50%;
30 | & svg {
31 | stroke:white;
32 | }
33 | `
34 |
35 | export const ResizeControl = ({callback=()=>null}:{callback:MouseEventHandler|undefined}) => {
36 | return (
37 |
38 |
39 |
40 |
41 |
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/src/addons/Stage/components/StageAudio.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react";
2 |
3 | //Could be a general Audio Element for all other components
4 | export const AudioComponent:React.FunctionComponent<{audio:IAudioTrack | undefined, volume:number, className?:string, id?:string}> = ({ audio, volume, className, id }) => {
5 |
6 | const myRef: any = useRef();
7 |
8 | useEffect(() => {
9 | if (myRef.current && myRef.current.volume !== undefined)
10 | myRef.current.volume = volume;
11 | }, [volume]);
12 |
13 | useEffect(() => {
14 | const currentEl = myRef.current;
15 | let tmpEl = undefined;
16 | if (audio?.containers && audio?.containers.length > 0) {
17 | tmpEl = audio.containers[0];
18 | if(tmpEl) audio.detach(tmpEl);
19 | }
20 | if (audio?.containers.length === 0)
21 | audio?.attach(currentEl);
22 | return (() => {
23 | audio?.detach(currentEl);
24 | if (tmpEl)
25 | audio?.attach(tmpEl);
26 | });
27 | },[audio]);
28 |
29 | return ;
30 | };
31 |
--------------------------------------------------------------------------------
/src/addons/Stage/components/StageButton.tsx:
--------------------------------------------------------------------------------
1 | import { IconButton } from "../../../components/common/Buttons/Button"
2 | import { useConferenceStore } from "../../../store/ConferenceStore"
3 | import { useLocalStore } from "../../../store/LocalStore"
4 | import { useEffect } from "react"
5 | import StageIcon from "../../../assets/icons/StageIcon"
6 |
7 | export const StageButton = ({callback=()=>null}) => {
8 | const conference = useConferenceStore(store => store.conferenceObject)
9 | const onStage = useLocalStore(store => store.onStage)
10 | const setOnStage = useLocalStore(store => store.setOnStage)
11 |
12 | const onClick = () => {
13 | const tmpStage = !onStage
14 | setOnStage(tmpStage)
15 | conference?.setLocalParticipantProperty('onStage', tmpStage)
16 | }
17 |
18 | useEffect(()=>{
19 | return(() => {
20 | setOnStage(false)
21 | })
22 | },[setOnStage])
23 |
24 | return (
25 | } />
26 | )
27 |
28 | }
--------------------------------------------------------------------------------
/src/addons/Stage/components/StageUser.tsx:
--------------------------------------------------------------------------------
1 | import { memo} from "react"
2 | import { createPortal } from "react-dom"
3 | import styled from "styled-components"
4 | import { useLocalStore } from "../../../store/LocalStore"
5 | import { ResizeControl } from "./ResizeControl"
6 | import { AudioComponent } from "./StageAudio"
7 | import { VideoComponent } from "./StageVideo"
8 |
9 | type IStageContainer = {
10 | readonly width?: string
11 | readonly toggle?: boolean
12 | }
13 |
14 | const UserContainer = styled.div`
15 | width: 100%;
16 | height: auto;
17 | background-color: white;
18 | border-radius: 10px;
19 | overflow: hidden;
20 | box-shadow: 0px 24px 48px 0px rgba(0, 0, 0, 0.25);
21 | position: relative;
22 | `
23 |
24 |
25 | const SelectedUserContainer = styled(UserContainer)`
26 | position: absolute;
27 | width: auto;
28 | max-width: 90%;
29 | height: 50%;
30 | top: 20px;
31 | left: 50%;
32 | transform: translateX(-50%);
33 | & > div {
34 | width: auto;
35 | height: 100%;
36 | }
37 | `
38 |
39 | const setSelector = (store) => store.setSelectedUserOnStage
40 |
41 | const RemoteUser = ({ video, audio, volume, id, selected = false }) => {
42 | const setSelectedUser = useLocalStore(setSelector)
43 |
44 | if (selected) {
45 | return createPortal(
46 |
47 |
48 |
49 |
50 | setSelectedUser(id)} />
51 |
52 | ,
53 | document.body,
54 | )
55 | }
56 | return (
57 |
58 |
59 |
60 | setSelectedUser(id)} />
61 |
62 | )
63 | }
64 | //memo prevents rerender of stage users on move -> TODO: check if maybe should be solved up in the tree
65 | export const User = memo(RemoteUser)
66 |
67 | export const LocalUser = () => {
68 | const localVideo = useLocalStore((store) => store.video)
69 | const videoType = useLocalStore((store) => store.videoType)
70 |
71 | return (
72 |
73 | {localVideo && videoType === "camera" && (
74 |
75 | )}
76 | {localVideo && videoType === "desktop" && (
77 |
78 | )}
79 |
80 | )
81 | }
82 |
--------------------------------------------------------------------------------
/src/addons/Stage/components/StageUsers.tsx:
--------------------------------------------------------------------------------
1 | import { useConferenceStore } from "../../../store/ConferenceStore"
2 | import { useLocalStore } from "../../../store/LocalStore"
3 | import { User as StageUser } from "./StageUser"
4 |
5 | const userSelector = store => store.users
6 |
7 | export const StageUsers = ({volume}:{volume:number}) => {
8 | const users: IUsers = useConferenceStore(userSelector)
9 | const selectedUsersOnStage = useLocalStore(store => store.selectedUsersOnStage)
10 | return (
11 | <>
12 | {Object.entries(users).map(user => {
13 | if (user[1]?.properties?.onStage) {
14 | /**
15 | * Quick Fixed by memo to remote User; still this might be better fixed up in the tree
16 | TODO: this is rerendering on every move, even though react will diff it out, Im not sure thats cool;
17 | we only need updates if properties change so we could have a stageusers array instead;
18 | -> could also have a "users Visible" array to only render them on stage at all
19 | */
20 |
21 | return
22 | }
23 | return null
24 | })}
25 | >
26 | )
27 | }
--------------------------------------------------------------------------------
/src/addons/Stage/components/StageVideo.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react";
2 | import styled from "styled-components";
3 |
4 | interface IVideo extends React.HTMLAttributes {
5 | mirrored: boolean;
6 | }
7 |
8 | type IVideoParams = {
9 | video:IVideoTrack | undefined
10 | mirrored?:boolean
11 | className?:string
12 | id?:string
13 | Element?:any
14 | } & React.HTMLAttributes
15 |
16 | const Video = styled.video`
17 | width: inherit;
18 | height: inherit;
19 | display: block;
20 | object-fit: cover;
21 | transform: ${props => props.mirrored ? "scaleX(-1)" : "scaleX(1)"};
22 | `
23 |
24 | // export const StageVideo:React.FunctionComponent = ({ video, mirrored=false, className, id }) =>
25 | export const StageDesktop = (props) =>
26 |
27 | export const StageVideo = (props) => {
28 |
29 | if(props.videoType === "camera") return
30 | if(props.videoType === "desktop") return
31 | }
32 |
33 | //Could be a General Video Element
34 | // BEWARE - this detaches and reattaches the video element from previous dom element!!!
35 | export const VideoComponent:React.FunctionComponent = (props) => {
36 | const {video, mirrored=false, className, id, Element=Video, ...rest} = props
37 |
38 | const myRef: any = useRef();
39 | useEffect(() => {
40 | const el = myRef.current;
41 | video?.attach(el);
42 | return (() => {
43 | video?.detach(el);
44 | });
45 | }, [video]);
46 |
47 | return (
48 |
49 | );
50 | };
--------------------------------------------------------------------------------
/src/addons/addonsStore.ts:
--------------------------------------------------------------------------------
1 | import create from "zustand"
2 |
3 | export const useAddonsStore = create((set:any, get:any) => {
4 | return ({
5 | addonElements: [],
6 | footerElements:[] as Array<{id:string, el:any}>,
7 | settingsElements: [],
8 | addFooterElement: (el) => set(state => ({footerElements:[...state.footerElements, el]})),
9 | removeFooterElement: (el) => set(state => ({footerElements:state.footerElements.filter(e => e.id !== el.id)})),
10 | addAddon: (el) => set(state => ({addonElements:[...state.addonElements, el]})),
11 | removeAddon: (el) => set(state => ({addonElements:state.addonElements.filter(e => e.id !== el.id)})),
12 | addSettingsElement: (el) => set(state => ({settingsElements:[...state.settingsElements, el]})),
13 | removeSettingsElement: (el) => set(state => ({settingsElements:state.settingsElements.filter(e => e.id !== el.id)}))
14 | })
15 | })
--------------------------------------------------------------------------------
/src/assets/call.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fonts/inter-v7/Inter-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/inter-v7/Inter-Regular.woff
--------------------------------------------------------------------------------
/src/assets/fonts/inter-v7/Inter-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/inter-v7/Inter-Regular.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/inter-v7/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016-2020 The Inter Project Authors.
2 | "Inter" is trademark of Rasmus Andersson.
3 | https://github.com/rsms/inter
4 |
5 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
6 | This license is copied below, and is also available with a FAQ at:
7 | http://scripts.sil.org/OFL
8 |
9 | -----------------------------------------------------------
10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
11 | -----------------------------------------------------------
12 |
13 | PREAMBLE
14 | The goals of the Open Font License (OFL) are to stimulate worldwide
15 | development of collaborative font projects, to support the font creation
16 | efforts of academic and linguistic communities, and to provide a free and
17 | open framework in which fonts may be shared and improved in partnership
18 | with others.
19 |
20 | The OFL allows the licensed fonts to be used, studied, modified and
21 | redistributed freely as long as they are not sold by themselves. The
22 | fonts, including any derivative works, can be bundled, embedded,
23 | redistributed and/or sold with any software provided that any reserved
24 | names are not used by derivative works. The fonts and derivatives,
25 | however, cannot be released under any other type of license. The
26 | requirement for fonts to remain under this license does not apply
27 | to any document created using the fonts or their derivatives.
28 |
29 | DEFINITIONS
30 | "Font Software" refers to the set of files released by the Copyright
31 | Holder(s) under this license and clearly marked as such. This may
32 | include source files, build scripts and documentation.
33 |
34 | "Reserved Font Name" refers to any names specified as such after the
35 | copyright statement(s).
36 |
37 | "Original Version" refers to the collection of Font Software components as
38 | distributed by the Copyright Holder(s).
39 |
40 | "Modified Version" refers to any derivative made by adding to, deleting,
41 | or substituting -- in part or in whole -- any of the components of the
42 | Original Version, by changing formats or by porting the Font Software to a
43 | new environment.
44 |
45 | "Author" refers to any designer, engineer, programmer, technical
46 | writer or other person who contributed to the Font Software.
47 |
48 | PERMISSION AND CONDITIONS
49 | Permission is hereby granted, free of charge, to any person obtaining
50 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
51 | redistribute, and sell modified and unmodified copies of the Font
52 | Software, subject to the following conditions:
53 |
54 | 1) Neither the Font Software nor any of its individual components,
55 | in Original or Modified Versions, may be sold by itself.
56 |
57 | 2) Original or Modified Versions of the Font Software may be bundled,
58 | redistributed and/or sold with any software, provided that each copy
59 | contains the above copyright notice and this license. These can be
60 | included either as stand-alone text files, human-readable headers or
61 | in the appropriate machine-readable metadata fields within text or
62 | binary files as long as those fields can be easily viewed by the user.
63 |
64 | 3) No Modified Version of the Font Software may use the Reserved Font
65 | Name(s) unless explicit written permission is granted by the corresponding
66 | Copyright Holder. This restriction only applies to the primary font name as
67 | presented to the users.
68 |
69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
70 | Software shall not be used to promote, endorse or advertise any
71 | Modified Version, except to acknowledge the contribution(s) of the
72 | Copyright Holder(s) and the Author(s) or with their explicit written
73 | permission.
74 |
75 | 5) The Font Software, modified or unmodified, in part or in whole,
76 | must be distributed entirely under this license, and must not be
77 | distributed under any other license. The requirement for fonts to
78 | remain under this license does not apply to any document created
79 | using the Font Software.
80 |
81 | TERMINATION
82 | This license becomes null and void if any of the above conditions are
83 | not met.
84 |
85 | DISCLAIMER
86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
94 | OTHER DEALINGS IN THE FONT SOFTWARE.
95 |
--------------------------------------------------------------------------------
/src/assets/fonts/inter-v7/inter-v7-latin-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/inter-v7/inter-v7-latin-regular.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-500.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-500.eot
--------------------------------------------------------------------------------
/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-500.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-500.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-500.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-500.woff
--------------------------------------------------------------------------------
/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-500.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-500.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-700.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-700.eot
--------------------------------------------------------------------------------
/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-700.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-700.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-700.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-700.woff
--------------------------------------------------------------------------------
/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-700.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-700.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-regular.eot
--------------------------------------------------------------------------------
/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-regular.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-regular.woff
--------------------------------------------------------------------------------
/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/roboto-v20-latin/roboto-v20-latin-regular.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/space-grotesk-v2/AUTHORS.txt:
--------------------------------------------------------------------------------
1 | # This is the official list of project authors for copyright purposes.
2 | # This file is distinct from the CONTRIBUTORS.txt file.
3 | # See the latter for an explanation.
4 | #
5 | # Names should be added to this file as:
6 | # Name or Organization
7 |
8 | Florian Karsten
9 | Květoslav Bartoš
--------------------------------------------------------------------------------
/src/assets/fonts/space-grotesk-v2/CONTRIBUTORS.txt:
--------------------------------------------------------------------------------
1 | # This is the list of people who have contributed to this project,
2 | # and includes those not listed in AUTHORS.txt because they are not
3 | # copyright authors. For example, company employees may be listed
4 | # here because their company holds the copyright and is listed there.
5 | #
6 | # When adding J Random Contributor's name to this file, either J's
7 | # name or J's organization's name should be added to AUTHORS.txt
8 | #
9 | # Names should be added to this file as:
10 | # Name
11 |
12 | Florian Karsten
13 | Květoslav Bartoš
14 | Viviana Monsalve
15 | Colophon Foundry
--------------------------------------------------------------------------------
/src/assets/fonts/space-grotesk-v2/OFL.txt:
--------------------------------------------------------------------------------
1 | Copyright 2020 The Space Grotesk Project Authors (https://github.com/floriankarsten/space-grotesk)
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | http://scripts.sil.org/OFL
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/src/assets/fonts/space-grotesk-v2/SpaceGrotesk-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/space-grotesk-v2/SpaceGrotesk-Bold.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/space-grotesk-v2/SpaceGrotesk-Bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/space-grotesk-v2/SpaceGrotesk-Bold.woff
--------------------------------------------------------------------------------
/src/assets/fonts/space-grotesk-v2/SpaceGrotesk-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/space-grotesk-v2/SpaceGrotesk-Bold.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/space-grotesk-v2/SpaceGrotesk-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/space-grotesk-v2/SpaceGrotesk-Medium.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/space-grotesk-v2/SpaceGrotesk-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/space-grotesk-v2/SpaceGrotesk-Medium.woff
--------------------------------------------------------------------------------
/src/assets/fonts/space-grotesk-v2/SpaceGrotesk-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/space-grotesk-v2/SpaceGrotesk-Medium.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/space-grotesk-v2/SpaceGrotesk-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/space-grotesk-v2/SpaceGrotesk-Regular.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/space-grotesk-v2/SpaceGrotesk-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/space-grotesk-v2/SpaceGrotesk-Regular.woff
--------------------------------------------------------------------------------
/src/assets/fonts/space-grotesk-v2/SpaceGrotesk-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fossasia/chatmosphere-app/2dd8e5e7c2f2541bef18ad3fbc77730f930f4ec4/src/assets/fonts/space-grotesk-v2/SpaceGrotesk-Regular.woff2
--------------------------------------------------------------------------------
/src/assets/icons/ArrowUp.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function ArrowUpIcon() {
4 | return (
5 |
32 | );
33 | }
34 |
35 | export default ArrowUpIcon;
36 |
--------------------------------------------------------------------------------
/src/assets/icons/Camera.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function CameraIcon() {
4 | return (
5 |
21 | );
22 | }
23 |
24 | export default CameraIcon;
25 |
--------------------------------------------------------------------------------
/src/assets/icons/CameraOff.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function CameraOffIcon() {
4 | return (
5 |
26 | );
27 | }
28 |
29 | export default CameraOffIcon;
--------------------------------------------------------------------------------
/src/assets/icons/ChatFilledIcon.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function ChatFilledIcon() {
4 | return (
5 |
26 | );
27 | }
28 |
29 | export default ChatFilledIcon;
30 |
--------------------------------------------------------------------------------
/src/assets/icons/ChatIcon.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function Icon() {
4 | return (
5 |
28 | );
29 | }
30 |
31 | export default Icon;
32 |
--------------------------------------------------------------------------------
/src/assets/icons/MicIcon.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function Icon() {
4 | return (
5 |
44 | );
45 | }
46 |
47 | export default Icon;
48 |
--------------------------------------------------------------------------------
/src/assets/icons/MicOff.js:
--------------------------------------------------------------------------------
1 | // used https://svg2jsx.com/ - thx a lot <3
2 |
3 | function Icon() {
4 | return (
5 |
46 | );
47 | }
48 |
49 | export default Icon;
50 |
--------------------------------------------------------------------------------
/src/assets/icons/MoreVertical.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function MoreVerticalIcon() {
4 | return (
5 |
49 | );
50 | }
51 |
52 | export default MoreVerticalIcon;
--------------------------------------------------------------------------------
/src/assets/icons/ScreenShare.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function Icon() {
4 | return (
5 |
44 | );
45 | }
46 |
47 | export default Icon;
48 |
--------------------------------------------------------------------------------
/src/assets/icons/SettingsIcon.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function SettingsIcon() {
4 | return (
5 |
35 | );
36 | }
37 |
38 | export default SettingsIcon;
39 |
--------------------------------------------------------------------------------
/src/assets/icons/StageIcon.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function Icon() {
4 | return (
5 |
35 | );
36 | }
37 |
38 | export default Icon;
39 |
--------------------------------------------------------------------------------
/src/assets/icons/StageIcon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/UserFilled.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function UserFilledIcon() {
4 | return (
5 |
36 | );
37 | }
38 |
39 | export default UserFilledIcon;
40 |
--------------------------------------------------------------------------------
/src/assets/icons/UserIcon.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function UserIcon() {
4 | return (
5 |
28 | );
29 | }
30 |
31 | export default UserIcon;
32 |
--------------------------------------------------------------------------------
/src/assets/kissingCat.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/src/assets/loveCat.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/src/assets/muteCatBig.svg:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/src/assets/muteCatCircle.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/src/assets/muteCatSmall.svg:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/src/assets/reloadIcon.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/assets/twitter.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/wave.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/components/DragWrapper/DragWrapper.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react'
2 | import styled from 'styled-components'
3 |
4 | export interface DragProps {
5 | initPos:Point
6 | children:any
7 | currentScale:number
8 | panOffset:Point
9 | callback: (Point)=>void
10 | }
11 |
12 | interface Point {
13 | x:number,
14 | y:number
15 | }
16 |
17 | const DragElement = styled.div`
18 | position: absolute;
19 | `
20 |
21 | const DragWrapper = ({initPos={x:0,y:0}, children, callback=(pos)=>null, currentScale = 1, panOffset}:DragProps) => {
22 | panOffset = panOffset || {x:0,y:0}
23 | const clickDelta:any = useRef()
24 | const element:any = useRef()
25 |
26 | const onDrag = (e) => {
27 | e.preventDefault()
28 | e.stopPropagation()
29 | if(element.current !== undefined) {
30 | const xPos = Math.trunc((e.clientX) / currentScale - clickDelta.current.x)
31 | const yPos = Math.trunc((e.clientY) / currentScale - clickDelta.current.y)
32 | // element?.current?.setAttribute('style', `left:${xPos}px; top:${yPos}px`)
33 | element?.current?.setAttribute('style', `transform:translate(${xPos}px, ${yPos}px);`)
34 | callback({x:xPos, y:yPos})
35 | }
36 | }
37 |
38 | const onUp = (e) => {
39 | e.preventDefault()
40 | document.removeEventListener("mouseup", onUp)
41 | document.removeEventListener("mousemove", onDrag)
42 | }
43 |
44 | const onDown = (e) => {
45 | e.preventDefault()
46 | const boundingRect = e.currentTarget.getBoundingClientRect()
47 | clickDelta.current = {
48 | x: Math.trunc((e.clientX - boundingRect.x + panOffset.x) / currentScale),
49 | y: Math.trunc((e.clientY - boundingRect.y + panOffset.y) / currentScale),
50 | }
51 | document.addEventListener("mouseup", onUp)
52 | document.addEventListener("mousemove", onDrag)
53 | }
54 |
55 | useEffect(() => {
56 | // element?.current?.setAttribute('style', `left:${initPos.x}px; top:${initPos.y}px`)
57 | element?.current?.setAttribute('style', `transform:translate(${initPos.x}px, ${initPos.y}px);`)
58 | // this is by purpose & needed to set the pos once only initially
59 | // eslint-disable-next-line react-hooks/exhaustive-deps
60 | },[])
61 |
62 | return (
63 |
64 | {children}
65 |
66 | )
67 | }
68 |
69 | export default DragWrapper
--------------------------------------------------------------------------------
/src/components/Footer/CallControlBox.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const CallControlBox = styled.div`
4 | /* position: fixed; */
5 | /* bottom: 20px;
6 | left: 50%; */
7 | display: flex;
8 | flex-direction: row;
9 | align-items: center;
10 | /* transform: translateX(-50%); */
11 | & > button {
12 | margin: 0 5px;
13 | }
14 | `
--------------------------------------------------------------------------------
/src/components/Footer/Footer.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from 'styled-components'
3 |
4 |
5 | const Container = styled.div`
6 | position: absolute;
7 | bottom: 0;
8 | width: 100%;
9 | display: flex;
10 | flex-direction: row;
11 | align-items: center;
12 | justify-content:space-between;
13 | `
14 |
15 | const LeftBox = styled.div`
16 | margin-left: 12px;
17 | `
18 |
19 | const CenterBox = styled.div`
20 | display: flex;
21 | flex-direction: row;
22 | align-items: center;
23 | margin: 10px;
24 | & > button {
25 | margin: 0 5px;
26 | }
27 | `
28 | const RightBox = styled.div`
29 | margin-right: 12px;
30 | display: flex;
31 | flex-direction: row;
32 | gap:10px;
33 | `
34 |
35 | export const Footer = ({ children, rightBox, leftBox }:{children?:React.ReactNode, rightBox?:React.ReactNode, leftBox?:React.ReactNode}) => {
36 |
37 | return (
38 |
39 |
40 | {leftBox}
41 |
42 |
43 | {children}
44 |
45 |
46 | {rightBox}
47 |
48 |
49 | )
50 | }
51 |
52 | // const ConnectedFooterAddons = () => {
53 | // const footerEl = useAddonsStore(
54 | // useCallback((store) => store.footerElements, []),
55 | // )
56 | //
57 | // return (
58 | // <>
59 | // {footerEl?.map((b) => {
60 | // console.log(b)
61 | // return b?.el
62 | // })}
63 | // >
64 | // )
65 | // }
66 |
--------------------------------------------------------------------------------
/src/components/Footer/JoinButton/JoinButton.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { useConferenceStore } from '../../../store/ConferenceStore'
3 | import { useConnectionStore } from '../../../store/ConnectionStore'
4 | import { useHistory } from 'react-router-dom'
5 | import { Button } from '../../common/Buttons/Button'
6 | import { PhoneOff } from 'react-feather'
7 |
8 | export const JoinButton = ({ joined = false }) => {
9 | const leave = useConferenceStore((store) => store.leaveConference)
10 | const disconnectServer = useConnectionStore((store) => store.disconnectServer)
11 | const conferenceName = useConferenceStore((store) => store.conferenceName)
12 | const history = useHistory()
13 |
14 | const onEndCall = () => {
15 | leave()
16 | disconnectServer()
17 | history.push(`/`)
18 | }
19 |
20 | const onStartCall = (e) => {
21 | e.preventDefault()
22 | //perhaps it is better to create a connection and then forward to "session/" page?
23 | history.push(`/session/${conferenceName}`)
24 | }
25 |
26 | if (joined) {
27 | return (
28 |
29 | )
30 | } else {
31 | return (
32 | } />
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/Footer/MoreTab/MoreTab.tsx:
--------------------------------------------------------------------------------
1 | import { GitHub, Heart, MessageCircle, MoreVertical, Twitter } from "react-feather"
2 | import styled from "styled-components"
3 | import { List, ListLink, Menu } from "../../common/Menu/Menu"
4 |
5 | const parameters = {
6 | title: "More",
7 | label: "More",
8 | icon:
9 | }
10 |
11 | const StyledHeart = styled(Heart)`
12 | /* fill: ${(props) => props.theme.color[7]}; */
13 | `
14 | const StyledTwitter = styled(Twitter)`
15 | /* fill: ${(props) => props.theme.color[5]}; */
16 | `
17 | const StyledGitHub = styled(GitHub)`
18 | /* fill: ${(props) => props.theme.base[5]}; */
19 | `
20 | const StyledMessageCircle = styled(MessageCircle)`
21 | /* fill: ${(props) => props.theme.color[4]}; */
22 | `
23 |
24 | export const MoreTab = (props) => {
25 | return (
26 |
34 | )
35 | }
36 |
37 | export {}
--------------------------------------------------------------------------------
/src/components/Footer/MuteButton/MuteButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { IconButton } from './../../common/Buttons/Button';
3 | import { useLocalStore } from './../../../store/LocalStore';
4 | import MicIcon from '../../../assets/icons/MicIcon';
5 | import MicOff from '../../../assets/icons/MicOff';
6 |
7 |
8 |
9 | export const MuteButton = () => {
10 |
11 | const toggleMute = useLocalStore(store => store.toggleMute)
12 | const mute = useLocalStore(store => store.mute)
13 |
14 | if(mute) {
15 | return } label="Unmute" />
16 | } else {
17 | return } label="Mute" />
18 | }
19 | }
--------------------------------------------------------------------------------
/src/components/Footer/Settings/Settings.tsx:
--------------------------------------------------------------------------------
1 | import { MdMoreVert } from "react-icons/md"
2 | import styled from "styled-components"
3 | import { Menu } from "../../common/Menu/Menu"
4 | import { useSettingsStore } from "./SettingsStore"
5 |
6 | export const Settings = () => {
7 | const elements = useSettingsStore((store) => store.elements)
8 |
9 | return (
10 | }>
11 |
12 | {elements.map((element) => element)}
13 |
14 |
15 |
16 | )
17 | }
18 |
19 | const Select = ({
20 | name,
21 | callback = () => null,
22 | children,
23 | }: {
24 | name: string
25 | callback?: () => void
26 | children: React.ReactNode
27 | }) => {
28 | return (
29 |
30 |
31 | {children}
32 |
33 | )
34 | }
35 |
36 | const StyledSelect = styled.div`
37 | display: flex;
38 | flex-direction: row;
39 | gap: 10px;
40 | padding: 5px 0;
41 | text-align: left;
42 | border-bottom: 1px solid ${(props) => props.theme.line.light};
43 | `
44 |
45 | const StyledLabel = styled.label`
46 | font-size: ${(props) => props.theme.fontSize.strong};
47 | `
48 |
49 | const StyledContentBox = styled.section`
50 | display: flex;
51 | flex-direction: column;
52 | flex-grow: 1;
53 | overflow: scroll;
54 | `
55 |
--------------------------------------------------------------------------------
/src/components/Footer/Settings/SettingsStore.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import create from "zustand"
3 |
4 | type ISettingsStore = {
5 | elements: Array
6 | addElement:(el:React.ReactNode)=>void
7 | removeElement:(el:React.ReactNode)=>void
8 | }
9 |
10 | export const useSettingsStore = create(set => ({
11 | elements: [],
12 | addElement: (el) => {
13 | set((state) => ({
14 | elements: [...state.elements, el],
15 | }))
16 | },
17 | removeElement:(el) => {
18 | set(state => ({
19 | elements: [state.elements.filter(setting => setting !== el)]
20 | }))
21 | }
22 | }))
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/components/Footer/SocialIcons.tsx:
--------------------------------------------------------------------------------
1 | import { FaGithub, FaTwitterSquare } from 'react-icons/fa'
2 | import styled from 'styled-components'
3 | import { IconLink } from '../common/Buttons/IconLink'
4 |
5 | const ContainerBox = styled.div`
6 | display: flex;
7 | flex-direction: row;
8 | justify-content: flex-end;
9 | align-items: center;
10 | padding: 2px;
11 | position: absolute;
12 | right: 30px;
13 | bottom: 20px;
14 | `
15 |
16 | const FeedbackLink = styled.a`
17 | font-weight: bold;
18 | font-size: ${props => props.theme.fontSize.small};
19 | padding: 3px 8px;
20 | margin-left: 2px;
21 | color: ${props => props.theme.text.default};
22 | &:hover {
23 | color: ${props => props.theme.text.primary};
24 | }
25 | `
26 |
27 | export const SocialIcons = () => (
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | Feedback
37 |
38 |
39 | )
40 |
--------------------------------------------------------------------------------
/src/components/Footer/VideoButton/VideoButton.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { FaVideo } from 'react-icons/fa'
3 | import { Button } from '../../common/Buttons/Button'
4 |
5 | export const VideoButton = ({ callback = () => null }) => {
6 | const [ active, setActive ] = React.useState(true)
7 |
8 | const onClick = () => {
9 | setActive(!active)
10 | callback()
11 | }
12 |
13 | return (
14 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/Header/Header.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import styled from "styled-components";
3 | import kissingCat from './../../assets/kissingCat.svg'
4 | import loveCat from './../../assets/loveCat.svg'
5 |
6 | const StyledHeader = styled.a`
7 | &:before {
8 | content: url(${kissingCat});
9 | margin-right: 5px;
10 | }
11 | &:after {
12 | display:none;
13 | content: url(${loveCat});
14 | }
15 | display: flex;
16 | flex-direction: row;
17 | align-items: center;
18 | position: fixed;
19 | margin: 10px 0;
20 | right: 10px;
21 | /* background: ${props => props.theme.base['6']}; */
22 | padding: 10px;
23 | border-radius: 5px;
24 | z-index: 10000;
25 | font-weight: 500;
26 | font-size: 1.25rem;
27 | text-decoration: none;
28 | color:#000;
29 | height: 40px;
30 | &:hover {
31 | /* background-color: ${props => props.theme.base['4']}; */
32 | &:before {
33 | content: url(${loveCat});
34 | margin-right: 5px;
35 | }
36 | }
37 | `
38 |
39 | export const Header = ({children=""}) => (
40 | {children}
41 | )
--------------------------------------------------------------------------------
/src/components/JitsiConnection/JitsiConnection.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react'
2 | import { useConnectionStore } from './../../store/ConnectionStore'
3 | import { useConferenceStore } from './../../store/ConferenceStore'
4 | import {useParams} from 'react-router-dom'
5 |
6 | /* globals: JitisMeetJS */
7 |
8 | //no reload?
9 | const getConnection = store => store.connected
10 | const getInitJitsi = store => store.initJitsiMeet
11 | const getConnectServer = store => store.connectServer
12 | const getJitsiMeet = store => store.initJitsiMeet
13 | const getDisconnectServer = store => store.disconnectServer
14 | const getInitConference = store => store.initConference
15 |
16 | const JitsiConnection = () => {
17 | const disconnectServer = useConnectionStore(getDisconnectServer)
18 | const jsMeet = useConnectionStore(getJitsiMeet)
19 | const connectServer = useConnectionStore(getConnectServer)
20 | const initJitsiMeet = useConnectionStore(getInitJitsi)
21 | const connected = useConnectionStore(getConnection)
22 | const initConference = useConferenceStore(getInitConference)
23 | const {id} = useParams() //get Id from url, should error check here I guess
24 |
25 | useEffect(() => {
26 | //jitsi might have been initialized in enter.tsx.
27 | //But session "/session/:id" might have been called directly, so initJitsiMeet should be called.
28 | initJitsiMeet()
29 | },[initJitsiMeet])
30 |
31 | useEffect(() => {
32 | connectServer()
33 | return ()=> disconnectServer()
34 | },[connectServer, disconnectServer])
35 |
36 | useEffect(() => {
37 | if(jsMeet && connected) {
38 | initConference(id)
39 | }
40 | },[jsMeet, connected, initConference, id])
41 |
42 |
43 | return (
44 | null
45 | )
46 | }
47 |
48 |
49 | export default JitsiConnection
--------------------------------------------------------------------------------
/src/components/JitsiConnection/jitsiOptions.tsx:
--------------------------------------------------------------------------------
1 | // SETTINGS - these are Connection and Room Options for the Jitsi lib
2 |
3 | export interface IJitsiInitOptions {
4 | disableAudioLevels?: boolean
5 | disableSimulcast?: boolean
6 | useIPv6?: boolean
7 | enableWindowOnErrorHandler?: boolean
8 | disableThirdPartyRequests?: boolean
9 | enableAnalyticsLogging?: boolean
10 | preferredCodec?: string
11 | disabledCodec?: string
12 | preferH264?: boolean //deprecated
13 | videoConstraints?: any
14 | }
15 |
16 | // -------------------------
17 |
18 | export const conferenceName = process.env.REACT_APP_DEMO_SESSION || "chatmosphere"
19 |
20 | export const conferenceOptions = {
21 | openBridgeChannel: false, //what is this doing?
22 | channelLastN: 3, //does work
23 | enableLayerSuspension: true,
24 | }
25 |
26 | export const jitsiInitOptions:IJitsiInitOptions = {
27 | // // useIPv6:true, // can be off
28 | disableAudioLevels: false,
29 | disableSimulcast: false,
30 | disableThirdPartyRequests: true,
31 | // preferredCodec: 'H264',
32 | preferredCodec: 'VP8',
33 | // disabledCodec: 'VP8',
34 | // preferH264: true,
35 | // enableWindowOnErrorHandler: false,
36 | // disableThirdPartyRequests: false,
37 | // enableAnalyticsLogging: true
38 | }
39 |
40 | export const localTrackOptions = {
41 | devices: ["audio", "video"],
42 | resolution: 480,
43 | minFps: 15,
44 | maxFps: 15,
45 | constraints: {
46 | video: {
47 | height: {
48 | ideal: 360,
49 | max: 360,
50 | min: 240,
51 | },
52 | frameRate: {
53 | max: 15,
54 | },
55 | },
56 | },
57 | // room: { size: { x: 6000, y: 6000 } },
58 | // get user() {
59 | // return {
60 | // //center the room
61 | // initialPosition: { x: this.room.size.x / 2, y: this.room.size.y / 2 },
62 | // size: { x: 200, y: 200 },
63 | // }
64 | // },
65 | }
66 |
67 | export const getConnectionOptions = (): object => {
68 | let serverConfig: any = {}
69 | try {
70 | serverConfig = require("./../../serverConfig")
71 | } catch (e) {
72 | serverConfig = require("./../../serverConfig-example")
73 | }
74 |
75 | return serverConfig.connectionOptions
76 | }
77 |
78 | // export const transformWrapperOptions: PropsList = {
79 | // wheel: { step: 50 },
80 | // scale: 1,
81 | // //center the window, considering the size of the user view
82 | // defaultPositionX: -localTrackOptions.user.initialPosition.x+(window.innerWidth-localTrackOptions.user.size.x)/2,
83 | // defaultPositionY: -localTrackOptions.user.initialPosition.y+(window.innerHeight-localTrackOptions.user.size.y)/2,
84 | // positionX: 0,
85 | // positionY: 0,
86 | // options: {
87 | // centerContent: false,
88 | // limitToBounds: true,
89 | // limitToWrapper: true,
90 | // minScale: 0.2,
91 | // // maxPositionX:10000, maxPositionY:10000,
92 | // // minPositionX:0, minPositionY:0
93 | // },
94 | // // scalePadding:{animationTime:10},
95 | // pan: { velocityEqualToMove: true },
96 | // pinch: { disabled: true },
97 | // }
98 |
--------------------------------------------------------------------------------
/src/components/NameTag/NameTag.tsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const NameTag = styled.div`
4 | margin-top: 5px;
5 | border-radius: ${props => props.theme.radius.small};
6 | font-weight: 500;
7 | color:${props => props.theme.base['1']};
8 | background: ${props => props.theme.base['5']};
9 | text-align: center;
10 | display: flex;
11 | flex-direction: row;
12 | justify-content: center;
13 | align-items: center;
14 | padding: 8px 5px;
15 | &:hover {
16 | background: ${props => props.theme.base['4']};
17 | border: 1px solid ${props => props.theme.base['4']};
18 | }
19 | `;
20 |
--------------------------------------------------------------------------------
/src/components/PanWrapper/PanWrapper.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react"
2 | import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch"
3 | import { useLocalStore } from "./../../store/LocalStore"
4 | import { panOptions, transformWrapperOptions} from './panOptions'
5 |
6 | const panChange = store => store.onPanChange
7 | const setPos = store => store.setLocalPosition
8 |
9 | export const PanWrapper = ({children}) => {
10 |
11 | const onPanChange = useLocalStore(panChange)
12 | const setLocalPosition = useLocalStore(setPos)
13 |
14 | // const throttlePan = useCallback(throttle(onPanChange, 200),[])
15 | // const throttleSendPos = useCallback(throttle(setLocalPosition, 200),[])
16 |
17 | useEffect(() => {
18 | onPanChange({scale:transformWrapperOptions.scale,positionX:transformWrapperOptions.defaultPositionX,positionY:transformWrapperOptions.defaultPositionY})
19 | setLocalPosition(panOptions.user.initialPosition)
20 | // throttlePan({scale:transformWrapperOptions.scale,positionX:transformWrapperOptions.defaultPositionX,positionY:transformWrapperOptions.defaultPositionY})
21 | // throttleSendPos(panOptions.user.initialPosition)
22 | },[onPanChange, setLocalPosition])
23 |
24 | return (
25 |
31 |
32 | {children}
33 |
34 |
35 | )
36 | }
--------------------------------------------------------------------------------
/src/components/PanWrapper/panOptions.ts:
--------------------------------------------------------------------------------
1 | import { PropsList } from "react-zoom-pan-pinch/dist/store/interfaces/propsInterface"
2 |
3 | export const panOptions = {
4 | room: { size: { x: 6000, y: 6000 } },
5 | get user() {
6 | return {
7 | //center the room
8 | initialPosition: { x: this.room.size.x / 2 - (Math.random() * 200 - 100), y: this.room.size.y / 2 - (Math.random() * 200 - 100) },
9 | size: { x: 200, y: 200 },
10 | }
11 | },
12 | }
13 |
14 | export const transformWrapperOptions: PropsList = {
15 | wheel: { step: 50 },
16 | scale: 1.0,
17 | //center the window, considering the size of the user view
18 | defaultPositionX: -panOptions.user.initialPosition.x +(window.innerWidth-panOptions.user.size.x)/2,
19 | defaultPositionY: -panOptions.user.initialPosition.y+(window.innerHeight-panOptions.user.size.y)/2,
20 | positionX: 0,
21 | positionY: 0,
22 | options: {
23 | centerContent: false,
24 | limitToBounds: true,
25 | limitToWrapper: true,
26 | minScale: 0.3,
27 | maxScale: 3,
28 | // maxPositionX:500, maxPositionY:500,
29 | // minPositionX:0, minPositionY:0
30 | },
31 | // scalePadding:{animationTime:10},
32 | pan: {
33 | velocityEqualToMove: true,
34 | },
35 | pinch: { disabled: true },
36 | }
37 |
--------------------------------------------------------------------------------
/src/components/Room/Room.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react"
2 | import { ReactNode } from "react"
3 | import styled from "styled-components"
4 | import { panOptions } from "../PanWrapper/panOptions"
5 |
6 | /* fixed size won't work, because when scale is 1 there will be room to pan; but the plugin won't allow it because scale is 1.
7 | the fix is to set the size of the react-transform-component and react-transform-element exlusively (see App.css) */
8 |
9 | const RoomContainer = styled.div<{ bg: string }>`
10 | width: ${panOptions.room.size.x}px;
11 | height: ${panOptions.room.size.y}px;
12 | box-sizing: border-box;
13 | display: block;
14 | background-image: ${(props) =>
15 | props.bg ? "url(props.bg)" : "none"};
16 | `
17 |
18 | // const Background = styled.div`
19 | // background-image:url("/build/favicon.ico");
20 | // opacity:0.1;
21 | // width:100%;
22 | // height:100%;
23 | // `
24 |
25 | interface Props {
26 | children?: ReactNode
27 | identifier?: string
28 | }
29 |
30 | export const Room: React.FC = ({ children, identifier }) => {
31 | const [background, setBackground] = useState("")
32 | useEffect(() => {
33 | const getBg = async () => {
34 | const data = await fetch(
35 | `https://api.eventyay.com/v1/events/${identifier}/chatmosphere`,
36 | {
37 | headers: {
38 | Accept: "application/vnd.api+json",
39 | },
40 | },
41 | ).then((res) => res.json())
42 | const bg = data["data"]["attributes"]["bg-img-url"].toString()
43 | console.log(bg)
44 | setBackground(bg)
45 | }
46 |
47 | getBg().catch(console.error)
48 | }, [])
49 |
50 | return (
51 |
52 | {/* */}
53 | {children}
54 |
55 | )
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/User/Localuser/LocalUserContainer.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useLocalStore } from '../../../store/LocalStore'
3 | import DragWrapper from '../../DragWrapper/DragWrapper'
4 |
5 | export const UserDragContainer = ({children}) => {
6 |
7 | const pos = useLocalStore(store => store.pos)
8 | const zoomTransformPan = useLocalStore(store => store.pan)
9 | const zoomTransformScale = useLocalStore(store => store.scale)
10 | const setLocalPosition = useLocalStore(store => store.setLocalPosition)
11 |
12 | // const throttledSendPos = useCallback(throttle(setLocalPosition, 200),[]) //this works pretty well; TODO: test & implement
13 |
14 | return (
15 |
16 | {children}
17 |
18 | )
19 | }
--------------------------------------------------------------------------------
/src/components/User/Localuser/Localuser.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import { Localuser } from './Localuser';
4 |
5 |
6 | it("renders correctly", () => {
7 |
8 | })
--------------------------------------------------------------------------------
/src/components/User/Localuser/Localuser.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from "react"
2 | import styled from "styled-components"
3 | import { useLocalStore } from "../../../store/LocalStore"
4 | import LocalVideo from "./components/LocalVideo"
5 | import LocalAudio from "./components/LocalAudio"
6 | import { MuteIndicator } from "./components/MuteIndicator"
7 | import { UserBackdrop } from "../components/Backdrop/UserBackdrop"
8 | import { panOptions } from "../../PanWrapper/panOptions"
9 | import { AudioRadius } from "./components/AudioRadius"
10 | import { NameContainer } from "./components/NameContainer"
11 | import LocalDesktop from "../../../addons/Screenshare/components/LocalDesktop"
12 | import { VideoContainer } from "../RemoteUser/DesktopVideo"
13 |
14 | const Container = styled.div`
15 | width: ${panOptions.user.size.x}px;
16 | height: ${panOptions.user.size.y}px;
17 | position: absolute;
18 | border: 4px solid ${(props) => props.theme.base[4]};
19 | border-radius: 300px;
20 | cursor: default;
21 | &:active {
22 | cursor: default;
23 | }
24 | `
25 |
26 | interface ILocaluser {
27 | // panChanged: (callback: (params) => void) => void
28 | audioRadius?: boolean
29 | }
30 |
31 |
32 | export const Localuser: React.FC = ({audioRadius = false}) => {
33 |
34 | const audioTrack = useLocalStore((store) => store.audio)
35 | const videoTrack = useLocalStore((store) => store.video)
36 | const videoType = useLocalStore(store => store.videoType)
37 | const isMute = useLocalStore((store) => store.mute)
38 | const isOnStage = useLocalStore((store) => store.onStage)
39 |
40 | const localUserNode = useRef(null)
41 |
42 | return (
43 |
46 | {audioRadius && }
47 |
48 | {isOnStage && }
49 |
50 | {!isOnStage &&
51 | <>
52 |
53 | {(videoTrack && videoType === "camera" ) && (
54 |
55 | )}
56 | {(videoTrack && videoType === "desktop") && (
57 |
58 | )}
59 | >
60 | }
61 |
62 | {audioTrack && (
63 |
64 | )}
65 | {isMute && }
66 |
67 |
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/src/components/User/Localuser/components/AudioRadius.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 |
3 | export const AudioRadius = styled.div`
4 | position: absolute;
5 | transform: translate(-50%, -50%);
6 | left: 50%;
7 | top: 50%;
8 | border: 2px dotted #ccc;
9 | width: 1100px;
10 | height: 1100px;
11 | display: block;
12 | border-radius: 800px;
13 | pointer-events: none;
14 | background: radial-gradient();
15 | z-index: -100;
16 | `
--------------------------------------------------------------------------------
/src/components/User/Localuser/components/LocalAudio.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react';
2 | import { useConnectionStore } from '../../../../store/ConnectionStore';
3 | import { useConferenceStore } from '../../../../store/ConferenceStore';
4 |
5 | const LocalAudio:React.FC<{track:IAudioTrack}> = ({track}) => {
6 | const myRef:any = useRef()
7 | const room = useConferenceStore(store => store.conferenceObject)
8 | const jsMeet = useConnectionStore(store => store.jsMeet)
9 |
10 | // const [audioLevel, setAudioLevel] = useState(0)
11 |
12 | useEffect(() => {
13 | const el = myRef.current
14 | if(track?.containers?.length === 0) track.attach(myRef.current)
15 | // track.addEventListener(jsMeet?.events.track.TRACK_AUDIO_LEVEL_CHANGED, setAudioLevel)
16 | return (() => {
17 | // track.removeEventListener(jsMeet?.events.track.TRACK_AUDIO_LEVEL_CHANGED, setAudioLevel)
18 | track.detach(el)
19 | // track.dispose()
20 | })
21 | },[track,jsMeet])
22 |
23 | useEffect(() => {
24 | room?.addTrack(track)
25 | .catch(error => {});//the track might have been added already, handle the promise error
26 | },[room,track])
27 |
28 | return
29 | }
30 |
31 | export default LocalAudio
--------------------------------------------------------------------------------
/src/components/User/Localuser/components/LocalVideo.tsx:
--------------------------------------------------------------------------------
1 | import React, { memo, useEffect, useRef } from 'react';
2 | import styled from 'styled-components';
3 | import { useConferenceStore } from '../../../../store/ConferenceStore';
4 |
5 | const Video = styled.video`
6 | width: 200px;
7 | height: 200px;
8 | object-position: 50% 50%;
9 | display: block;
10 | border-radius: 100px;
11 | object-fit: cover;
12 | transform: scaleX(-1);
13 | `
14 |
15 | const LocalVideo:React.FC<{track:IVideoTrack}> = memo(({track}) => {
16 | const myRef:any = useRef()
17 | const room = useConferenceStore(store => store.conferenceObject)
18 |
19 | useEffect(() => {
20 | room?.addTrack(track) //TODO should be done in store I think?
21 | .catch(error => console.log(error))//the track might have been added already, handle the promise error
22 | return () => {
23 | // room?.removeTrack(tmpTrack) //we're replacing, not deleting and adding a new one
24 | }
25 | },[room, track])
26 |
27 | useEffect(()=> {
28 | const el = myRef.current
29 | if(track?.containers?.length === 0) track.attach(el)
30 | return () => {
31 | track.detach(el)
32 | }
33 | },[track])
34 |
35 |
36 | return
37 | })
38 |
39 | export default LocalVideo
--------------------------------------------------------------------------------
/src/components/User/Localuser/components/MuteIndicator.tsx:
--------------------------------------------------------------------------------
1 | import { useLocalStore } from '../../../../store/LocalStore';
2 | import {ReactComponent as MuteCat} from '../../../../assets/muteCatSmall.svg'
3 | import { MuteContainer } from '../../RemoteUser/MuteIndicator';
4 |
5 |
6 |
7 | export const MuteIndicator = () => {
8 |
9 | const {toggleMute} = useLocalStore()
10 |
11 | const handleClick = () => {
12 | toggleMute()
13 | }
14 |
15 | return (
16 |
17 |
18 |
19 | )
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/src/components/User/Localuser/components/NameContainer.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useEffect, useState } from "react";
2 | import { useConferenceStore } from "../../../../store/ConferenceStore";
3 | import { NameTag } from "../../../NameTag/NameTag";
4 | import {InputField} from '../../../common/Input/InputField'
5 |
6 | export const NameContainer = () => {
7 |
8 | const [name, setName] = useState("Enter Your Name");
9 | const [isActive, setActive] = useState(false);
10 | const setDisplayName = useConferenceStore(store => store.setDisplayName)
11 | const displayName = useConferenceStore(store => store.displayName)
12 | const onClick = useCallback(() => {setActive(true);},[]);
13 |
14 | useEffect(()=>{
15 | setName(displayName)
16 | },[displayName])
17 |
18 | const onChange = (e) => {
19 | setName(e.target.value);
20 | };
21 | const onFocusLost = () => {
22 | setActive(false);
23 | setDisplayName(name)
24 | };
25 |
26 | if (isActive) {
27 | return (
28 |
34 | );
35 | } else {
36 | return {name};
37 | }
38 | };
39 |
40 | const InputContainer = (props) => {
41 | const handleClose = useCallback(() => {
42 | props?.close();
43 | },[props]);
44 |
45 | useEffect(() => {
46 | document.addEventListener("keyup", (e) => {
47 | if (e.key === "Escape" || e.key === "Enter") {
48 | handleClose();
49 | }
50 | });
51 | return document.removeEventListener("keyup", (e) => {
52 | if (e.key === "Escape" || e.key === "Enter") handleClose();
53 | });
54 | }, [handleClose]);
55 |
56 | return ;
57 | };
58 |
--------------------------------------------------------------------------------
/src/components/User/RemoteUser/AudioTrack.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useRef } from 'react';
2 | import { useLocalStore } from '../../../store/LocalStore';
3 | import { useConferenceStore } from "../../../store/ConferenceStore";
4 |
5 | export const AudioTrack = ({id, volume}) => {
6 | const audioTrack = useConferenceStore(useCallback(store => store.users[id]['audio'], [id]))
7 | const myRef:any = useRef()
8 |
9 | const localAudioTrack = useLocalStore((store) => store.audio)
10 |
11 |
12 | useEffect(() => {
13 | myRef.current.volume = volume
14 | },[volume])
15 |
16 | useEffect(() => {
17 | const currentElement = myRef.current
18 | audioTrack?.attach(currentElement)
19 | return(() => {
20 | audioTrack?.detach(currentElement)
21 | audioTrack?.dispose()
22 | })
23 | },[audioTrack])
24 |
25 | //On Firefox, if the media permissions are "Allowed Temporarily" ("Remember this decision" is unchecked in permission prompt);
26 | //then Autoplay will be set to Block Audio; resulting the audio of other users not to be heard.
27 | //To overcome this problem, we reattach remote audio track on permission is granted. If permission is granted, localAudioTrack is available; so we track its object reference.
28 | useEffect(() => {
29 | const currentElement = myRef.current
30 | audioTrack?.detach(currentElement)
31 | audioTrack?.attach(currentElement)
32 | },[localAudioTrack, audioTrack])
33 |
34 | return (
35 |
36 | )
37 | }
38 |
39 |
40 | // const Audio:React.FunctionComponent<{track:any, volume:number} & React.HTMLAttributes> = ({track, volume})=> {
41 |
42 | // const myRef:any = useRef()
43 |
44 | // useEffect(() => {
45 | // const el = myRef.current
46 | // if(el) {
47 | // if(el) track?.attach(el)
48 | // }
49 | // return(() => {
50 | // track?.detach(el)
51 | // })
52 | // },[track])
53 |
54 | // useEffect(() => {
55 | // if(myRef.current & myRef.current.volume) myRef.current.volume = volume
56 | // },[volume])
57 |
58 | // return ()
59 | // }
--------------------------------------------------------------------------------
/src/components/User/RemoteUser/ConnectedUser.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useRef } from 'react';
2 | import { useConferenceStore } from '../../../store/ConferenceStore';
3 | import { UserBackdrop } from '../components/Backdrop/UserBackdrop';
4 | import { AudioTrack } from './AudioTrack';
5 | import { MuteIndicator } from './MuteIndicator';
6 | import { VideoContainer, VideoTrack } from "./VideoTrack"
7 | import { NameTag } from '../../NameTag/NameTag';
8 | import { useLocalStore } from '../../../store/LocalStore';
9 | import { DesktopVideo } from './DesktopVideo';
10 |
11 |
12 | export const ConnectedUser = ({id}) => {
13 |
14 | const myPos = useConferenceStore(useCallback(store => store.users[id]['pos'], [id]))
15 | const myVolume = useConferenceStore(useCallback(store => store.users[id]['volume'], [id]))
16 | const isMute = useConferenceStore(useCallback(store => store.users[id]['mute'],[id]))
17 | const calculateVolume = useConferenceStore(useCallback(store => store.calculateVolume, []))
18 | const calculateUserInRadius = useLocalStore(useCallback((store) => store.calculateUserInRadius,[]))
19 | // const videoType = useConferenceStore(store => store.users[id]?.['video']?.['videoType'])
20 | const calculateUserOnScreen = useLocalStore(useCallback((store) => store.calculateUserOnScreen,[]))
21 | const user = useConferenceStore(useCallback(store => store.users[id], [id]))
22 | const isOnStage = user.properties?.onStage
23 | const dragRef = useRef()
24 |
25 | useEffect(() => {
26 | calculateVolume(id)
27 | calculateUserInRadius(id)
28 | calculateUserOnScreen(user, dragRef.current)
29 | },[id, calculateVolume, calculateUserInRadius, calculateUserOnScreen, user, myPos])
30 |
31 | // * on first switch to desktop its still recognized as camera - on second swtich its correctly recognized as desktop, so on track added needs to set type
32 | // * the double deletion by event handler is a problem
33 |
34 | // ATOMIC PICK correctly is undefined until camera image is really there!! so use user.video?.videoType instead fo user.videoType!!!
35 | return(
36 |