Compare commits

...

90 Commits

Author SHA1 Message Date
Hristo Terezov
303f81f427 fix(participants-pane): replace :first-child with :first-of-type to silence emotion SSR warning
emotion/stylis emits a console error for :first-child selectors as they can
cause hydration mismatches in SSR. All three occurrences that trigger on
conference load are in ParticipantsPane. The semantics are equivalent since
all sibling elements are the same type.
2026-02-18 08:30:23 -06:00
Hristo Terezov
23a65fbf2a fix(ui): migrate ReactDOM.render to createRoot for React 18 compatibility
ReactDOM.render is deprecated in React 18 and causes console errors on every
render. Migrate to createRoot API across the main entry point and the legacy
video layout modules, storing root references to avoid recreating them on
repeated calls.
2026-02-18 08:25:25 -06:00
Hristo Terezov
5fee521938 fix(ui): replace kebab-case CSS properties with camelCase in style objects
Vendor-prefixed CSS properties like -webkit-appearance and -moz-appearance
used as object keys in emotion/tss-react style objects cause console errors
because JavaScript CSS-in-JS libraries require camelCase property names.

Replace all occurrences across Input, Chat, Filmstrip, and Slider components.
2026-02-18 08:25:08 -06:00
Mihaela Dumitru
9ea9f4c899 [WIP] feat(ui) add semantic tokens (#16772) 2026-02-06 15:06:54 +02:00
Mihaela Dumitru
28c72bfa7f fix(toolbar): remove hang up from prejoin app 2026-02-06 12:48:29 +01:00
dependabot[bot]
24c78cf8ff chore(deps-dev): bump webpack from 5.95.0 to 5.105.0
Bumps [webpack](https://github.com/webpack/webpack) from 5.95.0 to 5.105.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Changelog](https://github.com/webpack/webpack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack/compare/v5.95.0...v5.105.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-version: 5.105.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-06 08:58:18 +01:00
Stephan Paternotte
147e64106d lang: Dutch translation updates.
* Update main-nl.json

Full update to latest state of main.json including Dutch translation updates.
Validated with JSONLint

* Fixes lint.

---------

Co-authored-by: Дамян Минков <damencho@jitsi.org>
2026-02-05 16:58:54 -06:00
Hristo Terezov
3d53f2e4c5 fix(PiP): Mac OS minimize not working
When we minimize the meeting window on Mac OS via the yellow button, 2 requests for PiP are triggered one after another because of the blur and the visibilitychange handlers. If we implement in Electron to focus the meeting window on the pipLeft event this will lead to the first request to exit triggering the pipLeft event after the second request is triggered which will actually bring the window back because we focus on pipLeft event. This behaviour breaks the ability to minimize a window.

The current fix will prevent us for sending 2 requests for PiP one after another by skipping the second unnecessary request.
2026-02-05 12:36:07 -06:00
damencho
e60bfc573a fix(muc_auth_ban): Adds checks for jaas. 2026-02-04 06:14:30 -06:00
damencho
796a7efa7f fix(tests): Make tests to go through pre-join automatically. 2026-02-03 17:58:28 -06:00
damencho
fb075c376d fix(prejoin): Handles different ways of disabling prejoin.
Now handles using `config.prejoinConfig.enabled=false` and `config.prejoinConfig={enabled:false}.

Fixes #16892.
2026-02-03 17:58:28 -06:00
Mihaela Dumitru
c457ed0d3c feat(external-api): fire participantMuted event for all mute state changes (#16893) 2026-02-03 10:59:28 +02:00
damencho
1efc5e40e1 fix(settings): Fixes rendering a tab when props change.
The Profile tab can display logged in state and auth id used for it. It does not re-render when this changes in the background.
2026-02-02 15:52:19 -06:00
Дамян Минков
dc84826d9c fix(muc_cleanup_backend_services): Stops timer before creating new one. (#16894)
* fix(muc_cleanup_backend_services): Stops timer before creating new one.

* squash: Adds some logs and skip if already being destroyed.
2026-02-02 10:09:26 -06:00
Horatiu Muresan
b4a64ebc44 fix(recording-button) Fix recording button tooltip (#16891) 2026-01-30 13:28:39 +02:00
Hristo Terezov
dbb3ccc274 feat(CustomPanel): Implement an customizable panel.
The panel will appear on the right side after the participant pane panel. Currently the panel is disabled by default and the components that  are rendered in the panel are empty (null).  The panel is easily customizable by adding some content in the CustomPanel component.
2026-01-29 14:59:06 -06:00
bgrozev
9a6ed65cb1 test: Retry some tests (#16888)
* chore: Update wdio to 9.23.2.

* test: Add a retry test property.

* test: Retry some of the flaky tests.
2026-01-29 13:58:57 -06:00
bgrozev
21ea67b29c fix: Only parse transcript for transcription-result messages. (#16885) 2026-01-28 16:23:12 -06:00
bgrozev
ab4be2366f test: Add test for jaas "async" transcriptions. (#16793)
* test: Add test for jaas "async" transcriptions.

* fix: Do not expect name in async transcription events.
2026-01-27 18:56:16 -06:00
Calin-Teodor
417c38ab9e fix(filmstrip): keep AudioTracksContainer in the DOM while Filmstrip is hidden in reduced UI 2026-01-27 17:53:57 +02:00
Calinteodor
33a4245a1f fix(lobby): reset lobbyVisible state when we a potential conference is being left (#16881)
* Reset lobby state when we we dispatch conferenceWillLeave action.
2026-01-27 12:24:16 +02:00
Mihaela Dumitru
2eb07cb79f feat(participants): store user context data for external API events and functions 2026-01-27 10:42:02 +01:00
damencho
63e4c41d92 fix(tests): Fixes tests stack traces.
Before:
```
Error: element ("[data-testid="participant1-more-options-ba16a58a"]") still not existing after 1000ms
    at async ParticipantsPane.openParticipantContextMenu (/tmp/tmp.bMVHEDmYin/jitsi-meet/tests/pageobjects/ParticipantsPane.ts:202:9)
    at async BreakoutRooms.sendParticipantToBreakoutRoom (/tmp/tmp.bMVHEDmYin/jitsi-meet/tests/pageobjects/BreakoutRooms.ts:205:9)
```

After:
```
Error: element ("[data-testid="participant1-more-options-ecea6dd6"]") still not existing after 1000ms
    at async ParticipantsPane.openParticipantContextMenu (/tmp/tmp.j8VkoO9abR/jitsi-meet/tests/pageobjects/ParticipantsPane.ts:202:9)
    at async BreakoutRooms.sendParticipantToBreakoutRoom (/tmp/tmp.j8VkoO9abR/jitsi-meet/tests/pageobjects/BreakoutRooms.ts:205:9)
    at async Context.<anonymous> (/tmp/tmp.j8VkoO9abR/jitsi-meet/tests/specs/misc/breakoutRooms.spec.ts:349:9)
```
2026-01-26 12:30:32 -06:00
damencho
2c6ccd7d6b fix(transcriptions): Fixes stop transcriptions via api. 2026-01-23 13:39:48 -06:00
damencho
4ce27eeb1a chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v2118.0.0+67fd2c84...v2124.0.0+80df84a1
2026-01-23 12:34:38 -06:00
damencho
11453dcc79 fix(transcriptions): Fixes stop transcriptions in some cases. 2026-01-23 10:18:23 -06:00
damencho
3375ee49bd fix(tests): Ignore clickable for password dialog.
It gives strange error that is not clickable, but it is seen on the screenshot and test passes without it.
2026-01-23 06:03:06 -06:00
damencho
e45df58cfb fix(tests): Fixes a problem with two notifications. 2026-01-23 06:03:06 -06:00
Calin-Teodor
7d628960d7 feat(breakout-rooms): button margin style fix 2026-01-22 10:52:48 -06:00
Calin-Teodor
cf13b8f0ba fix(responsive-ui): apply reducedUI only when both width and height are below threshold 2026-01-22 10:52:48 -06:00
Jaya Allamsetty
e106109090 fix(recording) Restores track mute state to original state after consent is provided.
Do not forcefully unmute audio and video if the user consents to being recorded and wants to stay unmuted but was muted before hitting join.
2026-01-22 10:11:43 -05:00
Calin-Teodor
fc170891cb feat(subtitles): rework how isAsyncTranscriptionEnabled is being used 2026-01-22 14:00:06 +02:00
Calin-Teodor
775cc52f66 feat(subtitles): hide translation UI for both web and native 2026-01-22 10:50:03 +02:00
Calin-Teodor
3baede6ff1 feat(config): add reducedUIEnabled config to control web side 2026-01-22 10:44:15 +02:00
Дамян Минков
9462a9ce36 feat(breakout-rooms): Adds some jid validation on joining. (#16858)
* feat(breakout-rooms): Adds some jid validation on joining.

* squash: Fix comments.

* Update resources/prosody-plugins/mod_muc_breakout_rooms.lua

Co-authored-by: bgrozev <boris@jitsi.org>

* Update resources/prosody-plugins/mod_muc_breakout_rooms.lua

Co-authored-by: bgrozev <boris@jitsi.org>

---------

Co-authored-by: bgrozev <boris@jitsi.org>
2026-01-21 19:53:27 -06:00
Aaron van Meerten
4713062200 feat(muc_auth_ban): add metrics and logs for error responses from access backend (#16853) 2026-01-21 13:47:32 +01:00
Calinteodor
65eb2a2899 feat(base/modal): small updates around JitsiScreen footer (#16727)
*UI updates and reworks around JitsiScreen footer and its children.
2026-01-21 13:05:05 +02:00
Boris Grozev
f0452d05b9 feat: Hide the translation UI when asyncTranscription is used. 2026-01-20 11:56:13 -06:00
damencho
1102f4205a fix: Fixes setting backend recording when async transcriptions are not on. 2026-01-20 11:56:06 -06:00
Calinteodor
447def54c8 feat(chat/native): add Closed Captions tab (#16787)
* Added CC tab inside Chat screen and some UI fixes on mobile.
2026-01-20 13:33:46 +02:00
Calinteodor
481b9a6e58 feat(tracks): add/remove "ended" listener when screen sharing
* Listen to the MediaStreamTrack "ended" event so local screen-share state is toggled off when capture is terminated externally.
2026-01-19 16:07:59 +02:00
damencho
fb3bc3c367 feat(dynamic-branding): Adds more options for overriding translations.
In branding you can add now
"labels-translation-languages": {
        "en": "/static/translation-overwritten-en.json"
    }

This allows overwriting strings from the translation-languages namespace, till now it was possible only for the main one.
2026-01-16 07:47:54 -06:00
zobadaniel
9fa5489154 add lower sorbian to the list of codes (#16844) 2026-01-16 07:39:02 -06:00
Anton
9499bf29ed fix(premeeting): device status indicator size when label is long (#16559) 2026-01-16 09:14:32 +01:00
José Luís Andrade
f605b5c487 lang: Update Portuguese translations in main-pt.json (#16790) 2026-01-15 22:04:30 -06:00
Nicolas
88fba5acab lang: Updates to Danish translation (#16781) 2026-01-15 22:01:23 -06:00
rassul
7bc79bc144 feat(lang): added kk translations 2026-01-15 21:50:00 -06:00
bgrozev
3e469019b5 test: Add a test for conference-request over XMPP. (#16838)
* test: Add a test for conference-request over XMPP.
2026-01-15 10:41:03 -06:00
bgrozev
d324935501 doc: Add a comment about JWT verification. (#16837) 2026-01-14 11:39:14 -06:00
Tobias
cd11cf6f65 ci: bump and pin Actions (#16553) 2026-01-14 10:24:41 +01:00
Matteo
5db3d529f4 lang: Update Italian translation 2026-01-13 18:40:46 -06:00
damencho
c7d2c9c204 fix(token): Keeps checks for allowlist only. 2026-01-12 15:51:55 -06:00
Harsh_w/n
9832c7a226 fix(recording): use cloned tracks for local recording; reset flags on stop (#16535)
Fixes: https://github.com/jitsi/jitsi-meet/issues/16491
2026-01-12 10:45:26 +01:00
mishraditi
12ee929499 docs: fix Contributing Guidelines link 2026-01-09 17:07:01 +01:00
vishal2005025
b9ed42613b fix(android): support String[] config overrides in mergeProps 2026-01-09 13:14:33 +02:00
Calinteodor
0d572b3bfb feat(participants-pane/native): fix participants sort (#13997)
* Make participants sort based on what web does.
2026-01-08 15:30:37 +02:00
damencho
609eab5f83 fix(chat): Clears private message notice if sender leaves.
If you are in a private conversation with someone and that participant leaves, the selection of messages switches to Everyone but the private message notice stays.
2026-01-07 12:33:38 -06:00
damencho
7bd0f479b9 fix(chat): Fixes sending messages after private message sender leaves. 2026-01-07 12:33:38 -06:00
Christoph Settgast
a634b6b2bc lang(de): Update chat permissions label as in #16807 2026-01-07 12:33:26 -06:00
Дамян Минков
0e53bd87ce fix(groupchat-polls-permissions): Adds UI setting to show the option. (#16807)
* fix(groupchat-polls-permissions): Adds UI setting to show the option.

Make sure we do not show the setting when everyone is a moderator in the room.

* squash: Change text and default value.

* squash: Defaults to false.

* squash: Set it in metadata.

* squash: Whitelist the config.
2026-01-05 16:27:32 -06:00
damencho
9e89c33796 fix(prosody): Fixes log message. 2026-01-05 13:18:36 -06:00
damencho
eaffd8b8f7 fix(breakout-rooms): Fix polls in main when breakout is enabled. 2026-01-05 13:18:36 -06:00
bgrozev
b8444d56ff fix: Fix nesting in the allure report (after allure 3 update). (#16816) 2026-01-05 13:15:47 -06:00
Christoph Settgast
42b2dd41c1 ci: fix android SDK build by removing unused preinstalled tools from github runner image (#16811)
* ci: try to fix android build by removing preinstalled stuff

The android SDK build anyway uses a docker image with react native and an android SDK inside, so remove android and haskell from the host runner

* More removals of preinstalled unused SDKs

dotnet is another 4GB, swift another 3GB
2025-12-30 14:58:22 -06:00
Christoph Settgast
229d1823fb lang: update German translation
Signed-off-by: Christoph Settgast <csett86_git@quicksands.de>
2025-12-30 12:20:45 -06:00
damencho
cd6e905b95 feat(test): Updates wdio to 9.22.0. 2025-12-29 13:40:50 -06:00
damencho
ddaf7a3180 fix(transcriptions): Drops not needed parameter.
Fixes an issue with iFrameAPI where toggleSubtitles will not do anything in case of async transcriptions turned on.
2025-12-29 12:24:47 -06:00
damencho
47aa51a58c fix(polls): Drops not needed check. 2025-12-29 12:24:36 -06:00
damencho
66f7b9de53 fix(polls): Updates polls validation. 2025-12-29 12:24:36 -06:00
emrah
bab87be9c9 fix(turnserver): no dtls 2025-12-23 14:02:53 +01:00
Jaya Allamsetty
6549d47233 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v2116.0.0+40ad2744...v2118.0.0+67fd2c84
2025-12-19 15:15:16 -05:00
damencho
2063e66b8e fix(transcription): Fixes a UI bug where you cannot start transcription 2nd time. 2025-12-17 11:55:19 -06:00
Mihaela Dumitru
4dd241712d feat(external-api) expose mute remote command and participant muted event (#16768) 2025-12-17 14:14:30 +02:00
Calinteodor
a574d5ec79 feat(conference): apply reduce ui for web (#16763)
* Change stage view and use newly reducedUImainToolbarButtons config to show different custom buttons as main toolbar buttons for when web is in reduced UI.
2025-12-17 12:17:06 +02:00
Vishal Malyan
4b2b85bd12 fix(avatar) fix memory leak in preloadImage 2025-12-16 22:52:24 +01:00
Jaya Allamsetty
77ab1ea8ed fix(large-video) Fix auto-pinning of SS in large meetings. (#16773)
Fix(large-video) Fox auto pinning of screenshare in large meetings
2025-12-16 15:09:58 -05:00
Дамян Минков
05e47ade7c feat(lobby): Handle disabling lobby. (#16770)
* feat(lobby): Handle disabling lobby.

* squash: rename field.
2025-12-16 08:21:39 -06:00
damencho
6c78ec9099 fix(recording): Fixes inviting jigasi when backend recording is enabled. 2025-12-15 16:24:55 -06:00
Hristo Terezov
4c5afc0b5e chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v2115.0.0+cc2f34c2...v2116.0.0+40ad2744
2025-12-08 18:52:57 -06:00
damencho
2e31ab9dca fix: Using recording dialog on asyncTranscription.
This partially reverts commit 02787b1 to avoid looping between request subtitles and start recording which results not closing the start recording dialog.
2025-12-08 14:00:51 -06:00
damencho
02787b1394 feat: Use recording dialog on asyncTranscription. 2025-12-05 16:10:24 -06:00
damencho
2476a06237 feat: Drops obsolete inviteJigasiOnBackendTranscribing. 2025-12-05 16:10:24 -06:00
Hristo Terezov
2d8909911e fix(pip): Fix PiP not working when enabled via overwriteConfig
Addresses multiple issues when enabling/disabling PiP dynamically:

1. External API: Replace dead config-overwrite event handler with
   interception in executeCommand. This properly manages the intersection
   observer and PiP state when pip config changes via overwriteConfig.

2. PiPVideoElement: Fix ref access pattern - access videoRef.current
   inside useEffects instead of capturing at render time. The captured
   value was null on first render, causing blur/focus listeners to never
   be set up when component mounted into a stable conference.

3. useCanvasAvatar: Return streamRef object instead of refs.current.stream
   so consumers can access .current inside their effects. The stream is
   created in an effect and wasn't available at render time.

4. Add on-mount focus check with loadedmetadata wait to handle PiP enable
   while app is in background, ensuring video source is ready before
   attempting to enter PiP mode.
2025-12-05 06:47:03 -06:00
Hristo Terezov
d06b847319 feat(pip): Add Picture-in-Picture support for Electron
Implements Picture-in-Picture functionality for the Electron wrapper to maintain video engagement when users are not actively focused on the conference window. This feature addresses the need to keep users visually connected to the conference even when multitasking.

Key features:
- Automatic PiP mode activation and deactivation based on user interaction
- Displays large video participant's stream or renders their avatar on canvas when video unavailable
- Provides audio/video mute controls via MediaSession API directly in PiP window
- Adds API events (_pip-requested) for Electron wrapper integration

Implementation includes new pip feature module with Redux architecture, canvas-based avatar rendering with custom backgrounds support, and integration with existing mute/unmute logic. Depends on jitsi-meet-electron-sdk#479 for proper user gesture handling in Electron.
2025-12-04 16:04:10 -06:00
Hristo Terezov
b517f614b3 fix(RN): Remove web files from build.
filmstrip/actions.web was imported in TileView native component.
filmstrip/actions.web was imported in config middleware.any.
2025-12-04 16:04:10 -06:00
Bastien Le Gall
10f77f1fbc fix(visitors): Fix s2sout host-unknown errors by skipping domain mapping 2025-12-04 14:26:56 -06:00
Vishal Malyan
77b89ece4a feat(api): add toolbarVisibilityChanged event to the IFrame API (#16659)
* 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

* feat(api): add toolbarVisibilityChanged event to IFrame API

* fix lint
2025-12-04 14:26:45 -06:00
Florian
129264c3c9 lang: Remove BETA label from noise suppression button (#16730)
Remove BETA label from noise suppression button as it was updated to a new stable RNNoise version
2025-12-03 08:19:33 -06:00
bgrozev
96c5a9abd1 fix: Fix reading transcription results from JVB. (#16725)
* fix: Fix reading transcription results from JVB.
2025-12-02 09:47:22 -06:00
Calinteodor
93ef2337ae dep(react-native): replace future deprecated SafeAreaView component (#16726)
* Replaced with react-native-safe-area-context component and fixed types around edges prop.
2025-12-02 16:34:57 +02:00
316 changed files with 12769 additions and 4144 deletions

View File

@@ -7,7 +7,7 @@ jobs:
name: Luacheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- 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@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f #v6.1.0
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -42,8 +42,8 @@ jobs:
matrix:
os: [macos-latest, ubuntu-latest]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f #v6.1.0
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@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f #v6.1.0
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@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f #v6.1.0
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -103,10 +103,21 @@ jobs:
android-sdk-build:
name: Build mobile SDK (Android)
runs-on: ubuntu-latest
container: reactnativecommunity/react-native-android:v15.0
container:
image: reactnativecommunity/react-native-android:v15.0
volumes:
- /usr:/host/usr
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- name: Make space in image by removing preinstalled, but unused SDKs
run: |
df -h /
rm -rf /host/usr/local/lib/android
rm -rf /host/usr/local/.ghcup
rm -rf /host/usr/share/dotnet
rm -rf /host/usr/share/swift
df -h /
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f #v6.1.0
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -126,8 +137,8 @@ jobs:
name: Build mobile SDK (iOS)
runs-on: macos-15
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f #v6.1.0
with:
node-version-file: '.nvmrc'
cache: 'npm'
@@ -149,7 +160,7 @@ jobs:
-workspace ios/jitsi-meet.xcworkspace \
-scheme JitsiMeetSDK
- name: setup-cocoapods
uses: ruby/setup-ruby@v1
uses: ruby/setup-ruby@4fc31e1c823882afd7ef55985266a526c589de90 #v1.282.0
with:
ruby-version: '3.4'
bundler-cache: true
@@ -176,8 +187,8 @@ jobs:
name: Test Debian packages build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f #v6.1.0
with:
node-version-file: '.nvmrc'
cache: 'npm'

View File

@@ -7,7 +7,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 #v10.1.0
with:
stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
stale-pr-message: 'This PR has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'

View File

@@ -264,4 +264,4 @@ react/features/sample/
- [Jitsi Handbook](https://jitsi.github.io/handbook/) - Comprehensive documentation
- [Community Forum](https://community.jitsi.org/) - Ask questions and get support
- [Architecture Guide](https://jitsi.github.io/handbook/docs/architecture) - System overview
- [Contributing Guidelines](https://jitsi.github.io/handbook/docs/dev-guide/contributing) - Detailed contribution process
- [Contributing Guidelines](https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-contributing/) - Detailed contribution process

View File

@@ -30,6 +30,9 @@ import com.facebook.react.ReactRootView;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.ArrayList;
import java.util.Arrays;
public class JitsiMeetView extends FrameLayout {
@@ -84,6 +87,10 @@ public class JitsiMeetView extends FrameLayout {
result.putInt(key, (int)bValue);
} else if (valueType.contentEquals("Bundle")) {
result.putBundle(key, mergeProps((Bundle)aValue, (Bundle)bValue));
} else if (valueType.contentEquals("String[]")) {
// Convert String[] to ArrayList<String> for React Native bridge compatibility
String[] stringArray = (String[]) bValue;
result.putStringArrayList(key, new ArrayList<>(Arrays.asList(stringArray)));
} else {
throw new RuntimeException("Unsupported type: " + valueType);
}

View File

@@ -151,6 +151,9 @@ var config = {
// Disables self-view settings in UI
// disableSelfViewSettings: false,
// Shows/hides the moderator setting for chat permissions.
// showChatPermissionsModeratorSetting: false,
// screenshotCapture : {
// Enables the screensharing capture feature.
// enabled: false,
@@ -520,9 +523,6 @@ var config = {
// // Note: Starting transcriptions from the recording dialog will still work.
// disableClosedCaptions: false,
// // Whether to invite jigasi when backend transcriptions are enabled (asyncTranscription is true in metadata).
// // By default, we invite it.
// inviteJigasiOnBackendTranscribing: true,
// },
// Misc
@@ -927,6 +927,14 @@ var config = {
// [ 'microphone', 'camera' ]
// ],
// Enable reduced UI on web.
// reducedUIEnabled: true,
// Overrides the buttons displayed in the main toolbar for reduced UI.
// When there isn't an override for a certain configuration the default jitsi-meet configuration will be used.
// The order of the buttons in the array is preserved.
// reducedUImainToolbarButtons: [ 'microphone', 'camera' ],
// Toolbar buttons which have their click/tap event exposed through the API on
// `toolbarButtonClicked`. Passing a string for the button key will
// prevent execution of the click/tap routine; passing an object with `key` and

View File

@@ -45,7 +45,7 @@ body {
.jitsi-icon {
&-default svg {
fill: white;
fill: var(--icon-default-color, white);
}
}

View File

@@ -1,5 +1,5 @@
.always-on-top-toolbox {
background-color: $newToolbarBackgroundColor;
background-color: var(--toolbox-background-color, $newToolbarBackgroundColor);
border-radius: 3px;
display: flex;
z-index: $toolbarZ;

View File

@@ -2,7 +2,7 @@
* Round badge.
*/
.badge-round {
background-color: #165ECC;
background-color: var(--toolbar-badge-background, #165ECC);
border-radius: 50%;
box-sizing: border-box;
color: #FFFFFF;
@@ -93,7 +93,7 @@
.toolbox-content-wrapper::after {
content: '';
background: $newToolbarBackgroundColor;
background: var(--toolbox-background-color, $newToolbarBackgroundColor);
padding-bottom: env(safe-area-inset-bottom, 0);
}

View File

@@ -60,7 +60,7 @@
}
#notification-participant-list {
background-color: $newToolbarBackgroundColor;
background-color: var(--toolbox-background-color, $newToolbarBackgroundColor);
border: 1px solid rgba(255, 255, 255, .4);
border-radius: 8px;
left: 0;

5
custom.d.ts vendored
View File

@@ -2,3 +2,8 @@ declare module '*.svg' {
const content: any;
export default content;
}
declare module '*.svg?raw' {
const content: string;
export default content;
}

View File

@@ -10,6 +10,7 @@ no-cli
no-loopback-peers
no-tcp-relay
no-tcp
no-dtls
listening-port=3478
tls-listening-port=5349
no-tlsv1

View File

@@ -1407,7 +1407,7 @@ PODS:
- Yoga
- react-native-performance (5.1.2):
- React-Core
- react-native-safe-area-context (5.5.2):
- react-native-safe-area-context (5.6.1):
- React-Core
- react-native-slider (4.5.6):
- DoubleConversion
@@ -2271,7 +2271,7 @@ SPEC CHECKSUMS:
react-native-orientation-locker: dbd3f6ddbe9e62389cb0807dc2af63f6c36dec36
react-native-pager-view: 11662c698c8f11d39e05891316d2a144fa00adc4
react-native-performance: 125a96c145e29918b55b45ce25cbba54f1e24dcd
react-native-safe-area-context: 0f7bf11598f9a61b7ceac8dc3f59ef98697e99e1
react-native-safe-area-context: 2243039f43d10cb1ea30ec5ac57fc6d1448413f4
react-native-slider: 1205801a8d29b28cacc14eef08cb120015cdafcb
react-native-video: eb861d67a71dfef1bbf6086a811af5f338b13781
react-native-webrtc: e8f0ce746353adc2744a2b933645e1aeb41eaa74

View File

@@ -31,6 +31,7 @@
"it": "Italiano",
"ja": "日本語",
"kab": "Taqbaylit",
"kk": "Қазақша",
"ko": "한국어",
"lt": "Lietuvių",
"lv": "Latviešu",

File diff suppressed because it is too large Load Diff

View File

@@ -109,9 +109,15 @@
}
},
"chat": {
"disabled": "Chat-Nachrichten sind deaktiviert.",
"enter": "Chat-Raum betreten",
"error": "Fehler: Ihre Nachricht wurde nicht versendet. Grund: {{error}}",
"everyone": "Alle",
"fieldPlaceHolder": "Geben Sie Ihre Nachricht hier ein",
"fileAccessibleTitle": "{{user}} hat eine Datei hochgeladen",
"fileAccessibleTitleMe": "Ich habe eine Datei hochgeladen",
"fileDeleted": "Eine Datei wurde gelöscht",
"guestsChatIndicator": "(Gast)",
"lobbyChatMessageTo": "Lobby-Nachricht an {{recipient}}",
"message": "Nachricht",
"messageAccessibleTitle": "{{user}} sagt:",
@@ -120,11 +126,20 @@
"messagebox": "Nachricht eingeben",
"newMessages": "Neue Nachrichten",
"nickname": {
"featureChat": "Chat",
"featureClosedCaptions": "Untertitel",
"featureFileSharing": "Dateien",
"featurePolls": "Umfragen",
"popover": "Wähle einen Alias",
"title": "Geben Sie einen Alias zum Chatten ein",
"titleWith1Features": "Geben Sie einen Alias ein, um {{feature1}} zu nutzen",
"titleWith2Features": "Geben Sie einen Alias ein, um {{feature1}} und {{feature2}} zu nutzen",
"titleWith3Features": "Geben Sie einen Alias ein, um {{feature1}}, {{feature2}} und {{feature3}} zu nutzen",
"titleWith4Features": "Geben Sie einen Alias ein, um {{feature1}}, {{feature2}}, {{feature3}} und {{feature4}} zu nutzen",
"titleWithCC": "Geben Sie einen Alias zum Chatten und für Untertitel ein",
"titleWithPolls": "Geben Sie einen Alias zum Chatten und für Umfragen ein",
"titleWithPollsAndCC": "Geben Sie einen Alias zum Chatten, für Umfragen und Untertitel ein"
"titleWithPollsAndCC": "Geben Sie einen Alias zum Chatten, für Umfragen und Untertitel ein",
"titleWithPollsAndCCAndFileSharing": "Geben Sie einen Alias zum Chatten, für Umfragen, Untertitel und Dateien ein"
},
"noMessagesMessage": "Es gibt noch keine Nachricht in dieser Konferenz. Starten Sie hier eine Unterhaltung!",
"privateNotice": "Private Nachricht an {{recipient}}",
@@ -132,14 +147,16 @@
"smileysPanel": "Emoji-Auswahl",
"systemDisplayName": "System",
"tabs": {
"chat": "Chatten",
"chat": "Chat",
"closedCaptions": "Untertitel",
"fileSharing": "Dateien",
"polls": "Umfragen"
},
"title": "Chatten",
"titleWithCC": "Chatten und Untertitel",
"titleWithPolls": "Chatten und Umfragen",
"titleWithPollsAndCC": "Chatten, Umfragen und Untertitel",
"title": "Chat",
"titleWithCC": "Untertitel",
"titleWithFeatures": "Chat und",
"titleWithFileSharing": "Dateien",
"titleWithPolls": "Umfragen",
"you": "Sie"
},
"chromeExtensionBanner": {
@@ -274,7 +291,6 @@
"Submit": "OK",
"Understand": "Verstanden, Stummschaltung beibehalten",
"UnderstandAndUnmute": "Verstanden, bitte Stummschaltung aufheben",
"WaitForHostMsg": "Die Konferenz wurde noch nicht gestartet. Falls Sie die Konferenz leiten, authentifizieren Sie sich bitte. Warten Sie andernfalls, bis die Konferenz gestartet wird.",
"WaitForHostNoAuthMsg": "Die Konferenz wurde noch nicht gestartet. Bitte warten Sie, bis die Konferenz gestartet wird.",
"WaitingForHostButton": "Auf Moderation warten",
"WaitingForHostTitle": "Warten auf den Beginn der Konferenz …",
@@ -296,6 +312,12 @@
"alreadySharedVideoTitle": "Nur ein geteiltes Video gleichzeitig",
"applicationWindow": "Anwendungsfenster",
"authenticationRequired": "Authentifizierung benötigt",
"cameraCaptureDialog": {
"description": "Ein Bild mit Ihrer Kamera aufnehmen und senden",
"ok": "Kamera starten",
"reject": "Jetzt nicht",
"title": "Ein Bild aufnehmen"
},
"cameraConstraintFailedError": "Ihre Kamera erfüllt die notwendigen Anforderungen nicht.",
"cameraNotFoundError": "Kamera nicht gefunden.",
"cameraNotSendingData": "Die Kamera ist nicht verfügbar. Bitte prüfen, ob eine andere Applikation die Kamera verwendet, eine andere Kamera vom Einstellungs-Menü auswählen oder die Applikation neu laden.",
@@ -371,22 +393,34 @@
"micTimeoutError": "Audioquelle konnte nicht gestartet werden. Zeitüberschreitung",
"micUnknownError": "Das Mikrofon kann aus einem unbekannten Grund nicht verwendet werden.",
"moderationAudioLabel": "Erlaube Anwesenden die Stummschaltung für sich aufzuheben",
"moderationDesktopLabel": "Erlaube Anwesenden ihren Bildschirm freizugeben",
"moderationVideoLabel": "Erlaube Anwesenden ihre Kamera einzuschalten",
"muteEveryoneDialog": "Wollen Sie wirklich alle stummschalten? Sie können deren Stummschaltung nicht mehr beenden, aber sie können ihre Stummschaltung jederzeit selbst beenden.",
"muteEveryoneDialogModerationOn": "Die Anwesenden können eine Anfrage zum Sprechen jederzeit senden.",
"muteEveryoneElseDialog": "Einmal stummgeschaltet, können Sie deren Stummschaltung nicht mehr beenden, aber sie können ihre Stummschaltung jederzeit selbst beenden.",
"muteEveryoneElseTitle": "Alle außer {{whom}} stummschalten?",
"muteEveryoneElsesDesktopDialog": "Sobald die Bildschirmfreigaben beendet sind, können Sie diese nicht mehr starten, aber die anderen können sie jederzeit wieder starten.",
"muteEveryoneElsesDesktopTitle": "Alle Bildschirmfreigaben außer {{whom}} beenden?",
"muteEveryoneElsesVideoDialog": "Sobald die Kamera für alle anderen Personen deaktiviert ist, können Sie diese nicht wieder für alle einschalten, die anderen Personen können ihre Kamera aber jederzeit wieder einschalten.",
"muteEveryoneElsesVideoTitle": "Die Kamera von allen außer {{whom}} ausschalten?",
"muteEveryoneSelf": "sich selbst",
"muteEveryoneStartMuted": "Alle beginnen von jetzt an stummgeschaltet",
"muteEveryoneTitle": "Alle stummschalten?",
"muteEveryonesDesktopDialog": "Die Anwesenden können ihren Bildschirm jederzeit freigeben.",
"muteEveryonesDesktopDialogModerationOn": "Die Anwesenden können jederzeit eine Anfrage zur Bildschirmfreigabe senden.",
"muteEveryonesDesktopTitle": "Alle Bildschirmfreigaben beenden?",
"muteEveryonesVideoDialog": "Sind Sie sicher, dass Sie die Kamera von allen Personen deaktivieren möchten? Sie können dies nicht wieder rückgängig machen, jede Personen kann ihre Kamera aber jederzeit wieder einschalten.",
"muteEveryonesVideoDialogModerationOn": "Die Anwesenden können jederzeit eine Anfrage senden, um ihre Kamera einzuschalten.",
"muteEveryonesVideoDialogOk": "deaktivieren",
"muteEveryonesVideoTitle": "Die Kamera von allen anderen ausschalten?",
"muteParticipantBody": "Sie können die Stummschaltung anderer Personen nicht aufheben, aber eine Person kann ihre eigene Stummschaltung jederzeit beenden.",
"muteParticipantButton": "Stummschalten",
"muteParticipantsDesktopBody": "Sie können Bildschirmfreigaben von anderen nicht starten, sie können dies aber jederzeit.",
"muteParticipantsDesktopBodyModerationOn": "Weder Sie noch andere Anwesende können Bildschirmfreigaben starten.",
"muteParticipantsDesktopButton": "Bildschirmfreigabe beenden",
"muteParticipantsDesktopDialog": "Sind Sie sicher, dass Sie die Bildschirmfreigabe von dieser Person beenden möchten? Sie können diese nicht mehr starten, die Person aber jederzeit.",
"muteParticipantsDesktopDialogModerationOn": "Sind Sie sicher, dass Sie die Bildschirmfreigabe von dieser Person beenden möchten? Weder Sie noch die Person kann die Bildschirmfreigabe wieder starten.",
"muteParticipantsDesktopTitle": "Bildschirmfreigabe von dieser Person beenden?",
"muteParticipantsVideoBody": "Sie können die Kamera nicht wieder einschalten, die Person kann ihre Kamera aber jederzeit wieder einschalten.",
"muteParticipantsVideoBodyModerationOn": "Sie können die Kamera nicht wieder aktivieren und die Person selbst auch nicht.",
"muteParticipantsVideoButton": "Kamera ausschalten",
@@ -499,6 +533,7 @@
"tokenAuthFailedWithReasons": "Teilnahme an der Konferenz fehlgeschlagen. Möglicher Grund: {{reason}}",
"tokenAuthUnsupported": "Token-Authentifizierung wird nicht unterstützt.",
"transcribing": "Wird transkribiert",
"unauthenticatedAccessDisabled": "Zur Teilnahme an dieser Konferenz müssen Sie sich anmelden.",
"unlockRoom": "Konferenz$t(lockRoomPassword) entfernen",
"user": "Anmeldename",
"userIdentifier": "Benutzername",
@@ -539,6 +574,25 @@
"veryBad": "Sehr schlecht",
"veryGood": "Sehr gut"
},
"fileSharing": {
"downloadFailedDescription": "Bitte nochmals versuchen.",
"downloadFailedTitle": "Download fehlgeschlagen",
"downloadFile": "Download",
"downloadStarted": "Download gestartet",
"dragAndDrop": "Dateien hier oder irgendwo auf dem Bildschirm loslassen",
"fileAlreadyUploaded": "Datei wurde schon zur Konferenz hochgeladen.",
"fileRemovedByOther": "Ihre Datei '{{ fileName }}' wurde entfernt",
"fileTooLargeDescription": "Bitte stellen Sie sicher, dass Ihre Datei nicht die Maximalgröße von {{ maxFileSize }} überschreitet.",
"fileTooLargeTitle": "Die ausgewählte Datei ist zu groß",
"fileUploadProgress": "Datei wird hochgeladen",
"fileUploadedSuccessfully": "Datei erfolgreich hochgeladen",
"newFileNotification": "{{ participantName }} hat Datei '{{ fileName }}' hochgeladen",
"removeFile": "Entfernen",
"removeFileSuccess": "Datei erfolgreich entfernt",
"uploadFailedDescription": "Bitte versuchen Sie es erneut.",
"uploadFailedTitle": "Dateiupload fehlgeschlagen",
"uploadFile": "Datei hochladen"
},
"filmstrip": {
"accessibilityLabel": {
"heading": "Videominiaturen"
@@ -707,7 +761,8 @@
"notificationTitle": "Lobby",
"passwordJoinButton": "Beitreten",
"title": "Lobby",
"toggleLabel": "Lobby aktivieren"
"toggleLabel": "Lobby aktivieren",
"waitForModerator": "Die Konferenz wurde noch nicht gestartet, da noch keine Moderation anwesend ist. Wenn Sie zur Moderation gehören, melden Sie sich bitte an, ansonsten warten Sie bitte."
},
"localRecording": {
"clientState": {
@@ -750,8 +805,9 @@
"me": "ich",
"notify": {
"OldElectronAPPTitle": "Sicherheitslücke!",
"allowAll": "Alles einschalten",
"allowAudio": "Mikrofon einschalten",
"allowBoth": "Beides",
"allowDesktop": "Bildschirmfreigabe einschalten",
"allowVideo": "Kamera einschalten",
"allowedUnmute": "Sie können die Stummschaltung aufheben, Ihre Kamera einschalten oder Ihren Bildschirm teilen.",
"audioUnmuteBlockedDescription": "Díe Stummschaltung kann aus Überlastungsschutzgründen temporär nicht aufgehoben werden.",
@@ -765,6 +821,7 @@
"dataChannelClosedDescription": "Die Steuerungsverbindung (Bridge Channel) wurde unterbrochen, daher ist die Videoqulität auf die schlechteste Stufe limitiert.",
"dataChannelClosedDescriptionWithAudio": "Die Steuerungsverbindung (Bridge Channel) wurde unterbrochen, daher können Video- und Tonprobleme auftreten.",
"dataChannelClosedWithAudio": "Ton- und Videoqualität können beeinträchtigt sein",
"desktopMutedRemotelyTitle": "Ihre Bildschirmfreigabe wurde von {{participantDisplayName}} gestoppt",
"disabledIframe": "Die Einbettung ist nur für Demo-Zwecke vorgesehen. Diese Konferenz wird in {{timeout}} Minuten beendet.",
"disabledIframeSecondaryNative": "Die Einbettung von {{domain}} ist nur für Demo-Zwecke vorgesehen. Diese Konferenz wird in {{timeout}} Minuten beendet.",
"disabledIframeSecondaryWeb": "Die Einbettung von {{domain}} ist nur für Demo-Zwecke vorgesehen. Diese Konferenz wird in {{timeout}} Minuten beendet. Bitte nutzen Sie <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a> für produktive Zwecke!",
@@ -822,6 +879,7 @@
"oldElectronClientDescription1": "Sie scheinen eine alte Version des Jitsi-Meet-Clients zu nutzen. Diese hat bekannte Schwachstellen. Bitte aktualisieren Sie auf unsere ",
"oldElectronClientDescription2": "aktuelle Version",
"oldElectronClientDescription3": "!",
"openChat": "Chat öffnen",
"participantWantsToJoin": "Möchte an der Konferenz teilnehmen",
"participantsWantToJoin": "Möchten an der Konferenz teilnehmen",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) von einer anderen Person entfernt",
@@ -845,6 +903,7 @@
"suggestRecordingDescription": "Möchten Sie eine Aufzeichnung starten?",
"suggestRecordingTitle": "Konferenz aufzeichnen",
"unmute": "Stummschaltung aufheben",
"unmuteScreen": "Bildschirmfreigabe starten",
"unmuteVideo": "Kamera einschalten",
"videoMutedRemotelyDescription": "Sie können sie jederzeit wieder einschalten.",
"videoMutedRemotelyTitle": "Ihre Kamera wurde von {{participantDisplayName}} ausgeschaltet!",
@@ -864,11 +923,14 @@
"admit": "Zulassen",
"admitAll": "Alle zulassen",
"allow": "Anwesenden erlauben:",
"allowDesktop": "Bildschirm freizugeben",
"allowVideo": "Kamera einschalten",
"askDesktop": "Anfragen, Bildschirm freizugeben",
"askUnmute": "Anfragen, Stummschaltung aufzuheben",
"audioModeration": "Für sich selbst die Stummschaltung aufzuheben",
"blockEveryoneMicCamera": "Kamera und Mikrofon von allen sperren",
"breakoutRooms": "Breakout-Räume",
"desktopModeration": "Bildschirmfreigabe",
"goLive": "Live gehen",
"invite": "Person einladen",
"lowerAllHands": "Alle Hände senken",
@@ -880,6 +942,8 @@
"muteAll": "Alle stummschalten",
"muteEveryoneElse": "Alle anderen stummschalten",
"reject": "Ablehnen",
"stopDesktop": "Bildschirmfreigabe beenden",
"stopEveryonesDesktop": "Alle Bildschirmfreigaben beenden",
"stopEveryonesVideo": "Alle Kameras ausschalten",
"stopVideo": "Kamera ausschalten",
"unblockEveryoneMicCamera": "Kamera und Mikrofon von allen entsperren",
@@ -889,9 +953,11 @@
"headings": {
"lobby": "Lobby ({{count}})",
"participantsList": "Anwesende ({{count}})",
"viewerRequests": "Zuschaueranfragen {{count}}",
"visitorInQueue": " (Wartende Gäste {{count}})",
"visitorRequests": " (Anfragen {{count}})",
"visitors": "Gäste ({{count}})",
"visitorsList": "Zuschauer ({{count}})",
"waitingLobby": "In der Lobby ({{count}})"
},
"search": "Suche Anwesende",
@@ -912,6 +978,9 @@
"by": "Von {{ name }}",
"closeButton": "Umfrage schließen",
"create": {
"accessibilityLabel": {
"send": "Umfrage erstellen"
},
"addOption": "Antwort hinzufügen",
"answerPlaceholder": "Antwort {{index}}",
"cancel": "Abbrechen",
@@ -920,8 +989,7 @@
"pollQuestion": "Frage",
"questionPlaceholder": "Eine Frage stellen",
"removeOption": "Antwort entfernen",
"save": "Erstellen",
"send": "Senden"
"save": "Erstellen"
},
"errors": {
"notUniqueOption": "Optionen müssen einzigartig sein"
@@ -1122,7 +1190,7 @@
"signedIn": "Momentan wird auf Kalendertermine von {{email}} zugegriffen. Klicken Sie auf die folgende Schaltfläche „Trennen“, um den Zugriff auf die Kalendertermine zu stoppen.",
"title": "Kalender"
},
"chatWithPermissions": "Chat mit Freigaben",
"chatWithPermissions": "Chat nur für Moderation erlauben",
"desktopShareFramerate": "Framerate für Bildschirmfreigabe",
"desktopShareHighFpsWarning": "Eine höhere Framerate könnte sich auf Ihre Datenrate auswirken. Sie müssen die Bildschirmfreigabe neustarten, damit die Einstellung übernommen wird.",
"desktopShareWarning": "Sie müssen die Bildschirmfreigabe neustarten, damit die Einstellung übernommen wird.",
@@ -1328,6 +1396,20 @@
"videounmute": "Kamera einschalten"
},
"addPeople": "Personen zur Konferenz hinzufügen",
"advancedAudioSettings": {
"aec": {
"label": "Echounterdrückung"
},
"agc": {
"label": "Automatische Mikrofonlautstärke"
},
"ns": {
"label": "Rauschunterdrückung"
},
"stereo": {
"label": "Stereo"
}
},
"audioOnlyOff": "Modus „Nur Audio“ deaktivieren",
"audioOnlyOn": "Modus „Nur Audio“ aktivieren",
"audioRoute": "Audiogerät auswählen",
@@ -1355,6 +1437,7 @@
"exitFullScreen": "Vollbildmodus verlassen",
"exitTileView": "Kachelansicht ausschalten",
"feedback": "Feedback hinterlassen",
"fileSharing": "Dateien",
"giphy": "GIPHY ein-/ausschalten",
"hangup": "Konferenz verlassen",
"help": "Hilfe",
@@ -1390,6 +1473,7 @@
"openReactionsMenu": "Interaktionsmenü öffnen",
"participants": "Anwesende",
"pip": "Bild-in-Bild-Modus einschalten",
"polls": "Umfragen",
"privateMessage": "Private Nachricht senden",
"profile": "Profil bearbeiten",
"raiseHand": "Hand heben",
@@ -1399,6 +1483,7 @@
"reactionHeart": "Herz senden",
"reactionLaugh": "Lachen senden",
"reactionLike": "Daumen hoch senden",
"reactionLove": "Liebe senden",
"reactionSilence": "Stille senden",
"reactionSurprised": "Überrascht senden",
"reactions": "Interaktionen",
@@ -1484,7 +1569,8 @@
"connectionInfo": "Verbindungsinformationen",
"demote": "Zu Gästen verschieben",
"domute": "Stummschalten",
"domuteDesktopOfOthers": "Bildschirm freigeben für alle beenden",
"domuteDesktop": "Bildschirmfreigabe beenden",
"domuteDesktopOfOthers": "Bildschirmfreigabe für alle beenden",
"domuteOthers": "Alle anderen stummschalten",
"domuteVideo": "Kamera ausschalten",
"domuteVideoOfOthers": "Alle anderen Kameras auschalten",
@@ -1549,6 +1635,8 @@
"noMainParticipantsTitle": "Diese Konferenz wurde noch nicht gestartet.",
"noVisitorLobby": "Sie können nicht teilnehmen, solange die Lobby für diese Konferenz aktiviert ist.",
"notAllowedPromotion": "Eine Person muss Ihre Anfrage erst erlauben.",
"requestToJoin": "Hand gehoben",
"requestToJoinDescription": "Ihre Anfrage wurde an die Moderation gesendet, bitte warten Sie.",
"title": "Sie sind Gast in der Konferenz"
},
"waitingMessage": "Sie werden der Konferenz beitreten, sobald sie gestartet ist!"

View File

@@ -126,8 +126,16 @@
"messagebox": "Digita un messaggio",
"newMessages": "Nuovi messaggi",
"nickname": {
"featureChat": "la chat",
"featureClosedCaptions": "i sottotitoli",
"featureFileSharing": "la condivisione file",
"featurePolls": "i sondaggi",
"popover": "Scegli un nickname",
"title": "Inserisci un nickname per usare la chat",
"titleWith1Features": "Inserisci un nickname per usare {{feature1}}",
"titleWith2Features": "Inserisci un nickname per usare {{feature1}} e {{feature2}}",
"titleWith3Features": "Inserisci un nickname per usare {{feature1}}, {{feature2}} e {{feature3}}",
"titleWith4Features": "Inserisci un nickname per usare {{feature1}}, {{feature2}}, {{feature3}} e {{feature4}}",
"titleWithCC": "Inserisci un nickname per usare la chat e i sottotitoli",
"titleWithPolls": "Inserisci un nickname per usare la chat e i sondaggi",
"titleWithPollsAndCC": "Inserisci un nickname per usare la chat, i sondaggi e i sottotitoli",
@@ -159,7 +167,7 @@
"installExtensionText": "Installa un'estensione per integrare Google Calendar e Office 365"
},
"closedCaptionsTab": {
"emptyState": "Il contenuto dei sottotitoli sarà disponibile una volta che l'organizzatore lo attiverà",
"emptyState": "Il contenuto dei sottotitoli sarà disponibile una volta che un moderatore lo attiverà",
"startClosedCaptionsButton": "Attiva sottotitoli"
},
"connectingOverlay": {
@@ -283,9 +291,9 @@
"Submit": "Invia",
"Understand": "Accetto, mantieni microfono e videocamera disattivati per ora",
"UnderstandAndUnmute": "Accetto, riattiva microfono e videocamera",
"WaitForHostNoAuthMsg": "La riunione non è ancora iniziata perché nessun organizzatore si è ancora collegato. Si prega di attendere.",
"WaitingForHostButton": "Attendi l'organizzatore",
"WaitingForHostTitle": "In attesa dell'organizzatore…",
"WaitForHostNoAuthMsg": "La riunione non è ancora iniziata perché nessun moderatore si è ancora collegato. Si prega di attendere.",
"WaitingForHostButton": "Attendi un moderatore",
"WaitingForHostTitle": "In attesa di un moderatore…",
"Yes": "Sì",
"accessibilityLabel": {
"Cancel": "Annulla (chiudi dialogo)",
@@ -348,8 +356,8 @@
"error": "Errore",
"errorRoomCreationRestriction": "Hai provato ad accedere alla riunione troppo presto, torna tra un po'.",
"gracefulShutdown": "Il nostro servizio è al momento inattivo per manutenzione. Si prega di riprovare più tardi.",
"grantModeratorDialog": "Vuoi rendere relatore questo partecipante?",
"grantModeratorTitle": "Fai diventare relatore",
"grantModeratorDialog": "Desideri di concedere i permessi da moderatore a {{participantName}}?",
"grantModeratorTitle": "Concedi permessi da moderatore",
"hide": "Nascondi",
"hideShareAudioHelper": "Non mostrare più questa finestra",
"incorrectPassword": "Nome utente o password errati",
@@ -727,12 +735,12 @@
"emailField": "Inserisci il tuo indirizzo email",
"enableDialogPasswordField": "Imposta password (opzionale)",
"enableDialogSubmit": "Attiva",
"enableDialogText": "La sala d'attesa ti permette di proteggere la riunione concedendo l'accesso solo alle persone autorizzate da un organizzatore.",
"enableDialogText": "La sala d'attesa ti permette di proteggere la riunione concedendo l'accesso solo alle persone autorizzate da un moderatore.",
"enterPasswordButton": "Inserisci password riunione",
"enterPasswordTitle": "Inserisci la password per entrare nella riunione",
"errorMissingPassword": "Inserisci la password della riunione",
"invalidPassword": "Password errata",
"joinRejectedMessage": "La tua richiesta d'accesso è stata respinta da un organizzatore.",
"joinRejectedMessage": "La tua richiesta d'accesso è stata respinta da un moderatore.",
"joinRejectedTitle": "Richiesta d'accesso respinta.",
"joinTitle": "Entra nella riunione",
"joinWithPasswordMessage": "Tentativo di accesso con password in corso, si prega di attendere…",
@@ -754,7 +762,7 @@
"passwordJoinButton": "Entra",
"title": "Sala d'attesa",
"toggleLabel": "Attiva sala d'attesa",
"waitForModerator": "La riunione non è ancora iniziata, perché non è arrivato alcun organizzatore. Se vuoi diventarlo autenticati, altrimenti attendi."
"waitForModerator": "La riunione non è ancora iniziata, perché non è arrivato alcun moderatore. Se vuoi diventarlo autenticati, altrimenti si prega di attendere."
},
"localRecording": {
"clientState": {
@@ -772,11 +780,11 @@
"me": "Io",
"messages": {
"engaged": "Registrazione avviata.",
"finished": "La registrazione della sessione {{token}} è terminata. Invia il file della registrazione all'organizzatore.",
"finishedModerator": "La registrazione della sessione {{token}} è terminata. La registrazione della traccia è stata salvata. Chiedi ai partecipanti di inviare le loro registrazioni.",
"notModerator": "Non sei un relatore. Non puoi avviare o interrompere la registrazione."
"finished": "La registrazione della sessione {{token}} è terminata. Invia il file della registrazione al moderatore.",
"finishedModerator": "La registrazione della sessione {{token}} è terminata. La registrazione della traccia locale è stata salvata. Si prega di chiedere ai partecipanti di inviare le loro registrazioni.",
"notModerator": "Non sei un moderatore. Non puoi avviare o interrompere la registrazione."
},
"moderator": "Relatore",
"moderator": "Moderatore",
"no": "No",
"participant": "Partecipante",
"participantStats": "Statistiche partecipante",
@@ -824,7 +832,7 @@
"focusFail": "{{component}} non disponibile - nuovo tentativo tra {{ms}} sec",
"gifsMenu": "GIPHY",
"groupTitle": "Notifiche",
"hostAskedUnmute": "Il relatore ti chiede di intervenire.",
"hostAskedUnmute": "Il moderatore ti chiede di intervenire.",
"invalidTenant": "Nome non valido",
"invalidTenantHyphenDescription": "Il nome che hai scelto non è valido (inizia o finisce con '-').",
"invalidTenantLengthDescription": "Il nome che hai scelto è troppo lungo.",
@@ -846,17 +854,17 @@
"localRecordingStopped": "{{name}} ha smesso di registrare.",
"me": "Io",
"moderationInEffectCSDescription": "Alza la mano, se vuoi condividere lo schermo.",
"moderationInEffectCSTitle": "La condivisione schermo è stata bloccata dal relatore",
"moderationInEffectCSTitle": "La condivisione schermo è stata bloccata dal moderatore",
"moderationInEffectDescription": "Alza la mano, se vuoi prendere la parola.",
"moderationInEffectTitle": "Il tuo microfono è stato spento dal relatore",
"moderationInEffectTitle": "Il tuo microfono è stato spento dal moderatore",
"moderationInEffectVideoDescription": "Alza la mano, se vuoi avviare la tua videocamera.",
"moderationInEffectVideoTitle": "La tua videocamera è stata spenta dal relatore",
"moderationRequestFromModerator": "Il relatore vorrebbe che tu accendessi il microfono",
"moderationInEffectVideoTitle": "La tua videocamera è stata spenta dal moderatore",
"moderationRequestFromModerator": "L'organizzatore ti chiede di accendere il microfono",
"moderationRequestFromParticipant": "Vuole parlare",
"moderationStartedTitle": "Moderazione in corso",
"moderationStoppedTitle": "Moderazione interrotta",
"moderationToggleDescription": "da {{participantDisplayName}}",
"moderator": "Ora sei un relatore!",
"moderator": "Ora sei un moderatore!",
"muted": "Hai iniziato la conversazione con il microfono disattivato.",
"mutedRemotelyDescription": "Puoi sempre attivare il microfono quando vuoi parlare. Spegni il microfono quando hai finito, per evitare rumori di fondo nella riunione.",
"mutedRemotelyTitle": "{{participantDisplayName}} ti ha spento il microfono",
@@ -1162,7 +1170,7 @@
},
"security": {
"about": "Puoi aggiungere una $t(lockRoomPassword) alla riunione. I partecipanti dovranno inserire la $t(lockRoomPassword) per accedere alla riunione.",
"aboutReadOnly": "Gli organizzatori possono aggiungere una $t(lockRoomPassword) alla riunione. I partecipanti dovranno inserire la $t(lockRoomPassword) per accedere alla riunione.",
"aboutReadOnly": "I moderatori possono aggiungere una $t(lockRoomPassword) alla riunione. I partecipanti dovranno inserire la $t(lockRoomPassword) per accedere alla riunione.",
"insecureRoomNameWarningNative": "Il nome della riunione è troppo semplice. Partecipanti non desiderati potrebbero entrare nella riunione. {{recommendAction}} Per saperne di più sulla sicurezza della tua riunione ",
"insecureRoomNameWarningWeb": "Il nome della riunione è troppo semplice. Partecipanti non desiderati potrebbero entrare nella riunione. {{recommendAction}} Per saperne di più sulla sicurezza della tua riunione <a href=\"{{securityUrl}}\" rel=\"security\" target=\"_blank\">clicca qui</a>.",
"title": "Impostazioni di sicurezza",
@@ -1182,7 +1190,7 @@
"signedIn": "Accesso agli eventi del calendario per {{email}} in corso. Clicca sul pulsante Disconnetti per interrompere laccesso agli eventi sul calendario.",
"title": "Calendario"
},
"chatWithPermissions": "La chat richiede il permesso",
"chatWithPermissions": "Disattiva la chat per i partecipanti",
"desktopShareFramerate": "Frequenza di aggiornamento condivisone schermo",
"desktopShareHighFpsWarning": "Una frequenza di aggiornamento della condivisione dello schermo più alta può influire sulla tua connessione. Devi riavviare la condivisione schermo, per applicare le modifiche.",
"desktopShareWarning": "Devi riavviare la condivisione schermo, per applicare le modifiche.",
@@ -1195,8 +1203,8 @@
"loggedIn": "Connesso come {{name}}",
"maxStageParticipants": "Numero massimo di partecipanti che possono essere messi in evidenza nella schermata principale",
"microphones": "Microfoni",
"moderator": "Relatore",
"moderatorOptions": "Opzioni relatore",
"moderator": "Moderatore",
"moderatorOptions": "Opzioni moderatore",
"more": "Generali",
"name": "Nome",
"noDevice": "Nessuno",
@@ -1325,7 +1333,7 @@
"feedback": "Lascia un feedback",
"fullScreen": "Attiva modalità a schermo intero",
"giphy": "Mostra menu GIPHY",
"grantModerator": "Concedi permessi da relatore",
"grantModerator": "Concedi permessi da moderatore",
"hangup": "Lascia la riunione",
"heading": "Barra degli strumenti",
"help": "Aiuto",
@@ -1350,7 +1358,7 @@
"muteEveryoneElsesVideoStream": "Spegni la videocamera a tutti gli altri",
"muteEveryonesVideoStream": "Spegni la videocamera a tutti",
"muteGUMPending": "Connessione del microfono in corso",
"noiseSuppression": "Cancellazione del rumore (BETA)",
"noiseSuppression": "Cancellazione del rumore",
"openChat": "Apri chat",
"participants": "Apri pannello partecipanti. {{participantsCount}} partecipanti",
"pip": "Attiva modalità Picture-in-Picture",
@@ -1415,20 +1423,21 @@
"closeParticipantsPane": "Chiudi pannello partecipanti",
"closeReactionsMenu": "Chiudi menu reazioni",
"closedCaptions": "Sottotitoli",
"disableNoiseSuppression": "Disattiva cancellazione del rumore (BETA)",
"disableNoiseSuppression": "Disattiva cancellazione del rumore",
"disableReactionSounds": "Puoi disattivare i suoni delle reazioni in questa riunione",
"documentClose": "Chiudi documento condiviso",
"documentOpen": "Apri documento condiviso",
"download": "Scarica le nostre app",
"e2ee": "Crittografia End-to-End",
"embedMeeting": "Incorpora riunione",
"enableNoiseSuppression": "Attiva cancellazione del rumore (BETA)",
"enableNoiseSuppression": "Attiva cancellazione del rumore",
"endConference": "Termina la riunione per tutti",
"enterFullScreen": "Mostra a schermo intero",
"enterTileView": "Mostra vista a mosaico",
"exitFullScreen": "Esci dalla modalità a schermo intero",
"exitTileView": "Esci dalla vista a mosaico",
"feedback": "Lascia un feedback",
"fileSharing": "Condivisione file",
"giphy": "Menu GIPHY",
"hangup": "Lascia la riunione",
"help": "Aiuto",
@@ -1457,13 +1466,14 @@
"noAudioSignalDialInDesc": "Puoi anche chiamare usando:",
"noAudioSignalDialInLinkDesc": "Numeri di telefono",
"noAudioSignalTitle": "Nessun suono rilevato dal tuo microfono!",
"noiseSuppression": "Cancellazione del rumore (BETA)",
"noiseSuppression": "Cancellazione del rumore",
"noisyAudioInputDesc": "Sembra che il tuo microfono faccia rumore, si prega di spegnerlo o cambiarlo.",
"noisyAudioInputTitle": "Il tuo microfono sembra fare rumore!",
"openChat": "Apri chat",
"openReactionsMenu": "Apri il menu reazioni",
"participants": "Partecipanti",
"pip": "Abilita modalità Picture-in-Picture",
"polls": "Sondaggi",
"privateMessage": "Invia un messaggio privato",
"profile": "Modifica profilo",
"raiseHand": "Alza la mano",
@@ -1565,11 +1575,11 @@
"domuteVideo": "Disattiva videocamera",
"domuteVideoOfOthers": "Disattiva videocamera a tutti gli altri",
"flip": "Specchia",
"grantModerator": "Concedi permessi da relatore",
"grantModerator": "Concedi permessi da moderatore",
"hideSelfView": "Nascondi la tua immagine",
"kick": "Espelli",
"mirrorVideo": "Specchia il tuo video",
"moderator": "Relatore",
"moderator": "Moderatore",
"mute": "Il partecipante ha il microfono spento",
"muted": "Microfono spento",
"pinToStage": "Metti in primo piano",
@@ -1616,7 +1626,7 @@
"description": "Adesso sei uno spettatore in questa riunione.",
"raiseHand": "Alza la mano",
"title": "Ingresso nella riunione in corso",
"wishToSpeak": "Se vuoi parlare, si prega di alzare la mano sotto e aspettare l'autorizzazione del relatore."
"wishToSpeak": "Per parlare si prega di alzare la mano sotto e aspettare l'autorizzazione del moderatore."
},
"labelTooltip": "Numero di spettatori: {{count}}",
"notification": {
@@ -1626,7 +1636,7 @@
"noVisitorLobby": "Non puoi partecipare se la sala d'attesa è attiva per la riunione.",
"notAllowedPromotion": "Un partecipante deve autorizzare la tua richiesta prima.",
"requestToJoin": "Mano alzata",
"requestToJoinDescription": "La tua richiesta è stata inviata ai relatori. Tieni duro!",
"requestToJoinDescription": "La tua richiesta è stata inviata ai moderatori. Tieni duro!",
"title": "Sei uno spettatore nella riunione"
},
"waitingMessage": "Ti unirai alla riunione quando inizierà!"
@@ -1667,7 +1677,7 @@
"mobileDownLoadLinkAndroid": "Scarica applicazione per Android",
"mobileDownLoadLinkFDroid": "Scarica applicazione da F-Droid",
"mobileDownLoadLinkIos": "Scarica applicazione per iOS",
"moderatedMessage": "O <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">prepara un URL di riunione</a> in anticipo, quando sei l'unico organizzatore.",
"moderatedMessage": "O <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">prepara un URL di riunione</a> in anticipo, quando sei l'unico moderatore.",
"privacy": "Privacy",
"recentList": "Recenti",
"recentListDelete": "Cancella",

1703
lang/main-kk.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -114,6 +114,10 @@
"error": "Erro: a sua mensagem não foi enviada. Motivo: {{error}}",
"everyone": "Todos",
"fieldPlaceHolder": "Aa",
"fileAccessibleTitle": "{{user}} carregou um ficheiro",
"fileAccessibleTitleMe": "carreguei um arquivo",
"fileDeleted": "Um ficheiro foi eliminado",
"guestsChatIndicator": "(convidado)",
"lobbyChatMessageTo": "Mensagem de chat na sala de espera para {{recipient}}",
"message": "Mensagem",
"messageAccessibleTitle": "{{user}} disse:",
@@ -122,8 +126,16 @@
"messagebox": "Escreva uma mensagem",
"newMessages": "Novas mensagens",
"nickname": {
"featureChat": "chat",
"featureClosedCaptions": "legendas ocultas",
"featureFileSharing": "partilha de ficheiros",
"featurePolls": "sondagens",
"popover": "Escolha um apelido",
"title": "Introduza um apelido para usar o chat",
"titleWith1Features": "Insira um apelido para usar {{feature1}}",
"titleWith2Features": "Insira um apelido para usar {{feature1}} e {{feature2}}",
"titleWith3Features": "Insira um apelido para usar {{feature1}}, {{feature2}} e {{feature3}}",
"titleWith4Features": "Insira um apelido para usar {{feature1}}, {{feature2}}, {{feature3}} e {{feature4}}",
"titleWithCC": "Insira um apelido para usar o chat e as legendas ocultas",
"titleWithPolls": "Digite um apelido para usar o chat e as sondagens",
"titleWithPollsAndCC": "Insira um apelido para utilizar o chat, as sondagens e as legendas ocultas",
@@ -279,7 +291,6 @@
"Submit": "Submeter",
"Understand": "Entendo, mantenha-me em silêncio por enquanto.",
"UnderstandAndUnmute": "Entendo, por favor, desative o silêncio.",
"WaitForHostMsg": "A conferência ainda não começou porque ainda não chegaram moderadores. Se quiser ser um moderador, inicie a sessão. Caso contrário, aguarde.",
"WaitForHostNoAuthMsg": "A conferência ainda não começou porque ainda não chegaram os moderadores. Por favor, aguarde.",
"WaitingForHostButton": "Esperar pelo moderador",
"WaitingForHostTitle": "À espera de um moderador…",
@@ -522,6 +533,7 @@
"tokenAuthFailedWithReasons": "Lamentamos, mas não está autorizado a participar nesta chamada. Razões possíveis: {{reason}}",
"tokenAuthUnsupported": "O URL de token não é suportado.",
"transcribing": "Transcrição",
"unauthenticatedAccessDisabled": "Esta chamada requer autenticação. Por favor, inicie sessão para prosseguir.",
"unlockRoom": "Retirar reunião $t(lockRoomPassword)",
"user": "Utilizador",
"userIdentifier": "Identificador do utilizador",
@@ -566,13 +578,17 @@
"downloadFailedDescription": "Por favor, tente novamente.",
"downloadFailedTitle": "Falha no descarregar",
"downloadFile": "Descarregar",
"downloadStarted": "O download do ficheiro foi iniciado",
"dragAndDrop": "Arraste e solte os ficheiros aqui ou em qualquer lugar do ecrã",
"fileAlreadyUploaded": "O ficheiro já foi carregado para esta reunião.",
"fileRemovedByOther": "O seu ficheiro '{{ fileName }}' foi removido",
"fileTooLargeDescription": "Certifique-se de que o ficheiro não exceda {{ maxFileSize }}.",
"fileTooLargeTitle": "O ficheiro selecionado é muito grande",
"fileUploadProgress": "Progresso do envio do ficheiro",
"fileUploadedSuccessfully": "Ficheiro carregado com sucesso",
"newFileNotification": "{{ participantName }} partilhou '{{ fileName }}'",
"removeFile": "Remover",
"removeFileSuccess": "Ficheiro removido com sucesso",
"uploadFailedDescription": "Por favor, tente novamente.",
"uploadFailedTitle": "Falha ao carregar",
"uploadFile": "Partilhar ficheiro"
@@ -745,7 +761,8 @@
"notificationTitle": "Sala de espera",
"passwordJoinButton": "Solicitar",
"title": "Sala de espera",
"toggleLabel": "Ativar sala de espera"
"toggleLabel": "Ativar sala de espera",
"waitForModerator": "A conferência ainda não começou porque não chegou nenhum moderador. Se deseja tornar-se um moderador, faça login. Caso contrário, aguarde."
},
"localRecording": {
"clientState": {
@@ -862,6 +879,7 @@
"oldElectronClientDescription1": "Parece estar a utilizar uma versão antiga do cliente Jitsi Meet que tem vulnerabilidades de segurança conhecidas. Por favor, certifique-se de que actualiza a nossa ",
"oldElectronClientDescription2": "compilação mais recente",
"oldElectronClientDescription3": " agora!",
"openChat": "Abrir chat",
"participantWantsToJoin": "Deseja juntar-se à reunião",
"participantsWantToJoin": "Desejam juntar-se à reunião",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) removido por outro participante",
@@ -960,6 +978,9 @@
"by": "Por {{ name }}",
"closeButton": "Fechar sondagem",
"create": {
"accessibilityLabel": {
"send": "Enviar sondagem"
},
"addOption": "Adicionar opção",
"answerPlaceholder": "Opção {{index}}",
"cancel": "Cancelar",
@@ -968,8 +989,7 @@
"pollQuestion": "Pergunta de Sondagem",
"questionPlaceholder": "Faça uma pergunta",
"removeOption": "Remover opção",
"save": "Guardar",
"send": "Enviar"
"save": "Guardar"
},
"errors": {
"notUniqueOption": "As opções devem ser únicas"
@@ -1338,7 +1358,7 @@
"muteEveryoneElsesVideo": "Parar o vídeo de todos os outros",
"muteEveryonesVideo": "Parar o vídeo de todos",
"muteGUMPending": "A ligar o seu microfone",
"noiseSuppression": "Supressão extra de ruído (BETA)",
"noiseSuppression": "Supressão extra de ruído",
"openChat": "Abrir chat",
"participants": "Abrir painel de participantes. {{participantsCount}} participantes",
"pip": "Mudar para o modo Picture-in-Picture",
@@ -1376,6 +1396,20 @@
"videounmute": "Iniciar câmara"
},
"addPeople": "Adicione pessoas à sua chamada",
"advancedAudioSettings": {
"aec": {
"label": "Cancelamento de eco acústico"
},
"agc": {
"label": "Controlo automático de ganho"
},
"ns": {
"label": "Supressão de ruído"
},
"stereo": {
"label": "Estéreo"
}
},
"audioOnlyOff": "Desativar modo de largura de banda baixa",
"audioOnlyOn": "Ativar modo de largura de banda baixa",
"audioRoute": "Selecionar o dispositivo de som",
@@ -1389,20 +1423,21 @@
"closeParticipantsPane": "Fechar painel de participantes",
"closeReactionsMenu": "Fechar menu de reações",
"closedCaptions": "Legendas ocultas",
"disableNoiseSuppression": "Desativar supressão de ruído extra (BETA)",
"disableNoiseSuppression": "Desativar supressão de ruído extra",
"disableReactionSounds": "Pode desactivar os sons de reacção para esta reunião",
"documentClose": "Fechar documento partilhado",
"documentOpen": "Abrir documento partilhado",
"download": "Descarregar as nossas aplicações",
"e2ee": "Criptografia ponta a ponta",
"embedMeeting": "Incorporar reunião",
"enableNoiseSuppression": "Ativar supressão extra de ruído (BETA)",
"enableNoiseSuppression": "Ativar supressão extra de ruído",
"endConference": "Terminar reunião para todos",
"enterFullScreen": "Ver em ecrã completo",
"enterTileView": "Ver em quadrícula",
"exitFullScreen": "Sair de ecrã completo",
"exitTileView": "Sair de quadrícula",
"feedback": "Deixar comentários",
"fileSharing": "Partilha de ficheiros",
"giphy": "Ativar/Desativar o menu GIPHY",
"hangup": "Sair da reunião",
"help": "Ajuda",
@@ -1431,13 +1466,14 @@
"noAudioSignalDialInDesc": "Também pode marcar usando:",
"noAudioSignalDialInLinkDesc": "Números de marcação",
"noAudioSignalTitle": "Não há nenhuma entrada vinda do seu microfone!",
"noiseSuppression": "Supressão extra de ruído (BETA)",
"noiseSuppression": "Supressão extra de ruído",
"noisyAudioInputDesc": "Parece que o seu microfone está a fazer barulho, por favor considere silenciar ou mudar de dispositivo.",
"noisyAudioInputTitle": "Seu microfone parece estar barulhento!",
"openChat": "Abrir chat",
"openReactionsMenu": "Abrir menu de reações",
"participants": "Participantes",
"pip": "Entrar no modo Picture-in-Picture",
"polls": "Sondagens",
"privateMessage": "Enviar mensagem privada",
"profile": "Editar o seu perfil",
"raiseHand": "Levantar a mão",
@@ -1447,6 +1483,7 @@
"reactionHeart": "Enviar reação com coração",
"reactionLaugh": "Enviar reação de risos",
"reactionLike": "Enviar reação de aprovado",
"reactionLove": "Enviar reação de amor",
"reactionSilence": "Enviar reação de silêncio",
"reactionSurprised": "Enviar reação de surpreendido",
"reactions": "Reações",
@@ -1598,6 +1635,8 @@
"noMainParticipantsTitle": "Esta reunião ainda não começou.",
"noVisitorLobby": "Não é possível aderir enquanto houver uma sala de espera activada para a reunião.",
"notAllowedPromotion": "É necessário que um participante autorize primeiro o seu pedido.",
"requestToJoin": "Mão levantada",
"requestToJoinDescription": "A sua solicitação foi enviada aos moderadores. Aguarde um momento!",
"title": "É um espectador na reunião"
},
"waitingMessage": "Participará na reunião assim que esta estiver em direto!"

View File

@@ -227,6 +227,9 @@
"video_ssrc": "Video SSRC:",
"yes": "yes"
},
"customPanel": {
"close": "Close"
},
"dateUtils": {
"earlier": "Earlier",
"today": "Today",
@@ -1190,7 +1193,7 @@
"signedIn": "Currently accessing calendar events for {{email}}. Click the Disconnect button below to stop accessing calendar events.",
"title": "Calendar"
},
"chatWithPermissions": "Chat requires permission",
"chatWithPermissions": "Disable chat for non-moderators",
"desktopShareFramerate": "Desktop sharing frame rate",
"desktopShareHighFpsWarning": "A higher frame rate for desktop sharing might affect your bandwidth. You need to restart the screen share for the new settings to take effect.",
"desktopShareWarning": "You need to restart the screen share for the new settings to take effect.",
@@ -1315,6 +1318,7 @@
"chat": "Open / Close chat",
"clap": "Clap",
"closeChat": "Close chat",
"closeCustomPanel": "Close",
"closeMoreActions": "Close more actions menu",
"closeParticipantsPane": "Close participants pane",
"closedCaptions": "Closed captions",
@@ -1358,7 +1362,7 @@
"muteEveryoneElsesVideoStream": "Stop everyone else's video",
"muteEveryonesVideoStream": "Stop everyone's video",
"muteGUMPending": "Connecting your microphone",
"noiseSuppression": "Extra noise suppression (BETA)",
"noiseSuppression": "Extra noise suppression",
"openChat": "Open chat",
"participants": "Open participants panel. {{participantsCount}} participants",
"pip": "Toggle Picture-in-Picture mode",
@@ -1420,17 +1424,19 @@
"chat": "Open / Close chat",
"clap": "Clap",
"closeChat": "Close chat",
"closeCustomPanel": "Close",
"closeParticipantsPane": "Close participants pane",
"closeReactionsMenu": "Close reactions menu",
"closedCaptions": "Closed captions",
"disableNoiseSuppression": "Disable extra noise suppression (BETA)",
"copilot": "Copilot",
"disableNoiseSuppression": "Disable extra noise suppression",
"disableReactionSounds": "You can disable reaction sounds for this meeting",
"documentClose": "Close shared document",
"documentOpen": "Open shared document",
"download": "Download our apps",
"e2ee": "End-to-End Encryption",
"embedMeeting": "Embed meeting",
"enableNoiseSuppression": "Enable extra noise suppression (BETA)",
"enableNoiseSuppression": "Enable extra noise suppression",
"endConference": "End meeting for all",
"enterFullScreen": "View full screen",
"enterTileView": "Enter tile view",
@@ -1466,7 +1472,7 @@
"noAudioSignalDialInDesc": "You can also dial-in using:",
"noAudioSignalDialInLinkDesc": "Dial-in numbers",
"noAudioSignalTitle": "There is no input coming from your mic!",
"noiseSuppression": "Extra noise suppression (BETA)",
"noiseSuppression": "Extra noise suppression",
"noisyAudioInputDesc": "It sounds like your microphone is making noise, please consider muting or changing the device.",
"noisyAudioInputTitle": "Your microphone appears to be noisy!",
"openChat": "Open chat",

View File

@@ -107,9 +107,10 @@ import {
open as openParticipantsPane
} from '../../react/features/participants-pane/actions';
import { getParticipantsPaneOpen } from '../../react/features/participants-pane/functions';
import { hidePiP, showPiP } from '../../react/features/pip/actions';
import { startLocalVideoRecording, stopLocalVideoRecording } from '../../react/features/recording/actions.any';
import { grantRecordingConsent, grantRecordingConsentAndUnmute } from '../../react/features/recording/actions.web';
import { RECORDING_METADATA_ID, RECORDING_TYPES } from '../../react/features/recording/constants';
import { RECORDING_TYPES } from '../../react/features/recording/constants';
import { getActiveSession, supportsLocalRecording } from '../../react/features/recording/functions';
import { startAudioScreenShareFlow, startScreenShareFlow } from '../../react/features/screen-share/actions';
import { isScreenAudioSupported } from '../../react/features/screen-share/functions';
@@ -125,7 +126,7 @@ import { extractYoutubeIdOrURL } from '../../react/features/shared-video/functio
import { setRequestingSubtitles, toggleRequestingSubtitles } from '../../react/features/subtitles/actions';
import { isAudioMuteButtonDisabled } from '../../react/features/toolbox/functions';
import { setTileView, toggleTileView } from '../../react/features/video-layout/actions.any';
import { muteAllParticipants } from '../../react/features/video-menu/actions';
import { muteAllParticipants, muteRemote } from '../../react/features/video-menu/actions';
import { setVideoQuality } from '../../react/features/video-quality/actions';
import { toggleBackgroundEffect, toggleBlurredBackgroundEffect } from '../../react/features/virtual-background/actions';
import { VIRTUAL_BACKGROUND_TYPE } from '../../react/features/virtual-background/constants';
@@ -238,6 +239,31 @@ function initCommands() {
APP.store.dispatch(muteAllParticipants(exclude, muteMediaType));
},
'mute-remote-participant': (participantId, mediaType) => {
const state = APP.store.getState();
const muteMediaType = mediaType ? mediaType : MEDIA_TYPE.AUDIO;
const localParticipant = getLocalParticipant(state);
// Check if targeting the local participant
if (participantId === localParticipant?.id) {
if (muteMediaType === MEDIA_TYPE.AUDIO) {
APP.conference.toggleAudioMuted(false);
} else if (muteMediaType === MEDIA_TYPE.VIDEO) {
APP.conference.toggleVideoMuted(false, true);
}
return;
}
if (!isLocalParticipantModerator(state)) {
logger.error('Missing moderator rights to mute remote participant');
return;
}
APP.store.dispatch(muteRemote(participantId, muteMediaType));
},
'toggle-lobby': isLobbyEnabled => {
APP.store.dispatch(toggleLobbyMode(isLobbyEnabled));
},
@@ -779,9 +805,6 @@ function initCommands() {
if (transcription) {
APP.store.dispatch(setRequestingSubtitles(true, false, null, true));
conference.getMetadataHandler().setMetadata(RECORDING_METADATA_ID, {
isTranscribingEnabled: true
});
}
},
@@ -803,10 +826,7 @@ function initCommands() {
}
if (transcription) {
APP.store.dispatch(setRequestingSubtitles(false, false, null));
conference.getMetadataHandler().setMetadata(RECORDING_METADATA_ID, {
isTranscribingEnabled: false
});
APP.store.dispatch(setRequestingSubtitles(false, false, null, true));
}
if (mode === 'local') {
@@ -907,6 +927,12 @@ function initCommands() {
backgroundType: VIRTUAL_BACKGROUND_TYPE.IMAGE,
virtualSource: backgroundImage
}, jitsiTrack));
},
'show-pip': () => {
APP.store.dispatch(showPiP());
},
'hide-pip': () => {
APP.store.dispatch(hidePiP());
}
};
transport.on('event', ({ data, name }) => {
@@ -1242,6 +1268,20 @@ class API {
});
}
/**
* Notify external application (if API is enabled) that the in-page toolbox
* visibility changed.
*
* @param {boolean} visible - True if the toolbox is visible, false otherwise.
* @returns {void}
*/
notifyToolbarVisibilityChanged(visible) {
this._sendEvent({
name: 'toolbar-visibility-changed',
visible
});
}
/**
* Notifies the external application (spot) that the local jitsi-participant
* has a status update.
@@ -1386,6 +1426,23 @@ class API {
});
}
/**
* Notify the external application that a participant's mute status changed.
*
* @param {string} participantId - The ID of the participant.
* @param {boolean} isMuted - True if muted, false if unmuted.
* @param {string} mediaType - Media type that was muted ('audio' or 'video').
* @returns {void}
*/
notifyParticipantMuted(participantId, isMuted, mediaType) {
this._sendEvent({
name: 'participant-muted',
id: participantId,
isMuted,
mediaType
});
}
/**
* Notify the external app that a notification has been triggered.
*
@@ -2235,6 +2292,40 @@ class API {
});
}
/**
* Notify external application (if API is enabled) that Picture-in-Picture was requested.
* Used by Electron to handle PiP requests with proper user gesture context.
*
* @returns {void}
*/
notifyPictureInPictureRequested() {
this._sendEvent({
name: '_pip-requested'
});
}
/**
* Notify external application (if API is enabled) that Picture-in-Picture mode was entered.
*
* @returns {void}
*/
notifyPictureInPictureEntered() {
this._sendEvent({
name: 'pip-entered'
});
}
/**
* Notify external application (if API is enabled) that Picture-in-Picture mode was exited.
*
* @returns {void}
*/
notifyPictureInPictureLeft() {
this._sendEvent({
name: 'pip-left'
});
}
/**
* Notify external application ( if API is enabled) that a participant menu button was clicked.
*

View File

@@ -2,6 +2,7 @@ import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
import EventEmitter from 'events';
import { urlObjectToString } from '../../../react/features/base/util/uri';
import { isPiPEnabled } from '../../../react/features/pip/external-api.shared';
import {
PostMessageTransportBackend,
Transport
@@ -46,6 +47,7 @@ const commands = {
localSubject: 'local-subject',
kickParticipant: 'kick-participant',
muteEveryone: 'mute-everyone',
muteRemoteParticipant: 'mute-remote-participant',
overwriteConfig: 'overwrite-config',
overwriteNames: 'overwrite-names',
password: 'password',
@@ -94,7 +96,9 @@ const commands = {
toggleTileView: 'toggle-tile-view',
toggleVirtualBackgroundDialog: 'toggle-virtual-background',
toggleVideo: 'toggle-video',
toggleWhiteboard: 'toggle-whiteboard'
toggleWhiteboard: 'toggle-whiteboard',
showPiP: 'show-pip',
hidePiP: 'hide-pip'
};
/**
@@ -102,6 +106,9 @@ const commands = {
* events expected by jitsi-meet.
*/
const events = {
'_pip-requested': '_pipRequested',
'pip-entered': 'pipEntered',
'pip-left': 'pipLeft',
'avatar-changed': 'avatarChanged',
'audio-availability-changed': 'audioAvailabilityChanged',
'audio-mute-status-changed': 'audioMuteStatusChanged',
@@ -144,6 +151,7 @@ const events = {
'participant-joined': 'participantJoined',
'participant-kicked-out': 'participantKickedOut',
'participant-left': 'participantLeft',
'participant-muted': 'participantMuted',
'participant-role-changed': 'participantRoleChanged',
'participants-pane-toggled': 'participantsPaneToggled',
'password-required': 'passwordRequired',
@@ -167,6 +175,7 @@ const events = {
'suspend-detected': 'suspendDetected',
'tile-view-changed': 'tileViewChanged',
'toolbar-button-clicked': 'toolbarButtonClicked',
'toolbar-visibility-changed': 'toolbarVisibilityChanged',
'transcribing-status-changed': 'transcribingStatusChanged',
'transcription-chunk-received': 'transcriptionChunkReceived',
'whiteboard-status-changed': 'whiteboardStatusChanged'
@@ -329,6 +338,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
this._myUserID = undefined;
this._onStageParticipant = undefined;
this._iAmvisitor = undefined;
this._pipConfig = configOverwrite?.pip;
this._setupListeners();
id++;
}
@@ -648,6 +658,56 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
this.emit(requestName, data, callback);
}
});
this._setupIntersectionObserver();
}
/**
* Sets up IntersectionObserver to monitor iframe visibility.
* Calls showPiP/hidePiP based on visibility.
*
* @private
* @returns {void}
*/
_setupIntersectionObserver() {
if (!isPiPEnabled(this._pipConfig)) {
return;
}
// Don't create duplicate observers.
if (this._intersectionObserver) {
return;
}
this._isIntersecting = true;
this._intersectionObserver = new IntersectionObserver(entries => {
const entry = entries[entries.length - 1];
const wasIntersecting = this._isIntersecting;
this._isIntersecting = entry.isIntersecting;
if (!entry.isIntersecting && wasIntersecting) {
this.showPiP();
} else if (entry.isIntersecting && !wasIntersecting) {
this.hidePiP();
}
});
this._intersectionObserver.observe(this._frame);
}
/**
* Tears down IntersectionObserver.
*
* @private
* @returns {void}
*/
_teardownIntersectionObserver() {
if (this._intersectionObserver) {
this._intersectionObserver.disconnect();
this._intersectionObserver = null;
}
}
/**
@@ -850,6 +910,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
this.emit('_willDispose');
this._transport.dispose();
this.removeAllListeners();
this._teardownIntersectionObserver();
if (this._frame && this._frame.parentNode) {
this._frame.parentNode.removeChild(this._frame);
}
@@ -878,10 +940,47 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
return;
}
// Handle pip config changes locally.
// We update local state, send command to iframe, then handle PiP show/hide
// so the iframe config is updated before we try to show PiP.
let pipTransition = null;
if (name === 'overwriteConfig' && args[0]?.pip !== undefined) {
const wasEnabled = isPiPEnabled(this._pipConfig);
this._pipConfig = {
...this._pipConfig,
...args[0].pip
};
const isEnabled = isPiPEnabled(this._pipConfig);
if (!wasEnabled && isEnabled) {
this._setupIntersectionObserver();
pipTransition = 'enabled';
} else if (wasEnabled && !isEnabled) {
this._teardownIntersectionObserver();
pipTransition = 'disabled';
}
}
// Send command to iframe first.
this._transport.sendEvent({
data: args,
name: commands[name]
});
// Handle PiP state after command is sent so iframe config is updated.
if (pipTransition === 'enabled') {
// Show PiP if iframe is currently not visible.
if (!this._isIntersecting) {
this.showPiP();
}
} else if (pipTransition === 'disabled') {
// Hide any open PiP window.
this.hidePiP();
}
}
/**
@@ -1495,6 +1594,24 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
this.executeCommand('setVirtualBackground', enabled, backgroundImage);
}
/**
* Shows Picture-in-Picture window.
*
* @returns {void}
*/
showPiP() {
this.executeCommand('showPiP');
}
/**
* Hides Picture-in-Picture window.
*
* @returns {void}
*/
hidePiP() {
this.executeCommand('hidePiP');
}
/**
* Opens the desktop picker. This is invoked by the Electron SDK when gDM is used.
*

View File

@@ -3,7 +3,7 @@
import Logger from '@jitsi/logger';
import $ from 'jquery';
import React from 'react';
import ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
@@ -166,7 +166,8 @@ export default class LargeVideoManager {
this.removePresenceLabel();
ReactDOM.unmountComponentAtNode(this._dominantSpeakerAvatarContainer);
this._avatarRoot?.unmount();
this._avatarRoot = null;
this.container.style.display = 'none';
}
@@ -518,14 +519,16 @@ export default class LargeVideoManager {
* Updates the src of the dominant speaker avatar
*/
updateAvatar() {
ReactDOM.render(
if (!this._avatarRoot) {
this._avatarRoot = createRoot(this._dominantSpeakerAvatarContainer);
}
this._avatarRoot.render(
<Provider store = { APP.store }>
<Avatar
id = "dominantSpeakerAvatar"
participantId = { this.id }
size = { 200 } />
</Provider>,
this._dominantSpeakerAvatarContainer
</Provider>
);
}
@@ -559,15 +562,18 @@ export default class LargeVideoManager {
const presenceLabelContainer = document.getElementById('remotePresenceMessage');
if (presenceLabelContainer) {
ReactDOM.render(
if (!this._presenceLabelRoot) {
this._presenceLabelRoot = createRoot(presenceLabelContainer);
}
this._presenceLabelRoot.render(
<Provider store = { APP.store }>
<I18nextProvider i18n = { i18next }>
<PresenceLabel
participantID = { id }
className = 'presence-label' />
</I18nextProvider>
</Provider>,
presenceLabelContainer);
</Provider>
);
}
}
@@ -577,11 +583,8 @@ export default class LargeVideoManager {
* @returns {void}
*/
removePresenceLabel() {
const presenceLabelContainer = document.getElementById('remotePresenceMessage');
if (presenceLabelContainer) {
ReactDOM.unmountComponentAtNode(presenceLabelContainer);
}
this._presenceLabelRoot?.unmount();
this._presenceLabelRoot = null;
}
/**

View File

@@ -4,7 +4,7 @@
import Logger from '@jitsi/logger';
import $ from 'jquery';
import React from 'react';
import ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import { browser } from '../../../react/features/base/lib-jitsi-meet';
import { FILMSTRIP_BREAKPOINT } from '../../../react/features/filmstrip/constants';
@@ -659,7 +659,12 @@ export class VideoContainer extends LargeContainer {
return;
}
ReactDOM.render(
const container = document.getElementById('largeVideoBackgroundContainer');
if (!this._backgroundRoot) {
this._backgroundRoot = createRoot(container);
}
this._backgroundRoot.render(
<LargeVideoBackground
hidden = { this._hideBackground || this._isHidden }
mirror = {
@@ -669,8 +674,7 @@ export class VideoContainer extends LargeContainer {
}
orientationFit = { this._backgroundOrientation }
videoElement = { this.video }
videoTrack = { this.stream } />,
document.getElementById('largeVideoBackgroundContainer')
videoTrack = { this.stream } />
);
}
}

5005
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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/v2115.0.0+cc2f34c2/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v2124.0.0+80df84a1/lib-jitsi-meet.tgz",
"lodash-es": "4.17.21",
"null-loader": "4.0.1",
"optional-require": "1.0.3",
@@ -97,7 +97,7 @@
"react-native-pager-view": "6.8.1",
"react-native-paper": "5.10.3",
"react-native-performance": "5.1.2",
"react-native-safe-area-context": "5.5.2",
"react-native-safe-area-context": "5.6.1",
"react-native-screens": "4.11.1",
"react-native-sound": "https://github.com/jitsi/react-native-sound.git#ea13c97b5c2a4ff5e0d9bacbd9ff5e4457fe2c3c",
"react-native-splash-view": "0.0.18",
@@ -164,12 +164,12 @@
"@types/w3c-image-capture": "1.0.6",
"@types/w3c-web-hid": "1.0.3",
"@types/zxcvbn": "4.4.1",
"@wdio/allure-reporter": "9.16.0",
"@wdio/cli": "9.16.0",
"@wdio/globals": "9.16.0",
"@wdio/junit-reporter": "9.16.0",
"@wdio/local-runner": "9.16.0",
"@wdio/mocha-framework": "9.16.0",
"@wdio/allure-reporter": "9.23.2",
"@wdio/cli": "9.23.2",
"@wdio/globals": "9.23.0",
"@wdio/junit-reporter": "9.23.2",
"@wdio/local-runner": "9.23.2",
"@wdio/mocha-framework": "9.23.2",
"babel-loader": "9.1.0",
"babel-plugin-optional-require": "0.3.1",
"circular-dependency-plugin": "5.2.0",
@@ -193,8 +193,8 @@
"ts-loader": "9.4.2",
"typescript": "5.7.2",
"unorm": "1.6.0",
"webdriverio": "9.16.0",
"webpack": "5.95.0",
"webdriverio": "9.22.0",
"webpack": "5.105.0",
"webpack-bundle-analyzer": "4.4.2",
"webpack-cli": "5.1.4",
"webpack-dev-server": "5.1.0"

View File

@@ -1,4 +1,4 @@
import { setRoom } from '../base/conference/actions';
import { setRoom } from '../base/conference/actions.native';
import { getConferenceState } from '../base/conference/functions';
import {
configWillLoad,
@@ -29,7 +29,7 @@ import {
} from '../mobile/navigation/rootNavigationContainerRef';
import { screen } from '../mobile/navigation/routes';
import { clearNotifications } from '../notifications/actions';
import { isUnsafeRoomWarningEnabled } from '../prejoin/functions';
import { isUnsafeRoomWarningEnabled } from '../prejoin/functions.native';
import { maybeRedirectToTokenAuthUrl } from './actions.any';
import { addTrackStateToURL, getDefaultURL } from './functions.native';

View File

@@ -5,6 +5,7 @@ import JitsiThemeProvider from '../../base/ui/components/JitsiThemeProvider.web'
import DialogContainer from '../../base/ui/components/web/DialogContainer';
import ChromeExtensionBanner from '../../chrome-extension-banner/components/ChromeExtensionBanner.web';
import OverlayContainer from '../../overlay/components/web/OverlayContainer';
import PiP from '../../pip/components/PiP';
import { AbstractApp } from './AbstractApp';
@@ -47,6 +48,7 @@ export class App extends AbstractApp {
<JitsiThemeProvider>
<GlobalStyles />
<ChromeExtensionBanner />
<PiP />
{ super._createMainElement(component, props) }
</JitsiThemeProvider>
);

View File

@@ -2,7 +2,6 @@ import '../analytics/middleware';
import '../authentication/middleware';
import '../av-moderation/middleware';
import '../base/conference/middleware';
import '../base/config/middleware';
import '../base/i18n/middleware';
import '../base/jwt/middleware';
import '../base/known-domains/middleware';

View File

@@ -1,3 +1,4 @@
import '../base/config/middleware';
import '../dynamic-branding/middleware';
import '../gifs/middleware';
import '../mobile/audio-mode/middleware';

View File

@@ -1,4 +1,5 @@
import '../base/app/middleware';
import '../base/config/middleware';
import '../base/connection/middleware';
import '../base/devices/middleware';
import '../base/media/middleware';
@@ -11,6 +12,7 @@ import '../no-audio-signal/middleware';
import '../notifications/middleware';
import '../noise-detection/middleware';
import '../old-client-notification/middleware';
import '../pip/middleware';
import '../power-monitor/middleware';
import '../prejoin/middleware';
import '../remote-control/middleware';
@@ -24,5 +26,6 @@ import '../face-landmarks/middleware';
import '../gifs/middleware';
import '../whiteboard/middleware.web';
import '../file-sharing/middleware.web';
import '../custom-panel/middleware.web';
import './middlewares.any';

View File

@@ -1,5 +1,6 @@
import '../base/devices/reducer';
import '../base/premeeting/reducer';
import '../custom-panel/reducer';
import '../base/tooltip/reducer';
import '../e2ee/reducer';
import '../face-landmarks/reducer';
@@ -8,6 +9,7 @@ import '../keyboard-shortcuts/reducer';
import '../no-audio-signal/reducer';
import '../noise-detection/reducer';
import '../participants-pane/reducer';
import '../pip/reducer';
import '../power-monitor/reducer';
import '../prejoin/reducer';
import '../remote-control/reducer';

View File

@@ -31,6 +31,7 @@ import { IUserInteractionState } from '../base/user-interaction/reducer';
import { IBreakoutRoomsState } from '../breakout-rooms/reducer';
import { ICalendarSyncState } from '../calendar-sync/reducer';
import { IChatState } from '../chat/reducer';
import { ICustomPanelState } from '../custom-panel/reducer';
import { IDeepLinkingState } from '../deep-linking/reducer';
import { IDropboxState } from '../dropbox/reducer';
import { IDynamicBrandingState } from '../dynamic-branding/reducer';
@@ -57,6 +58,7 @@ import { INoiseDetectionState } from '../noise-detection/reducer';
import { INoiseSuppressionState } from '../noise-suppression/reducer';
import { INotificationsState } from '../notifications/reducer';
import { IParticipantsPaneState } from '../participants-pane/reducer';
import { IPipState } from '../pip/reducer';
import { IPollsState } from '../polls/reducer';
import { IPollsHistoryState } from '../polls-history/reducer';
import { IPowerMonitorState } from '../power-monitor/reducer';
@@ -120,6 +122,7 @@ export interface IReduxState {
'features/calendar-sync': ICalendarSyncState;
'features/call-integration': ICallIntegrationState;
'features/chat': IChatState;
'features/custom-panel': ICustomPanelState;
'features/deep-linking': IDeepLinkingState;
'features/dropbox': IDropboxState;
'features/dynamic-branding': IDynamicBrandingState;
@@ -145,6 +148,7 @@ export interface IReduxState {
'features/noise-suppression': INoiseSuppressionState;
'features/notifications': INotificationsState;
'features/participants-pane': IParticipantsPaneState;
'features/pip': IPipState;
'features/polls': IPollsState;
'features/polls-history': IPollsHistoryState;
'features/power-monitor': IPowerMonitorState;

View File

@@ -81,7 +81,6 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
break;
}
case LOCAL_PARTICIPANT_MODERATION_NOTIFICATION: {
let descriptionKey;
let titleKey;
let uid = '';
const localParticipant = getLocalParticipant(getState);
@@ -111,8 +110,6 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
!raisedHand && dispatch(raiseHand(true));
dispatch(hideNotification(uid));
}) ],
descriptionKey,
sticky: true,
titleKey,
uid
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
@@ -271,7 +268,6 @@ StateListenerRegistry.register(
dispatch(showNotification({
titleKey: 'notify.hostAskedUnmute',
sticky: true,
customActionNameKey,
customActionHandler,
uid: ASKED_TO_UNMUTE_NOTIFICATION_ID

View File

@@ -5,7 +5,18 @@ import Icon from '../../../icons/components/Icon';
import { pixelsToRem } from '../../../ui/functions.any';
import { isIcon } from '../../functions';
import { IAvatarProps } from '../../types';
import { PRESENCE_AVAILABLE_COLOR, PRESENCE_AWAY_COLOR, PRESENCE_BUSY_COLOR, PRESENCE_IDLE_COLOR } from '../styles';
import {
PRESENCE_AVAILABLE_COLOR,
PRESENCE_AWAY_COLOR,
PRESENCE_BUSY_COLOR,
PRESENCE_IDLE_COLOR
} from '../styles';
import {
AVATAR_DEFAULT_BACKGROUND_COLOR,
getAvatarFont,
getAvatarInitialsColor
} from './styles';
interface IProps extends IAvatarProps {
@@ -48,10 +59,10 @@ interface IProps extends IAvatarProps {
const useStyles = makeStyles()(theme => {
return {
avatar: {
backgroundColor: '#AAA',
backgroundColor: AVATAR_DEFAULT_BACKGROUND_COLOR,
borderRadius: '50%',
color: theme.palette?.text01 || '#fff',
...(theme.typography?.heading1 ?? {}),
color: getAvatarInitialsColor(theme),
...getAvatarFont(theme),
fontSize: 'inherit',
objectFit: 'cover',
textAlign: 'center',

View File

@@ -0,0 +1,26 @@
import { Theme } from '@mui/material/styles';
// Default avatar background color
export const AVATAR_DEFAULT_BACKGROUND_COLOR = '#AAA';
/**
* Returns the avatar font style from the theme.
*
* @param {Theme} theme - The MUI theme.
* @returns {Object} The font style object containing fontFamily, fontWeight, etc.
*/
export const getAvatarFont = (theme: Theme) => theme.typography?.heading1 ?? {};
/**
* Default text color for avatar initials.
*/
export const AVATAR_DEFAULT_INITIALS_COLOR = '#FFFFFF';
/**
* Returns the text color for avatar initials from the theme.
*
* @param {Theme} theme - The MUI theme.
* @returns {string} The text color.
*/
export const getAvatarInitialsColor = (theme: Theme): string =>
theme.palette?.text01 || AVATAR_DEFAULT_INITIALS_COLOR;

View File

@@ -93,10 +93,17 @@ export function commonUserJoinedHandling(
if (!user.isHidden()) {
const isReplacing = user?.isReplacing();
const isPromoted = conference?.getMetadataHandler().getMetadata()?.visitors?.promoted?.[id];
const userIdentity = user.getIdentity()?.user;
// Map identity from JWT context to userContext for external API
const userContext = userIdentity ? {
id: userIdentity.id,
name: userIdentity.name
} : undefined;
// the identity and avatar come from jwt and never change in the presence
dispatch(participantJoined({
avatarURL: user.getIdentity()?.user?.avatar,
avatarURL: userIdentity?.avatar,
botType: user.getBotType(),
conference,
id,
@@ -105,7 +112,8 @@ export function commonUserJoinedHandling(
role: user.getRole(),
isPromoted,
isReplacing,
sources: user.getSources()
sources: user.getSources(),
userContext
}));
}
}

View File

@@ -517,6 +517,10 @@ export interface IConfig {
peopleSearchQueryTypes?: string[];
peopleSearchTokenLocation?: string;
peopleSearchUrl?: string;
pip?: {
disabled?: boolean;
showOnPrejoin?: boolean;
};
preferBosh?: boolean;
preferVisitor?: boolean;
preferredTranscribeLanguage?: string;
@@ -555,6 +559,8 @@ export interface IConfig {
skipConsentInMeeting?: boolean;
suggestRecording?: boolean;
};
reducedUIEnabled?: boolean;
reducedUImainToolbarButtons?: Array<string>;
remoteVideoMenu?: {
disableDemote?: boolean;
disableGrantModerator?: boolean;
@@ -577,6 +583,7 @@ export interface IConfig {
};
serviceUrl?: string;
sharedVideoAllowedURLDomains?: Array<string>;
showChatPermissionsModeratorSetting?: boolean;
sipInviteUrl?: string;
speakerStats?: {
disableSearch?: boolean;
@@ -632,7 +639,6 @@ export interface IConfig {
autoTranscribeOnRecord?: boolean;
disableClosedCaptions?: boolean;
enabled?: boolean;
inviteJigasiOnBackendTranscribing?: boolean;
preferredLanguage?: string;
translationLanguages?: Array<string>;
translationLanguagesHead?: Array<string>;

View File

@@ -199,6 +199,7 @@ export default [
'participantMenuButtonsWithNotifyClick',
'participantsPane',
'pcStatsInterval',
'pip',
'preferBosh',
'preferVisitor',
'prejoinConfig.enabled',
@@ -214,10 +215,13 @@ export default [
'recordings.showPrejoinWarning',
'recordings.showRecordingLink',
'recordings.suggestRecording',
'reducedUIEnabled',
'reducedUImainToolbarButtons',
'replaceParticipant',
'resolution',
'screenshotCapture',
'securityUi',
'showChatPermissionsModeratorSetting',
'speakerStats',
'startAudioMuted',
'startAudioOnly',

View File

@@ -387,7 +387,8 @@ export function setConfigFromURLParams(
// When not in an iframe, start without media if the pre-join page is not enabled.
if (!isEmbedded()
&& 'config.prejoinConfig.enabled' in params && config.prejoinConfig?.enabled === false) {
&& ('config.prejoinConfig' in params || 'config.prejoinConfig.enabled' in params)
&& config.prejoinConfig?.enabled === false) {
logger.warn('Using prejoinConfig.enabled config URL overwrite implies starting without media.');
config.disableInitialGUM = true;
}

View File

@@ -2,7 +2,6 @@ import { AnyAction } from 'redux';
import { IStore } from '../../app/types';
import { SET_DYNAMIC_BRANDING_DATA } from '../../dynamic-branding/actionTypes';
import { setUserFilmstripWidth } from '../../filmstrip/actions.web';
import { getFeatureFlag } from '../flags/functions';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import { updateSettings } from '../settings/actions';
@@ -80,29 +79,8 @@ function _setConfig({ dispatch, getState }: IStore, next: Function, action: AnyA
}));
}
const { initialWidth, stageFilmstripParticipants } = action.config.filmstrip || {};
if (stageFilmstripParticipants !== undefined) {
dispatch(updateSettings({
maxStageParticipants: stageFilmstripParticipants
}));
}
if (initialWidth) {
dispatch(setUserFilmstripWidth(initialWidth));
}
dispatch(updateConfig(config));
// FIXME On Web we rely on the global 'config' variable which gets altered
// multiple times, before it makes it to the reducer. At some point it may
// not be the global variable which is being modified anymore due to
// different merge methods being used along the way. The global variable
// must be synchronized with the final state resolved by the reducer.
if (typeof window.config !== 'undefined') {
window.config = state['features/base/config'];
}
return result;
}

View File

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

View File

@@ -0,0 +1,48 @@
import { setUserFilmstripWidth } from '../../filmstrip/actions.web';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import { updateSettings } from '../settings/actions';
import { SET_CONFIG } from './actionTypes';
import './middleware.any';
/**
* The middleware of the feature {@code base/config}.
*
* @param {Store} store - The redux store.
* @private
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
const result = next(action);
switch (action.type) {
case SET_CONFIG: {
const { initialWidth, stageFilmstripParticipants } = action.config.filmstrip || {};
const { dispatch, getState } = store;
const state = getState();
if (stageFilmstripParticipants !== undefined) {
dispatch(updateSettings({
maxStageParticipants: stageFilmstripParticipants
}));
}
if (initialWidth) {
dispatch(setUserFilmstripWidth(initialWidth));
}
// FIXME On Web we rely on the global 'config' variable which gets altered
// multiple times, before it makes it to the reducer. At some point it may
// not be the global variable which is being modified anymore due to
// different merge methods being used along the way. The global variable
// must be synchronized with the final state resolved by the reducer.
if (typeof window.config !== 'undefined') {
window.config = state['features/base/config'];
}
break;
}
}
return result;
});

View File

@@ -4,6 +4,7 @@ import { getCustomerDetails } from '../../jaas/actions.any';
import { getJaasJWT, isVpaasMeeting } from '../../jaas/functions';
import { navigateRoot } from '../../mobile/navigation/rootNavigationContainerRef';
import { screen } from '../../mobile/navigation/routes';
import { conferenceWillLeave } from '../conference/actions.native';
import { setJWT } from '../jwt/actions';
import { JitsiConnectionErrors } from '../lib-jitsi-meet';
@@ -58,5 +59,8 @@ export function connect(id?: string, password?: string) {
* @returns {Function}
*/
export function hangup(_requestFeedback = false) {
return (dispatch: IStore['dispatch']) => dispatch(appNavigate(undefined));
return (dispatch: IStore['dispatch']) => {
dispatch(conferenceWillLeave());
dispatch(appNavigate(undefined));
};
}

View File

@@ -1,5 +1,6 @@
import React, { PureComponent, ReactNode } from 'react';
import { SafeAreaView, ScrollView, View, ViewStyle } from 'react-native';
import { ScrollView, View, ViewStyle } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { connect } from 'react-redux';
import { IStore } from '../../../../app/types';
@@ -122,6 +123,7 @@ class BottomSheet extends PureComponent<Props> {
style = { styles.sheetAreaCover } />
{ renderHeader?.() }
<SafeAreaView
edges = { [ 'left', 'right' ] }
style = { [
styles.sheetItemContainer,
renderHeader

View File

@@ -8,13 +8,14 @@ import i18next from './i18next';
*
* @param {string} language - The language e.g. 'en', 'fr'.
* @param {string} url - The url of the translation bundle.
* @param {string} ns - The namespace of the translation bundle.
* @returns {void}
*/
export async function changeLanguageBundle(language: string, url: string) {
export async function changeLanguageBundle(language: string, url: string, ns = 'main') {
const res = await fetch(url);
const bundle = await res.json();
i18next.addResourceBundle(language, 'main', bundle, true, true);
i18next.addResourceBundle(language, ns, bundle, true, true);
}
/**

View File

@@ -58,6 +58,14 @@ export const DEFAULT_LANGUAGE = 'en';
*/
export const TRANSLATION_LANGUAGES_HEAD: Array<string> = [ DEFAULT_LANGUAGE ];
/**
* The available/supported i18n namespaces.
*
* @public
* @type {Array<string>}
*/
export const SUPPORTED_NS = [ 'main', 'languages', 'countries', 'translation-languages' ];
/**
* The options to initialize i18next with.
*
@@ -81,7 +89,7 @@ const options: i18next.InitOptions = {
escapeValue: false // not needed for react as it escapes by default
},
load: 'all',
ns: [ 'main', 'languages', 'countries', 'translation-languages' ],
ns: SUPPORTED_NS,
react: {
// re-render when a new resource bundle is added
// @ts-expect-error. Fixed in i18next 19.6.1.

View File

@@ -4,7 +4,7 @@ import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import { I18NEXT_INITIALIZED, LANGUAGE_CHANGED } from './actionTypes';
import { changeLanguageBundle } from './functions';
import i18next from './i18next';
import i18next, { SUPPORTED_NS } from './i18next';
import logger from './logger';
/**
@@ -19,9 +19,10 @@ MiddlewareRegistry.register(store => next => action => {
case LANGUAGE_CHANGED:
case SET_DYNAMIC_BRANDING_DATA: {
const { language } = i18next;
const { labels } = action.type === SET_DYNAMIC_BRANDING_DATA
const data = action.type === SET_DYNAMIC_BRANDING_DATA
? action.value
: store.getState()['features/dynamic-branding'];
const labels = data?.labels;
if (language && labels?.[language]) {
changeLanguageBundle(language, labels[language])
@@ -30,6 +31,17 @@ MiddlewareRegistry.register(store => next => action => {
});
}
SUPPORTED_NS.forEach(ns => {
const nsLabels = data?.[`labels-${ns}`];
if (language && nsLabels?.[language]) {
changeLanguageBundle(language, nsLabels[language], ns)
.catch(err => {
logger.log(`Error setting dynamic language bundle for ${ns}`, err);
});
}
});
// Update transcription language, if applicable.
if (action.type === SET_DYNAMIC_BRANDING_DATA) {
const { defaultTranscriptionLanguage } = action.value;

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sparkles-icon lucide-sparkles"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/></svg>

After

Width:  |  Height:  |  Size: 582 B

View File

@@ -1,3 +1,4 @@
import { default as IconAI } from './AI.svg';
import { default as IconRecordAccount } from './account-record.svg';
import { default as IconAddUser } from './add-user.svg';
import { default as IconArrowBack } from './arrow-back.svg';
@@ -112,6 +113,7 @@ import { default as IconYahoo } from './yahoo.svg';
*/
export const DEFAULT_ICON: Record<string, any> = {
IconAddUser,
IconAI,
IconArrowBack,
IconArrowDown,
IconArrowDownLarge,

View File

@@ -4,6 +4,7 @@ import { DEFAULT_ICON } from './constants';
const {
IconAddUser,
IconAI,
IconArrowBack,
IconArrowDown,
IconArrowDownLarge,
@@ -123,6 +124,7 @@ const {
export {
IconAddUser,
IconAI,
IconArrowBack,
IconArrowDown,
IconArrowDownLarge,

View File

@@ -10,6 +10,10 @@ import { parseURLParams } from '../util/parseURLParams';
import { JWT_VALIDATION_ERRORS, MEET_FEATURES } from './constants';
import logger from './logger';
/**
* Note that this is just client-side code and it intentionally does not verify the signature of the JWT.
*/
/**
* Retrieves the JSON Web Token (JWT), if any, defined by a specific
* {@link URL}.

View File

@@ -56,9 +56,9 @@ const useStyles = makeStyles()(theme => {
label: {
...theme.typography.labelRegular,
alignItems: 'center',
background: theme.palette.ui04,
background: theme.palette.labelBackground,
borderRadius: '4px',
color: theme.palette.text01,
color: theme.palette.labelText,
display: 'flex',
margin: '0 2px',
padding: '6px',
@@ -72,11 +72,11 @@ const useStyles = makeStyles()(theme => {
cursor: 'pointer'
},
[COLORS.white]: {
background: theme.palette.ui09,
color: theme.palette.text04,
background: theme.palette.labelWhiteBackground,
color: theme.palette.labelWhiteText,
'& svg': {
fill: theme.palette.icon04
fill: theme.palette.labelWhiteIcon
}
},
[COLORS.green]: {

View File

@@ -1,4 +1,5 @@
import { IReduxState, IStore } from '../../app/types';
import { getLocalParticipant } from '../participants/functions';
import StateListenerRegistry from '../redux/StateListenerRegistry';
/**
@@ -13,6 +14,13 @@ StateListenerRegistry.register(
if (muted !== previousMuted) {
APP.API.notifyAudioMutedStatusChanged(muted);
// Also fire the participantMuted event for consistency
const localParticipant = getLocalParticipant(store.getState());
if (localParticipant) {
APP.API.notifyParticipantMuted(localParticipant.id, muted, 'audio');
}
}
}
);

View File

@@ -17,7 +17,7 @@ interface IProps {
/**
* The children component(s) of the Modal, to be rendered.
*/
children: React.ReactNode;
children?: React.ReactNode;
/**
* Additional style to be appended to the KeyboardAvoidingView content container.
@@ -63,7 +63,7 @@ const JitsiScreen = ({
footerComponent,
hasBottomTextInput = false,
hasExtraHeaderHeight = false,
safeAreaInsets = [ 'left', 'right' ],
safeAreaInsets = [ 'bottom', 'left', 'right' ],
style
}: IProps) => {
const renderContent = () => (
@@ -78,8 +78,8 @@ const JitsiScreen = ({
edges = { safeAreaInsets }
style = { styles.safeArea }>
{ children }
{ footerComponent?.() }
</SafeAreaView>
{ footerComponent?.() }
</JitsiKeyboardAvoidingView>
);

View File

@@ -80,3 +80,8 @@ export const LOWER_HAND_AUDIO_LEVEL = 0.2;
* Icon URL for the whiteboard participant.
*/
export const WHITEBOARD_PARTICIPANT_ICON = IconWhiteboard;
/**
* The ID used for non-participant (system) messages coming from a transcriber.
*/
export const TRANSCRIBER_ID = 'transcriber';

View File

@@ -818,7 +818,7 @@ export const addPeopleFeatureControl = (stateful: IStateful) => {
* @param {Function} dispatch - The Redux dispatch function.
* @returns {Function}
*/
export const setShareDialogVisiblity = (addPeopleFeatureEnabled: boolean, dispatch: IStore['dispatch']) => {
export const setShareDialogVisiblity = (addPeopleFeatureEnabled: boolean | undefined, dispatch: IStore['dispatch']) => {
if (addPeopleFeatureEnabled) {
dispatch(toggleShareDialog(false));
} else {

View File

@@ -606,13 +606,21 @@ function _e2eeUpdated({ getState, dispatch }: IStore, conference: IJitsiConferen
function _localParticipantJoined({ getState, dispatch }: IStore, next: Function, action: AnyAction) {
const result = next(action);
const settings = getState()['features/base/settings'];
const state = getState();
const settings = state['features/base/settings'];
const jwtUser = state['features/base/jwt']?.user;
const userContext = jwtUser ? {
id: jwtUser.id,
name: jwtUser.name
} : undefined;
dispatch(localParticipantJoined({
avatarURL: settings.avatarURL,
email: settings.email,
name: settings.displayName,
id: ''
id: '',
userContext
}));
return result;

View File

@@ -22,17 +22,35 @@ export function preloadImage(
return new Promise((resolve, reject) => {
const image = document.createElement('img');
// Cleanup function to release resources and prevent memory leaks
const cleanup = () => {
// Clear event handlers to break circular references
image.onload = null;
image.onerror = null;
// Clear src to stop any pending load and allow GC
image.src = '';
};
if (useCORS) {
image.setAttribute('crossOrigin', '');
}
image.onload = () => resolve({
src,
isUsingCORS: useCORS
});
image.onload = () => {
cleanup();
resolve({
src,
isUsingCORS: useCORS
});
};
image.onerror = error => {
cleanup();
if (tryOnce) {
reject(error);
} else {
// Retry with different CORS mode
preloadImage(src, !useCORS, true)
.then(resolve)
.catch(reject);

View File

@@ -627,7 +627,8 @@ function _participantJoined({ participant }: { participant: IParticipant; }) {
pinned,
presence,
role,
sources
sources,
userContext
} = participant;
let { conference, id } = participant;
@@ -659,7 +660,8 @@ function _participantJoined({ participant }: { participant: IParticipant; }) {
pinned: pinned || false,
presence,
role: role || PARTICIPANT_ROLE.NONE,
sources
sources,
userContext
};
}

View File

@@ -41,6 +41,12 @@ export interface IParticipant {
role?: string;
sources?: Map<string, Map<string, ISourceInfo>>;
supportsRemoteControl?: boolean;
userContext?: IUserContext;
}
export interface IUserContext {
id?: string;
name?: string;
}
export interface ILocalParticipant extends IParticipant {

View File

@@ -84,7 +84,7 @@ const useStyles = makeStyles()(theme => {
...theme.typography.bodyLongBold,
borderRadius: theme.shape.borderRadius,
boxSizing: 'border-box',
color: theme.palette.text01,
color: theme.palette.actionButtonText,
cursor: 'pointer',
display: 'inline-block',
marginBottom: '16px',
@@ -95,20 +95,20 @@ const useStyles = makeStyles()(theme => {
border: 0,
'&.primary': {
background: theme.palette.action01,
color: theme.palette.text01,
background: theme.palette.prejoinActionButtonPrimary,
color: theme.palette.prejoinActionButtonPrimaryText,
'&:hover': {
backgroundColor: theme.palette.action01Hover
backgroundColor: theme.palette.prejoinActionButtonPrimaryHover
}
},
'&.secondary': {
background: theme.palette.action02,
color: theme.palette.text04,
background: theme.palette.prejoinActionButtonSecondary,
color: theme.palette.prejoinActionButtonSecondaryText,
'&:hover': {
backgroundColor: theme.palette.action02Hover
backgroundColor: theme.palette.prejoinActionButtonSecondaryHover
}
},
@@ -120,7 +120,7 @@ const useStyles = makeStyles()(theme => {
},
'&.disabled': {
background: theme.palette.disabled01,
background: theme.palette.prejoinActionButtonDisabled,
border: '1px solid #5E6D7A',
color: '#AFB6BC',
cursor: 'initial',

View File

@@ -109,7 +109,7 @@ const useStyles = makeStyles()(theme => {
position: 'absolute',
inset: '0 0 0 0',
display: 'flex',
backgroundColor: theme.palette.ui01,
backgroundColor: theme.palette.preMeetingBackground,
zIndex: 252,
'@media (max-width: 720px)': {
@@ -163,7 +163,7 @@ const useStyles = makeStyles()(theme => {
},
title: {
...theme.typography.heading4,
color: `${theme.palette.text01}!important`,
color: theme.palette.prejoinTitleText,
marginBottom: theme.spacing(3),
textAlign: 'center',
@@ -179,7 +179,7 @@ const useStyles = makeStyles()(theme => {
roomName: {
...theme.typography.heading5,
color: theme.palette.text01,
color: theme.palette.prejoinRoomNameText,
display: 'inline-block',
overflow: 'hidden',
textOverflow: 'ellipsis',

View File

@@ -6,7 +6,7 @@ const useStyles = makeStyles()(theme => {
return {
warning: {
bottom: 0,
color: theme.palette.text03,
color: theme.palette.prejoinRecordingWarningText,
display: 'flex',
justifyContent: 'center',
...theme.typography.bodyShortRegular,

View File

@@ -11,8 +11,8 @@ import { setUnsafeRoomConsent } from '../../actions.web';
const useStyles = makeStyles()(theme => {
return {
warning: {
backgroundColor: theme.palette.warning01,
color: theme.palette.text04,
backgroundColor: theme.palette.prejoinWarningBackground,
color: theme.palette.prejoinWarningText,
...theme.typography.bodyShortRegular,
padding: theme.spacing(3),
borderRadius: theme.shape.borderRadius,

View File

@@ -1,10 +1,10 @@
import React, { Component } from 'react';
import {
SectionList as ReactNativeSectionList,
SafeAreaView,
SectionListRenderItemInfo,
ViewStyle
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Item, Section } from '../../types';
@@ -76,6 +76,7 @@ export default class SectionList extends Component<IProps> {
override render() {
return (
<SafeAreaView
edges = { [ 'left', 'right' ] }
style = { styles.container as ViewStyle } >
<ReactNativeSectionList
ListEmptyComponent = { this.props.ListEmptyComponent }

View File

@@ -9,11 +9,11 @@ import { getSupportUrl } from '../../functions';
const useStyles = makeStyles()(theme => {
return {
dialog: {
backgroundColor: theme.palette.ui01,
border: `1px solid ${theme.palette.ui04}`,
backgroundColor: theme.palette.dialogBackground,
border: `1px solid ${theme.palette.inlineDialogBorder}`,
borderRadius: `${Number(theme.shape.borderRadius)}px`,
boxShadow: '0px 1px 2px rgba(41, 41, 41, 0.25)',
color: theme.palette.text01,
color: theme.palette.dialogText,
...theme.typography.bodyShortRegular,
padding: `${theme.spacing(3)} 10`,
'& .retry-button': {

View File

@@ -2,6 +2,7 @@ import { batch } from 'react-redux';
import { IStore } from '../../app/types';
import { CHAT_SIZE } from '../../chat/constants';
import { getCustomPanelWidth } from '../../custom-panel/functions';
import { getParticipantsPaneWidth } from '../../participants-pane/functions';
import {
@@ -24,6 +25,7 @@ import { ASPECT_RATIO_NARROW, ASPECT_RATIO_WIDE } from './constants';
* determine whether and how to render it.
*/
const REDUCED_UI_THRESHOLD = 300;
const WEB_REDUCED_UI_THRESHOLD = 320;
/**
* Indicates a resize of the window.
@@ -42,6 +44,7 @@ export function clientResized(clientWidth: number, clientHeight: number) {
if (navigator.product !== 'ReactNative') {
const state = getState();
const { reducedUIEnabled = true } = state['features/base/config'];
const { isOpen: isChatOpen, width } = state['features/chat'];
if (isChatOpen) {
@@ -49,6 +52,9 @@ export function clientResized(clientWidth: number, clientHeight: number) {
}
availableWidth -= getParticipantsPaneWidth(state);
availableWidth -= getCustomPanelWidth(state);
reducedUIEnabled && dispatch(setReducedUI(availableWidth, clientHeight));
}
batch(() => {
@@ -106,7 +112,10 @@ export function setAspectRatio(width: number, height: number) {
*/
export function setReducedUI(width: number, height: number) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const reducedUI = Math.min(width, height) < REDUCED_UI_THRESHOLD;
const threshold = navigator.product === 'ReactNative'
? REDUCED_UI_THRESHOLD
: WEB_REDUCED_UI_THRESHOLD;
const reducedUI = Math.max(width, height) < threshold;
if (reducedUI !== getState()['features/base/responsive-ui'].reducedUI) {
return dispatch({

View File

@@ -22,11 +22,11 @@ interface IProps {
const useStyles = makeStyles()(theme => {
return {
container: {
backgroundColor: theme.palette.uiBackground,
backgroundColor: theme.palette.tooltipBackground,
borderRadius: '3px',
padding: theme.spacing(2),
...theme.typography.labelRegular,
color: theme.palette.text01,
color: theme.palette.tooltipText,
position: 'relative',
'&.mounting-animation': {

View File

@@ -29,6 +29,7 @@ import {
TRACK_UPDATED,
TRACK_WILL_CREATE
} from './actionTypes';
import { toggleScreensharing } from './actions';
import {
createLocalTracksF,
getCameraFacingMode,
@@ -385,8 +386,22 @@ export function trackAdded(track: any) {
const mediaType = track.getVideoType() === VIDEO_TYPE.DESKTOP
? MEDIA_TYPE.SCREENSHARE
: track.getType();
let isReceivingData, noDataFromSourceNotificationInfo, participantId;
// Make screen share toggle off listen to MediaStreamTrack "ended" event
// when it's terminated via Android status bar chip.
if (navigator.product === 'ReactNative') {
const mediaStreamTrack = track?.getTrack?.();
if (mediaType === MEDIA_TYPE.SCREENSHARE) {
const onEnded = () => dispatch(toggleScreensharing(false));
mediaStreamTrack.addEventListener('ended', onEnded);
track._onEnded = onEnded;
}
}
if (local) {
// Reset the no data from src notification state when we change the track, as it's context is set
// on a per device basis.
@@ -568,6 +583,16 @@ export function trackRemoved(track: any): {
track.removeAllListeners(JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED);
track.removeAllListeners(JitsiTrackEvents.NO_DATA_FROM_SOURCE);
// Remove MediaStreamTrack "ended" event.
if (navigator.product === 'ReactNative') {
const mediaStreamTrack = track?.getTrack?.();
if (track._onEnded) {
mediaStreamTrack.removeEventListener('ended', track._onEnded);
delete track._onEnded;
}
}
return {
type: TRACK_REMOVED,
track: {

View File

@@ -33,7 +33,6 @@ import {
isUserInteractionRequiredForUnmute,
setTrackMuted
} from './functions';
import './subscriber';
/**
* Middleware that captures LIB_DID_DISPOSE and LIB_DID_INIT actions and,

View File

@@ -38,6 +38,7 @@ import {
import { ITrack, ITrackOptions } from './types';
import './middleware.any';
import './subscriber.web';
/**
* Middleware that captures LIB_DID_DISPOSE and LIB_DID_INIT actions and,
@@ -50,7 +51,7 @@ import './middleware.any';
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case TRACK_ADDED: {
const { local } = action.track;
const { local, jitsiTrack } = action.track;
// The devices list needs to be refreshed when no initial video permissions
// were granted and a local video track is added by umuting the video.
@@ -64,6 +65,16 @@ MiddlewareRegistry.register(store => next => action => {
if (participantId) {
logTracksForParticipant(store.getState()['features/base/tracks'], participantId, 'Track added');
// Fire participantMuted event for initial state of remote tracks
if (typeof action.track?.muted !== 'undefined' && jitsiTrack) {
const isVideoTrack = jitsiTrack.getType() !== MEDIA_TYPE.AUDIO;
const mediaType = isVideoTrack
? (jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP ? 'desktop' : 'video')
: 'audio';
APP.API.notifyParticipantMuted(participantId, action.track.muted, mediaType);
}
}
return result;
@@ -118,6 +129,16 @@ MiddlewareRegistry.register(store => next => action => {
// TODO Remove the following calls to APP.UI once components interested
// in track mute changes are moved into React and/or redux.
const { jitsiTrack } = action.track;
const participantID = jitsiTrack.getParticipantId();
const isVideoTrack = jitsiTrack.type !== MEDIA_TYPE.AUDIO;
const local = jitsiTrack.isLocal();
// Get old muted state BEFORE updating
const tracks = store.getState()['features/base/tracks'];
const oldTrack = tracks.find((t: ITrack) => t.jitsiTrack === jitsiTrack);
const oldMutedState = oldTrack?.muted;
const result = next(action);
const state = store.getState();
@@ -125,11 +146,6 @@ MiddlewareRegistry.register(store => next => action => {
return result;
}
const { jitsiTrack } = action.track;
const participantID = jitsiTrack.getParticipantId();
const isVideoTrack = jitsiTrack.type !== MEDIA_TYPE.AUDIO;
const local = jitsiTrack.isLocal();
if (isVideoTrack) {
if (local && !(jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP)) {
APP.conference.setVideoMuteStatus();
@@ -142,6 +158,15 @@ MiddlewareRegistry.register(store => next => action => {
if (typeof action.track?.muted !== 'undefined' && participantID && !local) {
logTracksForParticipant(store.getState()['features/base/tracks'], participantID, 'Track updated');
// Fire participantMuted event only if muted state actually changed
if (oldMutedState !== action.track.muted) {
const mediaType = isVideoTrack
? (jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP ? 'desktop' : 'video')
: 'audio';
APP.API.notifyParticipantMuted(participantID, action.track.muted, mediaType);
}
}
return result;

View File

@@ -1,40 +0,0 @@
import { isEqual, sortBy } from 'lodash-es';
import { MEDIA_TYPE } from '../media/constants';
import { getScreenshareParticipantIds } from '../participants/functions';
import StateListenerRegistry from '../redux/StateListenerRegistry';
import { isLocalTrackMuted } from './functions';
/**
* Notifies when the list of currently sharing participants changes.
*/
StateListenerRegistry.register(
/* selector */ state => getScreenshareParticipantIds(state),
/* listener */ (participantIDs, store, previousParticipantIDs) => {
if (typeof APP !== 'object') {
return;
}
if (!isEqual(sortBy(participantIDs), sortBy(previousParticipantIDs))) {
APP.API.notifySharingParticipantsChanged(participantIDs);
}
}
);
/**
* Notifies when the local video mute state changes.
*/
StateListenerRegistry.register(
/* selector */ state => isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO),
/* listener */ (muted, store, previousMuted) => {
if (typeof APP !== 'object') {
return;
}
if (muted !== previousMuted) {
APP.API.notifyVideoMutedStatusChanged(muted);
}
}
);

View File

@@ -0,0 +1,59 @@
import { isEqual, sortBy } from 'lodash-es';
// @ts-expect-error
import VideoLayout from '../../../../modules/UI/videolayout/VideoLayout';
import { getAutoPinSetting } from '../../video-layout/functions.any';
import { MEDIA_TYPE } from '../media/constants';
import { getLocalParticipant, getScreenshareParticipantIds } from '../participants/functions';
import StateListenerRegistry from '../redux/StateListenerRegistry';
import { isLocalTrackMuted } from './functions';
/**
* Notifies when the list of currently sharing participants changes.
*/
StateListenerRegistry.register(
/* selector */ state => getScreenshareParticipantIds(state),
/* listener */ (participantIDs, store, previousParticipantIDs) => {
if (getAutoPinSetting() && participantIDs !== previousParticipantIDs) {
const { participantId } = store.getState()['features/large-video'];
// Check if any new screenshare participants were added
const newParticipants = participantIDs.filter((id: string) => !previousParticipantIDs.includes(id));
// If the current large video participant is a new screensharer, update the display. This is needed when
// the track is created much later after the action for auto-pinning is dispatched. This usually happens in
// very large meetings if the screenshare was already ongoing when the participant joined. The track is
// signaled only after the receiver constraints with SS source id is processed by the bridge but the
// auto-pinning action is dispatched when the participant tile is created as soon as the presence is
// received.
if (participantId && newParticipants.includes(participantId)) {
VideoLayout.updateLargeVideo(participantId, true);
}
}
if (!isEqual(sortBy(participantIDs), sortBy(previousParticipantIDs))) {
APP.API.notifySharingParticipantsChanged(participantIDs);
}
}
);
/**
* Notifies when the local video mute state changes.
*/
StateListenerRegistry.register(
/* selector */ state => isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO),
/* listener */ (muted, store, previousMuted) => {
if (muted !== previousMuted) {
APP.API.notifyVideoMutedStatusChanged(muted);
// Also fire the participantMuted event for consistency
const localParticipant = getLocalParticipant(store.getState());
if (localParticipant) {
APP.API.notifyParticipantMuted(localParticipant.id, muted, 'video');
}
}
}
);

View File

@@ -1,3 +1,4 @@
/* eslint-disable @stylistic/no-multi-spaces */
// Mapping between the token used and the color
export const colorMap = {
// ----- Surfaces -----
@@ -7,7 +8,7 @@ export const colorMap = {
// - JitsiMeetView.java
uiBackground: 'surface01',
// Container backgrounds
// Container backgrounds (legacy tokens)
ui01: 'surface02',
ui02: 'surface03',
ui03: 'ui02',
@@ -47,6 +48,426 @@ export const colorMap = {
// Focus
focus01: 'focus01',
// ----- Semantic Tokens (component-based, backwards compatible) -----
// Dialog/Modal Components
dialogBackground: 'surface02', // Main dialog background (same as ui01)
dialogOverlay: 'surface03', // Overlay/backdrop (same as ui02)
dialogBorder: 'ui02', // Dialog borders
dialogText: 'textColor01', // Primary dialog text (same as text01)
dialogSecondaryText: 'textColor02', // Secondary dialog text (same as text02)
// Large Video
largeVideoBackground: 'surface03', // Main video area background (same as ui02)
largeVideoPlaceholder: 'surface03', // Placeholder when no video (same as ui02)
// Filmstrip
filmstripBackground: 'surface03', // Filmstrip container background (same as ui02)
filmstripBackgroundHover: 'uiBackground', // Filmstrip background on hover/focus
filmstripDragHandle: 'icon02', // Filmstrip resize drag handle color
filmstripDragHandleHover: 'icon01', // Filmstrip resize drag handle hover color
thumbnailBackground: 'surface03', // Individual thumbnail background (same as ui02)
thumbnailBorder: 'ui03', // Thumbnail borders (same as ui03)
thumbnailHover: 'hover05', // Thumbnail hover state (same as action03Hover)
thumbnailTintBackground: 'uiBackground', // Thumbnail tint overlay background
thumbnailRaisedHandIcon: 'uiBackground', // Thumbnail raised hand indicator icon
thumbnailVideoBackground: 'uiBackground', // Thumbnail video/placeholder background
// Chat
chatBackground: 'surface02', // Chat panel background (same as ui01)
chatBackdrop: 'ui04', // Chat screen background (same as ui10)
chatEmptyText: 'ui03', // Empty component text
chatInputBackground: 'surface03', // Chat input field background (same as ui02)
chatInputBorder: 'surface03', // Chat input border (same as ui02)
chatLink: 'action01', // Chat link color (same as link01)
chatLobbyMessageBubble: 'support06', // Lobby message bubble background
chatLobbyMessageNotice: 'surface01', // Lobby message notice text
chatLobbyRecipientContainer: 'support06', // Lobby recipient container background
chatMessageLocal: 'surface05', // Local participant message bubble (same as ui04)
chatMessagePrivate: 'support05', // Private/DM message bubble
chatMessageRemote: 'surface03', // Remote participant message bubble (same as ui02)
chatMessageText: 'textColor01', // Chat message text
chatPrivateNotice: 'textColor02', // Private message notice text
chatRecipientCancelIcon: 'icon01', // Recipient cancel icon color
chatRecipientContainer: 'support05', // Recipient container background
chatRecipientText: 'textColor01', // Recipient text color
chatReplyIcon: 'icon01', // Reply icon color
chatSenderName: 'textColor02', // Sender display name color
chatTimestamp: 'ui03', // Chat timestamp text
// Toolbox/Toolbar
toolboxBackground: 'surface02', // Main toolbox background
drawerBackground: 'surface02', // Drawer/side panel background
toolboxIconHover: 'surface05', // Toolbox icon hover background
toolboxIconActive: 'ui02', // Toolbox icon active/pressed background
toolboxIconToggled: 'ui02', // Toolbox icon toggled background
toolbarButton: 'action01', // Toolbar button color
toolbarButtonHover: 'hover01', // Toolbar button hover (same as action01Hover)
toolbarButtonActive: 'active01', // Toolbar button active/pressed state
toolbarIcon: 'icon01', // Toolbar icon color
toolbarIconHover: 'icon01', // Toolbar icon hover state
toolbarIconActive: 'action01', // Toolbar icon active/toggled state
// Overflow Menu (More Actions)
overflowMenuBackground: 'surface02', // Overflow menu background
overflowMenuBorder: 'surface05', // Overflow menu border
overflowMenuItemText: 'text01', // Overflow menu item text
overflowMenuItemIcon: 'text01', // Overflow menu item icon
overflowMenuItemHover: 'surface03', // Overflow menu item hover background
overflowMenuItemDisabled: 'text03', // Overflow menu item disabled text/icon
overflowMenuSeparator: 'ui03', // Overflow menu group separator
// Participants Pane
participantsPaneBackground: 'surface02', // Participants list background
participantItemBackground: 'surface03', // Individual participant item background
participantItemHover: 'hover05', // Participant item hover
participantItemBorder: 'ui02', // Participant item border
participantCounterBadge: 'ui02', // Participant counter badge background
participantCounterText: 'text01', // Participant counter text
participantModeratorLabel: 'text03', // Moderator label text
participantSectionText: 'text02', // Section header/subtitle text
participantActionButton: 'action02', // Action button background
participantLinkText: 'link01', // Link text color
participantWarningText: 'warning02', // Warning text color
participantRaisedHandBadge: 'warning02', // Raised hand indicator background
participantRaisedHandIcon: 'icon04', // Raised hand icon color
// Lobby
lobbyBackground: 'surface02', // Lobby screen background (same as ui01)
lobbyPreviewBackground: 'surface03', // Video preview background (same as ui02)
// Speaker Stats
speakerStatsBackground: 'surface02', // Speaker stats panel background
speakerStatsRowBackground: 'ui02', // Individual stat row background
speakerStatsRowAlternate: 'ui03', // Alternate row background
speakerStatsBorder: 'surface03', // Speaker stats borders
speakerStatsHeaderBackground: 'ui09', // Header background
speakerStatsSearchBackground: 'field01', // Search input background
speakerStatsSearchBorder: 'ui05', // Search input border
speakerStatsSearchText: 'text01', // Search input text
speakerStatsSearchPlaceholder: 'text03', // Search placeholder
speakerStatsSearchIcon: 'icon03', // Search icon color
speakerStatsLabelText: 'text03', // Label text color
speakerStatsSuccessBar: 'success02', // Success/progress bar
speakerStatsAvatarLeft: 'surface05', // Avatar background for participants who left
// Pre-meeting/Prejoin
preMeetingBackground: 'surface02', // Pre-meeting screen container background
preMeetingPreview: 'ui01', // Video preview in pre-meeting
prejoinDialogBackground: 'uiBackground', // Prejoin dialog background
prejoinDialogDelimiter: 'ui03', // Prejoin dialog delimiter line
prejoinDialogDelimiterText: 'text01', // Prejoin dialog delimiter text
prejoinTitleText: 'text01', // Prejoin title text color
prejoinRoomNameText: 'text01', // Prejoin room name text color
prejoinWarningBackground: 'warning01', // Warning banner background
prejoinWarningText: 'text04', // Warning banner text
prejoinRecordingWarningText: 'text03', // Recording warning text
prejoinActionButtonPrimary: 'action01', // Primary action button
prejoinActionButtonPrimaryHover: 'action01Hover', // Primary button hover
prejoinActionButtonPrimaryText: 'text01', // Primary button text
prejoinActionButtonSecondary: 'action02', // Secondary action button
prejoinActionButtonSecondaryHover: 'action02Hover', // Secondary button hover
prejoinActionButtonSecondaryText: 'text04', // Secondary button text
prejoinActionButtonDanger: 'actionDanger', // Danger button (leave)
prejoinActionButtonDisabled: 'disabled01', // Disabled button
prejoinCountryPickerBackground: 'ui01', // Country picker background
prejoinCountryPickerBorder: 'ui03', // Country picker border
prejoinCountryPickerText: 'text01', // Country picker text
prejoinCountryRowBackground: 'action03', // Country row background
prejoinCountryRowHover: 'action03Hover', // Country row hover
prejoinDeviceStatusOk: 'success01', // Device status OK background
prejoinDeviceStatusWarning: 'warning01', // Device status warning background
prejoinDeviceStatusText: 'uiBackground', // Device status text
// Notifications
notificationBackground: 'ui04', // Notification background
notificationNormalIcon: 'action01', // Normal notification icon
notificationError: 'iconError', // Error notification icon
notificationSuccess: 'success01', // Success notification icon
notificationWarning: 'warning01', // Warning notification icon
notificationText: 'text04', // Notification text
notificationActionText: 'action01', // Notification action text
notificationErrorText: 'textError', // Error notification text
notificationActionFocus: 'action01', // Notification action focus outline
notificationCloseIcon: 'icon04', // Notification close icon
// Forms/Inputs
inputBackground: 'field01', // Input field background
inputBorder: 'surface03', // Input field border (same as ui02)
inputText: 'textColor01', // Input field text (same as text01)
inputPlaceholder: 'textColor02', // Input placeholder text (same as text02)
// Breakout Rooms
breakoutRoomBackground: 'ui01', // Breakout rooms panel background
breakoutRoomItemBackground: 'surface03', // Individual breakout room background
breakoutRoomArrowBackground: 'ui02', // Breakout room arrow container background
// Settings
settingsBackground: 'ui01', // Settings dialog background
settingsSectionBackground: 'ui02', // Settings section background
settingsTabText: 'text01', // Settings tab text
settingsShortcutKey: 'surface05', // Keyboard shortcut key background
settingsVideoPreviewBorder: 'action01Hover', // Video preview border (selected)
settingsErrorIcon: 'iconError', // Error icon color
// Visitors
visitorsCountBadge: 'warning02', // Visitors count badge background
visitorsCountText: 'uiBackground', // Visitors count badge text
visitorsCountIcon: 'icon04', // Visitors count icon
visitorsQueueBackground: 'ui01', // Visitors queue panel background
visitorsQueueText: 'text01', // Visitors queue text
visitorsArrowBackground: 'ui02', // Visitors arrow container background
// Welcome Page
welcomeBackground: 'surface01', // Welcome page background (same as uiBackground)
welcomeCard: 'ui01', // Welcome page tab bar background
welcomeTabActive: 'icon01', // Welcome page active tab icon
welcomeTabInactive: 'icon03', // Welcome page inactive tab icon
// ----- Form Components -----
// Input
inputLabel: 'text01', // Input field label text
inputFieldBackground: 'ui02', // Input field background color
inputFieldBorder: 'ui02', // Input field border color
inputFieldText: 'text01', // Input field text color
inputFieldPlaceholder: 'text02', // Input field placeholder text
inputFieldDisabled: 'text03', // Input field disabled text
inputFieldError: 'textError', // Input field error state
inputFieldFocus: 'focus01', // Input field focus outline
inputClearButton: 'transparent', // Input clear button background
inputBottomLabel: 'text02', // Input bottom label text
inputBottomLabelError: 'textError', // Input bottom label error text
// Select
selectLabel: 'text01', // Select label text
selectBackground: 'ui02', // Select background color
selectText: 'text01', // Select text color
selectDisabled: 'text03', // Select disabled text
selectError: 'textError', // Select error state
selectFocus: 'focus01', // Select focus outline
selectIcon: 'icon01', // Select dropdown icon (enabled)
selectIconDisabled: 'icon03', // Select dropdown icon (disabled)
selectBottomLabel: 'text02', // Select bottom label text
selectBottomLabelError: 'textError', // Select bottom label error text
// MultiSelect
multiSelectBackground: 'ui01', // MultiSelect dropdown background
multiSelectBorder: 'ui04', // MultiSelect dropdown border
multiSelectItemText: 'text01', // MultiSelect item text
multiSelectItemHover: 'ui02', // MultiSelect item hover background
multiSelectItemDisabled: 'text03', // MultiSelect disabled item text
// Checkbox
checkboxLabel: 'text01', // Checkbox label text
checkboxBorder: 'icon03', // Checkbox border color
checkboxChecked: 'action01', // Checkbox checked background
checkboxDisabledBackground: 'ui02', // Checkbox disabled background
checkboxDisabledBorder: 'surface05', // Checkbox disabled border
checkboxDisabledChecked: 'ui02', // Checkbox disabled checked background
checkboxIcon: 'icon01', // Checkbox check icon (enabled)
checkboxIconDisabled: 'icon03', // Checkbox check icon (disabled)
// Switch
switchBackground: 'ui01', // Switch background (unchecked)
switchBackgroundOn: 'action01', // Switch background (checked)
switchToggle: 'ui04', // Switch toggle circle
switchToggleDisabled: 'ui03', // Switch toggle circle (disabled)
switchFocus: 'focus01', // Switch focus outline
// Tabs
tabText: 'text02', // Tab text (unselected)
tabTextHover: 'text01', // Tab text (hover)
tabTextSelected: 'text01', // Tab text (selected)
tabTextDisabled: 'text03', // Tab text (disabled)
tabBorder: 'ui05', // Tab bottom border (unselected)
tabBorderHover: 'ui10', // Tab bottom border (hover)
tabBorderSelected: 'action01', // Tab bottom border (selected)
tabBorderDisabled: 'ui05', // Tab bottom border (disabled)
tabFocus: 'focus01', // Tab focus outline
tabBadgeBackground: 'warning01', // Tab count badge background
tabBadgeText: 'text04', // Tab count badge text
// ListItem
listItemText: 'text01', // List item text color
listItemBackground: 'ui01', // List item default background
listItemHover: 'surface03', // List item hover background
listItemHighlighted: 'surface03', // List item highlighted/active background
listItemBoxShadow: 'ui02', // List item actions box shadow color
// ClickableIcon
clickableIconBackground: 'transparent', // Clickable icon background
clickableIconHover: 'ui02', // Clickable icon hover background
clickableIconActive: 'ui03', // Clickable icon active/pressed background
clickableIconFocus: 'focus01', // Clickable icon focus outline
// Label
labelBackground: 'ui04', // Label default background
labelText: 'text01', // Label text color
labelWhiteBackground: 'ui08', // Label white variant background
labelWhiteText: 'text04', // Label white variant text
labelWhiteIcon: 'surface01', // Label white variant icon
// Tooltip
tooltipBackground: 'uiBackground', // Tooltip background color
tooltipText: 'text01', // Tooltip text color
// Polls
pollsBackground: 'surface03', // Poll container background
pollsTitle: 'text01', // Poll title text
pollsSubtitle: 'text02', // Poll subtitle/secondary text
pollsQuestion: 'text01', // Poll question text
pollsAnswer: 'text01', // Poll answer text
pollsBarBackground: 'ui03', // Poll results bar background
pollsBarPercentage: 'text01', // Poll results percentage text
pollsVotersBackground: 'ui03', // Poll voters list background
pollsVotersText: 'text01', // Poll voters list text
pollsSeparator: 'ui03', // Poll section separator
pollsSendLabel: 'text01', // Poll send button label
pollsSendDisabled: 'text03', // Poll send button disabled label
pollsPaneBackground: 'ui01', // Poll pane container background
pollsPaneBorder: 'ui05', // Poll pane border
pollsCreateBackground: 'uiBackground', // Poll create dialog background
pollsCreateBorder: 'ui06', // Poll create dialog border
// Video Quality / Slider
sliderKnob: 'text01', // Slider knob/thumb color
sliderTrack: 'text03', // Slider track color
sliderFocus: 'ui06', // Slider focus outline
videoQualityText: 'text01', // Video quality dialog text
videoQualityBackground: 'surface02', // Video quality dialog background
// Connection Indicator
connectionIndicatorLost: 'ui05', // Connection indicator lost status
connectionIndicatorOther: 'action01', // Connection indicator other status
// Device Selection
deviceSelectorBackground: 'ui01', // Device selector background
deviceSelectorText: 'text01', // Device selector text
deviceSelectorBorder: 'ui03', // Device selector border
deviceSelectorTextBackground: 'uiBackground', // Device selector text-only background
deviceSelectorVideoPreview: 'uiBackground', // Device selector video preview background
// Invite / Dial-in
dialInBackground: 'ui01', // Dial-in summary background
dialInText: 'text01', // Dial-in summary text
dialInSecondaryText: 'text02', // Dial-in summary secondary text
// Reactions
reactionsMenuBackground: 'ui01', // Reactions menu background
reactionsMenuBorder: 'ui02', // Reactions menu border
// Recording / Live Stream
recordingBackground: 'ui01', // Recording panel background
recordingText: 'text01', // Recording panel text
recordingHighlightButton: 'ui04', // Recording highlight button background
recordingHighlightButtonDisabled: 'text02', // Recording highlight button disabled background
recordingHighlightButtonIcon: 'ui02', // Recording highlight button icon color
recordingHighlightButtonIconDisabled: 'text03', // Recording highlight button disabled icon color
recordingNotificationText: 'surface01', // Recording notification text color
recordingNotificationAction: 'action01', // Recording notification action color
// Virtual Background
virtualBackgroundBackground: 'ui01', // Virtual background picker background
virtualBackgroundText: 'text01', // Virtual background picker text
virtualBackgroundBorder: 'ui03', // Virtual background item border
virtualBackgroundPreview: 'uiBackground', // Virtual background preview container
// Conference / Meeting
conferenceTimerText: 'text01', // Conference timer text
conferenceSubjectText: 'text01', // Conference subject text
conferenceNoticeBackground: 'uiBackground', // Conference notice background
conferenceNoticeText: 'text01', // Conference notice text
conferenceRaisedHandLabelText: 'uiBackground', // Raised hands count label text
conferenceRaisedHandLabelIcon: 'surface01', // Raised hands count label icon
// Subtitle Messages
subtitleMessageBackground: 'ui02', // Subtitle message background
subtitleMessageText: 'text01', // Subtitle message text
subtitleMessageSender: 'text02', // Subtitle message sender name
subtitleMessageTime: 'text03', // Subtitle message timestamp
// Language Selector
languageSelectorBackground: 'ui01', // Language selector background
languageSelectorText: 'text01', // Language selector text
languageSelectorHover: 'ui02', // Language selector item hover
// Video Menu
videoMenuBackground: 'ui01', // Video menu background
videoMenuBorder: 'ui02', // Video menu border
videoMenuText: 'text01', // Video menu text
videoMenuSliderBackground: 'ui03', // Video menu slider background
// File Sharing
fileSharingBackground: 'ui01', // File sharing panel background
fileSharingText: 'text01', // File sharing text
fileSharingEmptyText: 'text02', // File sharing empty state text
fileSharingEmptyIcon: 'icon03', // File sharing empty state icon
fileSharingItemBackground: 'surface03', // File sharing item background
fileSharingItemBorder: 'ui02', // File sharing item hover/border
// Gifs
gifsBackground: 'ui01', // GIFs panel background
gifsText: 'text01', // GIFs panel text
// Whiteboard
whiteboardBackground: 'ui03', // Whiteboard background
whiteboardText: 'text01', // Whiteboard panel text
// Salesforce
salesforceSearchBackground: 'field01', // Salesforce search input background
salesforceSearchBorder: 'ui05', // Salesforce search input border
salesforceSearchText: 'dialogText', // Salesforce search input text
salesforceSearchPlaceholder: 'text03', // Salesforce search placeholder
salesforceSearchIcon: 'text03', // Salesforce search icon
// Security Dialog
securityDialogBackground: 'ui01', // Security dialog background
securityDialogText: 'text01', // Security dialog text
securityDialogSecondaryText: 'text02', // Security dialog secondary text
securityDialogBorder: 'ui07', // Security dialog border color
// Deep Linking
deepLinkingBackground: 'ui01', // Deep linking page content pane background
deepLinkingBorder: 'ui03', // Deep linking page content pane border
deepLinkingText: 'text01', // Deep linking page text
deepLinkingSeparator: 'ui03', // Deep linking separator line
deepLinkingLabelText: 'text02', // Deep linking label text
deepLinkingLink: 'link01', // Deep linking link color
// Base React Components
baseReactBackground: 'ui01', // Base react component background
baseReactText: 'text01', // Base react component text
baseReactBorder: 'ui03', // Base react component border
// Inline Dialog
inlineDialogBackground: 'ui01', // Inline dialog background
inlineDialogText: 'text01', // Inline dialog text
inlineDialogBorder: 'ui02', // Inline dialog border
// Pre-meeting / Action Button
actionButtonBackground: 'ui01', // Action button background (different from main buttons)
actionButtonText: 'text01', // Action button text
actionButtonBorder: 'ui03', // Action button border
// Audio Route Picker
audioRoutePickerBackground: 'ui01', // Audio route picker background
audioRoutePickerText: 'text01', // Audio route picker text
audioRoutePickerBorder: 'ui03', // Audio route picker border
// Etherpad
etherpadBackground: 'ui01', // Etherpad panel background
etherpadText: 'text01', // Etherpad panel text
// Display Name
displayNameBackground: 'ui01', // Display name background
displayNameText: 'text01', // Display name text
// Car Mode
carModeBackground: 'ui01', // Car mode background
carModeText: 'text01', // Car mode text
carModeBorder: 'ui03', // Car mode border
// ----- Links -----
link01: 'action01',

View File

@@ -14,7 +14,7 @@ const useStyles = makeStyles()(theme => {
width: '100%',
height: '100%',
position: 'fixed',
color: theme.palette.text01,
color: theme.palette.dialogText,
...theme.typography.bodyLongRegular,
top: 0,
left: 0,
@@ -49,13 +49,13 @@ const useStyles = makeStyles()(theme => {
height: '100%',
top: 0,
left: 0,
backgroundColor: theme.palette.ui02,
backgroundColor: theme.palette.dialogOverlay,
opacity: 0.75
},
modal: {
backgroundColor: theme.palette.ui01,
border: `1px solid ${theme.palette.ui03}`,
backgroundColor: theme.palette.dialogBackground,
border: `1px solid ${theme.palette.dialogBorder}`,
boxShadow: '0px 4px 25px 4px rgba(20, 20, 20, 0.6)',
borderRadius: `${theme.shape.borderRadius}px`,
display: 'flex',

View File

@@ -47,7 +47,7 @@ const useStyles = makeStyles()(theme => {
return {
formControl: {
...theme.typography.bodyLongRegular,
color: theme.palette.text01,
color: theme.palette.checkboxLabel,
display: 'inline-flex',
alignItems: 'center',
@@ -76,10 +76,10 @@ const useStyles = makeStyles()(theme => {
backgroundColor: 'transparent',
margin: '3px',
font: 'inherit',
color: theme.palette.icon03,
color: theme.palette.checkboxBorder,
width: '18px',
height: '18px',
border: `2px solid ${theme.palette.icon03}`,
border: `2px solid ${theme.palette.checkboxBorder}`,
borderRadius: '3px',
display: 'grid',
@@ -90,7 +90,7 @@ const useStyles = makeStyles()(theme => {
width: '18px',
height: '18px',
opacity: 0,
backgroundColor: theme.palette.action01,
backgroundColor: theme.palette.checkboxChecked,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
@@ -104,11 +104,11 @@ const useStyles = makeStyles()(theme => {
},
'&:disabled': {
backgroundColor: theme.palette.ui03,
borderColor: theme.palette.ui04,
backgroundColor: theme.palette.checkboxDisabledBackground,
borderColor: theme.palette.checkboxDisabledBorder,
'&::before': {
backgroundColor: theme.palette.ui04
backgroundColor: theme.palette.checkboxDisabledChecked
}
},
@@ -173,7 +173,7 @@ const Checkbox = ({
<Icon
aria-hidden = { true }
className = 'checkmark'
color = { disabled ? theme.palette.icon03 : theme.palette.icon01 }
color = { disabled ? theme.palette.checkboxIconDisabled : theme.palette.checkboxIcon }
size = { 18 }
src = { IconCheck } />
</div>

View File

@@ -16,22 +16,22 @@ const useStyles = makeStyles()(theme => {
return {
button: {
padding: '2px',
backgroundColor: theme.palette.action03,
backgroundColor: theme.palette.clickableIconBackground,
border: 0,
outline: 0,
borderRadius: `${theme.shape.borderRadius}px`,
'&:hover': {
backgroundColor: theme.palette.ui02
backgroundColor: theme.palette.clickableIconHover
},
'&.focus-visible': {
outline: 0,
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
boxShadow: `0px 0px 0px 2px ${theme.palette.clickableIconFocus}`
},
'&:active': {
backgroundColor: theme.palette.ui03
backgroundColor: theme.palette.clickableIconActive
},
'&.is-mobile': {

View File

@@ -133,11 +133,11 @@ const MAX_HEIGHT = 400;
const useStyles = makeStyles()(theme => {
return {
contextMenu: {
backgroundColor: theme.palette.ui01,
border: `1px solid ${theme.palette.ui04}`,
backgroundColor: theme.palette.overflowMenuBackground,
border: `1px solid ${theme.palette.overflowMenuBorder}`,
borderRadius: `${Number(theme.shape.borderRadius)}px`,
boxShadow: '0px 1px 2px rgba(41, 41, 41, 0.25)',
color: theme.palette.text01,
color: theme.palette.overflowMenuItemText,
...theme.typography.bodyShortRegular,
marginTop: '48px',
position: 'absolute',

View File

@@ -122,11 +122,11 @@ const useStyles = makeStyles()(theme => {
},
'&:hover': {
backgroundColor: theme.palette.ui02
backgroundColor: theme.palette.overflowMenuItemHover
},
'&:active': {
backgroundColor: theme.palette.ui03
backgroundColor: theme.palette.overflowMenuItemHover
},
'&.focus-visible': {
@@ -137,7 +137,7 @@ const useStyles = makeStyles()(theme => {
selected: {
borderLeft: `3px solid ${theme.palette.action01Hover}`,
paddingLeft: '13px',
backgroundColor: theme.palette.ui02
backgroundColor: theme.palette.overflowMenuItemHover
},
contextMenuItemDisabled: {
@@ -146,19 +146,19 @@ const useStyles = makeStyles()(theme => {
contextMenuItemIconDisabled: {
'& svg': {
fill: `${theme.palette.text03} !important`
fill: `${theme.palette.overflowMenuItemDisabled} !important`
}
},
contextMenuItemLabelDisabled: {
color: theme.palette.text03,
color: theme.palette.overflowMenuItemDisabled,
'&:hover': {
background: 'none'
},
'& svg': {
fill: theme.palette.text03
fill: theme.palette.overflowMenuItemDisabled
}
},
@@ -168,13 +168,13 @@ const useStyles = makeStyles()(theme => {
contextMenuItemIcon: {
'& svg': {
fill: theme.palette.icon01
fill: theme.palette.overflowMenuItemIcon
}
},
text: {
...theme.typography.bodyShortRegular,
color: theme.palette.text01
color: theme.palette.overflowMenuItemText
},
drawerText: {

View File

@@ -30,7 +30,7 @@ const useStyles = makeStyles()(theme => {
},
'& + &:not(:empty)': {
borderTop: `1px solid ${theme.palette.ui03}`
borderTop: `1px solid ${theme.palette.overflowMenuSeparator}`
},
'&:first-of-type': {

View File

@@ -24,7 +24,7 @@ const useStyles = makeStyles()(theme => {
},
title: {
color: theme.palette.text01,
color: theme.palette.dialogText,
...theme.typography.heading5,
margin: 0,
padding: 0

View File

@@ -42,7 +42,7 @@ const useStyles = makeStyles()(theme => {
flexDirection: 'column',
minWidth: '211px',
maxWidth: '100%',
borderRight: `1px solid ${theme.palette.ui03}`,
borderRight: `1px solid ${theme.palette.dialogBorder}`,
[`@media (max-width: ${MOBILE_BREAKPOINT}px)`]: {
width: '100%',
@@ -70,7 +70,7 @@ const useStyles = makeStyles()(theme => {
title: {
...theme.typography.heading5,
color: `${theme.palette.text01} !important`,
color: `${theme.palette.dialogText} !important`,
margin: 0,
padding: 0
},
@@ -301,7 +301,7 @@ const DialogWithTabs = ({
}
return null;
}, [ selectedTabIndex, tabStates ]);
}, [ selectedTabIndex, tabStates, tabs ]);
const closeIcon = useMemo(() => (
<ClickableIcon

View File

@@ -49,7 +49,7 @@ const useStyles = makeStyles()(theme => {
},
label: {
color: theme.palette.text01,
color: theme.palette.inputLabel,
...theme.typography.bodyShortRegular,
marginBottom: theme.spacing(2),
@@ -64,9 +64,9 @@ const useStyles = makeStyles()(theme => {
},
input: {
backgroundColor: theme.palette.ui03,
background: theme.palette.ui03,
color: theme.palette.text01,
backgroundColor: theme.palette.inputFieldBackground,
background: theme.palette.inputFieldBackground,
color: theme.palette.inputFieldText,
...theme.typography.bodyShortRegular,
padding: '10px 16px',
borderRadius: theme.shape.borderRadius,
@@ -76,16 +76,16 @@ const useStyles = makeStyles()(theme => {
width: '100%',
'&::placeholder': {
color: theme.palette.text02
color: theme.palette.inputFieldPlaceholder
},
'&:focus': {
outline: 0,
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
boxShadow: `0px 0px 0px 2px ${theme.palette.inputFieldFocus}`
},
'&:disabled': {
color: theme.palette.text03
color: theme.palette.inputFieldDisabled
},
'&.is-mobile': {
@@ -99,7 +99,7 @@ const useStyles = makeStyles()(theme => {
},
'&.error': {
boxShadow: `0px 0px 0px 2px ${theme.palette.textError}`
boxShadow: `0px 0px 0px 2px ${theme.palette.inputFieldError}`
},
'&.clearable-input': {
paddingRight: '46px'
@@ -107,12 +107,12 @@ const useStyles = makeStyles()(theme => {
},
'input::-webkit-outer-spin-button, input::-webkit-inner-spin-button': {
'-webkit-appearance': 'none',
WebkitAppearance: 'none',
margin: 0
},
'input[type=number]': {
'-moz-appearance': 'textfield'
MozAppearance: 'textfield'
},
icon: {
@@ -131,7 +131,7 @@ const useStyles = makeStyles()(theme => {
right: '16px',
top: '10px',
cursor: 'pointer',
backgroundColor: theme.palette.action03,
backgroundColor: theme.palette.inputClearButton,
border: 0,
padding: 0
},
@@ -139,14 +139,14 @@ const useStyles = makeStyles()(theme => {
bottomLabel: {
marginTop: theme.spacing(2),
...theme.typography.labelRegular,
color: theme.palette.text02,
color: theme.palette.inputBottomLabel,
'&.is-mobile': {
...theme.typography.bodyShortRegular
},
'&.error': {
color: theme.palette.textError
color: theme.palette.inputBottomLabelError
}
}
};

View File

@@ -83,7 +83,7 @@ const useStyles = makeStyles()(theme => {
return {
container: {
alignItems: 'center',
color: theme.palette.text01,
color: theme.palette.listItemText,
display: 'flex',
...theme.typography.bodyShortBold,
margin: `0 -${participantsPaneTheme.panePadding}px`,
@@ -93,7 +93,7 @@ const useStyles = makeStyles()(theme => {
minHeight: '40px',
'&:hover, &:focus-within': {
backgroundColor: theme.palette.ui02,
backgroundColor: theme.palette.listItemHover,
'& .indicators': {
display: 'none'
@@ -103,8 +103,8 @@ const useStyles = makeStyles()(theme => {
display: 'flex',
position: 'relative',
top: 'auto',
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
backgroundColor: theme.palette.ui02
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`,
backgroundColor: theme.palette.listItemHover
}
},
@@ -115,14 +115,14 @@ const useStyles = makeStyles()(theme => {
},
highlighted: {
backgroundColor: theme.palette.ui02,
backgroundColor: theme.palette.listItemHighlighted,
'& .actions': {
display: 'flex',
position: 'relative',
top: 'auto',
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
backgroundColor: theme.palette.ui02
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`,
backgroundColor: theme.palette.listItemHighlighted
}
},
@@ -170,20 +170,20 @@ const useStyles = makeStyles()(theme => {
actionsContainer: {
position: 'absolute',
top: '-1000px',
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
backgroundColor: theme.palette.ui02
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`,
backgroundColor: theme.palette.listItemHover
},
actionsPermanent: {
display: 'flex',
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui01}`,
backgroundColor: theme.palette.ui01
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBackground}`,
backgroundColor: theme.palette.listItemBackground
},
actionsVisible: {
display: 'flex',
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
backgroundColor: theme.palette.ui02
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`,
backgroundColor: theme.palette.listItemHighlighted
}
};
});

View File

@@ -38,8 +38,8 @@ const useStyles = makeStyles()(theme => {
},
marginTop: theme.spacing(2),
width: '100%',
backgroundColor: theme.palette.ui01,
border: `1px solid ${theme.palette.ui04}`,
backgroundColor: theme.palette.multiSelectBackground,
border: `1px solid ${theme.palette.multiSelectBorder}`,
borderRadius: `${Number(theme.shape.borderRadius)}px`,
...theme.typography.bodyShortRegular,
zIndex: 2,
@@ -57,7 +57,7 @@ const useStyles = makeStyles()(theme => {
inlineSize: 'calc(100% - 38px)',
overflowWrap: 'break-word',
marginLeft: theme.spacing(2),
color: theme.palette.text01,
color: theme.palette.multiSelectItemText,
'&.with-remove': {
// 60px because of the icon before the content and the remove button
inlineSize: 'calc(100% - 60px)',
@@ -76,15 +76,15 @@ const useStyles = makeStyles()(theme => {
cursor: 'pointer',
padding: `10px ${theme.spacing(3)}`,
'&:hover': {
backgroundColor: theme.palette.ui02
backgroundColor: theme.palette.multiSelectItemHover
}
},
'&.disabled': {
cursor: 'not-allowed',
'&:hover': {
backgroundColor: theme.palette.ui01
backgroundColor: theme.palette.multiSelectBackground
},
color: theme.palette.text03
color: theme.palette.multiSelectItemDisabled
}
},
errorMessage: {

View File

@@ -70,7 +70,7 @@ const useStyles = makeStyles()(theme => {
},
label: {
color: theme.palette.text01,
color: theme.palette.selectLabel,
...theme.typography.bodyShortRegular,
marginBottom: theme.spacing(2),
@@ -84,11 +84,11 @@ const useStyles = makeStyles()(theme => {
},
select: {
backgroundColor: theme.palette.ui03,
backgroundColor: theme.palette.selectBackground,
borderRadius: `${theme.shape.borderRadius}px`,
width: '100%',
...theme.typography.bodyShortRegular,
color: theme.palette.text01,
color: theme.palette.selectText,
padding: '10px 16px',
paddingRight: '42px',
border: 0,
@@ -99,11 +99,11 @@ const useStyles = makeStyles()(theme => {
'&:focus': {
outline: 0,
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
boxShadow: `0px 0px 0px 2px ${theme.palette.selectFocus}`
},
'&:disabled': {
color: theme.palette.text03
color: theme.palette.selectDisabled
},
'&.is-mobile': {
@@ -113,7 +113,7 @@ const useStyles = makeStyles()(theme => {
},
'&.error': {
boxShadow: `0px 0px 0px 2px ${theme.palette.textError}`
boxShadow: `0px 0px 0px 2px ${theme.palette.selectError}`
}
},
@@ -132,14 +132,14 @@ const useStyles = makeStyles()(theme => {
bottomLabel: {
marginTop: theme.spacing(2),
...theme.typography.labelRegular,
color: theme.palette.text02,
color: theme.palette.selectBottomLabel,
'&.is-mobile': {
...theme.typography.bodyShortRegular
},
'&.error': {
color: theme.palette.textError
color: theme.palette.selectBottomLabelError
}
}
};
@@ -180,7 +180,7 @@ const Select = ({
</select>
<Icon
className = { cx(classes.icon, isMobile && 'is-mobile') }
color = { disabled ? theme.palette.icon03 : theme.palette.icon01 }
color = { disabled ? theme.palette.selectIconDisabled : theme.palette.selectIcon }
size = { 22 }
src = { IconArrowDown } />
</div>

View File

@@ -18,7 +18,7 @@ const useStyles = makeStyles()(theme => {
return {
container: {
position: 'relative',
backgroundColor: theme.palette.ui05,
backgroundColor: theme.palette.switchBackground,
borderRadius: '12px',
width: '40px',
height: '24px',
@@ -29,11 +29,11 @@ const useStyles = makeStyles()(theme => {
display: 'inline-block',
'&.disabled': {
backgroundColor: theme.palette.ui05,
backgroundColor: theme.palette.switchBackground,
cursor: 'default',
'& .toggle': {
backgroundColor: theme.palette.ui03
backgroundColor: theme.palette.switchToggleDisabled
}
},
@@ -45,7 +45,7 @@ const useStyles = makeStyles()(theme => {
},
containerOn: {
backgroundColor: theme.palette.action01
backgroundColor: theme.palette.switchBackgroundOn
},
toggle: {
@@ -55,7 +55,7 @@ const useStyles = makeStyles()(theme => {
zIndex: 5,
top: '4px',
left: '4px',
backgroundColor: theme.palette.ui10,
backgroundColor: theme.palette.switchToggle,
borderRadius: '100%',
transition: '.3s',
@@ -87,7 +87,7 @@ const useStyles = makeStyles()(theme => {
'&.focus-visible + .toggle-checkbox-ring': {
outline: 0,
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
boxShadow: `0px 0px 0px 2px ${theme.palette.switchFocus}`
}
},

View File

@@ -29,13 +29,13 @@ const useStyles = makeStyles()(theme => {
tab: {
...theme.typography.bodyShortBold,
color: theme.palette.text02,
color: theme.palette.tabText,
flex: 1,
padding: '14px',
background: 'none',
border: 0,
appearance: 'none',
borderBottom: `2px solid ${theme.palette.ui05}`,
borderBottom: `2px solid ${theme.palette.tabBorder}`,
transition: 'color, border-color 0.2s',
display: 'flex',
alignItems: 'center',
@@ -43,25 +43,25 @@ const useStyles = makeStyles()(theme => {
borderRadius: 0,
'&:hover': {
color: theme.palette.text01,
borderColor: theme.palette.ui10
color: theme.palette.tabTextHover,
borderColor: theme.palette.tabBorderHover
},
'&.focus-visible': {
outline: 0,
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`,
boxShadow: `0px 0px 0px 2px ${theme.palette.tabFocus}`,
border: 0,
color: theme.palette.text01
color: theme.palette.tabTextSelected
},
'&.selected': {
color: theme.palette.text01,
borderColor: theme.palette.action01
color: theme.palette.tabTextSelected,
borderColor: theme.palette.tabBorderSelected
},
'&:disabled': {
color: theme.palette.text03,
borderColor: theme.palette.ui05
color: theme.palette.tabTextDisabled,
borderColor: theme.palette.tabBorderDisabled
},
'&.is-mobile': {
@@ -72,9 +72,9 @@ const useStyles = makeStyles()(theme => {
badge: {
...theme.typography.labelBold,
alignItems: 'center',
backgroundColor: theme.palette.warning01,
backgroundColor: theme.palette.tabBadgeBackground,
borderRadius: theme.spacing(2),
color: theme.palette.text04,
color: theme.palette.tabBadgeText,
display: 'inline-flex',
height: theme.spacing(3),
justifyContent: 'center',

View File

@@ -11,6 +11,23 @@ export * from './constants.any';
*/
export const commonStyles = (theme: Theme) => {
return {
':root': {
// Inject semantic tokens as CSS custom properties for use in SCSS
'--toolbox-background-color': theme.palette.toolboxBackground,
'--drawer-background-color': theme.palette.drawerBackground,
'--toolbar-button-color': theme.palette.toolbarButton,
'--toolbar-button-hover-color': theme.palette.toolbarButtonHover,
'--toolbar-button-active-color': theme.palette.toolbarButtonActive,
'--toolbar-icon-color': theme.palette.toolbarIcon,
'--toolbar-icon-hover-color': theme.palette.toolbarIconHover,
'--toolbar-icon-active-color': theme.palette.toolbarIconActive,
'--overflow-menu-background-color': theme.palette.overflowMenuBackground,
'--overflow-menu-item-text-color': theme.palette.overflowMenuItemText,
'--overflow-menu-item-icon-color': theme.palette.overflowMenuItemIcon,
'--overflow-menu-item-hover-color': theme.palette.overflowMenuItemHover,
'--overflow-menu-item-disabled-color': theme.palette.overflowMenuItemDisabled
},
'.empty-list': {
listStyleType: 'none',
margin: 0,
@@ -39,7 +56,7 @@ export const commonStyles = (theme: Theme) => {
'.overflow-menu-item': {
alignItems: 'center',
color: theme.palette.text01,
color: theme.palette.overflowMenuItemText,
cursor: 'pointer',
display: 'flex',
fontSize: '0.875rem',
@@ -59,20 +76,20 @@ export const commonStyles = (theme: Theme) => {
'&.disabled': {
cursor: 'initial',
color: theme.palette.text03,
color: theme.palette.overflowMenuItemDisabled,
'&:hover': {
background: 'none'
},
'& svg': {
fill: theme.palette.text03
fill: theme.palette.overflowMenuItemDisabled
}
},
'@media (hover: hover) and (pointer: fine)': {
'&:hover': {
background: theme.palette.action02Hover
background: theme.palette.overflowMenuItemHover
},
'&.unclickable:hover': {
background: 'inherit'
@@ -100,14 +117,14 @@ export const commonStyles = (theme: Theme) => {
},
'& svg': {
fill: theme.palette.text01,
fill: theme.palette.overflowMenuItemIcon,
height: 20,
width: 20
}
},
'.prejoin-dialog': {
backgroundColor: theme.palette.uiBackground,
backgroundColor: theme.palette.prejoinDialogBackground,
boxShadow: '0px 2px 20px rgba(0, 0, 0, 0.5)',
borderRadius: theme.shape.borderRadius,
color: '#fff',
@@ -173,7 +190,7 @@ export const commonStyles = (theme: Theme) => {
},
'.prejoin-dialog-delimiter': {
background: theme.palette.ui03,
background: theme.palette.prejoinDialogDelimiter,
border: '0',
height: '1px',
margin: '0',
@@ -194,8 +211,8 @@ export const commonStyles = (theme: Theme) => {
},
'.prejoin-dialog-delimiter-txt': {
background: theme.palette.uiBackground,
color: theme.palette.text01,
background: theme.palette.prejoinDialogBackground,
color: theme.palette.prejoinDialogDelimiterText,
fontSize: '0.75rem',
textTransform: 'uppercase' as const,
padding: `0 ${theme.spacing(2)}`
@@ -219,11 +236,11 @@ export const commonStyles = (theme: Theme) => {
'@media (hover: hover) and (pointer: fine)': {
'&:hover': {
backgroundColor: theme.palette.ui04
backgroundColor: theme.palette.toolboxIconHover
},
'&:active': {
backgroundColor: theme.palette.ui03
backgroundColor: theme.palette.toolboxIconActive
}
},
[theme.breakpoints.down(320)]: {
@@ -232,7 +249,7 @@ export const commonStyles = (theme: Theme) => {
},
'&.toggled': {
backgroundColor: theme.palette.ui03
backgroundColor: theme.palette.toolboxIconToggled
},
'&.disabled': {
@@ -240,13 +257,13 @@ export const commonStyles = (theme: Theme) => {
backgroundColor: `${theme.palette.disabled01} !important`,
'& svg': {
fill: `${theme.palette.text03} !important`
fill: `${theme.palette.icon03} !important`
}
}
},
'.toolbox-button': {
color: theme.palette.text01,
color: theme.palette.toolbarIcon,
cursor: 'pointer',
display: 'inline-block',
lineHeight: '3rem',
@@ -254,7 +271,7 @@ export const commonStyles = (theme: Theme) => {
},
'.toolbox-content-items': {
background: theme.palette.ui01,
background: theme.palette.toolboxBackground,
borderRadius: 6,
margin: '0 auto',
padding: 6,

11
react/features/base/ui/types.d.ts vendored Normal file
View File

@@ -0,0 +1,11 @@
import '@mui/material/styles';
import { IPalette, ITypography } from './types';
declare module '@mui/material/styles' {
interface Palette extends IPalette {}
interface PaletteOptions extends Partial<IPalette> {}
interface Typography extends ITypography {}
interface TypographyOptions extends Partial<ITypography> {}
}

View File

@@ -5,6 +5,7 @@ interface ITypographyType {
lineHeight: string;
}
/* eslint-disable typescript-sort-keys/interface */
export interface IPalette {
action01: string;
action01Active: string;
@@ -58,6 +59,324 @@ export interface IPalette {
uiBackground: string;
warning01: string;
warning02: string;
// Semantic tokens (component-based, more descriptive names)
breakoutRoomArrowBackground: string;
breakoutRoomBackground: string;
breakoutRoomItemBackground: string;
chatBackground: string;
chatBackdrop: string;
chatEmptyText: string;
chatInputBackground: string;
chatInputBorder: string;
chatLink: string;
chatLobbyMessageBubble: string;
chatLobbyMessageNotice: string;
chatLobbyRecipientContainer: string;
chatMessageLocal: string;
chatMessagePrivate: string;
chatMessageRemote: string;
chatMessageText: string;
chatPrivateNotice: string;
chatRecipientCancelIcon: string;
chatRecipientContainer: string;
chatRecipientText: string;
chatReplyIcon: string;
chatSenderName: string;
chatTimestamp: string;
dialogBackground: string;
dialogBorder: string;
dialogOverlay: string;
dialogSecondaryText: string;
dialogText: string;
drawerBackground: string;
filmstripBackground: string;
filmstripBackgroundHover: string;
filmstripDragHandle: string;
filmstripDragHandleHover: string;
inputBackground: string;
inputBorder: string;
inputPlaceholder: string;
inputText: string;
largeVideoBackground: string;
largeVideoPlaceholder: string;
lobbyBackground: string;
lobbyPreviewBackground: string;
notificationActionFocus: string;
notificationActionText: string;
notificationBackground: string;
notificationCloseIcon: string;
notificationError: string;
notificationErrorText: string;
notificationNormalIcon: string;
notificationSuccess: string;
notificationText: string;
notificationWarning: string;
overflowMenuBackground: string;
overflowMenuBorder: string;
overflowMenuItemDisabled: string;
overflowMenuItemHover: string;
overflowMenuItemIcon: string;
overflowMenuItemText: string;
overflowMenuSeparator: string;
participantActionButton: string;
participantCounterBadge: string;
participantCounterText: string;
participantItemBackground: string;
participantItemBorder: string;
participantItemHover: string;
participantLinkText: string;
participantModeratorLabel: string;
participantRaisedHandBadge: string;
participantRaisedHandIcon: string;
participantSectionText: string;
participantsPaneBackground: string;
participantWarningText: string;
preMeetingBackground: string;
preMeetingPreview: string;
prejoinActionButtonDanger: string;
prejoinActionButtonDisabled: string;
prejoinActionButtonPrimary: string;
prejoinActionButtonPrimaryHover: string;
prejoinActionButtonPrimaryText: string;
prejoinActionButtonSecondary: string;
prejoinActionButtonSecondaryHover: string;
prejoinActionButtonSecondaryText: string;
prejoinCountryPickerBackground: string;
prejoinCountryPickerBorder: string;
prejoinCountryPickerText: string;
prejoinCountryRowBackground: string;
prejoinCountryRowHover: string;
prejoinDeviceStatusOk: string;
prejoinDeviceStatusText: string;
prejoinDeviceStatusWarning: string;
prejoinDialogBackground: string;
prejoinDialogDelimiter: string;
prejoinDialogDelimiterText: string;
prejoinRecordingWarningText: string;
prejoinRoomNameText: string;
prejoinTitleText: string;
prejoinWarningBackground: string;
prejoinWarningText: string;
settingsBackground: string;
settingsErrorIcon: string;
settingsSectionBackground: string;
settingsShortcutKey: string;
settingsTabText: string;
settingsVideoPreviewBorder: string;
speakerStatsAvatarLeft: string;
speakerStatsBackground: string;
speakerStatsBorder: string;
speakerStatsHeaderBackground: string;
speakerStatsLabelText: string;
speakerStatsRowAlternate: string;
speakerStatsRowBackground: string;
speakerStatsSearchBackground: string;
speakerStatsSearchBorder: string;
speakerStatsSearchIcon: string;
speakerStatsSearchPlaceholder: string;
speakerStatsSearchText: string;
speakerStatsSuccessBar: string;
thumbnailBackground: string;
thumbnailBorder: string;
thumbnailHover: string;
thumbnailRaisedHandIcon: string;
thumbnailTintBackground: string;
thumbnailVideoBackground: string;
toolbarButton: string;
toolbarButtonActive: string;
toolbarButtonHover: string;
toolbarIcon: string;
toolbarIconActive: string;
toolbarIconHover: string;
toolboxBackground: string;
toolboxIconActive: string;
toolboxIconHover: string;
toolboxIconToggled: string;
visitorsArrowBackground: string;
visitorsCountBadge: string;
visitorsCountIcon: string;
visitorsCountText: string;
visitorsQueueBackground: string;
visitorsQueueText: string;
welcomeBackground: string;
welcomeCard: string;
welcomeTabActive: string;
welcomeTabInactive: string;
// Form components
actionButtonBackground: string;
actionButtonBorder: string;
actionButtonText: string;
audioRoutePickerBackground: string;
audioRoutePickerBorder: string;
audioRoutePickerText: string;
baseReactBackground: string;
baseReactBorder: string;
baseReactText: string;
carModeBackground: string;
carModeBorder: string;
carModeText: string;
checkboxBorder: string;
checkboxChecked: string;
checkboxDisabledBackground: string;
checkboxDisabledBorder: string;
checkboxDisabledChecked: string;
checkboxIcon: string;
checkboxIconDisabled: string;
checkboxLabel: string;
clickableIconActive: string;
clickableIconBackground: string;
clickableIconFocus: string;
clickableIconHover: string;
conferenceNoticeBackground: string;
conferenceNoticeText: string;
conferenceRaisedHandLabelIcon: string;
conferenceRaisedHandLabelText: string;
conferenceSubjectText: string;
conferenceTimerText: string;
connectionIndicatorLost: string;
connectionIndicatorOther: string;
deepLinkingBackground: string;
deepLinkingBorder: string;
deepLinkingLabelText: string;
deepLinkingLink: string;
deepLinkingSeparator: string;
deepLinkingText: string;
deviceSelectorBackground: string;
deviceSelectorBorder: string;
deviceSelectorText: string;
deviceSelectorTextBackground: string;
deviceSelectorVideoPreview: string;
dialInBackground: string;
dialInSecondaryText: string;
dialInText: string;
displayNameBackground: string;
displayNameText: string;
etherpadBackground: string;
etherpadText: string;
fileSharingBackground: string;
fileSharingEmptyIcon: string;
fileSharingEmptyText: string;
fileSharingItemBackground: string;
fileSharingItemBorder: string;
fileSharingText: string;
gifsBackground: string;
gifsText: string;
inlineDialogBackground: string;
inlineDialogBorder: string;
inlineDialogText: string;
inputBottomLabel: string;
inputBottomLabelError: string;
inputClearButton: string;
inputFieldBackground: string;
inputFieldBorder: string;
inputFieldDisabled: string;
inputFieldError: string;
inputFieldFocus: string;
inputFieldPlaceholder: string;
inputFieldText: string;
inputLabel: string;
labelBackground: string;
labelText: string;
labelWhiteBackground: string;
labelWhiteIcon: string;
labelWhiteText: string;
languageSelectorBackground: string;
languageSelectorHover: string;
languageSelectorText: string;
listItemBackground: string;
listItemBoxShadow: string;
listItemHighlighted: string;
listItemHover: string;
listItemText: string;
multiSelectBackground: string;
multiSelectBorder: string;
multiSelectItemDisabled: string;
multiSelectItemHover: string;
multiSelectItemText: string;
pollsAnswer: string;
pollsBackground: string;
pollsBarBackground: string;
pollsBarPercentage: string;
pollsCreateBackground: string;
pollsCreateBorder: string;
pollsPaneBackground: string;
pollsPaneBorder: string;
pollsQuestion: string;
pollsSendDisabled: string;
pollsSendLabel: string;
pollsSeparator: string;
pollsSubtitle: string;
pollsTitle: string;
pollsVotersBackground: string;
pollsVotersText: string;
reactionsMenuBackground: string;
reactionsMenuBorder: string;
recordingBackground: string;
recordingHighlightButton: string;
recordingHighlightButtonDisabled: string;
recordingHighlightButtonIcon: string;
recordingHighlightButtonIconDisabled: string;
recordingNotificationAction: string;
recordingNotificationText: string;
recordingText: string;
securityDialogBackground: string;
securityDialogBorder: string;
securityDialogSecondaryText: string;
securityDialogText: string;
selectBackground: string;
selectBottomLabel: string;
selectBottomLabelError: string;
selectDisabled: string;
selectError: string;
selectFocus: string;
selectIcon: string;
selectIconDisabled: string;
selectLabel: string;
selectText: string;
sliderFocus: string;
sliderKnob: string;
sliderTrack: string;
subtitleMessageBackground: string;
subtitleMessageSender: string;
subtitleMessageText: string;
subtitleMessageTime: string;
switchBackground: string;
switchBackgroundOn: string;
switchFocus: string;
switchToggle: string;
switchToggleDisabled: string;
tabBadgeBackground: string;
tabBadgeText: string;
tabBorder: string;
tabBorderDisabled: string;
tabBorderHover: string;
tabBorderSelected: string;
tabFocus: string;
tabText: string;
tabTextDisabled: string;
tabTextHover: string;
tabTextSelected: string;
tooltipBackground: string;
tooltipText: string;
videoMenuBackground: string;
videoMenuBorder: string;
videoMenuSliderBackground: string;
videoMenuText: string;
videoQualityBackground: string;
videoQualityText: string;
virtualBackgroundBackground: string;
virtualBackgroundBorder: string;
virtualBackgroundPreview: string;
virtualBackgroundText: string;
whiteboardBackground: string;
whiteboardText: string;
salesforceSearchBackground: string;
salesforceSearchBorder: string;
salesforceSearchIcon: string;
salesforceSearchPlaceholder: string;
salesforceSearchText: string;
}
export interface ITypography {

View File

@@ -11,13 +11,49 @@ import * as tokens from './tokens.json';
*/
export function createColorTokens(colorMap: Object): any {
const allTokens = merge({}, tokens, jitsiTokens);
const result: any = {};
return Object.entries(colorMap)
.reduce((result, [ token, value ]: [any, string]) => {
const color = allTokens[value as keyof typeof allTokens] || value;
// First pass: resolve tokens that reference allTokens directly
Object.entries(colorMap).forEach(([ token, value ]: [any, string]) => {
const color = allTokens[value as keyof typeof allTokens] || value;
return Object.assign(result, { [token]: color });
}, {});
result[token] = color;
});
// Second pass: resolve semantic tokens that reference other colorMap entries
// Recursively resolve until we get actual color values
const resolveToken = (value: string, depth = 0): string => {
// Prevent infinite loops
if (depth > 10) {
return value;
}
// If it's already a color (starts with # or rgb/rgba), return it
if (value.startsWith('#') || value.startsWith('rgb')) {
return value;
}
// Look up in the result map first (for colorMap token references)
if (result[value]) {
return resolveToken(result[value], depth + 1);
}
// Then look up in allTokens
const resolved = allTokens[value as keyof typeof allTokens];
if (resolved && resolved !== value && typeof resolved === 'string') {
return resolveToken(resolved, depth + 1);
}
return value;
};
// Third pass: recursively resolve all values
Object.entries(result).forEach(([ token, value ]) => {
result[token] = resolveToken(String(value));
});
return result;
}
/**

View File

@@ -22,7 +22,6 @@ export function assignIfDefined(target: Object, source: Object) {
return to;
}
const MATCH_OPERATOR_REGEXP = /[|\\{}()[\]^$+*?.-]/g;
/**
@@ -79,6 +78,21 @@ export function getJitsiMeetGlobalNS() {
return window.JitsiMeetJS.app;
}
/**
* Gets the Electron-specific global namespace.
*
* @returns {Object} The Electron namespace.
*/
export function getElectronGlobalNS() {
const globalNS = getJitsiMeetGlobalNS();
if (!globalNS.electron) {
globalNS.electron = {};
}
return globalNS.electron;
}
/**
* Returns the object that stores the connection times.
*

View File

@@ -8,7 +8,7 @@ export default {
button: {
marginBottom: BaseTheme.spacing[4],
marginHorizontal: BaseTheme.spacing[2]
marginHorizontal: BaseTheme.spacing[3]
},
collapsibleList: {

View File

@@ -44,6 +44,16 @@ export const getMainRoom = (stateful: IStateful) => {
* @returns {IRoomsInfo} The rooms info.
*/
export const getRoomsInfo = (stateful: IStateful) => {
const state = toState(stateful);
const localParticipant = getLocalParticipant(stateful);
const jwtUser = state['features/base/jwt']?.user;
const localUserContext = jwtUser ? {
id: jwtUser.id,
name: jwtUser.name
} : {
id: localParticipant?.jwtId,
name: localParticipant?.name
};
const breakoutRooms = getBreakoutRooms(stateful);
const conference = getCurrentConference(stateful);
@@ -57,7 +67,6 @@ export const getRoomsInfo = (stateful: IStateful) => {
const conferenceParticipants = conference?.getParticipants()
.filter((participant: IJitsiParticipant) => !participant.isHidden());
const localParticipant = getLocalParticipant(stateful);
let localParticipantInfo;
if (localParticipant) {
@@ -65,7 +74,8 @@ export const getRoomsInfo = (stateful: IStateful) => {
role: localParticipant.role,
displayName: localParticipant.name,
avatarUrl: localParticipant.loadableAvatarUrl,
id: localParticipant.id
id: localParticipant.id,
userContext: localUserContext
};
}
@@ -86,7 +96,8 @@ export const getRoomsInfo = (stateful: IStateful) => {
role: participantItem.getRole(),
displayName: participantItem.getDisplayName(),
avatarUrl: storeParticipant?.loadableAvatarUrl,
id: participantItem.getId()
id: participantItem.getId(),
userContext: storeParticipant?.userContext
} as IRoomInfoParticipant;
}) ]
: [ localParticipantInfo ]
@@ -110,13 +121,18 @@ export const getRoomsInfo = (stateful: IStateful) => {
const storeParticipant = getParticipantById(stateful,
ids.length > 1 ? ids[1] : participantItem.jid);
// Check if this is the local participant
const isLocal = storeParticipant?.id === localParticipant?.id;
const userContext = isLocal ? localUserContext : (storeParticipant?.userContext || participantItem.userContext);
return {
jid: participantItem?.jid,
role: participantItem?.role,
displayName: participantItem?.displayName,
avatarUrl: storeParticipant?.loadableAvatarUrl,
id: storeParticipant ? storeParticipant.id
: participantLongId
: participantLongId,
userContext
} as IRoomInfoParticipant;
}) : []
} as IRoomInfo;

View File

@@ -44,19 +44,59 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
switch (type) {
case UPDATE_BREAKOUT_ROOMS: {
// edit name if it was overwritten
// Enrich participants with userContext from Redux store
if (!action.updatedNames) {
const { overwrittenNameList } = getState()['features/base/participants'];
const state = getState();
const { overwrittenNameList, local: localParticipant } = state['features/base/participants'];
const jwtUser = state['features/base/jwt']?.user;
const localUserContext = jwtUser ? {
id: jwtUser.id,
name: jwtUser.name
} : {
id: localParticipant?.jwtId,
name: localParticipant?.name
};
if (Object.keys(overwrittenNameList).length > 0) {
const newRooms: IRooms = {};
// Get existing userContext cache
const existingCache = state['features/breakout-rooms'].userContextCache || {};
const newCache = { ...existingCache };
Object.entries(action.rooms as IRooms).forEach(([ key, r ]) => {
let participants = r?.participants || {};
let jid;
const newRooms: IRooms = {};
Object.entries(action.rooms as IRooms).forEach(([ key, r ]) => {
let participants = r?.participants || {};
// Add userContext to each participant
const enhancedParticipants: typeof participants = {};
for (const [ participantJid, participantData ] of Object.entries(participants)) {
const ids = participantJid.split('/');
const participantId = ids.length > 1 ? ids[1] : participantData.jid;
const storeParticipant = getParticipantById(state, participantId);
const isLocal = storeParticipant?.id === localParticipant?.id;
// Try to get userContext from: local, store, cache, or incoming data
const userContext = isLocal
? localUserContext
: (storeParticipant?.userContext || newCache[participantId] || participantData.userContext);
// Update cache if we have userContext
if (userContext && participantId) {
newCache[participantId] = userContext;
}
enhancedParticipants[participantJid] = {
...participantData,
userContext
};
}
participants = enhancedParticipants;
// Apply overwritten display names
if (Object.keys(overwrittenNameList).length > 0) {
for (const id of Object.keys(overwrittenNameList)) {
jid = Object.keys(participants).find(p => p.slice(p.indexOf('/') + 1) === id);
const jid = Object.keys(participants).find(p => p.slice(p.indexOf('/') + 1) === id);
if (jid) {
participants = {
@@ -68,15 +108,16 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
};
}
}
}
newRooms[key] = {
...r,
participants
};
});
newRooms[key] = {
...r,
participants
};
});
action.rooms = newRooms;
}
action.rooms = newRooms;
action.userContextCache = newCache;
}
// edit the chat history to match names for participants in breakout rooms

View File

@@ -10,12 +10,19 @@ import { IRooms } from './types';
const DEFAULT_STATE = {
rooms: {},
roomCounter: 0
roomCounter: 0,
userContextCache: {}
};
export interface IBreakoutRoomsState {
roomCounter: number;
rooms: IRooms;
userContextCache: {
[participantId: string]: {
id?: string;
name?: string;
};
};
}
/**
@@ -29,12 +36,13 @@ ReducerRegistry.register<IBreakoutRoomsState>(FEATURE_KEY, (state = DEFAULT_STAT
roomCounter: action.roomCounter
};
case UPDATE_BREAKOUT_ROOMS: {
const { roomCounter, rooms } = action;
const { roomCounter, rooms, userContextCache } = action;
return {
...state,
roomCounter,
rooms
rooms,
userContextCache: userContextCache || state.userContextCache
};
}
case _RESET_BREAKOUT_ROOMS: {

View File

@@ -8,6 +8,10 @@ export interface IRoom {
displayName: string;
jid: string;
role: string;
userContext?: {
id?: string;
name?: string;
};
};
};
}
@@ -33,4 +37,8 @@ export interface IRoomInfoParticipant {
id: string;
jid: string;
role: string;
userContext?: {
id?: string;
name?: string;
};
}

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