Compare commits

...

58 Commits

Author SHA1 Message Date
Дамян Минков
54161a828c Tests PR testing do not merge
Added a new line for formatting in the README.
2025-11-30 11:55:06 -07:00
Saúl Ibarra Corretgé
9013881f76 chore(ci) clean Android build to save space
Add git clean command to CI workflow for Android.
2025-11-27 12:45:54 +01:00
dependabot[bot]
b6e7e0a19e chore(ci): bump actions/setup-node from 4 to 6
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-27 12:21:16 +01:00
dependabot[bot]
ae42e42534 chore(ci): bump actions/checkout from 4 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-27 11:17:58 +01:00
Saúl Ibarra Corretgé
21e2504cf9 chore(ci) add Dependabot configuration for GitHub Actions 2025-11-27 09:55:51 +01:00
dependabot[bot]
7a9ba79783 chore(deps): bump node-forge from 1.3.1 to 1.3.2
Bumps [node-forge](https://github.com/digitalbazaar/forge) from 1.3.1 to 1.3.2.
- [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md)
- [Commits](https://github.com/digitalbazaar/forge/compare/v1.3.1...v1.3.2)

---
updated-dependencies:
- dependency-name: node-forge
  dependency-version: 1.3.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-27 08:51:30 +01:00
Damien Fetis
1f5a3b5b0f fix(recording): allow samesite iframe embeds to work with local recording
* fix(recording): allow samesite iframe embeds to work with local recording

Skip capture handle validation when inside an iframe to ensure local
recording works. This only applies if the iframe is served from the
same domain.

* fix(recording): add missing line breaks for better readability in LocalRecordingManager
2025-11-26 07:35:23 -07:00
bgrozev
fe2aff4f3c chore(deps) lib-jitsi-meet@latest (#16706)
https://github.com/jitsi/lib-jitsi-meet/compare/v2114.0.0+0e62818c...v2115.0.0+cc2f34c2
2025-11-25 11:57:53 -06:00
bgrozev
d847f6f96b feat: Accept transcription messages from non-participant entities. (#16631) 2025-11-25 11:06:16 -06:00
damencho
45ce467dcd feat(polls): Fixes support for breakout rooms.
Fixes #16693.
2025-11-25 09:30:10 -06:00
bgrozev
2b81fa6bd3 config(webpack): Listen on localhost by default. (#16703) 2025-11-25 08:42:19 -06:00
damencho
6f6100ceb2 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v2113.0.0+ffcffaa7...v2114.0.0+0e62818c
2025-11-21 05:56:46 -06:00
Calin-Teodor
62cd1c29d7 align react native docker version with package json version 2025-11-21 13:45:53 +02:00
Дамян Минков
64869e8970 fix(deb): Adds Include on upgrade prosody. (#16687)
* fix(deb): Adds Include on upgrade prosody.

* squash: Restart if config has changed.
2025-11-20 16:13:29 -06:00
Saúl Ibarra Corretgé
29464e6886 Revert "chore(deps-dev): bump @react-native-community/cli from 15.0.1 to 17.0.1"
This reverts commit 421b21edeb.
2025-11-20 13:45:05 +01:00
Saúl Ibarra Corretgé
5ed92f2bc5 fix(deps) use Olm from npm
The Matrix GitLab repo was behind CF and thus affected by today's
outage.

Since they released the last Olm version to npm, let's consume that one.
2025-11-20 13:01:52 +01:00
srijan
048d12de24 feat(logging): replace console.* with centralized logger infrastructure (#16655)
* feat(logging): replace console.* with centralized logger infrastructure

* fix(logging): remove logger from size-constrained bundles
2025-11-19 18:31:35 -06:00
emrah
40c240c7ca fix(lang): add the missing translation (German) 2025-11-18 17:00:51 -06:00
Boris Grozev
289c1907e7 test: Skip iframe tests when the API is disabled. 2025-11-18 17:00:21 -06:00
damencho
35adea48ae fix(muc_rate_limit): Check connection when processing rate limited events.
If it happens that a connection was closed during waiting in the rate limited queue, we want to ignore those occupant events.
2025-11-18 14:24:26 -06:00
bgrozev
d72114d5bc test: Expect pin to have digits only, configure length. (#16670) 2025-11-18 12:00:06 -06:00
bgrozev
2f6b6ca837 fix: Fix transcription test expectation. (#16664) 2025-11-18 07:45:07 -06:00
bgrozev
615bbdc39b test: Order attachments by participant. (#16663) 2025-11-17 16:12:58 -06:00
bgrozev
ef97778158 test: Assert jaas visitors enabled. (#16662) 2025-11-17 16:12:45 -06:00
bgrozev
2885f39355 More test expectations (#16661)
* test: Add iframe API expectation.

* test: Add expectations for recording and live streaming.

* test: Remove iframe references from jaas/.

* test: Add a transcription expectation.
2025-11-17 16:12:34 -06:00
damencho
ae256b23b8 fix(cleanup_backend): Avoids cleanup when breakout rooms are active. 2025-11-17 12:56:00 -06:00
Дамян Минков
412aa83268 feat(jwt): Supports JWKS endpoint. (#16649)
* feat(jwt): Supports JWKS endpoint.

* squash: Allow setting just cache_keys_url.
2025-11-17 09:48:28 -06:00
damencho
f4c61e4760 fix(prosody): Order room-destroyed event.
Make sure we execute before prosody cleans it up from the list of room. If we try to look it up after that we will not find it. If we also add at 0 we cannot guarantee the order of hook execution.
2025-11-14 14:01:12 -06:00
Jaya Allamsetty
f313fb81d0 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v2109.0.0+cb9d000c...v2113.0.0+ffcffaa7
2025-11-13 21:25:10 -05:00
Srijan
975af80e27 fix(chat): remove debug console.log statements from resize handlers 2025-11-13 16:52:24 -06:00
damencho
0a30a51bab feat(localstorage): Filter items. 2025-11-13 11:02:34 -06:00
Дамян Минков
54e28e223c fix(tests): Split participants presence. (#16642)
* fix(tests): Split participants presence.

* squash: Drop unused listener.
2025-11-12 11:42:43 -06:00
Edgars Voroboks
a4def96763 fix(lang): Update Latvian language translation 2025-11-12 11:42:29 -06:00
Hristo Terezov
dad4fb9e06 Revert "fix(large-video): Prevents unnecessary updates when container is hidden"
This reverts commit 6deb0a6385.
2025-11-11 12:48:16 -06:00
Vishal Malyan
3772b9a5ae feat(toolbar): implement toolbar background color via configOverwrite for web and mobile
* Change toolbar background color from IFrame API #16468 fixed

* fix(toolbar #16468): implement toolbar background color via configOverwrite for web and mobile

* keep toolbarConfig defaults commented in config.js

* add trailing comma to commented toolbarConfig.backgroundColor

* fix: resolve linting errors

Fixes #16468
2025-11-11 07:02:28 -06:00
bgrozev
89b9c75242 test: Default sort by test order in allure report. (#16636) 2025-11-10 14:07:23 -06:00
Дамян Минков
b24b60b735 fix(tests): Wait for transcriptions to be off via an event. (#16635) 2025-11-10 12:55:35 -06:00
Дамян Минков
486a1f6511 fix(tests): Avoids being blocked by notification when clicking toolbar buttons
* fix(tests): Avoids clicking UI buttons to avoid being blocked by notification.

In AV moderation tests sometimes clicking mute/unmute buttons is blocked by askedToUnmute notification.

* squash: fix waiting for button.

* squash: adds some docs.
2025-11-10 11:04:36 -06:00
Werner Fleischer
80b3f1d7d4 fix(mod_jitsi_permissions): Use correct session on moderator revocation
In the `process_set_affiliation` function, an undefined `session` variable was used when revoking moderator privileges. This prevented the `jitsi_meet_context_features` from being cleared for the occupant.
2025-11-10 05:07:47 -06:00
dependabot[bot]
421b21edeb chore(deps-dev): bump @react-native-community/cli from 15.0.1 to 17.0.1
Bumps [@react-native-community/cli](https://github.com/react-native-community/cli/tree/HEAD/packages/cli) from 15.0.1 to 17.0.1.
- [Release notes](https://github.com/react-native-community/cli/releases)
- [Changelog](https://github.com/react-native-community/cli/blob/main/packages/cli/CHANGELOG.md)
- [Commits](https://github.com/react-native-community/cli/commits/v17.0.1/packages/cli)

---
updated-dependencies:
- dependency-name: "@react-native-community/cli"
  dependency-version: 17.0.1
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-07 09:06:19 +01:00
Дамян Минков
a58b0d9a85 fix(tests): Update lobby test. (#16618)
* fix(tests): Update lobby test.
* test: Include name in "hangup" logs.
---------

Co-authored-by: Boris Grozev <boris@jitsi.org>
2025-11-06 14:56:43 -06:00
Дамян Минков
1aca8ab985 feat(dialog): Adds name to all dialogs. (#16626)
* feat(dialog): Adds name to all dialogs.

The name is used for debugging purposes to be added to logs.

* squash: Drop empty string.
2025-11-06 09:49:30 -06:00
Jaya Allamsetty
f9daba728f chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v2101.0.0+8061f52a...v2109.0.0+cb9d000c
2025-11-05 15:45:04 -05:00
damencho
fbb6456317 fix(wait_for_host): Make sure the main room is set back to non-persistent. 2025-11-05 09:26:13 -06:00
damencho
52ead26bed feat(prosody): Adds a module to cleanup room with just service components in it. 2025-11-04 16:42:09 -06:00
damencho
8d1da83e3c fix(persistent_lobby): Avoids calling destroy twice. 2025-11-04 16:41:58 -06:00
Jaya Allamsetty
5453b615f5 fix(tests): Add missing helper function. 2025-11-04 11:04:54 -05:00
Jaya Allamsetty
81a7301a3e Revert "fix(large-video)pin prev speaker on stage when local user is dominant speaker. (#16511)"
This reverts commit 82d4628976.
2025-11-04 11:04:54 -05:00
Jaya Allamsetty
1138b7779b Revert "fix(filmstrip) Fixes an issue where remote tiles can disappear when SS is started"
This reverts commit 077602c427.
2025-11-04 11:04:54 -05:00
Calin-Teodor
2fd653d928 fix(chat): be explicit about screen navigation when polls are disabled 2025-11-04 15:35:24 +02:00
eastmancr
012c9fb329 feat(base): add WebRTC availability detection (#16608)
* feat(base/environment) add WebRTC availability detection

* feat(base/unsupported-browser) switch to JitsiMeetJS WebRTC detection

* fix(static/webrtcUnsupported) remove links
2025-11-03 14:25:10 -06:00
damencho
fdf95444e9 fix(lobby): Hide login button if authenticated(jwt is available). 2025-11-03 14:22:53 -06:00
Hristo Terezov
919c60b3d2 feat(chat): Add disableChat configuration option
Introduces a comprehensive disableChat config option that disables the entire chat feature including button visibility, notifications, sounds, private messages, and keyboard shortcuts. When disabled, the chat tab is hidden from the chat panel while allowing other tabs (polls, files, CC) to remain accessible.
2025-11-03 12:44:29 -06:00
Hristo Terezov
e02c4e8f7f feat(toolbox): Add polls and file sharing buttons to overflow menu
Adds dedicated buttons for polls and file sharing in the toolbar overflow menu, following the pattern of the CC button. Both buttons open the chat panel with their respective tab selected when clicked.
2025-11-03 12:44:29 -06:00
Дамян Минков
3fd9ce5f11 * fix(lobby): Updates metadata on destroy lobby room and let in participants on empty main.
* fix(lobby): Updates metadata on destroy lobby room.

* fix(visitors): Let people join lobby when main room is empty but with lobby.
2025-10-30 16:07:46 -05:00
bgrozev
93022b3281 feat: Filter transcription results. (#16606) 2025-10-30 15:38:00 -05:00
Jaya Allamsetty
5d63b31071 fix(video-layout) Possibly fixes auto-pinning of SS in a large call.
When a user joins a very large call with SS, sometime SS is not auto-pinned to stage. This may happen when lot of participant joins are processed at the same time and therefore the state for remoteScreenShares may not get updated in time. Added extra logging to help debug if this issue reproduces.
2025-10-30 15:21:29 -04:00
Jaya Allamsetty
4432f727a4 fix(tests) Check for continguous thunbnails in the filmstrip 2025-10-30 12:21:03 -04:00
170 changed files with 1711 additions and 995 deletions

12
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
version: 2
updates:
# Monitor GitHub Actions versions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- "dependencies"
- "github-actions"
commit-message:
prefix: "chore(ci)"

View File

@@ -7,7 +7,7 @@ jobs:
name: Luacheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install luarocks
run: sudo apt-get --install-recommends -y install luarocks

View File

@@ -7,8 +7,8 @@ jobs:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -42,8 +42,8 @@ jobs:
matrix:
os: [macos-latest, ubuntu-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -59,8 +59,8 @@ jobs:
name: Build mobile bundle (Android)
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -74,8 +74,8 @@ jobs:
name: Build mobile bundle (iOS)
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -103,10 +103,10 @@ jobs:
android-sdk-build:
name: Build mobile SDK (Android)
runs-on: ubuntu-latest
container: reactnativecommunity/react-native-android:v18.0
container: reactnativecommunity/react-native-android:v15.0
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -119,12 +119,15 @@ jobs:
cd android
./gradlew :sdk:clean
./gradlew :sdk:assembleRelease
- run: |
git config --global --add safe.directory /__w/jitsi-meet/jitsi-meet
git clean -dfx
ios-sdk-build:
name: Build mobile SDK (iOS)
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -173,8 +176,8 @@ jobs:
name: Test Debian packages build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'npm'

View File

@@ -1,5 +1,8 @@
# <p align="center">Jitsi Meet</p>
Jitsi Meet is a set of Open Source projects which empower users to use and deploy
video conferencing platforms with state-of-the-art video quality and features.

View File

@@ -139,6 +139,9 @@ var config = {
// Disables polls feature.
// disablePolls: false,
// Disables chat feature entirely including notifications, sounds, and private messages.
// disableChat: false,
// Disables demote button from self-view
// disableSelfDemote: false,
@@ -904,6 +907,8 @@ var config = {
// alwaysVisible: false,
// // Indicates whether the toolbar should still autohide when chat is open
// autoHideWhileChatIsOpen: false,
// // Default background color for the main toolbar. Accepts any valid CSS color.
// // backgroundColor: '#ffffff',
// },
// Overrides the buttons displayed in the main toolbar. Depending on the screen size the number of displayed

View File

@@ -124,10 +124,17 @@ case "$1" in
ln -s $PROSODY_HOST_CONFIG /etc/prosody/conf.d/$JVB_HOSTNAME.cfg.lua
fi
PROSODY_CREATE_JICOFO_USER="true"
fi
if ! grep -q "VirtualHost \"$JVB_HOSTNAME\"" $PROSODY_CONFIG_OLD; then
# on some distributions main prosody config doesn't include configs
# from conf.d folder enable it as this where we put our config by default
# also when upgrading to new prosody version from prosody repo we need to add it again
if ! grep -q "Include \"conf\.d\/\*\.cfg.lua\"" $PROSODY_CONFIG_OLD; then
echo -e "\nInclude \"conf.d/*.cfg.lua\"" >> $PROSODY_CONFIG_OLD
# trigger a restart
PROSODY_CONFIG_PRESENT="false"
fi
fi

View File

@@ -1484,6 +1484,7 @@
"connectionInfo": "Verbindungsinformationen",
"demote": "Zu Gästen verschieben",
"domute": "Stummschalten",
"domuteDesktopOfOthers": "Bildschirm freigeben für alle beenden",
"domuteOthers": "Alle anderen stummschalten",
"domuteVideo": "Kamera ausschalten",
"domuteVideoOfOthers": "Alle anderen Kameras auschalten",

View File

@@ -114,6 +114,9 @@
"error": "Kļūda: Jūsu ziņa netika nosūtīta. Cēlonis: {{error}}",
"everyone": "Visi",
"fieldPlaceHolder": "Rakstiet ziņu šeit",
"fileAccessibleTitle": "{{user}} augšuplādēja failu",
"fileAccessibleTitleMe": "es augšuplādēju failu",
"fileDeleted": "Fails tika dzēsts",
"guestsChatIndicator": "(viesis)",
"lobbyChatMessageTo": "Vestibila tērzēšanas ziņa adresātam {{recipient}}",
"message": "Ziņa",
@@ -123,12 +126,20 @@
"messagebox": "Rakstiet ziņu",
"newMessages": "Jaunas ziņas",
"nickname": {
"popover": "Izvēlieties vārdu",
"title": "Ierakstiet vārdu, lai izmantotu tērzēšanā",
"titleWithCC": "Ievadiet segvārdu, lai izmantotu tērzēšanā un slēptos subtitros",
"titleWithPolls": "Ierakstiet segvārdu, lai izmantotu tērzēšanā un aptaujās",
"titleWithPollsAndCC": "Ievadiet segvārdu, lai izmantotu tērzēšanā, aptaujās un slēptos subtitros",
"titleWithPollsAndCCAndFileSharing": "Ievadiet segvārdu, lai izmantotu tērzēšanā, aptaujās, slēptos subtitros un failos"
"featureChat": "tērzētava",
"featureClosedCaptions": "slēgtie subtitri",
"featureFileSharing": "failu kopīgošana",
"featurePolls": "aptaujas",
"popover": "Izvēlieties segvārdu",
"title": "Ierakstiet segvārdu, lai izmantotu tērzēšanu",
"titleWith1Features": "Ievadiet segvārdu, lai izmantotu {{feature1}}",
"titleWith2Features": "Ievadiet segvārdu, lai izmantotu {{feature1}} un {{feature2}}",
"titleWith3Features": "Ievadiet segvārdu, lai izmantotu {{feature1}}, {{feature2}} un {{feature3}}",
"titleWith4Features": "Ievadiet segvārdu, lai izmantotu {{feature1}}, {{feature2}}, {{feature3}} un {{feature4}}",
"titleWithCC": "Ievadiet segvārdu, lai izmantotu tērzēšanu un slēgtos subtitrus",
"titleWithPolls": "Ierakstiet segvārdu, lai izmantotu tērzēšanu un aptaujas",
"titleWithPollsAndCC": "Ievadiet segvārdu, lai izmantotu tērzēšanu, aptaujas un slēgtos subtitrus",
"titleWithPollsAndCCAndFileSharing": "Ievadiet segvārdu, lai izmantotu tērzēšanu, aptaujas, slēgtos subtitrus un failus"
},
"noMessagesMessage": "Sapulcē pagaidām nav nevienas ziņas. Uzsāciet saraksti!",
"privateNotice": "Privāta ziņa adresātam {{recipient}}",
@@ -137,12 +148,12 @@
"systemDisplayName": "Sistēma",
"tabs": {
"chat": "Tērzēšana",
"closedCaptions": "Slēptie subtitri",
"closedCaptions": "Slēgtie subtitri",
"fileSharing": "Faili",
"polls": "Aptaujas"
},
"title": "Tērzēšana",
"titleWithCC": "Tērzēšana un Slēptie subtitri",
"titleWithCC": "Tērzēšana un Slēgtie subtitri",
"titleWithFeatures": "Tērzēšana un",
"titleWithFileSharing": "Faili",
"titleWithPolls": "Tērzēšana un Aptaujas",
@@ -156,8 +167,8 @@
"installExtensionText": "Uzstādīt spraudni Google kalendāra un Office 365 integrācijai"
},
"closedCaptionsTab": {
"emptyState": "Slēpto subtitru saturs būs pieejams, tiklīdz moderators uzsāks to.",
"startClosedCaptionsButton": "Uzsākt slēptos subtitrus"
"emptyState": "Slēgto subtitru saturs būs pieejams, tiklīdz moderators uzsāks to.",
"startClosedCaptionsButton": "Uzsākt slēgtos subtitrus"
},
"connectingOverlay": {
"joiningRoom": "Notiek pieslēgšanās jūsu sapulcei…"
@@ -280,7 +291,6 @@
"Submit": "Iesniegt",
"Understand": "Saprotu",
"UnderstandAndUnmute": "Es saprotu, lūdzu, ieslēdziet skaņu.",
"WaitForHostMsg": "Sapulce vēl nav sākusies, jo vēl nav ieradies neviens moderators. Lūdzu, autorizējieties, lai kļūtu par moderatoru. Pretējā gadījumā, lūdzu, uzgaidiet.",
"WaitForHostNoAuthMsg": "Sapulce vēl nav sākusies, jo vēl nav ieradies neviens moderators. Lūdzu, uzgaidiet.",
"WaitingForHostButton": "Gaidīt rīkotāju",
"WaitingForHostTitle": "Gaida rīkotāju…",
@@ -417,7 +427,7 @@
"muteParticipantsVideoDialog": "Vai tiešām vēlaties izslēgt šī dalībnieka kameru? Jūs nevarēsiet to ieslēgt atpakaļ, taču dalībnieks pats to varēs izdarīt jebkurā laikā.",
"muteParticipantsVideoDialogModerationOn": "Vai tiešām vēlaties izslēgt šī dalībnieka kameru? Ne Jūs, ne dalībnieks nevarēsiet to ieslēgt atpakaļ.",
"muteParticipantsVideoTitle": "Vai izslēgt šī dalībnieka video?",
"noDropboxToken": "Nav derīga Dropbox tokena",
"noDropboxToken": "Nav derīgas Dropbox pilnvaras",
"password": "Parole",
"passwordLabel": "Dalībnieks ir aizslēdzis sapulci. Lūdzu, ievadiet $t(lockRoomPassword), lai pievienotos.",
"passwordNotSupported": "Sapulces slēgšana ar $t(lockRoomPassword) netiek atbalstīta.",
@@ -501,7 +511,7 @@
"stopStreamingWarning": "Tiešām vēlaties beigt tiešraidi?",
"streamKey": "Tiešraides atslēga",
"thankYou": "Paldies, ka izmantojāt {{appName}}!",
"token": "tokens",
"token": "pilnvara",
"tokenAuthFailed": "Atvainojiet, jums nav atļauts pievienoties šim zvanam.",
"tokenAuthFailedReason": {
"audInvalid": "Nederīga `aud` vērtība. Tai vajadzētu būt `jitsi`.",
@@ -517,12 +527,13 @@
"nbfFuture": "`nbf` vērtība ir nākotnē.",
"nbfInvalid": "Nederīga `nbf` vērtība.",
"payloadNotFound": "Trūkst satura.",
"tokenExpired": "Token ir beidzies."
"tokenExpired": "Pilnvara ir beigusies."
},
"tokenAuthFailedTitle": "Autentifikācijas kļūda",
"tokenAuthFailedWithReasons": "Atvainojiet, jums nav atļauts pievienoties šim zvanam. Iespējamie iemesli: {{reason}}",
"tokenAuthUnsupported": "Token URL netiek atbalstīts.",
"tokenAuthUnsupported": "Pilnvaras URL nav atbalstīts.",
"transcribing": "Notiek atšifrējuma izveide",
"unauthenticatedAccessDisabled": "Šim zvanam nepieciešama autentifikācija. Lūdzu, piesakieties, lai turpinātu.",
"unlockRoom": "Noņemt $t(lockRoomPassword)",
"user": "Lietotājs",
"userIdentifier": "Lietotājvārds",
@@ -570,10 +581,12 @@
"downloadStarted": "Sākta faila lejuplāde",
"dragAndDrop": "Velciet un palaidiet failus šeit, vai jebkurā ekrāna vietā",
"fileAlreadyUploaded": "Fails jau ir augšuplādēts šajā sanāksmē.",
"fileRemovedByOther": "Jūsu fails '{{ fileName }}' tika noņemts",
"fileTooLargeDescription": "Lūdzu, pārliecinieties, vai faila lielums nepārsniedz {{ maxFileSize }}.",
"fileTooLargeTitle": "Izvēlētais fails ir pārāk liels",
"fileUploadProgress": "Faila augšuplādes gaita",
"fileUploadedSuccessfully": "Fails veiksmīgi augšuplādēts",
"newFileNotification": "{{ participantName }} kopīgoja '{{ fileName }}'",
"removeFile": "Noņemt",
"removeFileSuccess": "Fails veiksmīgi noņemts",
"uploadFailedDescription": "Lūdzu, mēģiniet vēlreiz.",
@@ -748,7 +761,8 @@
"notificationTitle": "Vestibils",
"passwordJoinButton": "Pievienoties",
"title": "Vestibils",
"toggleLabel": "Iespējot vestibilu"
"toggleLabel": "Iespējot vestibilu",
"waitForModerator": "Konference vēl nav sākusies, jo vēl nav ieradušies moderatori. Ja vēlaties kļūt par moderatoru, lūdzu, piesakieties. Pretējā gadījumā, lūdzu, uzgaidiet."
},
"localRecording": {
"clientState": {
@@ -775,7 +789,7 @@
"participant": "Dalībnieks",
"participantStats": "Dalībnieku statistika",
"selectTabTitle": "🎥 Lūdzu, atveriet šo cilni ierakstīšanai",
"sessionToken": "Sessijas tokens",
"sessionToken": "Sesijas Pilnvara",
"start": "Sākt ierakstu",
"stop": "Beigt ierakstu",
"stopping": "Ierakstīšanas pārtraukšana",
@@ -865,6 +879,7 @@
"oldElectronClientDescription1": "Izskatās, ka jūs izmantojat vecu Jitsi Meet klienta versiju, kurai ir zināmas drošības ievainojamības. Lūdzu, atjauniniet uz ",
"oldElectronClientDescription2": "jaunākā versija",
"oldElectronClientDescription3": "tagad!",
"openChat": "Atvērt tērzētavu",
"participantWantsToJoin": "Vēlas pievienoties sapulcei",
"participantsWantToJoin": "Vēlas pievienoties sapulcei",
"passwordRemovedRemotely": "Kāds dalībnieks noņēma $t(lockRoomPasswordUppercase).",
@@ -963,6 +978,9 @@
"by": "Pēc {{ name }} iniciatīvas",
"closeButton": "Slēgt aptauju",
"create": {
"accessibilityLabel": {
"send": "Nosūtīt aptauju"
},
"addOption": "Pievienot opciju",
"answerPlaceholder": "Opcija {{index}}",
"cancel": "Atcelt",
@@ -971,8 +989,7 @@
"pollQuestion": "Aptaujas Jautājums",
"questionPlaceholder": "Uzdod jautājumu",
"removeOption": "Noņemt opciju",
"save": "Saglabāt",
"send": "Nosūtīt"
"save": "Saglabāt"
},
"errors": {
"notUniqueOption": "Iespējām jābūt unikālām"
@@ -1091,7 +1108,7 @@
}
},
"recording": {
"authDropboxText": "Augšupielādēt uz Dropbox",
"authDropboxText": "Augšuplādēt uz Dropbox",
"availableSpace": "Pieejama vieta: {{spaceLeft}} MB (apmēram {{duration}} ieraksta minūtes)",
"beta": "BETA",
"busy": "Cenšamies nodrošināt ierakstam vairāk resursu. Lūdzu, pēc dažām minūtēm pamēģiniet vēlreiz.",
@@ -1145,7 +1162,7 @@
"title": "Ieraksts",
"unavailable": "Hmm! {{serviceName}} pašlaik nav pieejams. Mēs strādājam pie problēmas risināšanas. Lūdzu, pamēģiniet vēlreiz vēlāk.",
"unavailableTitle": "Ieraksts nav iespējams",
"uploadToCloud": "Augšupielādēt mākonī"
"uploadToCloud": "Augšuplādēt mākonī"
},
"screenshareDisplayName": "{{name}} ekrāns",
"sectionList": {
@@ -1300,7 +1317,7 @@
"closeChat": "Aizvērt tērzēšanu",
"closeMoreActions": "Aizvērt vairāk darbību izvēlni",
"closeParticipantsPane": "Aizvērt dalībnieku paneli",
"closedCaptions": "Slēptie subtitri",
"closedCaptions": "Slēgtie subtitri",
"collapse": "Sakļaut",
"document": "Kopīgotais dokuments (iesl./izsl.)",
"documentClose": "Aizvērt kopīgoto dokumentu",
@@ -1378,7 +1395,21 @@
"videomuteGUMPending": "Kameras pievienošana",
"videounmute": "Ieslēgt kameru"
},
"addPeople": "Pievienot cilvēkus savai sesijai/zvanam",
"addPeople": "Pievienot cilvēkus savam zvanam",
"advancedAudioSettings": {
"aec": {
"label": "Akustiskās atbalss slāpēšana"
},
"agc": {
"label": "Automātiska pastiprinājuma kontrole"
},
"ns": {
"label": "Trokšņu slāpēšana"
},
"stereo": {
"label": "Stereo"
}
},
"audioOnlyOff": "Atspējot kanāla/trafika taupības režīmu",
"audioOnlyOn": "Iespējot kanāla/trafika taupības režīmu",
"audioRoute": "Izvēlēties audioierīci",
@@ -1391,7 +1422,7 @@
"closeChat": "Aizvērt tērzētavu",
"closeParticipantsPane": "Aizvērt dalībnieku paneli",
"closeReactionsMenu": "Aizvērt reakciju izvēlni",
"closedCaptions": "Slēptie subtitri",
"closedCaptions": "Slēgtie subtitri",
"disableNoiseSuppression": "Atspējot trokšņu slāpēšanu",
"disableReactionSounds": "Šai sapulcei varat atspējot reakcijas skaņas",
"documentClose": "Aizvērt kopīgoto dokumentu",
@@ -1406,6 +1437,7 @@
"exitFullScreen": "Pilnekrāna režīms",
"exitTileView": "Tuvplāna režīms",
"feedback": "Atstāts atsauksmi",
"fileSharing": "Failu kopīgošana",
"giphy": "GIPHY izvēlne (rādīt/nerādīt)",
"hangup": "Iziet no sapulces",
"help": "Palīdzība",
@@ -1441,17 +1473,19 @@
"openReactionsMenu": "Atvērt reakciju izvēlni",
"participants": "Dalībnieki",
"pip": "Iesl. attēls attēlā (PIP) režīmu",
"polls": "Aptaujas",
"privateMessage": "Nosūtīt privātu ziņu",
"profile": "Rediģēt profilu",
"raiseHand": "Pacelt roku",
"raiseYourHand": "Pacelt roku",
"reactionBoo": "Nosūtīt būū reakciju",
"reactionClap": "Nosūtīt aplausu reakciju",
"reactionHeart": "Nosūtīt sirds reakciju",
"reactionLaugh": "Nosūtīt smieklu reakciju",
"reactionLike": "Nosūtīt īkšķi augšup reakciju",
"reactionSilence": "Nosūtīt klusuma reakciju",
"reactionSurprised": "Nosūtīt pārsteigts reakciju",
"reactionBoo": "Sūtīt būū reakciju",
"reactionClap": "Sūtīt aplausu reakciju",
"reactionHeart": "Sūtīt sirds reakciju",
"reactionLaugh": "Sūtīt smieklu reakciju",
"reactionLike": "Sūtīt īkšķis augšup reakciju",
"reactionLove": "Sūtīt mīlestības reakciju",
"reactionSilence": "Sūtīt klusuma reakciju",
"reactionSurprised": "Sūtīt pārsteiguma reakciju",
"reactions": "Reakcijas",
"security": "Drošības iespējas",
"selectBackground": "Izvēlēties fonu",
@@ -1484,7 +1518,7 @@
"failed": "Atšifrējuma izveide neizdevās",
"labelTooltip": "Šajā sapulcē notiek atšifrējuma izveide.",
"labelTooltipExtra": "Turklāt vēlāk būs pieejams atšifrējums.",
"openClosedCaptions": "Atvērt slēptos subtitrus",
"openClosedCaptions": "Atvērt slēgtos subtitrus",
"original": "Oriģināls",
"sourceLanguageDesc": "Pašlaik sapulces valoda ir iestatīta uz <b>{{sourceLanguage}}</b>. <br/> Varat to mainīt no ",
"sourceLanguageHere": "šeit",
@@ -1582,7 +1616,7 @@
"removeBackground": "Noņemt fonu",
"slightBlur": "Viegli izplūdis",
"title": "Virtuālie foni",
"uploadedImage": "Augšupielādēts attēls {{index}}",
"uploadedImage": "Augšuplādēts attēls {{index}}",
"webAssemblyWarning": "WebAssembly netiek atbalstīts",
"webAssemblyWarningDescription": "WebAssemb ir atspējots vai šī pārlūkprogramma to neatbalsta"
},
@@ -1601,6 +1635,8 @@
"noMainParticipantsTitle": "Šī sapulce vēl nav sākusies.",
"noVisitorLobby": "Jūs nevarat pievienoties, kamēr sapulcei ir iespējots vestibils.",
"notAllowedPromotion": "Dalībniekam vispirms ir jāatļauj jūsu pieprasījums.",
"requestToJoin": "Roka Pacelta",
"requestToJoinDescription": "Jūsu pieprasījums tika nosūtīts moderatoriem. Uzgaidiet!",
"title": "Jūs esat sapulces apmeklētājs"
},
"waitingMessage": "Jūs pievienosities sapulcei, tiklīdz tā sāksies!"

View File

@@ -126,8 +126,16 @@
"messagebox": "Type a message",
"newMessages": "New messages",
"nickname": {
"featureChat": "chat",
"featureClosedCaptions": "closed captions",
"featureFileSharing": "file sharing",
"featurePolls": "polls",
"popover": "Choose a nickname",
"title": "Enter a nickname to use chat",
"titleWith1Features": "Enter a nickname to use {{feature1}}",
"titleWith2Features": "Enter a nickname to use {{feature1}} and {{feature2}}",
"titleWith3Features": "Enter a nickname to use {{feature1}}, {{feature2}} and {{feature3}}",
"titleWith4Features": "Enter a nickname to use {{feature1}}, {{feature2}}, {{feature3}} and {{feature4}}",
"titleWithCC": "Enter a nickname to use chat and closed captions",
"titleWithPolls": "Enter a nickname to use chat and polls",
"titleWithPollsAndCC": "Enter a nickname to use chat, polls and closed captions",
@@ -1429,6 +1437,7 @@
"exitFullScreen": "Exit full screen",
"exitTileView": "Exit tile view",
"feedback": "Leave feedback",
"fileSharing": "File sharing",
"giphy": "Toggle GIPHY menu",
"hangup": "Leave the meeting",
"help": "Help",
@@ -1464,6 +1473,7 @@
"openReactionsMenu": "Open reactions menu",
"participants": "Participants",
"pip": "Enter Picture-in-Picture mode",
"polls": "Polls",
"privateMessage": "Send private message",
"profile": "Edit your profile",
"raiseHand": "Raise your hand",

View File

@@ -340,6 +340,7 @@ function initCommands() {
APP.store.dispatch(setAssumedBandwidthBps(value));
},
'set-blurred-background': blurType => {
const tracks = APP.store.getState()['features/base/tracks'];
const videoTrack = getLocalVideoTrack(tracks)?.jitsiTrack;

View File

@@ -158,11 +158,10 @@ const VideoLayout = {
return;
}
const state = APP.store.getState();
const currentContainer = largeVideo.getCurrentContainer();
const currentContainerType = largeVideo.getCurrentContainerType();
const isOnLarge = this.isCurrentlyOnLarge(id);
const state = APP.store.getState();
const participant = getParticipantById(state, id);
const videoTrack = getVideoTrackByParticipant(state, participant);
const videoStream = videoTrack?.jitsiTrack;

72
package-lock.json generated
View File

@@ -22,7 +22,7 @@
"@jitsi/js-utils": "2.6.7",
"@jitsi/logger": "2.1.1",
"@jitsi/rnnoise-wasm": "0.2.1",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"@matrix-org/olm": "3.2.15",
"@microsoft/microsoft-graph-client": "3.0.1",
"@mui/material": "5.12.1",
"@react-native-async-storage/async-storage": "1.23.1",
@@ -66,7 +66,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2115.0.0+cc2f34c2/lib-jitsi-meet.tgz",
"lodash-es": "4.17.21",
"null-loader": "4.0.1",
"optional-require": "1.0.3",
@@ -4690,9 +4690,9 @@
"dev": true
},
"node_modules/@matrix-org/olm": {
"version": "3.2.3",
"resolved": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"integrity": "sha512-OhC9wwZ/ox9vputA1MR2A7QlYlvfXCV+tdbADOR7Jn7o9qoXh3HWf+AbSpXTK3daF0GIHA69Ws8XOnWqu5n53A==",
"version": "3.2.15",
"resolved": "https://registry.npmjs.org/@matrix-org/olm/-/olm-3.2.15.tgz",
"integrity": "sha512-S7lOrndAK9/8qOtaTq/WhttJC/o4GAzdfK0MUPpo8ApzsJEC0QjtwrkC3KBXdFP1cD1MXi/mlKR7aaoVMKgs6Q==",
"license": "Apache-2.0"
},
"node_modules/@microsoft/microsoft-graph-client": {
@@ -18259,11 +18259,11 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
"integrity": "sha512-PCMJIfFWIZtDC6UA/53mT79hiqTGNCRE04/XFgWEr7KRf2QIni2tFh3hW1IPW0OjbtMAkJ1KGQpca/3l6sa5Mw==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2115.0.0+cc2f34c2/lib-jitsi-meet.tgz",
"integrity": "sha512-/fpQChyB3jTrQbAhm1YVHmU8HcU7hZyfK+dq8NblKKdnnalzfYUelG1c3leSG3SVb6s3xPyxgmulIgTBX5y6eA==",
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "2.4.6",
"@jitsi/js-utils": "^2.6.7",
"@jitsi/logger": "2.1.1",
"@jitsi/precall-test": "1.0.6",
"@jitsi/rtcstats": "9.7.0",
@@ -18279,17 +18279,6 @@
"webrtc-adapter": "8.1.1"
}
},
"node_modules/lib-jitsi-meet/node_modules/@jitsi/js-utils": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.4.6.tgz",
"integrity": "sha512-z/VbM9c0V35T8Zkhxq2gdWbMWmM/3w4BD68xJVmQNrq/NQHxH0fDkRoT/MUds9Mp6dK3AV/h15tCKxVA/0w8Kg==",
"license": "Apache-2.0",
"dependencies": {
"@hapi/bourne": "^3.0.0",
"js-md5": "0.7.3",
"ua-parser-js": "1.0.35"
}
},
"node_modules/lib-jitsi-meet/node_modules/@jitsi/logger": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.2.tgz",
@@ -18312,12 +18301,6 @@
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
"license": "MIT"
},
"node_modules/lib-jitsi-meet/node_modules/js-md5": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ==",
"license": "MIT"
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
@@ -20103,9 +20086,10 @@
}
},
"node_modules/node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz",
"integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==",
"license": "(BSD-3-Clause OR GPL-2.0)",
"engines": {
"node": ">= 6.13.0"
}
@@ -30122,8 +30106,9 @@
"dev": true
},
"@matrix-org/olm": {
"version": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"integrity": "sha512-OhC9wwZ/ox9vputA1MR2A7QlYlvfXCV+tdbADOR7Jn7o9qoXh3HWf+AbSpXTK3daF0GIHA69Ws8XOnWqu5n53A=="
"version": "3.2.15",
"resolved": "https://registry.npmjs.org/@matrix-org/olm/-/olm-3.2.15.tgz",
"integrity": "sha512-S7lOrndAK9/8qOtaTq/WhttJC/o4GAzdfK0MUPpo8ApzsJEC0QjtwrkC3KBXdFP1cD1MXi/mlKR7aaoVMKgs6Q=="
},
"@microsoft/microsoft-graph-client": {
"version": "3.0.1",
@@ -39690,10 +39675,10 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
"integrity": "sha512-PCMJIfFWIZtDC6UA/53mT79hiqTGNCRE04/XFgWEr7KRf2QIni2tFh3hW1IPW0OjbtMAkJ1KGQpca/3l6sa5Mw==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2115.0.0+cc2f34c2/lib-jitsi-meet.tgz",
"integrity": "sha512-/fpQChyB3jTrQbAhm1YVHmU8HcU7hZyfK+dq8NblKKdnnalzfYUelG1c3leSG3SVb6s3xPyxgmulIgTBX5y6eA==",
"requires": {
"@jitsi/js-utils": "2.4.6",
"@jitsi/js-utils": "^2.6.7",
"@jitsi/logger": "2.1.1",
"@jitsi/precall-test": "1.0.6",
"@jitsi/rtcstats": "9.7.0",
@@ -39709,16 +39694,6 @@
"webrtc-adapter": "8.1.1"
},
"dependencies": {
"@jitsi/js-utils": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.4.6.tgz",
"integrity": "sha512-z/VbM9c0V35T8Zkhxq2gdWbMWmM/3w4BD68xJVmQNrq/NQHxH0fDkRoT/MUds9Mp6dK3AV/h15tCKxVA/0w8Kg==",
"requires": {
"@hapi/bourne": "^3.0.0",
"js-md5": "0.7.3",
"ua-parser-js": "1.0.35"
}
},
"@jitsi/logger": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.2.tgz",
@@ -39739,11 +39714,6 @@
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="
},
"js-md5": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
}
}
},
@@ -41051,9 +41021,9 @@
}
},
"node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz",
"integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw=="
},
"node-int64": {
"version": "0.4.0",

View File

@@ -28,7 +28,7 @@
"@jitsi/js-utils": "2.6.7",
"@jitsi/logger": "2.1.1",
"@jitsi/rnnoise-wasm": "0.2.1",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"@matrix-org/olm": "3.2.15",
"@microsoft/microsoft-graph-client": "3.0.1",
"@mui/material": "5.12.1",
"@react-native-async-storage/async-storage": "1.23.1",
@@ -72,7 +72,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2101.0.0+8061f52a/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2115.0.0+cc2f34c2/lib-jitsi-meet.tgz",
"lodash-es": "4.17.21",
"null-loader": "4.0.1",
"optional-require": "1.0.3",

View File

@@ -139,7 +139,7 @@ function _upgradeRoleStarted(thenableWithCancel: Object) {
* @returns {Function}
*/
export function hideLoginDialog() {
return hideDialog(LoginDialog);
return hideDialog('LoginDialog', LoginDialog);
}
/**
@@ -199,7 +199,7 @@ export function enableModeratorLogin() {
* @returns {Action}
*/
export function openWaitForOwnerDialog() {
return openDialog(WaitForOwnerDialog);
return openDialog('WaitForOwnerDialog', WaitForOwnerDialog);
}
@@ -240,7 +240,7 @@ export function waitForOwner() {
* @returns {Action}
*/
export function openLoginDialog() {
return openDialog(LoginDialog);
return openDialog('LoginDialog', LoginDialog);
}
/**

View File

@@ -65,7 +65,7 @@ export function openTokenAuthUrl(tokenAuthServiceUrl: string): any {
// Show warning for leaving conference only when in a conference.
if (!browser.isElectron() && getState()['features/base/conference'].conference) {
dispatch(openDialog(LoginQuestionDialog, {
dispatch(openDialog('LoginQuestionDialog', LoginQuestionDialog, {
handler: () => {
// Give time for the dialog to close.
setTimeout(() => redirect(), 500);

View File

@@ -209,7 +209,7 @@ MiddlewareRegistry.register(store => next => action => {
case STOP_WAIT_FOR_OWNER:
_clearExistingWaitForOwnerTimeout(store);
store.dispatch(hideDialog(WaitForOwnerDialog));
store.dispatch(hideDialog('WaitForOwnerDialog', WaitForOwnerDialog));
break;
case UPGRADE_ROLE_FINISHED: {

View File

@@ -285,6 +285,7 @@ export interface IConfig {
disableAudioLevels?: boolean;
disableBeforeUnloadHandlers?: boolean;
disableCameraTintForeground?: boolean;
disableChat?: boolean;
disableChatSmileys?: boolean;
disableDeepLinking?: boolean;
disableFilmstripAutohiding?: boolean;
@@ -393,6 +394,7 @@ export interface IConfig {
disabled?: boolean;
initialWidth?: number;
minParticipantCountForTopPanel?: number;
stageFilmstripParticipants?: number;
};
flags?: {
ssrcRewritingEnabled: boolean;
@@ -616,6 +618,10 @@ export interface IConfig {
toolbarConfig?: {
alwaysVisible?: boolean;
autoHideWhileChatIsOpen?: boolean;
/**
* Background color for the main toolbar. Accepts any valid CSS color.
*/
backgroundColor?: string;
initialTimeout?: number;
timeout?: number;
};

View File

@@ -94,6 +94,7 @@ export default [
'disableAudioLevels',
'disableBeforeUnloadHandlers',
'disableCameraTintForeground',
'disableChat',
'disableChatSmileys',
'disableDeepLinking',
'disabledNotifications',

View File

@@ -14,6 +14,7 @@ import logger from './logger';
/**
* Signals Dialog to close its dialog.
*
* @param {string|undefined} name - The name of the component for logging purposes.
* @param {Object} [component] - The {@code Dialog} component to close/hide. If
* {@code undefined}, closes/hides {@code Dialog} regardless of which
* component it's rendering; otherwise, closes/hides {@code Dialog} only if
@@ -23,8 +24,8 @@ import logger from './logger';
* component: (React.Component | undefined)
* }}
*/
export function hideDialog(component?: ComponentType<any>) {
logger.info(`Hide dialog: ${getComponentDisplayName(component)}`);
export function hideDialog(name?: string, component?: ComponentType<any>) {
logger.info(`Hide dialog: ${name}`);
return {
type: HIDE_DIALOG,
@@ -48,6 +49,7 @@ export function hideSheet() {
/**
* Signals Dialog to open dialog.
*
* @param {string} name - The name of the component for logging purposes.
* @param {Object} component - The component to display as dialog.
* @param {Object} [componentProps] - The React {@code Component} props of the
* specified {@code component}.
@@ -57,8 +59,8 @@ export function hideSheet() {
* componentProps: (Object | undefined)
* }}
*/
export function openDialog(component: ComponentType<any>, componentProps?: Object) {
logger.info(`Open dialog: ${getComponentDisplayName(component)}`);
export function openDialog(name: string, component: ComponentType<any>, componentProps?: Object) {
logger.info(`Open dialog: ${name}`);
return {
type: OPEN_DIALOG,
@@ -92,35 +94,18 @@ export function openSheet(component: ComponentType<any>, componentProps?: Object
* is not already open. If it is open, then Dialog is signaled to close its
* dialog.
*
* @param {string} name - The name of the component for logging purposes.
* @param {Object} component - The component to display as dialog.
* @param {Object} [componentProps] - The React {@code Component} props of the
* specified {@code component}.
* @returns {Function}
*/
export function toggleDialog(component: ComponentType<any>, componentProps?: Object) {
export function toggleDialog(name: string, component: ComponentType<any>, componentProps?: Object) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
if (isDialogOpen(getState, component)) {
dispatch(hideDialog(component));
dispatch(hideDialog(name, component));
} else {
dispatch(openDialog(component, componentProps));
dispatch(openDialog(name, component, componentProps));
}
};
}
/**
* Extracts a printable name for a dialog component.
*
* @param {Object} component - The component to extract the name for.
*
* @returns {string} The display name.
*/
function getComponentDisplayName(component?: ComponentType<any>) {
if (!component) {
return '';
}
const name = component.displayName ?? component.name ?? 'Component';
return name.replace('withI18nextTranslation(Connect(', '') // dialogs with translations
.replace('))', ''); // dialogs with translations suffix
}

View File

@@ -3,13 +3,14 @@ import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import { safeJsonParse } from '@jitsi/js-utils/json';
import { pick } from 'lodash-es';
import { browser } from '../lib-jitsi-meet';
import { isEmbedded } from '../util/embedUtils';
import { parseURLParams } from '../util/parseURLParams';
import logger from './logger';
import WHITELIST from './whitelist';
/**
* Handles changes of the fake local storage.
@@ -61,7 +62,7 @@ function setupJitsiLocalStorage() {
if (shouldUseHostPageLocalStorage(urlParams)) {
try {
const localStorageContent = safeJsonParse(urlParams['appData.localStorageContent']);
let localStorageContent = safeJsonParse(urlParams['appData.localStorageContent']);
// We need to disable the local storage before setting the data in case the browser local storage doesn't
// throw exception (in some cases when this happens the local storage may be cleared for every session.
@@ -71,6 +72,10 @@ function setupJitsiLocalStorage() {
jitsiLocalStorage.setLocalStorageDisabled(true);
if (typeof localStorageContent === 'object') {
if (!isEmbedded()) {
localStorageContent = pick(localStorageContent, WHITELIST);
}
Object.keys(localStorageContent).forEach(key => {
jitsiLocalStorage.setItem(key, localStorageContent[key]);
});

View File

@@ -0,0 +1,11 @@
/**
* Keys of localStorage that are used by jibri.
*/
export default [
'callStatsUserName',
'displayname',
'email',
'xmpp_username_override',
'xmpp_password_override',
'xmpp_conference_password_override'
];

View File

@@ -26,7 +26,7 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
case SET_VIDEO_MUTED: {
if (LocalRecordingManager.isRecordingLocally() && LocalRecordingManager.selfRecording.on) {
if (action.muted && LocalRecordingManager.selfRecording.withVideo) {
dispatch(openDialog(StopRecordingDialog, { localRecordingVideoStop: true }));
dispatch(openDialog('StopRecordingDialog', StopRecordingDialog, { localRecordingVideoStop: true }));
return;
} else if (!action.muted && !LocalRecordingManager.selfRecording.withVideo) {

View File

@@ -62,6 +62,65 @@ const AVATAR_CHECKER_FUNCTIONS = [
];
/* eslint-enable arrow-body-style */
/**
* Returns the list of active speakers that should be moved to the top of the sorted list of participants so that the
* dominant speaker is visible always on the vertical filmstrip in stage layout.
*
* @param {Function | Object} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
* retrieve the state.
* @returns {Array<string>}
*/
export function getActiveSpeakersToBeDisplayed(stateful: IStateful) {
const state = toState(stateful);
const {
dominantSpeaker,
fakeParticipants,
sortedRemoteVirtualScreenshareParticipants,
speakersList
} = state['features/base/participants'];
const { visibleRemoteParticipants } = state['features/filmstrip'];
let activeSpeakers = new Map(speakersList);
// Do not re-sort the active speakers if dominant speaker is currently visible.
if (dominantSpeaker && visibleRemoteParticipants.has(dominantSpeaker)) {
return activeSpeakers;
}
let availableSlotsForActiveSpeakers = visibleRemoteParticipants.size;
if (activeSpeakers.has(dominantSpeaker ?? '')) {
activeSpeakers.delete(dominantSpeaker ?? '');
}
// Add dominant speaker to the beginning of the list (not including self) since the active speaker list is always
// alphabetically sorted.
if (dominantSpeaker && dominantSpeaker !== getLocalParticipant(state)?.id) {
const updatedSpeakers = Array.from(activeSpeakers);
updatedSpeakers.splice(0, 0, [ dominantSpeaker, getParticipantById(state, dominantSpeaker)?.name ?? '' ]);
activeSpeakers = new Map(updatedSpeakers);
}
// Remove screenshares from the count.
if (sortedRemoteVirtualScreenshareParticipants) {
availableSlotsForActiveSpeakers -= sortedRemoteVirtualScreenshareParticipants.size * 2;
for (const screenshare of Array.from(sortedRemoteVirtualScreenshareParticipants.keys())) {
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare as string);
activeSpeakers.delete(ownerId);
}
}
// Remove fake participants from the count.
if (fakeParticipants) {
availableSlotsForActiveSpeakers -= fakeParticipants.size;
}
const truncatedSpeakersList = Array.from(activeSpeakers).slice(0, availableSlotsForActiveSpeakers);
truncatedSpeakersList.sort((a: any, b: any) => a[1].localeCompare(b[1]));
return new Map(truncatedSpeakersList);
}
/**
* Resolves the first loadable avatar URL for a participant.
*

View File

@@ -27,6 +27,7 @@ import {
isRemoteScreenshareParticipant,
isScreenShareParticipant
} from './functions';
import logger from './logger';
import { FakeParticipant, ILocalParticipant, IParticipant, ISourceInfo } from './types';
/**
@@ -76,12 +77,12 @@ const DEFAULT_STATE = {
numberOfParticipantsNotSupportingE2EE: 0,
overwrittenNameList: {},
pinnedParticipant: undefined,
previousSpeakers: new Set<string>(),
raisedHandsQueue: [],
remote: new Map(),
remoteVideoSources: new Set<string>(),
sortedRemoteVirtualScreenshareParticipants: new Map(),
sortedRemoteParticipants: new Map()
sortedRemoteParticipants: new Map(),
speakersList: new Map()
};
export interface IParticipantsState {
@@ -94,12 +95,12 @@ export interface IParticipantsState {
numberOfParticipantsNotSupportingE2EE: number;
overwrittenNameList: { [id: string]: string; };
pinnedParticipant?: string;
previousSpeakers: Set<string>;
raisedHandsQueue: Array<{ hasBeenNotified?: boolean; id: string; raisedHandTimestamp: number; }>;
remote: Map<string, IParticipant>;
remoteVideoSources: Set<string>;
sortedRemoteParticipants: Map<string, string>;
sortedRemoteVirtualScreenshareParticipants: Map<string, string>;
speakersList: Map<string, string>;
}
/**
@@ -156,10 +157,22 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
const { participant } = action;
const { id, previousSpeakers = [] } = participant;
const { dominantSpeaker, local } = state;
const newSpeakers = [ id, ...previousSpeakers ];
const sortedSpeakersList: Array<Array<string>> = [];
// Build chronologically ordered Set of remote speakers (excluding local)
const previousSpeakersSet: Set<string>
= new Set(previousSpeakers.filter((speaker: string) => speaker !== local?.id));
for (const speaker of newSpeakers) {
if (speaker !== local?.id) {
const remoteParticipant = state.remote.get(speaker);
remoteParticipant
&& sortedSpeakersList.push(
[ speaker, _getDisplayName(state, remoteParticipant?.name) ]
);
}
}
// Keep the remote speaker list sorted alphabetically.
sortedSpeakersList.sort((a, b) => a[1].localeCompare(b[1]));
// Only one dominant speaker is allowed.
if (dominantSpeaker) {
@@ -169,8 +182,8 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
if (_updateParticipantProperty(state, id, 'dominantSpeaker', true)) {
return {
...state,
dominantSpeaker: id,
previousSpeakers: previousSpeakersSet
dominantSpeaker: id, // @ts-ignore
speakersList: new Map(sortedSpeakersList)
};
}
@@ -352,6 +365,8 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
sortedRemoteVirtualScreenshareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
logger.debug('Remote screenshare participant joined', id);
}
// Exclude the screenshare participant from the fake participant count to avoid duplicates.
@@ -423,7 +438,7 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
}
// Remove the participant from the list of speakers.
state.previousSpeakers.delete(id);
state.speakersList.has(id) && state.speakersList.delete(id);
if (pinnedParticipant === id) {
state.pinnedParticipant = undefined;
@@ -436,6 +451,8 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
if (sortedRemoteVirtualScreenshareParticipants.has(id)) {
sortedRemoteVirtualScreenshareParticipants.delete(id);
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
logger.debug('Remote screenshare participant left', id);
}
if (oldParticipant && !oldParticipant.fakeParticipant && !isLocalScreenShare) {

View File

@@ -21,6 +21,7 @@ import {
getRemoteScreensharesBasedOnPresence,
getVirtualScreenshareParticipantOwnerId
} from './functions';
import logger from './logger';
import { FakeParticipant } from './types';
StateListenerRegistry.register(
@@ -69,14 +70,19 @@ function _createOrRemoveVirtualParticipants(
const addedScreenshareSourceNames = difference(newScreenshareSourceNames, oldScreenshareSourceNames);
if (removedScreenshareSourceNames.length) {
removedScreenshareSourceNames.forEach(id => dispatch(participantLeft(id, conference, {
fakeParticipant: FakeParticipant.RemoteScreenShare
})));
removedScreenshareSourceNames.forEach(id => {
logger.debug('Dispatching participantLeft for virtual screenshare', id);
dispatch(participantLeft(id, conference, {
fakeParticipant: FakeParticipant.RemoteScreenShare
}));
});
}
if (addedScreenshareSourceNames.length) {
addedScreenshareSourceNames.forEach(id => dispatch(
createVirtualScreenshareParticipant(id, false, conference)));
addedScreenshareSourceNames.forEach(id => {
logger.debug('Creating virtual screenshare participant', id);
dispatch(createVirtualScreenshareParticipant(id, false, conference));
});
}
}

View File

@@ -1,5 +1,7 @@
import { IReduxState, IStore } from '../../app/types';
import { isTrackStreamingStatusActive } from '../../connection-indicator/functions';
import { handleToggleVideoMuted } from '../../toolbox/actions.any';
import { muteLocal } from '../../video-menu/actions.any';
import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
import { getParticipantById, isScreenShareParticipant } from '../participants/functions';
import {
@@ -78,3 +80,43 @@ export function isRemoteVideoReceived({ getState }: IStore, id: string): boolean
return Boolean(videoTrack && !videoTrack.muted && isTrackStreamingStatusActive(videoTrack));
}
/**
* Mutes the local audio. Same as clicking the audio mute button.
*
* @param {IStore} store - The redux store.
* @returns {Promise} Resolves when the action is complete.
*/
export function audioMute({ dispatch }: IStore) {
return dispatch(muteLocal(true, MEDIA_TYPE.AUDIO));
}
/**
* Unmutes the local audio. Same as clicking the audio unmute button.
*
* @param {IStore} store - The redux store.
* @returns {Promise} Resolves when the action is complete.
*/
export function audioUnmute({ dispatch }: IStore) {
return dispatch(muteLocal(false, MEDIA_TYPE.AUDIO));
}
/**
* Mutes the local video. Same as clicking the video mute button.
*
* @param {IStore} store - The redux store.
* @returns {Promise} Resolves when the action is complete.
*/
export function videoMute({ dispatch }: IStore) {
return dispatch(handleToggleVideoMuted(true, true, true));
}
/**
* Unmutes the local video. Same as clicking the video unmute button.
*
* @param {IStore} store - The redux store.
* @returns {Promise} Resolves when the action is complete.
*/
export function videoUnmute({ dispatch }: IStore) {
return dispatch(handleToggleVideoMuted(false, true, true));
}

View File

@@ -8,11 +8,15 @@ import { getJitsiMeetGlobalNS } from '../util/helpers';
import { setConnectionState } from './actions';
import {
audioMute,
audioUnmute,
getLocalCameraEncoding,
getRemoteVideoType,
isLargeVideoReceived,
isRemoteVideoReceived,
isTestModeEnabled
isTestModeEnabled,
videoMute,
videoUnmute
} from './functions';
import logger from './logger';
@@ -85,10 +89,14 @@ function _bindTortureHelpers(store: IStore) {
// All torture helper methods go in here
getJitsiMeetGlobalNS().testing = {
audioMute: audioMute.bind(null, store),
audioUnmute: audioUnmute.bind(null, store),
getRemoteVideoType: getRemoteVideoType.bind(null, store),
isLargeVideoReceived: isLargeVideoReceived.bind(null, store),
getLocalCameraEncoding: getLocalCameraEncoding.bind(null, store),
isRemoteVideoReceived: isRemoteVideoReceived.bind(null, store)
isRemoteVideoReceived: isRemoteVideoReceived.bind(null, store),
videoMute: videoMute.bind(null, store),
videoUnmute: videoUnmute.bind(null, store),
};
}

View File

@@ -6,6 +6,7 @@ import { setScreenshareMuted } from '../media/actions';
import { addLocalTrack, replaceLocalTrack } from './actions.any';
import { getLocalDesktopTrack, getTrackState } from './functions.native';
import logger from './logger';
export * from './actions.any';
@@ -63,6 +64,6 @@ async function _startScreenSharing(dispatch: IStore['dispatch'], state: IReduxSt
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
}
} catch (error: any) {
console.log('ERROR creating screen-sharing stream ', error);
logger.error('Error creating screen-sharing stream', error);
}
}

View File

@@ -294,7 +294,7 @@ export function setCameraFacingMode(facingMode: string | undefined) {
* @returns {Object} - The open dialog action.
*/
export function openAllowToggleCameraDialog(onAllow: Function, initiatorId: string) {
return openDialog(AllowToggleCameraDialog, {
return openDialog('AllowToggleCameraDialog', AllowToggleCameraDialog, {
onAllow,
initiatorId
});

View File

@@ -58,7 +58,7 @@ const BreakoutRoomContextMenu = ({ room, actions = ALL_ACTIONS }: IProps) => {
}, [ dispatch, room ]);
const onRenameBreakoutRoom = useCallback(() => {
dispatch(openDialog(BreakoutRoomNamePrompt, {
dispatch(openDialog('BreakoutRoomNamePrompt', BreakoutRoomNamePrompt, {
breakoutRoomJid: room.jid,
initialRoomName: room.name
}));

View File

@@ -22,7 +22,7 @@ export * from './actions.any';
* }}
*/
export function openUpdateCalendarEventDialog(eventId: string) {
return openDialog(UpdateCalendarEventDialog, { eventId });
return openDialog('UpdateCalendarEventDialog', UpdateCalendarEventDialog, { eventId });
}
/**

View File

@@ -220,6 +220,34 @@ export function openCCPanel() {
};
}
/**
* Opens the chat panel with polls tab active.
*
* @returns {Object} The redux action.
*/
export function openPollsPanel() {
return async (dispatch: IStore['dispatch']) => {
dispatch(setFocusedTab(ChatTabs.POLLS));
dispatch({
type: OPEN_CHAT
});
};
}
/**
* Opens the chat panel with file sharing tab active.
*
* @returns {Object} The redux action.
*/
export function openFileSharingPanel() {
return async (dispatch: IStore['dispatch']) => {
dispatch(setFocusedTab(ChatTabs.FILE_SHARING));
dispatch({
type: OPEN_CHAT
});
};
}
/**
* Initiates the sending of messages between a moderator and a lobby attendee.

View File

@@ -1,30 +1,34 @@
import { IStore } from '../app/types';
import { IParticipant } from '../base/participants/types';
import { navigate } from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../mobile/navigation/routes';
import { OPEN_CHAT } from './actionTypes';
import { setFocusedTab } from './actions.any';
import { ChatTabs } from './constants';
export * from './actions.any';
/**
* Displays the chat panel.
* Displays the chat panel with the CHAT tab active.
*
* @param {Object} participant - The recipient for the private chat.
* @param {boolean} disablePolls - Checks if polls are disabled.
*
* @returns {{
* participant: participant,
* type: OPEN_CHAT
* }}
* @returns {Function}
*/
export function openChat(participant?: IParticipant | undefined | Object, disablePolls?: boolean) {
if (disablePolls) {
navigate(screen.conference.chat);
}
navigate(screen.conference.chatandpolls.main);
return (dispatch: IStore['dispatch']) => {
if (disablePolls) {
navigate(screen.conference.chat);
} else {
navigate(screen.conference.chatandpolls.main);
}
return {
participant,
type: OPEN_CHAT
dispatch(setFocusedTab(ChatTabs.CHAT));
dispatch({
participant,
type: OPEN_CHAT
});
};
}

View File

@@ -8,12 +8,13 @@ import {
SET_CHAT_WIDTH,
SET_USER_CHAT_WIDTH
} from './actionTypes';
import { closeChat } from './actions.any';
import { closeChat, setFocusedTab } from './actions.any';
import { ChatTabs } from './constants';
export * from './actions.any';
/**
* Displays the chat panel.
* Displays the chat panel with the CHAT tab active.
*
* @param {Object} participant - The recipient for the private chat.
* @param {Object} _disablePolls - Used on native.
@@ -24,6 +25,7 @@ export * from './actions.any';
*/
export function openChat(participant?: Object, _disablePolls?: boolean) {
return function(dispatch: IStore['dispatch']) {
dispatch(setFocusedTab(ChatTabs.CHAT));
dispatch({
participant,
type: OPEN_CHAT

View File

@@ -10,7 +10,7 @@ import { arePollsDisabled } from '../../../conference/functions.any';
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';
import { getUnreadPollCount } from '../../../polls/functions';
import { getUnreadCount, getUnreadFilesCount } from '../../functions';
import { getUnreadCount, getUnreadFilesCount, isChatDisabled } from '../../functions';
interface IProps extends AbstractButtonProps {
@@ -65,7 +65,7 @@ class ChatButton extends AbstractButton<IProps> {
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState, ownProps: any) {
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
const enabled = getFeatureFlag(state, CHAT_ENABLED, true) && !isChatDisabled(state);
const { visible = enabled } = ownProps;
return {

View File

@@ -24,7 +24,7 @@ import {
toggleChat
} from '../../actions.web';
import { CHAT_SIZE, ChatTabs, OPTION_GROUPCHAT, SMALL_WIDTH_THRESHOLD } from '../../constants';
import { getChatMaxSize } from '../../functions';
import { getChatMaxSize, getFocusedTab, isChatDisabled } from '../../functions';
import { IChatProps as AbstractProps } from '../../types';
import ChatHeader from './ChatHeader';
@@ -41,13 +41,18 @@ interface IProps extends AbstractProps {
/**
* The currently focused tab.
*/
_focusedTab: ChatTabs;
_focusedTab?: ChatTabs;
/**
* True if the CC tab is enabled and false otherwise.
*/
_isCCTabEnabled: boolean;
/**
* True if chat is disabled.
*/
_isChatDisabled: boolean;
/**
* True if file sharing tab is enabled.
*/
@@ -217,6 +222,7 @@ const Chat = ({
_isOpen,
_isPollsEnabled,
_isCCTabEnabled,
_isChatDisabled,
_isFileSharingTabEnabled,
_focusedTab,
_isResizing,
@@ -229,6 +235,11 @@ const Chat = ({
dispatch,
t
}: IProps) => {
// If no tabs are available, don't render the chat panel at all.
if (_isChatDisabled && !_isPollsEnabled && !_isCCTabEnabled && !_isFileSharingTabEnabled) {
return null;
}
const { classes, cx } = useStyles({ _isResizing, width: _width });
const [ isMouseDown, setIsMouseDown ] = useState(false);
const [ mousePosition, setMousePosition ] = useState<number | null>(null);
@@ -287,8 +298,6 @@ const Chat = ({
// Disable text selection during resize
document.body.style.userSelect = 'none';
console.log('Chat resize: Mouse down', { clientX: e.clientX, initialWidth: _width });
}, [ _width, dispatch ]);
/**
@@ -304,8 +313,6 @@ const Chat = ({
// Restore cursor and text selection
document.body.style.cursor = '';
document.body.style.userSelect = '';
console.log('Chat resize: Mouse up');
}
}, [ isMouseDown, dispatch ]);
@@ -316,7 +323,6 @@ const Chat = ({
* @returns {void}
*/
const onChatResize = useCallback(throttle((e: MouseEvent) => {
// console.log('Chat resize: Mouse move', { clientX: e.clientX, isMouseDown, mousePosition, _width });
if (isMouseDown && mousePosition !== null && dragChatWidth !== null) {
// For chat panel resizing on the left edge:
// - Dragging left (decreasing X coordinate) should make the panel wider
@@ -416,7 +422,7 @@ const Chat = ({
return (
<>
{renderTabs()}
<div
{!_isChatDisabled && (<div
aria-labelledby = { ChatTabs.CHAT }
className = { cx(
classes.chatPanel,
@@ -442,7 +448,7 @@ const Chat = ({
)}
<ChatInput
onSend = { onSendMessage } />
</div>
</div>) }
{ _isPollsEnabled && (
<>
<div
@@ -484,8 +490,18 @@ const Chat = ({
* @returns {ReactElement}
*/
function renderTabs() {
let tabs = [
{
// The only way focused tab will be undefined is when no tab is enabled. Therefore this function won't be
// executed because Chat component won't render anything. This should never happen but adding the check
// here to make TS happy (when passing the _focusedTab in the selected prop for Tabs).
if (!_focusedTab) {
return null;
}
let tabs = [];
// Only add chat tab if chat is not disabled.
if (!_isChatDisabled) {
tabs.push({
accessibilityLabel: t('chat.tabs.chat'),
countBadge:
_focusedTab !== ChatTabs.CHAT && _unreadMessagesCount > 0 ? _unreadMessagesCount : undefined,
@@ -493,8 +509,8 @@ const Chat = ({
controlsId: `${ChatTabs.CHAT}-panel`,
icon: IconMessage,
title: t('chat.tabs.chat')
}
];
});
}
if (_isPollsEnabled) {
tabs.push({
@@ -564,6 +580,8 @@ const Chat = ({
{_showNamePrompt
? <DisplayNameForm
isCCTabEnabled = { _isCCTabEnabled }
isChatDisabled = { _isChatDisabled }
isFileSharingEnabled = { _isFileSharingTabEnabled }
isPollsEnabled = { _isPollsEnabled } />
: renderChat()}
<div
@@ -602,7 +620,7 @@ const Chat = ({
* }}
*/
function _mapStateToProps(state: IReduxState, _ownProps: any) {
const { isOpen, focusedTab, messages, unreadMessagesCount, unreadFilesCount, width, isResizing } = state['features/chat'];
const { isOpen, messages, unreadMessagesCount, unreadFilesCount, width, isResizing } = state['features/chat'];
const { unreadPollsCount } = state['features/polls'];
const _localParticipant = getLocalParticipant(state);
@@ -611,8 +629,9 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
_isOpen: isOpen,
_isPollsEnabled: !arePollsDisabled(state),
_isCCTabEnabled: isCCTabEnabled(state),
_isChatDisabled: isChatDisabled(state),
_isFileSharingTabEnabled: isFileSharingEnabled(state),
_focusedTab: focusedTab,
_focusedTab: getFocusedTab(state),
_messages: messages,
_unreadMessagesCount: unreadMessagesCount,
_unreadPollsCount: unreadPollsCount,

View File

@@ -9,6 +9,7 @@ import { IconMessage } from '../../../base/icons/svg';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { closeOverflowMenuIfOpen } from '../../../toolbox/actions.web';
import { toggleChat } from '../../actions.web';
import { isChatDisabled } from '../../functions';
import ChatCounter from './ChatCounter';
@@ -91,7 +92,8 @@ class ChatButton extends AbstractButton<IProps> {
*/
const mapStateToProps = (state: IReduxState) => {
return {
_chatOpen: state['features/chat'].isOpen
_chatOpen: state['features/chat'].isOpen,
visible: !isChatDisabled(state)
};
};

View File

@@ -2,12 +2,12 @@ import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { IReduxState } from '../../../app/types';
import Icon from '../../../base/icons/components/Icon';
import { IconCloseLarge } from '../../../base/icons/svg';
import { isFileSharingEnabled } from '../../../file-sharing/functions.any';
import { toggleChat } from '../../actions.web';
import { ChatTabs } from '../../constants';
import { getFocusedTab, isChatDisabled } from '../../functions';
interface IProps {
@@ -40,7 +40,8 @@ interface IProps {
function ChatHeader({ className, isCCTabEnabled, isPollsEnabled }: IProps) {
const dispatch = useDispatch();
const { t } = useTranslation();
const { focusedTab } = useSelector((state: IReduxState) => state['features/chat']);
const _isChatDisabled = useSelector(isChatDisabled);
const focusedTab = useSelector(getFocusedTab);
const fileSharingTabEnabled = useSelector(isFileSharingEnabled);
const onCancel = useCallback(() => {
@@ -56,7 +57,7 @@ function ChatHeader({ className, isCCTabEnabled, isPollsEnabled }: IProps) {
let title = 'chat.title';
if (focusedTab === ChatTabs.CHAT) {
if (!_isChatDisabled && focusedTab === ChatTabs.CHAT) {
title = 'chat.tabs.chat';
} else if (isPollsEnabled && focusedTab === ChatTabs.POLLS) {
title = 'chat.tabs.polls';
@@ -64,6 +65,11 @@ function ChatHeader({ className, isCCTabEnabled, isPollsEnabled }: IProps) {
title = 'chat.tabs.closedCaptions';
} else if (fileSharingTabEnabled && focusedTab === ChatTabs.FILE_SHARING) {
title = 'chat.tabs.fileSharing';
} else {
// If the focused tab is not enabled, don't render the header.
// This should not happen in normal circumstances since Chat.tsx already checks
// if any tabs are available before rendering.
return null;
}
return (

View File

@@ -25,6 +25,16 @@ interface IProps extends WithTranslation {
*/
isCCTabEnabled: boolean;
/**
* Whether chat is disabled.
*/
isChatDisabled: boolean;
/**
* Whether file sharing is enabled.
*/
isFileSharingEnabled: boolean;
/**
* Whether the polls feature is enabled or not.
*/
@@ -74,18 +84,31 @@ class DisplayNameForm extends Component<IProps, IState> {
* @returns {ReactElement}
*/
override render() {
const { isCCTabEnabled, isPollsEnabled, t } = this.props;
const { isCCTabEnabled, isChatDisabled, isFileSharingEnabled, isPollsEnabled, t } = this.props;
let title = 'chat.nickname.title';
// Build array of enabled feature names (translated).
const features = [
!isChatDisabled ? t('chat.nickname.featureChat') : '',
isPollsEnabled ? t('chat.nickname.featurePolls') : '',
isFileSharingEnabled ? t('chat.nickname.featureFileSharing') : '',
isCCTabEnabled ? t('chat.nickname.featureClosedCaptions') : ''
].filter(Boolean);
if (isCCTabEnabled && isPollsEnabled) {
title = 'chat.nickname.titleWithPollsAndCC';
} else if (isCCTabEnabled) {
title = 'chat.nickname.titleWithCC';
} else if (isPollsEnabled) {
title = 'chat.nickname.titleWithPolls';
// Return null if no features available - component won't render.
if (features.length === 0) {
return null;
}
// Build translation arguments dynamically: { feature1: "chat", feature2: "polls", ... }
const translationArgs = features.reduce((acc, feature, index) => {
acc[`feature${index + 1}`] = feature;
return acc;
}, {} as Record<string, string>);
// Use dynamic translation key: "titleWith1Features", "titleWith2Features", etc.
const title = t(`chat.nickname.titleWith${features.length}Features`, translationArgs);
return (
<div id = 'nickname'>
<form onSubmit = { this._onSubmit }>

View File

@@ -12,6 +12,7 @@ import Button from '../../../base/ui/components/web/Button';
import { BUTTON_TYPES } from '../../../base/ui/constants.any';
import { copyText } from '../../../base/util/copyText.web';
import { handleLobbyChatInitialized, openChat } from '../../actions.web';
import logger from '../../logger';
export interface IProps {
className?: string;
@@ -125,11 +126,11 @@ const MessageMenu = ({ message, participantId, isFromVisitor, isLobbyMessage, en
setShowCopiedMessage(false);
}, 2000);
} else {
console.error('Failed to copy text');
logger.error('Failed to copy text');
}
})
.catch(error => {
console.error('Error copying text:', error);
.catch((error: Error) => {
logger.error('Error copying text', error);
});
handleClose();
}, [ message ]);

View File

@@ -12,11 +12,14 @@ import { isJwtFeatureEnabled } from '../base/jwt/functions';
import { getParticipantById, isPrivateChatEnabled } from '../base/participants/functions';
import { IParticipant } from '../base/participants/types';
import { escapeRegexp } from '../base/util/helpers';
import { arePollsDisabled } from '../conference/functions.any';
import { isFileSharingEnabled } from '../file-sharing/functions.any';
import { getParticipantsPaneWidth } from '../participants-pane/functions';
import { isCCTabEnabled } from '../subtitles/functions.any';
import { VIDEO_SPACE_MIN_SIZE } from '../video-layout/constants';
import { IVisitorChatParticipant } from '../visitors/types';
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL, TIMESTAMP_FORMAT } from './constants';
import { ChatTabs, MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL, TIMESTAMP_FORMAT } from './constants';
import { IMessage } from './types';
/**
@@ -153,6 +156,53 @@ export function areSmileysDisabled(state: IReduxState) {
return disableChatSmileys;
}
/**
* Returns whether the chat feature is disabled.
*
* @param {IReduxState} state - The redux state.
* @returns {boolean} True if chat is disabled, false otherwise.
*/
export function isChatDisabled(state: IReduxState): boolean {
return Boolean(state['features/base/config']?.disableChat);
}
/**
* Gets the default focused tab based on what features are enabled.
* Returns the first available tab in priority order: CHAT -> POLLS -> FILE_SHARING -> CLOSED_CAPTIONS.
*
* @param {IReduxState} state - The redux state.
* @returns {ChatTabs | undefined} The default focused tab.
*/
export function getDefaultFocusedTab(state: IReduxState): ChatTabs | undefined {
if (!isChatDisabled(state)) {
return ChatTabs.CHAT;
}
if (!arePollsDisabled(state)) {
return ChatTabs.POLLS;
}
if (isFileSharingEnabled(state)) {
return ChatTabs.FILE_SHARING;
}
if (isCCTabEnabled(state)) {
return ChatTabs.CLOSED_CAPTIONS;
}
return undefined;
}
/**
* Returns the currently focused tab or the default focused tab if none is set.
*
* @param {IReduxState} state - The redux state.
* @returns {ChatTabs | undefined} The focused tab or undefined if no tabs are available.
*/
export function getFocusedTab(state: IReduxState): ChatTabs | undefined {
return state['features/chat'].focusedTab || getDefaultFocusedTab(state);
}
/**
* Returns the timestamp to display for the message.
*

View File

@@ -0,0 +1,23 @@
import { useSelector } from 'react-redux';
import ChatButton from './components/web/ChatButton';
import { isChatDisabled } from './functions';
const chat = {
key: 'chat',
Content: ChatButton,
group: 2
};
/**
* A hook that returns the chat button if chat is not disabled.
*
* @returns {Object | undefined} - The chat button object or undefined.
*/
export function useChatButton() {
const _isChatDisabled = useSelector(isChatDisabled);
if (!_isChatDisabled) {
return chat;
}
}

View File

@@ -0,0 +1,3 @@
import { getLogger } from '../base/logging/functions';
export default getLogger('app:chat');

View File

@@ -25,6 +25,8 @@ import { IParticipant } from '../base/participants/types';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { playSound, registerSound, unregisterSound } from '../base/sounds/actions';
import { arePollsDisabled } from '../conference/functions.any';
import { isFileSharingEnabled } from '../file-sharing/functions.any';
import { addGif } from '../gifs/actions';
import { extractGifURL, getGifDisplayMode, isGifEnabled, isGifMessage } from '../gifs/function.any';
import { showMessageNotification } from '../notifications/actions';
@@ -34,6 +36,7 @@ import { ADD_REACTION_MESSAGE } from '../reactions/actionTypes';
import { pushReactions } from '../reactions/actions.any';
import { ENDPOINT_REACTION_NAME } from '../reactions/constants';
import { getReactionMessageFromBuffer, isReactionsEnabled } from '../reactions/functions.any';
import { isCCTabEnabled } from '../subtitles/functions.any';
import { showToolbox } from '../toolbox/actions';
import { getDisplayName } from '../visitors/functions';
@@ -66,7 +69,9 @@ import {
} from './constants';
import {
getDisplayNameSuffix,
getFocusedTab,
getUnreadCount,
isChatDisabled,
isSendGroupChatDisabled,
isVisitorChatParticipant
} from './functions';
@@ -181,23 +186,28 @@ MiddlewareRegistry.register(store => next => action => {
case SET_FOCUSED_TAB:
case OPEN_CHAT: {
const focusedTab = action.tabId || getState()['features/chat'].focusedTab;
const state = store.getState();
const focusedTab = action.tabId || getFocusedTab(state);
if (focusedTab === ChatTabs.CHAT) {
// Don't allow opening chat if it's disabled AND user is trying to open the CHAT tab.
if (isChatDisabled(state)) {
return next(action);
}
unreadCount = 0;
if (typeof APP !== 'undefined') {
APP.API.notifyChatUpdated(unreadCount, true);
}
const { privateMessageRecipient } = store.getState()['features/chat'];
const { privateMessageRecipient } = state['features/chat'];
if (
isSendGroupChatDisabled(store.getState())
isSendGroupChatDisabled(state)
&& privateMessageRecipient
&& !action.participant
) {
const participant = getParticipantById(store.getState(), privateMessageRecipient.id);
const participant = getParticipantById(state, privateMessageRecipient.id);
if (participant) {
action.participant = participant;
@@ -207,7 +217,21 @@ MiddlewareRegistry.register(store => next => action => {
}
}
} else if (focusedTab === ChatTabs.POLLS) {
// Don't allow opening chat panel if polls are disabled AND user is trying to open the POLLS tab.
if (arePollsDisabled(state)) {
return next(action);
}
dispatch(resetUnreadPollsCount());
// Don't allow opening chat panel if file sharing is disabled AND user is trying to open the
// FILE_SHARING tab.
} else if (focusedTab === ChatTabs.FILE_SHARING && !isFileSharingEnabled(state)) {
return next(action);
// Don't allow opening chat panel if closed captions are disabled AND user is trying to open the
// CLOSED_CAPTIONS tab.
} else if (focusedTab === ChatTabs.CLOSED_CAPTIONS && !isCCTabEnabled(state)) {
return next(action);
}
break;
@@ -239,7 +263,7 @@ MiddlewareRegistry.register(store => next => action => {
const participantExists = getParticipantById(state, shouldSendPrivateMessageTo.id);
if (participantExists || shouldSendPrivateMessageTo.isFromVisitor) {
dispatch(openDialog(ChatPrivacyDialog, {
dispatch(openDialog('ChatPrivacyDialog', ChatPrivacyDialog, {
message: action.message,
participantID: shouldSendPrivateMessageTo.id,
isFromVisitor: shouldSendPrivateMessageTo.isFromVisitor,
@@ -576,6 +600,11 @@ function _handleReceivedMessage({ dispatch, getState }: IStore,
const { isOpen: isChatOpen } = state['features/chat'];
const { soundsIncomingMessage: soundEnabled, userSelectedNotifications } = state['features/base/settings'];
// Don't play sound or show notifications if chat is disabled.
if (isChatDisabled(state)) {
return;
}
if (soundEnabled && shouldPlaySound && !isChatOpen) {
dispatch(playSound(INCOMING_MSG_SOUND_ID));
}

View File

@@ -35,7 +35,7 @@ const DEFAULT_STATE = {
privateMessageRecipient: undefined,
lobbyMessageRecipient: undefined,
isLobbyChatActive: false,
focusedTab: ChatTabs.CHAT,
focusedTab: undefined,
isResizing: false,
width: {
current: CHAT_SIZE,
@@ -44,7 +44,7 @@ const DEFAULT_STATE = {
};
export interface IChatState {
focusedTab: ChatTabs;
focusedTab?: ChatTabs;
groupChatWithPermissions: boolean;
isLobbyChatActive: boolean;
isOpen: boolean;

View File

@@ -22,7 +22,7 @@ export function notifyKickedOut(participant: any, submit?: Function) {
return;
}
dispatch(openDialog(AlertDialog, {
dispatch(openDialog('AlertDialog', AlertDialog, {
contentKey: {
key: participant ? 'dialog.kickTitle' : 'dialog.kickSystemTitle',
params: {
@@ -52,7 +52,7 @@ export function notifyConferenceFailed(reasonKey: string, submit?: Function) {
// we have to push the opening of the dialog to the queue
// so that we make sure it will be visible after the events
// of conference destroyed are done
setTimeout(() => dispatch(openDialog(AlertDialog, {
setTimeout(() => dispatch(openDialog('AlertDialog', AlertDialog, {
contentKey: {
key: reasonKey
},
@@ -60,7 +60,7 @@ export function notifyConferenceFailed(reasonKey: string, submit?: Function) {
},
onSubmit: () => {
submit?.();
dispatch(hideDialog(AlertDialog));
dispatch(hideDialog('AlertDialog', AlertDialog));
}
})));
};

View File

@@ -17,7 +17,7 @@ import logger from './logger';
*/
export function openLeaveReasonDialog(title?: string) {
return (dispatch: IStore['dispatch']): Promise<void> => new Promise(resolve => {
dispatch(openDialog(LeaveReasonDialog, {
dispatch(openDialog('LeaveReasonDialog', LeaveReasonDialog, {
onClose: resolve,
title
}));

View File

@@ -26,7 +26,7 @@ function SpeakerStatsLabel() {
const { t } = useTranslation();
const onClick = () => {
dispatch(openDialog(SpeakerStats, { conference }));
dispatch(openDialog('SpeakerStats', SpeakerStats, { conference }));
};
if (count <= 2 || _isSpeakerStatsDisabled) {

View File

@@ -327,7 +327,7 @@ export function _mapDispatchToProps(dispatch: IStore['dispatch']) {
* @returns {void}
*/
_onOpenBandwidthDialog() {
dispatch(openDialog(BandwidthSettingsDialog));
dispatch(openDialog('BandwidthSettingsDialog', BandwidthSettingsDialog));
}
};
}

View File

@@ -18,7 +18,7 @@ type Options = {
export function showDesktopPicker(options: Options = {}, onSourceChoose: Function) {
const { desktopSharingSources } = options;
return openDialog(DesktopPicker, {
return openDialog('DesktopPicker', DesktopPicker, {
desktopSharingSources,
onSourceChoose
});

View File

@@ -14,7 +14,7 @@ export function openDisplayNamePrompt({ onPostSubmit, validateInput }: {
onPostSubmit?: Function;
validateInput?: Function;
}) {
return openDialog(DisplayNamePrompt, {
return openDialog('DisplayNamePrompt', DisplayNamePrompt, {
onPostSubmit,
validateInput
});

View File

@@ -16,7 +16,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
case SETTINGS_UPDATED: {
if (action.settings.displayName
&& isDialogOpen(getState, DisplayNamePrompt)) {
dispatch(hideDialog(DisplayNamePrompt));
dispatch(hideDialog('DisplayNamePrompt', DisplayNamePrompt));
}
}
}

View File

@@ -159,7 +159,7 @@ StateListenerRegistry.register(
});
conference.on(JitsiConferenceEvents.E2EE_VERIFICATION_READY, (pId: string, sas: object) => {
dispatch(openDialog(ParticipantVerificationDialog, { pId,
dispatch(openDialog('ParticipantVerificationDialog', ParticipantVerificationDialog, { pId,
sas }));
});

View File

@@ -31,7 +31,7 @@ class EmbedMeetingButton extends AbstractButton<AbstractButtonProps> {
const { dispatch } = this.props;
sendAnalytics(createToolbarEvent('embed.meeting'));
dispatch(openDialog(EmbedMeetingDialog));
dispatch(openDialog('EmbedMeetingDialog', EmbedMeetingDialog));
}
}

View File

@@ -109,7 +109,7 @@ export function maybeOpenFeedbackDialog(conference: IJitsiConference, title?: st
* @returns {Object}
*/
export function openFeedbackDialog(conference?: IJitsiConference, title?: string, onClose?: Function) {
return openDialog(FeedbackDialog, {
return openDialog('FeedbackDialog', FeedbackDialog, {
conference,
onClose,
title

View File

@@ -0,0 +1,45 @@
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import { IconShareDoc } from '../../../base/icons/svg';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { openFileSharingPanel } from '../../../chat/actions.any';
import { isFileSharingEnabled } from '../../functions.any';
/**
* Component that renders a button to open the file sharing panel.
*
* @augments AbstractButton
*/
class FileSharingButton extends AbstractButton<AbstractButtonProps> {
override icon = IconShareDoc;
override label = 'toolbar.fileSharing';
override tooltip = 'toolbar.fileSharing';
/**
* Handles clicking the button to open the file sharing panel.
*
* @private
* @returns {void}
*/
override _handleClick() {
const { dispatch } = this.props;
dispatch(openFileSharingPanel());
}
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {IReduxState} state - The Redux state.
* @returns {Object} - Mapped props.
*/
function mapStateToProps(state: IReduxState) {
return {
visible: isFileSharingEnabled(state)
};
}
export default translate(connect(mapStateToProps)(FileSharingButton));

View File

@@ -0,0 +1,23 @@
import { useSelector } from 'react-redux';
import FileSharingButton from './components/web/FileSharingButton';
import { isFileSharingEnabled } from './functions.any';
const fileSharing = {
key: 'filesharing',
Content: FileSharingButton,
group: 2
};
/**
* A hook that returns the file sharing button if it is enabled and undefined otherwise.
*
* @returns {Object | undefined} - The file sharing button object or undefined.
*/
export function useFileSharingButton() {
const isEnabled = useSelector(isFileSharingEnabled);
if (isEnabled) {
return fileSharing;
}
}

View File

@@ -1,28 +1,12 @@
import { IReduxState, IStore } from '../app/types';
import { getParticipantById, getVirtualScreenshareParticipantOwnerId } from '../base/participants/functions';
import {
getActiveSpeakersToBeDisplayed,
getVirtualScreenshareParticipantOwnerId
} from '../base/participants/functions';
import { setRemoteParticipants } from './actions';
import { isFilmstripScrollVisible } from './functions';
/**
* Returns an array containing the first `count` items from a set.
*
* @param {Set<T>} set - The set from which to take items.
* @param {number} count - The number of items to take.
* @returns {T[]} An array containing the taken items.
* @private
*/
function _takeFirstN<T>(set: Set<T>, count: number): T[] {
const result: T[] = [];
for (const item of set) {
if (result.length >= count) break;
result.push(item);
}
return result;
}
/**
* Computes the reorderd list of the remote participants.
*
@@ -32,7 +16,7 @@ function _takeFirstN<T>(set: Set<T>, count: number): T[] {
* @returns {void}
* @private
*/
export function updateRemoteParticipants(store: IStore, force?: boolean, participantId?: string): void {
export function updateRemoteParticipants(store: IStore, force?: boolean, participantId?: string) {
const state = store.getState();
let reorderedParticipants = [];
const { sortedRemoteVirtualScreenshareParticipants } = state['features/base/participants'];
@@ -49,65 +33,44 @@ export function updateRemoteParticipants(store: IStore, force?: boolean, partici
}
const {
dominantSpeaker,
fakeParticipants,
previousSpeakers,
sortedRemoteParticipants
} = state['features/base/participants'];
const { visibleRemoteParticipants } = state['features/filmstrip'];
const remoteParticipants = new Map(sortedRemoteParticipants);
const screenShareParticipants = sortedRemoteVirtualScreenshareParticipants
? [ ...sortedRemoteVirtualScreenshareParticipants.keys() ] : [];
const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
const speakers: Set<string> = new Set();
const dominant = dominantSpeaker ? getParticipantById(state, dominantSpeaker) : undefined;
const config = state['features/base/config'];
const defaultRemoteDisplayName = config?.defaultRemoteDisplayName ?? 'Fellow Jitster';
// Generate the remote active speakers list.
if (dominant && !dominant.local) {
speakers.add(dominant.id);
}
previousSpeakers.forEach(id => speakers.add(id));
const speakers = getActiveSpeakersToBeDisplayed(state);
for (const screenshare of screenShareParticipants) {
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare);
remoteParticipants.delete(ownerId);
remoteParticipants.delete(screenshare);
speakers.delete(ownerId);
}
// Calculate the number of slots available for active speakers and then sort them alphabetically to ensure
// consistent order.
const numberOfActiveSpeakerSlots
= visibleRemoteParticipants.size - screenShareParticipants.length - sharedVideos.length;
const activeSpeakersDisplayed = _takeFirstN(speakers, numberOfActiveSpeakerSlots)
.sort((a: string, b: string) => {
return (getParticipantById(state, a)?.name ?? defaultRemoteDisplayName)
.localeCompare(getParticipantById(state, b)?.name ?? defaultRemoteDisplayName);
});
for (const sharedVideo of sharedVideos) {
remoteParticipants.delete(sharedVideo);
}
for (const speaker of speakers.keys()) {
remoteParticipants.delete(speaker);
}
// Always update the order of the thubmnails.
const participantsWithScreenShare = screenShareParticipants.reduce<string[]>((acc, screenshare) => {
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare);
acc.push(ownerId);
acc.push(screenshare);
remoteParticipants.delete(ownerId);
remoteParticipants.delete(screenshare);
return acc;
}, []);
// Always update the order of the thumbnails.
reorderedParticipants = [
...participantsWithScreenShare,
...sharedVideos,
...activeSpeakersDisplayed,
...Array.from(speakers.keys()),
...Array.from(remoteParticipants.keys())
];

View File

@@ -1,4 +1,3 @@
import { getParticipantById } from '../base/participants/functions';
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { isFilmstripScrollVisible, updateRemoteParticipants } from './functions';
@@ -26,15 +25,7 @@ StateListenerRegistry.register(
*/
StateListenerRegistry.register(
/* selector */ state => state['features/base/participants'].dominantSpeaker,
/* listener */ (dominantSpeaker, store) => {
const { visibleRemoteParticipants } = store.getState()['features/filmstrip'];
const dominant = getParticipantById(store.getState(), dominantSpeaker);
// Only update the remote participants if the dominant speaker is not currently visible.
if (!dominant?.local && !visibleRemoteParticipants.has(dominantSpeaker)) {
updateRemoteParticipants(store);
}
});
/* listener */ (dominantSpeaker, store) => updateRemoteParticipants(store));
/**
* Listens for changes in the filmstrip scroll visibility.

View File

@@ -559,7 +559,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
* @returns {void}
*/
_showFailedInviteAlert() {
this.props.dispatch(openDialog(AlertDialog, {
this.props.dispatch(openDialog('AlertDialog', AlertDialog, {
contentKey: {
key: 'inviteDialog.alertText'
}

View File

@@ -95,7 +95,7 @@ class DialInSummary extends PureComponent<IProps> {
* @returns {void}
*/
_onError() {
this.props.dispatch(openDialog(DialInSummaryErrorDialog));
this.props.dispatch(openDialog('DialInSummaryErrorDialog', DialInSummaryErrorDialog));
}
/**

View File

@@ -41,7 +41,7 @@ MiddlewareRegistry.register(store => next => action => {
function _beginAddPeople({ dispatch }: IStore, next: Function, action: AnyAction) {
const result = next(action);
dispatch(openDialog(AddPeopleDialog));
dispatch(openDialog('AddPeopleDialog', AddPeopleDialog));
return result;
}
@@ -60,7 +60,7 @@ function _beginAddPeople({ dispatch }: IStore, next: Function, action: AnyAction
* @returns {*} The value returned by {@code next(action)}.
*/
function _hideAddPeopleDialog({ dispatch }: IStore, next: Function, action: AnyAction) {
dispatch(hideDialog(AddPeopleDialog));
dispatch(hideDialog('AddPeopleDialog', AddPeopleDialog));
return next(action);
}

View File

@@ -15,7 +15,7 @@ import { isFeatureDisabled } from './functions';
export function maybeShowPremiumFeatureDialog(feature: ParticipantFeaturesKey) {
return function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
if (isFeatureDisabled(getState(), feature)) {
dispatch(openDialog(PremiumFeatureDialog));
dispatch(openDialog('PremiumFeatureDialog', PremiumFeatureDialog));
return true;
}

View File

@@ -11,6 +11,7 @@ import {
getVirtualScreenshareParticipantByOwnerId
} from '../base/participants/functions';
import { toState } from '../base/redux/functions';
import { isStageFilmstripAvailable } from '../filmstrip/functions';
import { getAutoPinSetting } from '../video-layout/functions';
import {
@@ -18,7 +19,6 @@ import {
SET_LARGE_VIDEO_DIMENSIONS,
UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
} from './actionTypes';
import { shouldHideLargeVideo } from './functions';
/**
* Action to select the participant to be displayed in LargeVideo based on the
@@ -34,8 +34,12 @@ export function selectParticipantInLargeVideo(participant?: string) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
// Skip large video updates when the large video container is hidden.
if (shouldHideLargeVideo(state)) {
if (isStageFilmstripAvailable(state, 2)) {
return;
}
// Keep Etherpad open.
if (state['features/etherpad'].editing) {
return;
}
@@ -161,26 +165,18 @@ function _electParticipantInLargeVideo(state: IReduxState) {
}
}
// Next, pick the dominant speaker or the last active speaker if the dominant speaker is local.
// Next, pick the dominant speaker (other than self).
participant = getDominantSpeakerParticipant(state);
let speakerId: string | undefined;
if (participant && !participant.local) {
// Return the screensharing participant id associated with this endpoint if multi-stream is enabled and
// auto_pin_latest_screen_share setting is disabled.
const screenshareParticipant = getVirtualScreenshareParticipantByOwnerId(state, participant.id);
if (participant?.local) {
const { previousSpeakers } = state['features/base/participants'];
if (previousSpeakers?.size) {
speakerId = previousSpeakers.keys().next().value;
}
} else if (participant) {
speakerId = participant.id;
return screenshareParticipant?.id ?? participant.id;
}
// Return the screensharing participant id associated with this endpoint.
if (speakerId) {
const screenshareParticipant = getVirtualScreenshareParticipantByOwnerId(state, speakerId);
return screenshareParticipant?.id ?? speakerId;
}
// In case this is the local participant.
participant = undefined;
// Next, pick the most recent participant with video.
const lastVisibleRemoteParticipant = _electLastVisibleRemoteParticipant(state);

View File

@@ -1,7 +1,5 @@
import { IReduxState } from '../app/types';
import { getParticipantById } from '../base/participants/functions';
import { isStageFilmstripAvailable } from '../filmstrip/functions';
import { shouldDisplayTileView } from '../video-layout/functions.any';
/**
* Selector for the participant currently displaying on the large video.
@@ -14,17 +12,3 @@ export function getLargeVideoParticipant(state: IReduxState) {
return getParticipantById(state, participantId ?? '');
}
/**
* Determines whether the large video container should be hidden.
* Large video is hidden in tile view, stage filmstrip mode (with multiple participants),
* or when editing etherpad.
*
* @param {IReduxState} state - The Redux state.
* @returns {boolean} True if large video should be hidden, false otherwise.
*/
export function shouldHideLargeVideo(state: IReduxState): boolean {
return shouldDisplayTileView(state)
|| isStageFilmstripAvailable(state, 2)
|| Boolean(state['features/etherpad']?.editing);
}

View File

@@ -1,26 +0,0 @@
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { SELECT_LARGE_VIDEO_PARTICIPANT } from './actionTypes';
import { selectParticipantInLargeVideo } from './actions.any';
import { shouldHideLargeVideo } from './functions';
/**
* Updates the large video when transitioning from a hidden state to visible state.
* This ensures the large video is properly updated when exiting tile view, stage filmstrip,
* whiteboard, or etherpad editing modes.
*/
StateListenerRegistry.register(
/* selector */ state => shouldHideLargeVideo(state),
/* listener */ (isHidden, { dispatch }) => {
// When transitioning from hidden to visible state, select participant (because currently it is undefined).
// Otherwise set it to undefined because we don't show the large video.
if (!isHidden) {
dispatch(selectParticipantInLargeVideo());
} else {
dispatch({
type: SELECT_LARGE_VIDEO_PARTICIPANT,
participantId: undefined
});
}
}
);

View File

@@ -1 +0,0 @@
import './subscriber.any';

View File

@@ -4,7 +4,6 @@ import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { getVideoTrackByParticipant } from '../base/tracks/functions.web';
import { getLargeVideoParticipant } from './functions';
import './subscriber.any';
/**
* Updates the on stage participant video.

View File

@@ -467,7 +467,7 @@ export function _mapStateToProps(state: IReduxState) {
_knocking: knocking,
_lobbyChatMessages: messages,
_lobbyMessageRecipient: lobbyMessageRecipient?.name,
_login: showModeratorLogin,
_login: showModeratorLogin && !state['features/base/jwt'].jwt,
_hangUp: showHangUp,
_isLobbyChatActive: isLobbyChatActive,
_meetingName: getConferenceName(state),

View File

@@ -4,7 +4,6 @@ import { createMaterialTopTabNavigator } from '@react-navigation/material-top-ta
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { IReduxState } from '../../../../../app/types';
import {
getClientHeight,
getClientWidth
@@ -12,6 +11,7 @@ import {
import { setFocusedTab } from '../../../../../chat/actions.any';
import Chat from '../../../../../chat/components/native/Chat';
import { ChatTabs } from '../../../../../chat/constants';
import { getFocusedTab } from '../../../../../chat/functions';
import { resetUnreadPollsCount } from '../../../../../polls/actions';
import PollsPane from '../../../../../polls/components/native/PollsPane';
import { screen } from '../../../routes';
@@ -23,8 +23,8 @@ const ChatAndPolls = () => {
const clientHeight = useSelector(getClientHeight);
const clientWidth = useSelector(getClientWidth);
const dispatch = useDispatch();
const { focusedTab } = useSelector((state: IReduxState) => state['features/chat']);
const initialRouteName = focusedTab === ChatTabs.POLLS
const currentFocusedTab = useSelector(getFocusedTab);
const initialRouteName = currentFocusedTab === ChatTabs.POLLS
? screen.conference.chatandpolls.tab.polls
: screen.conference.chatandpolls.tab.chat;

View File

@@ -13,7 +13,7 @@ import PageReloadDialog from '../base/dialog/components/native/PageReloadDialog'
*/
export function openPageReloadDialog(
conferenceError?: Error, configError?: Error, connectionError?: ConnectionFailedError) {
return openDialog(PageReloadDialog, {
return openDialog('PageReloadDialog', PageReloadDialog, {
conferenceError,
configError,
connectionError

View File

@@ -34,7 +34,7 @@ export default function RenameButton({ breakoutRoomJid, name }: IProps) {
const dispatch = useDispatch();
const { classes, cx } = useStyles();
const onRename = useCallback(() => {
dispatch(openDialog(BreakoutRoomNamePrompt, {
dispatch(openDialog('BreakoutRoomNamePrompt', BreakoutRoomNamePrompt, {
breakoutRoomJid,
initialRoomName: name
}));

View File

@@ -68,7 +68,7 @@ export const RoomContextMenu = ({
}, [ dispatch, room ]);
const onRenameBreakoutRoom = useCallback(() => {
dispatch(openDialog(BreakoutRoomNamePrompt, {
dispatch(openDialog('BreakoutRoomNamePrompt', BreakoutRoomNamePrompt, {
breakoutRoomJid: room?.jid,
initialRoomName: room?.name
}));

View File

@@ -32,7 +32,7 @@ import styles from './styles';
export const ContextMenuMore = () => {
const dispatch = useDispatch();
const muteAllVideo = useCallback(() => {
dispatch(openDialog(MuteEveryonesVideoDialog));
dispatch(openDialog('MuteEveryonesVideoDialog', MuteEveryonesVideoDialog));
dispatch(hideSheet());
}, [ dispatch ]);
const conference = useSelector(getCurrentConference);

View File

@@ -42,7 +42,7 @@ const ParticipantsPaneFooter = (): JSX.Element => {
getFeatureFlag(state, BREAKOUT_ROOMS_BUTTON_ENABLED, true)
);
const openMoreMenu = useCallback(() => dispatch(openSheet(ContextMenuMore)), [ dispatch ]);
const muteAll = useCallback(() => dispatch(openDialog(MuteEveryoneDialog)),
const muteAll = useCallback(() => dispatch(openDialog('MuteEveryoneDialog', MuteEveryoneDialog)),
[ dispatch ]);
const showMoreActions = useSelector(isMoreActionsVisible);
const showMuteAll = useSelector(isMuteAllVisible);

View File

@@ -112,10 +112,10 @@ export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: IProp
const { classes } = useStyles();
const muteAllVideo = useCallback(
() => dispatch(openDialog(MuteEveryonesVideoDialog)), [ dispatch ]);
() => dispatch(openDialog('MuteEveryonesVideoDialog', MuteEveryonesVideoDialog)), [ dispatch ]);
const muteAllDesktop = useCallback(
() => dispatch(openDialog(MuteEveryonesDesktopDialog)), [ dispatch ]);
() => dispatch(openDialog('MuteEveryonesDesktopDialog', MuteEveryonesDesktopDialog)), [ dispatch ]);
const openModeratorSettings = () => dispatch(openSettingsDialog(SETTINGS_TABS.MODERATOR));

View File

@@ -181,7 +181,7 @@ const ParticipantsPane = () => {
}, []);
const onMuteAll = useCallback(() => {
dispatch(openDialog(MuteEveryoneDialog));
dispatch(openDialog('MuteEveryoneDialog', MuteEveryoneDialog));
}, []);
const onToggleContext = useCallback(() => {

View File

@@ -0,0 +1,45 @@
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import { IconInfo } from '../../../base/icons/svg';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { openPollsPanel } from '../../../chat/actions.any';
import { arePollsDisabled } from '../../../conference/functions.any';
/**
* Component that renders a button to open the polls panel.
*
* @augments AbstractButton
*/
class PollsButton extends AbstractButton<AbstractButtonProps> {
override icon = IconInfo;
override label = 'toolbar.polls';
override tooltip = 'toolbar.polls';
/**
* Handles clicking the button to open the polls panel.
*
* @private
* @returns {void}
*/
override _handleClick() {
const { dispatch } = this.props;
dispatch(openPollsPanel());
}
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {IReduxState} state - The Redux state.
* @returns {Object} - Mapped props.
*/
function mapStateToProps(state: IReduxState) {
return {
visible: !arePollsDisabled(state)
};
}
export default translate(connect(mapStateToProps)(PollsButton));

View File

@@ -0,0 +1,24 @@
import { useSelector } from 'react-redux';
import { arePollsDisabled } from '../conference/functions.any';
import PollsButton from './components/web/PollsButton';
const polls = {
key: 'polls',
Content: PollsButton,
group: 2
};
/**
* A hook that returns the polls button if it is enabled and undefined otherwise.
*
* @returns {Object | undefined} - The polls button object or undefined.
*/
export function usePollsButton() {
const isPollsDisabled = useSelector(arePollsDisabled);
if (!isPollsDisabled) {
return polls;
}
}

View File

@@ -11,6 +11,7 @@ import {
RESET_UNREAD_POLLS_COUNT,
SAVE_POLL
} from './actionTypes';
import logger from './logger';
import { IIncomingAnswerData, IPollData } from './types';
const INITIAL_STATE = {
@@ -87,7 +88,7 @@ ReducerRegistry.register<IPollsState>(STORE_NAME, (state = INITIAL_STATE, action
// if the poll doesn't exist
if (!(pollId in state.polls)) {
console.warn('requested poll does not exist: pollId ', pollId);
logger.warn('Requested poll does not exist', { pollId });
return state;
}

View File

@@ -110,7 +110,7 @@ class ReactionMenuDialog extends PureComponent<IProps> {
*/
_onCancel() {
if (this.props._isOpen) {
this.props.dispatch(hideDialog(ReactionMenu_));
this.props.dispatch(hideDialog('ReactionMenu_', ReactionMenu_));
return true;
}

View File

@@ -47,7 +47,7 @@ class ReactionsMenuButton extends AbstractButton<IProps> {
* @returns {void}
*/
override _handleClick() {
this.props.dispatch(openDialog(ReactionMenuDialog));
this.props.dispatch(openDialog('ReactionMenuDialog', ReactionMenuDialog));
}
/**

View File

@@ -76,7 +76,7 @@ export function showRecordingLimitNotification(streamType: string) {
*/
export function showStartRecordingNotification() {
return (dispatch: IStore['dispatch']) => {
const openDialogCallback = () => dispatch(openDialog(StartRecordingDialog));
const openDialogCallback = () => dispatch(openDialog('StartRecordingDialog', StartRecordingDialog));
dispatch(showStartRecordingNotificationWithCallback(openDialogCallback));
};

View File

@@ -34,7 +34,7 @@ class LiveStreamButton extends AbstractLiveStreamButton<Props> {
const { _isLiveStreamRunning, dispatch } = this.props;
if (_isLiveStreamRunning) {
dispatch(openDialog(StopLiveStreamDialog));
dispatch(openDialog('StopLiveStreamDialog', StopLiveStreamDialog));
} else {
navigate(screen.conference.liveStream);
}

View File

@@ -26,10 +26,10 @@ class LiveStreamButton extends AbstractLiveStreamButton<IProps> {
*/
override _onHandleClick() {
const { _isLiveStreamRunning, dispatch } = this.props;
const dialogComponent = _isLiveStreamRunning ? StopLiveStreamDialog : StartLiveStreamDialog;
const dialogName = _isLiveStreamRunning ? 'StopLiveStreamDialog' : 'StartLiveStreamDialog';
dispatch(openDialog(
_isLiveStreamRunning ? StopLiveStreamDialog : StartLiveStreamDialog
));
dispatch(openDialog(dialogName, dialogComponent));
}
}

View File

@@ -82,7 +82,7 @@ export default class AbstractHighlightButton<P extends IProps, S={}> extends Com
const dialogShown = dispatch(maybeShowPremiumFeatureDialog(MEET_FEATURES.RECORDING));
if (!dialogShown) {
dispatch(openDialog(StartRecordingDialog));
dispatch(openDialog('StartRecordingDialog', StartRecordingDialog));
}
} ],
appearance: NOTIFICATION_TYPE.NORMAL

View File

@@ -213,13 +213,16 @@ const LocalRecordingManager: ILocalRecordingManager = {
});
const gdmVideoTrack = gdmStream.getVideoTracks()[0];
const isBrowser = gdmVideoTrack.getSettings().displaySurface === 'browser';
const matchesHandle = (supportsCaptureHandle // @ts-ignore
&& gdmVideoTrack.getCaptureHandle()?.handle === `JitsiMeet-${tabId}`);
if (!isBrowser || !matchesHandle) {
gdmStream.getTracks().forEach((track: MediaStreamTrack) => track.stop());
throw new Error('WrongSurfaceSelected');
if (supportsCaptureHandle) {
const isBrowser = gdmVideoTrack.getSettings().displaySurface === 'browser';
const matchesHandle = (supportsCaptureHandle // @ts-ignore
&& gdmVideoTrack.getCaptureHandle()?.handle === `JitsiMeet-${tabId}`);
if (!isBrowser || !matchesHandle) {
gdmStream.getTracks().forEach((track: MediaStreamTrack) => track.stop());
throw new Error('WrongSurfaceSelected');
}
}
this.initializeAudioMixer();

View File

@@ -37,7 +37,7 @@ class RecordButton extends AbstractRecordButton<Props> {
const { _isRecordingRunning, dispatch } = this.props;
if (_isRecordingRunning) {
dispatch(openDialog(StopRecordingDialog));
dispatch(openDialog('StopRecordingDialog', StopRecordingDialog));
} else {
navigate(screen.conference.recording);
}

View File

@@ -138,7 +138,7 @@ export class HighlightButton extends AbstractHighlightButton<IProps, IState> {
const dialogShown = dispatch(maybeShowPremiumFeatureDialog(MEET_FEATURES.RECORDING));
if (!dialogShown) {
dispatch(openDialog(StartRecordingDialog));
dispatch(openDialog('StartRecordingDialog', StartRecordingDialog));
}
}

View File

@@ -26,10 +26,10 @@ class RecordingButton extends AbstractRecordButton<IProps> {
*/
override _onHandleClick() {
const { _isRecordingRunning, dispatch } = this.props;
const dialogComponent = _isRecordingRunning ? StopRecordingDialog : StartRecordingDialog;
const dialogName = _isRecordingRunning ? 'StopRecordingDialog' : 'StartRecordingDialog';
dispatch(openDialog(
_isRecordingRunning ? StopRecordingDialog : StartRecordingDialog
));
dispatch(openDialog(dialogName, dialogComponent));
}
}

View File

@@ -427,6 +427,6 @@ function _showExplicitConsentDialog(recorderSession: any, dispatch: IStore['disp
dispatch(setVideoUnmutePermissions(true, true));
dispatch(setAudioMuted(true));
dispatch(setVideoMuted(true));
dispatch(openDialog(RecordingConsentDialog));
dispatch(openDialog('RecordingConsentDialog', RecordingConsentDialog));
});
}

View File

@@ -64,7 +64,7 @@ let permissionsReplyListener: Function | undefined,
* @public
*/
export function openRemoteControlAuthorizationDialog(participantId: string) {
return openDialog(RemoteControlAuthorizationDialog, { participantId });
return openDialog('RemoteControlAuthorizationDialog', RemoteControlAuthorizationDialog, { participantId });
}
/**

View File

@@ -71,7 +71,7 @@ export function endRoomLockRequest(
= password
? dispatch(setPassword(conference, conference.lock, password))
: Promise.resolve();
const endRoomLockRequest_ = () => dispatch(hideDialog(SecurityDialog));
const endRoomLockRequest_ = () => dispatch(hideDialog('SecurityDialog', SecurityDialog));
setPassword_.then(endRoomLockRequest_, endRoomLockRequest_);
};
@@ -90,7 +90,7 @@ export function endRoomLockRequest(
* }}
*/
export function _openPasswordRequiredPrompt(conference: IJitsiConference) {
return openDialog(PasswordRequiredPrompt, { conference });
return openDialog('PasswordRequiredPrompt', PasswordRequiredPrompt, { conference });
}
/**

View File

@@ -74,7 +74,7 @@ MiddlewareRegistry.register(store => next => action => {
* @returns {*}
*/
function _conferenceJoined({ dispatch }: IStore, next: Function, action: AnyAction) {
dispatch(hideDialog(PasswordRequiredPrompt));
dispatch(hideDialog('PasswordRequiredPrompt', PasswordRequiredPrompt));
return next(action);
}
@@ -104,7 +104,7 @@ function _conferenceFailed({ dispatch }: IStore, next: Function, action: AnyActi
dispatch(_openPasswordRequiredPrompt(conference));
}
} else {
dispatch(hideDialog(PasswordRequiredPrompt));
dispatch(hideDialog('PasswordRequiredPrompt', PasswordRequiredPrompt));
}
return next(action);

View File

@@ -28,7 +28,7 @@ export function showSalesforceNotification() {
customActionNameKey: [ 'notify.linkToSalesforceKey' ],
customActionHandler: [ () => {
dispatch(hideNotification(SALESFORCE_LINK_NOTIFICATION_ID));
dispatch(openDialog(SalesforceLinkDialog));
dispatch(openDialog('SalesforceLinkDialog', SalesforceLinkDialog));
} ],
appearance: NOTIFICATION_TYPE.NORMAL
}, NOTIFICATION_TIMEOUT_TYPE.LONG));

View File

@@ -59,7 +59,7 @@ export function startAudioScreenShareFlow() {
// If we're already in a normal screen sharing session, warn the user.
if (isScreenVideoShared(state)) {
dispatch(openDialog(ShareMediaWarningDialog, { _isAudioScreenShareWarning: true }));
dispatch(openDialog('ShareMediaWarningDialog', ShareMediaWarningDialog, { _isAudioScreenShareWarning: true }));
return;
}
@@ -76,7 +76,7 @@ export function startAudioScreenShareFlow() {
return;
}
dispatch(openDialog(ShareAudioDialog));
dispatch(openDialog('ShareAudioDialog', ShareAudioDialog));
};
}
@@ -94,7 +94,7 @@ export function startScreenShareFlow(enabled: boolean) {
// If we're in an audio screen sharing session, warn the user.
if (audioOnlySharing) {
dispatch(openDialog(ShareMediaWarningDialog, { _isAudioScreenShareWarning: false }));
dispatch(openDialog('ShareMediaWarningDialog', ShareMediaWarningDialog, { _isAudioScreenShareWarning: false }));
return;
}

View File

@@ -78,7 +78,7 @@ export function openCameraCaptureDialog(callback: Function, componentProps: ICam
return;
}
dispatch(openDialog(CameraCaptureDialog, {
dispatch(openDialog('CameraCaptureDialog', CameraCaptureDialog, {
callback,
componentProps
}));

View File

@@ -10,6 +10,6 @@ import { SecurityDialog } from './components/security-dialog';
*/
export function toggleSecurityDialog() {
return function(dispatch: IStore['dispatch']) {
dispatch(toggleDialog(SecurityDialog));
dispatch(toggleDialog('SecurityDialog', SecurityDialog));
};
}

View File

@@ -21,7 +21,7 @@ export function openLogoutDialog() {
const config = state['features/base/config'];
const logoutUrl = config.tokenLogoutUrl;
dispatch(openDialog(LogoutDialog, {
dispatch(openDialog('LogoutDialog', LogoutDialog, {
onLogout() {
if (isTokenAuthEnabled(config)) {
if (logoutUrl) {

View File

@@ -53,7 +53,7 @@ export function openLogoutDialog() {
const { conference } = state['features/base/conference'];
const { jwt } = state['features/base/jwt'];
dispatch(openDialog(LogoutDialog, {
dispatch(openDialog('LogoutDialog', LogoutDialog, {
onLogout() {
if (isTokenAuthEnabled(config) && config.tokenAuthUrlAutoRedirect && jwt) {
@@ -90,7 +90,7 @@ export function openLogoutDialog() {
* @returns {Function}
*/
export function openSettingsDialog(defaultTab?: string, isDisplayedOnWelcomePage?: boolean) {
return openDialog(SettingsDialog, {
return openDialog('SettingsDialog', SettingsDialog, {
defaultTab,
isDisplayedOnWelcomePage
});

Some files were not shown because too many files have changed in this diff Show More