From f880e56a1e2f39a851a5f80cbec79ea4d464f12c Mon Sep 17 00:00:00 2001 From: Arthurk12 Date: Tue, 13 Jun 2023 13:26:43 -0300 Subject: [PATCH 001/252] feat(wake-lock): prototype Adds wake lock feature, which is available only for mobile users using browser with support to the given API. When an user using a supported device joins the meeting, a toast is displayed offering to activate the feature. Adds a toggle under settings menu to activate/deactivate it. --- .../imports/ui/components/app/component.jsx | 2 + .../submenus/application/component.jsx | 38 +++++ .../ui/components/wake-lock/component.jsx | 133 ++++++++++++++++++ .../ui/components/wake-lock/container.jsx | 19 +++ .../ui/components/wake-lock/service.js | 75 ++++++++++ .../imports/ui/components/wake-lock/styles.js | 46 ++++++ .../private/config/settings.yml | 2 + bigbluebutton-html5/public/locales/en.json | 6 + 8 files changed, 321 insertions(+) create mode 100644 bigbluebutton-html5/imports/ui/components/wake-lock/component.jsx create mode 100644 bigbluebutton-html5/imports/ui/components/wake-lock/container.jsx create mode 100644 bigbluebutton-html5/imports/ui/components/wake-lock/service.js create mode 100644 bigbluebutton-html5/imports/ui/components/wake-lock/styles.js diff --git a/bigbluebutton-html5/imports/ui/components/app/component.jsx b/bigbluebutton-html5/imports/ui/components/app/component.jsx index 5a62095241..bb67655f72 100644 --- a/bigbluebutton-html5/imports/ui/components/app/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/app/component.jsx @@ -14,6 +14,7 @@ import { Meteor } from 'meteor/meteor'; import ToastContainer from '/imports/ui/components/common/toast/container'; import PadsSessionsContainer from '/imports/ui/components/pads/sessions/container'; import ModalContainer from '/imports/ui/components/common/modal/container'; +import WakeLockContainer from '../wake-lock/container'; import NotificationsBarContainer from '../notifications-bar/container'; import AudioContainer from '../audio/container'; import ChatAlertContainer from '../chat/alert/container'; @@ -574,6 +575,7 @@ class App extends Component { + {this.renderActionsBar()} {customStyleUrl ? : null} {customStyle ? : null} diff --git a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx index ee94e646f6..a0364e3637 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx @@ -6,6 +6,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import BaseMenu from '../base/component'; import Styled from './styles'; import VideoService from '/imports/ui/components/video-provider/service'; +import WakeLockService from '/imports/ui/components/wake-lock/service'; import { ACTIONS } from '/imports/ui/components/layout/enums'; import Settings from '/imports/ui/services/settings'; @@ -77,6 +78,10 @@ const intlMessages = defineMessages({ id: 'app.submenu.application.wbToolbarsAutoHideLabel', description: 'enable/disable auto hiding of whitebord toolbars', }, + wakeLockEnabledLabel: { + id: 'app.submenu.application.wakeLockEnabledLabel', + description: 'enable/disable wake lock', + }, layoutOptionLabel: { id: 'app.submenu.application.layoutOptionLabel', description: 'layout options', @@ -366,6 +371,38 @@ class ApplicationMenu extends BaseMenu { ); } + renderWakeLockToggle() { + if (!WakeLockService.isSupported()) return null; + + const { intl, showToggleLabel, displaySettingsStatus } = this.props; + const { settings } = this.state; + + return ( + + + + {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */} + + {intl.formatMessage(intlMessages.wakeLockEnabledLabel)} + + + + + + {displaySettingsStatus(settings.wakeLockEnabled)} + this.handleToggle('wakeLockEnabled')} + ariaLabel={intl.formatMessage(intlMessages.wakeLockEnabledLabel)} + showToggleLabel={showToggleLabel} + /> + + + + ); + } + render() { const { allLocales, intl, showToggleLabel, displaySettingsStatus, @@ -423,6 +460,7 @@ class ApplicationMenu extends BaseMenu { {this.renderAudioFilters()} {this.renderPaginationToggle()} {this.renderDarkThemeToggle()} + {this.renderWakeLockToggle()} - {displaySettingsStatus(settings.wakeLockEnabled)} + {displaySettingsStatus(settings.wakeLock)} this.handleToggle('wakeLockEnabled')} + defaultChecked={settings.wakeLock} + onChange={() => this.handleToggle('wakeLock')} ariaLabel={intl.formatMessage(intlMessages.wakeLockEnabledLabel)} showToggleLabel={showToggleLabel} /> diff --git a/bigbluebutton-html5/imports/ui/components/wake-lock/container.jsx b/bigbluebutton-html5/imports/ui/components/wake-lock/container.jsx index aaa1a49e36..223c70f2f3 100644 --- a/bigbluebutton-html5/imports/ui/components/wake-lock/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/wake-lock/container.jsx @@ -45,7 +45,7 @@ WakeLockContainer.propTypes = propTypes; WakeLockContainer.defaultProps = defaultProps; export default withTracker(() => { - const wakeLockSettings = Settings.application.wakeLockEnabled; + const wakeLockSettings = Settings.application.wakeLock; return { request: Service.request, release: Service.release, diff --git a/bigbluebutton-html5/imports/ui/components/wake-lock/service.js b/bigbluebutton-html5/imports/ui/components/wake-lock/service.js index f56719e9cd..8ffd9be95b 100644 --- a/bigbluebutton-html5/imports/ui/components/wake-lock/service.js +++ b/bigbluebutton-html5/imports/ui/components/wake-lock/service.js @@ -1,7 +1,7 @@ import logger from '/imports/startup/client/logger'; import deviceInfo from '/imports/utils/deviceInfo'; -const WAKELOCK_ENABLED = Meteor.settings.public.app.enableWakeLock; +const WAKELOCK_ENABLED = Meteor.settings.public.app.wakeLock.enabled; class WakeLock { constructor() { diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 797d042032..44abbf0354 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -59,7 +59,8 @@ public: # the default logoutUrl matches window.location.origin i.e. bigbluebutton.org for demo.bigbluebutton.org # in some cases we want only custom logoutUrl to be used when provided on meeting create. Default value: true askForConfirmationOnLeave: true - enableWakeLock: false + wakeLock: + enabled: false allowDefaultLogoutUrl: true allowUserLookup: false dynamicGuestPolicy: true @@ -176,7 +177,7 @@ public: raiseHandPushAlerts: true guestWaitingAudioAlerts: true guestWaitingPushAlerts: true - wakeLockEnabled: false + wakeLock: false paginationEnabled: true whiteboardToolbarAutoHide: false darkTheme: false From 602f2e01f8d61948dc96c5cce056e6f377b4a786 Mon Sep 17 00:00:00 2001 From: Arthurk12 Date: Wed, 14 Jun 2023 13:04:55 -0300 Subject: [PATCH 005/252] fix(wake-lock): call wake lock instance method --- bigbluebutton-html5/imports/ui/components/wake-lock/service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/ui/components/wake-lock/service.js b/bigbluebutton-html5/imports/ui/components/wake-lock/service.js index 8ffd9be95b..2d58b7f12c 100644 --- a/bigbluebutton-html5/imports/ui/components/wake-lock/service.js +++ b/bigbluebutton-html5/imports/ui/components/wake-lock/service.js @@ -67,7 +67,7 @@ class WakeLock { const wakeLock = new WakeLock(); export default { - isEnabled: () => WakeLock.isEnabled(), + isEnabled: () => wakeLock.isEnabled(), isSupported: () => wakeLock.isSupported(), isActive: () => wakeLock.isActive(), request: () => wakeLock.request(), From 5d1611811ad49e8abea37311b00b91282a0d31ef Mon Sep 17 00:00:00 2001 From: Arthurk12 Date: Wed, 14 Jun 2023 13:33:29 -0300 Subject: [PATCH 006/252] fix(wake lock): change request return value Wake lock request function returns a boolean indicating whether there was an error or not. --- .../ui/components/wake-lock/component.jsx | 20 +++++++++---------- .../ui/components/wake-lock/service.js | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/wake-lock/component.jsx b/bigbluebutton-html5/imports/ui/components/wake-lock/component.jsx index d83fda9ca2..dabf1ec8f3 100644 --- a/bigbluebutton-html5/imports/ui/components/wake-lock/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/wake-lock/component.jsx @@ -65,12 +65,12 @@ class WakeLock extends Component { type="button" onClick={async () => { closeNotification(); - const success = await request(); - if (success) { + const error = await request(); + if (!error) { Settings.application.wakeLockEnabled = true; Settings.save(); } - this.feedbackToast(success); + this.feedbackToast(error); }} > { intl.formatMessage(intlMessages.wakeLockOfferAcceptButton) } @@ -97,26 +97,26 @@ class WakeLock extends Component { ); } - feedbackToast(success) { + feedbackToast(error) { const feedbackToastProps = { closeOnClick: true, autoClose: true, closeButton: false, }; - const feedbackToast = this.getToast(success ? 'wakeLockSuccess' : 'wakeLockFailed', - success ? 'wakeLockAcquireSuccess' : 'wakeLockAcquireFailed', null, null); + const feedbackToast = this.getToast(error ? 'wakeLockFailed' : 'wakeLockSuccess', + error ? 'wakeLockAcquireFailed' : 'wakeLockAcquireSuccess', null, null); setTimeout(() => { - notify(feedbackToast, success ? 'success' : 'error', 'lock', feedbackToastProps, null, true); + notify(feedbackToast, error ? 'error' : 'success', 'lock', feedbackToastProps, null, true); }, 800); } render() { const { wakeLockSettings, request, release } = this.props; if (wakeLockSettings) { - request().then((success) => { - if (!success) { - this.feedbackToast(success); + request().then((error) => { + if (error) { + this.feedbackToast(error); Settings.application.wakeLockEnabled = false; Settings.save(); } diff --git a/bigbluebutton-html5/imports/ui/components/wake-lock/service.js b/bigbluebutton-html5/imports/ui/components/wake-lock/service.js index 2d58b7f12c..2c0a9f55f0 100644 --- a/bigbluebutton-html5/imports/ui/components/wake-lock/service.js +++ b/bigbluebutton-html5/imports/ui/components/wake-lock/service.js @@ -38,7 +38,7 @@ class WakeLock { logger.warn({ logCode: 'wake_lock_request_error', }, 'Wake lock API not supported'); - return false; + return true; } try { @@ -54,9 +54,9 @@ class WakeLock { errorMessage: err.message, }, }, 'Error requesting wake lock.'); - return false; + return true; } - return true; + return false; } release() { From 26dea6076a9cdd4b8b1e052992e07a6d397578b9 Mon Sep 17 00:00:00 2001 From: danielpetri1 Date: Tue, 4 Jul 2023 21:37:20 +0000 Subject: [PATCH 007/252] Fix permission issue with Etherpad 1.9.1 A zero-width-space is used to work around an issue with Etherpad 1.9.1 where empty pads are not being created. --- bbb-etherpad.placeholder.sh | 2 +- build/packages-template/bbb-etherpad/settings.json | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bbb-etherpad.placeholder.sh b/bbb-etherpad.placeholder.sh index 34ab1048ea..3ef5b11ccd 100755 --- a/bbb-etherpad.placeholder.sh +++ b/bbb-etherpad.placeholder.sh @@ -1,2 +1,2 @@ -git clone --branch 1.8.17 --depth 1 https://github.com/ether/etherpad-lite bbb-etherpad +git clone --branch 1.9.1 --depth 1 https://github.com/ether/etherpad-lite bbb-etherpad diff --git a/build/packages-template/bbb-etherpad/settings.json b/build/packages-template/bbb-etherpad/settings.json index 95ae330926..a82ecfc0f2 100644 --- a/build/packages-template/bbb-etherpad/settings.json +++ b/build/packages-template/bbb-etherpad/settings.json @@ -220,9 +220,10 @@ */ /* - * The default text of a pad - */ - "defaultPadText" : "", + * The default text of a pad: A zero-width-space is used to work around an issue with Etherpad 1.9.1 where empty pads are not being created. + * See: https://github.com/ether/etherpad-lite/issues/5787 + */ + "defaultPadText" : "\u200b", /* * Default Pad behavior. From 634f1ca5fe1a65efd5f17fb4f78cf9b441b008c6 Mon Sep 17 00:00:00 2001 From: Anton B Date: Fri, 7 Jul 2023 14:05:06 -0300 Subject: [PATCH 008/252] test: fix wrong imports --- bigbluebutton-tests/playwright/layouts/layouts.spec.js | 2 +- .../playwright/presentation/presentation.spec.js | 2 +- bigbluebutton-tests/playwright/whiteboard/whiteboard.spec.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bigbluebutton-tests/playwright/layouts/layouts.spec.js b/bigbluebutton-tests/playwright/layouts/layouts.spec.js index cbc85218e6..3545d22ee0 100644 --- a/bigbluebutton-tests/playwright/layouts/layouts.spec.js +++ b/bigbluebutton-tests/playwright/layouts/layouts.spec.js @@ -1,5 +1,5 @@ const { test } = require('@playwright/test'); -const { encodeCustomParams } = require('../customparameters/util'); +const { encodeCustomParams } = require('../parameters/util'); const { PARAMETER_HIDE_PRESENTATION_TOAST } = require('../core/constants'); const { Layouts } = require('./layouts'); diff --git a/bigbluebutton-tests/playwright/presentation/presentation.spec.js b/bigbluebutton-tests/playwright/presentation/presentation.spec.js index 4765ac4e18..b10241e464 100644 --- a/bigbluebutton-tests/playwright/presentation/presentation.spec.js +++ b/bigbluebutton-tests/playwright/presentation/presentation.spec.js @@ -1,5 +1,5 @@ const { test } = require('@playwright/test'); -const { encodeCustomParams } = require('../customparameters/util'); +const { encodeCustomParams } = require('../parameters/util'); const { Presentation } = require('./presentation'); const customStyleAvoidUploadingNotifications = encodeCustomParams(`userdata-bbb_custom_style=.presentationUploaderToast{display: none;}`); diff --git a/bigbluebutton-tests/playwright/whiteboard/whiteboard.spec.js b/bigbluebutton-tests/playwright/whiteboard/whiteboard.spec.js index c99ea479f1..0fb16e8672 100644 --- a/bigbluebutton-tests/playwright/whiteboard/whiteboard.spec.js +++ b/bigbluebutton-tests/playwright/whiteboard/whiteboard.spec.js @@ -12,7 +12,7 @@ const { Pan } = require('./pan'); const { Eraser } = require('./eraser'); const { DrawArrow } = require('./drawArrow'); const { MultiUsers } = require('../user/multiusers'); -const { encodeCustomParams } = require('../customparameters/util'); +const { encodeCustomParams } = require('../parameters/util'); const { PARAMETER_HIDE_PRESENTATION_TOAST } = require('../core/constants'); const { DeleteDrawing } = require('./deleteDrawing'); const { UndoDrawing } = require('./undoDraw'); From 29c86b5300d86b11f8bcadb6478b3e824feb82ae Mon Sep 17 00:00:00 2001 From: Gabriel Porfirio Date: Mon, 10 Jul 2023 16:29:30 -0300 Subject: [PATCH 009/252] refactoring parameters tests --- .../playwright/core/helpers.js | 30 +++- bigbluebutton-tests/playwright/core/page.js | 18 +- .../playwright/parameters/customparameters.js | 8 +- .../playwright/parameters/parameters.spec.js | 158 +++++++++--------- 4 files changed, 108 insertions(+), 106 deletions(-) diff --git a/bigbluebutton-tests/playwright/core/helpers.js b/bigbluebutton-tests/playwright/core/helpers.js index d03b89cf79..a9ec41bd40 100644 --- a/bigbluebutton-tests/playwright/core/helpers.js +++ b/bigbluebutton-tests/playwright/core/helpers.js @@ -4,6 +4,7 @@ const axios = require('axios'); const { test, expect } = require('@playwright/test'); const xml2js = require('xml2js'); const { runScript } = require('./util'); +const { env } = require('node:process'); const parameters = require('./parameters'); @@ -26,36 +27,49 @@ function apiCall(name, callParams) { return axios.get(url, { adapter: 'http' }).then(response => xml2js.parseStringPromise(response.data)); } -function createMeetingUrl(params, customParameter, customMeetingId) { +function createMeetingUrl(params, createParameter, customMeetingId) { const meetingID = (customMeetingId) ? customMeetingId : `random-${getRandomInt(1000000, 10000000).toString()}`; const mp = params.moderatorPW; const ap = params.attendeePW; const baseQuery = `name=${meetingID}&meetingID=${meetingID}&attendeePW=${ap}&moderatorPW=${mp}` + `&allowStartStopRecording=true&autoStartRecording=false&welcome=${params.welcome}`; - const query = customParameter !== undefined ? `${baseQuery}&${customParameter}` : baseQuery; + const query = createParameter !== undefined ? `${baseQuery}&${createParameter}` : baseQuery; const apiCall = `create${query}${params.secret}`; const checksum = sha1(apiCall); const url = `${params.server}/create?${query}&checksum=${checksum}`; return url; } -function createMeetingPromise(params, customParameter, customMeetingId) { - const url = createMeetingUrl(params, customParameter, customMeetingId); +function createMeetingPromise(params, createParameter, customMeetingId) { + const url = createMeetingUrl(params, createParameter, customMeetingId); return axios.get(url, { adapter: 'http' }); } -async function createMeeting(params, customParameter) { - const promise = createMeetingPromise(params, customParameter); +async function createMeeting(params, createParameter, page) { + const promise = createMeetingPromise(params, createParameter); const response = await promise; expect(response.status).toEqual(200); const xmlResponse = await xml2js.parseStringPromise(response.data); + + if (env.CONSOLE !== undefined) { + const CONSOLE_strings = env.CONSOLE.split(',').map(opt => opt.trim().toLowerCase()); + const CONSOLE_options = { + colorize: CONSOLE_strings.includes('color') || CONSOLE_strings.includes('colour'), + drop_references: CONSOLE_strings.includes('norefs'), + drop_timestamps: CONSOLE_strings.includes('nots'), + line_label: CONSOLE_strings.includes('label') ? this.username + " " : undefined, + noClientLogger: CONSOLE_strings.includes('nocl') || CONSOLE_strings.includes('noclientlogger'), + }; + page.on('console', async (msg) => console.log(await console_format(msg, CONSOLE_options))); + } + return xmlResponse.response.meetingID[0]; } -function getJoinURL(meetingID, params, moderator, customParameter) { +function getJoinURL(meetingID, params, moderator, joinParameter) { const pw = moderator ? params.moderatorPW : params.attendeePW; const baseQuery = `fullName=${params.fullName}&meetingID=${meetingID}&password=${pw}`; - const query = customParameter !== undefined ? `${baseQuery}&${customParameter}` : baseQuery; + const query = joinParameter !== undefined ? `${baseQuery}&${joinParameter}` : baseQuery; const apiCall = `join${query}${params.secret}`; const checksum = sha1(apiCall); return `${params.server}/join?${query}&checksum=${checksum}`; diff --git a/bigbluebutton-tests/playwright/core/page.js b/bigbluebutton-tests/playwright/core/page.js index d8493de42c..f745e5db2c 100644 --- a/bigbluebutton-tests/playwright/core/page.js +++ b/bigbluebutton-tests/playwright/core/page.js @@ -98,26 +98,14 @@ class Page { } async init(isModerator, shouldCloseAudioModal, initOptions) { - const { fullName, meetingId, customParameter, customMeetingId } = initOptions || {}; + const { fullName, meetingId, createParameter, joinParameter, customMeetingId } = initOptions || {}; if (!isModerator) this.initParameters.moderatorPW = ''; if (fullName) this.initParameters.fullName = fullName; this.username = this.initParameters.fullName; - if (env.CONSOLE !== undefined) { - const CONSOLE_strings = env.CONSOLE.split(',').map(opt => opt.trim().toLowerCase()); - const CONSOLE_options = { - colorize: CONSOLE_strings.includes('color') || CONSOLE_strings.includes('colour'), - drop_references: CONSOLE_strings.includes('norefs'), - drop_timestamps: CONSOLE_strings.includes('nots'), - line_label: CONSOLE_strings.includes('label') ? this.username + " " : undefined, - noClientLogger: CONSOLE_strings.includes('nocl') || CONSOLE_strings.includes('noclientlogger'), - }; - this.page.on('console', async (msg) => console.log(await console_format(msg, CONSOLE_options))); - } - - this.meetingId = (meetingId) ? meetingId : await helpers.createMeeting(parameters, customParameter, customMeetingId); - const joinUrl = helpers.getJoinURL(this.meetingId, this.initParameters, isModerator, customParameter); + this.meetingId = (meetingId) ? meetingId : await helpers.createMeeting(parameters, createParameter, customMeetingId, this.page); + const joinUrl = helpers.getJoinURL(this.meetingId, this.initParameters, isModerator, joinParameter); const response = await this.page.goto(joinUrl); await expect(response.ok()).toBeTruthy(); const hasErrorLabel = await this.checkElement(e.errorMessageLabel); diff --git a/bigbluebutton-tests/playwright/parameters/customparameters.js b/bigbluebutton-tests/playwright/parameters/customparameters.js index d0e1498617..fc3b21a4c3 100644 --- a/bigbluebutton-tests/playwright/parameters/customparameters.js +++ b/bigbluebutton-tests/playwright/parameters/customparameters.js @@ -133,8 +133,8 @@ class CustomParameters extends MultiUsers { await this.modPage.wasRemoved(e.presentationPlaceholder); } - async forceRestorePresentationOnNewEvents(customParameter) { - await this.initUserPage(true, this.context, { useModMeetingId: true, customParameter }); + async forceRestorePresentationOnNewEvents(joinParameter) { + await this.initUserPage(true, this.context, { useModMeetingId: true, joinParameter }); const { presentationHidden, pollEnabled } = getSettings(); if (!presentationHidden) await this.userPage.waitAndClick(e.minimizePresentation); const zoomInCase = await util.zoomIn(this.modPage); @@ -148,8 +148,8 @@ class CustomParameters extends MultiUsers { await this.userPage.checkElement(e.restorePresentation); } - async forceRestorePresentationOnNewPollResult(customParameter) { - await this.initUserPage(true, this.context, { useModMeetingId: true, customParameter }) + async forceRestorePresentationOnNewPollResult(joinParameter) { + await this.initUserPage(true, this.context, { useModMeetingId: true, joinParameter }) const { presentationHidden,pollEnabled } = getSettings(); if (!presentationHidden) await this.userPage.waitAndClick(e.minimizePresentation); if (pollEnabled) await util.poll(this.modPage, this.userPage); diff --git a/bigbluebutton-tests/playwright/parameters/parameters.spec.js b/bigbluebutton-tests/playwright/parameters/parameters.spec.js index 10f3bf131b..175982abf1 100644 --- a/bigbluebutton-tests/playwright/parameters/parameters.spec.js +++ b/bigbluebutton-tests/playwright/parameters/parameters.spec.js @@ -8,87 +8,87 @@ const { CreateParameters } = require('./createParameters'); test.describe.parallel('Create Parameters', () => { test('Record Meeting', async ({ browser, context, page }) => { const createParam = new CreateParameters(browser, context); - await createParam.initModPage(page, true, { customParameter: c.recordMeeting }); + await createParam.initModPage(page, true, { createParameter: c.recordMeeting }); await createParam.recordMeeting(); }); test.describe.parallel('Banner', () => { test('Banner Text @ci', async ({ browser, context, page }) => { const createParam = new CreateParameters(browser, context); - await createParam.initModPage(page, true, { customParameter: encodeCustomParams(c.bannerText) }); + await createParam.initModPage(page, true, { createParameter: encodeCustomParams(c.bannerText) }); await createParam.bannerText(); }); test('Banner Color @ci', async ({ browser, context, page }) => { const createParam = new CreateParameters(browser, context); const colorToRGB = hexToRgb(c.color); - await createParam.initModPage(page, true, { customParameter: `${c.bannerColor}&${encodeCustomParams(c.bannerText)}` }); + await createParam.initModPage(page, true, { createParameter: `${c.bannerColor}&${encodeCustomParams(c.bannerText)}` }); await createParam.bannerColor(colorToRGB); }); }); test('Max Participants', async ({ browser, context, page }) => { const createParam = new CreateParameters(browser, context); - await createParam.initModPage(page, true, { customParameter: c.maxParticipants }); + await createParam.initModPage(page, true, { createParameter: c.maxParticipants }); await createParam.initModPage2(true, context); await createParam.maxParticipants(context); }); test('Meeting Duration', async ({ browser, context, page }) => { const createParam = new CreateParameters(browser, context); - await createParam.initModPage(page, true, { customParameter: c.duration }); + await createParam.initModPage(page, true, { createParameter: c.duration }); await createParam.duration(); }); test('Message Only To Moderators', async ({ browser, context, page }) => { const createParam = new CreateParameters(browser, context); - await createParam.initModPage(page, true, { customParameter: c.moderatorOnlyMessage }); + await createParam.initModPage(page, true, { createParameter: c.moderatorOnlyMessage }); await createParam.moderatorOnlyMessage(context); }); test('Webcams Shows Only For Moderators', async ({ browser, context, page }) => { const createParam = new CreateParameters(browser, context); - await createParam.initModPage(page, true, { customParameter: c.webcamsOnlyForModerator }); + await createParam.initModPage(page, true, { createParameter: c.webcamsOnlyForModerator }); await createParam.initUserPage2(true, context); await createParam.webcamsOnlyForModerator(context); }); test('Mute On Start', async ({ browser, context, page }) => { const createParam = new CreateParameters(browser, context); - await createParam.initModPage(page, true, { customParameter: c.muteOnStart }); + await createParam.initModPage(page, true, { createParameter: c.muteOnStart }); await createParam.muteOnStart(); }); test('Allow Mods To Unmute Users', async ({ browser, context, page }) => { const createParam = new CreateParameters(browser, context); - await createParam.initModPage(page, true, { customParameter: c.allowModsToUnmuteUsers }); + await createParam.initModPage(page, true, { createParameter: c.allowModsToUnmuteUsers }); await createParam.allowModsToUnmuteUsers(context); }); test('Lock Settings Disable Webcam', async ({ browser, context, page }) => { const createParam = new CreateParameters(browser, context); - await createParam.initModPage(page, true, { customParameter: c.lockSettingsDisableCam }); + await createParam.initModPage(page, true, { createParameter: c.lockSettingsDisableCam }); await createParam.initUserPage(true, context); await createParam.lockSettingsDisableCam(); }); test('Lock Settings Disable Microphone', async ({ browser, context, page }) => { const createParam = new CreateParameters(browser, context); - await createParam.initModPage(page, true, { customParameter: c.lockSettingsDisableMic }); + await createParam.initModPage(page, true, { createParameter: c.lockSettingsDisableMic }); await createParam.initUserPage(false, context); await createParam.lockSettingsDisableMic(); }); test('Lock Settings Disable Public Chat', async ({ browser, context, page }) => { const createParam = new CreateParameters(browser, context); - await createParam.initModPage(page, true, { customParameter: c.lockSettingsDisablePublicChat }); + await createParam.initModPage(page, true, { createParameter: c.lockSettingsDisablePublicChat }); await createParam.initUserPage(true, context); await createParam.lockSettingsDisablePublicChat(); }); test('Lock Settings Hide User List', async ({ browser, context, page }) => { const createParam = new CreateParameters(browser, context); - await createParam.initModPage(page, true, { customParameter: c.lockSettingsHideUserList }); + await createParam.initModPage(page, true, { createParameter: c.lockSettingsHideUserList }); await createParam.initUserPage(true, context); await createParam.initUserPage2(true, context); await createParam.lockSettingsHideUserList(); @@ -96,7 +96,7 @@ test.describe.parallel('Create Parameters', () => { test('Allow Moderator To Eject Cameras', async ({ browser, context, page }) => { const createParam = new CreateParameters(browser, context); - await createParam.initModPage(page, true, { customParameter: c.allowModsToEjectCameras }); + await createParam.initModPage(page, true, { createParameter: c.allowModsToEjectCameras }); await createParam.initUserPage(true, context); await createParam.allowModsToEjectCameras(); }); @@ -105,12 +105,12 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Breakout rooms', async ({ browser, context, page}) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.breakoutRoomsDisabled }); + await disabledFeatures.initModPage(page, true, { createParameter: c.breakoutRoomsDisabled }); await disabledFeatures.breakoutRooms(); }); test('Breakout rooms (exclude)', async ({ browser, context, page}) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.breakoutRoomsExclude }); + await disabledFeatures.initModPage(page, true, { createParameter: c.breakoutRoomsExclude }); await disabledFeatures.breakoutRoomsExclude(); }); }); @@ -118,12 +118,12 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Speech Recognition', async ({ browser, context, page}) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, false, { customParameter: c.speechRecognitionDisabled }); + await disabledFeatures.initModPage(page, false, { createParameter: c.speechRecognitionDisabled }); await disabledFeatures.speechRecognition(); }); test('Speech Recognition (exclude)', async ({ browser, context, page}) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, false, { customParameter: c.speechRecognitionExclude }); + await disabledFeatures.initModPage(page, false, { createParameter: c.speechRecognitionExclude }); await disabledFeatures.speechRecognitionExclude(); }); }); @@ -131,12 +131,12 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Captions', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.captionsDisabled }); + await disabledFeatures.initModPage(page, true, { createParameter: c.captionsDisabled }); await disabledFeatures.captions(); }); test('Captions (exclude)', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.captionsExclude }); + await disabledFeatures.initModPage(page, true, { createParameter: c.captionsExclude }); await disabledFeatures.captionsExclude(); }); }); @@ -144,12 +144,12 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Chat', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.chatDisabled }); + await disabledFeatures.initModPage(page, true, { createParameter: c.chatDisabled }); await disabledFeatures.chat(); }); test('Chat (exclude)', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.chatExclude }); + await disabledFeatures.initModPage(page, true, { createParameter: c.chatExclude }); await disabledFeatures.chatExclude(); }); }); @@ -157,12 +157,12 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('External Videos', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.externalVideosDisabled }); + await disabledFeatures.initModPage(page, true, { createParameter: c.externalVideosDisabled }); await disabledFeatures.externalVideos(); }); test('External Videos (exclude)', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.externalVideosExclude }); + await disabledFeatures.initModPage(page, true, { createParameter: c.externalVideosExclude }); await disabledFeatures.externalVideosExclude(); }); }); @@ -170,12 +170,12 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Layouts', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.layoutsDisabled }); + await disabledFeatures.initModPage(page, true, { createParameter: c.layoutsDisabled }); await disabledFeatures.layouts(); }); test('Layouts (exclude)', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.layoutsExclude }); + await disabledFeatures.initModPage(page, true, { createParameter: c.layoutsExclude }); await disabledFeatures.layoutsExclude(); }); }); @@ -183,12 +183,12 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Learning Dashboard', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.learningDashboardDisabled }); + await disabledFeatures.initModPage(page, true, { createParameter: c.learningDashboardDisabled }); await disabledFeatures.learningDashboard(); }); test('Learning Dashboard (exclude)', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.learningDashboardExclude }); + await disabledFeatures.initModPage(page, true, { createParameter: c.learningDashboardExclude }); await disabledFeatures.learningDashboardExclude(); }); }); @@ -196,12 +196,12 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Polls', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.pollsDisabled }); + await disabledFeatures.initModPage(page, true, { createParameter: c.pollsDisabled }); await disabledFeatures.polls(); }); test('Polls (exclude)', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.pollsExclude }); + await disabledFeatures.initModPage(page, true, { createParameter: c.pollsExclude }); await disabledFeatures.pollsExclude(); }); }); @@ -209,12 +209,12 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Screenshare', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.screenshareDisabled }); + await disabledFeatures.initModPage(page, true, { createParameter: c.screenshareDisabled }); await disabledFeatures.screenshare(); }); test('Screenshare (exclude)', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.screenshareExclude }); + await disabledFeatures.initModPage(page, true, { createParameter: c.screenshareExclude }); await disabledFeatures.screenshareExclude(); }); }); @@ -222,12 +222,12 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Shared Notes', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.sharedNotesDisabled }); + await disabledFeatures.initModPage(page, true, { createParameter: c.sharedNotesDisabled }); await disabledFeatures.sharedNotes(); }); test('Shared Notes (exclude)', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.sharedNotesExclude }); + await disabledFeatures.initModPage(page, true, { createParameter: c.sharedNotesExclude }); await disabledFeatures.sharedNotesExclude(); }); }); @@ -235,12 +235,12 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Virtual Background', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.virtualBackgroundsDisabled }); + await disabledFeatures.initModPage(page, true, { createParameter: c.virtualBackgroundsDisabled }); await disabledFeatures.virtualBackgrounds(); }); test('Virtual Background (exclude)', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.virtualBackgroundsExclude }); + await disabledFeatures.initModPage(page, true, { createParameter: c.virtualBackgroundsExclude }); await disabledFeatures.virtualBackgroundsExclude(); }); }); @@ -248,12 +248,12 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Download Presentation With Annotations', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.downloadPresentationWithAnnotationsDisabled }); + await disabledFeatures.initModPage(page, true, { createParameter: c.downloadPresentationWithAnnotationsDisabled }); await disabledFeatures.downloadPresentationWithAnnotations(); }); test('Download Presentation With Annotations (exclude)', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.downloadPresentationWithAnnotationsExclude }); + await disabledFeatures.initModPage(page, true, { createParameter: c.downloadPresentationWithAnnotationsExclude }); await disabledFeatures.downloadPresentationWithAnnotationsExclude(); }); }); @@ -261,12 +261,12 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Import Presentation With Annotations From Breakout Rooms', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.importPresentationWithAnnotationsFromBreakoutRoomsDisabled }); + await disabledFeatures.initModPage(page, true, { createParameter: c.importPresentationWithAnnotationsFromBreakoutRoomsDisabled }); await disabledFeatures.importPresentationWithAnnotationsFromBreakoutRooms(); }); test('Import Presentation With Annotations From Breakout Rooms (exclude)', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.importPresentationWithAnnotationsFromBreakoutRoomsExclude }); + await disabledFeatures.initModPage(page, true, { createParameter: c.importPresentationWithAnnotationsFromBreakoutRoomsExclude }); await disabledFeatures.importPresentationWithAnnotationsFromBreakoutRoomsExclude(); }); }); @@ -274,12 +274,12 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Import Shared Notes From Breakout Rooms', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.importSharedNotesFromBreakoutRoomsDisabled }); + await disabledFeatures.initModPage(page, true, { createParameter: c.importSharedNotesFromBreakoutRoomsDisabled }); await disabledFeatures.importSharedNotesFromBreakoutRooms(); }); test('Import Shared Notes From Breakout Rooms (exclude)', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.importSharedNotesFromBreakoutRoomsExclude }); + await disabledFeatures.initModPage(page, true, { createParameter: c.importSharedNotesFromBreakoutRoomsExclude }); await disabledFeatures.importSharedNotesFromBreakoutRoomsExclude(); }); }); @@ -287,12 +287,12 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Presentation', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.presentationDisabled }); + await disabledFeatures.initModPage(page, true, { createParameter: c.presentationDisabled }); await disabledFeatures.presentation(); }); test('Presentation (exclude)', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.presentationExclude }); + await disabledFeatures.initModPage(page, true, { createParameter: c.presentationExclude }); await disabledFeatures.presentationExclude(); }); }); @@ -300,12 +300,12 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Custom Virtual Background', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.customVirtualBackgroundDisabled }); + await disabledFeatures.initModPage(page, true, { createParameter: c.customVirtualBackgroundDisabled }); await disabledFeatures.customVirtualBackground(); }); test('Custom Virtual Background (exclude)', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.customVirtualBackgroundExclude }); + await disabledFeatures.initModPage(page, true, { createParameter: c.customVirtualBackgroundExclude }); await disabledFeatures.customVirtualBackgroundExclude(); }); }); @@ -315,113 +315,113 @@ test.describe.parallel('Create Parameters', () => { test.describe.parallel('Custom Parameters', () => { test('Show Public Chat On Login', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.showPublicChatOnLogin }); + await customParam.initModPage(page, true, { joinParameter: c.showPublicChatOnLogin }); await customParam.showPublicChatOnLogin(); }); test('Show Participants on Login', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.showParticipantsOnLogin }); + await customParam.initModPage(page, true, { joinParameter: c.showParticipantsOnLogin }); await customParam.showParticipantsOnLogin(); }); test('Client title', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.clientTitle }); + await customParam.initModPage(page, true, { joinParameter: c.clientTitle }); await customParam.clientTitle(); }); test('Ask For Feedback On Logout', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.askForFeedbackOnLogout }); + await customParam.initModPage(page, true, { joinParameter: c.askForFeedbackOnLogout }); await customParam.askForFeedbackOnLogout(); }); test('Display Branding Area', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: `${c.displayBrandingArea}&${encodeCustomParams(c.logo)}` }); + await customParam.initModPage(page, true, { joinParameter: `${c.displayBrandingArea}&${encodeCustomParams(c.logo)}` }); await customParam.displayBrandingArea(); }); test('Shortcuts', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); const shortcutParam = getAllShortcutParams(); - await customParam.initModPage(page, true, { customParameter: encodeCustomParams(shortcutParam) }); + await customParam.initModPage(page, true, { joinParameter: encodeCustomParams(shortcutParam) }); await customParam.initUserPage(true, context, { useModMeetingId: true }); await customParam.shortcuts(); }); test('Custom Styles: CSS code @ci', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: encodeCustomParams(c.customStyle) }); + await customParam.initModPage(page, true, { joinParameter: encodeCustomParams(c.customStyle) }); await customParam.customStyle(); }); test('Custom Styles: URL', async ({ browser, context, page }) => { test.fixme(); const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: encodeCustomParams(c.customStyleUrl) }); + await customParam.initModPage(page, true, { joinParameter: encodeCustomParams(c.customStyleUrl) }); await customParam.customStyle(); }); test('Auto Swap Layout', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.autoSwapLayout }); + await customParam.initModPage(page, true, { joinParameter: c.autoSwapLayout }); await customParam.autoSwapLayout(); }); test('Hide Actions Bar @ci', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.hideActionsBar }); + await customParam.initModPage(page, true, { joinParameter: c.hideActionsBar }); await customParam.hideActionsBarTest(); }); test('Override Default Locale', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.overrideDefaultLocale }); + await customParam.initModPage(page, true, { joinParameter: c.overrideDefaultLocale }); await customParam.overrideDefaultLocaleTest(); }); test('Hide NavBar @ci', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.hideNavBar }); + await customParam.initModPage(page, true, { joinParameter: c.hideNavBar }); await customParam.hideNavBarTest(); }); test('Preferred Camera Profile', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.preferredCameraProfile }); + await customParam.initModPage(page, true, { joinParameter: c.preferredCameraProfile }); await customParam.preferredCameraProfileTest(); }); test.describe.parallel('Audio', () => { test('Auto join @ci', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, false, { customParameter: c.autoJoin }); + await customParam.initModPage(page, false, { joinParameter: c.autoJoin }); await customParam.autoJoin(); }); test('Disable Listen Only Mode @ci', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, false, { customParameter: c.listenOnlyMode }); + await customParam.initModPage(page, false, { joinParameter: c.listenOnlyMode }); await customParam.listenOnlyMode(); }); test('Force Listen Only @ci', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initUserPage(false, context, { useModMeetingId: false, customParameter: c.forceListenOnly }); + await customParam.initUserPage(false, context, { useModMeetingId: false, joinParameter: c.forceListenOnly }); await customParam.forceListenOnly(page); }); test('Skip audio check', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, false, { customParameter: c.skipCheck }); + await customParam.initModPage(page, false, { joinParameter: c.skipCheck }); await customParam.skipCheck(); }); test('Skip audio check on first join', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, false, { customParameter: c.skipCheckOnFirstJoin }); + await customParam.initModPage(page, false, { joinParameter: c.skipCheckOnFirstJoin }); await customParam.skipCheckOnFirstJoin(); }); }); @@ -429,47 +429,47 @@ test.describe.parallel('Custom Parameters', () => { test.describe.parallel('Presentation', () => { test('Hide Presentation on join @ci', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: encodeCustomParams(c.hidePresentationOnJoin) }); + await customParam.initModPage(page, true, { joinParameter: encodeCustomParams(c.hidePresentationOnJoin) }); await customParam.hidePresentationOnJoin(); }); test('Force Restore Presentation On New Events @ci', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - const customParameter = c.forceRestorePresentationOnNewEvents; - await customParam.initModPage(page, true, { customParameter }); - await customParam.forceRestorePresentationOnNewEvents(customParameter); + const joinParameter = c.forceRestorePresentationOnNewEvents; + await customParam.initModPage(page, true, { joinParameter }); + await customParam.forceRestorePresentationOnNewEvents(joinParameter); }); test('Force Restore Presentation On New Poll Result', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - const customParameter = c.forceRestorePresentationOnNewEvents; - await customParam.initModPage(page, true, { customParameter }); - await customParam.forceRestorePresentationOnNewPollResult(customParameter); + const joinParameter = c.forceRestorePresentationOnNewEvents; + await customParam.initModPage(page, true, { joinParameter }); + await customParam.forceRestorePresentationOnNewPollResult(joinParameter); }); }); test.describe.parallel('Webcam', () => { test('Disable Webcam Sharing @ci', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.enableVideo }); + await customParam.initModPage(page, true, { joinParameter: c.enableVideo }); await customParam.enableVideo(); }); test('Skip Video Preview', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.skipVideoPreview }); + await customParam.initModPage(page, true, { joinParameter: c.skipVideoPreview }); await customParam.skipVideoPreview(); }); test('Skip Video Preview on First Join @ci', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.skipVideoPreviewOnFirstJoin }); + await customParam.initModPage(page, true, { joinParameter: c.skipVideoPreviewOnFirstJoin }); await customParam.skipVideoPreviewOnFirstJoin(); }); test('Mirror Own Webcam @ci', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.mirrorOwnWebcam }); + await customParam.initModPage(page, true, { joinParameter: c.mirrorOwnWebcam }); await customParam.mirrorOwnWebcam(); }); }); @@ -478,21 +478,21 @@ test.describe.parallel('Custom Parameters', () => { test.skip(); test('Multi Users Pen Only', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.multiUserPenOnly }); + await customParam.initModPage(page, true, { joinParameter: c.multiUserPenOnly }); await customParam.initUserPage(true, context, { useModMeetingId: true, customParameter: c.multiUserPenOnly }); await customParam.multiUserPenOnly(); }); test('Presenter Tools', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: encodeCustomParams(c.presenterTools) }); + await customParam.initModPage(page, true, { joinParameter: encodeCustomParams(c.presenterTools) }); await customParam.presenterTools(); }); test('Multi Users Tools', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: encodeCustomParams(c.multiUserTools) }); - await customParam.initUserPage(true, context, { useModMeetingId: true, customParameter: encodeCustomParams(c.multiUserTools) }); + await customParam.initModPage(page, true, { joinParameter: encodeCustomParams(c.multiUserTools) }); + await customParam.initUserPage(true, context, { useModMeetingId: true, joinParameter: encodeCustomParams(c.multiUserTools) }); await customParam.multiUserTools(); }); }); From e97427a9aa328596d97527fdb776cf19957fc346 Mon Sep 17 00:00:00 2001 From: KDSBrowne Date: Tue, 11 Jul 2023 16:17:03 +0000 Subject: [PATCH 010/252] add resize event when whiteboard mounts --- .../imports/ui/components/whiteboard/component.jsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx index 81489931f1..9f0a3208af 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx @@ -26,6 +26,7 @@ const SMALL_WIDTH = 800; const SMALLEST_DOCK_WIDTH = 710; const TOOLBAR_SMALL = 28; const TOOLBAR_LARGE = 32; +const MOUNTED_RESIZE_DELAY = 1500; export default function Whiteboard(props) { const { @@ -666,7 +667,11 @@ export default function Whiteboard(props) { ); setIsMounting(true); } - + + // needed to ensure the correct calculations for cursors on mount. + setTimeout(() => { + window.dispatchEvent(new Event('resize')); + }, MOUNTED_RESIZE_DELAY); }; const onPatch = (e, t, reason) => { From a41ea56bc9d2885790a96ade4744ad89d4fd1c26 Mon Sep 17 00:00:00 2001 From: Anton B Date: Tue, 11 Jul 2023 17:54:02 -0300 Subject: [PATCH 011/252] test: update breakout test steps to follow new expected behavior --- bigbluebutton-tests/playwright/breakout/create.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bigbluebutton-tests/playwright/breakout/create.js b/bigbluebutton-tests/playwright/breakout/create.js index cea922c03f..c9b020d14a 100644 --- a/bigbluebutton-tests/playwright/breakout/create.js +++ b/bigbluebutton-tests/playwright/breakout/create.js @@ -101,15 +101,13 @@ class Create extends MultiUsers { await this.modPage.waitAndClick(e.createBreakoutRooms); //testing no user assigned - await this.modPage.waitAndClick(e.modalConfirmButton); - await this.modPage.hasElement(e.warningNoUserAssigned); - - //await this.modPage.hasElementDisabled(e.modalConfirmButton); const modalConfirmButton = this.modPage.getLocator(e.modalConfirmButton); await expect(modalConfirmButton, 'Getting error when trying to create a breakout room without designating any user.').toBeDisabled(); + await this.modPage.hasElement(e.warningNoUserAssigned); await this.modPage.dragDropSelector(e.userTest, e.breakoutBox1); await this.modPage.hasText(e.breakoutBox1, /Attendee/); + await this.modPage.wasRemoved(e.warningNoUserAssigned); await this.modPage.waitAndClick(e.modalConfirmButton, ELEMENT_WAIT_LONGER_TIME); await this.userPage.waitAndClick(e.modalConfirmButton); From db4479d6dd08a942508fb40c0a2f81057b5b6929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Thu, 13 Jul 2023 09:58:05 -0300 Subject: [PATCH 012/252] adjust quick poll char limit --- .../imports/ui/components/presentation/service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/ui/components/presentation/service.js b/bigbluebutton-html5/imports/ui/components/presentation/service.js index d693fab8da..84c76d0891 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/service.js +++ b/bigbluebutton-html5/imports/ui/components/presentation/service.js @@ -5,6 +5,7 @@ import { safeMatch } from '/imports/utils/string-utils'; const POLL_SETTINGS = Meteor.settings.public.poll; const MAX_CUSTOM_FIELDS = POLL_SETTINGS.maxCustom; +const MAX_CHAR_LIMIT = POLL_SETTINGS.maxTypedAnswerLength; const getCurrentPresentation = (podId) => Presentations.findOne({ podId, @@ -120,7 +121,6 @@ const parseCurrentSlideContent = (yesValue, noValue, abstentionValue, trueValue, if (optionsPoll) { optionsPoll = optionsPoll.map((opt) => { - const MAX_CHAR_LIMIT = 30; const formattedOpt = opt.substring(0, MAX_CHAR_LIMIT); optionsWithLabels.push(formattedOpt); return `\r${opt[0]}.`; From b4d61ec5a96d7cd318952347738bcb41ca954a0c Mon Sep 17 00:00:00 2001 From: Anton B Date: Thu, 13 Jul 2023 17:31:39 -0300 Subject: [PATCH 013/252] test: improve presentation zoom test --- bigbluebutton-tests/playwright/presentation/presentation.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bigbluebutton-tests/playwright/presentation/presentation.js b/bigbluebutton-tests/playwright/presentation/presentation.js index 635fc5965a..691822c2f7 100644 --- a/bigbluebutton-tests/playwright/presentation/presentation.js +++ b/bigbluebutton-tests/playwright/presentation/presentation.js @@ -249,8 +249,9 @@ class Presentation extends MultiUsers { //Zoom In 150% await this.modPage.waitAndClick(e.zoomInButton); - await this.modPage.waitAndClick(e.zoomInButton); await expect(zoomOutButtonLocator).toBeEnabled(); + await expect(resetZoomButtonLocator).toContainText(/125%/); + await this.modPage.waitAndClick(e.zoomInButton); await expect(resetZoomButtonLocator).toContainText(/150%/); await expect(wbBox).toHaveScreenshot('moderator1-zoom150.png'); @@ -260,8 +261,11 @@ class Presentation extends MultiUsers { await expect(wbBox).toHaveScreenshot('moderator1-zoom125.png'); //Reset Zoom 100% + await this.modPage.waitAndClick(e.zoomInButton); + await expect(resetZoomButtonLocator).toContainText(/150%/); await this.modPage.waitAndClick(e.resetZoomButton); await expect(resetZoomButtonLocator).toContainText(/100%/); + await expect(zoomOutButtonLocator).toBeDisabled(); await expect(wbBox).toHaveScreenshot('moderator1-zoom100.png'); } From 972b907f5cdde82ff8d89a83c88fa92bde415112 Mon Sep 17 00:00:00 2001 From: Gabriel Porfirio Date: Fri, 14 Jul 2023 11:57:42 -0300 Subject: [PATCH 014/252] moving testsuite console log to helpers.js --- .../playwright/core/helpers.js | 94 ++++++++++++++++--- bigbluebutton-tests/playwright/core/page.js | 73 +------------- 2 files changed, 83 insertions(+), 84 deletions(-) diff --git a/bigbluebutton-tests/playwright/core/helpers.js b/bigbluebutton-tests/playwright/core/helpers.js index a9ec41bd40..f497d7e70f 100644 --- a/bigbluebutton-tests/playwright/core/helpers.js +++ b/bigbluebutton-tests/playwright/core/helpers.js @@ -6,6 +6,9 @@ const xml2js = require('xml2js'); const { runScript } = require('./util'); const { env } = require('node:process'); +const { format } = require('node:util'); +// This is version 4 of chalk, not version 5, which uses ESM +const chalk = require('chalk'); const parameters = require('./parameters'); function getRandomInt(min, max) { @@ -50,19 +53,6 @@ async function createMeeting(params, createParameter, page) { const response = await promise; expect(response.status).toEqual(200); const xmlResponse = await xml2js.parseStringPromise(response.data); - - if (env.CONSOLE !== undefined) { - const CONSOLE_strings = env.CONSOLE.split(',').map(opt => opt.trim().toLowerCase()); - const CONSOLE_options = { - colorize: CONSOLE_strings.includes('color') || CONSOLE_strings.includes('colour'), - drop_references: CONSOLE_strings.includes('norefs'), - drop_timestamps: CONSOLE_strings.includes('nots'), - line_label: CONSOLE_strings.includes('label') ? this.username + " " : undefined, - noClientLogger: CONSOLE_strings.includes('nocl') || CONSOLE_strings.includes('noclientlogger'), - }; - page.on('console', async (msg) => console.log(await console_format(msg, CONSOLE_options))); - } - return xmlResponse.response.meetingID[0]; } @@ -82,6 +72,83 @@ async function checkRootPermission() { await expect(checkSudo, 'Sudo failed: need to run this test with root permission (can be fixed by running "sudo -v" and entering the password)').toBeTruthy(); } +async function console_format(msg, CONSOLE_options) { + const args = await Promise.all(msg.args().map(itm => itm.jsonValue())); + // For Chrome, args[0] is a format string that we will process using + // node.js's util.format, but that function discards css style + // information from "%c" format specifiers. So first loop over the + // format string, replacing every "%c" with "%s" and replacing the + // corresponding css style with an ANSI color sequence. + // + // See https://console.spec.whatwg.org/ sections 2.2.1 and 2.3.4 + + let split_arg0 = args[0].split("%"); + for (let i = 1, j = 1; i < split_arg0.length; i++, j++) { + if (split_arg0[i].startsWith('c')) { + split_arg0[i] = 's' + split_arg0[i].substr(1); + const styles = args[j].split(';'); + args[j] = ''; + for (const style of styles) { + const stdStyle = style.trim().toLowerCase(); + if (stdStyle.startsWith('color:') && CONSOLE_options.colorize) { + const color = stdStyle.substr(6).trim(); + args[j] = chalk.keyword(color)._styler.open; + } else if (stdStyle.startsWith('font-size:') && CONSOLE_options.drop_references) { + // For Chrome, we "drop references" by discarding everything after a font size change + split_arg0.length = i; + args.length = j; + } + } + } else if (split_arg0[i] == "") { + // format is "%%", so don't do special processing for + // split_arg0[i+1], and only increment i, not j + i++; // NOSONAR + } + } + args[0] = split_arg0.join('%'); + + // see playwright consoleMessage class documentation + let result = format(...args); + + if (CONSOLE_options.drop_references) { + // For Firefox, we "drop references" by discarding a URL at the end of the line + result = result.replace(/https:\/\/\S*$/, ''); + } + + if (CONSOLE_options.noClientLogger) { + result = result.replace(/clientLogger: /, ''); + } + + if (CONSOLE_options.drop_timestamps) { + // timestamp formatting is a bit complicated, with four "%s" fields and corresponding arguments, + // so just filter them out (if requested) after all the other formatting is done + result = result.replace(/\[\d\d:\d\d:\d\d:\d\d\d\d\] /, ''); + } + + if (CONSOLE_options.line_label) { + if (CONSOLE_options.colorize) { + result = chalk.keyword('green')(CONSOLE_options.line_label) + result; + } else { + result = CONSOLE_options.line_label + result; + } + } + + return result; +} + +async function setBrowserLogs(page) { + const CONSOLE_strings = env.CONSOLE.split(',').map(opt => opt.trim().toLowerCase()); + const CONSOLE_options = { + colorize: CONSOLE_strings.includes('color') || CONSOLE_strings.includes('colour'), + drop_references: CONSOLE_strings.includes('norefs'), + drop_timestamps: CONSOLE_strings.includes('nots'), + line_label: CONSOLE_strings.includes('label') ? this.username + " " : undefined, + noClientLogger: CONSOLE_strings.includes('nocl') || CONSOLE_strings.includes('noclientlogger'), + }; + + page.on('console', async (msg) => console.log(await console_format(msg, CONSOLE_options))); +} + function linkIssue(issueNumber) { test.info().annotations.push({ type: 'Issue/PR', @@ -105,3 +172,4 @@ exports.getJoinURL = getJoinURL; exports.checkRootPermission = checkRootPermission; exports.linkIssue = linkIssue; exports.sleep = sleep; +exports.setBrowserLogs = setBrowserLogs; diff --git a/bigbluebutton-tests/playwright/core/page.js b/bigbluebutton-tests/playwright/core/page.js index f745e5db2c..8853f0e928 100644 --- a/bigbluebutton-tests/playwright/core/page.js +++ b/bigbluebutton-tests/playwright/core/page.js @@ -1,10 +1,6 @@ require('dotenv').config(); const { expect, default: test } = require('@playwright/test'); const { readFileSync } = require('fs'); -const { format } = require('node:util'); - -// This is version 4 of chalk, not version 5, which uses ESM -const chalk = require('chalk'); const parameters = require('./parameters'); const helpers = require('./helpers'); @@ -14,73 +10,6 @@ const { ELEMENT_WAIT_TIME, ELEMENT_WAIT_LONGER_TIME, VIDEO_LOADING_WAIT_TIME } = const { checkElement, checkElementLengthEqualTo } = require('./util'); const { generateSettingsData, getSettings } = require('./settings'); -function formatWithCss(CONSOLE_options, ...args) { - // For Chrome, args[0] is a format string that we will process using - // node.js's util.format, but that function discards css style - // information from "%c" format specifiers. So first loop over the - // format string, replacing every "%c" with "%s" and replacing the - // corresponding css style with an ANSI color sequence. - // - // See https://console.spec.whatwg.org/ sections 2.2.1 and 2.3.4 - - let split_arg0 = args[0].split("%"); - for (let i = 1, j = 1; i < split_arg0.length; i++, j++) { - if (split_arg0[i].startsWith('c')) { - split_arg0[i] = 's' + split_arg0[i].substr(1); - const styles = args[j].split(';'); - args[j] = ''; - for (const style of styles) { - const stdStyle = style.trim().toLowerCase(); - if (stdStyle.startsWith('color:') && CONSOLE_options.colorize) { - const color = stdStyle.substr(6).trim(); - args[j] = chalk.keyword(color)._styler.open; - } else if (stdStyle.startsWith('font-size:') && CONSOLE_options.drop_references) { - // For Chrome, we "drop references" by discarding everything after a font size change - split_arg0.length = i; - args.length = j; - } - } - } else if (split_arg0[i] == "") { - // format is "%%", so don't do special processing for - // split_arg0[i+1], and only increment i, not j - i++; // NOSONAR - } - } - args[0] = split_arg0.join('%'); - return format(...args); -} - -async function console_format(msg, CONSOLE_options) { - // see playwright consoleMessage class documentation - const args = await Promise.all(msg.args().map(itm => itm.jsonValue())); - let result = formatWithCss(CONSOLE_options, ...args); - - if (CONSOLE_options.drop_references) { - // For Firefox, we "drop references" by discarding a URL at the end of the line - result = result.replace(/https:\/\/\S*$/, ''); - } - - if (CONSOLE_options.noClientLogger) { - result = result.replace(/clientLogger: /, ''); - } - - if (CONSOLE_options.drop_timestamps) { - // timestamp formatting is a bit complicated, with four "%s" fields and corresponding arguments, - // so just filter them out (if requested) after all the other formatting is done - result = result.replace(/\[\d\d:\d\d:\d\d:\d\d\d\d\] /, ''); - } - - if (CONSOLE_options.line_label) { - if (CONSOLE_options.colorize) { - result = chalk.keyword('green')(CONSOLE_options.line_label) + result; - } else { - result = CONSOLE_options.line_label + result; - } - } - - return result; -} - class Page { constructor(browser, page) { this.browser = browser; @@ -103,6 +32,8 @@ class Page { if (!isModerator) this.initParameters.moderatorPW = ''; if (fullName) this.initParameters.fullName = fullName; this.username = this.initParameters.fullName; + + if (env.CONSOLE !== undefined) await helpers.setBrowserLogs(this.page); this.meetingId = (meetingId) ? meetingId : await helpers.createMeeting(parameters, createParameter, customMeetingId, this.page); const joinUrl = helpers.getJoinURL(this.meetingId, this.initParameters, isModerator, joinParameter); From a10fbaba4f0dd35f978b1105954016b08a7087dc Mon Sep 17 00:00:00 2001 From: GuiLeme Date: Fri, 14 Jul 2023 14:55:12 -0300 Subject: [PATCH 015/252] [issue-18308] - add predefined label in result quick poll options --- .../imports/ui/components/poll/service.js | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/poll/service.js b/bigbluebutton-html5/imports/ui/components/poll/service.js index a668bbfe8e..e43373a1b1 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/service.js +++ b/bigbluebutton-html5/imports/ui/components/poll/service.js @@ -80,15 +80,39 @@ const intlMessages = defineMessages({ }, }); +const checkPollAnswersMatchFormat = (listOfAnswers, formattedLabels) => listOfAnswers.reduce( + (acc, answer) => acc && (formattedLabels.includes(answer.key[0].toLowerCase()) + || formattedLabels.includes(answer.key[0].toUpperCase())), true, +); + +const getFormattedAnswerValue = (answerText) => { + // Remove the Letter from the beginning and the following sign, if any, like so: + // "A- the answer is" -> Remove "A-" -> "the answer is" + const listOfForbiddenSignsToStart = ['.', ':', '-']; + const newText = answerText.slice(1).trim(); + if (listOfForbiddenSignsToStart.includes(newText[0])) { + return newText.slice(1).trim(); + } + return newText; +}; + +const getAlphabetList = () => Array.from(Array(26)) + .map((e, i) => i + 65).map((x) => String.fromCharCode(x)); + const getPollResultsText = (isDefaultPoll, answers, numRespondents, intl) => { let responded = 0; let resultString = ''; let optionsString = ''; + const alphabetLabels = getAlphabetList(); + const isPollAnswerMatchFormat = !isDefaultPoll + ? checkPollAnswersMatchFormat(answers, alphabetLabels) + : false; + answers.map((item) => { responded += item.numVotes; return item; - }).forEach((item) => { + }).forEach((item, index) => { const numResponded = responded === numRespondents ? numRespondents : responded; const pct = Math.round((item.numVotes / numResponded) * 100); const pctBars = '|'.repeat((pct * MAX_POLL_RESULT_BARS) / 100); @@ -99,8 +123,15 @@ const getPollResultsText = (isDefaultPoll, answers, numRespondents, intl) => { : item.key; resultString += `${translatedKey}: ${item.numVotes || 0} |${pctBars} ${pctFotmatted}\n`; } else { - resultString += `${item.id + 1}: ${item.numVotes || 0} |${pctBars} ${pctFotmatted}\n`; - optionsString += `${item.id + 1}: ${item.key}\n`; + if (isPollAnswerMatchFormat) { + resultString += `${alphabetLabels[index]}`; + const formattedAnswerValue = getFormattedAnswerValue(item.key); + optionsString += `${alphabetLabels[index]}: ${formattedAnswerValue}\n`; + } else { + resultString += `${item.id + 1}`; + optionsString += `${item.id + 1}: ${item.key}\n`; + } + resultString += `: ${item.numVotes || 0} |${pctBars} ${pctFotmatted}\n`; } }); From fa8883ee29a1f66bef7f0cad98fcbccf3f695278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Fri, 14 Jul 2023 16:47:40 -0300 Subject: [PATCH 016/252] use list for users in connections status --- .../ui/components/connection-status/modal/component.jsx | 4 ++-- .../imports/ui/components/connection-status/modal/styles.js | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/connection-status/modal/component.jsx b/bigbluebutton-html5/imports/ui/components/connection-status/modal/component.jsx index fc66142e78..caf2601ecd 100644 --- a/bigbluebutton-html5/imports/ui/components/connection-status/modal/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/connection-status/modal/component.jsx @@ -563,12 +563,12 @@ class ConnectionStatusComponent extends PureComponent { -
{this.renderConnections()}
+
    {this.renderConnections()}
{Service.isModerator() && ( -
{this.renderConnections()}
+
    {this.renderConnections()}
) } diff --git a/bigbluebutton-html5/imports/ui/components/connection-status/modal/styles.js b/bigbluebutton-html5/imports/ui/components/connection-status/modal/styles.js index 47231e28e7..156d92ef2a 100644 --- a/bigbluebutton-html5/imports/ui/components/connection-status/modal/styles.js +++ b/bigbluebutton-html5/imports/ui/components/connection-status/modal/styles.js @@ -36,7 +36,7 @@ import { Tab, Tabs, TabList, TabPanel, } from 'react-tabs'; -const Item = styled.div` +const Item = styled.li` display: flex; width: 100%; height: 4rem; @@ -322,6 +322,10 @@ const ConnectionTabPanel = styled(TabPanel)` flex-flow: column; } + & ul { + padding: 0; + } + @media ${smallOnly} { width: 100%; margin: 0; From 937d18b192192b33c5a9631ee903716ff8dd5684 Mon Sep 17 00:00:00 2001 From: KDSBrowne Date: Sat, 15 Jul 2023 15:40:27 +0000 Subject: [PATCH 017/252] improve style menu color contrast in dark mode --- .../imports/ui/components/whiteboard/styles.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/styles.js b/bigbluebutton-html5/imports/ui/components/whiteboard/styles.js index d454a91c63..a0043ec4ac 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/styles.js +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/styles.js @@ -88,6 +88,10 @@ const TldrawGlobalStyle = createGlobalStyle` border: solid ${borderSize} ${colorWhite} !important; } } + [id="TD-Styles-Color-Container"], + [id="TD-StylesMenu"] { + filter: invert(0%) hue-rotate(180deg) contrast(100%) !important; + } `} ${({ isPresenter, hasWBAccess }) => (!isPresenter && !hasWBAccess) && ` #presentationInnerWrapper div{ From e73110be30b623bdaf1478248c0a49323bfccc3d Mon Sep 17 00:00:00 2001 From: KDSBrowne Date: Sat, 15 Jul 2023 15:51:22 +0000 Subject: [PATCH 018/252] add lang attribute to locale select options --- .../imports/ui/components/common/locales-dropdown/component.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/ui/components/common/locales-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/common/locales-dropdown/component.jsx index ee95c4e699..46fbca022a 100755 --- a/bigbluebutton-html5/imports/ui/components/common/locales-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/common/locales-dropdown/component.jsx @@ -70,7 +70,7 @@ class LocalesDropdown extends PureComponent { }); return ( - ); From 2523fbc54fc2be6c7e50e8f31f5b6adb7796048a Mon Sep 17 00:00:00 2001 From: KDSBrowne Date: Sat, 15 Jul 2023 18:20:46 +0000 Subject: [PATCH 019/252] convert first column from td to th / add scope & label --- .../src/components/StatusTable.jsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/bbb-learning-dashboard/src/components/StatusTable.jsx b/bbb-learning-dashboard/src/components/StatusTable.jsx index bd2923987f..50c7faa0bc 100644 --- a/bbb-learning-dashboard/src/components/StatusTable.jsx +++ b/bbb-learning-dashboard/src/components/StatusTable.jsx @@ -204,7 +204,7 @@ class StatusTable extends React.Component { { hasSlides ? ( - + { periods.map((period) => { const { slide, start, end } = period; const padding = isRTL ? 'paddingLeft' : 'paddingRight'; @@ -260,7 +260,14 @@ class StatusTable extends React.Component { }) .map((user) => ( - +
@@ -270,7 +277,7 @@ class StatusTable extends React.Component {

{user.name}

- + { periods.map((period) => { const boundaryLeft = period.start; const boundaryRight = period.end; From 5919109a73698cbc836d8696ad4ab20014349f6d Mon Sep 17 00:00:00 2001 From: KDSBrowne Date: Sun, 16 Jul 2023 14:21:50 +0000 Subject: [PATCH 020/252] darken activity score green boxes for compliant contrast ratio --- bbb-learning-dashboard/src/components/UsersTable.jsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bbb-learning-dashboard/src/components/UsersTable.jsx b/bbb-learning-dashboard/src/components/UsersTable.jsx index 71eed2f30c..f10dd13765 100644 --- a/bbb-learning-dashboard/src/components/UsersTable.jsx +++ b/bbb-learning-dashboard/src/components/UsersTable.jsx @@ -472,12 +472,12 @@ class UsersTable extends React.Component { !user.isModerator ? ( - 0 ? '#A7F3D0' : '#e4e4e7'} /> - 2 ? '#6EE7B7' : '#e4e4e7'} /> - 4 ? '#34D399' : '#e4e4e7'} /> - 6 ? '#10B981' : '#e4e4e7'} /> - 8 ? '#059669' : '#e4e4e7'} /> - + 0 ? '#4BA381' : '#e4e4e7'} /> + 2 ? '#338866' : '#e4e4e7'} /> + 4 ? '#1A6653' : '#e4e4e7'} /> + 6 ? '#055C42' : '#e4e4e7'} /> + 8 ? '#023B34' : '#e4e4e7'} /> +   From fea3a6fb1e8dce7bd5cfa3d4cc0688a25aeb025c Mon Sep 17 00:00:00 2001 From: Paul Trudel Date: Mon, 17 Jul 2023 11:10:10 +0000 Subject: [PATCH 021/252] Upgraded tomcat to 9.0.78 --- bigbluebutton-web/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-web/gradle.properties b/bigbluebutton-web/gradle.properties index 513b22963e..14b3eefebd 100644 --- a/bigbluebutton-web/gradle.properties +++ b/bigbluebutton-web/gradle.properties @@ -3,5 +3,5 @@ gormVersion=7.1.0 gradleWrapperVersion=7.3.1 grailsGradlePluginVersion=5.0.0 groovyVersion=3.0.9 -tomcatEmbedVersion=9.0.74 +tomcatEmbedVersion=9.0.78 springVersion=2.7.12 From 6fb701045a9e7ded6a02808a498ccf6bc58bc5e5 Mon Sep 17 00:00:00 2001 From: GuiLeme Date: Thu, 13 Jul 2023 12:40:46 -0300 Subject: [PATCH 022/252] [issue-18259] - fix join audio in breakout room --- .../imports/ui/components/audio/service.js | 13 ++++++++++++- .../ui/components/breakout-room/container.jsx | 3 ++- .../imports/ui/services/audio-manager/index.js | 2 ++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/audio/service.js b/bigbluebutton-html5/imports/ui/components/audio/service.js index a3f72f2d8c..8b47e17071 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/service.js +++ b/bigbluebutton-html5/imports/ui/components/audio/service.js @@ -127,7 +127,18 @@ export default { changeInputStream: (newInputStream) => { AudioManager.inputStream = newInputStream; }, liveChangeInputDevice: (inputDeviceId) => AudioManager.liveChangeInputDevice(inputDeviceId), changeOutputDevice: (outputDeviceId, isLive) => AudioManager.changeOutputDevice(outputDeviceId, isLive), - isConnected: () => AudioManager.isConnected, + isConnectedToBreakout: () => { + const transferStatus = AudioManager.getBreakoutAudioTransferStatus(); + if (transferStatus.status + === AudioManager.BREAKOUT_AUDIO_TRANSFER_STATES.CONNECTED) return true; + return false; + }, + isConnected: () => { + const transferStatus = AudioManager.getBreakoutAudioTransferStatus(); + if (!!transferStatus.breakoutMeetingId + && transferStatus.breakoutMeetingId !== Auth.meetingID) return false; + return AudioManager.isConnected; + }, isTalking: () => AudioManager.isTalking, isHangingUp: () => AudioManager.isHangingUp, isUsingAudio: () => AudioManager.isUsingAudio(), diff --git a/bigbluebutton-html5/imports/ui/components/breakout-room/container.jsx b/bigbluebutton-html5/imports/ui/components/breakout-room/container.jsx index 59734a682c..bf042e621b 100644 --- a/bigbluebutton-html5/imports/ui/components/breakout-room/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/breakout-room/container.jsx @@ -43,7 +43,8 @@ export default withTracker((props) => { } = Service; const breakoutRooms = findBreakouts(); - const isMicrophoneUser = AudioService.isConnected() && !AudioService.isListenOnly(); + const isMicrophoneUser = (AudioService.isConnectedToBreakout() || AudioService.isConnected()) + && !AudioService.isListenOnly(); const isMeteorConnected = Meteor.status().connected; const isReconnecting = AudioService.isReconnecting(); const { diff --git a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js index 388b8a6b27..e5084cb8cc 100755 --- a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js +++ b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js @@ -829,6 +829,8 @@ class AudioManager { if (typeof breakoutMeetingId === 'string') { currentStatus.breakoutMeetingId = breakoutMeetingId; + } else { + currentStatus.breakoutMeetingId = null; } if (typeof status === 'string') { From 7964bb987712f3f9420a19dc13cd13ba23c90a2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Mon, 17 Jul 2023 10:00:51 -0300 Subject: [PATCH 023/252] lint --- .../imports/ui/components/connection-status/modal/component.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/ui/components/connection-status/modal/component.jsx b/bigbluebutton-html5/imports/ui/components/connection-status/modal/component.jsx index caf2601ecd..e89b327d12 100644 --- a/bigbluebutton-html5/imports/ui/components/connection-status/modal/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/connection-status/modal/component.jsx @@ -563,7 +563,7 @@ class ConnectionStatusComponent extends PureComponent { -
    {this.renderConnections()}
+
    {this.renderConnections()}
{Service.isModerator() && ( From a9d75b51646ab563cc52baadd0ee783ee0f611f3 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 09:56:16 -0400 Subject: [PATCH 024/252] Translate en.json in fa_IR (#18326) 100% translated source file: 'en.json' on 'fa_IR'. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- bigbluebutton-html5/public/locales/fa_IR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/public/locales/fa_IR.json b/bigbluebutton-html5/public/locales/fa_IR.json index 6a9054517f..c30ccb79c5 100644 --- a/bigbluebutton-html5/public/locales/fa_IR.json +++ b/bigbluebutton-html5/public/locales/fa_IR.json @@ -1177,7 +1177,7 @@ "app.debugWindow.form.button.copy": "رونوشت", "app.debugWindow.form.enableAutoarrangeLayoutLabel": "فعال‌سازی چیدمان خودکار", "app.debugWindow.form.enableAutoarrangeLayoutDescription": "(اگر ناحیهٔ دوربین‌ها را بکشید یا تغییر اندازه دهید غیرفعال می‌شود)", - "app.debugWindow.form.chatLoggerLabel": "آزمایش سطوح گپ گزارشات", + "app.debugWindow.form.chatLoggerLabel": "آزمایش سطوح گفتگو گزارشات", "app.debugWindow.form.button.apply": "اعمال", "app.layout.modal.title": "چیدمان‌ها", "app.layout.modal.confirm": "تایید", From 3c19c2573dc257150ffba2618dd523efe5b7a3b8 Mon Sep 17 00:00:00 2001 From: Calvin Walton Date: Mon, 17 Jul 2023 12:01:12 -0400 Subject: [PATCH 025/252] bbb-presentation-video: Update to 4.0.3 * Fix crashes in ellipse rendering --- bbb-presentation-video.placeholder.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbb-presentation-video.placeholder.sh b/bbb-presentation-video.placeholder.sh index 397447cf31..4d5d35056f 100755 --- a/bbb-presentation-video.placeholder.sh +++ b/bbb-presentation-video.placeholder.sh @@ -1,6 +1,6 @@ #!/bin/sh set -ex -RELEASE=4.0.2 +RELEASE=4.0.3 cat < Date: Mon, 17 Jul 2023 17:19:05 +0000 Subject: [PATCH 026/252] add focus trap to handle focus leaving simple modals --- .../common/modal/simple/component.jsx | 105 +++++++++++------- bigbluebutton-html5/package-lock.json | 22 ++++ bigbluebutton-html5/package.json | 1 + 3 files changed, 90 insertions(+), 38 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/common/modal/simple/component.jsx b/bigbluebutton-html5/imports/ui/components/common/modal/simple/component.jsx index d1df406058..7526b8e92a 100755 --- a/bigbluebutton-html5/imports/ui/components/common/modal/simple/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/common/modal/simple/component.jsx @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; +import FocusTrap from 'focus-trap-react'; import { withModalState } from '../base/component'; import Styled from './styles'; @@ -21,30 +22,69 @@ const propTypes = { callback: PropTypes.func, }), headerPosition: PropTypes.string, + shouldCloseOnOverlayClick: PropTypes.bool, + shouldShowCloseButton: PropTypes.bool, + overlayClassName: PropTypes.string, + modalisOpen: PropTypes.bool, }; const defaultProps = { + title: '', + dismiss: { + callback: null, + }, shouldCloseOnOverlayClick: true, shouldShowCloseButton: true, overlayClassName: 'modalOverlay', headerPosition: 'inner', + modalisOpen: false, }; class ModalSimple extends Component { constructor(props) { super(props); + this.modalRef = React.createRef(); this.handleDismiss = this.handleDismiss.bind(this); + this.handleRequestClose = this.handleRequestClose.bind(this); + this.handleOutsideClick = this.handleOutsideClick.bind(this); + } + + componentDidMount() { + document.addEventListener('mousedown', this.handleOutsideClick, false); + } + + componentWillUnmount() { + document.removeEventListener('mousedown', this.handleOutsideClick, false); } handleDismiss() { - const { - modalHide, - dismiss, - } = this.props; + const { modalHide, dismiss } = this.props; if (!dismiss || !modalHide) return; modalHide(dismiss.callback); } + handleRequestClose(event) { + const { onRequestClose } = this.props; + const closeModal = onRequestClose || this.handleDismiss; + + closeModal(); + + if (event && event.type === 'click') { + setTimeout(() => { + if (document.activeElement) { + document.activeElement.blur(); + } + }, 0); + } + } + + handleOutsideClick(e) { + const { modalisOpen } = this.props; + if (this.modalRef.current && !this.modalRef.current.contains(e.target) && modalisOpen) { + this.handleRequestClose(e); + } + } + render() { const { id, @@ -59,53 +99,42 @@ class ModalSimple extends Component { contentLabel, headerPosition, 'data-test': dataTest, + children, ...otherProps } = this.props; - const closeModal = (onRequestClose || this.handleDismiss); - - const handleRequestClose = (event) => { - closeModal(); - - if (event) { - event.persist(); - - if (event.type === 'click') { - setTimeout(() => { - document.activeElement.blur(); - }, 0); - } - } - } - return ( - - {title} - - - {this.props.children} - + +
+ + {title || ''} + + + {children} + +
+
); } diff --git a/bigbluebutton-html5/package-lock.json b/bigbluebutton-html5/package-lock.json index 0a83414c46..cc0be61686 100644 --- a/bigbluebutton-html5/package-lock.json +++ b/bigbluebutton-html5/package-lock.json @@ -3430,6 +3430,23 @@ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, + "focus-trap": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.2.tgz", + "integrity": "sha512-p6vGNNWLDGwJCiEjkSK6oERj/hEyI9ITsSwIUICBoKLlWiTWXJRfQibCwcoi50rTZdbi87qDtUlMCmQwsGSgPw==", + "requires": { + "tabbable": "^6.2.0" + } + }, + "focus-trap-react": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-10.2.1.tgz", + "integrity": "sha512-UrAKOn52lvfHF6lkUMfFhlQxFgahyNW5i6FpHWkDxAeD4FSk3iwx9n4UEA4Sims0G5WiGIi0fAyoq3/UVeNCYA==", + "requires": { + "focus-trap": "^7.5.2", + "tabbable": "^6.2.0" + } + }, "follow-redirects": { "version": "1.14.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", @@ -6768,6 +6785,11 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, + "tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" + }, "table": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", diff --git a/bigbluebutton-html5/package.json b/bigbluebutton-html5/package.json index ae95b8eb3b..31e9a9906d 100644 --- a/bigbluebutton-html5/package.json +++ b/bigbluebutton-html5/package.json @@ -47,6 +47,7 @@ "fastdom": "^1.0.10", "fibers": "^4.0.2", "flat": "^5.0.2", + "focus-trap-react": "^10.2.1", "hark": "^1.2.3", "html-to-image": "^1.9.0", "immutability-helper": "~2.8.1", From 9e87a475f2a962df42991e0eb87c67431139d8ca Mon Sep 17 00:00:00 2001 From: KDSBrowne Date: Mon, 17 Jul 2023 14:04:04 -0400 Subject: [PATCH 027/252] Update bigbluebutton-html5/imports/ui/components/common/modal/simple/component.jsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ramón Souza --- .../imports/ui/components/common/modal/simple/component.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/ui/components/common/modal/simple/component.jsx b/bigbluebutton-html5/imports/ui/components/common/modal/simple/component.jsx index 7526b8e92a..e88ffc41e2 100755 --- a/bigbluebutton-html5/imports/ui/components/common/modal/simple/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/common/modal/simple/component.jsx @@ -115,7 +115,7 @@ class ModalSimple extends Component { }} {...otherProps} > - +
Date: Mon, 17 Jul 2023 17:11:03 -0300 Subject: [PATCH 028/252] test: skip zoom test --- .../playwright/presentation/presentation.spec.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bigbluebutton-tests/playwright/presentation/presentation.spec.js b/bigbluebutton-tests/playwright/presentation/presentation.spec.js index b10241e464..9e9c633cef 100644 --- a/bigbluebutton-tests/playwright/presentation/presentation.spec.js +++ b/bigbluebutton-tests/playwright/presentation/presentation.spec.js @@ -52,7 +52,11 @@ test.describe.parallel('Presentation', () => { await presentation.hidePresentationToolbar(); }); - test('Zoom In, Zoom Out, Reset Zoom @ci', async ({ browser, context, page }) => { + /** + * temporally skipped because it's currently failing the screenshot comparisons + * due to https://github.com/bigbluebutton/bigbluebutton/issues/18232 + */ + test.skip('Zoom In, Zoom Out, Reset Zoom @ci', async ({ browser, context, page }) => { const presentation = new Presentation(browser, context); await presentation.initPages(page); await presentation.zoom(); From c5f3d07ceb1ab5c89a9638c62ef381e4e0ce5423 Mon Sep 17 00:00:00 2001 From: Paulo Lanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Mon, 17 Jul 2023 21:35:35 -0300 Subject: [PATCH 029/252] build(bbb-webrtc-sfu): v2.9.15 - build(mediasoup): 3.12.6 * See https://github.com/bigbluebutton/bbb-webrtc-sfu/releases/tag/v2.9.15 --- bbb-webrtc-sfu.placeholder.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbb-webrtc-sfu.placeholder.sh b/bbb-webrtc-sfu.placeholder.sh index c52a3946c5..e3baf72bd3 100755 --- a/bbb-webrtc-sfu.placeholder.sh +++ b/bbb-webrtc-sfu.placeholder.sh @@ -1 +1 @@ -git clone --branch v2.9.14 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu +git clone --branch v2.9.15 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu From f400c28b74609f2005316dd77d31a6122646159d Mon Sep 17 00:00:00 2001 From: Anton B Date: Tue, 18 Jul 2023 14:49:34 -0300 Subject: [PATCH 030/252] test: update connection status selector --- bigbluebutton-tests/playwright/core/elements.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-tests/playwright/core/elements.js b/bigbluebutton-tests/playwright/core/elements.js index f85c64ed6c..7ef93badef 100644 --- a/bigbluebutton-tests/playwright/core/elements.js +++ b/bigbluebutton-tests/playwright/core/elements.js @@ -337,7 +337,7 @@ exports.connectionStatusModal = 'div[data-test="connectionStatusModal"]'; exports.copyStats = 'span[data-test="copyStats"]'; exports.dataSavingScreenshare = 'input[data-test="dataSavingScreenshare"]'; exports.screenshareLocked = 'button[data-test="screenshareLocked"]'; -exports.connectionStatusItemEmpty = 'div[data-test="connectionStatusItemEmpty"]'; +exports.connectionStatusItemEmpty = 'li[data-test="connectionStatusItemEmpty"]'; exports.connectionStatusTab2 = 'li[id="react-tabs-2"]'; exports.connectionStatusItemUser = 'div[data-test="connectionStatusItemUser"]'; exports.connectionStatusLinkToSettings = `${networkDataContainer} span[role="button"]`; From 19ea1893025a1c549a8f11bd6dd3ec3b2dd0f0a9 Mon Sep 17 00:00:00 2001 From: KDSBrowne Date: Tue, 18 Jul 2023 22:55:35 +0000 Subject: [PATCH 031/252] restore messages tab stop and arrow key navigation --- .../ui/components/user-list/chat-list-item/component.jsx | 2 +- .../user-list/user-list-content/user-messages/component.jsx | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx index 647aea0aef..a3e0a2c51e 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx @@ -51,7 +51,7 @@ const propTypes = { const defaultProps = { shortcuts: '', - tabIndex: 0, + tabIndex: -1, }; const ChatListItem = (props) => { diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/component.jsx index 4dfb300ef3..c722e43f70 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/component.jsx @@ -47,13 +47,13 @@ class UserMessages extends PureComponent { this._msgsList.addEventListener( 'keydown', this.rove, + true, ); } } componentDidUpdate(prevProps, prevState) { const { selectedChat } = this.state; - if (selectedChat && selectedChat !== prevState.selectedChat) { const { firstChild } = selectedChat; if (firstChild) firstChild.focus(); @@ -97,6 +97,7 @@ class UserMessages extends PureComponent { const { selectedChat } = this.state; const msgItemsRef = findDOMNode(this._msgItems); roving(event, this.changeState, msgItemsRef, selectedChat); + event.stopPropagation(); } render() { @@ -120,7 +121,7 @@ class UserMessages extends PureComponent { { this._msgsList = ref; }} > From 0c934c72f49b8cd9b907b510cada0c61f8118041 Mon Sep 17 00:00:00 2001 From: KDSBrowne Date: Wed, 19 Jul 2023 00:23:21 +0000 Subject: [PATCH 032/252] restore captions list keyboard access --- .../user-captions/component.jsx | 58 ++++++++++++++++++- .../user-captions/container.jsx | 5 +- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-captions/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-captions/component.jsx index 039eebcc5c..ca51772081 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-captions/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-captions/component.jsx @@ -3,12 +3,15 @@ import PropTypes from 'prop-types'; import { TransitionGroup, CSSTransition } from 'react-transition-group'; import CaptionsListItem from '/imports/ui/components/user-list/captions-list-item/component'; import { defineMessages, injectIntl } from 'react-intl'; +import KEY_CODES from '/imports/utils/keyCodes'; import Styled from './styles'; +import { findDOMNode } from 'react-dom'; const propTypes = { ownedLocales: PropTypes.arrayOf(PropTypes.object).isRequired, sidebarContentPanel: PropTypes.string.isRequired, layoutContextDispatch: PropTypes.func.isRequired, + roving: PropTypes.func.isRequired, }; const intlMessages = defineMessages({ @@ -19,6 +22,53 @@ const intlMessages = defineMessages({ }); class UserCaptions extends Component { + constructor() { + super(); + + this.state = { + selectedCaption: null, + }; + + this.activeCaptionRefs = []; + + this.changeState = this.changeState.bind(this); + this.rove = this.rove.bind(this); + } + + componentDidMount() { + if (this._captionsList) { + this._captionsList.addEventListener( + 'keydown', + this.rove, + true, + ); + } + } + + componentDidUpdate(prevProps, prevState) { + const { selectedCaption } = this.state; + if (selectedCaption && selectedCaption !== prevState.selectedCaption) { + const { firstChild } = selectedCaption; + if (firstChild) firstChild.focus(); + } + } + + changeState(ref) { + this.setState({ selectedCaption: ref }); + } + + rove(event) { + const { roving } = this.props; + const { selectedCaption } = this.state; + const captionItemsRef = findDOMNode(this._captionItems); + if ([KEY_CODES.SPACE, KEY_CODES.ENTER].includes(event?.which)) { + selectedCaption?.firstChild?.click(); + } else { + roving(event, this.changeState, captionItemsRef, selectedCaption); + } + event.stopPropagation(); + } + renderCaptions() { const { ownedLocales, @@ -26,6 +76,8 @@ class UserCaptions extends Component { layoutContextDispatch, } = this.props; + let index = -1; + return ownedLocales.map((ownedLocale) => ( - + { this.activeCaptionRefs[index += 1] = node; }}> { this._msgsList = ref; }} + ref={(ref) => { this._captionsList = ref; }} > - { this._msgItems = ref; }}> + { this._captionItems = ref; }}> {this.renderCaptions()} diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-captions/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-captions/container.jsx index 7417a90340..a9d79e4980 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-captions/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-captions/container.jsx @@ -1,6 +1,7 @@ import React from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import UserCaptionsItem from './component'; +import Service from '/imports/ui/components/user-list/service'; import CaptionsService from '/imports/ui/components/captions/service'; import { layoutSelectInput, layoutDispatch } from '../../../layout/context'; @@ -8,8 +9,8 @@ const Container = (props) => { const sidebarContent = layoutSelectInput((i) => i.sidebarContent); const { sidebarContentPanel } = sidebarContent; const layoutContextDispatch = layoutDispatch(); - - return ; + const { roving } = Service; + return ; }; export default withTracker(() => ({ From 217f252c29f3537807e6997910e896804aab8653 Mon Sep 17 00:00:00 2001 From: GuiLeme Date: Wed, 19 Jul 2023 09:21:11 -0300 Subject: [PATCH 033/252] [issue-18308] - Fix deleting of first letter every time --- .../imports/ui/components/poll/service.js | 53 +++++++++++++------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/poll/service.js b/bigbluebutton-html5/imports/ui/components/poll/service.js index e43373a1b1..93911d7765 100644 --- a/bigbluebutton-html5/imports/ui/components/poll/service.js +++ b/bigbluebutton-html5/imports/ui/components/poll/service.js @@ -80,33 +80,56 @@ const intlMessages = defineMessages({ }, }); -const checkPollAnswersMatchFormat = (listOfAnswers, formattedLabels) => listOfAnswers.reduce( - (acc, answer) => acc && (formattedLabels.includes(answer.key[0].toLowerCase()) - || formattedLabels.includes(answer.key[0].toUpperCase())), true, +const getUsedLabels = (listOfAnswers, possibleLabels) => listOfAnswers.map( + (answer) => { + if (answer.key.length >= 2) { + const formattedLabel = answer.key.slice(0, 2).toUpperCase(); + if (possibleLabels.includes(formattedLabel)) { + return formattedLabel; + } + } + return undefined; + }, ); const getFormattedAnswerValue = (answerText) => { - // Remove the Letter from the beginning and the following sign, if any, like so: - // "A- the answer is" -> Remove "A-" -> "the answer is" - const listOfForbiddenSignsToStart = ['.', ':', '-']; - const newText = answerText.slice(1).trim(); - if (listOfForbiddenSignsToStart.includes(newText[0])) { - return newText.slice(1).trim(); - } + // In generatePossibleLabels there is a check to see if the + // answer's length is greater than 2 + const newText = answerText.slice(2).trim(); return newText; }; -const getAlphabetList = () => Array.from(Array(26)) +const generateAlphabetList = () => Array.from(Array(26)) .map((e, i) => i + 65).map((x) => String.fromCharCode(x)); +const generatePossibleLabels = (alphabetCharacters) => { + // Remove the Letter from the beginning and the following sign, if any, like so: + // "A- the answer is" -> Remove "A-" -> "the answer is" + const listOfForbiddenSignsToStart = ['.', ':', '-']; + + const possibleLabels = []; + for (let i = 0; i < alphabetCharacters.length; i += 1) { + for (let j = 0; j < listOfForbiddenSignsToStart.length; j += 1) { + possibleLabels.push(alphabetCharacters[i] + listOfForbiddenSignsToStart[j]); + } + } + return possibleLabels; +}; + const getPollResultsText = (isDefaultPoll, answers, numRespondents, intl) => { let responded = 0; let resultString = ''; let optionsString = ''; - const alphabetLabels = getAlphabetList(); + const alphabetCharacters = generateAlphabetList(); + const possibleLabels = generatePossibleLabels(alphabetCharacters); + + // We need to guarantee that the labels are in the correct order, and that all options have label + const pollAnswerMatchLabeledFormat = getUsedLabels(answers, possibleLabels); const isPollAnswerMatchFormat = !isDefaultPoll - ? checkPollAnswersMatchFormat(answers, alphabetLabels) + ? pollAnswerMatchLabeledFormat.reduce( + (acc, label, index) => acc && !!label && label[0] === alphabetCharacters[index][0], true, + ) : false; answers.map((item) => { @@ -124,9 +147,9 @@ const getPollResultsText = (isDefaultPoll, answers, numRespondents, intl) => { resultString += `${translatedKey}: ${item.numVotes || 0} |${pctBars} ${pctFotmatted}\n`; } else { if (isPollAnswerMatchFormat) { - resultString += `${alphabetLabels[index]}`; + resultString += `${pollAnswerMatchLabeledFormat[index][0]}`; const formattedAnswerValue = getFormattedAnswerValue(item.key); - optionsString += `${alphabetLabels[index]}: ${formattedAnswerValue}\n`; + optionsString += `${pollAnswerMatchLabeledFormat[index][0]}: ${formattedAnswerValue}\n`; } else { resultString += `${item.id + 1}`; optionsString += `${item.id + 1}: ${item.key}\n`; From fbb622ba5233a78ed02ede811193cf08afe69f9e Mon Sep 17 00:00:00 2001 From: Anton B Date: Thu, 20 Jul 2023 16:59:53 -0300 Subject: [PATCH 034/252] test: update breakout selectors --- .../components/actions-bar/create-breakout-room/component.jsx | 4 +++- .../imports/ui/components/common/toast/container.jsx | 2 +- bigbluebutton-tests/playwright/core/elements.js | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx index b115f64b42..18b70a7c96 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx @@ -1028,7 +1028,7 @@ class BreakoutRoom extends PureComponent { data-test="durationTime" /> - + { intl.formatMessage( intlMessages.minimumDurationWarnBreakout, @@ -1149,6 +1149,8 @@ class BreakoutRoom extends PureComponent { leastOneUserIsValid, } = this.state; + console.log({leastOneUserIsValid}) + return ( diff --git a/bigbluebutton-html5/imports/ui/components/common/toast/container.jsx b/bigbluebutton-html5/imports/ui/components/common/toast/container.jsx index ce060787e4..ba94c8ff22 100755 --- a/bigbluebutton-html5/imports/ui/components/common/toast/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/common/toast/container.jsx @@ -13,7 +13,7 @@ class ToastContainer extends React.Component { return ( )} + closeButton={()} autoClose={5000} toastClassName="toastClass" bodyClassName="toastBodyClass" diff --git a/bigbluebutton-tests/playwright/core/elements.js b/bigbluebutton-tests/playwright/core/elements.js index 04b2a2129a..58dbb304a7 100644 --- a/bigbluebutton-tests/playwright/core/elements.js +++ b/bigbluebutton-tests/playwright/core/elements.js @@ -64,8 +64,7 @@ exports.allowChoiceRoom = 'input[id="freeJoinCheckbox"]'; exports.labelGeneratingURL = 'span[data-test="labelGeneratingURL"]'; exports.endBreakoutRoomsButton = 'button[data-test="endBreakoutRoomsButton"]'; exports.durationTime = 'input[data-test="durationTime"]'; -exports.decreaseBreakoutTime = 'button[data-test="decreaseBreakoutTime"]'; -exports.increaseBreakoutTime = 'button[data-test="increaseBreakoutTime"]'; +exports.minimumDurationWarnBreakout = 'span[data-test="minimumDurationWarnBreakout"]'; exports.selectNumberOfRooms = 'select[id="numberOfRooms"]'; exports.roomGrid = 'div[data-test="roomGrid"] >> input'; exports.breakoutBox0 = 'div[id="breakoutBox-0"]'; @@ -182,6 +181,7 @@ exports.exporthtml = 'span[id="exporthtml"]'; // Notifications exports.smallToastMsg = 'div[data-test="toastSmallMsg"]'; +exports.closeToastBtn = 'i[data-test="closeToastBtn"]'; const currentPresentationToast = 'div[data-test="currentPresentationToast"]'; exports.currentPresentationToast = currentPresentationToast exports.notificationsTab = 'span[id="notificationTab"]'; From 8deb07137f9a1a366892a0522069d7747c833a22 Mon Sep 17 00:00:00 2001 From: Anton B Date: Thu, 20 Jul 2023 17:04:06 -0300 Subject: [PATCH 035/252] test: update Breakout - change duration time test steps --- .../playwright/breakout/create.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/bigbluebutton-tests/playwright/breakout/create.js b/bigbluebutton-tests/playwright/breakout/create.js index c9b020d14a..eeeb8805fc 100644 --- a/bigbluebutton-tests/playwright/breakout/create.js +++ b/bigbluebutton-tests/playwright/breakout/create.js @@ -54,19 +54,23 @@ class Create extends MultiUsers { await this.modPage.waitAndClick(e.createBreakoutRooms); await this.modPage.waitAndClick(e.randomlyAssign); + const createButtonLocator = this.modPage.getLocator(e.modalConfirmButton); + //test minimum 5 minutes await this.modPage.getLocator(e.durationTime).press('Backspace'); await this.modPage.getLocator(e.durationTime).press('Backspace'); await this.modPage.type(e.durationTime, '5'); - await this.modPage.waitAndClick(e.decreaseBreakoutTime); - await this.modPage.hasValue(e.durationTime, '5'); + await expect(createButtonLocator).toBeEnabled(); - await this.modPage.getLocator(e.durationTime).press('Backspace'); - await this.modPage.type(e.durationTime, '15'); - await this.modPage.waitAndClick(e.increaseBreakoutTime); + await this.modPage.page.fill(e.durationTime, '4'); + await expect(createButtonLocator).toBeDisabled(); + await this.modPage.hasElement(e.minimumDurationWarnBreakout); + + // await this.modPage.getLocator(e.durationTime).press('Backspace'); + await this.modPage.page.fill(e.durationTime, '15'); await this.modPage.waitAndClick(e.modalConfirmButton, ELEMENT_WAIT_LONGER_TIME); await this.modPage.waitAndClick(e.breakoutRoomsItem); - await this.modPage.hasText(e.breakoutRemainingTime, /15:[0-5][0-9]/, ELEMENT_WAIT_LONGER_TIME); + await this.modPage.hasText(e.breakoutRemainingTime, /14:[0-5][0-9]/, ELEMENT_WAIT_LONGER_TIME); } async changeRoomsName() { @@ -107,6 +111,7 @@ class Create extends MultiUsers { await this.modPage.dragDropSelector(e.userTest, e.breakoutBox1); await this.modPage.hasText(e.breakoutBox1, /Attendee/); + await expect(modalConfirmButton).toBeEnabled(); await this.modPage.wasRemoved(e.warningNoUserAssigned); await this.modPage.waitAndClick(e.modalConfirmButton, ELEMENT_WAIT_LONGER_TIME); await this.userPage.waitAndClick(e.modalConfirmButton); From cdf3c0d2fbd1221debc02587927d0422e290f8b0 Mon Sep 17 00:00:00 2001 From: Anton B Date: Thu, 20 Jul 2023 17:07:18 -0300 Subject: [PATCH 036/252] test(breakout): remove all notifications before ending rooms and temporally avoid screenshot comparison due to a reported bug --- .../create-breakout-room/component.jsx | 2 -- .../playwright/breakout/join.js | 22 ++++++++++++++----- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx index 18b70a7c96..d3a03b9722 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx @@ -1149,8 +1149,6 @@ class BreakoutRoom extends PureComponent { leastOneUserIsValid, } = this.state; - console.log({leastOneUserIsValid}) - return ( diff --git a/bigbluebutton-tests/playwright/breakout/join.js b/bigbluebutton-tests/playwright/breakout/join.js index 0ba888436c..08125b6612 100644 --- a/bigbluebutton-tests/playwright/breakout/join.js +++ b/bigbluebutton-tests/playwright/breakout/join.js @@ -157,6 +157,10 @@ class Join extends Create { await this.modPage.waitAndClick(e.breakoutRoomsItem); await this.modPage.waitAndClick(e.breakoutOptionsMenu); + // close all notifications displayed before ending rooms + for (const closeButton of await this.modPage.getLocator(e.closeToastBtn).all()) { + await closeButton.click(); + } await this.modPage.waitAndClick(e.endAllBreakouts); await this.modPage.hasElement(e.presentationUploadProgressToast); @@ -164,7 +168,7 @@ class Join extends Create { const shareNotesPDF = await this.modPage.getLocatorByIndex(e.actionsItem, 1); await expect(shareNotesPDF).toHaveText(/Notes/, { timeout: 30000 }); await expect(this.modPage.getLocatorByIndex(e.actionsItem, 2)).toHaveText("Upload/Manage presentations"); //This checks if no other content was exported. - await this.modPage.checkElementCount(e.actionsItem, 10); + await this.modPage.checkElementCount(e.actionsItem, 9); await shareNotesPDF.click(); const wbBox = await this.modPage.getLocator(e.whiteboard); @@ -196,6 +200,10 @@ class Join extends Create { await this.modPage.waitAndClick(e.breakoutRoomsItem); await this.modPage.waitAndClick(e.breakoutOptionsMenu); + // close all notifications displayed before ending rooms + for (const closeButton of await this.modPage.getLocator(e.closeToastBtn).all()) { + await closeButton.click(); + } await this.modPage.waitAndClick(e.endAllBreakouts); await this.modPage.waitForSelector(e.presentationUploadProgressToast, ELEMENT_WAIT_LONGER_TIME); @@ -203,15 +211,17 @@ class Join extends Create { const whiteboardPDF = await this.modPage.getLocatorByIndex(e.actionsItem, 1); await expect(whiteboardPDF).toHaveText(/Whiteboard/, { timeout: 30000 }); await expect(this.modPage.getLocatorByIndex(e.actionsItem, 2)).toHaveText("Upload/Manage presentations"); //This checks if no other content was exported. - await this.modPage.checkElementCount(e.actionsItem, 10); + await this.modPage.checkElementCount(e.actionsItem, 9); await whiteboardPDF.click(); await this.modPage.waitAndClick('i[type="info"]'); await this.modPage.waitAndClick(e.currentPresentationToast); - const wbBox = await this.modPage.getLocator(e.whiteboard); - await expect(wbBox).toHaveScreenshot('capture-breakout-whiteboard.png', { - maxDiffPixels: 1000, - }); + //! below lines commented due to https://github.com/bigbluebutton/bigbluebutton/issues/18233 + //! once it's fixed, re-add lines to the code + // const wbBox = await this.modPage.getLocator(e.whiteboard); + // await expect(wbBox).toHaveScreenshot('capture-breakout-whiteboard.png', { + // maxDiffPixels: 1000, + // }); } async userCanChooseRoom() { From 3886f8c273c050a867a9e6e322228412a60cfebd Mon Sep 17 00:00:00 2001 From: Gabriel Porfirio Date: Fri, 21 Jul 2023 09:43:52 -0300 Subject: [PATCH 037/252] adding a check for the fit to with button on/off --- .../playwright/presentation/presentation.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bigbluebutton-tests/playwright/presentation/presentation.js b/bigbluebutton-tests/playwright/presentation/presentation.js index fe2ce79d9e..d074892453 100644 --- a/bigbluebutton-tests/playwright/presentation/presentation.js +++ b/bigbluebutton-tests/playwright/presentation/presentation.js @@ -106,7 +106,16 @@ class Presentation extends MultiUsers { await this.modPage.waitAndClick(e.userListToggleBtn); await uploadSinglePresentation(this.modPage, e.uploadPresentationFileName); const width1 = (await this.modPage.getElementBoundingBox(e.whiteboard)).width; + // check if its off + const fitToWidthButtonLocator = this.modPage.getLocator(`${e.fitToWidthButton} > span>>nth=0`); + const fitToWidthBorderColorOff = await fitToWidthButtonLocator.evaluate((elem) => getComputedStyle(elem).borderColor); + await expect(fitToWidthBorderColorOff).toBe('rgba(0, 0, 0, 0)'); + await this.modPage.waitAndClick(e.fitToWidthButton); + //check if its on + const fitToWidthBorderColorOn = await fitToWidthButtonLocator.evaluate((elem) => getComputedStyle(elem).borderColor); + await expect(fitToWidthBorderColorOn).toBe('rgb(6, 23, 42)'); + const width2 = (await this.modPage.getElementBoundingBox(e.whiteboard)).width; await expect(Number(width2) > Number(width1)).toBeTruthy(); } From a5eb537cab7e273054b7ed934de371dad9ff8eaf Mon Sep 17 00:00:00 2001 From: Anton B Date: Fri, 21 Jul 2023 17:39:33 -0300 Subject: [PATCH 038/252] test(notes): check unpin notes and cover bug when other presenter unpin --- .../playwright/sharednotes/sharednotes.js | 20 +++++++++++++++---- .../sharednotes/sharednotes.spec.js | 4 ++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/bigbluebutton-tests/playwright/sharednotes/sharednotes.js b/bigbluebutton-tests/playwright/sharednotes/sharednotes.js index fb9d22bbb4..efa37a13f0 100644 --- a/bigbluebutton-tests/playwright/sharednotes/sharednotes.js +++ b/bigbluebutton-tests/playwright/sharednotes/sharednotes.js @@ -191,26 +191,38 @@ class SharedNotes extends MultiUsers { await this.userPage.wasRemoved(e.hideNotesLabel); } - async pinNotesOntoWhiteboard() { + async pinAndUnpinNotesOntoWhiteboard() { const { sharedNotesEnabled } = getSettings(); test.fail(!sharedNotesEnabled, 'Shared notes is disabled'); await this.userPage.waitAndClick(e.minimizePresentation); await this.userPage.hasElement(e.restorePresentation); - await startSharedNotes(this.modPage); await this.modPage.waitAndClick(e.notesOptions); await this.modPage.waitAndClick(e.pinNotes); await this.modPage.hasElement(e.unpinNotes); await this.userPage.hasElement(e.minimizePresentation); - const notesLocator = getNotesLocator(this.modPage); await notesLocator.type('Hello'); const notesLocatorUser = getNotesLocator(this.userPage); - await expect(notesLocator).toContainText(/Hello/, { timeout: 20000 }); await expect(notesLocatorUser).toContainText(/Hello/); + + // unpin notes + await this.modPage.waitAndClick(e.unpinNotes); + await this.modPage.hasElement(e.whiteboard); + await this.userPage.hasElement(e.whiteboard); + await startSharedNotes(this.modPage); + await this.modPage.waitAndClick(e.notesOptions); + await this.modPage.waitAndClick(e.pinNotes); + await this.modPage.hasElement(e.unpinNotes); + // make viewer as presenter and unpin pinned notes + await this.modPage.waitAndClick(e.userListItem); + await this.modPage.waitAndClick(e.makePresenter); + await this.userPage.waitAndClick(e.unpinNotes); + await this.userPage.hasElement(e.whiteboard); + await this.modPage.hasElement(e.whiteboard); } async editMessage() { diff --git a/bigbluebutton-tests/playwright/sharednotes/sharednotes.spec.js b/bigbluebutton-tests/playwright/sharednotes/sharednotes.spec.js index 0241c381ce..5d1f00da6f 100644 --- a/bigbluebutton-tests/playwright/sharednotes/sharednotes.spec.js +++ b/bigbluebutton-tests/playwright/sharednotes/sharednotes.spec.js @@ -38,7 +38,7 @@ test.describe.parallel('Shared Notes', () => { await sharedNotes.seeNotesWithoutEditPermission(); }); - test('Pin notes onto whiteboard', async () => { - await sharedNotes.pinNotesOntoWhiteboard(); + test('Pin and unpin notes onto whiteboard', async () => { + await sharedNotes.pinAndUnpinNotesOntoWhiteboard(); }); }); From 077cbf87937ed02a2fdbda891e9a83ac010a8737 Mon Sep 17 00:00:00 2001 From: Paulo Lanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Fri, 21 Jul 2023 17:50:04 -0300 Subject: [PATCH 039/252] build(bbb-webrtc-sfu): v2.10.0 Full changelog: https://github.com/bigbluebutton/bbb-webrtc-sfu/releases/tag/v2.10.0 --- bbb-webrtc-sfu.placeholder.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbb-webrtc-sfu.placeholder.sh b/bbb-webrtc-sfu.placeholder.sh index 798c4549f3..d62240a39a 100755 --- a/bbb-webrtc-sfu.placeholder.sh +++ b/bbb-webrtc-sfu.placeholder.sh @@ -1 +1 @@ -git clone --branch v2.10.0-alpha.6 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu +git clone --branch v2.10.0 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu From 3dec9de44e330de7eb02a8765475c603449d7a40 Mon Sep 17 00:00:00 2001 From: KDSBrowne Date: Mon, 24 Jul 2023 00:03:22 +0000 Subject: [PATCH 040/252] optimize action creation and improved question / url regex --- .../smart-video-share/component.jsx | 29 ++++++++++++------- .../ui/components/presentation/service.js | 2 +- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/smart-video-share/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/smart-video-share/component.jsx index ae12bdd724..56dec36a19 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/smart-video-share/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/smart-video-share/component.jsx @@ -12,25 +12,32 @@ const intlMessages = defineMessages({ }, }); +const createAction = (url) => { + const hasHttps = url?.startsWith("https://"); + const finalUrl = hasHttps ? url : `https://${url}`; + const label = hasHttps ? url?.replace("https://", "") : url; + + if (isUrlValid(finalUrl)) { + return { + label, + onClick: () => startWatching(finalUrl), + }; + } +}; + export const SmartMediaShare = (props) => { const { currentSlide, intl, isMobile, isRTL, } = props; - const linkPatt = /(https?:\/\/.*[ ]$)/g; + const linkPatt = /(https?:\/\/.*?)(?=\s|$)/g; const externalLinks = safeMatch(linkPatt, currentSlide?.content?.replace(/[\r\n]/g, ' '), false); if (!externalLinks) return null; - const lnkParts = externalLinks[0]?.split(' ')?.filter(s => !s?.includes(' ')).join(''); const actions = []; - - const splitLink = lnkParts?.split('https://'); - splitLink.forEach((l) => { - if (isUrlValid(`https://${l}`)) { - actions.push({ - label: l, - onClick: () => startWatching(`https://${l}`), - }); - } + + externalLinks?.forEach((l) => { + const action = createAction(l); + if (action) actions.push(action); }); if (actions?.length === 0) return null; diff --git a/bigbluebutton-html5/imports/ui/components/presentation/service.js b/bigbluebutton-html5/imports/ui/components/presentation/service.js index 84c76d0891..a8b415d7c4 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/service.js +++ b/bigbluebutton-html5/imports/ui/components/presentation/service.js @@ -92,7 +92,7 @@ const parseCurrentSlideContent = (yesValue, noValue, abstentionValue, trueValue, content, } = currentSlide; - const questionRegex = /^[\s\S]+\?\s*$/gm; + const questionRegex = /(?:^|\n)([A-Z][^\n?]*\?)/gm; const question = safeMatch(questionRegex, content, ''); if (question?.length > 0) { From 1b2cf21066e60111e5708a52bed16b9a3ccc7448 Mon Sep 17 00:00:00 2001 From: KDSBrowne Date: Mon, 24 Jul 2023 01:09:45 +0000 Subject: [PATCH 041/252] refactor smart slide question extraction --- .../ui/components/presentation/service.js | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/presentation/service.js b/bigbluebutton-html5/imports/ui/components/presentation/service.js index a8b415d7c4..8e9112fbb1 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/service.js +++ b/bigbluebutton-html5/imports/ui/components/presentation/service.js @@ -92,8 +92,34 @@ const parseCurrentSlideContent = (yesValue, noValue, abstentionValue, trueValue, content, } = currentSlide; - const questionRegex = /(?:^|\n)([A-Z][^\n?]*\?)/gm; - const question = safeMatch(questionRegex, content, ''); + let lines = content.split('\n'); + let questions = []; + let questionLines = []; + + for (let line of lines) { + let startsWithCapital = /^[A-Z]/.test(line); + let isEndOfQuestion = /\?$/.test(line); + + if (startsWithCapital) { + if (questionLines.length > 0) { + questions.push(questionLines.join(' ')); + } + questionLines = []; + } + + questionLines.push(line.trim()); + + if (isEndOfQuestion) { + questions.push(questionLines.join(' ')); + questionLines = []; + } + } + + if (questionLines.length > 0) { + questions.push(questionLines.join(' ')); + } + + const question = questions.filter(q => /^[A-Z].*\?$/.test(q?.trim())); if (question?.length > 0) { question[0] = question[0]?.replace(/\n/g, ' '); From 0b4f84290260844e60334752281a17ca92600df4 Mon Sep 17 00:00:00 2001 From: Gabriel Porfirio Date: Mon, 24 Jul 2023 09:22:28 -0300 Subject: [PATCH 042/252] few small suggestions --- bigbluebutton-tests/playwright/presentation/presentation.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-tests/playwright/presentation/presentation.js b/bigbluebutton-tests/playwright/presentation/presentation.js index d074892453..c5f9e3091c 100644 --- a/bigbluebutton-tests/playwright/presentation/presentation.js +++ b/bigbluebutton-tests/playwright/presentation/presentation.js @@ -104,7 +104,6 @@ class Presentation extends MultiUsers { async fitToWidthTest() { await this.modPage.waitForSelector(e.whiteboard, ELEMENT_WAIT_LONGER_TIME); await this.modPage.waitAndClick(e.userListToggleBtn); - await uploadSinglePresentation(this.modPage, e.uploadPresentationFileName); const width1 = (await this.modPage.getElementBoundingBox(e.whiteboard)).width; // check if its off const fitToWidthButtonLocator = this.modPage.getLocator(`${e.fitToWidthButton} > span>>nth=0`); @@ -112,12 +111,14 @@ class Presentation extends MultiUsers { await expect(fitToWidthBorderColorOff).toBe('rgba(0, 0, 0, 0)'); await this.modPage.waitAndClick(e.fitToWidthButton); + await this.modPage.wasRemoved(e.chatButton); + //check if its on const fitToWidthBorderColorOn = await fitToWidthButtonLocator.evaluate((elem) => getComputedStyle(elem).borderColor); await expect(fitToWidthBorderColorOn).toBe('rgb(6, 23, 42)'); const width2 = (await this.modPage.getElementBoundingBox(e.whiteboard)).width; - await expect(Number(width2) > Number(width1)).toBeTruthy(); + await expect(Number(width2)).toBeGreaterThan(Number(width1)); } async enableAndDisablePresentationDownload(testInfo) { From e915799d45349badd4170924d991dfb11c4e64f3 Mon Sep 17 00:00:00 2001 From: Scroody Date: Mon, 24 Jul 2023 11:55:43 -0300 Subject: [PATCH 043/252] Client: Re-writing of the meeting ending to make navigation more clear for the users --- bigbluebutton-html5/public/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/public/locales/en.json b/bigbluebutton-html5/public/locales/en.json index b040379440..ab7575b6e7 100755 --- a/bigbluebutton-html5/public/locales/en.json +++ b/bigbluebutton-html5/public/locales/en.json @@ -227,7 +227,7 @@ "app.meeting.endedByUserMessage": "This session was ended by {0}", "app.meeting.endedByNoModeratorMessageSingular": "The meeting has ended because no moderator has been present for one minute", "app.meeting.endedByNoModeratorMessagePlural": "The meeting has ended because no moderator has been present for {0} minutes", - "app.meeting.endedMessage": "You will be forwarded back to the home screen", + "app.meeting.endedMessage": "Press the button to continue to home screen.", "app.meeting.alertMeetingEndsUnderMinutesSingular": "Meeting is closing in one minute.", "app.meeting.alertMeetingEndsUnderMinutesPlural": "Meeting is closing in {0} minutes.", "app.meeting.alertBreakoutEndsUnderMinutesPlural": "Breakout is closing in {0} minutes.", From a4058cc98844a869da5e5a1204078fed32d48468 Mon Sep 17 00:00:00 2001 From: Anton Georgiev Date: Mon, 24 Jul 2023 11:54:24 -0400 Subject: [PATCH 044/252] chore: bump BBB version to 2.7.0-beta.2 --- bigbluebutton-config/bigbluebutton-release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-config/bigbluebutton-release b/bigbluebutton-config/bigbluebutton-release index 393f3d4513..fa44a5591a 100644 --- a/bigbluebutton-config/bigbluebutton-release +++ b/bigbluebutton-config/bigbluebutton-release @@ -1 +1 @@ -BIGBLUEBUTTON_RELEASE=2.7.0-beta.1 +BIGBLUEBUTTON_RELEASE=2.7.0-beta.2 From 082bbf3178b61144e6e7381b9133afeee621d535 Mon Sep 17 00:00:00 2001 From: Anton Georgiev Date: Mon, 24 Jul 2023 12:11:41 -0400 Subject: [PATCH 045/252] docs: added link to 2.7.0-beta.2 --- docs/docs/new-features.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/new-features.md b/docs/docs/new-features.md index 9cb76e7c91..a9a713682c 100644 --- a/docs/docs/new-features.md +++ b/docs/docs/new-features.md @@ -118,6 +118,7 @@ For full details on what is new in BigBlueButton 2.7, see the release notes. Recent releases: +- [2.7.0-beta.2](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.7.0-beta.2) - [2.7.0-beta.1](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.7.0-beta.1) - [2.7.0-alpha.3](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.7.0-alpha.3) - [2.7.0-alpha.2](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.7.0-alpha.2) From 356a3ea6855e4f05cdfa90e7ab358f121e817369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Mon, 24 Jul 2023 13:21:45 -0300 Subject: [PATCH 046/252] update layout labels --- bigbluebutton-html5/public/locales/en.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bigbluebutton-html5/public/locales/en.json b/bigbluebutton-html5/public/locales/en.json index b040379440..965d954f42 100755 --- a/bigbluebutton-html5/public/locales/en.json +++ b/bigbluebutton-html5/public/locales/en.json @@ -1239,7 +1239,7 @@ "app.debugWindow.form.enableAutoarrangeLayoutDescription": "(it will be disabled if you drag or resize the webcams area)", "app.debugWindow.form.chatLoggerLabel": "Test Chat Logger Levels", "app.debugWindow.form.button.apply": "Apply", - "app.layout.modal.title": "Where do you want the presentation?", + "app.layout.modal.title": "Layouts", "app.layout.modal.update": "Update", "app.layout.modal.updateAll": "Update everyone", "app.layout.modal.layoutLabel": "Select your layout", @@ -1247,10 +1247,10 @@ "app.layout.modal.layoutToastLabel": "Layout settings changed", "app.layout.modal.layoutSingular": "Layout", "app.layout.modal.layoutBtnDesc": "Sets layout as selected option", - "app.layout.style.custom": "Shared", - "app.layout.style.smart": "Dynamic", - "app.layout.style.presentationFocus": "Larger", - "app.layout.style.videoFocus": "Smaller", + "app.layout.style.custom": "Custom layout", + "app.layout.style.smart": "Smart layout", + "app.layout.style.presentationFocus": "Focus on presentation", + "app.layout.style.videoFocus": "Grid layout", "app.layout.style.customPush": "Custom (push layout to all)", "app.layout.style.smartPush": "Smart layout (push layout to all)", "app.layout.style.presentationFocusPush": "Focus on presentation (push layout to all)", From a40c204d2692c3550011edd426010e4e5b6969fd Mon Sep 17 00:00:00 2001 From: Gabriel Porfirio Date: Mon, 24 Jul 2023 13:52:15 -0300 Subject: [PATCH 047/252] add sleep() function to wait fit whiteboard --- bigbluebutton-tests/playwright/presentation/presentation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-tests/playwright/presentation/presentation.js b/bigbluebutton-tests/playwright/presentation/presentation.js index c5f9e3091c..f8d3deef99 100644 --- a/bigbluebutton-tests/playwright/presentation/presentation.js +++ b/bigbluebutton-tests/playwright/presentation/presentation.js @@ -111,7 +111,7 @@ class Presentation extends MultiUsers { await expect(fitToWidthBorderColorOff).toBe('rgba(0, 0, 0, 0)'); await this.modPage.waitAndClick(e.fitToWidthButton); - await this.modPage.wasRemoved(e.chatButton); + await sleep(500); //check if its on const fitToWidthBorderColorOn = await fitToWidthButtonLocator.evaluate((elem) => getComputedStyle(elem).borderColor); From 42557eee208edb1ca2e17607947e05509ad6c1ff Mon Sep 17 00:00:00 2001 From: Anton Georgiev Date: Mon, 24 Jul 2023 13:29:24 -0400 Subject: [PATCH 048/252] chore: Bump bbb-pads to 1.5.0 --- bbb-pads.placeholder.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbb-pads.placeholder.sh b/bbb-pads.placeholder.sh index 72972e1a12..7e7e5dae33 100755 --- a/bbb-pads.placeholder.sh +++ b/bbb-pads.placeholder.sh @@ -1 +1 @@ -git clone --branch v1.4.1 --depth 1 https://github.com/bigbluebutton/bbb-pads bbb-pads +git clone --branch v1.5.0 --depth 1 https://github.com/bigbluebutton/bbb-pads bbb-pads From bf32c51f957dd50931689050bf8b09171eda626c Mon Sep 17 00:00:00 2001 From: Anton B Date: Mon, 24 Jul 2023 16:21:55 -0300 Subject: [PATCH 049/252] test: remove disabled features code duplicated --- .../playwright/parameters/customparameters.js | 195 ------------------ .../playwright/parameters/disabledFeatures.js | 32 ++- .../playwright/parameters/parameters.spec.js | 16 +- 3 files changed, 35 insertions(+), 208 deletions(-) diff --git a/bigbluebutton-tests/playwright/parameters/customparameters.js b/bigbluebutton-tests/playwright/parameters/customparameters.js index 68f5d7e7df..8b5f757060 100644 --- a/bigbluebutton-tests/playwright/parameters/customparameters.js +++ b/bigbluebutton-tests/playwright/parameters/customparameters.js @@ -230,201 +230,6 @@ class CustomParameters extends MultiUsers { expect(await this.modPage.getLocator(e.selectCameraQualityId).inputValue()).toBe('low'); await this.modPage.waitAndClick(e.startSharingWebcam); } - - async breakoutRooms() { - await this.modPage.waitAndClick(e.manageUsers); - await this.modPage.wasRemoved(e.createBreakoutRooms); - } - - async speechRecognition() { - await this.modPage.waitForSelector(e.audioModal, ELEMENT_WAIT_LONGER_TIME); - await this.modPage.wasRemoved(e.speechRecognition); - } - - async captions() { - await this.modPage.waitAndClick(e.manageUsers); - await this.modPage.wasRemoved(e.writeClosedCaptions); - } - - async chat() { - await this.modPage.wasRemoved(e.publicChat); - } - - async externalVideos() { - await this.modPage.waitAndClick(e.actions); - await this.modPage.wasRemoved(e.shareExternalVideoBtn); - } - - async layouts() { - await this.modPage.waitAndClick(e.actions); - await this.modPage.wasRemoved(e.layoutModal); - } - - async learningDashboard() { - await this.modPage.waitAndClick(e.manageUsers); - await this.modPage.wasRemoved(e.learningDashboard); - } - - async polls() { - await this.modPage.waitAndClick(e.actions); - await this.modPage.wasRemoved(e.polling); - } - - async screenshare() { - await this.modPage.wasRemoved(e.startScreenSharing); - } - - async sharedNotes() { - await this.modPage.wasRemoved(e.sharedNotes); - } - - async virtualBackgrounds() { - await this.modPage.waitAndClick(e.joinVideo); - await this.modPage.wasRemoved(e.virtualBackgrounds); - } - - async downloadPresentationWithAnnotations() { - await this.modPage.waitAndClick(e.actions); - await this.modPage.waitAndClick(e.managePresentations); - await this.modPage.waitAndClick(e.presentationOptionsDownloadBtn); - await this.modPage.wasRemoved(e.sendPresentationInCurrentStateBtn); - } - - async importPresentationWithAnnotationsFromBreakoutRooms() { - await this.modPage.waitAndClick(e.manageUsers); - await this.modPage.waitAndClick(e.createBreakoutRooms); - await this.modPage.wasRemoved(e.captureBreakoutWhiteboard); - } - - async importSharedNotesFromBreakoutRooms() { - await this.modPage.waitAndClick(e.manageUsers); - await this.modPage.waitAndClick(e.createBreakoutRooms); - await this.modPage.wasRemoved(e.captureBreakoutSharedNotes); - } - - async presentation() { - await this.modPage.wasRemoved(e.whiteboard); - await this.modPage.wasRemoved(e.minimizePresentation); - await this.modPage.wasRemoved(e.restorePresentation); - } - - async customVirtualBackground() { - await this.modPage.waitAndClick(e.joinVideo); - await this.modPage.waitForSelector(e.webcamSettingsModal); - await this.modPage.wasRemoved(e.inputBackgroundButton); - } - - async slideSnapshot() { - await this.modPage.waitForSelector(e.whiteboard); - await this.modPage.waitAndClick(e.whiteboardOptionsButton); - await this.modPage.hasElement(e.presentationFullscreen); - await this.modPage.wasRemoved(e.presentationSnapshot); - } - - async cameraAsContent() { - await this.modPage.waitForSelector(e.whiteboard); - await this.modPage.waitAndClick(e.actions); - await this.modPage.hasElement(e.managePresentations); - await this.modPage.wasRemoved(e.shareCameraAsContent); - } - - // Disabled Features Exclude - async breakoutRoomsExclude() { - await this.modPage.waitAndClick(e.manageUsers); - await this.modPage.hasElement(e.createBreakoutRooms); - } - - async speechRecognitionExclude() { - const { speechRecognitionEnabled } = getSettings(); - test.fail(!speechRecognitionEnabled, 'Live Transcription is disabled'); - await this.modPage.waitForSelector(e.audioModal, ELEMENT_WAIT_LONGER_TIME); - await this.modPage.hasElement(e.speechRecognition); - } - - async captionsExclude() { - await this.modPage.waitAndClick(e.manageUsers); - await this.modPage.hasElement(e.writeClosedCaptions); - } - - async chatExclude() { - await this.modPage.hasElement(e.publicChat); - } - - async externalVideosExclude() { - await this.modPage.waitAndClick(e.actions); - await this.modPage.hasElement(e.shareExternalVideoBtn); - } - - async layoutsExclude() { - await this.modPage.waitAndClick(e.actions); - await this.modPage.hasElement(e.layoutModal); - } - - async learningDashboardExclude() { - await this.modPage.waitAndClick(e.manageUsers); - await this.modPage.hasElement(e.learningDashboard); - } - - async pollsExclude() { - await this.modPage.waitAndClick(e.actions); - await this.modPage.hasElement(e.polling); - } - - async screenshareExclude() { - await this.modPage.hasElement(e.startScreenSharing); - } - - async sharedNotesExclude() { - await this.modPage.hasElement(e.sharedNotes); - } - - async virtualBackgroundsExclude() { - await this.modPage.waitAndClick(e.joinVideo); - await this.modPage.hasElement(e.virtualBackgrounds); - } - - async downloadPresentationWithAnnotationsExclude() { - await this.modPage.waitAndClick(e.actions); - await this.modPage.waitAndClick(e.managePresentations); - await this.modPage.waitAndClick(e.presentationOptionsDownloadBtn); - await this.modPage.hasElement(e.sendPresentationInCurrentStateBtn); - } - - async importPresentationWithAnnotationsFromBreakoutRoomsExclude() { - await this.modPage.waitAndClick(e.manageUsers); - await this.modPage.waitAndClick(e.createBreakoutRooms); - await this.modPage.hasElement(e.captureBreakoutWhiteboard); - } - - async importSharedNotesFromBreakoutRoomsExclude() { - await this.modPage.waitAndClick(e.manageUsers); - await this.modPage.waitAndClick(e.createBreakoutRooms); - await this.modPage.hasElement(e.captureBreakoutSharedNotes); - } - - async presentationExclude() { - await this.modPage.hasElement(e.whiteboard); - await this.modPage.waitAndClick(e.minimizePresentation); - await this.modPage.hasElement(e.restorePresentation); - } - - async customVirtualBackgroundExclude() { - await this.modPage.waitAndClick (e.joinVideo); - await this.modPage.waitForSelector(e.webcamSettingsModal); - await this.modPage.hasElement(e.inputBackgroundButton); - } - - async slideSnapshotExclude() { - await this.modPage.waitForSelector(e.whiteboard); - await this.modPage.waitAndClick(e.whiteboardOptionsButton); - await this.modPage.hasElement(e.presentationSnapshot); - } - - async cameraAsContentExclude() { - await this.modPage.waitForSelector(e.whiteboard); - await this.modPage.waitAndClick(e.actions); - await this.modPage.hasElement(e.shareCameraAsContent); - } } exports.CustomParameters = CustomParameters; diff --git a/bigbluebutton-tests/playwright/parameters/disabledFeatures.js b/bigbluebutton-tests/playwright/parameters/disabledFeatures.js index abfddc8e3f..d64e858719 100644 --- a/bigbluebutton-tests/playwright/parameters/disabledFeatures.js +++ b/bigbluebutton-tests/playwright/parameters/disabledFeatures.js @@ -1,11 +1,7 @@ -const { expect, default: test } = require('@playwright/test'); const { MultiUsers } = require('../user/multiusers'); const e = require('../core/elements'); -const c = require('./constants'); -const { VIDEO_LOADING_WAIT_TIME, ELEMENT_WAIT_LONGER_TIME, ELEMENT_WAIT_EXTRA_LONG_TIME } = require('../core/constants'); -const util = require('./util'); +const { ELEMENT_WAIT_LONGER_TIME } = require('../core/constants'); const { getSettings } = require('../core/settings'); -const { waitAndClearDefaultPresentationNotification } = require('../notifications/util'); class DisabledFeatures extends MultiUsers { constructor(browser, context) { @@ -102,6 +98,20 @@ class DisabledFeatures extends MultiUsers { await this.modPage.wasRemoved(e.inputBackgroundButton); } + async slideSnapshot() { + await this.modPage.waitForSelector(e.whiteboard); + await this.modPage.waitAndClick(e.whiteboardOptionsButton); + await this.modPage.hasElement(e.presentationFullscreen); + await this.modPage.wasRemoved(e.presentationSnapshot); + } + + async cameraAsContent() { + await this.modPage.waitForSelector(e.whiteboard); + await this.modPage.waitAndClick(e.actions); + await this.modPage.hasElement(e.managePresentations); + await this.modPage.wasRemoved(e.shareCameraAsContent); + } + // Disabled Features Exclude async breakoutRoomsExclude() { await this.modPage.waitAndClick(e.manageUsers); @@ -192,6 +202,18 @@ class DisabledFeatures extends MultiUsers { await this.modPage.waitForSelector(e.webcamSettingsModal); await this.modPage.hasElement(e.inputBackgroundButton); } + + async slideSnapshotExclude() { + await this.modPage.waitForSelector(e.whiteboard); + await this.modPage.waitAndClick(e.whiteboardOptionsButton); + await this.modPage.hasElement(e.presentationSnapshot); + } + + async cameraAsContentExclude() { + await this.modPage.waitForSelector(e.whiteboard); + await this.modPage.waitAndClick(e.actions); + await this.modPage.hasElement(e.shareCameraAsContent); + } } exports.DisabledFeatures = DisabledFeatures; diff --git a/bigbluebutton-tests/playwright/parameters/parameters.spec.js b/bigbluebutton-tests/playwright/parameters/parameters.spec.js index a956f12ecd..aadc30e6e1 100644 --- a/bigbluebutton-tests/playwright/parameters/parameters.spec.js +++ b/bigbluebutton-tests/playwright/parameters/parameters.spec.js @@ -312,25 +312,25 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Slide Snapshot', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.slideSnapshotDisabled }); - await customParam.slideSnapshot(); + const disabledFeatures = new DisabledFeatures(browser, context); + await disabledFeatures.initModPage(page, true, { customParameter: c.slideSnapshotDisabled }); + await disabledFeatures.slideSnapshot(); }); test('Slide Snapshot (exclude)', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.slideSnapshotExclude }); - await customParam.slideSnapshotExclude(); + const disabledFeatures = new DisabledFeatures(browser, context); + await disabledFeatures.initModPage(page, true, { customParameter: c.slideSnapshotExclude }); + await disabledFeatures.slideSnapshotExclude(); }); }); test.describe.serial(() => { test('Camera As Content', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); + const customParam = new DisabledFeatures(browser, context); await customParam.initModPage(page, true, { customParameter: c.cameraAsContent }); await customParam.cameraAsContent(); }); test('Camera As Content (exclude)', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); + const customParam = new DisabledFeatures(browser, context); await customParam.initModPage(page, true, { customParameter: c.cameraAsContentExclude }); await customParam.cameraAsContentExclude(); }); From f3a834c9f23788f8697ed00fb12d0802f00a500d Mon Sep 17 00:00:00 2001 From: Anton B Date: Mon, 24 Jul 2023 16:22:09 -0300 Subject: [PATCH 050/252] test: fix current presentation download test behavior --- .../playwright/parameters/disabledFeatures.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-tests/playwright/parameters/disabledFeatures.js b/bigbluebutton-tests/playwright/parameters/disabledFeatures.js index d64e858719..b6d6fec02c 100644 --- a/bigbluebutton-tests/playwright/parameters/disabledFeatures.js +++ b/bigbluebutton-tests/playwright/parameters/disabledFeatures.js @@ -71,7 +71,8 @@ class DisabledFeatures extends MultiUsers { async downloadPresentationWithAnnotations() { await this.modPage.waitAndClick(e.actions); await this.modPage.waitAndClick(e.managePresentations); - await this.modPage.wasRemoved(e.exportPresentationToPublicChat); + await this.modPage.waitAndClick(e.presentationOptionsDownloadBtn); + await this.modPage.wasRemoved(e.sendPresentationInCurrentStateBtn); } async importPresentationWithAnnotationsFromBreakoutRooms() { @@ -176,7 +177,8 @@ class DisabledFeatures extends MultiUsers { async downloadPresentationWithAnnotationsExclude() { await this.modPage.waitAndClick(e.actions); await this.modPage.waitAndClick(e.managePresentations); - await this.modPage.hasElement(e.exportPresentationToPublicChat); + await this.modPage.waitAndClick(e.presentationOptionsDownloadBtn); + await this.modPage.hasElement(e.sendPresentationInCurrentStateBtn); } async importPresentationWithAnnotationsFromBreakoutRoomsExclude() { From 0c434236a242ae3251b6e44d1be2a5595a071e8f Mon Sep 17 00:00:00 2001 From: Anton B Date: Mon, 24 Jul 2023 16:25:17 -0300 Subject: [PATCH 051/252] test: fix disabledFeatures declaration --- .../playwright/parameters/parameters.spec.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bigbluebutton-tests/playwright/parameters/parameters.spec.js b/bigbluebutton-tests/playwright/parameters/parameters.spec.js index aadc30e6e1..6348a788f6 100644 --- a/bigbluebutton-tests/playwright/parameters/parameters.spec.js +++ b/bigbluebutton-tests/playwright/parameters/parameters.spec.js @@ -325,14 +325,14 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Camera As Content', async ({ browser, context, page }) => { - const customParam = new DisabledFeatures(browser, context); - await customParam.initModPage(page, true, { customParameter: c.cameraAsContent }); - await customParam.cameraAsContent(); + const disabledFeatures = new DisabledFeatures(browser, context); + await disabledFeatures.initModPage(page, true, { customParameter: c.cameraAsContent }); + await disabledFeatures.cameraAsContent(); }); test('Camera As Content (exclude)', async ({ browser, context, page }) => { - const customParam = new DisabledFeatures(browser, context); - await customParam.initModPage(page, true, { customParameter: c.cameraAsContentExclude }); - await customParam.cameraAsContentExclude(); + const disabledFeatures = new DisabledFeatures(browser, context); + await disabledFeatures.initModPage(page, true, { customParameter: c.cameraAsContentExclude }); + await disabledFeatures.cameraAsContentExclude(); }); }); }); From 0265b0d97efb8bf9a0b26d233ca209cfa4d464c4 Mon Sep 17 00:00:00 2001 From: Anton B Date: Mon, 24 Jul 2023 16:44:31 -0300 Subject: [PATCH 052/252] test: fix connection status element selector --- bigbluebutton-tests/playwright/core/elements.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-tests/playwright/core/elements.js b/bigbluebutton-tests/playwright/core/elements.js index c617ea6ab1..bd41c73c0d 100644 --- a/bigbluebutton-tests/playwright/core/elements.js +++ b/bigbluebutton-tests/playwright/core/elements.js @@ -341,7 +341,7 @@ exports.dataSavingScreenshare = 'input[data-test="dataSavingScreenshare"]'; exports.screenshareLocked = 'button[data-test="screenshareLocked"]'; exports.connectionStatusItemEmpty = 'li[data-test="connectionStatusItemEmpty"]'; exports.connectionStatusTab2 = 'li[id="react-tabs-2"]'; -exports.connectionStatusItemUser = 'div[data-test="connectionStatusItemUser"]'; +exports.connectionStatusItemUser = 'li[data-test="connectionStatusItemUser"]'; exports.connectionStatusLinkToSettings = `${networkDataContainer} span[role="button"]`; exports.dataSavingWebcams = 'input[data-test="dataSavingWebcams"]'; exports.connectionStatusOfflineUser = 'div[data-test="offlineUser"]'; From 03aa9d3bec918780180a0bd6e9e841065210e1fe Mon Sep 17 00:00:00 2001 From: Anton B Date: Mon, 24 Jul 2023 18:07:51 -0300 Subject: [PATCH 053/252] test: update all raise and lower hand related tests --- .../reactions-button/component.jsx | 8 +++-- .../emoji-picker/reactions-bar/component.jsx | 4 +-- .../playwright/core/elements.js | 5 ++-- .../playwright/core/settings.js | 2 +- .../learningdashboard/learningdashboard.js | 1 + .../playwright/notifications/notifications.js | 20 +++++++++---- .../playwright/options/options.js | 6 ++-- .../playwright/parameters/customparameters.js | 2 +- .../playwright/user/multiusers.js | 29 ++++++++++++++----- .../playwright/user/user.spec.js | 2 +- 10 files changed, 53 insertions(+), 26 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx index c11fdb7eeb..406e8b9103 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx @@ -45,7 +45,11 @@ const ReactionsButton = (props) => { const renderReactionsBar = () => ( - + ); @@ -56,7 +60,7 @@ const ReactionsButton = (props) => { trigger={( { ))} - onRaiseHand()} active={raiseHand}> - + onRaiseHand()} active={raiseHand}> + {RaiseHandButtonLabel()} diff --git a/bigbluebutton-tests/playwright/core/elements.js b/bigbluebutton-tests/playwright/core/elements.js index bd41c73c0d..6bb767b69d 100644 --- a/bigbluebutton-tests/playwright/core/elements.js +++ b/bigbluebutton-tests/playwright/core/elements.js @@ -11,8 +11,9 @@ exports.modalDismissButton = 'button[data-test="modalDismissButton"]'; exports.closeModal = 'button[data-test="closeModal"]'; exports.isSharingScreen = 'div[data-test="isSharingScreen"]'; exports.pdfFileName = '100PagesFile.pdf'; -exports.raiseHandBtn = 'button[data-test="raiseHandLabel"]'; -exports.lowerHandBtn = 'button[data-test="lowerHandLabel"]'; +exports.reactionsButton = 'button[data-test="reactionsButton"]'; +exports.raiseHandBtn = 'div[data-test="raiseHandBtn"]'; +exports.lowerHandBtn = 'div[data-test="lowerHandBtn"]'; exports.raiseHandRejection = 'button[data-test="raiseHandRejection"]'; exports.meetingEndedModal = 'div[data-test="meetingEndedModal"]'; exports.logout = 'li[data-test="logout"]'; diff --git a/bigbluebutton-tests/playwright/core/settings.js b/bigbluebutton-tests/playwright/core/settings.js index b810509228..8398444701 100644 --- a/bigbluebutton-tests/playwright/core/settings.js +++ b/bigbluebutton-tests/playwright/core/settings.js @@ -9,7 +9,7 @@ async function generateSettingsData(page) { }); settings = { - raiseHandButton: settingsData.app.raiseHandActionButton.enabled, + reactionsButton: settingsData.app.reactionsButton.enabled, sharedNotesEnabled: settingsData.notes.enabled, // Audio autoJoinAudioModal: settingsData.app.autoJoin, diff --git a/bigbluebutton-tests/playwright/learningdashboard/learningdashboard.js b/bigbluebutton-tests/playwright/learningdashboard/learningdashboard.js index a580a9352b..8ac87ad424 100644 --- a/bigbluebutton-tests/playwright/learningdashboard/learningdashboard.js +++ b/bigbluebutton-tests/playwright/learningdashboard/learningdashboard.js @@ -138,6 +138,7 @@ class LearningDashboard extends MultiUsers { async overview() { await this.modPage.waitAndClick(e.joinVideo); await this.modPage.waitAndClick(e.startSharingWebcam); + await this.modPage.waitAndClick(e.reactionsButton); await this.modPage.waitAndClick(e.raiseHandBtn); await this.dashboardPage.reloadPage(); diff --git a/bigbluebutton-tests/playwright/notifications/notifications.js b/bigbluebutton-tests/playwright/notifications/notifications.js index 6ce07194d0..759bb94d97 100644 --- a/bigbluebutton-tests/playwright/notifications/notifications.js +++ b/bigbluebutton-tests/playwright/notifications/notifications.js @@ -5,6 +5,7 @@ const util = require('./util'); const { openSettings } = require('../options/util'); const { ELEMENT_WAIT_LONGER_TIME } = require('../core/constants'); const { getSettings } = require('../core/settings'); +const { sleep } = require('../core/helpers'); class Notifications extends MultiUsers { constructor(browser, context) { @@ -45,16 +46,23 @@ class Notifications extends MultiUsers { } async raiseAndLowerHandNotification() { - const { raiseHandButton } = getSettings(); - test.fail(!raiseHandButton, 'Raise/lower hand button is disabled'); + const { reactionsButton } = getSettings(); + if (!reactionsButton) { + await this.modPage.waitForSelector(e.whiteboard); + await this.modPage.hasElement(e.joinAudio); + await this.modPage.wasRemoved(e.reactionsButton); + return + } await util.waitAndClearDefaultPresentationNotification(this.modPage); + await this.modPage.waitAndClick(e.reactionsButton); await this.modPage.waitAndClick(e.raiseHandBtn); - await this.modPage.waitForSelector(e.smallToastMsg); - await util.checkNotificationText(this.modPage, e.raisingHandToast); - await util.waitAndClearNotification(this.modPage); + await sleep(1000); await this.modPage.waitAndClick(e.lowerHandBtn); - await util.checkNotificationText(this.modPage, e.loweringHandToast); + await this.modPage.wasRemoved(e.raiseHandRejection); + await util.checkNotificationText(this.modPage, e.raisingHandToast); + await this.modPage.hasText(`${e.smallToastMsg}>>nth=0`, e.raisingHandToast); + await this.modPage.hasText(`${e.smallToastMsg}>>nth=1`, e.loweringHandToast); } async userJoinNotification(page) { diff --git a/bigbluebutton-tests/playwright/options/options.js b/bigbluebutton-tests/playwright/options/options.js index 3f7b0f73d5..2cfe9175f7 100644 --- a/bigbluebutton-tests/playwright/options/options.js +++ b/bigbluebutton-tests/playwright/options/options.js @@ -36,7 +36,7 @@ class Options extends MultiUsers { [e.joinVideo]: 'app.video.joinVideo', [e.startScreenSharing]: 'app.actionsBar.actionsDropdown.desktopShareLabel', [e.minimizePresentation]: 'app.actionsBar.actionsDropdown.minimizePresentationLabel', - [e.raiseHandBtn]: 'app.actionsBar.emojiMenu.raiseHandLabel', + [e.reactionsButton]: 'app.actionsBar.reactions.reactionsButtonLabel', [e.connectionStatusBtn]: 'app.connection-status.label', [e.optionsButton]: 'app.navBar.settingsDropdown.optionsLabel', } @@ -98,8 +98,8 @@ class Options extends MultiUsers { await this.modPage.backgroundColorTest(`${e.joinVideo} >> span`, 'rgba(0, 0, 0, 0)'); await this.modPage.textColorTest(`${e.startScreenSharing} >> span`, 'rgb(222, 220, 217)'); await this.modPage.backgroundColorTest(`${e.startScreenSharing} >> span`, 'rgba(0, 0, 0, 0)'); - await this.modPage.textColorTest(`${e.raiseHandBtn} >> span`, 'rgb(222, 220, 217)'); - await this.modPage.backgroundColorTest(`${e.raiseHandBtn} >> span`, 'rgba(0, 0, 0, 0)'); + await this.modPage.textColorTest(`${e.reactionsButton} >> span`, 'rgb(222, 220, 217)'); + await this.modPage.backgroundColorTest(`${e.reactionsButton} >> span`, 'rgba(0, 0, 0, 0)'); await this.modPage.backgroundColorTest(`${e.actions} >> span`, 'rgb(24, 94, 168)'); await this.modPage.backgroundColorTest(`${e.minimizePresentation} >> span`, 'rgb(24, 94, 168)'); diff --git a/bigbluebutton-tests/playwright/parameters/customparameters.js b/bigbluebutton-tests/playwright/parameters/customparameters.js index 68f5d7e7df..16ec74fd08 100644 --- a/bigbluebutton-tests/playwright/parameters/customparameters.js +++ b/bigbluebutton-tests/playwright/parameters/customparameters.js @@ -214,7 +214,7 @@ class CustomParameters extends MultiUsers { await this.modPage.wasRemoved(e.joinVideo); await this.modPage.wasRemoved(e.startScreenSharing); await this.modPage.wasRemoved(e.minimizePresentation); - await this.modPage.wasRemoved(e.raiseHandBtn); + await this.modPage.wasRemoved(e.reactionsButton); } async overrideDefaultLocaleTest() { diff --git a/bigbluebutton-tests/playwright/user/multiusers.js b/bigbluebutton-tests/playwright/user/multiusers.js index aa1d6fef68..7514983b9c 100644 --- a/bigbluebutton-tests/playwright/user/multiusers.js +++ b/bigbluebutton-tests/playwright/user/multiusers.js @@ -8,7 +8,7 @@ const { checkTextContent, checkElementLengthEqualTo } = require('../core/util'); const { checkAvatarIcon, checkIsPresenter, checkMutedUsers } = require('./util'); const { getNotesLocator } = require('../sharednotes/util'); const { getSettings } = require('../core/settings'); -const { ELEMENT_WAIT_TIME, ELEMENT_WAIT_LONGER_TIME } = require('../core/constants'); +const { ELEMENT_WAIT_TIME } = require('../core/constants'); class MultiUsers { constructor(browser, context) { @@ -136,30 +136,43 @@ class MultiUsers { } async raiseAndLowerHand() { - const { raiseHandButton } = getSettings(); - test.fail(!raiseHandButton, 'Raise/lower hand button is disabled'); + const { reactionsButton } = getSettings(); + if (!reactionsButton) { + await this.modPage.waitForSelector(e.whiteboard); + await this.modPage.hasElement(e.joinAudio); + await this.modPage.wasRemoved(e.reactionsButton); + return; + } - await waitAndClearDefaultPresentationNotification(this.modPage); await this.initUserPage(); + await this.userPage.waitAndClick(e.reactionsButton); await this.userPage.waitAndClick(e.raiseHandBtn); - await sleep(1000); await this.userPage.hasElement(e.lowerHandBtn); await this.modPage.comparingSelectorsBackgroundColor(e.avatarsWrapperAvatar, `${e.userListItem} > div ${e.userAvatar}`); + await sleep(1000); await this.userPage.waitAndClick(e.lowerHandBtn); await this.userPage.hasElement(e.raiseHandBtn); } async raiseHandRejected() { - const { raiseHandButton } = getSettings(); - test.fail(!raiseHandButton, 'Raise/lower hand button is disabled'); + const { reactionsButton } = getSettings(); + if (!reactionsButton) { + console.log('=== inside') + await this.modPage.waitForSelector(e.whiteboard); + await this.modPage.hasElement(e.joinAudio); + await this.modPage.wasRemoved(e.reactionsButton); + return + } await waitAndClearDefaultPresentationNotification(this.modPage); await this.initUserPage(); + await this.userPage.waitAndClick(e.reactionsButton); await this.userPage.waitAndClick(e.raiseHandBtn); - await sleep(1000); await this.userPage.hasElement(e.lowerHandBtn); + await this.userPage.press('Escape'); await this.modPage.comparingSelectorsBackgroundColor(e.avatarsWrapperAvatar, `${e.userListItem} > div ${e.userAvatar}`); await this.modPage.waitAndClick(e.raiseHandRejection); + await this.userPage.waitAndClick(e.reactionsButton); await this.userPage.hasElement(e.raiseHandBtn); } diff --git a/bigbluebutton-tests/playwright/user/user.spec.js b/bigbluebutton-tests/playwright/user/user.spec.js index 6d42812657..ce309f26aa 100644 --- a/bigbluebutton-tests/playwright/user/user.spec.js +++ b/bigbluebutton-tests/playwright/user/user.spec.js @@ -10,7 +10,7 @@ const iPhone11 = devices['iPhone 11']; test.describe.parallel('User', () => { test.describe.parallel('Actions', () => { // https://docs.bigbluebutton.org/2.6/release-tests.html#set-status--raise-hand-automated - test('Raise and lower Hand Toast', async ({ browser, context, page }) => { + test('Raise and lower Hand', async ({ browser, context, page }) => { const multiusers = new MultiUsers(browser, context); await multiusers.initModPage(page, true); await multiusers.raiseAndLowerHand(); From fbc3a9625d01ccef6b6654dc7fff360f2105750c Mon Sep 17 00:00:00 2001 From: Anton B Date: Tue, 25 Jul 2023 11:23:21 -0300 Subject: [PATCH 054/252] ci: fix new bbb-imdt url --- .github/workflows/automated-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 1228bef74e..bed0893257 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -58,7 +58,7 @@ jobs: # sudo sh -c ' # echo "Faking a package build (to speed up installation test)" # cd / - # wget -q "http://ci.bbbvm.imdt.com.br/artifacts.tar" + # wget -q "http://ci.bbb.imdt.dev/artifacts.tar" # tar xf artifacts.tar # ' - name: Generate CA @@ -108,7 +108,7 @@ jobs: run: | sudo sh -c ' apt install -yq dpkg-dev - cd /root && wget -q http://ci.bbbvm.imdt.com.br/cache-3rd-part-packages.tar + cd /root && wget -q http://ci.bbb.imdt.dev/cache-3rd-part-packages.tar cp -r /home/runner/work/bigbluebutton/bigbluebutton/artifacts/ /artifacts/ cd /artifacts && tar xf /root/cache-3rd-part-packages.tar cd /artifacts && dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz From 0bd5e96ac87b41e558da862090871754f14336f5 Mon Sep 17 00:00:00 2001 From: Anton Georgiev Date: Tue, 25 Jul 2023 11:05:27 -0400 Subject: [PATCH 055/252] chore: Bump bbb-pads to 1.5.1 --- bbb-pads.placeholder.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbb-pads.placeholder.sh b/bbb-pads.placeholder.sh index 7e7e5dae33..6a7066e2d0 100755 --- a/bbb-pads.placeholder.sh +++ b/bbb-pads.placeholder.sh @@ -1 +1 @@ -git clone --branch v1.5.0 --depth 1 https://github.com/bigbluebutton/bbb-pads bbb-pads +git clone --branch v1.5.1 --depth 1 https://github.com/bigbluebutton/bbb-pads bbb-pads From c3dd31635eebfcbdf3107d3d1ec623dd87a88530 Mon Sep 17 00:00:00 2001 From: danielpetri1 Date: Tue, 25 Jul 2023 16:55:57 +0000 Subject: [PATCH 056/252] Instructions when upgrading to Java 17 --- docs/docs/development/dev-guide.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/docs/development/dev-guide.md b/docs/docs/development/dev-guide.md index c8e152c577..ecfb2c4e1f 100644 --- a/docs/docs/development/dev-guide.md +++ b/docs/docs/development/dev-guide.md @@ -663,6 +663,18 @@ sudo /etc/init.d/ntp restart The above will re-sync your clock. +### Resolving Conflicts Between Java Versions + +In situations where multiple versions of Java are installed, BBB components may encounter build errors. One such error message could state, for example, that `'17' is not a valid choice for '-release'`. This specific error arises when the `bbb-common-message` component requires Java 17 for its operation, but the `sbt` build tool is using Java 11 instead. + +To address this, you need to set the appropriate Java version. The following command will set Java 17 as the active version: + +```bash +update-java-alternatives -s java-1.17.0-openjdk-amd64 +``` + +By executing this command, the system is instructed to use Java 17, i.e., the version with which BBB is currently compatible. + ## Set up HTTPS See the [installation instructions](/administration/install) on how to configure ssl on your BigBlueButton server. From abf4a0a2b4d6f68a8c4792f0eeb0690f94c4d316 Mon Sep 17 00:00:00 2001 From: Anton Georgiev Date: Tue, 25 Jul 2023 12:59:12 -0400 Subject: [PATCH 057/252] chore: Bump BBB version to 2.6.11 --- bigbluebutton-config/bigbluebutton-release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-config/bigbluebutton-release b/bigbluebutton-config/bigbluebutton-release index 4ce8f7d96d..948f2e996f 100644 --- a/bigbluebutton-config/bigbluebutton-release +++ b/bigbluebutton-config/bigbluebutton-release @@ -1 +1 @@ -BIGBLUEBUTTON_RELEASE=2.6.10 +BIGBLUEBUTTON_RELEASE=2.6.11 From ee78814827dc72d699ba7121a836532e3c9e6ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Tue, 25 Jul 2023 15:05:00 -0300 Subject: [PATCH 058/252] change quick poll button styles --- .../actions-bar/quick-poll-dropdown/component.jsx | 1 + .../actions-bar/quick-poll-dropdown/styles.js | 12 ++++-------- .../presentation/presentation-toolbar/component.jsx | 4 ++-- .../smart-video-share/component.jsx | 2 +- .../presentation-toolbar/smart-video-share/styles.js | 3 +++ .../presentation/presentation-toolbar/styles.js | 6 ++++++ 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/quick-poll-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/quick-poll-dropdown/component.jsx index f57a647c59..6ade67fac5 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/quick-poll-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/quick-poll-dropdown/component.jsx @@ -224,6 +224,7 @@ const QuickPollDropdown = (props) => { }} size="lg" data-test="quickPollBtn" + color="primary" /> ); diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/quick-poll-dropdown/styles.js b/bigbluebutton-html5/imports/ui/components/actions-bar/quick-poll-dropdown/styles.js index f783e18420..fae468b7c3 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/quick-poll-dropdown/styles.js +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/quick-poll-dropdown/styles.js @@ -1,23 +1,19 @@ import styled from 'styled-components'; import Button from '/imports/ui/components/common/button/component'; -import { colorOffWhite, toolbarButtonColor } from '/imports/ui/stylesheets/styled-components/palette'; -import { whiteboardToolbarPadding, borderSizeLarge } from '/imports/ui/stylesheets/styled-components/general'; +import { borderSizeLarge } from '/imports/ui/stylesheets/styled-components/general'; import { headingsFontWeight } from '/imports/ui/stylesheets/styled-components/typography'; const QuickPollButton = styled(Button)` - padding: ${whiteboardToolbarPadding}; - background-color: ${colorOffWhite} !important; + margin-left: .5rem; + padding: .1rem; box-shadow: none !important; + background-clip: unset !important; & > span:first-child { - border: 1px solid ${toolbarButtonColor}; border-radius: ${borderSizeLarge}; - color: ${toolbarButtonColor}; font-size: small; font-weight: ${headingsFontWeight}; opacity: 1; - padding-right: ${borderSizeLarge}; - padding-left: ${borderSizeLarge}; } & > span:first-child:hover { diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx index 32a7301453..27e7a4924c 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx @@ -291,7 +291,7 @@ class PresentationToolbar extends PureComponent { id="presentationToolbarWrapper" > {this.renderAriaDescs()} -
+ {isPollingEnabled ? ( -
+ { Date: Tue, 25 Jul 2023 15:23:57 -0300 Subject: [PATCH 059/252] Add required props to dial-in users --- .../imports/api/users/server/modifiers/addDialInUser.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bigbluebutton-html5/imports/api/users/server/modifiers/addDialInUser.js b/bigbluebutton-html5/imports/api/users/server/modifiers/addDialInUser.js index 9cb82b76e2..a760c989ac 100644 --- a/bigbluebutton-html5/imports/api/users/server/modifiers/addDialInUser.js +++ b/bigbluebutton-html5/imports/api/users/server/modifiers/addDialInUser.js @@ -20,6 +20,9 @@ export default async function addDialInUser(meetingId, voiceUser) { waitingForAcceptance: false, guestStatus: 'ALLOW', emoji: 'none', + reactionEmoji: 'none', + raiseHand: false, + away: false, presenter: false, locked: false, // TODO avatar: '', From 1024930a326ff9fc23e955423b94c735cbadd21d Mon Sep 17 00:00:00 2001 From: Anton B Date: Tue, 25 Jul 2023 15:26:42 -0300 Subject: [PATCH 060/252] test: disabled features test fixes and managePresentationButton selector update --- .../actions-bar/actions-dropdown/component.jsx | 2 +- bigbluebutton-tests/playwright/core/elements.js | 3 +-- bigbluebutton-tests/playwright/layouts/layouts.js | 10 +++++----- .../playwright/parameters/disabledFeatures.js | 7 +++---- .../playwright/parameters/parameters.spec.js | 8 ++++---- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx index 1d6b0b3363..02bef3e08d 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx @@ -276,7 +276,7 @@ class ActionsDropdown extends PureComponent { label: intl.formatMessage(intlMessages.layoutModal), key: 'layoutModal', onClick: () => this.setLayoutModalIsOpen(true), - dataTest: 'layoutModal', + dataTest: 'manageLayoutBtn', }); } diff --git a/bigbluebutton-tests/playwright/core/elements.js b/bigbluebutton-tests/playwright/core/elements.js index c617ea6ab1..560f727522 100644 --- a/bigbluebutton-tests/playwright/core/elements.js +++ b/bigbluebutton-tests/playwright/core/elements.js @@ -158,7 +158,6 @@ exports.usersList = 'div[data-test="userList"]'; exports.selectCameraQualityId = 'select[id="setQuality"]'; exports.virtualBackgrounds = 'div[data-test="virtualBackground"]'; exports.learningDashboard = 'li[data-test="learningDashboard"]'; -exports.layoutModal = 'li[data-test="layoutModal"]'; // Notes exports.sharedNotes = 'div[data-test="sharedNotes"]'; @@ -491,7 +490,7 @@ exports.sharedNotesBackground = 'div[data-test="notes"]'; exports.whiteboardOptionsButton = 'button[data-test="whiteboardOptionsButton"]'; // Layout management -exports.layoutSettingsModalButton = 'li[data-test="layoutModal"]'; +exports.manageLayoutBtn = 'li[data-test="manageLayoutBtn"]'; exports.focusOnPresentation = 'button[data-test="presentationFocusLayout"]'; exports.focusOnVideo = 'button[data-test="videoFocusLayout"]'; exports.smartLayout = 'button[data-test="smartLayout"]'; diff --git a/bigbluebutton-tests/playwright/layouts/layouts.js b/bigbluebutton-tests/playwright/layouts/layouts.js index 3bf996ded7..d38577b010 100644 --- a/bigbluebutton-tests/playwright/layouts/layouts.js +++ b/bigbluebutton-tests/playwright/layouts/layouts.js @@ -5,7 +5,7 @@ const { reopenChatSidebar, checkScreenshots } = require('./util'); class Layouts extends MultiUsers { async focusOnPresentation() { await this.modPage.waitAndClick(e.actions); - await this.modPage.waitAndClick(e.layoutSettingsModalButton); + await this.modPage.waitAndClick(e.manageLayoutBtn); await this.modPage.waitAndClick(e.focusOnPresentation); await this.modPage.waitAndClick(e.updateLayoutBtn); await this.modPage.waitAndClick(e.toastContainer); @@ -16,7 +16,7 @@ class Layouts extends MultiUsers { async focusOnVideo() { await this.modPage.waitAndClick(e.actions); - await this.modPage.waitAndClick(e.layoutSettingsModalButton); + await this.modPage.waitAndClick(e.manageLayoutBtn); await this.modPage.waitAndClick(e.focusOnVideo); await this.modPage.waitAndClick(e.updateLayoutBtn); await this.modPage.waitAndClick(e.toastContainer); @@ -27,7 +27,7 @@ class Layouts extends MultiUsers { async smartLayout() { await this.modPage.waitAndClick(e.actions); - await this.modPage.waitAndClick(e.layoutSettingsModalButton); + await this.modPage.waitAndClick(e.manageLayoutBtn); await this.modPage.waitAndClick(e.smartLayout); await this.modPage.waitAndClick(e.updateLayoutBtn); await this.modPage.waitAndClick(e.toastContainer); @@ -44,7 +44,7 @@ class Layouts extends MultiUsers { async customLayout() { await this.modPage.waitAndClick(e.actions); - await this.modPage.waitAndClick(e.layoutSettingsModalButton); + await this.modPage.waitAndClick(e.manageLayoutBtn); await this.modPage.waitAndClick(e.customLayout); await this.modPage.waitAndClick(e.updateLayoutBtn); await this.modPage.waitAndClick(e.toastContainer); @@ -81,7 +81,7 @@ class Layouts extends MultiUsers { async updateEveryone() { await this.modPage.waitAndClick(e.actions); - await this.modPage.waitAndClick(e.layoutSettingsModalButton); + await this.modPage.waitAndClick(e.manageLayoutBtn); await this.modPage.waitAndClick(e.customLayout); await this.modPage.waitAndClick(e.updateEveryoneLayoutBtn); await this.modPage.waitAndClick(e.toastContainer); diff --git a/bigbluebutton-tests/playwright/parameters/disabledFeatures.js b/bigbluebutton-tests/playwright/parameters/disabledFeatures.js index b6d6fec02c..682464c738 100644 --- a/bigbluebutton-tests/playwright/parameters/disabledFeatures.js +++ b/bigbluebutton-tests/playwright/parameters/disabledFeatures.js @@ -41,8 +41,7 @@ class DisabledFeatures extends MultiUsers { async layouts() { await this.modPage.waitAndClick(e.actions); - await this.modPage.wasRemoved(e.propagateLayout); - await this.modPage.wasRemoved(e.layoutModal); + await this.modPage.wasRemoved(e.manageLayoutBtn); } async learningDashboard() { @@ -147,8 +146,7 @@ class DisabledFeatures extends MultiUsers { async layoutsExclude() { await this.modPage.waitAndClick(e.actions); - await this.modPage.hasElement(e.propagateLayout); - await this.modPage.hasElement(e.layoutModal); + await this.modPage.hasElement(e.manageLayoutBtn); } async learningDashboardExclude() { @@ -175,6 +173,7 @@ class DisabledFeatures extends MultiUsers { } async downloadPresentationWithAnnotationsExclude() { + await this.modPage.waitForSelector(e.whiteboard); await this.modPage.waitAndClick(e.actions); await this.modPage.waitAndClick(e.managePresentations); await this.modPage.waitAndClick(e.presentationOptionsDownloadBtn); diff --git a/bigbluebutton-tests/playwright/parameters/parameters.spec.js b/bigbluebutton-tests/playwright/parameters/parameters.spec.js index 6348a788f6..96ef80cf42 100644 --- a/bigbluebutton-tests/playwright/parameters/parameters.spec.js +++ b/bigbluebutton-tests/playwright/parameters/parameters.spec.js @@ -313,12 +313,12 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Slide Snapshot', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.slideSnapshotDisabled }); + await disabledFeatures.initModPage(page, true, { createParameter: c.slideSnapshotDisabled }); await disabledFeatures.slideSnapshot(); }); test('Slide Snapshot (exclude)', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.slideSnapshotExclude }); + await disabledFeatures.initModPage(page, true, { createParameter: c.slideSnapshotExclude }); await disabledFeatures.slideSnapshotExclude(); }); }); @@ -326,12 +326,12 @@ test.describe.parallel('Create Parameters', () => { test.describe.serial(() => { test('Camera As Content', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.cameraAsContent }); + await disabledFeatures.initModPage(page, true, { createParameter: c.cameraAsContent }); await disabledFeatures.cameraAsContent(); }); test('Camera As Content (exclude)', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); - await disabledFeatures.initModPage(page, true, { customParameter: c.cameraAsContentExclude }); + await disabledFeatures.initModPage(page, true, { createParameter: c.cameraAsContentExclude }); await disabledFeatures.cameraAsContentExclude(); }); }); From 22605b3fbb34eab958279515311c48c8130c0abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Tue, 25 Jul 2023 13:20:45 -0300 Subject: [PATCH 061/252] grid mode pagination --- .../layout/push-layout/pushLayoutEngine.jsx | 6 ++++ .../components/video-provider/component.jsx | 2 +- .../ui/components/video-provider/service.js | 33 +++++++++++++++++-- .../video-provider/stream-sorting.js | 1 + .../video-provider/video-list/component.jsx | 17 +--------- .../video-provider/video-list/container.jsx | 20 ++--------- .../private/config/settings.yml | 8 +++++ 7 files changed, 51 insertions(+), 36 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/layout/push-layout/pushLayoutEngine.jsx b/bigbluebutton-html5/imports/ui/components/layout/push-layout/pushLayoutEngine.jsx index bb0eae956b..d1bddc2172 100644 --- a/bigbluebutton-html5/imports/ui/components/layout/push-layout/pushLayoutEngine.jsx +++ b/bigbluebutton-html5/imports/ui/components/layout/push-layout/pushLayoutEngine.jsx @@ -73,6 +73,8 @@ class PushLayoutEngine extends React.Component { selectedLayout = selectedLayout === 'custom' ? 'smart' : selectedLayout; Settings.application.selectedLayout = selectedLayout; } + Session.set('isGridEnabled', selectedLayout === LAYOUT_TYPE.VIDEO_FOCUS); + Settings.save(); const initialPresentation = !getFromUserSettings('bbb_hide_presentation_on_join', HIDE_PRESENTATION || !meetingPresentationIsOpen) || shouldShowScreenshare || shouldShowExternalVideo; @@ -255,6 +257,10 @@ class PushLayoutEngine extends React.Component { setMeetingLayout(); } } + + if (selectedLayout !== prevProps.selectedLayout) { + Session.set('isGridEnabled', selectedLayout === LAYOUT_TYPE.VIDEO_FOCUS); + } } render() { diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx index 4ac4b7740c..1f0ce7f351 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx @@ -400,7 +400,7 @@ class VideoProvider extends Component { } getStreamsToConnectAndDisconnect(streams) { - const streamsCameraIds = streams.map(s => s.stream); + const streamsCameraIds = streams.filter(s => !s?.isGridItem).map(s => s.stream); const streamsConnected = Object.keys(this.webRtcPeers); const streamsToConnect = streamsCameraIds.filter(stream => { diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/service.js b/bigbluebutton-html5/imports/ui/components/video-provider/service.js index ddcf92f9d1..cab6c179a5 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/service.js +++ b/bigbluebutton-html5/imports/ui/components/video-provider/service.js @@ -42,6 +42,8 @@ const { pageChangeDebounceTime: PAGE_CHANGE_DEBOUNCE_TIME, desktopPageSizes: DESKTOP_PAGE_SIZES, mobilePageSizes: MOBILE_PAGE_SIZES, + desktopGridPageSizes: DESKTOP_GRID_PAGE_SIZES, + mobileGridPageSizes: MOBILE_GRID_PAGE_SIZES, } = Meteor.settings.public.kurento.pagination; const PAGINATION_THRESHOLDS_CONF = Meteor.settings.public.kurento.paginationThresholds; const PAGINATION_THRESHOLDS = PAGINATION_THRESHOLDS_CONF.thresholds.sort((t1, t2) => t1.users - t2.users); @@ -317,7 +319,11 @@ class VideoService { getPageSizeDictionary () { // Dynamic page sizes are disabled. Fetch the stock page sizes. if (!PAGINATION_THRESHOLDS_ENABLED || PAGINATION_THRESHOLDS.length <= 0) { - return !this.isMobile ? DESKTOP_PAGE_SIZES : MOBILE_PAGE_SIZES; + if (this.isGridEnabled()) { + return !this.isMobile ? DESKTOP_GRID_PAGE_SIZES : MOBILE_GRID_PAGE_SIZES; + } else { + return !this.isMobile ? DESKTOP_PAGE_SIZES : MOBILE_PAGE_SIZES; + } } // Dynamic page sizes are enabled. Get the user count, isolate the @@ -420,18 +426,41 @@ class VideoService { makeCall('changePin', userId, !userIsPinned); } + isGridEnabled() { + return Session.get('isGridEnabled'); + } + getVideoStreams() { const pageSize = this.getMyPageSize(); const isPaginationDisabled = !this.isPaginationEnabled() || pageSize === 0; const { neededDataTypes } = isPaginationDisabled ? getSortingMethod(DEFAULT_SORTING) : getSortingMethod(PAGINATION_SORTING); + const isGridEnabled = this.isGridEnabled(); let streams = VideoStreams.find( { meetingId: Auth.meetingID }, { fields: neededDataTypes }, ).fetch(); + if (isGridEnabled) { + const users = Users.find( + { meetingId: Auth.meetingID }, + { fields: { loggedOut: 1, left: 1, ...neededDataTypes} }, + ).fetch(); + + const streamUsers = streams.map((stream) => stream.userId); + + const filteredUsers = users.filter( + (user) => !user.loggedOut && !user.left && !streamUsers.includes(user.userId) + ).map((user) => ({ + isGridItem: true, + ...user, + })); + + streams = sortVideoStreams(streams.concat(filteredUsers), DEFAULT_SORTING); + } + // Data savings enabled will only show local streams const { viewParticipantsWebcams } = Settings.dataSaving; if (!viewParticipantsWebcams) streams = this.filterLocalOnly(streams); @@ -679,7 +708,7 @@ class VideoService { } isLocalStream(cameraId) { - return cameraId.startsWith(Auth.userID); + return cameraId?.startsWith(Auth.userID); } playStart(cameraId) { diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/stream-sorting.js b/bigbluebutton-html5/imports/ui/components/video-provider/stream-sorting.js index 83fc1243a9..dcee88b9dc 100644 --- a/bigbluebutton-html5/imports/ui/components/video-provider/stream-sorting.js +++ b/bigbluebutton-html5/imports/ui/components/video-provider/stream-sorting.js @@ -130,6 +130,7 @@ export const sortVideoStreams = (streams, mode) => { return sorted.map(videoStream => ({ stream: videoStream.stream, + isGridItem: videoStream?.isGridItem, userId: videoStream.userId, name: videoStream.name, sortName: videoStream.sortName, diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/component.jsx index d144a02479..3852c898fa 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/component.jsx @@ -98,24 +98,20 @@ class VideoList extends Component { } componentDidUpdate(prevProps) { - const { layoutType, cameraDock, streams, focusedId, isGridEnabled, users } = this.props; + const { layoutType, cameraDock, streams, focusedId } = this.props; const { width: cameraDockWidth, height: cameraDockHeight } = cameraDock; const { layoutType: prevLayoutType, cameraDock: prevCameraDock, streams: prevStreams, - users: prevUsers, focusedId: prevFocusedId, } = prevProps; const { width: prevCameraDockWidth, height: prevCameraDockHeight } = prevCameraDock; - const focusedStream = streams.filter(s => s.stream === focusedId); - if (layoutType !== prevLayoutType || focusedId !== prevFocusedId || cameraDockWidth !== prevCameraDockWidth || cameraDockHeight !== prevCameraDockHeight - || (isGridEnabled && users?.length !== prevUsers?.length) || streams.length !== prevStreams.length) { this.handleCanvasResize(); } @@ -182,15 +178,9 @@ class VideoList extends Component { streams, cameraDock, layoutContextDispatch, - isGridEnabled, - users, } = this.props; let numItems = streams.length; - if (isGridEnabled) { - numItems += users.length; - } - if (numItems < 1 || !this.canvas || !this.grid) { return; } @@ -310,16 +300,11 @@ class VideoList extends Component { swapLayout, handleVideoFocus, focusedId, - users, } = this.props; const numOfStreams = streams.length; let videoItems = streams; - if (users) { - videoItems = sortVideoStreams(videoItems.concat(users), DEFAULT_SORTING); - } - return videoItems.map((item) => { const { userId, name } = item; const isStream = !!item.stream; diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/container.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/container.jsx index 1b2e852bc8..4d16a5b87a 100644 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/container.jsx @@ -1,38 +1,24 @@ -import React, { useContext } from 'react'; +import React from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import VideoList from '/imports/ui/components/video-provider/video-list/component'; import VideoService from '/imports/ui/components/video-provider/service'; import { layoutSelect, layoutSelectOutput, layoutDispatch } from '../../layout/context'; -import { UsersContext } from '/imports/ui/components/components-data/users-context/context'; -import Auth from '/imports/ui/services/auth'; import Users from '/imports/api/users'; const VideoListContainer = ({ children, ...props }) => { const layoutType = layoutSelect((i) => i.layoutType); const cameraDock = layoutSelectOutput((i) => i.cameraDock); const layoutContextDispatch = layoutDispatch(); - const usingUsersContext = useContext(UsersContext); - const { users: contextUsers } = usingUsersContext; - const { streams, isGridEnabled } = props; - - const streamUsers = streams.map((stream) => stream.userId); - - const users = isGridEnabled && contextUsers - ? Object.values(contextUsers[Auth.meetingID]).filter( - (user) => !user.loggedOut && !user.left && !streamUsers.includes(user.userId) - ) - : null; + const { streams } = props; return ( - !streams.length && !isGridEnabled + !streams.length ? null : ( diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 42f728e744..cad46f1425 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -476,6 +476,14 @@ public: mobilePageSizes: moderator: 2 viewer: 2 + # video page sizes for DESKTOP endpoints in GRID mode + desktopGridPageSizes: + moderator: 49 + viewer: 49 + # video page sizes for MOBILE endpoints in GRID mode + mobileGridPageSizes: + moderator: 27 + viewer: 27 paginationThresholds: enabled: false thresholds: From 692370d3b140dc4866ac1bd4e1c280e08efe6492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Wed, 26 Jul 2023 09:55:39 -0300 Subject: [PATCH 062/252] reduce page size --- bigbluebutton-html5/private/config/settings.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index cad46f1425..e051decf55 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -478,12 +478,12 @@ public: viewer: 2 # video page sizes for DESKTOP endpoints in GRID mode desktopGridPageSizes: - moderator: 49 - viewer: 49 + moderator: 48 + viewer: 48 # video page sizes for MOBILE endpoints in GRID mode mobileGridPageSizes: - moderator: 27 - viewer: 27 + moderator: 14 + viewer: 14 paginationThresholds: enabled: false thresholds: From 842cfb4fab85d2494921fe19d91a4926eda9b223 Mon Sep 17 00:00:00 2001 From: Anton B Date: Tue, 25 Jul 2023 11:23:21 -0300 Subject: [PATCH 063/252] ci: fix new bbb-imdt url --- .github/workflows/automated-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 0821a7c0bd..973062fbfb 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -56,7 +56,7 @@ jobs: # sudo sh -c ' # echo "Faking a package build (to speed up installation test)" # cd / - # wget -q "http://ci.bbbvm.imdt.com.br/artifacts.tar" + # wget -q "http://ci.bbb.imdt.dev/artifacts.tar" # tar xf artifacts.tar # ' - name: Generate CA @@ -106,7 +106,7 @@ jobs: run: | sudo sh -c ' apt install -yq dpkg-dev - cd /root && wget -q http://ci.bbbvm.imdt.com.br/cache-3rd-part-packages.tar + cd /root && wget -q http://ci.bbb.imdt.dev/cache-3rd-part-packages.tar cp -r /home/runner/work/bigbluebutton/bigbluebutton/artifacts/ /artifacts/ cd /artifacts && tar xf /root/cache-3rd-part-packages.tar cd /artifacts && dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz From b31ba3fbc06daaae471743aee3a49290d0b003d6 Mon Sep 17 00:00:00 2001 From: Anton B Date: Mon, 24 Jul 2023 16:44:31 -0300 Subject: [PATCH 064/252] test: fix connection status element selector --- bigbluebutton-tests/playwright/core/elements.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-tests/playwright/core/elements.js b/bigbluebutton-tests/playwright/core/elements.js index 7ef93badef..16ebf75619 100644 --- a/bigbluebutton-tests/playwright/core/elements.js +++ b/bigbluebutton-tests/playwright/core/elements.js @@ -339,7 +339,7 @@ exports.dataSavingScreenshare = 'input[data-test="dataSavingScreenshare"]'; exports.screenshareLocked = 'button[data-test="screenshareLocked"]'; exports.connectionStatusItemEmpty = 'li[data-test="connectionStatusItemEmpty"]'; exports.connectionStatusTab2 = 'li[id="react-tabs-2"]'; -exports.connectionStatusItemUser = 'div[data-test="connectionStatusItemUser"]'; +exports.connectionStatusItemUser = 'li[data-test="connectionStatusItemUser"]'; exports.connectionStatusLinkToSettings = `${networkDataContainer} span[role="button"]`; exports.dataSavingWebcams = 'input[data-test="dataSavingWebcams"]'; exports.connectionStatusOfflineUser = 'div[data-test="offlineUser"]'; From 838accf0158f1ecf0fdf60c6319d200a98ab9106 Mon Sep 17 00:00:00 2001 From: Tainan Felipe Date: Wed, 26 Jul 2023 10:38:10 -0300 Subject: [PATCH 065/252] Improve text sanitizing of lobby messages --- .../imports/api/common/server/helpers.js | 25 +++++++++++++++++ .../server/methods/sendGroupChatMsg.js | 27 ++----------------- .../server/modifiers/addBulkGroupChatMsgs.js | 2 +- .../server/methods/setGuestLobbyMessage.js | 5 ++-- .../methods/setPrivateGuestLobbyMessage.js | 5 ++-- 5 files changed, 33 insertions(+), 31 deletions(-) diff --git a/bigbluebutton-html5/imports/api/common/server/helpers.js b/bigbluebutton-html5/imports/api/common/server/helpers.js index 9cb8f66dc2..56a4142314 100755 --- a/bigbluebutton-html5/imports/api/common/server/helpers.js +++ b/bigbluebutton-html5/imports/api/common/server/helpers.js @@ -1,9 +1,34 @@ import Users from '/imports/api/users'; import Logger from '/imports/startup/server/logger'; +import RegexWebUrl from '/imports/utils/regex-weburl'; const MSG_DIRECT_TYPE = 'DIRECT'; const NODE_USER = 'nodeJSapp'; +const HTML_SAFE_MAP = { + '<': '<', + '>': '>', + '"': '"', + "'": ''', +}; + +export const parseMessage = (message) => { + let parsedMessage = message || ''; + parsedMessage = parsedMessage.trim(); + + // Replace
with \n\r + parsedMessage = parsedMessage.replace(//gi, '\n\r'); + + // Sanitize. See: http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript/ + parsedMessage = parsedMessage.replace(/[<>'"]/g, (c) => HTML_SAFE_MAP[c]); + + // Replace flash links to flash valid ones + parsedMessage = parsedMessage.replace(RegexWebUrl, "$&"); + + return parsedMessage; +}; + + export const spokeTimeoutHandles = {}; export const clearSpokeTimeout = (meetingId, userId) => { if (spokeTimeoutHandles[`${meetingId}-${userId}`]) { diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/sendGroupChatMsg.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/sendGroupChatMsg.js index 28593f602f..f4caf78df6 100644 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/sendGroupChatMsg.js +++ b/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/sendGroupChatMsg.js @@ -1,33 +1,10 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import RedisPubSub from '/imports/startup/server/redis'; -import RegexWebUrl from '/imports/utils/regex-weburl'; -import { extractCredentials } from '/imports/api/common/server/helpers'; + +import { extractCredentials, parseMessage } from '/imports/api/common/server/helpers'; import Logger from '/imports/startup/server/logger'; -const HTML_SAFE_MAP = { - '<': '<', - '>': '>', - '"': '"', - "'": ''', -}; - -const parseMessage = (message) => { - let parsedMessage = message || ''; - parsedMessage = parsedMessage.trim(); - - // Replace
with \n\r - parsedMessage = parsedMessage.replace(//gi, '\n\r'); - - // Sanitize. See: http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript/ - parsedMessage = parsedMessage.replace(/[<>'"]/g, (c) => HTML_SAFE_MAP[c]); - - // Replace flash links to flash valid ones - parsedMessage = parsedMessage.replace(RegexWebUrl, "$&"); - - return parsedMessage; -}; - export default function sendGroupChatMsg(chatId, message) { const REDIS_CONFIG = Meteor.settings.private.redis; const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addBulkGroupChatMsgs.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addBulkGroupChatMsgs.js index 372bfa0034..262e3a3f7b 100644 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addBulkGroupChatMsgs.js +++ b/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addBulkGroupChatMsgs.js @@ -2,7 +2,7 @@ import { GroupChatMsg } from '/imports/api/group-chat-msg'; import GroupChat from '/imports/api/group-chat'; import Logger from '/imports/startup/server/logger'; import flat from 'flat'; -import { parseMessage } from './addGroupChatMsg'; +import { parseMessage } from '/imports/api/common/server/helpers'; export default async function addBulkGroupChatMsgs(msgs) { if (!msgs.length) return; diff --git a/bigbluebutton-html5/imports/api/guest-users/server/methods/setGuestLobbyMessage.js b/bigbluebutton-html5/imports/api/guest-users/server/methods/setGuestLobbyMessage.js index d3c8f6b0e6..544de8c85c 100644 --- a/bigbluebutton-html5/imports/api/guest-users/server/methods/setGuestLobbyMessage.js +++ b/bigbluebutton-html5/imports/api/guest-users/server/methods/setGuestLobbyMessage.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import RedisPubSub from '/imports/startup/server/redis'; import Logger from '/imports/startup/server/logger'; -import { extractCredentials } from '/imports/api/common/server/helpers'; +import { extractCredentials, parseMessage } from '/imports/api/common/server/helpers'; const REDIS_CONFIG = Meteor.settings.private.redis; const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; @@ -16,8 +16,7 @@ export default function setGuestLobbyMessage(message) { check(meetingId, String); check(requesterUserId, String); - - const payload = { message }; + const payload = { message: parseMessage(message) }; Logger.info(`User=${requesterUserId} set guest lobby message to ${message}`); diff --git a/bigbluebutton-html5/imports/api/guest-users/server/methods/setPrivateGuestLobbyMessage.js b/bigbluebutton-html5/imports/api/guest-users/server/methods/setPrivateGuestLobbyMessage.js index 425c8c6254..72aad70143 100644 --- a/bigbluebutton-html5/imports/api/guest-users/server/methods/setPrivateGuestLobbyMessage.js +++ b/bigbluebutton-html5/imports/api/guest-users/server/methods/setPrivateGuestLobbyMessage.js @@ -2,7 +2,8 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import RedisPubSub from '/imports/startup/server/redis'; import Logger from '/imports/startup/server/logger'; -import { extractCredentials } from '/imports/api/common/server/helpers'; +import { extractCredentials, parseMessage } from '/imports/api/common/server/helpers'; + const REDIS_CONFIG = Meteor.settings.private.redis; const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; @@ -17,7 +18,7 @@ export default function setPrivateGuestLobbyMessage(message, guestId) { check(meetingId, String); check(requesterUserId, String); - const payload = { guestId, message }; + const payload = { guestId, message: parseMessage(message) }; Logger.info(`User=${requesterUserId} sent a private guest lobby message to guest user=${guestId}`); From 7b57565fc55847a60dbfad28a363dc3347f576ef Mon Sep 17 00:00:00 2001 From: Arthurk12 Date: Wed, 26 Jul 2023 17:38:34 -0300 Subject: [PATCH 066/252] fix(wake-lock): settings attribute name --- .../imports/ui/components/wake-lock/component.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/wake-lock/component.jsx b/bigbluebutton-html5/imports/ui/components/wake-lock/component.jsx index dabf1ec8f3..8e0ae75653 100644 --- a/bigbluebutton-html5/imports/ui/components/wake-lock/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/wake-lock/component.jsx @@ -67,7 +67,7 @@ class WakeLock extends Component { closeNotification(); const error = await request(); if (!error) { - Settings.application.wakeLockEnabled = true; + Settings.application.wakeLock = true; Settings.save(); } this.feedbackToast(error); @@ -117,7 +117,7 @@ class WakeLock extends Component { request().then((error) => { if (error) { this.feedbackToast(error); - Settings.application.wakeLockEnabled = false; + Settings.application.wakeLock = false; Settings.save(); } }); From 5303265bc2e1873ac78a4a234eba6899e8830481 Mon Sep 17 00:00:00 2001 From: Anton Georgiev Date: Wed, 26 Jul 2023 22:19:05 -0400 Subject: [PATCH 067/252] Update bigbluebutton-html5/public/locales/en.json --- bigbluebutton-html5/public/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/public/locales/en.json b/bigbluebutton-html5/public/locales/en.json index ab7575b6e7..3a6d3943c8 100755 --- a/bigbluebutton-html5/public/locales/en.json +++ b/bigbluebutton-html5/public/locales/en.json @@ -227,7 +227,7 @@ "app.meeting.endedByUserMessage": "This session was ended by {0}", "app.meeting.endedByNoModeratorMessageSingular": "The meeting has ended because no moderator has been present for one minute", "app.meeting.endedByNoModeratorMessagePlural": "The meeting has ended because no moderator has been present for {0} minutes", - "app.meeting.endedMessage": "Press the button to continue to home screen.", + "app.meeting.endedMessage": "Press the button to continue to the home screen.", "app.meeting.alertMeetingEndsUnderMinutesSingular": "Meeting is closing in one minute.", "app.meeting.alertMeetingEndsUnderMinutesPlural": "Meeting is closing in {0} minutes.", "app.meeting.alertBreakoutEndsUnderMinutesPlural": "Breakout is closing in {0} minutes.", From 94252fcdb818d66b546d0c50de6bbbbee77aef98 Mon Sep 17 00:00:00 2001 From: Anton Georgiev Date: Wed, 26 Jul 2023 22:24:49 -0400 Subject: [PATCH 068/252] docs: Added link for 2.6.11 --- docs/docs/new-features.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/new-features.md b/docs/docs/new-features.md index 0b489d5b35..28638e956d 100644 --- a/docs/docs/new-features.md +++ b/docs/docs/new-features.md @@ -187,6 +187,7 @@ For full details on what is new in BigBlueButton 2.6, see the release notes. ### Recent releases: +- [2.6.11](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.6.11) - [2.6.10](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.6.10) - [2.6.9](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.6.9) - [2.6.8](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.6.8) From 796b630736c287221d14467b8eae1544d2b01f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Thu, 27 Jul 2023 09:22:50 -0300 Subject: [PATCH 069/252] update layout images --- .../resources/images/layouts/custom.svg | 31 +++++++++---------- .../images/layouts/presentationFocus.svg | 2 +- .../public/resources/images/layouts/smart.svg | 26 +++++++++------- .../resources/images/layouts/videoFocus.svg | 24 +++++++------- 4 files changed, 42 insertions(+), 41 deletions(-) diff --git a/bigbluebutton-html5/public/resources/images/layouts/custom.svg b/bigbluebutton-html5/public/resources/images/layouts/custom.svg index 08bdfa65cc..ff869330ce 100644 --- a/bigbluebutton-html5/public/resources/images/layouts/custom.svg +++ b/bigbluebutton-html5/public/resources/images/layouts/custom.svg @@ -1,20 +1,19 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/bigbluebutton-html5/public/resources/images/layouts/presentationFocus.svg b/bigbluebutton-html5/public/resources/images/layouts/presentationFocus.svg index a5a746870b..77fca948b5 100644 --- a/bigbluebutton-html5/public/resources/images/layouts/presentationFocus.svg +++ b/bigbluebutton-html5/public/resources/images/layouts/presentationFocus.svg @@ -14,5 +14,5 @@ - + diff --git a/bigbluebutton-html5/public/resources/images/layouts/smart.svg b/bigbluebutton-html5/public/resources/images/layouts/smart.svg index fed8a4fd8a..ee66fed55a 100644 --- a/bigbluebutton-html5/public/resources/images/layouts/smart.svg +++ b/bigbluebutton-html5/public/resources/images/layouts/smart.svg @@ -1,16 +1,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/bigbluebutton-html5/public/resources/images/layouts/videoFocus.svg b/bigbluebutton-html5/public/resources/images/layouts/videoFocus.svg index cae37cecf1..76cbf2020f 100644 --- a/bigbluebutton-html5/public/resources/images/layouts/videoFocus.svg +++ b/bigbluebutton-html5/public/resources/images/layouts/videoFocus.svg @@ -1,18 +1,18 @@ - - - - - - - - - - - - + + + + + + + + + + + + From bd97a008a0695dae418554757697d46059d92590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Thu, 27 Jul 2023 15:49:21 -0300 Subject: [PATCH 070/252] fix hide presentation on join --- bigbluebutton-html5/imports/ui/components/app/container.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bigbluebutton-html5/imports/ui/components/app/container.jsx b/bigbluebutton-html5/imports/ui/components/app/container.jsx index 66339c0451..2a43c56dcf 100755 --- a/bigbluebutton-html5/imports/ui/components/app/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/app/container.jsx @@ -69,6 +69,7 @@ const AppContainer = (props) => { meetingLayoutCameraPosition, meetingLayoutFocusedCamera, meetingLayoutVideoRate, + isSharedNotesPinned, ...otherProps } = props; @@ -97,6 +98,7 @@ const AppContainer = (props) => { layoutContextDispatch && (typeof meetingLayout !== 'undefined') && (layoutType.current !== meetingLayout) + && isSharedNotesPinned ) { layoutType.current = meetingLayout; MediaService.setPresentationIsOpen(layoutContextDispatch, true); @@ -322,5 +324,6 @@ export default withTracker(() => { hidePresentationOnJoin: getFromUserSettings('bbb_hide_presentation_on_join', LAYOUT_CONFIG.hidePresentationOnJoin), hideActionsBar: getFromUserSettings('bbb_hide_actions_bar', false), ignorePollNotifications: Session.get('ignorePollNotifications'), + isSharedNotesPinned: MediaService.shouldShowSharedNotes(), }; })(AppContainer); From 6a712c78500609d1ae848f8f6a4eaa4a3abac226 Mon Sep 17 00:00:00 2001 From: Gabriel Porfirio Date: Fri, 28 Jul 2023 09:16:07 -0300 Subject: [PATCH 071/252] only present can edit the polls --- .../playwright/polling/poll.js | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-tests/playwright/polling/poll.js b/bigbluebutton-tests/playwright/polling/poll.js index a90d401f2b..eabd7cf2a1 100644 --- a/bigbluebutton-tests/playwright/polling/poll.js +++ b/bigbluebutton-tests/playwright/polling/poll.js @@ -262,8 +262,29 @@ class Polling extends MultiUsers { const wbDrawnRectangleLocator = await this.modPage.getLocator(e.wbDrawnRectangle).last(); await expect(wbDrawnRectangleLocator).toBeVisible({ timeout: ELEMENT_WAIT_TIME}); - await this.modPage.waitAndClick(e.closePollingBtn); - await this.modPage.wasRemoved(e.closePollingBtn); + const modWbLocator = this.modPage.getLocator(e.whiteboard); + const wbBox = await modWbLocator.boundingBox(); + + await wbDrawnRectangleLocator.click(); + await this.modPage.page.mouse.down(); + await this.modPage.page.mouse.move(wbBox.x + 0.7 * wbBox.width, wbBox.y + 0.7 * wbBox.height); + await this.modPage.page.mouse.up(); + await wbDrawnRectangleLocator.dblclick(); + await this.modPage.page.keyboard.type('test'); + await expect(wbDrawnRectangleLocator).toContainText('test'); + + // user turns to presenter to edit the poll results + await this.modPage.waitAndClick(e.userListItem); + await this.modPage.waitAndClick(e.makePresenter); + + const wbDrawnRectangleUserLocator = await this.userPage.getLocator(e.wbDrawnRectangle).last(); + await wbDrawnRectangleUserLocator.dblclick(); + await this.userPage.page.keyboard.type('testUser'); + await expect(wbDrawnRectangleUserLocator).toContainText('testUser'); + + await this.modPage.waitAndClick(e.currentUser); + await this.modPage.waitAndClick(e.takePresenter); + await this.userPage.waitAndClick(e.hidePublicChat); } async pollResultsInDifferentPresentation() { From 0a9b061434d06913920c8801467c637520bfdf3f Mon Sep 17 00:00:00 2001 From: Scroody Date: Fri, 28 Jul 2023 13:31:44 -0300 Subject: [PATCH 072/252] Fix: Pulsing animation not working with avatar --- .../imports/ui/components/user-avatar/component.jsx | 2 +- .../imports/ui/components/user-avatar/styles.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/ui/components/user-avatar/component.jsx b/bigbluebutton-html5/imports/ui/components/user-avatar/component.jsx index 92f7650c6f..dd5b91c25e 100755 --- a/bigbluebutton-html5/imports/ui/components/user-avatar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-avatar/component.jsx @@ -80,7 +80,7 @@ const UserAvatar = ({ }} > - + {avatar.length !== 0 && !emoji ? ( diff --git a/bigbluebutton-html5/imports/ui/components/user-avatar/styles.js b/bigbluebutton-html5/imports/ui/components/user-avatar/styles.js index fa87998726..ddf31d3ce4 100644 --- a/bigbluebutton-html5/imports/ui/components/user-avatar/styles.js +++ b/bigbluebutton-html5/imports/ui/components/user-avatar/styles.js @@ -34,6 +34,11 @@ const Image = styled.div` height: 100%; width: 100%; justify-content: center; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; `; const Img = styled.img` From 1e08c28962984a299946e106af96790c21b051e2 Mon Sep 17 00:00:00 2001 From: Anton Georgiev Date: Mon, 31 Jul 2023 09:49:32 -0400 Subject: [PATCH 073/252] chore: set guestPolicyExtraAllowOptions to false --- bigbluebutton-html5/private/config/settings.yml | 2 +- docs/docs/new-features.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 5dc5ca0a77..146250a8fb 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -65,7 +65,7 @@ public: allowUserLookup: false dynamicGuestPolicy: true enableGuestLobbyMessage: true - guestPolicyExtraAllowOptions: true + guestPolicyExtraAllowOptions: false alwaysShowWaitingRoomUI: true enableLimitOfViewersInWebcam: false enableMultipleCameras: true diff --git a/docs/docs/new-features.md b/docs/docs/new-features.md index a9a713682c..aa64302342 100644 --- a/docs/docs/new-features.md +++ b/docs/docs/new-features.md @@ -130,6 +130,10 @@ Recent releases: If you are using bbb-install to configure your servers, be aware that starting with BigBlueButton 2.6's version of bbb-install by default we install a local TURN server. For more information: https://github.com/bigbluebutton/bbb-install/pull/579 and https://docs.bigbluebutton.org/administration/turn-server +#### Changing the default setting `guestPolicyExtraAllowOptions` + +Starting with BigBlueButton 2.7.0-beta.3 we are hiding by default a couple extra options in the guest approve panel. 'Allow all authenticated users' and 'Allow all guests' options will be hidden unless you override the option `app.public.guestPolicyExtraAllowOptions` in `bbb-html5` config file `settings.yml`. These extra options were not relevant to the vast majority of the use cases and when hidden, the interface becomes much simpler. + ### Development For information on developing in BigBlueButton, see [setting up a development environment for 2.7](/development/guide). From 26815f4679a5abaad9549a324a28a15ddf9c9db9 Mon Sep 17 00:00:00 2001 From: prlanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Thu, 27 Jul 2023 10:54:53 -0300 Subject: [PATCH 074/252] chore(audio): add more data to audio_joined/failure logs Add secondsToActivateAudio, inputDeviceId, outputDeviceId and isListenOnly to audio_joined.extraInfo Add inputDeviceId, outputDeviceId and isListenOnly to audio_failure.extraInfo Add a try-catch to the device enforcement procedure triggered by onAudioJoin - it may throw and block the modal. --- .../ui/services/audio-manager/index.js | 64 +++++++++++++------ 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js index 7a54a64d6e..e712f26438 100755 --- a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js +++ b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js @@ -548,10 +548,48 @@ class AudioManager { ); } + try { + this.inputStream = this.bridge ? this.bridge.inputStream : null; + // Enforce correct output device on audio join + this.changeOutputDevice(this.outputDeviceId, true); + storeAudioOutputDeviceId(this.outputDeviceId); + // Extract the deviceId again from the stream to guarantee consistency + // between stream DID vs chosen DID. That's necessary in scenarios where, + // eg, there's no default/pre-set deviceId ('') and the browser's + // default device has been altered by the user (browser default != system's + // default). + if (this.inputStream) { + const extractedDeviceId = MediaStreamUtils.extractDeviceIdFromStream(this.inputStream, 'audio'); + if (extractedDeviceId && extractedDeviceId !== this.inputDeviceId) { + this.changeInputDevice(extractedDeviceId); + } + } + // Audio joined successfully - add device IDs to session storage so they + // can be re-used on refreshes/other sessions + storeAudioInputDeviceId(this.inputDeviceId); + } catch (error) { + logger.warn({ + logCode: 'audiomanager_device_enforce_failed', + extraInfo: { + errorName: error.name, + errorMessage: error.message, + inputDeviceId: this.inputDeviceId, + outputDeviceId: this.outputDeviceId, + }, + }, `Failed to enforce input/output devices: ${error.message}`); + } + if (!this.isEchoTest) { this.notify(this.intl.formatMessage(this.messages.info.JOINED_AUDIO)); - logger.info({ logCode: 'audio_joined' }, 'Audio Joined'); - this.inputStream = this.bridge ? this.bridge.inputStream : null; + logger.info({ + logCode: 'audio_joined', + extraInfo: { + secondsToActivateAudio, + inputDeviceId: this.inputDeviceId, + outputDeviceId: this.outputDeviceId, + isListenOnly: this.isListenOnly, + }, + }, 'Audio Joined'); if (STATS.enabled) this.monitor(); this.audioEventHandler({ name: 'started', @@ -559,25 +597,6 @@ class AudioManager { }); } Session.set('audioModalIsOpen', false); - - // Enforce correct output device on audio join - this.changeOutputDevice(this.outputDeviceId, true); - storeAudioOutputDeviceId(this.outputDeviceId); - - // Extract the deviceId again from the stream to guarantee consistency - // between stream DID vs chosen DID. That's necessary in scenarios where, - // eg, there's no default/pre-set deviceId ('') and the browser's - // default device has been altered by the user (browser default != system's - // default). - if (this.inputStream) { - const extractedDeviceId = MediaStreamUtils.extractDeviceIdFromStream(this.inputStream, 'audio'); - if (extractedDeviceId && extractedDeviceId !== this.inputDeviceId) { - this.changeInputDevice(extractedDeviceId); - } - } - // Audio joined successfully - add device IDs to session storage so they - // can be re-used on refreshes/other sessions - storeAudioInputDeviceId(this.inputDeviceId); } onTransferStart() { @@ -648,6 +667,9 @@ class AudioManager { errorCode: error, cause: bridgeError, bridge, + inputDeviceId: this.inputDeviceId, + outputDeviceId: this.outputDeviceId, + isListenOnly: this.isListenOnly, }, }, `Audio error - errorCode=${error}, cause=${bridgeError}` From 7c3ac51e38462ff8fcc59e0707ae2966304d2429 Mon Sep 17 00:00:00 2001 From: prlanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Thu, 27 Jul 2023 10:59:49 -0300 Subject: [PATCH 075/252] feat(audio): add retryThroughRelay flag for 1007 errors 1007 errors are still a large fraction of our overall audio join error rate. This usually indicates some sort of firewall block or UDP issues carrier networks. I can't figure out why some scenarios won't trickle down to relay candidates though - I'm leaning to scenarios where STUN packets with USE-CANDIDATE are being mangled/lost along the way or something else that borks the (already fragile) conn checks for ICE-lite implementations. Add a new feature called retryThroughRelay which triggers a retry with iceTransportPolicy=relay whenever audio fails to join with a 1007 error. The goal is to force relay usage to try and bypass 1007s scenarios that still happen. Disabled by default. --- .../audio/client/bridge/sfu-audio-bridge.js | 83 ++++++++++++++----- .../private/config/settings.yml | 3 + 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/sfu-audio-bridge.js b/bigbluebutton-html5/imports/api/audio/client/bridge/sfu-audio-bridge.js index 0c02be6371..7e47b5255f 100755 --- a/bigbluebutton-html5/imports/api/audio/client/bridge/sfu-audio-bridge.js +++ b/bigbluebutton-html5/imports/api/audio/client/bridge/sfu-audio-bridge.js @@ -24,6 +24,7 @@ const TRACE_LOGS = Meteor.settings.public.kurento.traceLogs; const GATHERING_TIMEOUT = Meteor.settings.public.kurento.gatheringTimeout; const MEDIA = Meteor.settings.public.media; const DEFAULT_FULLAUDIO_MEDIA_SERVER = MEDIA.audio.fullAudioMediaServer; +const RETRY_THROUGH_RELAY = MEDIA.audio.retryThroughRelay || false; const LISTEN_ONLY_OFFERING = MEDIA.listenOnlyOffering; const MEDIA_TAG = MEDIA.mediaTag.replace(/#/g, ''); const RECONNECT_TIMEOUT_MS = MEDIA.listenOnlyCallTimeout || 15000; @@ -123,9 +124,19 @@ export default class SFUAudioBridge extends BaseAudioBridge { } } - reconnect() { + reconnect(options = {}) { + // If broker has already started, fire the reconnecting callback so the user + // knows what's going on + if (this.broker.started) { + this.callback({ status: this.baseCallStates.reconnecting, bridge: this.bridgeName }); + } else { + // Otherwise: override termination handler so the ended callback doesn't get + // triggered - this is a retry attempt and the user shouldn't be notified + // yet. + this.broker.onended = () => {}; + } + this.broker.stop(); - this.callback({ status: this.baseCallStates.reconnecting, bridge: this.bridgeName }); this.reconnecting = true; // Set up a reconnectionTimeout in case the server is unresponsive // for some reason. If it gets triggered, end the session and stop @@ -141,7 +152,7 @@ export default class SFUAudioBridge extends BaseAudioBridge { this.clearReconnectionTimeout(); }, RECONNECT_TIMEOUT_MS); - this.joinAudio({ isListenOnly: this.isListenOnly }, this.callback).then( + this.joinAudio({ isListenOnly: this.isListenOnly, ...options }, this.callback).then( () => this.clearReconnectionTimeout(), ).catch((error) => { // Error handling is a no-op because it will be "handled" in handleBrokerFailure @@ -162,19 +173,36 @@ export default class SFUAudioBridge extends BaseAudioBridge { mapErrorCode(error); const { errorMessage, errorCause, errorCode } = error; - if (this.broker.started && !this.reconnecting) { - logger.error({ - logCode: 'sfuaudio_error_try_to_reconnect', - extraInfo: { - errorMessage, - errorCode, - errorCause, - bridge: this.bridgeName, - role: this.role, - }, - }, 'SFU audio failed, try to reconnect'); - this.reconnect(); - return resolve(); + if (!this.reconnecting) { + if (this.broker.started) { + logger.error({ + logCode: 'sfuaudio_error_try_to_reconnect', + extraInfo: { + errorMessage, + errorCode, + errorCause, + bridge: this.bridgeName, + role: this.role, + }, + }, 'SFU audio failed, try to reconnect'); + this.reconnect(); + return resolve(); + } + + if (errorCode === 1007 && RETRY_THROUGH_RELAY) { + logger.error({ + logCode: 'sfuaudio_error_retry_through_relay', + extraInfo: { + errorMessage, + errorCode, + errorCause, + bridge: this.bridgeName, + role: this.role, + }, + }, 'SFU audio failed to connect, retry through relay'); + this.reconnect({ forceRelay: true }); + return resolve(); + } } // Already tried reconnecting once OR the user handn't succesfully // connected firsthand. Just finish the session and reject with error @@ -248,8 +276,21 @@ export default class SFUAudioBridge extends BaseAudioBridge { async _startBroker(options) { return new Promise((resolve, reject) => { + const { + isListenOnly, + extension, + inputStream, + forceRelay: _forceRelay = false, + } = options; + + const handleInitError = (_error) => { + mapErrorCode(_error); + if (_error?.errorCode !== 1007 || !RETRY_THROUGH_RELAY || this.reconnecting) { + reject(_error); + } + }; + try { - const { isListenOnly, extension, inputStream } = options; this.inEchoTest = !!extension; this.isListenOnly = isListenOnly; @@ -259,7 +300,7 @@ export default class SFUAudioBridge extends BaseAudioBridge { iceServers: this.iceServers, mediaServer: getMediaServerAdapter(isListenOnly), constraints: getAudioConstraints({ deviceId: this.inputDeviceId }), - forceRelay: shouldForceRelay(), + forceRelay: _forceRelay || shouldForceRelay(), stream: (inputStream && inputStream.active) ? inputStream : undefined, offering: isListenOnly ? LISTEN_ONLY_OFFERING : true, signalCandidates: SIGNAL_CANDIDATES, @@ -283,11 +324,9 @@ export default class SFUAudioBridge extends BaseAudioBridge { this.handleStart().then(resolve).catch(reject); }; - this.broker.joinAudio().catch(reject); + this.broker.joinAudio().catch(handleInitError); } catch (error) { - logger.warn({ logCode: 'sfuaudio_bridge_broker_init_fail' }, - 'Problem when initializing SFU broker for fullaudio bridge'); - reject(error); + handleInitError(error); } }); } diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 146250a8fb..3a365e6b15 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -639,6 +639,9 @@ public: path: 'bridge/sip' - name: fullaudio path: 'bridge/sfu-audio-bridge' + # Forces a retry with iceTransportPolicy = 'relay' if the first attempt + # fails with error code 1007. + retryThroughRelay: false stunTurnServersFetchAddress: '/bigbluebutton/api/stuns' cacheStunTurnServers: true fallbackStunServer: '' From a8e4e876d0212f139591f5453666b353b2fb5d32 Mon Sep 17 00:00:00 2001 From: prlanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Thu, 27 Jul 2023 15:08:29 -0300 Subject: [PATCH 076/252] fix(audio): add connection timers for SFU audio SFU based audio is missing connection timers, which means the join procedure can go on indefinitely in a couple of scenarios. Refactor the connection timers added for re-connections in the SFU audio bridge and make them valid for the first try as well. Make 1010 errors (connection timeout) retriable when retryThroughRelay is enabled. --- .../audio/client/bridge/sfu-audio-bridge.js | 131 ++++++++++-------- .../private/config/settings.yml | 2 +- 2 files changed, 74 insertions(+), 59 deletions(-) diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/sfu-audio-bridge.js b/bigbluebutton-html5/imports/api/audio/client/bridge/sfu-audio-bridge.js index 7e47b5255f..6021ce1c2e 100755 --- a/bigbluebutton-html5/imports/api/audio/client/bridge/sfu-audio-bridge.js +++ b/bigbluebutton-html5/imports/api/audio/client/bridge/sfu-audio-bridge.js @@ -27,7 +27,7 @@ const DEFAULT_FULLAUDIO_MEDIA_SERVER = MEDIA.audio.fullAudioMediaServer; const RETRY_THROUGH_RELAY = MEDIA.audio.retryThroughRelay || false; const LISTEN_ONLY_OFFERING = MEDIA.listenOnlyOffering; const MEDIA_TAG = MEDIA.mediaTag.replace(/#/g, ''); -const RECONNECT_TIMEOUT_MS = MEDIA.listenOnlyCallTimeout || 15000; +const CONNECTION_TIMEOUT_MS = MEDIA.listenOnlyCallTimeout || 15000; const { audio: NETWORK_PRIORITY } = MEDIA.networkPriorities || {}; const SENDRECV_ROLE = 'sendrecv'; const RECV_ROLE = 'recv'; @@ -45,6 +45,9 @@ const errorCodeMap = { 1307: 1007, }; +// Error codes that are prone to a retry according to RETRY_THROUGH_RELAY +const RETRYABLE_ERRORS = [1007, 1010]; + const mapErrorCode = (error) => { const { errorCode } = error; const mappedErrorCode = errorCodeMap[errorCode]; @@ -77,8 +80,9 @@ export default class SFUAudioBridge extends BaseAudioBridge { this.broker = null; this.reconnecting = false; this.iceServers = []; - this.inEchoTest = false; this.bridgeName = BRIDGE_NAME; + + this.handleTermination = this.handleTermination.bind(this); } get inputStream() { @@ -112,18 +116,34 @@ export default class SFUAudioBridge extends BaseAudioBridge { return doGUM(constraints, true); } - handleTermination() { - return this.callback({ status: this.baseCallStates.ended, bridge: this.bridgeName }); + setConnectionTimeout() { + if (this.connectionTimeout) this.clearConnectionTimeout(); + + this.connectionTimeout = setTimeout(() => { + const error = new Error(`ICE negotiation timeout after ${CONNECTION_TIMEOUT_MS / 1000}s`); + error.errorCode = 1010; + // Duplicating key-vals because I can'decide settle on an error pattern - prlanzarin again + error.errorCause = error.message; + error.errorMessage = error.message; + this.handleBrokerFailure(error); + }, CONNECTION_TIMEOUT_MS); } - clearReconnectionTimeout() { - this.reconnecting = false; - if (this.reconnectionTimeout) { - clearTimeout(this.reconnectionTimeout); - this.reconnectionTimeout = null; + clearConnectionTimeout() { + if (this.connectionTimeout) { + clearTimeout(this.connectionTimeout); + this.connectionTimeout = null; } } + dispatchAutoplayHandlingEvent(mediaElement) { + const tagFailedEvent = new CustomEvent('audioPlayFailed', { + detail: { mediaElement }, + }); + window.dispatchEvent(tagFailedEvent); + this.callback({ status: this.baseCallStates.autoplayBlocked, bridge: this.bridgeName }); + } + reconnect(options = {}) { // If broker has already started, fire the reconnecting callback so the user // knows what's going on @@ -138,38 +158,24 @@ export default class SFUAudioBridge extends BaseAudioBridge { this.broker.stop(); this.reconnecting = true; - // Set up a reconnectionTimeout in case the server is unresponsive - // for some reason. If it gets triggered, end the session and stop - // trying to reconnect - this.reconnectionTimeout = setTimeout(() => { - this.callback({ - status: this.baseCallStates.failed, - error: 1010, - bridgeError: 'Reconnection timeout', - bridge: this.bridgeName, + this._startBroker({ isListenOnly: this.isListenOnly, ...options }) + .catch((error) => { + // Error handling is a no-op because it will be "handled" in handleBrokerFailure + logger.debug({ + logCode: 'sfuaudio_reconnect_failed', + extraInfo: { + errorMessage: error.errorMessage, + reconnecting: this.reconnecting, + bridge: this.bridgeName, + role: this.role, + }, + }, 'SFU audio reconnect failed'); }); - this.broker.stop(); - this.clearReconnectionTimeout(); - }, RECONNECT_TIMEOUT_MS); - - this.joinAudio({ isListenOnly: this.isListenOnly, ...options }, this.callback).then( - () => this.clearReconnectionTimeout(), - ).catch((error) => { - // Error handling is a no-op because it will be "handled" in handleBrokerFailure - logger.debug({ - logCode: 'sfuaudio_reconnect_failed', - extraInfo: { - errorMessage: error.errorMessage, - reconnecting: this.reconnecting, - bridge: this.bridgeName, - role: this.role, - }, - }, 'SFU audio reconnect failed'); - }); } handleBrokerFailure(error) { return new Promise((resolve, reject) => { + this.clearConnectionTimeout(); mapErrorCode(error); const { errorMessage, errorCause, errorCode } = error; @@ -189,7 +195,7 @@ export default class SFUAudioBridge extends BaseAudioBridge { return resolve(); } - if (errorCode === 1007 && RETRY_THROUGH_RELAY) { + if (RETRYABLE_ERRORS.includes(errorCode) && RETRY_THROUGH_RELAY) { logger.error({ logCode: 'sfuaudio_error_retry_through_relay', extraInfo: { @@ -204,8 +210,10 @@ export default class SFUAudioBridge extends BaseAudioBridge { return resolve(); } } + // Already tried reconnecting once OR the user handn't succesfully - // connected firsthand. Just finish the session and reject with error + // connected firsthand and retrying isn't an option. Finish the session + // and reject with the error logger.error({ logCode: 'sfuaudio_error', extraInfo: { @@ -217,7 +225,7 @@ export default class SFUAudioBridge extends BaseAudioBridge { role: this.role, }, }, 'SFU audio failed'); - this.clearReconnectionTimeout(); + this.clearConnectionTimeout(); this.broker.stop(); this.callback({ status: this.baseCallStates.failed, @@ -229,23 +237,23 @@ export default class SFUAudioBridge extends BaseAudioBridge { }); } - dispatchAutoplayHandlingEvent(mediaElement) { - const tagFailedEvent = new CustomEvent('audioPlayFailed', { - detail: { mediaElement }, - }); - window.dispatchEvent(tagFailedEvent); - this.callback({ status: this.baseCallStates.autoplayBlocked, bridge: this.bridgeName }); + handleTermination() { + this.clearConnectionTimeout(); + return this.callback({ status: this.baseCallStates.ended, bridge: this.bridgeName }); } handleStart() { const stream = this.broker.webRtcPeer.getRemoteStream(); const mediaElement = document.getElementById(MEDIA_TAG); - return loadAndPlayMediaStream(stream, mediaElement, false).then(() => this - .callback({ + return loadAndPlayMediaStream(stream, mediaElement, false).then(() => { + this.callback({ status: this.baseCallStates.started, bridge: this.bridgeName, - })).catch((error) => { + }); + this.clearConnectionTimeout(); + this.reconnecting = false; + }).catch((error) => { // NotAllowedError equals autoplay issues, fire autoplay handling event. // This will be handled in audio-manager. if (error.name === 'NotAllowedError') { @@ -275,6 +283,14 @@ export default class SFUAudioBridge extends BaseAudioBridge { } async _startBroker(options) { + try { + this.iceServers = await fetchWebRTCMappedStunTurnServers(this.sessionToken); + } catch (error) { + logger.error({ logCode: 'sfuaudio_stun-turn_fetch_failed' }, + 'SFU audio bridge failed to fetch STUN/TURN info, using default servers'); + this.iceServers = getMappedFallbackStun(); + } + return new Promise((resolve, reject) => { const { isListenOnly, @@ -285,7 +301,9 @@ export default class SFUAudioBridge extends BaseAudioBridge { const handleInitError = (_error) => { mapErrorCode(_error); - if (_error?.errorCode !== 1007 || !RETRY_THROUGH_RELAY || this.reconnecting) { + if (RETRYABLE_ERRORS.includes(_error?.errorCode) + || !RETRY_THROUGH_RELAY + || this.reconnecting) { reject(_error); } }; @@ -324,6 +342,9 @@ export default class SFUAudioBridge extends BaseAudioBridge { this.handleStart().then(resolve).catch(reject); }; + // Set up a connectionTimeout in case the server or network are botching + // negotiation or conn checks. + this.setConnectionTimeout(); this.broker.joinAudio().catch(handleInitError); } catch (error) { handleInitError(error); @@ -333,14 +354,7 @@ export default class SFUAudioBridge extends BaseAudioBridge { async joinAudio(options, callback) { this.callback = callback; - - try { - this.iceServers = await fetchWebRTCMappedStunTurnServers(this.sessionToken); - } catch (error) { - logger.error({ logCode: 'sfuaudio_stun-turn_fetch_failed' }, - 'SFU audio bridge failed to fetch STUN/TURN info, using default servers'); - this.iceServers = getMappedFallbackStun(); - } + this.reconnecting = false; return this._startBroker(options); } @@ -429,7 +443,8 @@ export default class SFUAudioBridge extends BaseAudioBridge { exitAudio() { const mediaElement = document.getElementById(MEDIA_TAG); - this.clearReconnectionTimeout(); + this.clearConnectionTimeout(); + this.reconnecting = false; if (this.broker) { this.broker.stop(); diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 3a365e6b15..2d2ea9c76b 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -640,7 +640,7 @@ public: - name: fullaudio path: 'bridge/sfu-audio-bridge' # Forces a retry with iceTransportPolicy = 'relay' if the first attempt - # fails with error code 1007. + # fails with a few selected errors codes (eg 1007, 1010) retryThroughRelay: false stunTurnServersFetchAddress: '/bigbluebutton/api/stuns' cacheStunTurnServers: true From 50d019ac014fbc80f443b025fcbb3259fcf253ac Mon Sep 17 00:00:00 2001 From: prlanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Mon, 31 Jul 2023 11:05:39 -0300 Subject: [PATCH 077/252] chore(audio): lower SFU audio negotiation timeout to 15s (from 25s) --- bigbluebutton-html5/private/config/settings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 2d2ea9c76b..c7eaaab288 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -656,7 +656,7 @@ public: callHangupTimeout: 2000 callHangupMaximumRetries: 10 echoTestNumber: 'echo' - listenOnlyCallTimeout: 25000 + listenOnlyCallTimeout: 15000 # Experimental. True is the canonical behavior. Flip to false to reverse # the negotiation flow for LO subscribers. listenOnlyOffering: false From 4384fa7aca318d42bb784e7ac8379f94c9350975 Mon Sep 17 00:00:00 2001 From: Anton B Date: Mon, 31 Jul 2023 16:19:04 -0300 Subject: [PATCH 078/252] test: add @flaky test flag and avoid them when running the tests on CI --- bigbluebutton-tests/playwright/package.json | 6 +++--- .../playwright/parameters/parameters.spec.js | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/bigbluebutton-tests/playwright/package.json b/bigbluebutton-tests/playwright/package.json index a6234231cd..ed1014f386 100644 --- a/bigbluebutton-tests/playwright/package.json +++ b/bigbluebutton-tests/playwright/package.json @@ -5,8 +5,8 @@ "test:filter": "npx playwright test -g", "test:headed": "npx playwright test --headed", "test:debug": "npx playwright test --debug -g", - "test-chromium-ci": "export CI='true' && npx playwright test --project=chromium --grep @ci", - "test-firefox-ci": "export CI='true' && npx playwright test --project=firefox --grep @ci", + "test-chromium-ci": "export CI='true' && npx playwright test --project=chromium --grep @ci --grep-invert @flaky", + "test-firefox-ci": "export CI='true' && npx playwright test --project=firefox --grep @ci --grep-invert @flaky", "rewrite-snapshots": "read -p 'CAUTION: You will delete ALL testing folders containing snapshots and run the tests to rewrite these files.\nProceed? (y/n) ' confirm && test $confirm = 'y' && sh core/scripts/rewrite-snapshots.sh" }, "dependencies": { @@ -19,4 +19,4 @@ "sha1": "^1.1.1", "xml2js": "^0.6.0" } -} +} \ No newline at end of file diff --git a/bigbluebutton-tests/playwright/parameters/parameters.spec.js b/bigbluebutton-tests/playwright/parameters/parameters.spec.js index 96ef80cf42..452499f864 100644 --- a/bigbluebutton-tests/playwright/parameters/parameters.spec.js +++ b/bigbluebutton-tests/playwright/parameters/parameters.spec.js @@ -4,6 +4,7 @@ const { DisabledFeatures } = require('./disabledFeatures'); const c = require('./constants'); const { encodeCustomParams, getAllShortcutParams, hexToRgb } = require('./util'); const { CreateParameters } = require('./createParameters'); +const { linkIssue } = require('../core/helpers'); test.describe.parallel('Create Parameters', () => { test('Record Meeting', async ({ browser, context, page }) => { @@ -246,18 +247,19 @@ test.describe.parallel('Create Parameters', () => { }); test.describe.serial(() => { - test('Download Presentation With Annotations', async ({ browser, context, page }) => { + test('Download Presentation With Annotations @flaky', async ({ browser, context, page }) => { + linkIssue('18408'); const disabledFeatures = new DisabledFeatures(browser, context); await disabledFeatures.initModPage(page, true, { createParameter: c.downloadPresentationWithAnnotationsDisabled }); await disabledFeatures.downloadPresentationWithAnnotations(); }); - test('Download Presentation With Annotations (exclude)', async ({ browser, context, page }) => { + test('Download Presentation With Annotations (exclude) @flaky', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); await disabledFeatures.initModPage(page, true, { createParameter: c.downloadPresentationWithAnnotationsExclude }); await disabledFeatures.downloadPresentationWithAnnotationsExclude(); }); }); - + test.describe.serial(() => { test('Import Presentation With Annotations From Breakout Rooms', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); From df8e9e9440762599301c239b48bc2163493b2b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Mon, 31 Jul 2023 17:03:29 -0300 Subject: [PATCH 079/252] split disabled download of presentation params --- .../presentation-uploader/component.jsx | 22 ++++---- .../presentation-uploader/container.jsx | 9 +++- .../component.jsx | 50 +++++++++++-------- .../imports/ui/services/features/index.js | 6 ++- .../private/config/settings.yml | 3 +- 5 files changed, 54 insertions(+), 36 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx index 21a39c45bb..584c0d8990 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx @@ -20,7 +20,8 @@ import { isPresentationEnabled } from '/imports/ui/services/features'; const { isMobile } = deviceInfo; const propTypes = { - allowDownloadable: PropTypes.bool.isRequired, + allowDownloadOriginal: PropTypes.bool.isRequired, + allowDownloadWithAnnotations: PropTypes.bool.isRequired, intl: PropTypes.shape({ formatMessage: PropTypes.func.isRequired, }).isRequired, @@ -804,7 +805,7 @@ class PresentationUploader extends Component { renderPresentationList() { const { presentations } = this.state; - const { intl, allowDownloadable } = this.props; + const { intl } = this.props; let presentationsSorted = presentations; @@ -846,9 +847,7 @@ class PresentationUploader extends Component { {intl.formatMessage(intlMessages.currentLabel)} - { - allowDownloadable ? {intl.formatMessage(intlMessages.actionsLabel)} : null - } + {intl.formatMessage(intlMessages.actionsLabel)} @@ -978,10 +977,10 @@ class PresentationUploader extends Component { renderDownloadableWithAnnotationsHint() { const { intl, - allowDownloadable, + allowDownloadWithAnnotations, } = this.props; - return allowDownloadable ? ( + return allowDownloadWithAnnotations ? ( {intl.formatMessage(intlMessages.exportHint)} @@ -994,7 +993,8 @@ class PresentationUploader extends Component { const { intl, selectedToBeNextCurrent, - allowDownloadable, + allowDownloadOriginal, + allowDownloadWithAnnotations, renderPresentationItemStatus, hasAnnotations, } = this.props; @@ -1068,8 +1068,8 @@ class PresentationUploader extends Component { { hasError ? null : ( - - {allowDownloadable ? ( + + {allowDownloadOriginal || allowDownloadWithAnnotations ? ( Session.set('showUploadPresentationView', false)} diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/container.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/container.jsx index 331e8d4133..c870eae3d8 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/container.jsx @@ -8,7 +8,11 @@ import PresUploaderToast from '/imports/ui/components/presentation/presentation- import PresentationUploader from './component'; import { UsersContext } from '/imports/ui/components/components-data/users-context/context'; import Auth from '/imports/ui/services/auth'; -import { isDownloadPresentationWithAnnotationsEnabled, isPresentationEnabled } from '/imports/ui/services/features'; +import { + isDownloadPresentationWithAnnotationsEnabled, + isDownloadOriginalPresentationEnabled, + isPresentationEnabled, +} from '/imports/ui/services/features'; import { hasAnnotations } from '/imports/ui/components/whiteboard/service'; const PRESENTATION_CONFIG = Meteor.settings.public.presentation; @@ -44,7 +48,8 @@ export default withTracker(() => { fileSizeMax: PRESENTATION_CONFIG.mirroredFromBBBCore.uploadSizeMax, filePagesMax: PRESENTATION_CONFIG.mirroredFromBBBCore.uploadPagesMax, fileValidMimeTypes: PRESENTATION_CONFIG.uploadValidMimeTypes, - allowDownloadable: isDownloadPresentationWithAnnotationsEnabled(), + allowDownloadOriginal: isDownloadOriginalPresentationEnabled(), + allowDownloadWithAnnotations: isDownloadPresentationWithAnnotationsEnabled(), handleSave: Service.handleSavePresentation, handleDismissToast: PresUploaderToast.handleDismissToast, renderToastList: Service.renderToastList, diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/presentation-download-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/presentation-download-dropdown/component.jsx index 9eae5b38ec..235d4db760 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/presentation-download-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/presentation-download-dropdown/component.jsx @@ -74,6 +74,8 @@ class PresentationDownloadDropdown extends PureComponent { handleDownloadingOfPresentation, handleToggleDownloadable, isDownloadable, + allowDownloadOriginal, + allowDownloadWithAnnotations, item, closeModal, } = this.props; @@ -88,31 +90,35 @@ class PresentationDownloadDropdown extends PureComponent { closeModal(); }; - if (!isDownloadable) { + if (allowDownloadOriginal) { + if (!isDownloadable) { + this.menuItems.push({ + key: this.actionsKey[0], + dataTest: 'enableOriginalPresentationDownload', + label: intl.formatMessage(intlMessages.enableOriginalPresentationDownload), + onClick: () => toggleDownloadOriginalPresentation(true), + }); + } else { + this.menuItems.push({ + key: this.actionsKey[0], + dataTest: 'disableOriginalPresentationDownload', + label: intl.formatMessage(intlMessages.disableOriginalPresentationDownload), + onClick: () => toggleDownloadOriginalPresentation(false), + }); + } + } + if (allowDownloadWithAnnotations) { this.menuItems.push({ - key: this.actionsKey[0], - dataTest: 'enableOriginalPresentationDownload', - label: intl.formatMessage(intlMessages.enableOriginalPresentationDownload), - onClick: () => toggleDownloadOriginalPresentation(true), - }); - } else { - this.menuItems.push({ - key: this.actionsKey[0], - dataTest: 'disableOriginalPresentationDownload', - label: intl.formatMessage(intlMessages.disableOriginalPresentationDownload), - onClick: () => toggleDownloadOriginalPresentation(false), + key: this.actionsKey[1], + id: 'sendCurrentStateDocument', + dataTest: 'sendCurrentStateDocument', + label: intl.formatMessage(intlMessages.sendCurrentStateDocument), + onClick: () => { + closeModal(); + handleDownloadingOfPresentation('Annotated'); + }, }); } - this.menuItems.push({ - key: this.actionsKey[1], - id: 'sendCurrentStateDocument', - dataTest: 'sendCurrentStateDocument', - label: intl.formatMessage(intlMessages.sendCurrentStateDocument), - onClick: () => { - closeModal(); - handleDownloadingOfPresentation('Annotated'); - }, - }); return this.menuItems; } diff --git a/bigbluebutton-html5/imports/ui/services/features/index.js b/bigbluebutton-html5/imports/ui/services/features/index.js index f07320bd4a..80674336df 100644 --- a/bigbluebutton-html5/imports/ui/services/features/index.js +++ b/bigbluebutton-html5/imports/ui/services/features/index.js @@ -61,7 +61,11 @@ export function isCustomVirtualBackgroundsEnabled() { } export function isDownloadPresentationWithAnnotationsEnabled() { - return getDisabledFeatures().indexOf('downloadPresentationWithAnnotations') === -1 && Meteor.settings.public.presentation.allowDownloadable; + return getDisabledFeatures().indexOf('downloadPresentationWithAnnotations') === -1 && Meteor.settings.public.presentation.allowDownloadWithAnnotations; +} + +export function isDownloadOriginalPresentationEnabled() { + return getDisabledFeatures().indexOf('downloadOriginalPresentation') === -1 && Meteor.settings.public.presentation.allowDownloadOriginal; } export function isSnapshotOfCurrentSlideEnabled() { diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 146250a8fb..c150129dca 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -742,7 +742,8 @@ public: - critical help: STATS_HELP_URL presentation: - allowDownloadable: true + allowDownloadOriginal: true + allowDownloadWithAnnotations: true allowSnapshotOfCurrentSlide: true panZoomThrottle: 32 restoreOnUpdate: true From 4b6686ef76d2aeb42bc8fa88e85b891f05fae8c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Mon, 31 Jul 2023 17:07:41 -0300 Subject: [PATCH 080/252] add comment --- bigbluebutton-web/grails-app/conf/bigbluebutton.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties index 8f6115a638..a861405e30 100644 --- a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties +++ b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties @@ -432,7 +432,8 @@ endWhenNoModeratorDelayInMinutes=1 # Available options: # chat, sharedNotes, polls, screenshare, externalVideos, presentation, downloadPresentationWithAnnotations # learningDashboard, layouts, captions, liveTranscription, virtualBackgrounds, customVirtualBackgrounds -# breakoutRooms, importSharedNotesFromBreakoutRooms, importPresentationWithAnnotationsFromBreakoutRooms +# breakoutRooms, importSharedNotesFromBreakoutRooms, importPresentationWithAnnotationsFromBreakoutRooms, +# downloadOriginalPresentation disabledFeatures= # Notify users that recording is on From 70a8267284f688c885d48ca7d126963d055f3405 Mon Sep 17 00:00:00 2001 From: KDSBrowne Date: Tue, 1 Aug 2023 02:45:13 +0000 Subject: [PATCH 081/252] inject presentation options btn into tldraw toolbar | lint issues --- .../ui/components/presentation/component.jsx | 411 +++++++++++------- .../ui/components/whiteboard/component.jsx | 86 +++- .../ui/components/whiteboard/container.jsx | 191 ++++---- .../presentation-ops-injector/component.jsx | 317 ++++++++++++++ .../presentation-ops-injector/container.jsx | 53 +++ .../menu/component.jsx | 253 +++++++++++ 6 files changed, 1055 insertions(+), 256 deletions(-) create mode 100644 bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/component.jsx create mode 100644 bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/container.jsx create mode 100644 bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/menu/component.jsx diff --git a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx index f7ea701e94..e5b724cb24 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx @@ -1,62 +1,64 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import WhiteboardContainer from '/imports/ui/components/whiteboard/container'; -import { HUNDRED_PERCENT, MAX_PERCENT } from '/imports/utils/slideCalcUtils'; -import { SPACE } from '/imports/utils/keyCodes'; -import { defineMessages, injectIntl } from 'react-intl'; -import { toast } from 'react-toastify'; -import { Session } from 'meteor/session'; -import PresentationToolbarContainer from './presentation-toolbar/container'; -import PresentationMenu from './presentation-menu/container'; -import Styled from './styles'; -import FullscreenService from '/imports/ui/components/common/fullscreen-button/service'; -import Icon from '/imports/ui/components/common/icon/component'; -import PollingContainer from '/imports/ui/components/polling/container'; -import { ACTIONS, LAYOUT_TYPE } from '../layout/enums'; -import DEFAULT_VALUES from '../layout/defaultValues'; -import { colorContentBackground } from '/imports/ui/stylesheets/styled-components/palette'; -import browserInfo from '/imports/utils/browserInfo'; -import { addNewAlert } from '../screenreader-alert/service'; -import { clearCursors } from '/imports/ui/components/whiteboard/cursors/service'; -import { debounce } from 'radash'; +import React, { PureComponent } from "react"; +import PropTypes from "prop-types"; +import WhiteboardContainer from "/imports/ui/components/whiteboard/container"; +import { HUNDRED_PERCENT, MAX_PERCENT } from "/imports/utils/slideCalcUtils"; +import { SPACE } from "/imports/utils/keyCodes"; +import { defineMessages, injectIntl } from "react-intl"; +import { toast } from "react-toastify"; +import { Session } from "meteor/session"; +import PresentationToolbarContainer from "./presentation-toolbar/container"; +import PresentationMenu from "./presentation-menu/container"; +import Styled from "./styles"; +import FullscreenService from "/imports/ui/components/common/fullscreen-button/service"; +import Icon from "/imports/ui/components/common/icon/component"; +import PollingContainer from "/imports/ui/components/polling/container"; +import { ACTIONS, LAYOUT_TYPE } from "../layout/enums"; +import DEFAULT_VALUES from "../layout/defaultValues"; +import { colorContentBackground } from "/imports/ui/stylesheets/styled-components/palette"; +import browserInfo from "/imports/utils/browserInfo"; +import { addNewAlert } from "../screenreader-alert/service"; +import { clearCursors } from "/imports/ui/components/whiteboard/cursors/service"; +import { debounce } from "radash"; const intlMessages = defineMessages({ presentationLabel: { - id: 'app.presentationUploder.title', - description: 'presentation area element label', + id: "app.presentationUploder.title", + description: "presentation area element label", }, changeNotification: { - id: 'app.presentation.notificationLabel', - description: 'label displayed in toast when presentation switches', + id: "app.presentation.notificationLabel", + description: "label displayed in toast when presentation switches", }, downloadLabel: { - id: 'app.presentation.downloadLabel', - description: 'label for downloadable presentations', + id: "app.presentation.downloadLabel", + description: "label for downloadable presentations", }, slideContentStart: { - id: 'app.presentation.startSlideContent', - description: 'Indicate the slide content start', + id: "app.presentation.startSlideContent", + description: "Indicate the slide content start", }, slideContentEnd: { - id: 'app.presentation.endSlideContent', - description: 'Indicate the slide content end', + id: "app.presentation.endSlideContent", + description: "Indicate the slide content end", }, slideContentChanged: { - id: 'app.presentation.changedSlideContent', - description: 'Indicate the slide content has changed', + id: "app.presentation.changedSlideContent", + description: "Indicate the slide content has changed", }, noSlideContent: { - id: 'app.presentation.emptySlideContent', - description: 'No content available for slide', + id: "app.presentation.emptySlideContent", + description: "No content available for slide", }, }); const { isSafari } = browserInfo; -const FULLSCREEN_CHANGE_EVENT = isSafari ? 'webkitfullscreenchange' : 'fullscreenchange'; +const FULLSCREEN_CHANGE_EVENT = isSafari + ? "webkitfullscreenchange" + : "fullscreenchange"; const getToolbarHeight = () => { let height = 0; - const toolbarEl = document.getElementById('presentationToolbarWrapper'); + const toolbarEl = document.getElementById("presentationToolbarWrapper"); if (toolbarEl) { const { clientHeight } = toolbarEl; height = clientHeight; @@ -90,7 +92,8 @@ class Presentation extends PureComponent { this.panAndZoomChanger = this.panAndZoomChanger.bind(this); this.fitToWidthHandler = this.fitToWidthHandler.bind(this); this.onFullscreenChange = this.onFullscreenChange.bind(this); - this.getPresentationSizesAvailable = this.getPresentationSizesAvailable.bind(this); + this.getPresentationSizesAvailable = + this.getPresentationSizesAvailable.bind(this); this.handleResize = debounce({ delay: 200 }, this.handleResize.bind(this)); this.setTldrawAPI = this.setTldrawAPI.bind(this); this.setIsPanning = this.setIsPanning.bind(this); @@ -99,30 +102,39 @@ class Presentation extends PureComponent { this.renderPresentationMenu = this.renderPresentationMenu.bind(this); this.onResize = () => setTimeout(this.handleResize.bind(this), 0); - this.renderCurrentPresentationToast = this.renderCurrentPresentationToast.bind(this); + this.renderCurrentPresentationToast = + this.renderCurrentPresentationToast.bind(this); this.setPresentationRef = this.setPresentationRef.bind(this); this.setTldrawIsMounting = this.setTldrawIsMounting.bind(this); - Session.set('componentPresentationWillUnmount', false); + Session.set("componentPresentationWillUnmount", false); } static getDerivedStateFromProps(props, state) { const { prevProps } = state; const stateChange = { prevProps: props }; - if (props.userIsPresenter - && (!prevProps || !prevProps.userIsPresenter) - && props.currentSlide - && props.slidePosition) { - let potentialZoom = 100 / (props.slidePosition.viewBoxWidth / props.slidePosition.width); - potentialZoom = Math.max(HUNDRED_PERCENT, Math.min(MAX_PERCENT, potentialZoom)); + if ( + props.userIsPresenter && + (!prevProps || !prevProps.userIsPresenter) && + props.currentSlide && + props.slidePosition + ) { + let potentialZoom = + 100 / (props.slidePosition.viewBoxWidth / props.slidePosition.width); + potentialZoom = Math.max( + HUNDRED_PERCENT, + Math.min(MAX_PERCENT, potentialZoom) + ); stateChange.zoom = potentialZoom; } if (!prevProps) return stateChange; // When presenter is changed or slide changed we reset localPosition - if (prevProps.currentSlide?.id !== props.currentSlide?.id - || prevProps.userIsPresenter !== props.userIsPresenter) { + if ( + prevProps.currentSlide?.id !== props.currentSlide?.id || + prevProps.userIsPresenter !== props.userIsPresenter + ) { stateChange.localPosition = undefined; } @@ -131,19 +143,31 @@ class Presentation extends PureComponent { componentDidMount() { this.getInitialPresentationSizes(); - this.refPresentationContainer.addEventListener('keydown', this.handlePanShortcut); - this.refPresentationContainer.addEventListener('keyup', this.handlePanShortcut); - this.refPresentationContainer - .addEventListener(FULLSCREEN_CHANGE_EVENT, this.onFullscreenChange); - window.addEventListener('resize', this.onResize, false); + this.refPresentationContainer.addEventListener( + "keydown", + this.handlePanShortcut + ); + this.refPresentationContainer.addEventListener( + "keyup", + this.handlePanShortcut + ); + this.refPresentationContainer.addEventListener( + FULLSCREEN_CHANGE_EVENT, + this.onFullscreenChange + ); + window.addEventListener("resize", this.onResize, false); const { - currentSlide, slidePosition, numPages, layoutContextDispatch, currentPresentationId, + currentSlide, + slidePosition, + numPages, + layoutContextDispatch, + currentPresentationId, } = this.props; if (currentPresentationId) { this.setState({ - hadPresentation: true + hadPresentation: true, }); } @@ -184,9 +208,14 @@ class Presentation extends PureComponent { numPages, currentPresentationId, } = this.props; - const { - presentationWidth, presentationHeight, zoom, isPanning, fitToWidth, presentationId, hadPresentation, + presentationWidth, + presentationHeight, + zoom, + isPanning, + fitToWidth, + presentationId, + hadPresentation, } = this.state; const { numCameras: prevNumCameras, @@ -210,22 +239,29 @@ class Presentation extends PureComponent { } if ( - currentSlide?.num != null - && prevProps?.currentSlide?.num != null - && currentSlide?.num !== prevProps.currentSlide?.num + currentSlide?.num != null && + prevProps?.currentSlide?.num != null && + currentSlide?.num !== prevProps.currentSlide?.num ) { - addNewAlert(intl.formatMessage(intlMessages.slideContentChanged, { 0: currentSlide.num })); + addNewAlert( + intl.formatMessage(intlMessages.slideContentChanged, { + 0: currentSlide.num, + }) + ); } if (currentPresentation) { - const downloadableOn = !prevProps?.currentPresentation?.downloadable - && currentPresentation.downloadable; + const downloadableOn = + !prevProps?.currentPresentation?.downloadable && + currentPresentation.downloadable; - const shouldCloseToast = !(currentPresentation.downloadable && !userIsPresenter); + const shouldCloseToast = !( + currentPresentation.downloadable && !userIsPresenter + ); if ( - prevProps?.currentPresentation?.id !== currentPresentation.id - || (downloadableOn && !userIsPresenter) + prevProps?.currentPresentation?.id !== currentPresentation.id || + (downloadableOn && !userIsPresenter) ) { if (this.currentPresentationToastId) { toast.update(this.currentPresentationToastId, { @@ -233,16 +269,22 @@ class Presentation extends PureComponent { render: this.renderCurrentPresentationToast(), }); } else { - this.currentPresentationToastId = toast(this.renderCurrentPresentationToast(), { - onClose: () => { this.currentPresentationToastId = null; }, - autoClose: shouldCloseToast, - className: 'actionToast currentPresentationToast', - }); + this.currentPresentationToastId = toast( + this.renderCurrentPresentationToast(), + { + onClose: () => { + this.currentPresentationToastId = null; + }, + autoClose: shouldCloseToast, + className: "actionToast currentPresentationToast", + } + ); } } - const downloadableOff = prevProps?.currentPresentation?.downloadable - && !currentPresentation.downloadable; + const downloadableOff = + prevProps?.currentPresentation?.downloadable && + !currentPresentation.downloadable; if (this.currentPresentationToastId && downloadableOff) { toast.update(this.currentPresentationToastId, { @@ -269,13 +311,23 @@ class Presentation extends PureComponent { const isInitialPresentation = currentPresentation.isInitialPresentation; - if (!presentationIsOpen && restoreOnUpdate && (currentSlide || presentationChanged)) { + if ( + !presentationIsOpen && + restoreOnUpdate && + (currentSlide || presentationChanged) + ) { const slideChanged = currentSlide.id !== prevProps.currentSlide.id; - const positionChanged = slidePosition - .viewBoxHeight !== prevProps.slidePosition.viewBoxHeight - || slidePosition.viewBoxWidth !== prevProps.slidePosition.viewBoxWidth; + const positionChanged = + slidePosition.viewBoxHeight !== + prevProps.slidePosition.viewBoxHeight || + slidePosition.viewBoxWidth !== prevProps.slidePosition.viewBoxWidth; const pollPublished = publishedPoll && !prevProps.publishedPoll; - if (slideChanged || positionChanged || pollPublished || (presentationChanged && (hadPresentation || !isInitialPresentation))) { + if ( + slideChanged || + positionChanged || + pollPublished || + (presentationChanged && (hadPresentation || !isInitialPresentation)) + ) { setPresentationIsOpen(layoutContextDispatch, !presentationIsOpen); } } @@ -283,12 +335,15 @@ class Presentation extends PureComponent { if (presentationChanged) { this.setState({ presentationId: currentPresentationId, - hadPresentation: true + hadPresentation: true, }); } - if ((presentationBounds !== prevPresentationBounds) - || (!presentationWidth && !presentationHeight)) this.onResize(); + if ( + presentationBounds !== prevPresentationBounds || + (!presentationWidth && !presentationHeight) + ) + this.onResize(); } else if (slidePosition) { const { width: currWidth, height: currHeight } = slidePosition; @@ -305,28 +360,38 @@ class Presentation extends PureComponent { }); } - if ((zoom <= HUNDRED_PERCENT && isPanning && !fitToWidth) - || (!userIsPresenter && prevProps.userIsPresenter)) { + if ( + (zoom <= HUNDRED_PERCENT && isPanning && !fitToWidth) || + (!userIsPresenter && prevProps.userIsPresenter) + ) { this.setIsPanning(); } } componentWillUnmount() { - Session.set('componentPresentationWillUnmount', true); + Session.set("componentPresentationWillUnmount", true); const { fullscreenContext, layoutContextDispatch } = this.props; - window.removeEventListener('resize', this.onResize, false); - this.refPresentationContainer - .removeEventListener(FULLSCREEN_CHANGE_EVENT, this.onFullscreenChange); - this.refPresentationContainer.removeEventListener('keydown', this.handlePanShortcut); - this.refPresentationContainer.removeEventListener('keyup', this.handlePanShortcut); + window.removeEventListener("resize", this.onResize, false); + this.refPresentationContainer.removeEventListener( + FULLSCREEN_CHANGE_EVENT, + this.onFullscreenChange + ); + this.refPresentationContainer.removeEventListener( + "keydown", + this.handlePanShortcut + ); + this.refPresentationContainer.removeEventListener( + "keyup", + this.handlePanShortcut + ); if (fullscreenContext) { layoutContextDispatch({ type: ACTIONS.SET_FULLSCREEN_ELEMENT, value: { - element: '', - group: '', + element: "", + group: "", }, }); } @@ -337,9 +402,9 @@ class Presentation extends PureComponent { const { isPanning } = this.state; if (e.keyCode === SPACE && userIsPresenter) { switch (e.type) { - case 'keyup': + case "keyup": return isPanning && this.setIsPanning(); - case 'keydown': + case "keydown": return !isPanning && this.setIsPanning(); default: } @@ -351,7 +416,7 @@ class Presentation extends PureComponent { const presentationSizes = this.getPresentationSizesAvailable(); if (Object.keys(presentationSizes).length > 0) { // updating the size of the space available for the slide - if (!Session.get('componentPresentationWillUnmount')) { + if (!Session.get("componentPresentationWillUnmount")) { this.setState({ presentationHeight: presentationSizes.presentationHeight, presentationWidth: presentationSizes.presentationWidth, @@ -362,7 +427,9 @@ class Presentation extends PureComponent { onFullscreenChange() { const { isFullscreen } = this.state; - const newIsFullscreen = FullscreenService.isFullScreen(this.refPresentationContainer); + const newIsFullscreen = FullscreenService.isFullScreen( + this.refPresentationContainer + ); if (isFullscreen !== newIsFullscreen) { this.setState({ isFullscreen: newIsFullscreen }); } @@ -411,9 +478,11 @@ class Presentation extends PureComponent { }; if (newPresentationAreaSize) { - presentationSizes.presentationWidth = newPresentationAreaSize.presentationAreaWidth; - presentationSizes.presentationHeight = newPresentationAreaSize - .presentationAreaHeight - (getToolbarHeight() || 0); + presentationSizes.presentationWidth = + newPresentationAreaSize.presentationAreaWidth; + presentationSizes.presentationHeight = + newPresentationAreaSize.presentationAreaHeight - + (getToolbarHeight() || 0); return presentationSizes; } @@ -445,9 +514,7 @@ class Presentation extends PureComponent { } fitToWidthHandler() { - const { - fitToWidth, - } = this.state; + const { fitToWidth } = this.state; this.setState({ fitToWidth: !fitToWidth, @@ -458,24 +525,19 @@ class Presentation extends PureComponent { updateLocalPosition(x, y, width, height, zoom) { this.setState({ localPosition: { - x, y, width, height, + x, + y, + width, + height, }, zoom, }); } calculateSize(viewBoxDimensions) { - const { - presentationHeight, - presentationWidth, - fitToWidth, - } = this.state; + const { presentationHeight, presentationWidth, fitToWidth } = this.state; - const { - userIsPresenter, - currentSlide, - slidePosition, - } = this.props; + const { userIsPresenter, currentSlide, slidePosition } = this.props; if (!currentSlide || !slidePosition) { return { width: 0, height: 0 }; @@ -511,7 +573,7 @@ class Presentation extends PureComponent { if (svgHeight > presentationHeight) svgHeight = presentationHeight; } - if (typeof svgHeight !== 'number' || typeof svgWidth !== 'number') { + if (typeof svgHeight !== "number" || typeof svgWidth !== "number") { return { width: 0, height: 0 }; } @@ -522,11 +584,7 @@ class Presentation extends PureComponent { } panAndZoomChanger(w, h, x, y) { - const { - currentSlide, - podId, - zoomSlide, - } = this.props; + const { currentSlide, podId, zoomSlide } = this.props; zoomSlide(currentSlide.num, podId, w, h, x, y); } @@ -548,19 +606,18 @@ class Presentation extends PureComponent { multiUserSize, multiUser, } = this.props; - const { - zoom, fitToWidth, isPanning, - } = this.state; + const { zoom, fitToWidth, isPanning } = this.state; if (!currentSlide) return null; const { presentationToolbarMinWidth } = DEFAULT_VALUES; - const toolbarWidth = ((this.refWhiteboardArea && svgWidth > presentationToolbarMinWidth) - || isMobile - || (layoutType === LAYOUT_TYPE.VIDEO_FOCUS && numCameras > 0)) - ? svgWidth - : presentationToolbarMinWidth; + const toolbarWidth = + (this.refWhiteboardArea && svgWidth > presentationToolbarMinWidth) || + isMobile || + (layoutType === LAYOUT_TYPE.VIDEO_FOCUS && numCameras > 0) + ? svgWidth + : presentationToolbarMinWidth; return ( {`${currentPresentation.name}`} - {downloadable && !userIsPresenter - ? ( - - - - {intl.formatMessage(intlMessages.downloadLabel)} - - - ) : null} + {downloadable && !userIsPresenter ? ( + + + + {intl.formatMessage(intlMessages.downloadLabel)} + + + ) : null} ); } @@ -635,9 +696,12 @@ class Presentation extends PureComponent { intl, fullscreenElementId, layoutContextDispatch, + userIsPresenter, } = this.props; const { tldrawAPI, isToolbarVisible } = this.state; + if (userIsPresenter && isToolbarVisible) return null; + return ( presentationToolbarMinWidth || isMobile) - && !(layoutType === LAYOUT_TYPE.VIDEO_FOCUS && numCameras > 0 && !fullscreenContext); + const isLargePresentation = + (svgWidth > presentationToolbarMinWidth || isMobile) && + !( + layoutType === LAYOUT_TYPE.VIDEO_FOCUS && + numCameras > 0 && + !fullscreenContext + ); const containerWidth = isLargePresentation ? svgWidth : presentationToolbarMinWidth; - const slideContent = currentSlide?.content ? `${intl.formatMessage(intlMessages.slideContentStart)} + const slideContent = currentSlide?.content + ? `${intl.formatMessage(intlMessages.slideContentStart)} ${currentSlide.content} - ${intl.formatMessage(intlMessages.slideContentEnd)}` : intl.formatMessage(intlMessages.noSlideContent); + ${intl.formatMessage(intlMessages.slideContentEnd)}` + : intl.formatMessage(intlMessages.noSlideContent); return ( <> { this.refPresentationContainer = ref; }} + ref={(ref) => { + this.refPresentationContainer = ref; + }} style={{ top: presentationBounds.top, left: presentationBounds.left, right: presentationBounds.right, width: presentationBounds.width, height: presentationBounds.height, - display: !presentationIsOpen ? 'none' : 'flex', - overflow: 'hidden', + display: !presentationIsOpen ? "none" : "flex", + overflow: "hidden", zIndex: fullscreenContext ? presentationBounds.zIndex : undefined, background: - layoutType === LAYOUT_TYPE.VIDEO_FOCUS && numCameras > 0 && !fullscreenContext + layoutType === LAYOUT_TYPE.VIDEO_FOCUS && + numCameras > 0 && + !fullscreenContext ? colorContentBackground : null, }} > - { this.refPresentation = ref; }}> + { + this.refPresentation = ref; + }} + >
- {slideContent} - {!tldrawIsMounting && currentSlide && this.renderPresentationMenu()} + + {slideContent} + + {!tldrawIsMounting && + currentSlide && + this.renderPresentationMenu()} {!tldrawIsMounting && ( { this.refPresentationToolbar = ref; }} - style={ - { - width: containerWidth, - } - } + ref={(ref) => { + this.refPresentationToolbar = ref; + }} + style={{ + width: containerWidth, + }} > {this.renderPresentationToolbar(svgWidth)} @@ -805,7 +889,6 @@ class Presentation extends PureComponent { - ); } diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx index 9f0a3208af..8321daa07c 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import _ from 'lodash'; import { TldrawApp, Tldraw } from '@tldraw/tldraw'; @@ -19,6 +20,7 @@ import PanToolInjector from './pan-tool-injector/component'; import { findRemoved, filterInvalidShapes, mapLanguage, sendShapeChanges, usePrevious, } from './utils'; +import PresentationOpsContainer from './presentation-ops-injector/container'; const SMALL_HEIGHT = 435; const SMALLEST_DOCK_HEIGHT = 475; @@ -75,6 +77,11 @@ export default function Whiteboard(props) { sidebarNavigationWidth, animations, isToolbarVisible, + fullscreenRef, + fullscreen, + setIsToolbarVisible, + fullscreenElementId, + layoutContextDispatch, } = props; const { pages, pageStates } = initDefaultPages(curPres?.pages.length || 1); const rDocument = React.useRef({ @@ -104,6 +111,37 @@ export default function Whiteboard(props) { const isMountedRef = React.useRef(true); const [isToolLocked, setIsToolLocked] = React.useState(tldrawAPI?.appState?.isToolLocked); const [bgShape, setBgShape] = React.useState(null); + const [labels, setLabels] = React.useState({ + closeLabel: '', + activeLable: '', + optionsLabel: '', + fullscreenLabel: '', + exitFullscreenLabel: '', + hideToolsDesc: '', + showToolsDesc: '', + downloading: '', + downloaded: '', + downloadFailed: '', + snapshotLabel: '', + whiteboardLabel: '', + }); + + React.useEffect(() => { + setLabels({ + closeLabel: intl.formatMessage({id: 'app.dropdown.close', description: 'Close button label'}), + activeLable: intl.formatMessage({id: 'app.dropdown.list.item.activeLabel', description: 'active item label'}), + optionsLabel: intl.formatMessage({id: 'app.navBar.settingsDropdown.optionsLabel', description: 'Options button label'}), + fullscreenLabel: intl.formatMessage({id: 'app.presentation.options.fullscreen', description: 'Fullscreen label'}), + exitFullscreenLabel: intl.formatMessage({id: 'app.presentation.options.exitFullscreen', description: 'Exit fullscreen label'}), + hideToolsDesc: intl.formatMessage({id: 'app.presentation.presentationToolbar.hideToolsDesc', description: 'Hide toolbar label'}), + showToolsDesc: intl.formatMessage({id: 'app.presentation.presentationToolbar.showToolsDesc', description: 'Show toolbar label'}), + downloading: intl.formatMessage({id: 'app.presentation.options.downloading', description: 'Downloading label'}), + downloaded: intl.formatMessage({id: 'app.presentation.options.downloaded', description: 'Downloaded label'}), + downloadFailed: intl.formatMessage({id: 'app.presentation.options.downloadFailed', description: 'Downloaded failed label'}), + snapshotLabel: intl.formatMessage({id: 'app.presentation.options.snapshot', description: 'Snapshot of current slide label'}), + whiteboardLabel: intl.formatMessage({id: 'app.shortcut-help.whiteboard', description: 'used for aria whiteboard options button label'}), + }); + }, [intl?.locale]); // eslint-disable-next-line arrow-body-style React.useEffect(() => { @@ -545,11 +583,8 @@ export default function Whiteboard(props) { const fullscreenToggleHandler = () => { const { - fullscreenElementId, isFullscreen, - layoutContextDispatch, fullscreenAction, - fullscreenRef, handleToggleFullScreen, } = props; @@ -676,6 +711,44 @@ export default function Whiteboard(props) { const onPatch = (e, t, reason) => { if (!e?.pageState || !reason) return; + // Append Presentation Options to Tldraw + const tdStylesParent = document.getElementById('TD-Styles-Parent'); + if (tdStylesParent) { + tdStylesParent.style.right = '0px'; + tdStylesParent.style.width = '17.75rem'; + let presentationMenuNode = document.getElementById('PresentationMenuId'); + if (!presentationMenuNode) { + presentationMenuNode = document.createElement('div'); + presentationMenuNode.setAttribute('id', 'PresentationMenuId'); + tdStylesParent.appendChild(presentationMenuNode); + } + + ReactDOM.render( + , presentationMenuNode + ); + } + if (((isPanning || panSelected) && (reason === 'selected' || reason === 'set_hovered_id'))) { e.patchState( { @@ -936,7 +1009,10 @@ export default function Whiteboard(props) { if (command && command?.id?.includes('change_page')) { const camera = tldrawAPI?.getPageState()?.camera; if (currentCameraPoint[app?.currentPageId] && camera) { - tldrawAPI?.setCamera([currentCameraPoint[app?.currentPageId][0], currentCameraPoint[app?.currentPageId][1]], camera?.zoom); + tldrawAPI?.setCamera( + [currentCameraPoint[app?.currentPageId][0], currentCameraPoint[app?.currentPageId][1]], + camera?.zoom + ); } } @@ -1058,7 +1134,7 @@ export default function Whiteboard(props) { const menuOffset = menuOffsetValues[isRTL][isIphone]; return ( -
+
{ const isRTL = layoutSelect((i) => i.isRTL); const width = layoutSelect((i) => i?.output?.presentation?.width); const height = layoutSelect((i) => i?.output?.presentation?.height); - const sidebarNavigationWidth = layoutSelect((i) => i?.output?.sidebarNavigation?.width); + const sidebarNavigationWidth = layoutSelect( + (i) => i?.output?.sidebarNavigation?.width + ); const { users } = usingUsersContext; const currentUser = users[Auth.meetingID][Auth.userID]; const isPresenter = currentUser.presenter; const isModerator = currentUser.role === ROLE_MODERATOR; const { maxStickyNoteLength, maxNumberOfAnnotations } = WHITEBOARD_CONFIG; const fontFamily = WHITEBOARD_CONFIG.styles.text.family; - const handleToggleFullScreen = (ref) => FullscreenService.toggleFullScreen(ref); + const fullscreen = layoutSelect((i) => i.fullscreen); + const handleToggleFullScreen = (ref) => + FullscreenService.toggleFullScreen(ref); + const layoutContextDispatch = layoutDispatch(); const { shapes } = props; const hasShapeAccess = (id) => { const owner = shapes[id]?.userId; - const isBackgroundShape = id?.includes('slide-background'); - const isPollsResult = shapes[id]?.name?.includes('poll-result'); - const hasAccess = !isBackgroundShape && !isPollsResult || isPresenter - && ((owner && owner === currentUser?.userId) || !owner || isPresenter || isModerator); + const isBackgroundShape = id?.includes("slide-background"); + const isPollsResult = shapes[id]?.name?.includes("poll-result"); + const hasAccess = + (!isBackgroundShape && !isPollsResult) || + (isPresenter && + ((owner && owner === currentUser?.userId) || + !owner || + isPresenter || + isModerator)); + return hasAccess; }; // set shapes as locked for those who aren't allowed to edit it @@ -65,7 +74,7 @@ const WhiteboardContainer = (props) => { return ( { maxStickyNoteLength, maxNumberOfAnnotations, fontFamily, + fullscreen, hasShapeAccess, handleToggleFullScreen, sidebarNavigationWidth, + layoutContextDispatch, }} {...props} meetingId={Auth.meetingID} @@ -85,69 +96,75 @@ const WhiteboardContainer = (props) => { ); }; -export default withTracker(({ - whiteboardId, - curPageId, - intl, - slidePosition, - svgUri, - podId, - presentationId, - darkTheme, -}) => { - const shapes = getShapes(whiteboardId, curPageId, intl); - const curPres = getCurrentPres(); - const { isIphone } = deviceInfo; - - shapes['slide-background-shape'] = { - assetId: `slide-background-asset-${curPageId}`, - childIndex: -1, - id: 'slide-background-shape', - name: 'Image', - type: TDShapeType.Image, - parentId: `${curPageId}`, - point: [0, 0], - isLocked: true, - size: [slidePosition?.width || 0, slidePosition?.height || 0], - style: { - dash: DashStyle.Draw, - size: SizeStyle.Medium, - color: ColorStyle.Blue, - }, - }; - - const assets = {}; - assets[`slide-background-asset-${curPageId}`] = { - id: `slide-background-asset-${curPageId}`, - size: [slidePosition?.width || 0, slidePosition?.height || 0], - src: svgUri, - type: 'image', - }; - - return { - initDefaultPages, - persistShape, - isMultiUserActive, - hasMultiUserAccess, - changeCurrentSlide, - shapes, - assets, - curPres, - removeShapes, - zoomSlide: PresentationToolbarService.zoomSlide, - skipToSlide: PresentationToolbarService.skipToSlide, - nextSlide: PresentationToolbarService.nextSlide, - previousSlide: PresentationToolbarService.previousSlide, - numberOfSlides: PresentationToolbarService.getNumberOfSlides(podId, presentationId), - notifyNotAllowedChange, - notifyShapeNumberExceeded, +export default withTracker( + ({ + whiteboardId, + curPageId, + intl, + slidePosition, + svgUri, + podId, + presentationId, darkTheme, - whiteboardToolbarAutoHide: SettingsService?.application?.whiteboardToolbarAutoHide, - animations: SettingsService?.application?.animations, - toggleToolsAnimations, - isIphone, - }; -})(WhiteboardContainer); + }) => { + const shapes = getShapes(whiteboardId, curPageId, intl); + const curPres = getCurrentPres(); + const { isIphone } = deviceInfo; + + shapes["slide-background-shape"] = { + assetId: `slide-background-asset-${curPageId}`, + childIndex: -1, + id: "slide-background-shape", + name: "Image", + type: TDShapeType.Image, + parentId: `${curPageId}`, + point: [0, 0], + isLocked: true, + size: [slidePosition?.width || 0, slidePosition?.height || 0], + style: { + dash: DashStyle.Draw, + size: SizeStyle.Medium, + color: ColorStyle.Blue, + }, + }; + + const assets = {}; + assets[`slide-background-asset-${curPageId}`] = { + id: `slide-background-asset-${curPageId}`, + size: [slidePosition?.width || 0, slidePosition?.height || 0], + src: svgUri, + type: "image", + }; + + return { + initDefaultPages, + persistShape, + isMultiUserActive, + hasMultiUserAccess, + changeCurrentSlide, + shapes, + assets, + curPres, + removeShapes, + zoomSlide: PresentationToolbarService.zoomSlide, + skipToSlide: PresentationToolbarService.skipToSlide, + nextSlide: PresentationToolbarService.nextSlide, + previousSlide: PresentationToolbarService.previousSlide, + numberOfSlides: PresentationToolbarService.getNumberOfSlides( + podId, + presentationId + ), + notifyNotAllowedChange, + notifyShapeNumberExceeded, + darkTheme, + whiteboardToolbarAutoHide: + SettingsService?.application?.whiteboardToolbarAutoHide, + animations: SettingsService?.application?.animations, + toggleToolsAnimations, + isIphone, + }; + } +)(WhiteboardContainer); WhiteboardContainer.propTypes = { shapes: PropTypes.objectOf(PropTypes.shape).isRequired, diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/component.jsx new file mode 100644 index 0000000000..afbe440b10 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/component.jsx @@ -0,0 +1,317 @@ +import React, { useState, useEffect, useRef } from 'react'; +import PropTypes from 'prop-types'; +import { toPng } from 'html-to-image'; +import { toast } from 'react-toastify'; +import logger from '/imports/startup/client/logger'; +import Styled from '/imports/ui/components/presentation/presentation-menu/styles'; +import BBBMenu from './menu/component'; +import TooltipContainer from '/imports/ui/components/common/tooltip/container'; +import { ACTIONS } from '/imports/ui/components/layout/enums'; +import browserInfo from '/imports/utils/browserInfo'; +import AppService from '/imports/ui/components/app/service'; + +const propTypes = { + handleToggleFullscreen: PropTypes.func.isRequired, + isFullscreen: PropTypes.bool, + elementName: PropTypes.string, + fullscreenRef: PropTypes.instanceOf(Element), + meetingName: PropTypes.string, + isIphone: PropTypes.bool, + elementId: PropTypes.string, + elementGroup: PropTypes.string, + currentElement: PropTypes.string, + currentGroup: PropTypes.string, + layoutContextDispatch: PropTypes.func.isRequired, + isRTL: PropTypes.bool, + tldrawAPI: PropTypes.shape({ + copySvg: PropTypes.func.isRequired, + getShapes: PropTypes.func.isRequired, + currentPageId: PropTypes.string.isRequired, + }), +}; + +const defaultProps = { + isIphone: false, + isFullscreen: false, + isRTL: false, + elementName: '', + meetingName: '', + fullscreenRef: null, + elementId: '', + elementGroup: '', + currentElement: '', + currentGroup: '', + tldrawAPI: null, +}; + +const PresentationOps = (props) => { + const { + isFullscreen, + elementId, + elementName, + elementGroup, + currentElement, + currentGroup, + fullscreenRef, + tldrawAPI, + handleToggleFullscreen, + layoutContextDispatch, + meetingName, + isIphone, + isRTL, + isToolbarVisible, + setIsToolbarVisible, + exitFullscreenLabel, + fullscreenLabel, + hideToolsDesc, + showToolsDesc, + downloading, + downloaded, + downloadFailed, + snapshotLabel, + hasWBAccess, + amIPresenter, + optionsLabel, + whiteboardLabel, + } = props; + + const [state, setState] = useState({ + hasError: false, + loading: false, + }); + + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const toastId = useRef(null); + const dropdownRef = useRef(null); + + const formattedLabel = (fullscreen) => (fullscreen + ? exitFullscreenLabel + : fullscreenLabel + ); + + const formattedVisibilityLabel = (visible) => (visible + ? hideToolsDesc + : showToolsDesc + ); + + function renderToastContent() { + const { loading, hasError } = state; + + let icon = loading ? 'blank' : 'check'; + if (hasError) icon = 'circle_close'; + + return ( + + + + {loading && !hasError && downloading} + {!loading && !hasError && downloaded} + {!loading && hasError && downloadFailed} + + + + + + + ); + } + + function getAvailableOptions() { + const menuItems = []; + + if (!isIphone) { + menuItems.push( + { + key: 'list-item-fullscreen', + dataTest: 'presentationFullscreen', + label: formattedLabel(isFullscreen), + icon: isFullscreen ? 'exit_fullscreen' : 'fullscreen', + onClick: () => { + handleToggleFullscreen(fullscreenRef); + const newElement = (elementId === currentElement) ? '' : elementId; + const newGroup = (elementGroup === currentGroup) ? '' : elementGroup; + + layoutContextDispatch({ + type: ACTIONS.SET_FULLSCREEN_ELEMENT, + value: { + element: newElement, + group: newGroup, + }, + }); + }, + }, + ); + } + + const { isSafari } = browserInfo; + + if (!isSafari) { + menuItems.push( + { + key: 'list-item-screenshot', + label: snapshotLabel, + dataTest: 'presentationSnapshot', + icon: 'video', + onClick: async () => { + setState({ + loading: true, + hasError: false, + }); + + toastId.current = toast.info(renderToastContent(), { + hideProgressBar: true, + autoClose: false, + newestOnTop: true, + closeOnClick: true, + onClose: () => { + toastId.current = null; + }, + }); + + // This is a workaround to a conflict of the + // dark mode's styles and the html-to-image lib. + // Issue: + // https://github.com/bubkoo/html-to-image/issues/370 + const darkThemeState = AppService.isDarkThemeEnabled(); + AppService.setDarkTheme(false); + + try { + const { copySvg, getShapes, currentPageId } = tldrawAPI; + const svgString = await copySvg(getShapes(currentPageId).map((shape) => shape.id)); + const container = document.createElement('div'); + container.innerHTML = svgString; + const svgElem = container.firstChild; + const width = svgElem?.width?.baseVal?.value ?? window.screen.width; + const height = svgElem?.height?.baseVal?.value ?? window.screen.height; + + const data = await toPng(svgElem, { width, height, backgroundColor: '#FFF' }); + + const anchor = document.createElement('a'); + anchor.href = data; + anchor.setAttribute( + 'download', + `${elementName}_${meetingName}_${new Date().toISOString()}.png`, + ); + anchor.click(); + + setState({ + loading: false, + hasError: false, + }); + } catch (e) { + setState({ + loading: false, + hasError: true, + }); + + logger.warn({ + logCode: 'presentation_snapshot_error', + extraInfo: e, + }); + } finally { + // Workaround + AppService.setDarkTheme(darkThemeState); + } + }, + }, + ); + } + + const tools = document.querySelector('#TD-Tools'); + if (tools && (hasWBAccess || amIPresenter)){ + menuItems.push( + { + key: 'list-item-toolvisibility', + dataTest: 'toolVisibility', + label: formattedVisibilityLabel(isToolbarVisible), + icon: isToolbarVisible ? 'close' : 'pen_tool', + onClick: () => { + setIsToolbarVisible(!isToolbarVisible); + }, + }, + ); + } + + return menuItems; + } + + useEffect(() => { + if (toastId.current) { + toast.update(toastId.current, { + render: renderToastContent(), + hideProgressBar: state.loading, + autoClose: state.loading ? false : 3000, + newestOnTop: true, + closeOnClick: true, + onClose: () => { + toastId.current = null; + }, + }); + } + + if (dropdownRef.current) { + document.activeElement.blur(); + dropdownRef.current.focus(); + } + }); + + const options = getAvailableOptions(); + + if (options.length === 0) { + const undoCtrls = document.getElementById('TD-Styles')?.nextSibling; + if (undoCtrls?.style) { + undoCtrls.style = 'padding:0px'; + } + const styleTool = document.getElementById('TD-Styles')?.parentNode; + if (styleTool?.style) { + styleTool.style = 'right:0px'; + } + return null; + } + + return ( + + + { + setIsDropdownOpen((isOpen) => !isOpen); + }} + > + + + + )} + opts={{ + id: 'presentation-dropdown-menu', + keepMounted: true, + transitionDuration: 0, + elevation: 3, + getContentAnchorEl: null, + fullwidth: 'true', + anchorOrigin: { vertical: 'bottom', horizontal: isRTL ? 'right' : 'left' }, + transformOrigin: { vertical: 'top', horizontal: isRTL ? 'right' : 'left' }, + container: fullscreenRef, + }} + actions={options} + /> + + ); +}; + +PresentationOps.propTypes = propTypes; +PresentationOps.defaultProps = defaultProps; + +export default PresentationOps; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/container.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/container.jsx new file mode 100644 index 0000000000..a54adfe070 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/container.jsx @@ -0,0 +1,53 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { withTracker } from 'meteor/react-meteor-data'; +import PresentationOps from './component'; +import FullscreenService from '/imports/ui/components/common/fullscreen-button/service'; +import Auth from '/imports/ui/services/auth'; +import Meetings from '/imports/api/meetings'; +import WhiteboardService from '/imports/ui/components/whiteboard/service'; +import UserService from '/imports/ui/components/user-list/service'; + +const PresentationOpsContainer = (props) => { + const { elementId, isRTL, layoutContextDispatch, fullscreen } = props; + const { element: currentElement, group: currentGroup } = fullscreen; + const isFullscreen = currentElement === elementId; + + return ( + + ); +}; + +export default withTracker((props) => { + const handleToggleFullscreen = (ref) => FullscreenService.toggleFullScreen(ref); + const isIphone = !!(navigator.userAgent.match(/iPhone/i)); + const meetingId = Auth.meetingID; + const meetingObject = Meetings.findOne({ meetingId }, { fields: { 'meetingProp.name': 1 } }); + const hasWBAccess = WhiteboardService.hasMultiUserAccess( + WhiteboardService.getCurrentWhiteboardId(), + Auth.userID + ); + const amIPresenter = UserService.isUserPresenter(Auth.userID); + return { + ...props, + handleToggleFullscreen, + isIphone, + isDropdownOpen: Session.get('dropdownOpen'), + meetingName: meetingObject.meetingProp.name, + hasWBAccess, + amIPresenter, + }; +})(PresentationOpsContainer); + +PresentationOpsContainer.propTypes = { + elementId: PropTypes.string.isRequired, +}; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/menu/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/menu/component.jsx new file mode 100644 index 0000000000..5ec052c612 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/menu/component.jsx @@ -0,0 +1,253 @@ +import React from "react"; +import PropTypes from "prop-types"; + +import Menu from "@material-ui/core/Menu"; +import { Divider } from "@material-ui/core"; +import Icon from "/imports/ui/components/common/icon/component"; +import { SMALL_VIEWPORT_BREAKPOINT } from '/imports/ui/components/layout/enums'; +import KEY_CODES from '/imports/utils/keyCodes'; + +import { ENTER } from "/imports/utils/keyCodes"; + +import Styled from '/imports/ui/components/common/menu/styles'; + +class BBBMenu extends React.Component { + constructor(props) { + super(props); + this.state = { + anchorEl: null, + }; + + this.optsToMerge = {}; + this.autoFocus = false; + + this.handleKeyDown = this.handleKeyDown.bind(this); + this.handleClick = this.handleClick.bind(this); + this.handleClose = this.handleClose.bind(this); + } + + componentDidUpdate() { + const { anchorEl } = this.state; + const { open } = this.props; + if (open === false && anchorEl) { + this.setState({ anchorEl: null }); + } else if (open === true && !anchorEl) { + this.setState({ anchorEl: this.anchorElRef }); + } + } + + handleKeyDown(event) { + const { anchorEl } = this.state; + const isMenuOpen = Boolean(anchorEl); + + + if ([KEY_CODES.ESCAPE, KEY_CODES.TAB].includes(event.which)) { + this.handleClose(); + return; + } + + if (isMenuOpen && [KEY_CODES.ARROW_UP, KEY_CODES.ARROW_DOWN].includes(event.which)) { + event.preventDefault(); + event.stopPropagation(); + const menuItems = Array.from(document.querySelectorAll('[data-key^="menuItem-"]')); + if (menuItems.length === 0) return; + + const focusedIndex = menuItems.findIndex(item => item === document.activeElement); + const nextIndex = event.which === KEY_CODES.ARROW_UP ? focusedIndex - 1 : focusedIndex + 1; + let indexToFocus = 0; + if (nextIndex < 0) { + indexToFocus = menuItems.length - 1; + } else if (nextIndex >= menuItems.length) { + indexToFocus = 0; + } else { + indexToFocus = nextIndex; + } + + menuItems[indexToFocus].focus(); + } + }; + + handleClick(event) { + this.setState({ anchorEl: event.currentTarget }); + }; + + handleClose(event) { + const { onCloseCallback } = this.props; + this.setState({ anchorEl: null }, onCloseCallback()); + + if (event) { + event.persist(); + + if (event.type === 'click') { + setTimeout(() => { + document.activeElement.blur(); + }, 0); + } + } + }; + + makeMenuItems() { + const { actions, selectedEmoji, activeLabel } = this.props; + + return actions?.map(a => { + const { dataTest, label, onClick, key, disabled, description, selected } = a; + const emojiSelected = key?.toLowerCase()?.includes(selectedEmoji?.toLowerCase()); + + let customStyles = { + paddingLeft: '16px', + paddingRight: '16px', + paddingTop: '12px', + paddingBottom: '12px', + marginLeft: '0px', + marginRight: '0px', + }; + + if (a.customStyles) { + customStyles = { ...customStyles, ...a.customStyles }; + } + + return [ + a.dividerTop && , + { + onClick(); + const close = !key?.includes('setstatus') && !key?.includes('back'); + // prevent menu close for sub menu actions + if (close) this.handleClose(event); + event.stopPropagation(); + }}> + + {a.icon ? : null} + {label} + {description &&
{`${description}${selected ? ` - ${activeLabel}` : ''}`}
} + {a.iconRight ? : null} +
+
, + a.divider && + ]; + }); + } + + render() { + const { anchorEl } = this.state; + const { trigger, customStyles, dataTest, opts, accessKey, closeLabel } = this.props; + const actionsItems = this.makeMenuItems(); + + let menuStyles = { zIndex: 9999 }; + + if (customStyles) { + menuStyles = { ...menuStyles, ...customStyles }; + } + + return ( + <> +
{ + e.persist(); + // 1 = mouse, 5 = touch (firefox only) + const firefoxInputSource = !([1, 5].includes(e.nativeEvent.mozInputSource)); + const chromeInputSource = !(['mouse', 'touch'].includes(e.nativeEvent.pointerType)); + this.optsToMerge.autoFocus = firefoxInputSource && chromeInputSource; + this.handleClick(e); + }} + onKeyPress={(e) => { + e.persist(); + if (e.which !== ENTER) return null; + return this.handleClick(e); + }} + accessKey={accessKey} + ref={(ref) => { this.anchorElRef = ref; return null; }} + role="button" + tabIndex={-1} + > + {trigger} +
+ + + {actionsItems} + {anchorEl && window.innerWidth < SMALL_VIEWPORT_BREAKPOINT && + + } + + + ); + } +} + +export default BBBMenu; + +BBBMenu.defaultProps = { + opts: { + id: "default-dropdown-menu", + autoFocus: false, + keepMounted: true, + transitionDuration: 0, + elevation: 3, + getContentAnchorEl: null, + fullwidth: "true", + anchorOrigin: { vertical: 'top', horizontal: 'right' }, + transformorigin: { vertical: 'top', horizontal: 'right' }, + }, + dataTest: "", + onCloseCallback: () => { }, +}; + +BBBMenu.propTypes = { + trigger: PropTypes.element.isRequired, + + actions: PropTypes.arrayOf(PropTypes.shape({ + key: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + onClick: PropTypes.func, + icon: PropTypes.string, + iconRight: PropTypes.string, + disabled: PropTypes.bool, + divider: PropTypes.bool, + dividerTop: PropTypes.bool, + accessKey: PropTypes.string, + dataTest: PropTypes.string, + })).isRequired, + + opts: PropTypes.shape({ + id: PropTypes.string, + autoFocus: PropTypes.bool, + keepMounted: PropTypes.bool, + transitionDuration: PropTypes.number, + elevation: PropTypes.number, + getContentAnchorEl: PropTypes.element, + fullwidth: PropTypes.string, + anchorOrigin: PropTypes.shape({ + vertical: PropTypes.string, + horizontal: PropTypes.string + }), + transformorigin: PropTypes.shape({ + vertical: PropTypes.string, + horizontal: PropTypes.string + }), + }), + + onCloseCallback: PropTypes.func, + dataTest: PropTypes.string, +}; \ No newline at end of file From 6b34cd38c1f8add2d3469698eb61b92f02784a54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Tue, 1 Aug 2023 11:01:27 -0300 Subject: [PATCH 082/252] replace double quotes --- .../ui/components/presentation/component.jsx | 255 +++++++------ .../ui/components/whiteboard/container.jsx | 342 +++++++++--------- 2 files changed, 293 insertions(+), 304 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx index e5b724cb24..c710e55a9d 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx @@ -1,64 +1,64 @@ -import React, { PureComponent } from "react"; -import PropTypes from "prop-types"; -import WhiteboardContainer from "/imports/ui/components/whiteboard/container"; -import { HUNDRED_PERCENT, MAX_PERCENT } from "/imports/utils/slideCalcUtils"; -import { SPACE } from "/imports/utils/keyCodes"; -import { defineMessages, injectIntl } from "react-intl"; -import { toast } from "react-toastify"; -import { Session } from "meteor/session"; -import PresentationToolbarContainer from "./presentation-toolbar/container"; -import PresentationMenu from "./presentation-menu/container"; -import Styled from "./styles"; -import FullscreenService from "/imports/ui/components/common/fullscreen-button/service"; -import Icon from "/imports/ui/components/common/icon/component"; -import PollingContainer from "/imports/ui/components/polling/container"; -import { ACTIONS, LAYOUT_TYPE } from "../layout/enums"; -import DEFAULT_VALUES from "../layout/defaultValues"; -import { colorContentBackground } from "/imports/ui/stylesheets/styled-components/palette"; -import browserInfo from "/imports/utils/browserInfo"; -import { addNewAlert } from "../screenreader-alert/service"; -import { clearCursors } from "/imports/ui/components/whiteboard/cursors/service"; -import { debounce } from "radash"; +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import WhiteboardContainer from '/imports/ui/components/whiteboard/container'; +import { HUNDRED_PERCENT, MAX_PERCENT } from '/imports/utils/slideCalcUtils'; +import { SPACE } from '/imports/utils/keyCodes'; +import { defineMessages, injectIntl } from 'react-intl'; +import { toast } from 'react-toastify'; +import { Session } from 'meteor/session'; +import { debounce } from 'radash'; +import PresentationToolbarContainer from './presentation-toolbar/container'; +import PresentationMenu from './presentation-menu/container'; +import Styled from './styles'; +import FullscreenService from '/imports/ui/components/common/fullscreen-button/service'; +import Icon from '/imports/ui/components/common/icon/component'; +import PollingContainer from '/imports/ui/components/polling/container'; +import { ACTIONS, LAYOUT_TYPE } from '../layout/enums'; +import DEFAULT_VALUES from '../layout/defaultValues'; +import { colorContentBackground } from '/imports/ui/stylesheets/styled-components/palette'; +import browserInfo from '/imports/utils/browserInfo'; +import { addNewAlert } from '../screenreader-alert/service'; +import { clearCursors } from '/imports/ui/components/whiteboard/cursors/service'; const intlMessages = defineMessages({ presentationLabel: { - id: "app.presentationUploder.title", - description: "presentation area element label", + id: 'app.presentationUploder.title', + description: 'presentation area element label', }, changeNotification: { - id: "app.presentation.notificationLabel", - description: "label displayed in toast when presentation switches", + id: 'app.presentation.notificationLabel', + description: 'label displayed in toast when presentation switches', }, downloadLabel: { - id: "app.presentation.downloadLabel", - description: "label for downloadable presentations", + id: 'app.presentation.downloadLabel', + description: 'label for downloadable presentations', }, slideContentStart: { - id: "app.presentation.startSlideContent", - description: "Indicate the slide content start", + id: 'app.presentation.startSlideContent', + description: 'Indicate the slide content start', }, slideContentEnd: { - id: "app.presentation.endSlideContent", - description: "Indicate the slide content end", + id: 'app.presentation.endSlideContent', + description: 'Indicate the slide content end', }, slideContentChanged: { - id: "app.presentation.changedSlideContent", - description: "Indicate the slide content has changed", + id: 'app.presentation.changedSlideContent', + description: 'Indicate the slide content has changed', }, noSlideContent: { - id: "app.presentation.emptySlideContent", - description: "No content available for slide", + id: 'app.presentation.emptySlideContent', + description: 'No content available for slide', }, }); const { isSafari } = browserInfo; const FULLSCREEN_CHANGE_EVENT = isSafari - ? "webkitfullscreenchange" - : "fullscreenchange"; + ? 'webkitfullscreenchange' + : 'fullscreenchange'; const getToolbarHeight = () => { let height = 0; - const toolbarEl = document.getElementById("presentationToolbarWrapper"); + const toolbarEl = document.getElementById('presentationToolbarWrapper'); if (toolbarEl) { const { clientHeight } = toolbarEl; height = clientHeight; @@ -92,8 +92,7 @@ class Presentation extends PureComponent { this.panAndZoomChanger = this.panAndZoomChanger.bind(this); this.fitToWidthHandler = this.fitToWidthHandler.bind(this); this.onFullscreenChange = this.onFullscreenChange.bind(this); - this.getPresentationSizesAvailable = - this.getPresentationSizesAvailable.bind(this); + this.getPresentationSizesAvailable = this.getPresentationSizesAvailable.bind(this); this.handleResize = debounce({ delay: 200 }, this.handleResize.bind(this)); this.setTldrawAPI = this.setTldrawAPI.bind(this); this.setIsPanning = this.setIsPanning.bind(this); @@ -102,11 +101,10 @@ class Presentation extends PureComponent { this.renderPresentationMenu = this.renderPresentationMenu.bind(this); this.onResize = () => setTimeout(this.handleResize.bind(this), 0); - this.renderCurrentPresentationToast = - this.renderCurrentPresentationToast.bind(this); + this.renderCurrentPresentationToast = this.renderCurrentPresentationToast.bind(this); this.setPresentationRef = this.setPresentationRef.bind(this); this.setTldrawIsMounting = this.setTldrawIsMounting.bind(this); - Session.set("componentPresentationWillUnmount", false); + Session.set('componentPresentationWillUnmount', false); } static getDerivedStateFromProps(props, state) { @@ -114,16 +112,15 @@ class Presentation extends PureComponent { const stateChange = { prevProps: props }; if ( - props.userIsPresenter && - (!prevProps || !prevProps.userIsPresenter) && - props.currentSlide && - props.slidePosition + props.userIsPresenter + && (!prevProps || !prevProps.userIsPresenter) + && props.currentSlide + && props.slidePosition ) { - let potentialZoom = - 100 / (props.slidePosition.viewBoxWidth / props.slidePosition.width); + let potentialZoom = 100 / (props.slidePosition.viewBoxWidth / props.slidePosition.width); potentialZoom = Math.max( HUNDRED_PERCENT, - Math.min(MAX_PERCENT, potentialZoom) + Math.min(MAX_PERCENT, potentialZoom), ); stateChange.zoom = potentialZoom; } @@ -132,8 +129,8 @@ class Presentation extends PureComponent { // When presenter is changed or slide changed we reset localPosition if ( - prevProps.currentSlide?.id !== props.currentSlide?.id || - prevProps.userIsPresenter !== props.userIsPresenter + prevProps.currentSlide?.id !== props.currentSlide?.id + || prevProps.userIsPresenter !== props.userIsPresenter ) { stateChange.localPosition = undefined; } @@ -144,18 +141,18 @@ class Presentation extends PureComponent { componentDidMount() { this.getInitialPresentationSizes(); this.refPresentationContainer.addEventListener( - "keydown", - this.handlePanShortcut + 'keydown', + this.handlePanShortcut, ); this.refPresentationContainer.addEventListener( - "keyup", - this.handlePanShortcut + 'keyup', + this.handlePanShortcut, ); this.refPresentationContainer.addEventListener( FULLSCREEN_CHANGE_EVENT, - this.onFullscreenChange + this.onFullscreenChange, ); - window.addEventListener("resize", this.onResize, false); + window.addEventListener('resize', this.onResize, false); const { currentSlide, @@ -239,29 +236,28 @@ class Presentation extends PureComponent { } if ( - currentSlide?.num != null && - prevProps?.currentSlide?.num != null && - currentSlide?.num !== prevProps.currentSlide?.num + currentSlide?.num != null + && prevProps?.currentSlide?.num != null + && currentSlide?.num !== prevProps.currentSlide?.num ) { addNewAlert( intl.formatMessage(intlMessages.slideContentChanged, { 0: currentSlide.num, - }) + }), ); } if (currentPresentation) { - const downloadableOn = - !prevProps?.currentPresentation?.downloadable && - currentPresentation.downloadable; + const downloadableOn = !prevProps?.currentPresentation?.downloadable + && currentPresentation.downloadable; const shouldCloseToast = !( currentPresentation.downloadable && !userIsPresenter ); if ( - prevProps?.currentPresentation?.id !== currentPresentation.id || - (downloadableOn && !userIsPresenter) + prevProps?.currentPresentation?.id !== currentPresentation.id + || (downloadableOn && !userIsPresenter) ) { if (this.currentPresentationToastId) { toast.update(this.currentPresentationToastId, { @@ -276,15 +272,14 @@ class Presentation extends PureComponent { this.currentPresentationToastId = null; }, autoClose: shouldCloseToast, - className: "actionToast currentPresentationToast", - } + className: 'actionToast currentPresentationToast', + }, ); } } - const downloadableOff = - prevProps?.currentPresentation?.downloadable && - !currentPresentation.downloadable; + const downloadableOff = prevProps?.currentPresentation?.downloadable + && !currentPresentation.downloadable; if (this.currentPresentationToastId && downloadableOff) { toast.update(this.currentPresentationToastId, { @@ -309,24 +304,23 @@ class Presentation extends PureComponent { } const presentationChanged = presentationId !== currentPresentationId; - const isInitialPresentation = currentPresentation.isInitialPresentation; + const { isInitialPresentation } = currentPresentation; if ( - !presentationIsOpen && - restoreOnUpdate && - (currentSlide || presentationChanged) + !presentationIsOpen + && restoreOnUpdate + && (currentSlide || presentationChanged) ) { const slideChanged = currentSlide.id !== prevProps.currentSlide.id; - const positionChanged = - slidePosition.viewBoxHeight !== - prevProps.slidePosition.viewBoxHeight || - slidePosition.viewBoxWidth !== prevProps.slidePosition.viewBoxWidth; + const positionChanged = slidePosition.viewBoxHeight + !== prevProps.slidePosition.viewBoxHeight + || slidePosition.viewBoxWidth !== prevProps.slidePosition.viewBoxWidth; const pollPublished = publishedPoll && !prevProps.publishedPoll; if ( - slideChanged || - positionChanged || - pollPublished || - (presentationChanged && (hadPresentation || !isInitialPresentation)) + slideChanged + || positionChanged + || pollPublished + || (presentationChanged && (hadPresentation || !isInitialPresentation)) ) { setPresentationIsOpen(layoutContextDispatch, !presentationIsOpen); } @@ -340,10 +334,9 @@ class Presentation extends PureComponent { } if ( - presentationBounds !== prevPresentationBounds || - (!presentationWidth && !presentationHeight) - ) - this.onResize(); + presentationBounds !== prevPresentationBounds + || (!presentationWidth && !presentationHeight) + ) this.onResize(); } else if (slidePosition) { const { width: currWidth, height: currHeight } = slidePosition; @@ -361,37 +354,37 @@ class Presentation extends PureComponent { } if ( - (zoom <= HUNDRED_PERCENT && isPanning && !fitToWidth) || - (!userIsPresenter && prevProps.userIsPresenter) + (zoom <= HUNDRED_PERCENT && isPanning && !fitToWidth) + || (!userIsPresenter && prevProps.userIsPresenter) ) { this.setIsPanning(); } } componentWillUnmount() { - Session.set("componentPresentationWillUnmount", true); + Session.set('componentPresentationWillUnmount', true); const { fullscreenContext, layoutContextDispatch } = this.props; - window.removeEventListener("resize", this.onResize, false); + window.removeEventListener('resize', this.onResize, false); this.refPresentationContainer.removeEventListener( FULLSCREEN_CHANGE_EVENT, - this.onFullscreenChange + this.onFullscreenChange, ); this.refPresentationContainer.removeEventListener( - "keydown", - this.handlePanShortcut + 'keydown', + this.handlePanShortcut, ); this.refPresentationContainer.removeEventListener( - "keyup", - this.handlePanShortcut + 'keyup', + this.handlePanShortcut, ); if (fullscreenContext) { layoutContextDispatch({ type: ACTIONS.SET_FULLSCREEN_ELEMENT, value: { - element: "", - group: "", + element: '', + group: '', }, }); } @@ -402,9 +395,9 @@ class Presentation extends PureComponent { const { isPanning } = this.state; if (e.keyCode === SPACE && userIsPresenter) { switch (e.type) { - case "keyup": + case 'keyup': return isPanning && this.setIsPanning(); - case "keydown": + case 'keydown': return !isPanning && this.setIsPanning(); default: } @@ -416,7 +409,7 @@ class Presentation extends PureComponent { const presentationSizes = this.getPresentationSizesAvailable(); if (Object.keys(presentationSizes).length > 0) { // updating the size of the space available for the slide - if (!Session.get("componentPresentationWillUnmount")) { + if (!Session.get('componentPresentationWillUnmount')) { this.setState({ presentationHeight: presentationSizes.presentationHeight, presentationWidth: presentationSizes.presentationWidth, @@ -428,7 +421,7 @@ class Presentation extends PureComponent { onFullscreenChange() { const { isFullscreen } = this.state; const newIsFullscreen = FullscreenService.isFullScreen( - this.refPresentationContainer + this.refPresentationContainer, ); if (isFullscreen !== newIsFullscreen) { this.setState({ isFullscreen: newIsFullscreen }); @@ -478,11 +471,9 @@ class Presentation extends PureComponent { }; if (newPresentationAreaSize) { - presentationSizes.presentationWidth = - newPresentationAreaSize.presentationAreaWidth; - presentationSizes.presentationHeight = - newPresentationAreaSize.presentationAreaHeight - - (getToolbarHeight() || 0); + presentationSizes.presentationWidth = newPresentationAreaSize.presentationAreaWidth; + presentationSizes.presentationHeight = newPresentationAreaSize.presentationAreaHeight + - (getToolbarHeight() || 0); return presentationSizes; } @@ -573,7 +564,7 @@ class Presentation extends PureComponent { if (svgHeight > presentationHeight) svgHeight = presentationHeight; } - if (typeof svgHeight !== "number" || typeof svgWidth !== "number") { + if (typeof svgHeight !== 'number' || typeof svgWidth !== 'number') { return { width: 0, height: 0 }; } @@ -612,12 +603,11 @@ class Presentation extends PureComponent { const { presentationToolbarMinWidth } = DEFAULT_VALUES; - const toolbarWidth = - (this.refWhiteboardArea && svgWidth > presentationToolbarMinWidth) || - isMobile || - (layoutType === LAYOUT_TYPE.VIDEO_FOCUS && numCameras > 0) - ? svgWidth - : presentationToolbarMinWidth; + const toolbarWidth = (this.refWhiteboardArea && svgWidth > presentationToolbarMinWidth) + || isMobile + || (layoutType === LAYOUT_TYPE.VIDEO_FOCUS && numCameras > 0) + ? svgWidth + : presentationToolbarMinWidth; return ( presentationToolbarMinWidth || isMobile) && - !( - layoutType === LAYOUT_TYPE.VIDEO_FOCUS && - numCameras > 0 && - !fullscreenContext + const isLargePresentation = (svgWidth > presentationToolbarMinWidth || isMobile) + && !( + layoutType === LAYOUT_TYPE.VIDEO_FOCUS + && numCameras > 0 + && !fullscreenContext ); const containerWidth = isLargePresentation @@ -805,13 +794,13 @@ class Presentation extends PureComponent { right: presentationBounds.right, width: presentationBounds.width, height: presentationBounds.height, - display: !presentationIsOpen ? "none" : "flex", - overflow: "hidden", + display: !presentationIsOpen ? 'none' : 'flex', + overflow: 'hidden', zIndex: fullscreenContext ? presentationBounds.zIndex : undefined, background: - layoutType === LAYOUT_TYPE.VIDEO_FOCUS && - numCameras > 0 && - !fullscreenContext + layoutType === LAYOUT_TYPE.VIDEO_FOCUS + && numCameras > 0 + && !fullscreenContext ? colorContentBackground : null, }} @@ -828,20 +817,20 @@ class Presentation extends PureComponent { >
{slideContent} - {!tldrawIsMounting && - currentSlide && - this.renderPresentationMenu()} + {!tldrawIsMounting + && currentSlide + && this.renderPresentationMenu()} { - const usingUsersContext = useContext(UsersContext); - const isRTL = layoutSelect((i) => i.isRTL); - const width = layoutSelect((i) => i?.output?.presentation?.width); - const height = layoutSelect((i) => i?.output?.presentation?.height); - const sidebarNavigationWidth = layoutSelect( - (i) => i?.output?.sidebarNavigation?.width - ); - const { users } = usingUsersContext; - const currentUser = users[Auth.meetingID][Auth.userID]; - const isPresenter = currentUser.presenter; - const isModerator = currentUser.role === ROLE_MODERATOR; - const { maxStickyNoteLength, maxNumberOfAnnotations } = WHITEBOARD_CONFIG; - const fontFamily = WHITEBOARD_CONFIG.styles.text.family; - const fullscreen = layoutSelect((i) => i.fullscreen); - const handleToggleFullScreen = (ref) => - FullscreenService.toggleFullScreen(ref); - const layoutContextDispatch = layoutDispatch(); - - const { shapes } = props; - const hasShapeAccess = (id) => { - const owner = shapes[id]?.userId; - const isBackgroundShape = id?.includes("slide-background"); - const isPollsResult = shapes[id]?.name?.includes("poll-result"); - const hasAccess = - (!isBackgroundShape && !isPollsResult) || - (isPresenter && - ((owner && owner === currentUser?.userId) || - !owner || - isPresenter || - isModerator)); - - return hasAccess; - }; - // set shapes as locked for those who aren't allowed to edit it - Object.entries(shapes).forEach(([shapeId, shape]) => { - if (!shape.isLocked && !hasShapeAccess(shapeId)) { - const modShape = shape; - modShape.isLocked = true; - } - }); - - return ( - - ); -}; - -export default withTracker( - ({ - whiteboardId, - curPageId, - intl, - slidePosition, - svgUri, - podId, - presentationId, - darkTheme, - }) => { - const shapes = getShapes(whiteboardId, curPageId, intl); - const curPres = getCurrentPres(); - const { isIphone } = deviceInfo; - - shapes["slide-background-shape"] = { - assetId: `slide-background-asset-${curPageId}`, - childIndex: -1, - id: "slide-background-shape", - name: "Image", - type: TDShapeType.Image, - parentId: `${curPageId}`, - point: [0, 0], - isLocked: true, - size: [slidePosition?.width || 0, slidePosition?.height || 0], - style: { - dash: DashStyle.Draw, - size: SizeStyle.Medium, - color: ColorStyle.Blue, - }, - }; - - const assets = {}; - assets[`slide-background-asset-${curPageId}`] = { - id: `slide-background-asset-${curPageId}`, - size: [slidePosition?.width || 0, slidePosition?.height || 0], - src: svgUri, - type: "image", - }; - - return { - initDefaultPages, - persistShape, - isMultiUserActive, - hasMultiUserAccess, - changeCurrentSlide, - shapes, - assets, - curPres, - removeShapes, - zoomSlide: PresentationToolbarService.zoomSlide, - skipToSlide: PresentationToolbarService.skipToSlide, - nextSlide: PresentationToolbarService.nextSlide, - previousSlide: PresentationToolbarService.previousSlide, - numberOfSlides: PresentationToolbarService.getNumberOfSlides( - podId, - presentationId - ), - notifyNotAllowedChange, - notifyShapeNumberExceeded, - darkTheme, - whiteboardToolbarAutoHide: - SettingsService?.application?.whiteboardToolbarAutoHide, - animations: SettingsService?.application?.animations, - toggleToolsAnimations, - isIphone, - }; - } -)(WhiteboardContainer); - -WhiteboardContainer.propTypes = { - shapes: PropTypes.objectOf(PropTypes.shape).isRequired, -}; +import { withTracker } from 'meteor/react-meteor-data'; +import PropTypes from 'prop-types'; +import React, { useContext } from 'react'; +import { + ColorStyle, DashStyle, SizeStyle, TDShapeType, +} from '@tldraw/tldraw'; +import SettingsService from '/imports/ui/services/settings'; +import { + getShapes, + getCurrentPres, + initDefaultPages, + persistShape, + removeShapes, + isMultiUserActive, + hasMultiUserAccess, + changeCurrentSlide, + notifyNotAllowedChange, + notifyShapeNumberExceeded, + toggleToolsAnimations, +} from './service'; +import Whiteboard from './component'; +import { UsersContext } from '../components-data/users-context/context'; +import Auth from '/imports/ui/services/auth'; +import PresentationToolbarService from '../presentation/presentation-toolbar/service'; +import { + layoutSelect, + layoutDispatch, +} from '/imports/ui/components/layout/context'; +import FullscreenService from '/imports/ui/components/common/fullscreen-button/service'; +import deviceInfo from '/imports/utils/deviceInfo'; + +const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator; +const WHITEBOARD_CONFIG = Meteor.settings.public.whiteboard; + +const WhiteboardContainer = (props) => { + const usingUsersContext = useContext(UsersContext); + const isRTL = layoutSelect((i) => i.isRTL); + const width = layoutSelect((i) => i?.output?.presentation?.width); + const height = layoutSelect((i) => i?.output?.presentation?.height); + const sidebarNavigationWidth = layoutSelect( + (i) => i?.output?.sidebarNavigation?.width, + ); + const { users } = usingUsersContext; + const currentUser = users[Auth.meetingID][Auth.userID]; + const isPresenter = currentUser.presenter; + const isModerator = currentUser.role === ROLE_MODERATOR; + const { maxStickyNoteLength, maxNumberOfAnnotations } = WHITEBOARD_CONFIG; + const fontFamily = WHITEBOARD_CONFIG.styles.text.family; + const fullscreen = layoutSelect((i) => i.fullscreen); + const handleToggleFullScreen = (ref) => FullscreenService.toggleFullScreen(ref); + const layoutContextDispatch = layoutDispatch(); + + const { shapes } = props; + const hasShapeAccess = (id) => { + const owner = shapes[id]?.userId; + const isBackgroundShape = id?.includes('slide-background'); + const isPollsResult = shapes[id]?.name?.includes('poll-result'); + const hasAccess = (!isBackgroundShape && !isPollsResult) + || (isPresenter + && ((owner && owner === currentUser?.userId) + || !owner + || isPresenter + || isModerator)); + + return hasAccess; + }; + // set shapes as locked for those who aren't allowed to edit it + Object.entries(shapes).forEach(([shapeId, shape]) => { + if (!shape.isLocked && !hasShapeAccess(shapeId)) { + const modShape = shape; + modShape.isLocked = true; + } + }); + + return ( + + ); +}; + +export default withTracker( + ({ + whiteboardId, + curPageId, + intl, + slidePosition, + svgUri, + podId, + presentationId, + darkTheme, + }) => { + const shapes = getShapes(whiteboardId, curPageId, intl); + const curPres = getCurrentPres(); + const { isIphone } = deviceInfo; + + shapes['slide-background-shape'] = { + assetId: `slide-background-asset-${curPageId}`, + childIndex: -1, + id: 'slide-background-shape', + name: 'Image', + type: TDShapeType.Image, + parentId: `${curPageId}`, + point: [0, 0], + isLocked: true, + size: [slidePosition?.width || 0, slidePosition?.height || 0], + style: { + dash: DashStyle.Draw, + size: SizeStyle.Medium, + color: ColorStyle.Blue, + }, + }; + + const assets = {}; + assets[`slide-background-asset-${curPageId}`] = { + id: `slide-background-asset-${curPageId}`, + size: [slidePosition?.width || 0, slidePosition?.height || 0], + src: svgUri, + type: 'image', + }; + + return { + initDefaultPages, + persistShape, + isMultiUserActive, + hasMultiUserAccess, + changeCurrentSlide, + shapes, + assets, + curPres, + removeShapes, + zoomSlide: PresentationToolbarService.zoomSlide, + skipToSlide: PresentationToolbarService.skipToSlide, + nextSlide: PresentationToolbarService.nextSlide, + previousSlide: PresentationToolbarService.previousSlide, + numberOfSlides: PresentationToolbarService.getNumberOfSlides( + podId, + presentationId, + ), + notifyNotAllowedChange, + notifyShapeNumberExceeded, + darkTheme, + whiteboardToolbarAutoHide: + SettingsService?.application?.whiteboardToolbarAutoHide, + animations: SettingsService?.application?.animations, + toggleToolsAnimations, + isIphone, + }; + }, +)(WhiteboardContainer); + +WhiteboardContainer.propTypes = { + shapes: PropTypes.objectOf(PropTypes.shape).isRequired, +}; From c15d1a352ac20a50ed5cb8b5754040c26b87b592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Tue, 1 Aug 2023 11:08:27 -0300 Subject: [PATCH 083/252] undo auto lint --- .../ui/components/presentation/component.jsx | 159 ++++---- .../ui/components/whiteboard/container.jsx | 342 +++++++++--------- 2 files changed, 256 insertions(+), 245 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx index c710e55a9d..bf46dca8d1 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx @@ -6,7 +6,6 @@ import { SPACE } from '/imports/utils/keyCodes'; import { defineMessages, injectIntl } from 'react-intl'; import { toast } from 'react-toastify'; import { Session } from 'meteor/session'; -import { debounce } from 'radash'; import PresentationToolbarContainer from './presentation-toolbar/container'; import PresentationMenu from './presentation-menu/container'; import Styled from './styles'; @@ -19,6 +18,7 @@ import { colorContentBackground } from '/imports/ui/stylesheets/styled-component import browserInfo from '/imports/utils/browserInfo'; import { addNewAlert } from '../screenreader-alert/service'; import { clearCursors } from '/imports/ui/components/whiteboard/cursors/service'; +import { debounce } from 'radash'; const intlMessages = defineMessages({ presentationLabel: { @@ -92,7 +92,8 @@ class Presentation extends PureComponent { this.panAndZoomChanger = this.panAndZoomChanger.bind(this); this.fitToWidthHandler = this.fitToWidthHandler.bind(this); this.onFullscreenChange = this.onFullscreenChange.bind(this); - this.getPresentationSizesAvailable = this.getPresentationSizesAvailable.bind(this); + this.getPresentationSizesAvailable = + this.getPresentationSizesAvailable.bind(this); this.handleResize = debounce({ delay: 200 }, this.handleResize.bind(this)); this.setTldrawAPI = this.setTldrawAPI.bind(this); this.setIsPanning = this.setIsPanning.bind(this); @@ -101,7 +102,8 @@ class Presentation extends PureComponent { this.renderPresentationMenu = this.renderPresentationMenu.bind(this); this.onResize = () => setTimeout(this.handleResize.bind(this), 0); - this.renderCurrentPresentationToast = this.renderCurrentPresentationToast.bind(this); + this.renderCurrentPresentationToast = + this.renderCurrentPresentationToast.bind(this); this.setPresentationRef = this.setPresentationRef.bind(this); this.setTldrawIsMounting = this.setTldrawIsMounting.bind(this); Session.set('componentPresentationWillUnmount', false); @@ -112,15 +114,16 @@ class Presentation extends PureComponent { const stateChange = { prevProps: props }; if ( - props.userIsPresenter - && (!prevProps || !prevProps.userIsPresenter) - && props.currentSlide - && props.slidePosition + props.userIsPresenter && + (!prevProps || !prevProps.userIsPresenter) && + props.currentSlide && + props.slidePosition ) { - let potentialZoom = 100 / (props.slidePosition.viewBoxWidth / props.slidePosition.width); + let potentialZoom = + 100 / (props.slidePosition.viewBoxWidth / props.slidePosition.width); potentialZoom = Math.max( HUNDRED_PERCENT, - Math.min(MAX_PERCENT, potentialZoom), + Math.min(MAX_PERCENT, potentialZoom) ); stateChange.zoom = potentialZoom; } @@ -129,8 +132,8 @@ class Presentation extends PureComponent { // When presenter is changed or slide changed we reset localPosition if ( - prevProps.currentSlide?.id !== props.currentSlide?.id - || prevProps.userIsPresenter !== props.userIsPresenter + prevProps.currentSlide?.id !== props.currentSlide?.id || + prevProps.userIsPresenter !== props.userIsPresenter ) { stateChange.localPosition = undefined; } @@ -142,15 +145,15 @@ class Presentation extends PureComponent { this.getInitialPresentationSizes(); this.refPresentationContainer.addEventListener( 'keydown', - this.handlePanShortcut, + this.handlePanShortcut ); this.refPresentationContainer.addEventListener( 'keyup', - this.handlePanShortcut, + this.handlePanShortcut ); this.refPresentationContainer.addEventListener( FULLSCREEN_CHANGE_EVENT, - this.onFullscreenChange, + this.onFullscreenChange ); window.addEventListener('resize', this.onResize, false); @@ -236,28 +239,29 @@ class Presentation extends PureComponent { } if ( - currentSlide?.num != null - && prevProps?.currentSlide?.num != null - && currentSlide?.num !== prevProps.currentSlide?.num + currentSlide?.num != null && + prevProps?.currentSlide?.num != null && + currentSlide?.num !== prevProps.currentSlide?.num ) { addNewAlert( intl.formatMessage(intlMessages.slideContentChanged, { 0: currentSlide.num, - }), + }) ); } if (currentPresentation) { - const downloadableOn = !prevProps?.currentPresentation?.downloadable - && currentPresentation.downloadable; + const downloadableOn = + !prevProps?.currentPresentation?.downloadable && + currentPresentation.downloadable; const shouldCloseToast = !( currentPresentation.downloadable && !userIsPresenter ); if ( - prevProps?.currentPresentation?.id !== currentPresentation.id - || (downloadableOn && !userIsPresenter) + prevProps?.currentPresentation?.id !== currentPresentation.id || + (downloadableOn && !userIsPresenter) ) { if (this.currentPresentationToastId) { toast.update(this.currentPresentationToastId, { @@ -273,13 +277,14 @@ class Presentation extends PureComponent { }, autoClose: shouldCloseToast, className: 'actionToast currentPresentationToast', - }, + } ); } } - const downloadableOff = prevProps?.currentPresentation?.downloadable - && !currentPresentation.downloadable; + const downloadableOff = + prevProps?.currentPresentation?.downloadable && + !currentPresentation.downloadable; if (this.currentPresentationToastId && downloadableOff) { toast.update(this.currentPresentationToastId, { @@ -304,23 +309,24 @@ class Presentation extends PureComponent { } const presentationChanged = presentationId !== currentPresentationId; - const { isInitialPresentation } = currentPresentation; + const isInitialPresentation = currentPresentation.isInitialPresentation; if ( - !presentationIsOpen - && restoreOnUpdate - && (currentSlide || presentationChanged) + !presentationIsOpen && + restoreOnUpdate && + (currentSlide || presentationChanged) ) { const slideChanged = currentSlide.id !== prevProps.currentSlide.id; - const positionChanged = slidePosition.viewBoxHeight - !== prevProps.slidePosition.viewBoxHeight - || slidePosition.viewBoxWidth !== prevProps.slidePosition.viewBoxWidth; + const positionChanged = + slidePosition.viewBoxHeight !== + prevProps.slidePosition.viewBoxHeight || + slidePosition.viewBoxWidth !== prevProps.slidePosition.viewBoxWidth; const pollPublished = publishedPoll && !prevProps.publishedPoll; if ( - slideChanged - || positionChanged - || pollPublished - || (presentationChanged && (hadPresentation || !isInitialPresentation)) + slideChanged || + positionChanged || + pollPublished || + (presentationChanged && (hadPresentation || !isInitialPresentation)) ) { setPresentationIsOpen(layoutContextDispatch, !presentationIsOpen); } @@ -334,9 +340,10 @@ class Presentation extends PureComponent { } if ( - presentationBounds !== prevPresentationBounds - || (!presentationWidth && !presentationHeight) - ) this.onResize(); + presentationBounds !== prevPresentationBounds || + (!presentationWidth && !presentationHeight) + ) + this.onResize(); } else if (slidePosition) { const { width: currWidth, height: currHeight } = slidePosition; @@ -354,8 +361,8 @@ class Presentation extends PureComponent { } if ( - (zoom <= HUNDRED_PERCENT && isPanning && !fitToWidth) - || (!userIsPresenter && prevProps.userIsPresenter) + (zoom <= HUNDRED_PERCENT && isPanning && !fitToWidth) || + (!userIsPresenter && prevProps.userIsPresenter) ) { this.setIsPanning(); } @@ -368,15 +375,15 @@ class Presentation extends PureComponent { window.removeEventListener('resize', this.onResize, false); this.refPresentationContainer.removeEventListener( FULLSCREEN_CHANGE_EVENT, - this.onFullscreenChange, + this.onFullscreenChange ); this.refPresentationContainer.removeEventListener( 'keydown', - this.handlePanShortcut, + this.handlePanShortcut ); this.refPresentationContainer.removeEventListener( 'keyup', - this.handlePanShortcut, + this.handlePanShortcut ); if (fullscreenContext) { @@ -421,7 +428,7 @@ class Presentation extends PureComponent { onFullscreenChange() { const { isFullscreen } = this.state; const newIsFullscreen = FullscreenService.isFullScreen( - this.refPresentationContainer, + this.refPresentationContainer ); if (isFullscreen !== newIsFullscreen) { this.setState({ isFullscreen: newIsFullscreen }); @@ -471,9 +478,11 @@ class Presentation extends PureComponent { }; if (newPresentationAreaSize) { - presentationSizes.presentationWidth = newPresentationAreaSize.presentationAreaWidth; - presentationSizes.presentationHeight = newPresentationAreaSize.presentationAreaHeight - - (getToolbarHeight() || 0); + presentationSizes.presentationWidth = + newPresentationAreaSize.presentationAreaWidth; + presentationSizes.presentationHeight = + newPresentationAreaSize.presentationAreaHeight - + (getToolbarHeight() || 0); return presentationSizes; } @@ -603,11 +612,12 @@ class Presentation extends PureComponent { const { presentationToolbarMinWidth } = DEFAULT_VALUES; - const toolbarWidth = (this.refWhiteboardArea && svgWidth > presentationToolbarMinWidth) - || isMobile - || (layoutType === LAYOUT_TYPE.VIDEO_FOCUS && numCameras > 0) - ? svgWidth - : presentationToolbarMinWidth; + const toolbarWidth = + (this.refWhiteboardArea && svgWidth > presentationToolbarMinWidth) || + isMobile || + (layoutType === LAYOUT_TYPE.VIDEO_FOCUS && numCameras > 0) + ? svgWidth + : presentationToolbarMinWidth; return ( + - + - +
{`${intl.formatMessage(intlMessages.changeNotification)}`}
{`${currentPresentation.name}`}
@@ -665,13 +675,13 @@ class Presentation extends PureComponent { {intl.formatMessage(intlMessages.downloadLabel)} @@ -763,11 +773,12 @@ class Presentation extends PureComponent { const { presentationToolbarMinWidth } = DEFAULT_VALUES; - const isLargePresentation = (svgWidth > presentationToolbarMinWidth || isMobile) - && !( - layoutType === LAYOUT_TYPE.VIDEO_FOCUS - && numCameras > 0 - && !fullscreenContext + const isLargePresentation = + (svgWidth > presentationToolbarMinWidth || isMobile) && + !( + layoutType === LAYOUT_TYPE.VIDEO_FOCUS && + numCameras > 0 && + !fullscreenContext ); const containerWidth = isLargePresentation @@ -783,8 +794,8 @@ class Presentation extends PureComponent { return ( <> { this.refPresentationContainer = ref; }} @@ -798,9 +809,9 @@ class Presentation extends PureComponent { overflow: 'hidden', zIndex: fullscreenContext ? presentationBounds.zIndex : undefined, background: - layoutType === LAYOUT_TYPE.VIDEO_FOCUS - && numCameras > 0 - && !fullscreenContext + layoutType === LAYOUT_TYPE.VIDEO_FOCUS && + numCameras > 0 && + !fullscreenContext ? colorContentBackground : null, }} @@ -823,14 +834,14 @@ class Presentation extends PureComponent { textAlign: 'center', display: !presentationIsOpen ? 'none' : 'block', }} - id="presentationInnerWrapper" + id='presentationInnerWrapper' > - + {slideContent} - {!tldrawIsMounting - && currentSlide - && this.renderPresentationMenu()} + {!tldrawIsMounting && + currentSlide && + this.renderPresentationMenu()} { - const usingUsersContext = useContext(UsersContext); - const isRTL = layoutSelect((i) => i.isRTL); - const width = layoutSelect((i) => i?.output?.presentation?.width); - const height = layoutSelect((i) => i?.output?.presentation?.height); - const sidebarNavigationWidth = layoutSelect( - (i) => i?.output?.sidebarNavigation?.width, - ); - const { users } = usingUsersContext; - const currentUser = users[Auth.meetingID][Auth.userID]; - const isPresenter = currentUser.presenter; - const isModerator = currentUser.role === ROLE_MODERATOR; - const { maxStickyNoteLength, maxNumberOfAnnotations } = WHITEBOARD_CONFIG; - const fontFamily = WHITEBOARD_CONFIG.styles.text.family; - const fullscreen = layoutSelect((i) => i.fullscreen); - const handleToggleFullScreen = (ref) => FullscreenService.toggleFullScreen(ref); - const layoutContextDispatch = layoutDispatch(); - - const { shapes } = props; - const hasShapeAccess = (id) => { - const owner = shapes[id]?.userId; - const isBackgroundShape = id?.includes('slide-background'); - const isPollsResult = shapes[id]?.name?.includes('poll-result'); - const hasAccess = (!isBackgroundShape && !isPollsResult) - || (isPresenter - && ((owner && owner === currentUser?.userId) - || !owner - || isPresenter - || isModerator)); - - return hasAccess; - }; - // set shapes as locked for those who aren't allowed to edit it - Object.entries(shapes).forEach(([shapeId, shape]) => { - if (!shape.isLocked && !hasShapeAccess(shapeId)) { - const modShape = shape; - modShape.isLocked = true; - } - }); - - return ( - - ); -}; - -export default withTracker( - ({ - whiteboardId, - curPageId, - intl, - slidePosition, - svgUri, - podId, - presentationId, - darkTheme, - }) => { - const shapes = getShapes(whiteboardId, curPageId, intl); - const curPres = getCurrentPres(); - const { isIphone } = deviceInfo; - - shapes['slide-background-shape'] = { - assetId: `slide-background-asset-${curPageId}`, - childIndex: -1, - id: 'slide-background-shape', - name: 'Image', - type: TDShapeType.Image, - parentId: `${curPageId}`, - point: [0, 0], - isLocked: true, - size: [slidePosition?.width || 0, slidePosition?.height || 0], - style: { - dash: DashStyle.Draw, - size: SizeStyle.Medium, - color: ColorStyle.Blue, - }, - }; - - const assets = {}; - assets[`slide-background-asset-${curPageId}`] = { - id: `slide-background-asset-${curPageId}`, - size: [slidePosition?.width || 0, slidePosition?.height || 0], - src: svgUri, - type: 'image', - }; - - return { - initDefaultPages, - persistShape, - isMultiUserActive, - hasMultiUserAccess, - changeCurrentSlide, - shapes, - assets, - curPres, - removeShapes, - zoomSlide: PresentationToolbarService.zoomSlide, - skipToSlide: PresentationToolbarService.skipToSlide, - nextSlide: PresentationToolbarService.nextSlide, - previousSlide: PresentationToolbarService.previousSlide, - numberOfSlides: PresentationToolbarService.getNumberOfSlides( - podId, - presentationId, - ), - notifyNotAllowedChange, - notifyShapeNumberExceeded, - darkTheme, - whiteboardToolbarAutoHide: - SettingsService?.application?.whiteboardToolbarAutoHide, - animations: SettingsService?.application?.animations, - toggleToolsAnimations, - isIphone, - }; - }, -)(WhiteboardContainer); - -WhiteboardContainer.propTypes = { - shapes: PropTypes.objectOf(PropTypes.shape).isRequired, -}; +import { withTracker } from 'meteor/react-meteor-data'; +import PropTypes from 'prop-types'; +import React, { useContext } from 'react'; +import { ColorStyle, DashStyle, SizeStyle, TDShapeType } from '@tldraw/tldraw'; +import SettingsService from '/imports/ui/services/settings'; +import { + getShapes, + getCurrentPres, + initDefaultPages, + persistShape, + removeShapes, + isMultiUserActive, + hasMultiUserAccess, + changeCurrentSlide, + notifyNotAllowedChange, + notifyShapeNumberExceeded, + toggleToolsAnimations, +} from './service'; +import Whiteboard from './component'; +import { UsersContext } from '../components-data/users-context/context'; +import Auth from '/imports/ui/services/auth'; +import PresentationToolbarService from '../presentation/presentation-toolbar/service'; +import { + layoutSelect, + layoutDispatch, +} from '/imports/ui/components/layout/context'; +import FullscreenService from '/imports/ui/components/common/fullscreen-button/service'; +import deviceInfo from '/imports/utils/deviceInfo'; + +const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator; +const WHITEBOARD_CONFIG = Meteor.settings.public.whiteboard; + +const WhiteboardContainer = (props) => { + const usingUsersContext = useContext(UsersContext); + const isRTL = layoutSelect((i) => i.isRTL); + const width = layoutSelect((i) => i?.output?.presentation?.width); + const height = layoutSelect((i) => i?.output?.presentation?.height); + const sidebarNavigationWidth = layoutSelect( + (i) => i?.output?.sidebarNavigation?.width + ); + const { users } = usingUsersContext; + const currentUser = users[Auth.meetingID][Auth.userID]; + const isPresenter = currentUser.presenter; + const isModerator = currentUser.role === ROLE_MODERATOR; + const { maxStickyNoteLength, maxNumberOfAnnotations } = WHITEBOARD_CONFIG; + const fontFamily = WHITEBOARD_CONFIG.styles.text.family; + const fullscreen = layoutSelect((i) => i.fullscreen); + const handleToggleFullScreen = (ref) => + FullscreenService.toggleFullScreen(ref); + const layoutContextDispatch = layoutDispatch(); + + const { shapes } = props; + const hasShapeAccess = (id) => { + const owner = shapes[id]?.userId; + const isBackgroundShape = id?.includes('slide-background'); + const isPollsResult = shapes[id]?.name?.includes('poll-result'); + const hasAccess = + (!isBackgroundShape && !isPollsResult) || + (isPresenter && + ((owner && owner === currentUser?.userId) || + !owner || + isPresenter || + isModerator)); + + return hasAccess; + }; + // set shapes as locked for those who aren't allowed to edit it + Object.entries(shapes).forEach(([shapeId, shape]) => { + if (!shape.isLocked && !hasShapeAccess(shapeId)) { + const modShape = shape; + modShape.isLocked = true; + } + }); + + return ( + + ); +}; + +export default withTracker( + ({ + whiteboardId, + curPageId, + intl, + slidePosition, + svgUri, + podId, + presentationId, + darkTheme, + }) => { + const shapes = getShapes(whiteboardId, curPageId, intl); + const curPres = getCurrentPres(); + const { isIphone } = deviceInfo; + + shapes['slide-background-shape'] = { + assetId: `slide-background-asset-${curPageId}`, + childIndex: -1, + id: 'slide-background-shape', + name: 'Image', + type: TDShapeType.Image, + parentId: `${curPageId}`, + point: [0, 0], + isLocked: true, + size: [slidePosition?.width || 0, slidePosition?.height || 0], + style: { + dash: DashStyle.Draw, + size: SizeStyle.Medium, + color: ColorStyle.Blue, + }, + }; + + const assets = {}; + assets[`slide-background-asset-${curPageId}`] = { + id: `slide-background-asset-${curPageId}`, + size: [slidePosition?.width || 0, slidePosition?.height || 0], + src: svgUri, + type: 'image', + }; + + return { + initDefaultPages, + persistShape, + isMultiUserActive, + hasMultiUserAccess, + changeCurrentSlide, + shapes, + assets, + curPres, + removeShapes, + zoomSlide: PresentationToolbarService.zoomSlide, + skipToSlide: PresentationToolbarService.skipToSlide, + nextSlide: PresentationToolbarService.nextSlide, + previousSlide: PresentationToolbarService.previousSlide, + numberOfSlides: PresentationToolbarService.getNumberOfSlides( + podId, + presentationId + ), + notifyNotAllowedChange, + notifyShapeNumberExceeded, + darkTheme, + whiteboardToolbarAutoHide: + SettingsService?.application?.whiteboardToolbarAutoHide, + animations: SettingsService?.application?.animations, + toggleToolsAnimations, + isIphone, + }; + } +)(WhiteboardContainer); + +WhiteboardContainer.propTypes = { + shapes: PropTypes.objectOf(PropTypes.shape).isRequired, +}; From 1013e4443ae915f2df7d11c6092eaececc112993 Mon Sep 17 00:00:00 2001 From: Anton B Date: Tue, 1 Aug 2023 13:45:33 -0300 Subject: [PATCH 084/252] test: update Webcam ci tests flags --- bigbluebutton-tests/playwright/webcam/webcam.spec.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bigbluebutton-tests/playwright/webcam/webcam.spec.js b/bigbluebutton-tests/playwright/webcam/webcam.spec.js index 71c595e3ef..f3dfcad47c 100644 --- a/bigbluebutton-tests/playwright/webcam/webcam.spec.js +++ b/bigbluebutton-tests/playwright/webcam/webcam.spec.js @@ -2,9 +2,9 @@ const { test } = require('@playwright/test'); const { MultiUsers } = require('../user/multiusers'); const { Webcam } = require('./webcam'); -test.describe.parallel('Webcam @ci', () => { +test.describe.parallel('Webcam', () => { // https://docs.bigbluebutton.org/2.6/release-tests.html#joining-webcam-automated - test('Shares webcam', async ({ browser, page }) => { + test('Shares webcam @ci', async ({ browser, page }) => { const webcam = new Webcam(browser, page); await webcam.init(true, true); await webcam.share(); @@ -49,7 +49,9 @@ test.describe.parallel('Webcam @ci', () => { await webcam.applyBackground(); }); - test('Managing new background', async ({ browser, page }) => { + // following test is throwing failures due to mis-comparison screenshot + // as the emulated video is not static, we may add a mask in the middle part - where it moves the most + test('Managing new background @flaky', async ({ browser, page }) => { const webcam = new Webcam(browser, page); await webcam.init(true, true); await webcam.managingNewBackground(); From be057ac1552adca5692c0bb092bb952bccc1b5b8 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Tue, 1 Aug 2023 14:51:21 -0300 Subject: [PATCH 085/252] tests: Improve the handling of failures during the workflow (#18390) --- .github/workflows/automated-tests.yml | 44 ++++++++++++++++----------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index bed0893257..5ffb1555dc 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -55,37 +55,41 @@ jobs: artifacts.tar # - name: Fake package build # run: | - # sudo sh -c ' + # sudo -i < /root/bbb-ci-ssl/bbb-dev-ca.pass ; chmod 600 /root/bbb-ci-ssl/bbb-dev-ca.pass ; openssl genrsa -des3 -out bbb-dev-ca.key -passout file:/root/bbb-ci-ssl/bbb-dev-ca.pass 2048 ; openssl req -x509 -new -nodes -key bbb-dev-ca.key -sha256 -days 1460 -passin file:/root/bbb-ci-ssl/bbb-dev-ca.pass -out bbb-dev-ca.crt -subj "/C=CA/ST=BBB/L=BBB/O=BBB/OU=BBB/CN=BBB-DEV" ; - ' + EOF - name: Trust CA run: | - sudo sh -c ' + sudo -i <> /etc/hosts openssl genrsa -out bbb-ci.test.key 2048 - rm bbb-ci.test.csr bbb-ci.test.crt bbb-ci.test.key + rm -f bbb-ci.test.csr bbb-ci.test.crt bbb-ci.test.key cat > bbb-ci.test.ext << EOF authorityKeyIdentifier=keyid,issuer basicConstraints=CA:FALSE @@ -105,15 +109,17 @@ jobs: cat /root/bbb-ci-ssl/bbb-ci.test.key > /local/certs/privkey.pem ' - name: Setup local repository + shell: bash run: | - sudo sh -c ' + sudo -i < Packages.gz echo "deb [trusted=yes] file:/artifacts/ ./" >> /etc/apt/sources.list - ' + EOF - name: Prepare for install run: | sudo sh -c ' @@ -121,14 +127,15 @@ jobs: ' - name: Install BBB run: | - sudo sh -c ' - cd /root/ && wget -q https://raw.githubusercontent.com/bigbluebutton/bbb-install/v2.7.x-release/bbb-install.sh -O bbb-install.sh + sudo -i < /etc/apt/sources.list.d/bigbluebutton.list||g" | bash -s -- -v focal-27-dev -s bbb-ci.test -j -d /certs/ bbb-conf --salt bbbci echo "NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt" >> /usr/share/meteor/bundle/bbb-html5-with-roles.conf sed -i "s/\"minify\": true,/\"minify\": false,/" /usr/share/etherpad-lite/settings.json bbb-conf --restart - ' + EOF - name: Install test dependencies working-directory: ./bigbluebutton-tests/playwright run: | @@ -170,7 +177,7 @@ jobs: - if: failure() name: Prepare artifacts (configs and logs) run: | - sudo sh -c ' + sudo -i < Date: Tue, 1 Aug 2023 19:48:50 -0400 Subject: [PATCH 086/252] docs: Added link for 2.7.0-beta.3 --- docs/docs/new-features.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/new-features.md b/docs/docs/new-features.md index aa64302342..87fa793450 100644 --- a/docs/docs/new-features.md +++ b/docs/docs/new-features.md @@ -118,6 +118,7 @@ For full details on what is new in BigBlueButton 2.7, see the release notes. Recent releases: +- [2.7.0-beta.3](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.7.0-beta.3) - [2.7.0-beta.2](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.7.0-beta.2) - [2.7.0-beta.1](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.7.0-beta.1) - [2.7.0-alpha.3](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.7.0-alpha.3) From 8d88ace0ac093c9dae413fc86f58cd6f633276ff Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 08:55:18 -0300 Subject: [PATCH 087/252] tests: Speed up building time by splitting packages in different jobs (#18415) --- .github/workflows/automated-tests.yml | 147 +++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 5 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 5ffb1555dc..0ee5e84517 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -16,20 +16,105 @@ on: permissions: contents: read jobs: - build-install-and-test: + build-bbb-apps-akka: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - run: ./build/get_external_dependencies.sh - run: ./build/setup.sh bbb-apps-akka + - run: tar cvf artifacts.tar artifacts/ + - name: Archive packages + uses: actions/upload-artifact@v3 + with: + name: artifacts_bbb-apps-akka.tar + path: | + artifacts.tar + build-bbb-config: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - run: ./build/get_external_dependencies.sh - run: ./build/setup.sh bbb-config + - run: tar cvf artifacts.tar artifacts/ + - name: Archive packages + uses: actions/upload-artifact@v3 + with: + name: artifacts_bbb-config.tar + path: | + artifacts.tar + build-bbb-etherpad: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - run: ./build/get_external_dependencies.sh - run: ./build/setup.sh bbb-etherpad - - run: ./build/setup.sh bbb-export-annotations - - run: ./build/setup.sh bbb-freeswitch-core - - run: ./build/setup.sh bbb-freeswitch-sounds + - run: tar cvf artifacts.tar artifacts/ + - name: Archive packages + uses: actions/upload-artifact@v3 + with: + name: artifacts_bbb-etherpad.tar + path: | + artifacts.tar + build-bbb-bbb-web: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - run: ./build/get_external_dependencies.sh + - run: ./build/setup.sh bbb-web + - run: tar cvf artifacts.tar artifacts/ + - name: Archive packages + uses: actions/upload-artifact@v3 + with: + name: artifacts_bbb-web.tar + path: | + artifacts.tar + build-bbb-fsesl-akka: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - run: ./build/get_external_dependencies.sh - run: ./build/setup.sh bbb-fsesl-akka + - run: tar cvf artifacts.tar artifacts/ + - name: Archive packages + uses: actions/upload-artifact@v3 + with: + name: artifacts_bbb-fsesl-akka.tar + path: | + artifacts.tar + build-bbb-html5: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - run: ./build/get_external_dependencies.sh - run: ./build/setup.sh bbb-html5-nodejs - run: ./build/setup.sh bbb-html5 + - run: tar cvf artifacts.tar artifacts/ + - name: Archive packages + uses: actions/upload-artifact@v3 + with: + name: artifacts_bbb-html5.tar + path: | + artifacts.tar + build-bbb-freeswitch: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - run: ./build/get_external_dependencies.sh + - run: ./build/setup.sh bbb-freeswitch-core + - run: ./build/setup.sh bbb-freeswitch-sounds + - run: tar cvf artifacts.tar artifacts/ + - name: Archive packages + uses: actions/upload-artifact@v3 + with: + name: artifacts_bbb-freeswitch.tar + path: | + artifacts.tar + build-others: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - run: ./build/get_external_dependencies.sh + - run: ./build/setup.sh bbb-export-annotations - run: ./build/setup.sh bbb-learning-dashboard - run: ./build/setup.sh bbb-libreoffice-docker - run: ./build/setup.sh bbb-mkclean @@ -41,7 +126,6 @@ jobs: - run: ./build/setup.sh bbb-playback-screenshare - run: ./build/setup.sh bbb-playback-video - run: ./build/setup.sh bbb-record-core - - run: ./build/setup.sh bbb-web - run: ./build/setup.sh bbb-webrtc-sfu - run: ./build/setup.sh bbb-webrtc-recorder - run: ./build/setup.sh bbb-transcription-controller @@ -63,6 +147,59 @@ jobs: # tar xf artifacts.tar # mv artifacts /home/runner/work/bigbluebutton/bigbluebutton/artifacts/ # EOF + install-and-run-tests: + needs: [build-bbb-apps-akka, build-bbb-config, build-bbb-etherpad, build-bbb-bbb-web, build-bbb-fsesl-akka, build-bbb-html5, build-bbb-freeswitch, build-others] + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - run: ./build/get_external_dependencies.sh + - name: Download artifacts_bbb-apps-akka + uses: actions/download-artifact@v3 + with: + name: artifacts_bbb-apps-akka.tar + - run: tar xf artifacts.tar + - name: Download artifacts_bbb-config + uses: actions/download-artifact@v3 + with: + name: artifacts_bbb-config.tar + - run: tar xf artifacts.tar + - name: Download artifacts_bbb-etherpad + uses: actions/download-artifact@v3 + with: + name: artifacts_bbb-etherpad.tar + - run: tar xf artifacts.tar + - name: Download artifacts_bbb-freeswitch + uses: actions/download-artifact@v3 + with: + name: artifacts_bbb-freeswitch.tar + - run: tar xf artifacts.tar + - name: Download artifacts_bbb-web + uses: actions/download-artifact@v3 + with: + name: artifacts_bbb-web.tar + - run: tar xf artifacts.tar + - name: Download artifacts_bbb-fsesl-akka + uses: actions/download-artifact@v3 + with: + name: artifacts_bbb-fsesl-akka.tar + - run: tar xf artifacts.tar + - name: Download artifacts_bbb-html5 + uses: actions/download-artifact@v3 + with: + name: artifacts_bbb-html5.tar + - run: tar xf artifacts.tar + - name: Download artifacts + uses: actions/download-artifact@v3 + with: + name: artifacts.tar + - run: tar xf artifacts.tar + - name: Extracting files .tar + run: | + set -e + pwd + ls + ls artifacts/ + echo "Done" - name: Generate CA run: | sudo -i < Date: Wed, 2 Aug 2023 09:17:50 -0300 Subject: [PATCH 088/252] Generate artifacts cache for bbb-html5 --- .github/workflows/automated-tests.yml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 0ee5e84517..933a1a8cd1 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -85,10 +85,22 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - - run: ./build/get_external_dependencies.sh - - run: ./build/setup.sh bbb-html5-nodejs - - run: ./build/setup.sh bbb-html5 - - run: tar cvf artifacts.tar artifacts/ + - run: echo "CACHE_KEY=$(git log -1 --format=%H -- bigbluebutton-html5)" >> $GITHUB_ENV + - name: Cache bigbluebutton-html5 + id: cache-bigbluebutton-html5 + uses: actions/cache@v3 + env: + cache-name: test + with: + path: artifacts.tar + key: ${{ runner.os }}-${{ env.cache-name }}-${{ env.CACHE_KEY }} + - if: ${{ steps.cache-bigbluebutton-html5.outputs.cache-hit != 'true' }} + name: Generate html5 artifacts + run: | + ./build/get_external_dependencies.sh + ./build/setup.sh bbb-html5-nodejs + ./build/setup.sh bbb-html5 + tar cvf artifacts.tar artifacts/ - name: Archive packages uses: actions/upload-artifact@v3 with: From 938e937f1a88a9d3801f2e792e11b092f4d7733f Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 09:50:25 -0300 Subject: [PATCH 089/252] Add cache for freeswitch build --- .github/workflows/automated-tests.yml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 933a1a8cd1..c4c355960f 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -111,10 +111,22 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - - run: ./build/get_external_dependencies.sh - - run: ./build/setup.sh bbb-freeswitch-core - - run: ./build/setup.sh bbb-freeswitch-sounds - - run: tar cvf artifacts.tar artifacts/ + - run: echo "CACHE_FREESWITCH_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-core)" >> $GITHUB_ENV + - run: echo "CACHE_FREESWITCH_SOUNDS_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-sounds)" >> $GITHUB_ENV + - run: echo "CACHE_SOUNDS_KEY=$(curl -Is http://bigbluebutton.org/downloads/sounds.tar.gz | grep "Last-Modified")" >> $GITHUB_ENV + - name: Handle cache + id: cache-action + uses: actions/cache@v3 + with: + path: artifacts.tar + key: ${{ runner.os }}-bbb-freeswitch-${{ env.CACHE_FREESWITCH_KEY }}-${{ env.CACHE_FREESWITCH_SOUNDS_KEY }}-${{ env.CACHE_SOUNDS_KEY }} + - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} + name: Generate artifacts + run: | + ./build/get_external_dependencies.sh + ./build/setup.sh bbb-freeswitch-core + ./build/setup.sh bbb-freeswitch-sounds + tar cvf artifacts.tar artifacts/ - name: Archive packages uses: actions/upload-artifact@v3 with: From 84436d36dca169e5f2abfb278c08427c18c376b9 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 10:06:43 -0300 Subject: [PATCH 090/252] Fixes to cache flow --- .github/workflows/automated-tests.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index c4c355960f..8d82e0b8a5 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -85,6 +85,14 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 + - run: ls + - run: echo "-----------_" + - run: git status + - run: echo $(git log -n 5) + - run: echo "-----------_" + - run: echo $(git log -n 5 bigbluebutton-html5) + - run: echo "-----------_" + - run: echo $(git log -1 bigbluebutton-html5)" - run: echo "CACHE_KEY=$(git log -1 --format=%H -- bigbluebutton-html5)" >> $GITHUB_ENV - name: Cache bigbluebutton-html5 id: cache-bigbluebutton-html5 @@ -113,7 +121,7 @@ jobs: - uses: actions/checkout@v3 - run: echo "CACHE_FREESWITCH_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-core)" >> $GITHUB_ENV - run: echo "CACHE_FREESWITCH_SOUNDS_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-sounds)" >> $GITHUB_ENV - - run: echo "CACHE_SOUNDS_KEY=$(curl -Is http://bigbluebutton.org/downloads/sounds.tar.gz | grep "Last-Modified")" >> $GITHUB_ENV + - run: echo "CACHE_SOUNDS_KEY=$(curl -Is http://bigbluebutton.org/downloads/sounds.tar.gz | grep "Last-Modified" | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - name: Handle cache id: cache-action uses: actions/cache@v3 From 1f405db0294e2500d6e0b24122898afe64aa1736 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 10:16:11 -0300 Subject: [PATCH 091/252] Config checkout to fetch all history of commits --- .github/workflows/automated-tests.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 8d82e0b8a5..03af64191d 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -85,14 +85,13 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Fetch all history - run: ls - - run: echo "-----------_" - run: git status - run: echo $(git log -n 5) - - run: echo "-----------_" - run: echo $(git log -n 5 bigbluebutton-html5) - - run: echo "-----------_" - - run: echo $(git log -1 bigbluebutton-html5)" + - run: echo $(git log -1 bigbluebutton-html5) - run: echo "CACHE_KEY=$(git log -1 --format=%H -- bigbluebutton-html5)" >> $GITHUB_ENV - name: Cache bigbluebutton-html5 id: cache-bigbluebutton-html5 @@ -119,6 +118,8 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Fetch all history - run: echo "CACHE_FREESWITCH_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-core)" >> $GITHUB_ENV - run: echo "CACHE_FREESWITCH_SOUNDS_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-sounds)" >> $GITHUB_ENV - run: echo "CACHE_SOUNDS_KEY=$(curl -Is http://bigbluebutton.org/downloads/sounds.tar.gz | grep "Last-Modified" | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV From 64753f96e1a6e91ee6c3c2d718cbe0e0a5ddf59d Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 10:30:28 -0300 Subject: [PATCH 092/252] Test commits of the PR --- .github/workflows/automated-tests.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 03af64191d..9ed4938cba 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -86,21 +86,19 @@ jobs: steps: - uses: actions/checkout@v3 with: - fetch-depth: 0 # Fetch all history + fetch-depth: 5 # Fetch all history - run: ls - run: git status - - run: echo $(git log -n 5) - - run: echo $(git log -n 5 bigbluebutton-html5) + - run: echo $(git log -n 6) + - run: echo $(git log -n 6 bigbluebutton-html5) - run: echo $(git log -1 bigbluebutton-html5) - run: echo "CACHE_KEY=$(git log -1 --format=%H -- bigbluebutton-html5)" >> $GITHUB_ENV - name: Cache bigbluebutton-html5 id: cache-bigbluebutton-html5 uses: actions/cache@v3 - env: - cache-name: test with: path: artifacts.tar - key: ${{ runner.os }}-${{ env.cache-name }}-${{ env.CACHE_KEY }} + key: ${{ runner.os }}-bbb-html5-${{ env.CACHE_KEY }} - if: ${{ steps.cache-bigbluebutton-html5.outputs.cache-hit != 'true' }} name: Generate html5 artifacts run: | From 5b824fa858b6c231b576f2b591bef2fa223b8810 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 10:30:46 -0300 Subject: [PATCH 093/252] Test commits of the PR --- .github/workflows/automated-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 9ed4938cba..ff5b88790c 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -117,7 +117,7 @@ jobs: steps: - uses: actions/checkout@v3 with: - fetch-depth: 0 # Fetch all history + fetch-depth: 1000 # Fetch all history - run: echo "CACHE_FREESWITCH_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-core)" >> $GITHUB_ENV - run: echo "CACHE_FREESWITCH_SOUNDS_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-sounds)" >> $GITHUB_ENV - run: echo "CACHE_SOUNDS_KEY=$(curl -Is http://bigbluebutton.org/downloads/sounds.tar.gz | grep "Last-Modified" | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV From e1b6b16b6572e72b7bf5d3787b03b1d686591fd4 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Wed, 2 Aug 2023 13:39:36 +0000 Subject: [PATCH 094/252] Translate en.json in et 100% translated source file: 'en.json' on 'et'. --- bigbluebutton-html5/public/locales/et.json | 724 ++++++++++----------- 1 file changed, 362 insertions(+), 362 deletions(-) diff --git a/bigbluebutton-html5/public/locales/et.json b/bigbluebutton-html5/public/locales/et.json index d35d374225..2793da1350 100644 --- a/bigbluebutton-html5/public/locales/et.json +++ b/bigbluebutton-html5/public/locales/et.json @@ -2,12 +2,12 @@ "app.home.greeting": "Esitlus algab peatselt...", "app.chat.submitLabel": "Saada sõnum", "app.chat.loading": "Vestluse sõnumeid laaditud: {0}%", - "app.chat.errorMaxMessageLength": "Sõnum on liiga pikk, maksimaalne sümbolite arv on {0}.", + "app.chat.errorMaxMessageLength": "Sõnum on liiga pikk, maksimaalne pikkus on {0} tähemärki.", "app.chat.disconnected": "Ühendus on katkenud, sõnumeid ei saa saata", - "app.chat.locked": "Vestlus on lukus, sõnumeid ei saa saata", - "app.chat.inputLabel": "Sõnum vestluse {0} jaoks", - "app.chat.emojiButtonLabel": "Emojide valija", - "app.chat.inputPlaceholder": "Sõnum {0}", + "app.chat.locked": "Vestlus on lukustatud, sõnumeid ei saa saata", + "app.chat.inputLabel": "Sõnum vestluse jaoks: {0}", + "app.chat.emojiButtonLabel": "Emoji valija", + "app.chat.inputPlaceholder": "Sõnum: {0}", "app.chat.titlePublic": "Avalik vestlus", "app.chat.titlePrivate": "Privaatne vestlus kasutajaga {0}", "app.chat.partnerDisconnected": "{0} lahkus koosolekult", @@ -24,7 +24,7 @@ "app.chat.breakoutDurationUpdated": "Eraldatud ruumi aeg on nüüd {0} minutit", "app.chat.breakoutDurationUpdatedModerator": "Eraldatud ruumi aeg on nüüd {0} minutit, teavitus saadetud.", "app.chat.emptyLogLabel": "Vestluse logi on tühi", - "app.chat.clearPublicChatMessage": "Avaliku vestluse ajalugu tühjendati moderaatori poolt", + "app.chat.clearPublicChatMessage": "Moderaator tühjendas avaliku vestluse ajaloo", "app.chat.multi.typing": "Mitu kasutajat kirjutavad", "app.chat.one.typing": "{0} kirjutab", "app.chat.two.typing": "{0} ja {1} kirjutavad", @@ -43,7 +43,7 @@ "app.emojiPicker.categories.objects": "Objektid", "app.emojiPicker.categories.symbols": "Sümbolid", "app.emojiPicker.categories.flags": "Lipud", - "app.emojiPicker.categories.recent": "Sagedasti kasutatud", + "app.emojiPicker.categories.recent": "Sageli kasutatud", "app.emojiPicker.categories.search": "Otsingutulemused", "app.emojiPicker.skintones.1": "Tavaline nahatoon", "app.emojiPicker.skintones.2": "Hele nahatoon", @@ -51,24 +51,24 @@ "app.emojiPicker.skintones.4": "Keskmine nahatoon", "app.emojiPicker.skintones.5": "Keskmiselt tume nahatoon", "app.emojiPicker.skintones.6": "Tume nahatoon", - "app.captions.label": "Tiitrid", + "app.captions.label": "Subtiitrid", "app.captions.menu.close": "Sulge", "app.captions.menu.start": "Alusta", - "app.captions.menu.ariaStart": "Alusta tiitrite kirjutamist", - "app.captions.menu.ariaStartDesc": "Avab tiitrite redaktori ning sulgeb selle akna", - "app.captions.menu.select": "Vali saadaval olev keel", - "app.captions.menu.ariaSelect": "Tiitrite keel", - "app.captions.menu.subtitle": "Vali selle ruumi tiitrite jaoks keel ja kirjastiil", - "app.captions.menu.title": "Tiitrid", + "app.captions.menu.ariaStart": "Alusta subtiitrite kirjutamist", + "app.captions.menu.ariaStartDesc": "Avab subtiitrite redaktori ning sulgeb selle akna", + "app.captions.menu.select": "Vali olemasolev keel", + "app.captions.menu.ariaSelect": "Subtiitrite keel", + "app.captions.menu.subtitle": "Vali oma sessiooni subtiitrite jaoks keel ja kirjastiil", + "app.captions.menu.title": "Subtiitrid", "app.captions.menu.fontSize": "Suurus", "app.captions.menu.fontColor": "Teksti värv", "app.captions.menu.fontFamily": "Kirjatüüp", "app.captions.menu.backgroundColor": "Taustavärv", "app.captions.menu.previewLabel": "Eelvaade", "app.captions.menu.cancelLabel": "Tühista", - "app.captions.hide": "Peida tiitrid", + "app.captions.hide": "Peida subtiitrid", "app.captions.ownership": "Võta üle", - "app.captions.ownershipTooltip": "Sind määratakse {0} tiitri omanikuks", + "app.captions.ownershipTooltip": "Sind määratakse {0} subtiitri omanikuks", "app.captions.dictationStart": "Alusta dikteerimist", "app.captions.dictationStop": "Lõpeta dikteerimine", "app.captions.dictationOnDesc": "Lülitab kõnetuvastuse sisse", @@ -87,22 +87,22 @@ "app.notes.pinnedNotification": "Jagatud märkmed on nüüd tahvlile kinnitatud.", "app.notes.label": "Märkmed", "app.notes.hide": "Peida märkmed", - "app.notes.locked": "Lukus", + "app.notes.locked": "Lukustatud", "app.notes.disabled": "Kinnitatud meediaalale", "app.notes.notesDropdown.covertAndUpload": "Teisenda märkmed esitluseks", "app.notes.notesDropdown.pinNotes": "Kinnita märkmed tahvlile", "app.notes.notesDropdown.unpinNotes": "Vabasta märkmed", "app.notes.notesDropdown.notesOptions": "Märkmete valikud", "app.pads.hint": "Kirjutusala tööriistariba aktiveerimiseks vajuta Esc-klahvi", - "app.user.activityCheck": "Kasutaja tegevuse kontroll", - "app.user.activityCheck.label": "Kontrolli kas kasutaja ({0}) on veel koosolekul", + "app.user.activityCheck": "Kasutaja aktiivsuse kontroll", + "app.user.activityCheck.label": "Kontrolli, kas kasutaja on veel koosolekul ({0})", "app.user.activityCheck.check": "Kontrolli", "app.userList.usersTitle": "Kasutajad", "app.userList.participantsTitle": "Osalejad", "app.userList.messagesTitle": "Sõnumid", "app.userList.notesTitle": "Märkmed", - "app.userList.notesListItem.unreadContent": "Jagatud märkmetes on loodud uut sisu", - "app.userList.captionsTitle": "Tiitrid", + "app.userList.notesListItem.unreadContent": "Jagatud märkmetes on uut sisu", + "app.userList.captionsTitle": "Subtiitrid", "app.userList.presenter": "Esitleja", "app.userList.you": "Sina", "app.userList.locked": "Lukustatud", @@ -113,36 +113,36 @@ "app.userList.mobile": "Mobiil", "app.userList.guest": "Külaline", "app.userList.sharingWebcam": "Veebikaamera", - "app.userList.menuTitleContext": "Saadaval olevad valikud", + "app.userList.menuTitleContext": "Saadaolevad valikud", "app.userList.chatListItem.unreadSingular": "Üks uus sõnum", "app.userList.chatListItem.unreadPlural": "{0} uut sõnumit", "app.userList.menu.chat.label": "Alusta privaatset vestlust", - "app.userList.menu.clearStatus.label": "Tühista staatus", + "app.userList.menu.clearStatus.label": "Kustuta olek", "app.userList.menu.removeUser.label": "Eemalda kasutaja", "app.userList.menu.removeConfirmation.label": "Eemalda kasutaja ({0})", - "app.userlist.menu.removeConfirmation.desc": "Takista kasutajal sessiooniga taasliituda.", + "app.userlist.menu.removeConfirmation.desc": "Tõkesta sellel kasutajal sessiooniga taasliitumine.", "app.userList.menu.muteUserAudio.label": "Vaigista kasutaja", "app.userList.menu.unmuteUserAudio.label": "Eemalda kasutaja vaigistus", "app.userList.menu.webcamPin.label": "Kinnita kasutaja veebikaamera", "app.userList.menu.webcamUnpin.label": "Vabasta kasutaja veebikaamera", - "app.userList.menu.giveWhiteboardAccess.label" : "Anna tahvli kasutusluba", - "app.userList.menu.removeWhiteboardAccess.label": "Eemalda tahvli kasutusluba", + "app.userList.menu.giveWhiteboardAccess.label" : "Anna juurdepääs tahvlile", + "app.userList.menu.removeWhiteboardAccess.label": "Sulge juurdepääs tahvlile", "app.userList.menu.ejectUserCameras.label": "Sulge kaamerad", - "app.userList.userAriaLabel": "{0} {1} {2} staatus {3}", + "app.userList.userAriaLabel": "{0} {1} {2} olek {3}", "app.userList.menu.promoteUser.label": "Ülenda moderaatoriks", "app.userList.menu.demoteUser.label": "Alanda vaatajaks", - "app.userList.menu.unlockUser.label": "Ava {0}", + "app.userList.menu.unlockUser.label": "Vabasta {0}", "app.userList.menu.lockUser.label": "Lukusta {0}", - "app.userList.menu.directoryLookup.label": "Otsi kataloogist", + "app.userList.menu.directoryLookup.label": "Otsing kataloogist", "app.userList.menu.makePresenter.label": "Muuda esitlejaks", "app.userList.userOptions.manageUsersLabel": "Halda kasutajaid", "app.userList.userOptions.muteAllLabel": "Vaigista kõik kasutajad", "app.userList.userOptions.muteAllDesc": "Vaigistab kõik kasutajad koosolekul", - "app.userList.userOptions.clearAllLabel": "Tühista kõik staatuse ikoonid", - "app.userList.userOptions.clearAllDesc": "Tühistab kõikide kasutajate staatuse ikoonid", + "app.userList.userOptions.clearAllLabel": "Kustuta kõik olekuikoonid", + "app.userList.userOptions.clearAllDesc": "Kustutab kõikide kasutajate olekuikoonid", "app.userList.userOptions.muteAllExceptPresenterLabel": "Vaigista kõik kasutajad peale esitleja", "app.userList.userOptions.muteAllExceptPresenterDesc": "Vaigistab kõik kasutajad koosolekul peale esitleja", - "app.userList.userOptions.unmuteAllLabel": "Lülita koosoleku vaigistamine välja", + "app.userList.userOptions.unmuteAllLabel": "Eemalda koosoleku vaigistus", "app.userList.userOptions.unmuteAllDesc": "Taastab koosoleku heli", "app.userList.userOptions.lockViewersLabel": "Lukusta vaatajad", "app.userList.userOptions.lockViewersDesc": "Lukustab koosolekul osalejate teatud funktsionaalsused", @@ -152,45 +152,45 @@ "app.userList.userOptions.disableMic": "Vaatajate mikrofonid on keelatud", "app.userList.userOptions.disablePrivChat": "Privaatne vestlus on keelatud", "app.userList.userOptions.disablePubChat": "Avalik vestlus on keelatud", - "app.userList.userOptions.disableNotes": "Jagatud märkmed on nüüd lukus", - "app.userList.userOptions.hideUserList": "Kasutajate nimekiri on vaatajate eest peidetud", - "app.userList.userOptions.webcamsOnlyForModerator": "Ainult moderaatorid näevad kasutajate veebikaameraid (lukustamisseadete tõttu)", - "app.userList.content.participants.options.clearedStatus": "Kõikide kasutajate staatused tühistati", + "app.userList.userOptions.disableNotes": "Jagatud märkmed on nüüd lukustatud", + "app.userList.userOptions.hideUserList": "Kasutajate nimekiri on nüüd vaatajate eest peidetud", + "app.userList.userOptions.webcamsOnlyForModerator": "Ainult moderaatorid näevad vaatajate veebikaameraid (lukustussätete tõttu)", + "app.userList.content.participants.options.clearedStatus": "Kõigi kasutajate olekud kustutatud", "app.userList.userOptions.enableCam": "Vaatajate veebikaamerad on lubatud", "app.userList.userOptions.enableMic": "Vaatajate mikrofonid on lubatud", "app.userList.userOptions.enablePrivChat": "Privaatne vestlus on lubatud", "app.userList.userOptions.enablePubChat": "Avalik vestlus on lubatud", - "app.userList.userOptions.enableNotes": "Jagatud märkmed on nüüd lubatud", + "app.userList.userOptions.enableNotes": "Jagatud märkmed on nüüd avatud", "app.userList.userOptions.showUserList": "Kasutajate nimekiri on nüüd vaatajatele nähtav", - "app.userList.userOptions.enableOnlyModeratorWebcam": "Saad nüüd veebikaamera lubada ning kõik näevad seda", - "app.userList.userOptions.savedNames.title": "Kasutajate nimekiri koosolekul {0} kell {1}", + "app.userList.userOptions.enableOnlyModeratorWebcam": "Saad nüüd oma veebikaamera sisse lülitada, kõik näevad sind", + "app.userList.userOptions.savedNames.title": "Kasutajate nimekiri koosolekul {0} ajahetkel {1}", "app.userList.userOptions.sortedFirstName.heading": "Sorteeritult eesnime järgi:", "app.userList.userOptions.sortedLastName.heading": "Sorteeritult perekonnanime järgi:", "app.userList.userOptions.hideViewersCursor": "Vaatajate kursorid on lukustatud", - "app.userList.userOptions.showViewersCursor": "Vaatajate kursorid on vabastatud", + "app.userList.userOptions.showViewersCursor": "Vaatajate kursorid on vabad", "app.media.label": "Meedia", "app.media.autoplayAlertDesc": "Luba juurdepääs", "app.media.screenshare.start": "Ekraanijagamine algas", "app.media.screenshare.end": "Ekraanijagamine lõppes", - "app.media.screenshare.endDueToDataSaving": "Ekraanijagamine peatatud andmemahu kokkuhoiu tõttu", + "app.media.screenshare.endDueToDataSaving": "Ekraanijagamine peatatud andmesäästu tõttu", "app.media.screenshare.unavailable": "Ekraanijagamine pole saadaval", - "app.media.screenshare.notSupported": "Ekraanijagamine pole selles brauseris toetatud.", + "app.media.screenshare.notSupported": "See brauser ei toeta ekraanijagamist.", "app.media.screenshare.autoplayBlockedDesc": "Vajame sinu luba, et näidata sulle esitleja ekraani.", "app.media.screenshare.autoplayAllowLabel": "Vaata jagatud ekraani", "app.screenshare.presenterLoadingLabel": "Sinu ekraanijagamist laaditakse", - "app.screenshare.viewerLoadingLabel": "Esitleja ekraanijagamist laaditakse", + "app.screenshare.viewerLoadingLabel": "Esitleja ekraani laaditakse", "app.screenshare.presenterSharingLabel": "Sa jagad nüüd oma ekraani", "app.screenshare.screenshareFinalError": "Kood {0}. Ei saa ekraani jagada.", "app.screenshare.screenshareRetryError": "Kood {0}. Proovi uuesti ekraani jagada.", - "app.screenshare.screenshareRetryOtherEnvError": "Kood {0}. Ei saa ekraani jagada. Proovi mõne muu brauseri või seadmega.", - "app.screenshare.screenshareUnsupportedEnv": "Kood {0}. Brauserit ei toetata. Proovi mõne muu brauseri või seadmega.", - "app.screenshare.screensharePermissionError": "Kood {0}. Ekraani salvestamiseks on vaja luba.", + "app.screenshare.screenshareRetryOtherEnvError": "Kood {0}. Ei saa ekraani jagada. Proovi uuesti muu brauseri või seadmega.", + "app.screenshare.screenshareUnsupportedEnv": "Kood {0}. Brauserit ei toetata. Proovi uuesti muu brauseri või seadmega.", + "app.screenshare.screensharePermissionError": "Kood {0}. Ekraani hõivamiseks on vaja luba.", "app.meeting.ended": "Sessioon on lõppenud", - "app.meeting.meetingTimeRemaining": "Järelejäänud aeg: {0}", - "app.meeting.meetingTimeHasEnded": "Aeg sai läbi. Koosolek suletakse kohe", + "app.meeting.meetingTimeRemaining": "Koosoleku järelejäänud aeg: {0}", + "app.meeting.meetingTimeHasEnded": "Aeg sai läbi. Koosolek sulgub kohe", "app.meeting.endedByUserMessage": "{0} lõpetas sessiooni", - "app.meeting.endedByNoModeratorMessageSingular": "Koosolek lõppes, sest ükski moderaator pole ühe minuti jooksul kohal olnud", - "app.meeting.endedByNoModeratorMessagePlural": "Koosolek lõppes, sest ükski moderaator pole {0} minuti jooksul kohal olnud", + "app.meeting.endedByNoModeratorMessageSingular": "Koosolek lõppes, sest ühe minuti jooksul ei olnud ükski moderaator kohal", + "app.meeting.endedByNoModeratorMessagePlural": "Koosolek lõppes, sest {0} minuti jooksul ei olnud ükski moderaator kohal", "app.meeting.endedMessage": "Sind suunatakse tagasi avalehele", "app.meeting.alertMeetingEndsUnderMinutesSingular": "Koosolek sulgub ühe minuti pärast.", "app.meeting.alertMeetingEndsUnderMinutesPlural": "Koosolek sulgub {0} minuti pärast.", @@ -199,18 +199,18 @@ "app.presentation.hide": "Peida esitlus", "app.presentation.notificationLabel": "Aktiivne esitlus", "app.presentation.downloadLabel": "Laadi alla", - "app.presentation.slideContent": "Slaidi sisu", - "app.presentation.startSlideContent": "Slaidi sisu algus", - "app.presentation.endSlideContent": "Slaidi sisu lõpp", - "app.presentation.changedSlideContent": "Esitluses pööratud ette slaid {0}", + "app.presentation.slideContent": "Slaidisisu", + "app.presentation.startSlideContent": "Slaidisisu algus", + "app.presentation.endSlideContent": "Slaidisisu lõpp", + "app.presentation.changedSlideContent": "Esitluse slaid on nüüd {0}", "app.presentation.emptySlideContent": "Aktiivsel slaidil puudub sisu", "app.presentation.options.fullscreen": "Esitlus täisekraanil", "app.presentation.options.exitFullscreen": "Välju täisekraanilt", "app.presentation.options.minimize": "Minimeeri", "app.presentation.options.snapshot": "Praeguse slaidi hetktõmmis", "app.presentation.options.downloading": "Allalaadimine...", - "app.presentation.options.downloaded": "Aktiivne esitlus laaditi alla", - "app.presentation.options.downloadFailed": "Ei saa aktiivset esitlust alla laadida", + "app.presentation.options.downloaded": "Praegune slaid on alla laaditud", + "app.presentation.options.downloadFailed": "Ei saa praegust slaidi alla laadida", "app.presentation.presentationToolbar.noNextSlideDesc": "Esitluse lõpp", "app.presentation.presentationToolbar.noPrevSlideDesc": "Esitluse algus", "app.presentation.presentationToolbar.selectLabel": "Vali slaid", @@ -222,8 +222,8 @@ "app.presentation.presentationToolbar.skipSlideDesc": "Mine esitluses kindlale slaidile", "app.presentation.presentationToolbar.fitWidthLabel": "Kohanda laiusele", "app.presentation.presentationToolbar.fitWidthDesc": "Kuva slaid kogu laiuses", - "app.presentation.presentationToolbar.fitScreenLabel": "Täida ekraani ulatuses", - "app.presentation.presentationToolbar.fitScreenDesc": "Näita kogu slaidi", + "app.presentation.presentationToolbar.fitScreenLabel": "Kohanda ekraanile", + "app.presentation.presentationToolbar.fitScreenDesc": "Kuva kogu slaid", "app.presentation.presentationToolbar.zoomLabel": "Suurendus", "app.presentation.presentationToolbar.zoomDesc": "Muuda esitluse suurendust", "app.presentation.presentationToolbar.zoomInLabel": "Suurenda", @@ -233,95 +233,95 @@ "app.presentation.presentationToolbar.zoomReset": "Lähtesta suurendus", "app.presentation.presentationToolbar.zoomIndicator": "Praegune suurendusaste", "app.presentation.presentationToolbar.fitToWidth": "Kohanda laiusele", - "app.presentation.presentationToolbar.fitToPage": "Kohanda lehe laiusele", + "app.presentation.presentationToolbar.fitToPage": "Kohanda lehele", "app.presentation.presentationToolbar.goToSlide": "Slaid {0}", "app.presentation.presentationToolbar.hideToolsDesc": "Peida tööriistaribad", - "app.presentation.presentationToolbar.showToolsDesc": " Näita tööriistaribasid", + "app.presentation.presentationToolbar.showToolsDesc": "Näita tööriistaribasid", "app.presentation.placeholder": "Aktiivne esitlus puudub", "app.presentationUploder.title": "Esitlus", - "app.presentationUploder.message": "Esitlejana saad üles laadida igasuguseid Office'i dokumente ja PDF-faile. Parima tulemuse saamiseks soovitame PDFi. Veendu, et esitlus oleks valitud vasakul asuva märkeringi abil.", - "app.presentationUploader.exportHint": "Valides \"Saada vestlusesse\", saavad kasutajad avalikus vestluses allalaaditava lingi koos märkmetega.", + "app.presentationUploder.message": "Esitlejana saad üles laadida ükskõik millise Office'i dokumendi või PDF-faili. Parima tulemuse saamiseks soovitame PDF-faili. Veendu, et esitlus oleks valitud vasakul asuva märkeringiga.", + "app.presentationUploader.exportHint": "Valides \"Saada vestlusesse\", saavad kasutajad avalikus vestluses allalaaditava lingi koos märgetega.", "app.presentationUploader.exportToastHeader": "Vestlusesse saatmine ({0} fail)", "app.presentationUploader.exportToastHeaderPlural": "Vestlusesse saatmine ({0} faili)", "app.presentationUploader.exporting": "Vestlusesse saatmine", "app.presentationUploader.sending": "Saatmine...", "app.presentationUploader.collecting": "Slaidide väljaeraldamine: {0}/{1}...", - "app.presentationUploader.processing": "Slaidide märkmete lisamine: {0}/{1}...", + "app.presentationUploader.processing": "Slaididele märgete lisamine: {0}/{1}...", "app.presentationUploader.sent": "Saadetud", "app.presentationUploader.exportingTimeout": "Eksportimine võtab liiga kaua aega...", "app.presentationUploader.export": "Saada vestlusesse", "app.presentationUploader.export.linkAvailable": "Faili {0} allalaadimise link on avalikus vestluses.", - "app.presentationUploader.export.notAccessibleWarning": "ei tarvitse vastata hõlbustusnõuetele", + "app.presentationUploader.export.notAccessibleWarning": "ei tarvitse vastata ligipääsetavuse nõuetele", "app.presentationUploader.currentPresentationLabel": "Aktiivne esitlus", - "app.presentationUploder.extraHint": "TÄHTIS: ükski fail ei tohi suuruselt ületada {0} MB ja {1} lehekülge.", + "app.presentationUploder.extraHint": "TÄHELEPANU: ükski fail ei tohi suuruselt ületada {0} MB ja {1} lehte.", "app.presentationUploder.uploadLabel": "Laadi üles", "app.presentationUploder.confirmLabel": "Kinnita", - "app.presentationUploder.confirmDesc": "Salvesta muudatused ning alusta esitlust", + "app.presentationUploder.confirmDesc": "Salvesta muudatused ja käivita esitlus", "app.presentationUploder.dismissLabel": "Tühista", - "app.presentationUploder.dismissDesc": "Sulge aken ning tühista muudatused", + "app.presentationUploder.dismissDesc": "Sulge aken ja hülga muudatused", "app.presentationUploder.dropzoneLabel": "Aseta üleslaaditavad failid siia", "app.presentationUploder.dropzoneImagesLabel": "Aseta üleslaaditavad pildid siia", - "app.presentationUploder.browseFilesLabel": "või otsi faile arvutist", + "app.presentationUploder.browseFilesLabel": "või sirvi faile", "app.presentationUploder.browseImagesLabel": "või vali/tee pilte", "app.presentationUploder.externalUploadTitle": "Sisu lisamine kolmanda osapoole rakendusest", "app.presentationUploder.externalUploadLabel": "Sirvi faile", - "app.presentationUploder.fileToUpload": "Ootab üleslaadimist...", + "app.presentationUploder.fileToUpload": "Valmis üleslaadimiseks...", "app.presentationUploder.currentBadge": "Aktiivne", - "app.presentationUploder.rejectedError": "Valitud fail(id) lükati tagasi. Palun kontrolli failitüüpi.", + "app.presentationUploder.rejectedError": "Valitud failid lükati tagasi. Palun kontrolli failitüüpi.", "app.presentationUploder.connectionClosedError": "Katkestatud halva ühenduvuse tõttu. Palun proovi uuesti.", "app.presentationUploder.upload.progress": "Üleslaadimine ({0}%)", - "app.presentationUploder.conversion.204": "Puudub hõivatav sisu", + "app.presentationUploder.conversion.204": "Pole sisu, mida jäädvustada", "app.presentationUploder.upload.413": "Fail on liiga suur, maksimaalne suurus on {0} MB.", - "app.presentationUploder.genericError": "Oih, miskit läks valesti...", + "app.presentationUploder.genericError": "Oih, midagi läks valesti...", "app.presentationUploder.upload.408": "Üleslaadimistõendi taotlemine aegus.", "app.presentationUploder.upload.404": "404: Kehtetu üleslaadimistõend.", "app.presentationUploder.upload.401": "Esitluse üleslaadimistõendi taotlemine ebaõnnestus.", - "app.presentationUploder.conversion.conversionProcessingSlides": "Töötlen lehte {0} / {1}", - "app.presentationUploder.conversion.genericConversionStatus": "Teisendan faili...", - "app.presentationUploder.conversion.generatingThumbnail": "Genereerin pisipilte...", - "app.presentationUploder.conversion.generatedSlides": "Slaidid genereeritud...", - "app.presentationUploder.conversion.generatingSvg": "Genereerin SVG-pilte ...", - "app.presentationUploder.conversion.pageCountExceeded": "Lehekülgede arv on liiga suur, maksimum on {0}", - "app.presentationUploder.conversion.invalidMimeType": "Tuvastati lubamatu vorming (laiend = {0}, sisutüüp={1})", + "app.presentationUploder.conversion.conversionProcessingSlides": "Lehe töötlemine {0} / {1}", + "app.presentationUploder.conversion.genericConversionStatus": "Faili teisendamine...", + "app.presentationUploder.conversion.generatingThumbnail": "Pisipiltide genereerimine...", + "app.presentationUploder.conversion.generatedSlides": "Slaidid loodud...", + "app.presentationUploder.conversion.generatingSvg": "SVG-piltide genereerimine...", + "app.presentationUploder.conversion.pageCountExceeded": "Lehtede arv on liiga suur, maksimum on {0} lehte", + "app.presentationUploder.conversion.invalidMimeType": "Tuvastati sobimatu vorming (laiend = {0}, sisu tüüp = {1})", "app.presentationUploder.conversion.conversionTimeout": "Slaidi {0} ei õnnestunud {1} katsega töödelda.", "app.presentationUploder.conversion.officeDocConversionInvalid": "Office'i dokumendi töötlemine ebaõnnestus. Palun laadi selle asemel üles PDF.", "app.presentationUploder.conversion.officeDocConversionFailed": "Office'i dokumendi töötlemine ebaõnnestus. Palun laadi selle asemel üles PDF.", - "app.presentationUploder.conversion.pdfHasBigPage": "Ei suutnud PDF-faili teisendada, püüa faili optimeerida. Maksimaalne leheküljesuurus on {0}.", + "app.presentationUploder.conversion.pdfHasBigPage": "Ei suutnud PDF-faili teisendada, palun proovi faili optimeerida. Maksimaalne lehesuurus on {0}.", "app.presentationUploder.conversion.timeout": "Oih, teisendamine võttis liiga kaua aega", - "app.presentationUploder.conversion.pageCountFailed": "Lehekülgede arvu määramine ebaõnnestus", - "app.presentationUploder.conversion.unsupportedDocument": "Faililaiendit ei toetata.", + "app.presentationUploder.conversion.pageCountFailed": "Lehtede arvu määramine ebaõnnestus", + "app.presentationUploder.conversion.unsupportedDocument": "Faililaiendit ei toetata", "app.presentationUploder.removePresentationLabel": "Eemalda esitlus", "app.presentationUploder.setAsCurrentPresentation": "Määra esitlus aktiivseks", "app.presentationUploder.tableHeading.filename": "Faili nimi", "app.presentationUploder.tableHeading.options": "Valikud", - "app.presentationUploder.tableHeading.status": "Staatus", + "app.presentationUploder.tableHeading.status": "Olek", "app.presentationUploder.uploading": "Üleslaadimine {0} {1}", - "app.presentationUploder.uploadStatus": "{0} ülelaadimist {1}-st lõpetatud", + "app.presentationUploder.uploadStatus": "{0} üleslaadimist {1}-st lõpetatud", "app.presentationUploder.completed": "{0} üleslaadimist lõpetatud", - "app.presentationUploder.item" : "element", - "app.presentationUploder.itemPlural" : "elementi", - "app.presentationUploder.clearErrors": "Kustuta vead", - "app.presentationUploder.clearErrorsDesc": "Kustutab esitluste ebaõnnestunud üleslaadimised", + "app.presentationUploder.item" : "objekt", + "app.presentationUploder.itemPlural" : "objekti", + "app.presentationUploder.clearErrors": "Tühjenda vead", + "app.presentationUploder.clearErrorsDesc": "Tühjendab esitluste ebaõnnestunud üleslaadimised", "app.presentationUploder.uploadViewTitle": "Laadi esitlus üles", - "app.poll.questionAndoptions.label" : "Näidatav küsimuse tekst.\nA. Küsitluse vastusevariant *\nB. Küsitluse vastusevariant (valikuline)\nC. Küsitluse vastusevariant (valikuline)\nD. Küsitluse vastusevariant (valikuline)\nE. Küsitluse vastusevariant (valikuline)", + "app.poll.questionAndoptions.label" : "Kuvatav küsimuse tekst.\nA. Vastus *\nB. Vastus (valikuline)\nC. Vastus (valikuline)\nD. Vastus (valikuline)\nE. Vastus (valikuline)", "app.poll.customInput.label": "Kohandatud sisend", - "app.poll.customInputInstructions.label": "Kohandatud sisend on lubatud – kirjuta küsimus ja valikuvariant(-did) antud vormingus või vali tekstifail samas vormingus.", - "app.poll.maxOptionsWarning.label": "Kasutada saab ainult esimest 5 varianti!", + "app.poll.customInputInstructions.label": "Kohandatud sisend on lubatud – kirjuta küsimus ja vastus(-ed) antud vormingus või vali tekstifail samas vormingus.", + "app.poll.maxOptionsWarning.label": "Kasutada saab ainult esimest 5 vastust!", "app.poll.pollPaneTitle": "Küsitlus", - "app.poll.enableMultipleResponseLabel": "Kas lubada mitu vastust vastaja kohta?", + "app.poll.enableMultipleResponseLabel": "Kas lubada mitut vastust vastaja kohta?", "app.poll.quickPollTitle": "Kiirküsitlus", "app.poll.hidePollDesc": "Peidab küsitluse paneeli", - "app.poll.quickPollInstruction": "Vali sobiv variant ja alusta küsitlust.", - "app.poll.activePollInstruction": "Jäta see paneel lahti, et jooksvalt näha osalejate vastuseid. Kui oled valmis, vajuta 'Avalda küsitluse tulemused', et näidata tulemusi osalejatele ning küsitlus lõpetada.", - "app.poll.dragDropPollInstruction": "Küsitluse vastusevariantide määramiseks lohista vastusevariante sisaldav tekstifail märgistatud väljale", - "app.poll.customPollTextArea": "Täida küsitluse vastusevariandid", + "app.poll.quickPollInstruction": "Küsitluse alustamiseks vali all sobiv variant.", + "app.poll.activePollInstruction": "Jäta see paneel lahti, et jooksvalt näha osalejate vastuseid. Kui oled valmis, siis vajuta tulemuste avaldamiseks ja küsitluse lõpetamiseks nuppu 'Avalda küsitluse tulemused'.", + "app.poll.dragDropPollInstruction": "Küsitluse vastuste määramiseks lohista vastuseid sisaldav tekstifail märgistatud väljale", + "app.poll.customPollTextArea": "Lisa küsitlusele vastused", "app.poll.publishLabel": "Avalda küsitlus", "app.poll.cancelPollLabel": "Tühista", "app.poll.backLabel": "Alusta küsitlust", "app.poll.closeLabel": "Sulge", "app.poll.waitingLabel": "Vastuste ootamine ({0}/{1})", - "app.poll.ariaInputCount": "Kohandatud küsitluse vastusevariant {0} / {1}", - "app.poll.customPlaceholder": "Lisa küsitluse vastusevariant", + "app.poll.ariaInputCount": "Kohandatud vastus {0} / {1}", + "app.poll.customPlaceholder": "Lisa vastus", "app.poll.noPresentationSelected": "Esitlust ei ole valitud! Palun vali esitlus.", "app.poll.clickHereToSelect": "Valimiseks klõpsa siin", "app.poll.question.label" : "Kirjuta küsimus...", @@ -330,14 +330,14 @@ "app.poll.responseTypes.label" : "Vastusetüübid", "app.poll.optionDelete.label" : "Kustuta", "app.poll.responseChoices.label" : "Vastusevariandid", - "app.poll.typedResponse.desc" : "Kasutajale esitatakse tekstiväli vastuse kirjutamiseks", - "app.poll.addItem.label" : "Lisa element", + "app.poll.typedResponse.desc" : "Kasutajale esitatakse tekstiväli vastuse kirjutamiseks.", + "app.poll.addItem.label" : "Lisa vastus", "app.poll.start.label" : "Alusta küsitlust", "app.poll.secretPoll.label" : "Anonüümne küsitlus", - "app.poll.secretPoll.isSecretLabel": "Küsitlus on anonüümne - üksikvastused ei ole nähtavad", - "app.poll.questionErr": "Küsimuse määramine on nõutav.", + "app.poll.secretPoll.isSecretLabel": "Küsitlus on anonüümne – üksikvastused ei ole nähtavad.", + "app.poll.questionErr": "Küsimuse sisestamine on nõutav.", "app.poll.optionErr": "Sisesta vastusevariant", - "app.poll.startPollDesc": "Alustab küsitlust.", + "app.poll.startPollDesc": "Alustab küsitlust", "app.poll.showRespDesc": "Kuvab vastuste seadistuse", "app.poll.addRespDesc": "Lisab vastuse sisestamise välja", "app.poll.deleteRespDesc": "Eemaldab vastusevariandi {0}", @@ -364,10 +364,10 @@ "app.poll.answer.e": "E", "app.poll.liveResult.usersTitle": "Kasutaja", "app.poll.liveResult.responsesTitle": "Vastus", - "app.poll.liveResult.secretLabel": "See on anonüümne küsitlus. Üksikvastused ei ole nähtavad", - "app.poll.removePollOpt": "Eemaldatud vastusevariant {0}", + "app.poll.liveResult.secretLabel": "See on anonüümne küsitlus. Üksikvastuseid ei näidata.", + "app.poll.removePollOpt": "Eemaldatud vastus {0}", "app.poll.emptyPollOpt": "Tühi", - "app.polling.pollingTitle": "Küsitluse valikud", + "app.polling.pollingTitle": "Küsitluse vastused", "app.polling.pollQuestionTitle": "Küsitluse küsimus", "app.polling.submitLabel": "Saada", "app.polling.submitAriaLabel": "Saada küsitluse vastus", @@ -376,35 +376,35 @@ "app.polling.responseNotSecret": "Tavaline küsitlus - esitleja näeb sinu vastust.", "app.polling.pollAnswerLabel": "Küsitluse vastus {0}", "app.polling.pollAnswerDesc": "Vali see variant, et hääletada {0} poolt", - "app.failedMessage": "Vabandame! Serveriga ühendumisel esineb tõrkeid.", + "app.failedMessage": "Vabandust! Serveriga ühenduse loomisel esineb probleeme.", "app.downloadPresentationButton.label": "Laadi alla esitluse originaal", "app.connectingMessage": "Ühendumine...", - "app.waitingMessage": "Ühendus katkes. Uus ühendumiskatse {0} sekundi pärast ...", - "app.retryNow": "Proovi uuesti kohe", - "app.muteWarning.label": "Klõpsa {0}, et vaigistus eemaldada", - "app.muteWarning.disableMessage": "Vaigistuse alarmid keelatud kuni vaigistuse eemaldamiseni", - "app.muteWarning.tooltip": "Klõpsa, et sulgeda ja keelata hoiatus kuni vaigistuse eemaldamiseni", + "app.waitingMessage": "Ühendus katkes. Proovin uuesti ühendust luua {0} sekundi pärast ...", + "app.retryNow": "Proovi kohe", + "app.muteWarning.label": "Klõpsa {0} vaigistuse eemaldamiseks", + "app.muteWarning.disableMessage": "Vaigistuse märguanded keelatud kuni vaigistuse eemaldamiseni", + "app.muteWarning.tooltip": "Klõpsa, et sulgeda ja keelata märguanded kuni järgmise vaigistuse eemaldamiseni", "app.navBar.settingsDropdown.optionsLabel": "Valikud", - "app.navBar.settingsDropdown.fullscreenLabel": "Täisekraanrakendus", - "app.navBar.settingsDropdown.settingsLabel": "Seaded", - "app.navBar.settingsDropdown.aboutLabel": "Meist", + "app.navBar.settingsDropdown.fullscreenLabel": "Rakendus täisekraanil", + "app.navBar.settingsDropdown.settingsLabel": "Sätted", + "app.navBar.settingsDropdown.aboutLabel": "Info", "app.navBar.settingsDropdown.leaveSessionLabel": "Lahku koosolekult", "app.navBar.settingsDropdown.exitFullscreenLabel": "Välju täisekraanilt", - "app.navBar.settingsDropdown.fullscreenDesc": "Esita seadete menüü täisekraanil", - "app.navBar.settingsDropdown.settingsDesc": "Muuda üldseadeid", + "app.navBar.settingsDropdown.fullscreenDesc": "Kuva sätete menüü täisekraanil", + "app.navBar.settingsDropdown.settingsDesc": "Muuda üldisi sätteid", "app.navBar.settingsDropdown.aboutDesc": "Näita informatsiooni kliendi kohta", "app.navBar.settingsDropdown.leaveSessionDesc": "Lahku koosolekult", - "app.navBar.settingsDropdown.exitFullscreenDesc": "Välju täisekraani vaatest", + "app.navBar.settingsDropdown.exitFullscreenDesc": "Välju täisekraani režiimist", "app.navBar.settingsDropdown.hotkeysLabel": "Kiirklahvid", - "app.navBar.settingsDropdown.hotkeysDesc": "Olemasolevad klahvikombinatsioonid", + "app.navBar.settingsDropdown.hotkeysDesc": "Olemasolevate kiirklahvide loetelu", "app.navBar.settingsDropdown.helpLabel": "Abi", "app.navBar.settingsDropdown.openAppLabel": "Ava BigBlueButtoni mobiilirakenduses", - "app.navBar.settingsDropdown.helpDesc": "Suunab kasutaja videojuhiste juurde (avaneb uuel sakil)", + "app.navBar.settingsDropdown.helpDesc": "Suunab kasutaja videojuhiste juurde (avab uue vahekaardi)", "app.navBar.settingsDropdown.endMeetingDesc": "Lõpetab käimasoleva koosoleku", "app.navBar.settingsDropdown.endMeetingLabel": "Lõpeta koosolek", "app.navBar.userListToggleBtnLabel": "Kasutajate nimekiri sisse/välja", "app.navBar.toggleUserList.ariaLabel": "Kasutajad ja sõnumid sisse/välja", - "app.navBar.toggleUserList.newMessages": "koos uue sõnumi teavitusega", + "app.navBar.toggleUserList.newMessages": "koos uute sõnumite teavitustega", "app.navBar.toggleUserList.newMsgAria": "Uus sõnum kasutajalt {0}", "app.navBar.recording": "Sessiooni salvestatakse", "app.navBar.recording.on": "Salvestatakse", @@ -418,48 +418,48 @@ "app.endMeeting.contentWarning": "Selle sessiooni vestluse sõnumid, jagatud märkmed, tahvli sisu ja jagatud dokumendid pole seejärel enam vahetult kättesaadavad.", "app.endMeeting.yesLabel": "Lõpeta sessioon kõigi kasutajate jaoks", "app.endMeeting.noLabel": "Ei", - "app.about.title": "Meist", - "app.about.version": "Kliendi versioon:", - "app.about.version_label": "BigBlueButtoni versioon:", - "app.about.copyright": "Autoriõigused:", - "app.about.confirmLabel": "Ok", - "app.about.confirmDesc": "Ok", + "app.about.title": "Info", + "app.about.version": "Kliendi versioon", + "app.about.version_label": "BigBlueButtoni versioon", + "app.about.copyright": "Autoriõigus", + "app.about.confirmLabel": "OK", + "app.about.confirmDesc": "OK", "app.about.dismissLabel": "Tühista", "app.about.dismissDesc": "Sulge informatsioon kliendi kohta", "app.mobileAppModal.title": "Ava BigBlueButtoni mobiilirakendus", - "app.mobileAppModal.description": "Kas su seadmesse on installitud BigBlueButtoni rakendus?", + "app.mobileAppModal.description": "Kas BigBlueButtoni rakendus on seadmesse paigaldatud?", "app.mobileAppModal.openApp": "Jah, ava rakendus", "app.mobileAppModal.obtainUrlMsg": "Koosoleku URLi hankimine", "app.mobileAppModal.obtainUrlErrorMsg": "Viga koosoleku URLi hankimisel", - "app.mobileAppModal.openStore": "Ei, ava rakendusepood ja laadi alla", + "app.mobileAppModal.openStore": "Ei, laadi alla rakendustepoest", "app.mobileAppModal.dismissLabel": "Tühista", "app.mobileAppModal.dismissDesc": "Sulge", - "app.mobileAppModal.userConnectedWithSameId": "Kasutaja {0} ühendus just sama IDga nagu sinul.", - "app.actionsBar.changeStatusLabel": "Muuda staatust", + "app.mobileAppModal.userConnectedWithSameId": "Kasutaja {0} ühendus just sama IDga nagu sina.", + "app.actionsBar.changeStatusLabel": "Muuda olekut", "app.actionsBar.muteLabel": "Vaigista", "app.actionsBar.unmuteLabel": "Eemalda vaigistus", "app.actionsBar.camOffLabel": "Lülita kaamera välja", "app.actionsBar.raiseLabel": "Tõsta käsi", - "app.actionsBar.label": "Tegevusteriba", + "app.actionsBar.label": "Tegevusriba", "app.actionsBar.actionsDropdown.restorePresentationLabel": "Taasta esitlus", - "app.actionsBar.actionsDropdown.restorePresentationDesc": "Nupp esitluse taastamiseks, kui see on minimeeritud.", + "app.actionsBar.actionsDropdown.restorePresentationDesc": "Nupp esitluse taasavamiseks pärast selle minimeerimist", "app.actionsBar.actionsDropdown.minimizePresentationLabel": "Minimeeri esitlus", - "app.actionsBar.actionsDropdown.minimizePresentationDesc": "Esitluse minimeerimise nupp", - "app.actionsBar.actionsDropdown.layoutModal": "Paigutusseadete aken", - "app.screenshare.screenShareLabel" : "Ekraanijagamine", + "app.actionsBar.actionsDropdown.minimizePresentationDesc": "Nupp esitluse minimeerimiseks", + "app.actionsBar.actionsDropdown.layoutModal": "Paigutuse sätted", + "app.screenshare.screenShareLabel" : "Ekraani jagamine", "app.submenu.application.applicationSectionTitle": "Rakendus", "app.submenu.application.animationsLabel": "Animatsioonid", "app.submenu.application.audioFilterLabel": "Mikrofoni audiofiltrid", "app.submenu.application.wbToolbarsAutoHideLabel": "Peida tahvli tööriistaribad automaatselt", "app.submenu.application.darkThemeLabel": "Tume režiim", - "app.submenu.application.fontSizeControlLabel": "Teksti suurus", - "app.submenu.application.increaseFontBtnLabel": "Suurenda rakenduse teksti suurust", - "app.submenu.application.decreaseFontBtnLabel": "Vähenda rakenduste teksti suurust", + "app.submenu.application.fontSizeControlLabel": "Kirjasuurus", + "app.submenu.application.increaseFontBtnLabel": "Suurenda rakenduse kirjasuurust", + "app.submenu.application.decreaseFontBtnLabel": "Vähenda rakenduse kirjasuurust", "app.submenu.application.currentSize": "praegu {0}", "app.submenu.application.languageLabel": "Rakenduse keel", "app.submenu.application.languageOptionLabel": "Vali keel", - "app.submenu.application.noLocaleOptionLabel": "Aktiivseid tõlkeid ei ole", - "app.submenu.application.paginationEnabledLabel": "Videod lehtedena", + "app.submenu.application.noLocaleOptionLabel": "Tõlked puuduvad", + "app.submenu.application.paginationEnabledLabel": "Videod lehtedel", "app.submenu.application.layoutOptionLabel": "Paigutuse tüüp", "app.submenu.application.pushLayoutLabel": "Rakenda paigutus", "app.submenu.application.localeDropdown.af": "Afrikaani", @@ -525,13 +525,13 @@ "app.submenu.application.localeDropdown.zh-CN": "Lihtsustatud hiina (Hiina)", "app.submenu.application.localeDropdown.zh-TW": "Traditsiooniline hiina (Taiwan)", "app.submenu.notification.SectionTitle": "Teavitused", - "app.submenu.notification.Desc": "Määra, kuidas ja mille kohta teavitusi antakse.", - "app.submenu.notification.audioAlertLabel": "Audioteatised", - "app.submenu.notification.pushAlertLabel": "Hüpikteatised", + "app.submenu.notification.Desc": "Määra, kuidas ja millest teavitatakse.", + "app.submenu.notification.audioAlertLabel": "Helimärguanded", + "app.submenu.notification.pushAlertLabel": "Hüpikmärguanded", "app.submenu.notification.messagesLabel": "Sõnum vestluses", "app.submenu.notification.userJoinLabel": "Kasutaja liitus", "app.submenu.notification.userLeaveLabel": "Kasutaja lahkus", - "app.submenu.notification.guestWaitingLabel": "Külaline ootab nõusolekut", + "app.submenu.notification.guestWaitingLabel": "Külaline ootab heakskiitu", "app.submenu.audio.micSourceLabel": "Mikrofoni sisend", "app.submenu.audio.speakerSourceLabel": "Kõlarite sisend", "app.submenu.audio.streamVolumeLabel": "Audiovoo helitugevus", @@ -540,21 +540,21 @@ "app.submenu.video.videoOptionLabel": "Vali video sisend", "app.submenu.video.videoQualityLabel": "Video kvaliteet", "app.submenu.video.qualityOptionLabel": "Vali video kvaliteet", - "app.submenu.video.participantsCamLabel": "Näita osalejate veebikaameraid", + "app.submenu.video.participantsCamLabel": "Osalejate veebikaamerate vaatamine", "app.settings.applicationTab.label": "Rakendus", "app.settings.audioTab.label": "Audio", "app.settings.videoTab.label": "Video", "app.settings.usersTab.label": "Osalejad", - "app.settings.main.label": "Seaded", + "app.settings.main.label": "Sätted", "app.settings.main.cancel.label": "Tühista", - "app.settings.main.cancel.label.description": "Tühistab muudatused ning sulgeb seadete menüü", + "app.settings.main.cancel.label.description": "Tühistab muudatused ja sulgeb sätete menüü", "app.settings.main.save.label": "Salvesta", - "app.settings.main.save.label.description": "Salvestab muudatused ning sulgeb seadete menüü", - "app.settings.dataSavingTab.label": "Andmemahu säästmine", + "app.settings.main.save.label.description": "Salvestab muudatused ja sulgeb sätete menüü", + "app.settings.dataSavingTab.label": "Andmesääst", "app.settings.dataSavingTab.webcam": "Luba teiste osalejate veebikaamerad", "app.settings.dataSavingTab.screenShare": "Luba teiste osalejate töölaua jagamine", "app.settings.dataSavingTab.description": "Andmemahu säästmiseks võid muuta, mida ekraanil näidatakse.", - "app.settings.save-notification.label": "Seaded on salvestatud", + "app.settings.save-notification.label": "Sätted on salvestatud", "app.statusNotifier.lowerHands": "Langeta käed", "app.statusNotifier.lowerHandDescOneUser": "Langeta kasutaja {0} käsi", "app.statusNotifier.raisedHandsTitle": "Tõstetud käed", @@ -563,127 +563,127 @@ "app.statusNotifier.and": "ja", "app.switch.onLabel": "SEES", "app.switch.offLabel": "VÄLJAS", - "app.talkingIndicator.ariaMuteDesc" : "Vali vaigistamiseks kasutaja", + "app.talkingIndicator.ariaMuteDesc" : "Vali, et kasutaja vaigistada", "app.talkingIndicator.isTalking" : "{0} räägib", "app.talkingIndicator.moreThanMaxIndicatorsTalking" : "{0}+ räägivad", "app.talkingIndicator.moreThanMaxIndicatorsWereTalking" : "{0}+ rääkisid", "app.talkingIndicator.wasTalking" : "{0} lõpetas rääkimise", "app.actionsBar.actionsDropdown.actionsLabel": "Tegevused", - "app.actionsBar.actionsDropdown.presentationLabel": "Laadi üles/halda esitlusi", - "app.actionsBar.actionsDropdown.initPollLabel": "Alusta küsitlust", + "app.actionsBar.actionsDropdown.presentationLabel": "Laadi üles / halda esitlusi", + "app.actionsBar.actionsDropdown.initPollLabel": "Loo küsitlus", "app.actionsBar.actionsDropdown.desktopShareLabel": "Jaga ekraani", - "app.actionsBar.actionsDropdown.stopDesktopShareLabel": "Lõpeta ekraanijagamine", + "app.actionsBar.actionsDropdown.stopDesktopShareLabel": "Lõpeta ekraani jagamine", "app.actionsBar.actionsDropdown.presentationDesc": "Laadi esitlus üles", - "app.actionsBar.actionsDropdown.initPollDesc": "Alusta küsitlust", - "app.actionsBar.actionsDropdown.desktopShareDesc": "Jaga ekraani teistega", - "app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Lõpeta ekraanijagamine", + "app.actionsBar.actionsDropdown.initPollDesc": "Loo küsitlus", + "app.actionsBar.actionsDropdown.desktopShareDesc": "Jaga oma ekraani teistega", + "app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Lõpeta oma ekraani jagamine", "app.actionsBar.actionsDropdown.pollBtnLabel": "Alusta küsitlust", - "app.actionsBar.actionsDropdown.pollBtnDesc": "Vaheta küsitluse vaadet", + "app.actionsBar.actionsDropdown.pollBtnDesc": "Lülitab küsitluse paneeli", "app.actionsBar.actionsDropdown.saveUserNames": "Salvesta kasutajate nimed", "app.actionsBar.actionsDropdown.createBreakoutRoom": "Loo eraldatud ruumid", "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "Loob koosolekul osalejate jaoks eraldatud ruumid", - "app.actionsBar.actionsDropdown.captionsLabel": "Tiitrite kirjutamine", - "app.actionsBar.actionsDropdown.captionsDesc": "Tiitrite paneel sisse/välja", + "app.actionsBar.actionsDropdown.captionsLabel": "Subtiitrite kirjutamine", + "app.actionsBar.actionsDropdown.captionsDesc": "Lülitab subtiitrite paneeli", "app.actionsBar.actionsDropdown.takePresenter": "Võta esitleja roll", "app.actionsBar.actionsDropdown.takePresenterDesc": "Määra ennast uueks esitlejaks", "app.actionsBar.actionsDropdown.selectRandUserLabel": "Vali juhuslik kasutaja", - "app.actionsBar.actionsDropdown.selectRandUserDesc": "Valib kasutaja juhuslikult kõigi vaatajate seast", + "app.actionsBar.actionsDropdown.selectRandUserDesc": "Valib kõigi vaatajate seast juhuslikult ühe", "app.actionsBar.actionsDropdown.propagateLayoutLabel": "Levita paigutus", - "app.actionsBar.emojiMenu.statusTriggerLabel": "Määra staatus", + "app.actionsBar.emojiMenu.statusTriggerLabel": "Määra olek", "app.actionsBar.emojiMenu.awayLabel": "Eemal", - "app.actionsBar.emojiMenu.awayDesc": "Määra oma staatuseks Eemal", + "app.actionsBar.emojiMenu.awayDesc": "Määra oma olekuks Eemal", "app.actionsBar.emojiMenu.raiseHandLabel": "Tõsta käsi", "app.actionsBar.emojiMenu.lowerHandLabel": "Langeta käsi", "app.actionsBar.emojiMenu.raiseHandDesc": "Tõsta käsi küsimuse esitamiseks", "app.actionsBar.emojiMenu.neutralLabel": "Kõhklev", - "app.actionsBar.emojiMenu.neutralDesc": "Määra oma saatuseks Kõhklev", + "app.actionsBar.emojiMenu.neutralDesc": "Määra oma olekuks Kõhklev", "app.actionsBar.emojiMenu.confusedLabel": "Segaduses", - "app.actionsBar.emojiMenu.confusedDesc": "Määra oma staatuseks Segaduses", - "app.actionsBar.emojiMenu.sadLabel": "Nukker", - "app.actionsBar.emojiMenu.sadDesc": "Määra oma staatuseks Nukker", + "app.actionsBar.emojiMenu.confusedDesc": "Määra oma olekuks Segaduses", + "app.actionsBar.emojiMenu.sadLabel": "Kurb", + "app.actionsBar.emojiMenu.sadDesc": "Määra oma olekuks Kurb", "app.actionsBar.emojiMenu.happyLabel": "Rõõmus", - "app.actionsBar.emojiMenu.happyDesc": "Määra oma staatuseks Rõõmus", - "app.actionsBar.emojiMenu.noneLabel": "Tühista staatus", - "app.actionsBar.emojiMenu.noneDesc": "Tühista oma staatus", + "app.actionsBar.emojiMenu.happyDesc": "Määra oma olekuks Rõõmus", + "app.actionsBar.emojiMenu.noneLabel": "Kustuta olek", + "app.actionsBar.emojiMenu.noneDesc": "Kustuta oma olek", "app.actionsBar.emojiMenu.applauseLabel": "Aplodeeri", - "app.actionsBar.emojiMenu.applauseDesc": "Määra oma staatuseks Aplodeeri", + "app.actionsBar.emojiMenu.applauseDesc": "Määra oma olekuks Aplodeeri", "app.actionsBar.emojiMenu.thumbsUpLabel": "Pöial püsti", - "app.actionsBar.emojiMenu.thumbsUpDesc": "Määra oma staatuseks Pöial püsti", + "app.actionsBar.emojiMenu.thumbsUpDesc": "Määra oma olekuks Pöial püsti", "app.actionsBar.emojiMenu.thumbsDownLabel": "Pöial alla", - "app.actionsBar.emojiMenu.thumbsDownDesc": "Määra oma staatuseks Pöial alla", - "app.actionsBar.currentStatusDesc": "praegune staatus {0}", - "app.actionsBar.captions.start": "Alusta tiitrite vaatamist", - "app.actionsBar.captions.stop": "Lõpeta tiitrite vaatamine", - "app.audioNotification.audioFailedError1001": "WebSocket kaotas ühenduse (viga 1001)", + "app.actionsBar.emojiMenu.thumbsDownDesc": "Määra oma olekuks Pöial alla", + "app.actionsBar.currentStatusDesc": "praegune olek {0}", + "app.actionsBar.captions.start": "Kuva subtiitrid", + "app.actionsBar.captions.stop": "Peida subtiitrid", + "app.audioNotification.audioFailedError1001": "WebSocketi ühendus katkenud (viga 1001)", "app.audioNotification.audioFailedError1002": "WebSocketi ühendust ei õnnestunud luua (viga 1002)", - "app.audioNotification.audioFailedError1003": "Veebilehitseja versiooni ei toetata (viga 1003)", - "app.audioNotification.audioFailedError1004": "Ühendumise viga (põhjus={0}) (viga 1004)", - "app.audioNotification.audioFailedError1005": "Ühendus lõppes ootamatult (viga 1005)", - "app.audioNotification.audioFailedError1006": "Ühendus aegus (viga 1006)", - "app.audioNotification.audioFailedError1007": "Ühendamise viga (ICE viga 1007)", - "app.audioNotification.audioFailedError1008": "Ülekanne ebaõnnestus (viga 1008)", - "app.audioNotification.audioFailedError1009": "Ei õnnestunud hankida STUN/TURN serveri informatsiooni (viga 1009)", + "app.audioNotification.audioFailedError1003": "Brauseri versiooni ei toetata (viga 1003)", + "app.audioNotification.audioFailedError1004": "Viga ühendumisel (põhjus={0}) (viga 1004)", + "app.audioNotification.audioFailedError1005": "Ühendumine lõppes ootamatult (viga 1005)", + "app.audioNotification.audioFailedError1006": "Ühendumine aegus (viga 1006)", + "app.audioNotification.audioFailedError1007": "Ühendumine ebaõnnestus (ICE viga 1007)", + "app.audioNotification.audioFailedError1008": "Edasiandmine ebaõnnestus (viga 1008)", + "app.audioNotification.audioFailedError1009": "Ei õnnestunud hankida STUN/TURN-serveri informatsiooni (viga 1009)", "app.audioNotification.audioFailedError1010": "Ühenduse kooskõlastamine aegus (ICE viga 1010)", "app.audioNotification.audioFailedError1011": "Ühendus aegus (ICE viga 1011)", "app.audioNotification.audioFailedError1012": "Ühendus suletud (ICE viga 1012)", - "app.audioNotification.audioFailedMessage": "Audio ühendamine ebaõnnestus", - "app.audioNotification.mediaFailedMessage": "getUserMicMedia ebaõnnestus, sest lubatud on ainult turvalised allikad", - "app.audioNotification.deviceChangeFailed": "Audioseadme muutmine ebaõnnestus. Kontrolli, kas valitud seade on õigesti seadistatud ja saadaval", + "app.audioNotification.audioFailedMessage": "Audioühenduse loomine ebaõnnestus", + "app.audioNotification.mediaFailedMessage": "getUserMicMedia ebaõnnestus, sest lubatud on ainult turvalised allikad.", + "app.audioNotification.deviceChangeFailed": "Audioseadme muutmine ebaõnnestus. Kontrolli, kas valitud seade on õigesti seadistatud ja saadaval.", "app.audioNotification.closeLabel": "Sulge", "app.audioNotificaion.reconnectingAsListenOnly": "Vaatajate mikrofonid on lukustatud, sind ühendatakse ainult kuulajana", "app.breakoutJoinConfirmation.title": "Liitu eraldatud ruumiga", - "app.breakoutJoinConfirmation.message": "Kas soovid liituda?", - "app.breakoutJoinConfirmation.confirmDesc": "Liitu eraldatud ruumiga", + "app.breakoutJoinConfirmation.message": "Kas soovid liituda ruumiga", + "app.breakoutJoinConfirmation.confirmDesc": "Ühendab sind eraldatud ruumiga", "app.breakoutJoinConfirmation.dismissLabel": "Tühista", - "app.breakoutJoinConfirmation.dismissDesc": "Sulgeb ja keelab eraldatud ruumiga liitumise", + "app.breakoutJoinConfirmation.dismissDesc": "Sulgeb ja lükkab tagasi eraldatud ruumiga liitumise", "app.breakoutJoinConfirmation.freeJoinMessage": "Vali eraldatud ruum, millega liituda", "app.breakoutTimeRemainingMessage": "Eraldatud ruumi järelejäänud aeg: {0}", - "app.breakoutWillCloseMessage": "Aeg sai läbi. Eraldatud ruum suletakse kohe", + "app.breakoutWillCloseMessage": "Aeg sai läbi. Eraldatud ruum sulgub kohe.", "app.breakout.dropdown.manageDuration": "Muuda kestust", "app.breakout.dropdown.destroyAll": "Lõpeta eraldatud ruumid", "app.breakout.dropdown.options": "Eraldatud ruumide valikud", "app.breakout.dropdown.manageUsers": "Halda kasutajaid", - "app.calculatingBreakoutTimeRemaining": "Arvutan järelejäänud aega...", + "app.calculatingBreakoutTimeRemaining": "Järelejäänud aja arvutamine...", "app.audioModal.ariaTitle": "Audioga liitumise aken", "app.audioModal.microphoneLabel": "Mikrofoniga", "app.audioModal.listenOnlyLabel": "Ainult kuulata", - "app.audioModal.microphoneDesc": "Audiosessiooniga ühinemine mikrofoniga", - "app.audioModal.listenOnlyDesc": "Audiosessiooniga ühinemine ainult kuulajana", + "app.audioModal.microphoneDesc": "Audiosessioonil osalemine mikrofoniga", + "app.audioModal.listenOnlyDesc": "Audiosessioonil osalemine ainult kuulajana", "app.audioModal.audioChoiceLabel": "Kuidas soovid audioga liituda?", "app.audioModal.iOSBrowser": "Audio/video ei ole toetatud", - "app.audioModal.iOSErrorDescription": "Praegu Chrome iOSis audiot ja videot ei toeta.", + "app.audioModal.iOSErrorDescription": "Chrome praegu iOSis audiot ja videot ei toeta.", "app.audioModal.iOSErrorRecommendation": "Soovitame iOSis kasutada Safarit.", "app.audioModal.audioChoiceDesc": "Vali, kuidas selle koosoleku audioga liituda", - "app.audioModal.unsupportedBrowserLabel": "Paistab, et kasutad veebilehitsejat, mida täielikult ei toetata. Täielikuks toetuseks kasuta palun brausereid {0} või {1}.", + "app.audioModal.unsupportedBrowserLabel": "Paistab, et kasutad brauserit, mida täielikult ei toetata. Täielikult on toetatud näiteks {0} või {1}.", "app.audioModal.closeLabel": "Sulge", "app.audioModal.yes": "Jah", "app.audioModal.no": "Ei", "app.audioModal.yes.arialabel" : "Heli on kuulda", "app.audioModal.no.arialabel" : "Heli ei ole kuulda", "app.audioModal.echoTestTitle": "See on privaatne helitest. Ütle mõned sõnad. Kas kuuled ennast rääkimas?", - "app.audioModal.settingsTitle": "Muuda audioseadeid", + "app.audioModal.settingsTitle": "Muuda audiosätteid", "app.audioModal.helpTitle": "Sinu meediaseadmetega tekkis probleem", - "app.audioModal.helpText": "Kas sa andsid mikrofonile juurdepääsuks loa? Kui püüad audioga liituda, siis peaks ilmuma dialoogiaken, kus küsitakse luba meediaseadme kasutamiseks. Palun anna luba, et audiosessiooniga ühineda. Kui on midagi muud, siis püüa muuta veebilehitseja seadetes mikrofoniga seotud õigusi.", - "app.audioModal.help.noSSL": "See leht pole turvaline. Mikrofoni lubamiseks peab leht olema turvalisel HTTPS-ühendusel. Võta palun ühendust serveri administraatoriga.", - "app.audioModal.help.macNotAllowed": "Paistab, et Maci süsteemiseaded blokeerivad mikrofonile juurdepääsu. Ava System Preferences > Security & Privacy > Privacy > Microphone ning veendu, et veebilehitseja, mida kasutad, on märgitud.", + "app.audioModal.helpText": "Kas andsid loa juurdepääsuks mikrofonile? Kui püüad liituda audioga, siis peaks ilmuma dialoogiaken, kus küsitakse luba meediaseadme kasutamiseks; audiosessiooniga liitumiseks palun nõustu sellega. Muul juhul proovi muuta brauseri sätetes mikrofoniga seotud õigusi.", + "app.audioModal.help.noSSL": "See leht pole turvaline. Mikrofonile juurdepääsu lubamiseks peab leht kasutama HTTPSi. Palun võta ühendust serveri administraatoriga.", + "app.audioModal.help.macNotAllowed": "Paistab, et Maci süsteemieelistused blokeerivad juurdepääsu mikrofonile. Ava System Preferences > Security & Privacy > Privacy > Microphone ja kontrolli, et brauser, mida kasutad, oleks märgitud.", "app.audioModal.audioDialTitle": "Liitu telefoni abil", "app.audioDial.audioDialDescription": "Vali", - "app.audioDial.audioDialConfrenceText": "ja sisesta sessiooni PIN-number:", - "app.audioModal.autoplayBlockedDesc": "Vajame audio mängimiseks sinu luba", - "app.audioModal.playAudio": "Mängi audiot", - "app.audioModal.playAudio.arialabel" : "Mängi audiot", + "app.audioDial.audioDialConfrenceText": "ja sisesta koosoleku PIN-kood:", + "app.audioModal.autoplayBlockedDesc": "Vajame heli esitamiseks sinu luba", + "app.audioModal.playAudio": "Mängi heli", + "app.audioModal.playAudio.arialabel" : "Mängi heli", "app.audioDial.tipIndicator": "Soovitus", "app.audioDial.tipMessage": "Vajuta oma telefonil klahvi '0', et end vaigistada või vaigistus eemaldada.", "app.audioModal.connecting": "Audioühenduse loomine", - "app.audioManager.joinedAudio": "Oled audiosessiooniga ühinenud", - "app.audioManager.joinedEcho": "Helitest käivitus", + "app.audioManager.joinedAudio": "Oled audiosessiooniga liitunud", + "app.audioManager.joinedEcho": "Oled helitestiga liitunud", "app.audioManager.leftAudio": "Oled audiosessioonist lahkunud", "app.audioManager.reconnectingAudio": "Üritame uuesti audioga ühenduda", - "app.audioManager.genericError": "Viga: Esines viga, palun proovi uuesti", + "app.audioManager.genericError": "Viga: Tekkis viga, palun proovi uuesti", "app.audioManager.connectionError": "Viga: Ühenduse viga", "app.audioManager.requestTimeout": "Viga: Päring aegus", "app.audioManager.invalidTarget": "Viga: Päring teostati valele objektile", - "app.audioManager.mediaError": "Viga: Ei saanud ühendust sinu meediaseadmetega", + "app.audioManager.mediaError": "Viga: Tekkis probleem sinu meediaseadmete kättesaamisega", "app.audio.joinAudio": "Liitu audioga", "app.audio.leaveAudio": "Lahku audiost", "app.audio.changeAudioDevice": "Muuda audioseadet", @@ -695,8 +695,8 @@ "app.audio.microphones": "Mikrofonid", "app.audio.speakers": "Kõlarid", "app.audio.noDeviceFound": "Seadmeid ei leitud", - "app.audio.audioSettings.titleLabel": "Vali oma audio seaded", - "app.audio.audioSettings.descriptionLabel": "Pane tähele, et veebilehitsejas avaneb dialoogiaken, kus palutakse luba sinu mikrofoni jagamiseks.", + "app.audio.audioSettings.titleLabel": "Vali oma audio sätted", + "app.audio.audioSettings.descriptionLabel": "Pane tähele, et veebilehitsejas avaneb dialoogiaken, kus palutakse nõustuda mikrofoni jagamisega.", "app.audio.audioSettings.microphoneSourceLabel": "Mikrofoni sisend", "app.audio.audioSettings.speakerSourceLabel": "Kõlarite sisend", "app.audio.audioSettings.testSpeakerLabel": "Testi kõlarit", @@ -709,13 +709,13 @@ "app.audio.listenOnly.backLabel": "Tagasi", "app.audio.listenOnly.closeLabel": "Sulge", "app.audio.permissionsOverlay.title": "Luba juurdepääs oma mikrofonile", - "app.audio.permissionsOverlay.hint": "Vajame sinu meediaseadmete kasutamiseks luba, et saaksime sind audiokonverentsiga ühendada :)", - "app.audio.captions.button.start": "Kuva tiitrid", - "app.audio.captions.button.stop": "Peata tiitrid", + "app.audio.permissionsOverlay.hint": "Vajame luba sinu meediaseadmete kasutamiseks, et saaksime sind audiosessiooniga ühendada :)", + "app.audio.captions.button.start": "Kuva subtiitrid", + "app.audio.captions.button.stop": "Peida subtiitrid", "app.audio.captions.button.language": "Keel", "app.audio.captions.button.transcription": "Transkriptsioon", - "app.audio.captions.button.transcriptionSettings": "Transkriptsiooni seaded", - "app.audio.captions.speech.title": "Automaatne transkribeerimine", + "app.audio.captions.button.transcriptionSettings": "Transkriptsiooni sätted", + "app.audio.captions.speech.title": "Automaatne transkriptsioon", "app.audio.captions.speech.disabled": "Keelatud", "app.audio.captions.speech.unsupported": "See brauser ei toeta kõnetuvastust. Audiot ei transkribeerita.", "app.audio.captions.select.de-DE": "Saksa", @@ -728,28 +728,28 @@ "app.audio.captions.select.pt-BR": "Portugali", "app.audio.captions.select.ru-RU": "Vene", "app.audio.captions.select.zh-CN": "Hiina", - "app.error.removed": "Oled sessioonilt eemaldatud", - "app.error.meeting.ended": "Oled sessioonist välja logitud", - "app.meeting.logout.duplicateUserEjectReason": "Olemasolev kasutaja püüab uuesti koosolekuga liituda", - "app.meeting.logout.permissionEjectReason": "Tagasi lükatud õiguste rikkumise pärast", + "app.error.removed": "Oled koosolekult eemaldatud", + "app.error.meeting.ended": "Oled koosolekult välja logitud", + "app.meeting.logout.duplicateUserEjectReason": "Juba kohal olev kasutaja püüab koosolekuga liituda", + "app.meeting.logout.permissionEjectReason": "Välja heidetud õiguste rikkumise pärast", "app.meeting.logout.ejectedFromMeeting": "Oled koosolekult eemaldatud", "app.meeting.logout.validateTokenFailedEjectReason": "Autoriseerimistõendi kehtivuse kinnitamine ebaõnnestus", "app.meeting.logout.userInactivityEjectReason": "Kasutaja on olnud liiga kaua mitteaktiivne", "app.meeting.logout.maxParticipantsReached": "Sellel koosolekul lubatud osalejate maksimaalarv on täis", - "app.meeting-ended.rating.legendLabel": "Tagasiside hinnang", + "app.meeting-ended.rating.legendLabel": "Tagasisidehinnang", "app.meeting-ended.rating.starLabel": "Täht", "app.modal.close": "Sulge", - "app.modal.close.description": "Tühista muudatused ning sulge aken", + "app.modal.close.description": "Tühistab muudatused ja sulgeb akna", "app.modal.confirm": "Valmis", - "app.modal.newTab": "(avaneb uuel sakil)", - "app.modal.confirm.description": "Salvestab muudatused ning sulgeb akna", - "app.modal.randomUser.noViewers.description": "Pole osalejaid, kelle hulgast juhuslikult valida", + "app.modal.newTab": "(avab uue vahekaardi)", + "app.modal.confirm.description": "Salvestab muudatused ja sulgeb akna", + "app.modal.randomUser.noViewers.description": "Pole vaatajaid, kelle hulgast juhuslikult valida", "app.modal.randomUser.selected.description": "Sind valiti juhuslikult", - "app.modal.randomUser.title": "Juhuslikult valitud kasutaja", + "app.modal.randomUser.title": "Juhuslikult valitud osaleja", "app.modal.randomUser.who": "Kes valitakse...?", - "app.modal.randomUser.alone": "On ainult üks osaleja", + "app.modal.randomUser.alone": "On ainult üks vaataja", "app.modal.randomUser.reselect.label": "Vali uuesti", - "app.modal.randomUser.ariaLabel.title": "Juhuslikult valitud kasutaja aken", + "app.modal.randomUser.ariaLabel.title": "Juhusliku osaleja valimise aken", "app.dropdown.close": "Sulge", "app.dropdown.list.item.activeLabel": "Aktiivne", "app.error.400": "Vigane päring", @@ -764,39 +764,39 @@ "app.error.disconnected.rejoin": "Uuesti liitumiseks võid lehte värskendada.", "app.error.userLoggedOut": "Kasutaja sessioonitõend on kehtetu väljalogimise tõttu", "app.error.ejectedUser": "Kasutaja sessioonitõend on kehtetu väljaheitmise tõttu", - "app.error.joinedAnotherWindow": "See sessioon paistab olevat avatud teises brauseriaknas.", + "app.error.joinedAnotherWindow": "See sessioon paistab olevat juba avatud teises brauseriaknas.", "app.error.userBanned": "Kasutaja on blokeeritud", - "app.error.leaveLabel": "Sisene uuesti", - "app.error.fallback.presentation.title": "Esines viga", + "app.error.leaveLabel": "Logi uuesti sisse", + "app.error.fallback.presentation.title": "Tekkis viga", "app.error.fallback.presentation.description": "Viga on registreeritud. Palun proovi leht uuesti laadida.", "app.error.fallback.presentation.reloadButton": "Laadi uuesti", - "app.guest.errorSeeConsole": "Viga: täpsemad üksikasjad konsoolil.", + "app.guest.errorSeeConsole": "Viga: rohkem üksikasju konsoolil.", "app.guest.noModeratorResponse": "Pole vastust moderaatorilt.", - "app.guest.noSessionToken": "Pole kätte saanud sessioonitõendit.", + "app.guest.noSessionToken": "Pole saadud sessioonitõendit.", "app.guest.windowTitle": "BigBlueButton - Külaliste ala", - "app.guest.missingToken": "Külalisel pole sessioonitõendit.", - "app.guest.missingSession": "Külalisel pole sessiooni.", + "app.guest.missingToken": "Külalisel puudub sessioonitõend.", + "app.guest.missingSession": "Külalisel puudub sessioon.", "app.guest.missingMeeting": "Koosolekut pole olemas.", "app.guest.meetingEnded": "Koosolek on lõppenud.", - "app.guest.guestWait": "Palun oota, kuni moderaator annab nõusoleku koosolekuga ühinemiseks.", - "app.guest.guestDeny": "Külalisel ei lubatud koosolekuga ühineda.", + "app.guest.guestWait": "Palun oota, kuni moderaator kiidab heaks sinu liitumise koosolekuga.", + "app.guest.guestDeny": "Külalisel ei lubatud koosolekuga liituda.", "app.guest.seatWait": "Külaline ootab kohta koosolekul.", - "app.guest.allow": "Külalisele nõusolek antud, suunamine koosolekule.", + "app.guest.allow": "Külalisele heakskiit antud, suunamine koosolekule.", "app.guest.firstPositionInWaitingQueue": "Oled järjekorras esimene!", "app.guest.positionInWaitingQueue": "Sinu koht ootejärjekorras:", "app.guest.guestInvalid": "Külaline on sobimatu", "app.guest.meetingForciblyEnded": "Ei saa liituda koosolekuga, mis on lõpetatud", - "app.userList.guest.waitingUsers": "Kasutajate ootamine", - "app.userList.guest.waitingUsersTitle": "Kasutajate haldus", - "app.userList.guest.optionTitle": "Vaata üle ootel olevad kasutajad", - "app.userList.guest.allowAllAuthenticated": "Luba kõik autenditud kasutajad", + "app.userList.guest.waitingUsers": "Ootavad kasutajad", + "app.userList.guest.waitingUsersTitle": "Kasutajate haldamine", + "app.userList.guest.optionTitle": "Vaata ootel olevaid kasutajaid", + "app.userList.guest.allowAllAuthenticated": "Luba kõik autenditud", "app.userList.guest.allowAllGuests": "Luba kõik külalised", - "app.userList.guest.allowEveryone": "Luba kõik kasutajad", - "app.userList.guest.denyEveryone": "Keela kõik kasutajad", + "app.userList.guest.allowEveryone": "Luba kõik", + "app.userList.guest.denyEveryone": "Keela kõik", "app.userList.guest.pendingUsers": "{0} ootel olevat kasutajat", "app.userList.guest.noPendingUsers": "Ootel kasutajaid ei ole...", - "app.userList.guest.pendingGuestUsers": "{0} ootel olevat külalist", - "app.userList.guest.pendingGuestAlert": "On liitunud sessiooniga ning ootab sinu nõusolekut.", + "app.userList.guest.pendingGuestUsers": "{0} külalist ootel", + "app.userList.guest.pendingGuestAlert": "On sessiooniga liitunud ja ootab sinu heakskiitu.", "app.userList.guest.rememberChoice": "Jäta valik meelde", "app.userList.guest.emptyMessage": "Sõnum praegu puudub", "app.userList.guest.inputPlaceholder": "Sõnum külaliste alale", @@ -806,45 +806,45 @@ "app.userList.guest.denyLabel": "Keela", "app.userList.guest.feedbackMessage": "Rakendatud tegevus:", "app.user-info.title": "Otsi kataloogist", - "app.toast.breakoutRoomEnded": "Eraldatud ruum aegus. Palun ühendu uuesti audioga.", + "app.toast.breakoutRoomEnded": "Eraldatud ruum on lõppenud. Palun liitu uuesti audioga.", "app.toast.chat.public": "Uus avalik sõnum", "app.toast.chat.private": "Uus privaatne sõnum", "app.toast.chat.system": "Süsteem", "app.toast.chat.poll": "Küsitluse tulemused", "app.toast.chat.pollClick": "Küsitluse tulemused avaldati. Vaatamiseks klõpsa siin.", - "app.toast.clearedEmoji.label": "Emoji-staatus tühistatud", - "app.toast.setEmoji.label": "Emoji-staatus määratud: {0}", + "app.toast.clearedEmoji.label": "Emoji-olek kustutatud", + "app.toast.setEmoji.label": "Emoji-olek seatud: {0}", "app.toast.meetingMuteOn.label": "Kõik kasutajad on vaigistatud", "app.toast.meetingMuteOnViewers.label": "Kõik vaatajad on vaigistatud", "app.toast.meetingMuteOff.label": "Koosoleku vaigistamine on välja lülitatud", "app.toast.setEmoji.raiseHand": "Sa tõstsid käe", "app.toast.setEmoji.lowerHand": "Sinu käsi langetati", - "app.toast.promotedLabel": "Sa oled ülendatud moderaatoriks", - "app.toast.demotedLabel": "Sa oled alandatud vaatajaks", - "app.notification.recordingStart": "Sessiooni salvestatakse", - "app.notification.recordingStop": "Sessiooni ei salvestata", - "app.notification.recordingPaused": "Sessiooni enam ei salvestata", + "app.toast.promotedLabel": "Sind ülendati moderaatoriks", + "app.toast.demotedLabel": "Sind alandati vaatajaks", + "app.notification.recordingStart": "Sessiooni salvestamine algas", + "app.notification.recordingStop": "Sessiooni salvestamine lõppes", + "app.notification.recordingPaused": "Sessiooni salvestamine peatatud", "app.notification.recordingAriaLabel": "Salvestuse kestus", "app.notification.userJoinPushAlert": "{0} liitus sessiooniga", "app.notification.userLeavePushAlert": "{0} lahkus sessioonist", "app.submenu.notification.raiseHandLabel": "Tõsta käsi", "app.shortcut-help.title": "Kiirklahvid", - "app.shortcut-help.accessKeyNotAvailable": "Juurdepääsuklahvid pole saadaval", + "app.shortcut-help.accessKeyNotAvailable": "Juurdepääsuklahvid ei ole saadaval", "app.shortcut-help.comboLabel": "Klahv", "app.shortcut-help.alternativeLabel": "Alternatiiv", "app.shortcut-help.functionLabel": "Funktsioon", "app.shortcut-help.closeLabel": "Sulge", "app.shortcut-help.closeDesc": "Sulgeb kiirklahvide akna", "app.shortcut-help.openOptions": "Ava valikud", - "app.shortcut-help.toggleUserList": "Ava/peida kasutajate nimekiri", - "app.shortcut-help.toggleMute": "Vaigista / Eemalda vaigistus", - "app.shortcut-help.togglePublicChat": "Ava/peida avalik sõnumivahetus (kasutajate nimekiri peab olema avatud)", + "app.shortcut-help.toggleUserList": "Ava / Peida kasutajate nimekiri", + "app.shortcut-help.toggleMute": "Vaigistus sisse / välja", + "app.shortcut-help.togglePublicChat": "Ava / Peida avalik vestus (kasutajate nimekiri peab olema avatud)", "app.shortcut-help.hidePrivateChat": "Peida privaatne vestlus", "app.shortcut-help.closePrivateChat": "Sulge privaatne vestlus", - "app.shortcut-help.openActions": "Ava tegevustemenüü", - "app.shortcut-help.raiseHand": "Lülita käetõstmist", + "app.shortcut-help.openActions": "Ava tegevuste menüü", + "app.shortcut-help.raiseHand": "Tõsta / Langeta käsi", "app.shortcut-help.openDebugWindow": "Ava silumisaken", - "app.shortcut-help.openStatus": "Ava staatusemenüü", + "app.shortcut-help.openStatus": "Ava olekumenüü", "app.shortcut-help.togglePan": "Aktiveeri liigutamistööriist (Esitleja)", "app.shortcut-help.toggleFullscreen": "Lülita täisekraani (Esitleja)", "app.shortcut-help.nextSlideDesc": "Järgmine slaid (Esitleja)", @@ -855,13 +855,13 @@ "app.shortcut-help.previousSlideKey": "Nool vasakule", "app.shortcut-help.select": "Vali tööriist", "app.shortcut-help.pencil": "Pliiats", - "app.shortcut-help.eraser": "Kustutuskumm", + "app.shortcut-help.eraser": "Kustutaja", "app.shortcut-help.rectangle": "Ristkülik", "app.shortcut-help.elipse": "Ellips", "app.shortcut-help.triangle": "Kolmnurk", "app.shortcut-help.line": "Joon", "app.shortcut-help.arrow": "Nool", - "app.shortcut-help.text": "Tekstitööriist", + "app.shortcut-help.text": "Tekst", "app.shortcut-help.note": "Kleepmärge", "app.shortcut-help.general": "Üldine", "app.shortcut-help.presentation": "Esitlus", @@ -886,47 +886,47 @@ "app.shortcut-help.delete": "Kustuta", "app.shortcut-help.duplicate": "Dubleeri", "app.lock-viewers.title": "Lukusta vaatajad", - "app.lock-viewers.description": "Need valikud võimaldavad keelata vaatajatel teatud võimalusi kasutada.", - "app.lock-viewers.featuresLable": "Võimalus", - "app.lock-viewers.lockStatusLabel": "Staatus", - "app.lock-viewers.webcamLabel": "Jaga veebikaamerat", - "app.lock-viewers.otherViewersWebcamLabel": "Näita teiste vaatajate veebikaameraid", - "app.lock-viewers.microphoneLable": "Jaga mikrofoni", - "app.lock-viewers.PublicChatLabel": "Saada avalikke sõnumeid", - "app.lock-viewers.PrivateChatLable": "Saada privaatseid sõnumeid", - "app.lock-viewers.notesLabel": "Muuda jagatud märkmeid", - "app.lock-viewers.userListLabel": "Näita teisi vaatajaid kasutajate nimekirjas", - "app.lock-viewers.ariaTitle": "Vaatajate seadete lukustamise aken", - "app.lock-viewers.button.apply": "Kinnita", + "app.lock-viewers.description": "Need valikud võimaldavad keelata vaatajatel teatud funktsioone kasutada.", + "app.lock-viewers.featuresLable": "Funktsioon", + "app.lock-viewers.lockStatusLabel": "Olek", + "app.lock-viewers.webcamLabel": "Veebikaamera jagamine", + "app.lock-viewers.otherViewersWebcamLabel": "Teiste vaatajate veebikaamerate nägemine", + "app.lock-viewers.microphoneLable": "Mikrofoni jagamine", + "app.lock-viewers.PublicChatLabel": "Avalike sõnumite saatmine", + "app.lock-viewers.PrivateChatLable": "Privaatsete sõnumite saatmine", + "app.lock-viewers.notesLabel": "Jagatud märkmete muutmine", + "app.lock-viewers.userListLabel": "Kasutajate nimekirjas teiste vaatajate nägemine", + "app.lock-viewers.ariaTitle": "Vaatajate lukustamise aken", + "app.lock-viewers.button.apply": "Rakenda", "app.lock-viewers.button.cancel": "Tühista", "app.lock-viewers.locked": "Lukustatud", - "app.lock-viewers.hideViewersCursor": "Näita teiste vaatajate kursoreid", - "app.guest-policy.ariaTitle": "Külaliste reeglite seadistamise aken", + "app.lock-viewers.hideViewersCursor": "Teiste vaatajate kursorite nägemine", + "app.guest-policy.ariaTitle": "Külaliste reeglite määramise aken", "app.guest-policy.title": "Külaliste reeglid", "app.guest-policy.description": "Muuda koosoleku külaliste reegleid", "app.guest-policy.button.askModerator": "Küsi moderaatorilt", "app.guest-policy.button.alwaysAccept": "Luba alati", "app.guest-policy.button.alwaysDeny": "Keela alati", "app.guest-policy.policyBtnDesc": "Määrab koosoleku külaliste reeglid", - "app.connection-status.ariaTitle": "Ühenduse staatuse aken", - "app.connection-status.title": "Ühenduse staatus", - "app.connection-status.description": "Vaata kasutajate ühenduse staatust", - "app.connection-status.empty": "Ühendusprobleemide teavitused praegu puuduvad", + "app.connection-status.ariaTitle": "Ühenduse oleku aken", + "app.connection-status.title": "Ühenduse olek", + "app.connection-status.description": "Vaata kasutajate ühenduse olekut", + "app.connection-status.empty": "Ühendusprobleemidest praegu teatatud ei ole", "app.connection-status.more": "veel", "app.connection-status.copy": "Kopeeri statistika", "app.connection-status.copied": "Kopeeritud!", "app.connection-status.jitter": "Värin", - "app.connection-status.label": "Ühenduse staatus", - "app.connection-status.settings": "Seadete kohandamine", + "app.connection-status.label": "Ühenduse olek", + "app.connection-status.settings": "Sätete kohandamine", "app.connection-status.no": "Ei", "app.connection-status.notification": "Avastati sinu ühenduse kadumine", - "app.connection-status.offline": "väljas", + "app.connection-status.offline": "ühenduseta", "app.connection-status.clientNotRespondingWarning": "Klient ei vasta", "app.connection-status.audioUploadRate": "Audio üleslaadimise kiirus", "app.connection-status.audioDownloadRate": "Audio allalaadimise kiirus", "app.connection-status.videoUploadRate": "Video üleslaadimise kiirus", "app.connection-status.videoDownloadRate": "Video allalaadimise kiirus", - "app.connection-status.lostPackets": "Kadunud paketid", + "app.connection-status.lostPackets": "Kadunud pakette", "app.connection-status.usingTurn": "Kasutatakse TURNi", "app.connection-status.yes": "Jah", "app.connection-status.connectionStats": "Ühenduse statistika", @@ -940,8 +940,8 @@ "app.recording.startTitle": "Alusta salvestamist", "app.recording.stopTitle": "Peata salvestamine", "app.recording.resumeTitle": "Jätka salvestamist", - "app.recording.startDescription": "Salvestamise nuppu uuesti vajutades saad salvestamise peatada.", - "app.recording.stopDescription": "Kas oled kindel, et soovid salvestamise peatada? Salvestamise nuppu uuesti vajutades saad salvestamist jätkata.", + "app.recording.startDescription": "Salvestamise saad peatada salvestamisnuppu uuesti vajutades.", + "app.recording.stopDescription": "Kas oled kindel, et soovid salvestamise peatada? Salvestamist saad jätkata salvestamisnuppu uuesti vajutades.", "app.recording.notify.title": "Salvestamine algas", "app.recording.notify.description": "Sessiooni ülejäänud osa kohta tekib salvestis", "app.recording.notify.continue": "Jätka", @@ -956,46 +956,46 @@ "app.videoPreview.quality.hd": "Kõrglahutusega", "app.videoPreview.cancelLabel": "Tühista", "app.videoPreview.closeLabel": "Sulge", - "app.videoPreview.findingWebcamsLabel": "Otsime veebikaameraid", + "app.videoPreview.findingWebcamsLabel": "Veebikaamerate otsimine", "app.videoPreview.startSharingLabel": "Alusta jagamist", "app.videoPreview.stopSharingLabel": "Lõpeta jagamine", "app.videoPreview.stopSharingAllLabel": "Lõpeta kõik", "app.videoPreview.sharedCameraLabel": "Seda kaamerat juba jagatakse", "app.videoPreview.webcamOptionLabel": "Vali veebikaamera", "app.videoPreview.webcamPreviewLabel": "Veebikaamera eelvaade", - "app.videoPreview.webcamSettingsTitle": "Veebikaamera seaded", + "app.videoPreview.webcamSettingsTitle": "Veebikaamera sätted", "app.videoPreview.webcamEffectsTitle": "Veebikaamera visuaalefektid", - "app.videoPreview.webcamVirtualBackgroundLabel": "Virtuaalse tausta seaded", - "app.videoPreview.webcamVirtualBackgroundDisabledLabel": "See seade ei toeta virtuaalseid taustu", + "app.videoPreview.webcamVirtualBackgroundLabel": "Virtuaalse tausta sätted", + "app.videoPreview.webcamVirtualBackgroundDisabledLabel": "See seade ei toeta virtuaalset tausta", "app.videoPreview.webcamNotFoundLabel": "Veebikaamerat ei leitud", - "app.videoPreview.profileNotFoundLabel": "Puudub toetatud kaameraprofiil", + "app.videoPreview.profileNotFoundLabel": "Toetatud kaameraprofiil puudub", "app.videoPreview.brightness": "Heledus", - "app.videoPreview.wholeImageBrightnessLabel": "Terve pilt", - "app.videoPreview.wholeImageBrightnessDesc": "Rakendab heledust kaamerapildile ja taustapildile", - "app.videoPreview.sliderDesc": "Suurenda või vähenda heldust", + "app.videoPreview.wholeImageBrightnessLabel": "Kogu pilt", + "app.videoPreview.wholeImageBrightnessDesc": "Rakendab heledust kaamera- ja taustapildile", + "app.videoPreview.sliderDesc": "Suurenda või vähenda heledust", "app.video.joinVideo": "Jaga veebikaamerat", "app.video.connecting": "Veebikaamera jagamine käivitub...", "app.video.leaveVideo": "Lõpeta veebikaamera jagamine", - "app.video.videoSettings": "Videoseaded", + "app.video.videoSettings": "Videosätted", "app.video.visualEffects": "Visuaalefektid", - "app.video.advancedVideo": "Ava täpsemad seaded", + "app.video.advancedVideo": "Ava täpsemad sätted", "app.video.iceCandidateError": "Viga ICE kandidaadi lisamisel", - "app.video.iceConnectionStateError": "Ühendus ebaõnnestus (ICE viga 1107)", + "app.video.iceConnectionStateError": "Ühenduse tõrge (ICE viga 1107)", "app.video.permissionError": "Viga veebikaamera jagamisel. Palun kontrolli õigusi", "app.video.sharingError": "Viga veebikaamera jagamisel", - "app.video.abortError": "Tekkis tundmatu viga, mis tõkestas veebikaamera kasutamise", - "app.video.overconstrainedError": "Kaamera ei toeta seda kvaliteediprofiili.", + "app.video.abortError": "Tekkis tundmatu viga, mis takistas veebikaamera kasutamist", + "app.video.overconstrainedError": "Kaamera ei toeta seda kvaliteediprofiili", "app.video.securityError": "Brauser keelas kaamera kasutamise. Proovi mõnda teist brauserit", "app.video.typeError": "Kaamera kvaliteediprofiil on sobimatu. Võta ühendust administraatoriga", - "app.video.notFoundError": "Veebikaamerat ei leitud. Palun kontrolli, kas see on ühendatud", - "app.video.notAllowed": "Veebikaamera jagamiseks puuduvad õigused. Palun kontrolli veebilehitsejas jagamisõigusi", - "app.video.notSupportedError": "Veebikaamera videot saab jagada vaid üle turvalise ühenduse. Veendu, et su SSL sertifikaat on kehtiv.", - "app.video.notReadableError": "Veebikaamera videot ei õnnestunud kätte saada. Palun kontrolli, et ükski teine programm veebikaamerat samal ajal ei kasuta", + "app.video.notFoundError": "Veebikaamerat ei leitud. Palun veendu, et see on ühendatud", + "app.video.notAllowed": "Veebikaamerale juurdepääsuks peab olema antud luba.", + "app.video.notSupportedError": "Brauserit ei toetata. Proovi uuesti muu brauseri või seadmega.", + "app.video.notReadableError": "Veebikaamera videot ei õnnestunud kätte saada. Palun veendu, et veebikaamerat ei kasuta mõni muu programm", "app.video.timeoutError": "Brauser ei vastanud õigeaegselt.", "app.video.genericError": "Seadmega tekkis tundmatu viga ({0})", - "app.video.inactiveError": "Su veebikaamera peatus ootamatult. Palun vaata üle brauseri õigused", + "app.video.inactiveError": "Su veebikaamera peatus ootamatult. Palun kontrolli brauseri õigusi", "app.video.mediaTimedOutError": "Veebikaamera edastusvoog on katkenud. Proovi seda uuesti jagada", - "app.video.mediaFlowTimeout1020": "Meedia ei saanud serveriga ühendust (viga 1020)", + "app.video.mediaFlowTimeout1020": "Meedia ei saanud ühendust serveriga (viga 1020)", "app.video.suggestWebcamLock": "Kas jõustada vaatajate veebikaamerate lukustamine?", "app.video.suggestWebcamLockReason": "(see parandab koosoleku stabiilsust)", "app.video.enable": "Luba", @@ -1005,7 +1005,7 @@ "app.video.videoLocked": "Veebikaamerate jagamine on lukustatud", "app.video.videoButtonDesc": "Jaga veebikaamerat", "app.video.videoMenu": "Videomenüü", - "app.video.videoMenuDisabled": "Veebikaamera videomenüü on seadetes välja lülitatud", + "app.video.videoMenuDisabled": "Veebikaamera videomenüü on sätetes välja lülitatud", "app.video.videoMenuDesc": "Ava videomenüü", "app.video.pagination.prevPage": "Vaata eelmisi videoid", "app.video.pagination.nextPage": "Vaata järgmisi videoid", @@ -1020,7 +1020,7 @@ "app.video.virtualBackground.custom": "Laadi üles arvutist", "app.video.virtualBackground.remove": "Eemalda lisatud pilt", "app.video.virtualBackground.genericError": "Kaameraefekti rakendamine ebaõnnestus. Proovi uuesti.", - "app.video.virtualBackground.camBgAriaDesc": "Määrab {0} veebikaamera virtuaalseks taustaks", + "app.video.virtualBackground.camBgAriaDesc": "Veebikaamera virtuaalseks taustaks määratakse {0}", "app.video.virtualBackground.maximumFileSizeExceeded": "Maksimaalne failisuurus ületatud. ({0}MB)", "app.video.virtualBackground.typeNotAllowed": "Failitüüp pole lubatud.", "app.video.virtualBackground.errorOnRead": "Faili lugemisel läks midagi valesti.", @@ -1031,24 +1031,24 @@ "app.video.meetingCamCapReached": "Koosoleku samaaegsete kaamerate piir on saavutatud", "app.video.dropZoneLabel": "Aseta siia", "app.fullscreenButton.label": "Laienda {0} täisekraanile", - "app.fullscreenUndoButton.label": "Loobu {0} täisekraanist", + "app.fullscreenUndoButton.label": "Lõpeta {0} täisekraan", "app.switchButton.expandLabel": "Laienda ekraanijagamise videot", - "app.switchButton.shrinkLabel": "Ahenda ekraanijagamise videot", + "app.switchButton.shrinkLabel": "Kahanda ekraanijagamise videot", "app.sfu.mediaServerConnectionError2000": "Meediaserveriga ei saa ühendust (viga 2000)", - "app.sfu.mediaServerOffline2001": "Meediaserver ei vasta. Palun proovi hiljem uuesti (viga 2001)", + "app.sfu.mediaServerOffline2001": "Meediaserver on võrgust väljas. Palun proovi hiljem uuesti (viga 2001)", "app.sfu.mediaServerNoResources2002": "Meediaserveril ei ole vabu ressursse (viga 2002)", "app.sfu.mediaServerRequestTimeout2003": "Meediaserveri päringud aeguvad (viga 2003)", "app.sfu.serverIceGatheringFailed2021": "Meediaserver ei saa koguda ühendusekandidaate (ICE viga 2021)", "app.sfu.serverIceGatheringFailed2022": "Meediaserveriga ühendumine ebaõnnestus (ICE viga 2022)", "app.sfu.mediaGenericError2200": "Meediaserver ei suutnud päringut töödelda (viga 2200)", "app.sfu.invalidSdp2202":"Klient genereeris vigase meediapäringu (SDP viga 2202)", - "app.sfu.noAvailableCodec2203": "Server ei leidnud sobivat koodekit (viga 2203)", - "app.meeting.endNotification.ok.label": "Ok", + "app.sfu.noAvailableCodec2203": "Server ei suutnud leida sobivat koodekit (viga 2203)", + "app.meeting.endNotification.ok.label": "OK", "app.whiteboard.annotations.poll": "Küsitluse tulemused avaldati", "app.whiteboard.annotations.pollResult": "Küsitluse tulemus", "app.whiteboard.annotations.noResponses": "Vastuseid pole", "app.whiteboard.annotations.notAllowed": "Sul ei ole lubatud seda muudatust teha", - "app.whiteboard.annotations.numberExceeded": "Märkmete arv ületas piirväärtuse ({0})", + "app.whiteboard.annotations.numberExceeded": "Märgete arv ületas piirväärtuse ({0})", "app.whiteboard.toolbar.tools": "Tööriistad", "app.whiteboard.toolbar.tools.hand": "Liiguta", "app.whiteboard.toolbar.tools.pencil": "Pliiats", @@ -1066,31 +1066,31 @@ "app.whiteboard.toolbar.color.red": "Punane", "app.whiteboard.toolbar.color.orange": "Oranž", "app.whiteboard.toolbar.color.eletricLime": "Kollakasroheline", - "app.whiteboard.toolbar.color.lime": "Heleroheline", + "app.whiteboard.toolbar.color.lime": "Roheline", "app.whiteboard.toolbar.color.cyan": "Tsüaansinine", "app.whiteboard.toolbar.color.dodgerBlue": "Dodgeri sinine", "app.whiteboard.toolbar.color.blue": "Sinine", "app.whiteboard.toolbar.color.violet": "Violetne", "app.whiteboard.toolbar.color.magenta": "Magenta", "app.whiteboard.toolbar.color.silver": "Hõbe", - "app.whiteboard.toolbar.undo": "Võta märge tagasi", - "app.whiteboard.toolbar.clear": "Kustuta kõik märkmed", - "app.whiteboard.toolbar.clearConfirmation": "Kas soovid kõik märkmed kustutada?", + "app.whiteboard.toolbar.undo": "Tühista märge", + "app.whiteboard.toolbar.clear": "Kustuta kõik märked", + "app.whiteboard.toolbar.clearConfirmation": "Kas soovid kõik märked kustutada?", "app.whiteboard.toolbar.multiUserOn": "Lülita mitme kasutaja tahvel sisse", "app.whiteboard.toolbar.multiUserOff": "Lülita mitme kasutaja tahvel välja", "app.whiteboard.toolbar.palmRejectionOn": "Lülita käelaba hülgamine sisse", "app.whiteboard.toolbar.palmRejectionOff": "Lülita käelaba hülgamine välja", - "app.whiteboard.toolbar.fontSize": "Tekstisuuruste nimikiri", + "app.whiteboard.toolbar.fontSize": "Kirjasuuruste loetelu", "app.whiteboard.toolbarAriaLabel": "Esitluse tööriistad", - "app.feedback.title": "Oled konverentsist välja logitud", - "app.feedback.subtitle": "Tahaksime teada, mida arvad BigBlueButtonist (vabatahtlik)", + "app.feedback.title": "Oled koosolekult välja logitud", + "app.feedback.subtitle": "Sooviksime teada, mida BigBlueButtonist arvad (vabatahtlik)", "app.feedback.textarea": "Kuidas saaksime BigBlueButtonit paremaks muuta?", - "app.feedback.sendFeedback": "Saada tagasisidet", + "app.feedback.sendFeedback": "Saada tagasiside", "app.feedback.sendFeedbackDesc": "Saada tagasiside ning lahku koosolekult", "app.videoDock.webcamMirrorLabel": "Peegelda", "app.videoDock.webcamMirrorDesc": "Peegelda valitud veebikaamerat", - "app.videoDock.webcamFocusLabel": "Anna fookus", - "app.videoDock.webcamFocusDesc": "Anna valitud veebikaamerale fookus", + "app.videoDock.webcamFocusLabel": "Sea fookusesse", + "app.videoDock.webcamFocusDesc": "Sea valitud veebikaamera fookusesse", "app.videoDock.webcamUnfocusLabel": "Lõpeta fookus", "app.videoDock.webcamUnfocusDesc": "Lõpeta valitud veebikaamera fookus", "app.videoDock.webcamPinLabel": "Kinnita", @@ -1098,19 +1098,19 @@ "app.videoDock.webcamFullscreenLabel": "Veebikaamera täisekraanil", "app.videoDock.webcamSqueezedButtonLabel": "Veebikaamera valikud", "app.videoDock.webcamUnpinLabel": "Vabasta", - "app.videoDock.webcamUnpinLabelDisabled": "Kasutajat saab vabastada ainult moderaator", + "app.videoDock.webcamUnpinLabelDisabled": "Veebikaameraid saab vabastada ainult moderaator", "app.videoDock.webcamUnpinDesc": "Vabasta valitud veebikaamera", "app.videoDock.autoplayBlockedDesc": "Vajame sinu luba, et näidata sulle teiste kasutajate veebikaameraid.", "app.videoDock.autoplayAllowLabel": "Vaata veebikaameraid", "app.createBreakoutRoom.title": "Eraldatud ruumid", "app.createBreakoutRoom.ariaTitle": "Peida eraldatud ruumid", "app.createBreakoutRoom.breakoutRoomLabel": "Eraldatud ruumid {0}", - "app.createBreakoutRoom.askToJoin": "Palu liituda", - "app.createBreakoutRoom.generatingURL": "Lingi loomine", - "app.createBreakoutRoom.generatingURLMessage": "Ühinemise lingi loomine valitud eraldatud ruumi jaoks. See võib võtta paar sekundit...", + "app.createBreakoutRoom.askToJoin": "Liitu ruumiga", + "app.createBreakoutRoom.generatingURL": "URLi loomine", + "app.createBreakoutRoom.generatingURLMessage": "Liitumise URLi loomine valitud eraldatud ruumi jaoks. See võib võtta paar sekundit...", "app.createBreakoutRoom.duration": "Kestus {0}", "app.createBreakoutRoom.room": "Ruum {0}", - "app.createBreakoutRoom.notAssigned": "Pole määratud ({0})", + "app.createBreakoutRoom.notAssigned": "Jaotamata ({0})", "app.createBreakoutRoom.join": "Liitu ruumiga", "app.createBreakoutRoom.joinAudio": "Liitu audioga", "app.createBreakoutRoom.returnAudio": "Taasta audio", @@ -1123,37 +1123,37 @@ "app.createBreakoutRoom.randomlyAssignDesc": "Jaotab osalejad eraldatud ruumidesse juhuslikult", "app.createBreakoutRoom.resetAssignments": "Lähtesta jaotamine", "app.createBreakoutRoom.resetAssignmentsDesc": "Lähtesta kõik kasutajate ruumidesse jaotamised", - "app.createBreakoutRoom.endAllBreakouts": "Sulge kõik eraldatud ruumid", + "app.createBreakoutRoom.endAllBreakouts": "Lõpeta kõik eraldatud ruumid", "app.createBreakoutRoom.chatTitleMsgAllRooms": "kõik ruumid", "app.createBreakoutRoom.msgToBreakoutsSent": "Sõnum saadeti {0} eraldatud ruumi", "app.createBreakoutRoom.roomName": "{0} (Ruum - {1})", "app.createBreakoutRoom.doneLabel": "Valmis", "app.createBreakoutRoom.nextLabel": "Järgmine", - "app.createBreakoutRoom.minusRoomTime": "Vähenda eraldatud ruumi aega:", - "app.createBreakoutRoom.addRoomTime": "Suurenda eraldatud ruumi aega:", + "app.createBreakoutRoom.minusRoomTime": "Vähenda eraldatud ruumi aega kuni:", + "app.createBreakoutRoom.addRoomTime": "Suurenda eraldatud ruumi aega kuni:", "app.createBreakoutRoom.addParticipantLabel": "+ Lisa osaleja", "app.createBreakoutRoom.freeJoin": "Luba kasutajatel ise valida eraldatud ruum, millega liituda", - "app.createBreakoutRoom.captureNotes": "Jäädvusta jagatud märkmed pärast eraldatud ruumide lõppemist", - "app.createBreakoutRoom.captureSlides": "Jäädvusta tahvel pärast eraldatud ruumide lõppemist", - "app.createBreakoutRoom.leastOneWarnBreakout": "Eraldatud ruumi peab paigutama vähemalt ühe kasutaja.", + "app.createBreakoutRoom.captureNotes": "Salvesta jagatud märkmed, kui eraldatud ruumid lõpevad", + "app.createBreakoutRoom.captureSlides": "Salvesta tahvel, kui eraldatud ruumid lõpevad", + "app.createBreakoutRoom.leastOneWarnBreakout": "Tuleb paigutada vähemalt üks kasutaja eraldatud ruumi.", "app.createBreakoutRoom.minimumDurationWarnBreakout": "Eraldatud ruumi vähim kestus on {0} minutit.", - "app.createBreakoutRoom.modalDesc": "Soovitus: kasutajate nimesid saab hiirega tirida soovitud eraldatud ruumidesse.", + "app.createBreakoutRoom.modalDesc": "Vihje: kasutajate nimesid saab hiirega lohistada soovitud eraldatud ruumidesse.", "app.createBreakoutRoom.roomTime": "{0} minutit", "app.createBreakoutRoom.numberOfRoomsError": "Ruumide arv ei sobi.", - "app.createBreakoutRoom.duplicatedRoomNameError": "Ruumi nimi ei tohi korduda.", + "app.createBreakoutRoom.duplicatedRoomNameError": "Ruumi nimi ei või korduda.", "app.createBreakoutRoom.emptyRoomNameError": "Ruumi nimi ei või olla tühi.", "app.createBreakoutRoom.setTimeInMinutes": "Määra kestuseks (minutit)", "app.createBreakoutRoom.setTimeLabel": "Rakenda", "app.createBreakoutRoom.setTimeCancel": "Tühista", "app.createBreakoutRoom.setTimeHigherThanMeetingTimeError": "Eraldatud ruumide kestus ei või ületada koosoleku järelejäänud aega.", - "app.createBreakoutRoom.roomNameInputDesc": "Värskendab eraldatud ruumi nime", + "app.createBreakoutRoom.roomNameInputDesc": "Uuendab eraldatud ruumi nime", "app.createBreakoutRoom.movedUserLabel": "{0} üle viidud ruumi {1}", - "app.updateBreakoutRoom.modalDesc": "Kasutaja värskendamiseks või kutsumiseks lohista ta lihtsalt soovitud ruumi.", + "app.updateBreakoutRoom.modalDesc": "Kasutaja muutmiseks või kutsumiseks lohista ta lihtsalt soovitud ruumi.", "app.updateBreakoutRoom.cancelLabel": "Tühista", - "app.updateBreakoutRoom.title": "Värskenda eraldatud ruume", + "app.updateBreakoutRoom.title": "Uuenda eraldatud ruume", "app.updateBreakoutRoom.confirm": "Rakenda", "app.updateBreakoutRoom.userChangeRoomNotification": "Sind viidi üle ruumi {0}.", - "app.smartMediaShare.externalVideo": "Väline video", + "app.smartMediaShare.externalVideo": "Välised videod", "app.update.resetRoom": "Lähtesta kasutaja ruum", "app.externalVideo.start": "Jaga uut videot", "app.externalVideo.title": "Jaga välist videot", @@ -1164,20 +1164,20 @@ "app.externalVideo.autoPlayWarning": "Käivita video, et meedia sünkroniseerimine aktiveerida", "app.externalVideo.refreshLabel": "Värskenda videomängijat", "app.externalVideo.fullscreenLabel": "Videomängija", - "app.externalVideo.noteLabel": "Märkus: jagatud väliseid videoid sessiooni salvestisse ei kaasata. Toetatatakse keskkondi YouTube, Vimeo, Instructure Media, Twitch ja Daily Motion ning linke meediafailidele (nt https://example.com/xy.mp4).", + "app.externalVideo.noteLabel": "Märkus: jagatud väliseid videoid sessiooni salvestisse ei kaasata. Toetatatakse keskkondi YouTube, Vimeo, Instructure Media, Twitch ja Dailymotion ning meediafailide URL-e (nt https://example.com/xy.mp4).", "app.externalVideo.subtitlesOn": "Lülita välja", "app.externalVideo.subtitlesOff": "Lülita sisse (kui saadaval)", "app.actionsBar.actionsDropdown.shareExternalVideo": "Jaga välist videot", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Lõpeta välise video jagamine", - "app.legacy.unsupportedBrowser": "Paistab, et kasutad veebilehitsejat, mida ei toetata. Täielikuks toetuseks kasuta palun brauserit {0} või {1}.", - "app.legacy.upgradeBrowser": "Paistab, et kasutad toetatud veebilehitseja vanemat versiooni. Täielikuks toetuseks uuenda palun oma veebilehitsejat.", - "app.legacy.criosBrowser": "Täielikuks toetuseks kasuta palun iOSis Safarit.", + "app.legacy.unsupportedBrowser": "Paistab, et kasutad brauserit, mida ei toetata. Täielikult on toetatud näiteks {0} või {1}.", + "app.legacy.upgradeBrowser": "Paistab, et kasutad toetatud brauseri vanemat versiooni. Täielikuks toetuseks palun uuenda oma brauserit.", + "app.legacy.criosBrowser": "Täielikuks toetuseks kasuta iOSis palun Safarit.", "app.debugWindow.windowTitle": "Otsi vigu", - "app.debugWindow.form.userAgentLabel": "Kasutajaagent", + "app.debugWindow.form.userAgentLabel": "Kasutaja identifikaator", "app.debugWindow.form.button.copy": "Kopeeri", - "app.debugWindow.form.enableAutoarrangeLayoutLabel": "Lülita automaatpaigutamine sisse", + "app.debugWindow.form.enableAutoarrangeLayoutLabel": "Lülita sisse automaatne paigutamine", "app.debugWindow.form.enableAutoarrangeLayoutDescription": "(lülitub välja, kui veebikaamerate ala nihutada või selle suurust muuta)", - "app.debugWindow.form.chatLoggerLabel": "Vestluse logimistasemete test", + "app.debugWindow.form.chatLoggerLabel": "Testi vestluse logimistasemeid", "app.debugWindow.form.button.apply": "Rakenda", "app.layout.modal.title": "Paigutused", "app.layout.modal.confirm": "Kinnita", @@ -1185,7 +1185,7 @@ "app.layout.modal.layoutLabel": "Vali paigutus", "app.layout.modal.keepPushingLayoutLabel": "Rakenda paigutus kõigile", "app.layout.modal.pushLayoutLabel": "Rakenda kõigile", - "app.layout.modal.layoutToastLabel": "Paigutuse seaded muudetud", + "app.layout.modal.layoutToastLabel": "Paigutuse sätted muudetud", "app.layout.modal.layoutSingular": "Paigutus", "app.layout.modal.layoutBtnDesc": "Määrab paigutuse valitud valikuks", "app.layout.style.custom": "Kohandatud", @@ -1202,20 +1202,20 @@ "playback.button.fullscreen.aria": "Sisu täisekraanil", "playback.button.restore.aria": "Taasta sisu", "playback.button.search.aria": "Otsi", - "playback.button.section.aria": "Külgsektsioon", + "playback.button.section.aria": "Külgpaneel", "playback.button.swap.aria": "Vaheta sisu", - "playback.button.theme.aria": "Lülita teemat", + "playback.button.theme.aria": "Vaheta teemat", "playback.error.wrapper.aria": "Vigade ala", "playback.loader.wrapper.aria": "Laadija ala", "playback.player.wrapper.aria": "Mängija ala", "playback.player.about.modal.shortcuts.title": "Kiirklahvid", "playback.player.about.modal.shortcuts.alt": "Alt", "playback.player.about.modal.shortcuts.shift": "Shift", - "playback.player.about.modal.shortcuts.fullscreen": "Lülita täisekraaniks", + "playback.player.about.modal.shortcuts.fullscreen": "Lülita täisekraani", "playback.player.about.modal.shortcuts.play": "Esita/Peata", - "playback.player.about.modal.shortcuts.section": "Lülita külgsektsiooniks", - "playback.player.about.modal.shortcuts.seek.backward": "Otsi tagasisuunas", - "playback.player.about.modal.shortcuts.seek.forward": "Otsi edasisuunas", + "playback.player.about.modal.shortcuts.section": "Lülita külgpaneeli", + "playback.player.about.modal.shortcuts.seek.backward": "Keri tagasi", + "playback.player.about.modal.shortcuts.seek.forward": "Keri edasi", "playback.player.about.modal.shortcuts.skip.next": "Järgmine slaid", "playback.player.about.modal.shortcuts.skip.previous": "Eelmine slaid", "playback.player.about.modal.shortcuts.swap": "Vaheta sisu", @@ -1253,8 +1253,8 @@ "app.learningDashboard.indicators.activityScore": "Aktiivsusskoor", "app.learningDashboard.indicators.duration": "Kestus", "app.learningDashboard.userDetails.startTime": "Algusaeg", - "app.learningDashboard.userDetails.endTime": "Lõppaeg", - "app.learningDashboard.userDetails.joined": "Liitunud", + "app.learningDashboard.userDetails.endTime": "Lõpuaeg", + "app.learningDashboard.userDetails.joined": "Liitus", "app.learningDashboard.userDetails.category": "Kategooria", "app.learningDashboard.userDetails.average": "Keskmine", "app.learningDashboard.userDetails.activityPoints": "Aktiivsuspunktid", @@ -1262,7 +1262,7 @@ "app.learningDashboard.userDetails.response": "Vastus", "app.learningDashboard.userDetails.mostCommonAnswer": "Kõige sagedasem vastus", "app.learningDashboard.userDetails.anonymousAnswer": "Anonüümne küsitlus", - "app.learningDashboard.userDetails.talkTime": "Rääkimisaeg", + "app.learningDashboard.userDetails.talkTime": "Rääkimise aeg", "app.learningDashboard.userDetails.messages": "Sõnumid", "app.learningDashboard.userDetails.emojis": "Emojid", "app.learningDashboard.userDetails.raiseHands": "Tõstetud käed", @@ -1270,13 +1270,13 @@ "app.learningDashboard.userDetails.onlineIndicator": "{0} kohaloldud aeg", "app.learningDashboard.usersTable.title": "Ülevaade", "app.learningDashboard.usersTable.colOnline": "Kohaloldud aeg", - "app.learningDashboard.usersTable.colTalk": "Rääkimisaeg", + "app.learningDashboard.usersTable.colTalk": "Rääkimise aeg", "app.learningDashboard.usersTable.colWebcam": "Veebikaamera aeg", "app.learningDashboard.usersTable.colMessages": "Sõnumid", "app.learningDashboard.usersTable.colEmojis": "Emojid", "app.learningDashboard.usersTable.colRaiseHands": "Tõstetud käed", "app.learningDashboard.usersTable.colActivityScore": "Aktiivsusskoor", - "app.learningDashboard.usersTable.colStatus": "Staatus", + "app.learningDashboard.usersTable.colStatus": "Olek", "app.learningDashboard.usersTable.userStatusOnline": "Kohal", "app.learningDashboard.usersTable.userStatusOffline": "Väljas", "app.learningDashboard.usersTable.noUsers": "Pole veel kasutajaid", @@ -1290,7 +1290,7 @@ "app.learningDashboard.pollsTable.anonymousAnswer": "Anonüümne küsitlus (vastused viimases reas)", "app.learningDashboard.pollsTable.anonymousRowName": "Anonüümne", "app.learningDashboard.pollsTable.noPollsCreatedHeading": "Küsitlusi ei ole loodud", - "app.learningDashboard.pollsTable.noPollsCreatedMessage": "Tulemused ilmuvad sellesse loendisse niipea, kui küsitlus kasutajatele saadetakse.", + "app.learningDashboard.pollsTable.noPollsCreatedMessage": "Tulemused ilmuvad siia niipea, kui küsitlus kasutajatele saadetakse.", "app.learningDashboard.pollsTable.answerTotal": "Kokku", "app.learningDashboard.pollsTable.userLabel": "Kasutaja", "app.learningDashboard.statusTimelineTable.title": "Ajaskaala", @@ -1310,5 +1310,5 @@ "mobileApp.portals.drawerNavigation.button.label": "Portaalid", "mobileApp.portals.addPortalPopup.validation.emptyFields": "Nõutavad väljad", "mobileApp.portals.addPortalPopup.validation.portalNameAlreadyExists": "Nimi on juba kasutusel", - "mobileApp.portals.addPortalPopup.validation.urlInvalid": "Viga lehekülje laadimisel - kontrolli aadressi ja võrguühendust" + "mobileApp.portals.addPortalPopup.validation.urlInvalid": "Viga lehe laadimisel - kontrolli URLi ja võrguühendust" } From db8f90dafc96a8bad9e2afb1bc94576af3053b5e Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 10:40:15 -0300 Subject: [PATCH 095/252] Test commits of the PR --- .github/workflows/automated-tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index ff5b88790c..2f1dca2b24 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -91,6 +91,8 @@ jobs: - run: git status - run: echo $(git log -n 6) - run: echo $(git log -n 6 bigbluebutton-html5) + - run: echo $(git log -n 6 bbb-graphql-middleware) + - run: echo $(git log -n 6 bbb-learning-dashboard) - run: echo $(git log -1 bigbluebutton-html5) - run: echo "CACHE_KEY=$(git log -1 --format=%H -- bigbluebutton-html5)" >> $GITHUB_ENV - name: Cache bigbluebutton-html5 @@ -117,7 +119,7 @@ jobs: steps: - uses: actions/checkout@v3 with: - fetch-depth: 1000 # Fetch all history + fetch-depth: 500 # Fetch all history - run: echo "CACHE_FREESWITCH_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-core)" >> $GITHUB_ENV - run: echo "CACHE_FREESWITCH_SOUNDS_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-sounds)" >> $GITHUB_ENV - run: echo "CACHE_SOUNDS_KEY=$(curl -Is http://bigbluebutton.org/downloads/sounds.tar.gz | grep "Last-Modified" | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV From d188df13f048664d0c77d50d98d5f66109b7601a Mon Sep 17 00:00:00 2001 From: Anton B Date: Wed, 2 Aug 2023 10:48:30 -0300 Subject: [PATCH 096/252] test: add flaky flag to presentation tests --- .../playwright/presentation/presentation.spec.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-tests/playwright/presentation/presentation.spec.js b/bigbluebutton-tests/playwright/presentation/presentation.spec.js index 438971a13f..57f83e6a40 100644 --- a/bigbluebutton-tests/playwright/presentation/presentation.spec.js +++ b/bigbluebutton-tests/playwright/presentation/presentation.spec.js @@ -84,13 +84,15 @@ test.describe.parallel('Presentation', () => { }); // https://docs.bigbluebutton.org/2.6/release-tests.html#enabling-and-disabling-presentation-download-automated - test('Enable and disable original presentation download @ci', async ({ browser, context, page }, testInfo) => { + // flaky: update is needed due to changes made on https://github.com/bigbluebutton/bigbluebutton/pull/18411 + test('Enable and disable original presentation download @flaky', async ({ browser, context, page }, testInfo) => { const presentation = new Presentation(browser, context); await presentation.initPages(page); await presentation.enableAndDisablePresentationDownload(testInfo); }); - test('Send presentation in the current state (with annotations) to chat for downloading @ci', async ({ browser, context, page }, testInfo) => { + // flaky: update is needed due to changes made on https://github.com/bigbluebutton/bigbluebutton/pull/18411 + test('Send presentation in the current state (with annotations) to chat for downloading @flaky', async ({ browser, context, page }, testInfo) => { const presentation = new Presentation(browser, context); await presentation.initPages(page); await presentation.sendPresentationToDownload(testInfo); From 445192e372ca47b73bc8510ab7909cb4ab754716 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 10:49:37 -0300 Subject: [PATCH 097/252] Test commits of the PR --- .github/workflows/automated-tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 2f1dca2b24..9bfd44c7a5 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -86,12 +86,12 @@ jobs: steps: - uses: actions/checkout@v3 with: - fetch-depth: 5 # Fetch all history + fetch-depth: 1 # Fetch all history - run: ls - run: git status - run: echo $(git log -n 6) - run: echo $(git log -n 6 bigbluebutton-html5) - - run: echo $(git log -n 6 bbb-graphql-middleware) + - run: echo $(git log -n 6 akka-bbb-fsesl) - run: echo $(git log -n 6 bbb-learning-dashboard) - run: echo $(git log -1 bigbluebutton-html5) - run: echo "CACHE_KEY=$(git log -1 --format=%H -- bigbluebutton-html5)" >> $GITHUB_ENV @@ -119,7 +119,7 @@ jobs: steps: - uses: actions/checkout@v3 with: - fetch-depth: 500 # Fetch all history + fetch-depth: 0 # Fetch all history - run: echo "CACHE_FREESWITCH_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-core)" >> $GITHUB_ENV - run: echo "CACHE_FREESWITCH_SOUNDS_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-sounds)" >> $GITHUB_ENV - run: echo "CACHE_SOUNDS_KEY=$(curl -Is http://bigbluebutton.org/downloads/sounds.tar.gz | grep "Last-Modified" | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV From b7a9bb1a2822c0c322cb19453cd03817d966d5fa Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 10:55:52 -0300 Subject: [PATCH 098/252] Test commits of the PR --- .github/workflows/automated-tests.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 9bfd44c7a5..4ed67d2600 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -86,15 +86,15 @@ jobs: steps: - uses: actions/checkout@v3 with: - fetch-depth: 1 # Fetch all history + fetch-depth: 5 # Fetch all history - run: ls - run: git status - run: echo $(git log -n 6) - - run: echo $(git log -n 6 bigbluebutton-html5) - - run: echo $(git log -n 6 akka-bbb-fsesl) - - run: echo $(git log -n 6 bbb-learning-dashboard) + - run: echo $(git log --full-history -n 6 bigbluebutton-html5) + - run: echo $(git log --full-history -n 6 akka-bbb-fsesl) + - run: echo $(git log --full-history -n 6 bbb-learning-dashboard) - run: echo $(git log -1 bigbluebutton-html5) - - run: echo "CACHE_KEY=$(git log -1 --format=%H -- bigbluebutton-html5)" >> $GITHUB_ENV + - run: echo "CACHE_KEY=$(git log --full-history -1 --format=%H -- bigbluebutton-html5)" >> $GITHUB_ENV - name: Cache bigbluebutton-html5 id: cache-bigbluebutton-html5 uses: actions/cache@v3 @@ -119,7 +119,7 @@ jobs: steps: - uses: actions/checkout@v3 with: - fetch-depth: 0 # Fetch all history + fetch-depth: 1000 # Fetch all history - run: echo "CACHE_FREESWITCH_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-core)" >> $GITHUB_ENV - run: echo "CACHE_FREESWITCH_SOUNDS_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-sounds)" >> $GITHUB_ENV - run: echo "CACHE_SOUNDS_KEY=$(curl -Is http://bigbluebutton.org/downloads/sounds.tar.gz | grep "Last-Modified" | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV From f1746341de9d7fb59ad6e5be3e33057ebf88afd5 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 11:07:37 -0300 Subject: [PATCH 099/252] Test commits of the PR --- .github/workflows/automated-tests.yml | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 4ed67d2600..ce0c070d11 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -86,15 +86,8 @@ jobs: steps: - uses: actions/checkout@v3 with: - fetch-depth: 5 # Fetch all history - - run: ls - - run: git status - - run: echo $(git log -n 6) - - run: echo $(git log --full-history -n 6 bigbluebutton-html5) - - run: echo $(git log --full-history -n 6 akka-bbb-fsesl) - - run: echo $(git log --full-history -n 6 bbb-learning-dashboard) - - run: echo $(git log -1 bigbluebutton-html5) - - run: echo "CACHE_KEY=$(git log --full-history -1 --format=%H -- bigbluebutton-html5)" >> $GITHUB_ENV + fetch-depth: 0 # Fetch all history + - run: echo "CACHE_KEY=$(git log -1 --format=%H -- bigbluebutton-html5)" >> $GITHUB_ENV - name: Cache bigbluebutton-html5 id: cache-bigbluebutton-html5 uses: actions/cache@v3 @@ -119,7 +112,7 @@ jobs: steps: - uses: actions/checkout@v3 with: - fetch-depth: 1000 # Fetch all history + fetch-depth: 0 # Fetch all history - run: echo "CACHE_FREESWITCH_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-core)" >> $GITHUB_ENV - run: echo "CACHE_FREESWITCH_SOUNDS_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-sounds)" >> $GITHUB_ENV - run: echo "CACHE_SOUNDS_KEY=$(curl -Is http://bigbluebutton.org/downloads/sounds.tar.gz | grep "Last-Modified" | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV From baf4d689d77c3003f3b128360084c98d1ac55b38 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 11:43:07 -0300 Subject: [PATCH 100/252] Test commits of the PR --- .github/workflows/automated-tests.yml | 89 ++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 16 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index ce0c070d11..5651f76683 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -20,9 +20,22 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - - run: ./build/get_external_dependencies.sh - - run: ./build/setup.sh bbb-apps-akka - - run: tar cvf artifacts.tar artifacts/ + with: + fetch-depth: 0 # Fetch all history + - run: echo "CACHE_AKKA_APPS_KEY=$(git log -1 --format=%H -- akka-bbb-apps)" >> $GITHUB_ENV + - run: echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV + - name: Handle cache + id: cache-action + uses: actions/cache@v3 + with: + path: artifacts.tar + key: ${{ runner.os }}-bbb-apps-akka-${{ env.CACHE_AKKA_APPS_KEY }}-${{ env.CACHE_COMMON_MSG_KEY }} + - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} + name: Generate artifacts + run: | + ./build/get_external_dependencies.sh + ./build/setup.sh bbb-apps-akka + tar cvf artifacts.tar artifacts/ - name: Archive packages uses: actions/upload-artifact@v3 with: @@ -46,9 +59,26 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - - run: ./build/get_external_dependencies.sh - - run: ./build/setup.sh bbb-etherpad - - run: tar cvf artifacts.tar artifacts/ + with: + fetch-depth: 0 # Fetch all history + - run: echo "CACHE_ETHERPAD_VERSION_KEY=$(git log -1 --format=%H -- bbb-etherpad.placeholder.sh)" >> $GITHUB_ENV + - run: echo "CACHE_ETHERPAD_BUILD_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-etherpad)" >> $GITHUB_ENV + - run: echo "CACHE_URL1_KEY=$(curl -s https://api.github.com/repos/mconf/ep_pad_ttl/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV + - run: echo "CACHE_URL2_KEY=$(curl -s https://api.github.com/repos/alangecker/bbb-etherpad-plugin/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV + - run: echo "CACHE_URL3_KEY=$(curl -s https://api.github.com/repos/mconf/ep_redis_publisher/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV + - run: echo "CACHE_URL4_KEY=$(curl -s https://api.github.com/repos/alangecker/bbb-etherpad-skin/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV + - name: Handle cache + id: cache-action + uses: actions/cache@v3 + with: + path: artifacts.tar + key: ${{ runner.os }}-bbb-etherpad-${{ env.CACHE_ETHERPAD_VERSION_KEY }}-${{ env.CACHE_ETHERPAD_BUILD_KEY }}-${{ env.CACHE_URL1_KEY }}-${{ env.CACHE_URL2_KEY }}-${{ env.CACHE_URL3_KEY }}-${{ env.CACHE_URL4_KEY }} + - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} + name: Generate artifacts + run: | + ./build/get_external_dependencies.sh + ./build/setup.sh bbb-etherpad + tar cvf artifacts.tar artifacts/ - name: Archive packages uses: actions/upload-artifact@v3 with: @@ -59,9 +89,23 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - - run: ./build/get_external_dependencies.sh - - run: ./build/setup.sh bbb-web - - run: tar cvf artifacts.tar artifacts/ + with: + fetch-depth: 0 # Fetch all history + - run: echo "CACHE_BBB_WEB_KEY=$(git log -1 --format=%H -- bigbluebutton-web)" >> $GITHUB_ENV + - run: echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV + - run: echo "CACHE_COMMON_WEB_KEY=$(git log -1 --format=%H -- bbb-common-web)" >> $GITHUB_ENV + - name: Handle cache + id: cache-action + uses: actions/cache@v3 + with: + path: artifacts.tar + key: ${{ runner.os }}-bbb-web-${{ env.CACHE_BBB_WEB_KEY }}-${{ env.CACHE_COMMON_MSG_KEY }}-${{ env.CACHE_COMMON_WEB_KEY }} + - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} + name: Generate artifacts + run: | + ./build/get_external_dependencies.sh + ./build/setup.sh bbb-web + tar cvf artifacts.tar artifacts/ - name: Archive packages uses: actions/upload-artifact@v3 with: @@ -72,9 +116,22 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - - run: ./build/get_external_dependencies.sh - - run: ./build/setup.sh bbb-fsesl-akka - - run: tar cvf artifacts.tar artifacts/ + with: + fetch-depth: 0 # Fetch all history + - run: echo "CACHE_AKKA_FSESL_KEY=$(git log -1 --format=%H -- akka-bbb-fsesl)" >> $GITHUB_ENV + - run: echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV + - name: Handle cache + id: cache-action + uses: actions/cache@v3 + with: + path: artifacts.tar + key: ${{ runner.os }}-bbb-fsesl-akka-${{ env.CACHE_AKKA_FSESL_KEY }}-${{ env.CACHE_COMMON_MSG_KEY }} + - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} + name: Generate artifacts + run: | + ./build/get_external_dependencies.sh + ./build/setup.sh bbb-fsesl-akka + tar cvf artifacts.tar artifacts/ - name: Archive packages uses: actions/upload-artifact@v3 with: @@ -88,14 +145,14 @@ jobs: with: fetch-depth: 0 # Fetch all history - run: echo "CACHE_KEY=$(git log -1 --format=%H -- bigbluebutton-html5)" >> $GITHUB_ENV - - name: Cache bigbluebutton-html5 - id: cache-bigbluebutton-html5 + - name: Handle cache + id: cache-action uses: actions/cache@v3 with: path: artifacts.tar key: ${{ runner.os }}-bbb-html5-${{ env.CACHE_KEY }} - - if: ${{ steps.cache-bigbluebutton-html5.outputs.cache-hit != 'true' }} - name: Generate html5 artifacts + - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} + name: Generate artifacts run: | ./build/get_external_dependencies.sh ./build/setup.sh bbb-html5-nodejs From e0e68f3896a6eb4b36ff485cc8d62490e3d0272e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Wed, 2 Aug 2023 13:22:43 -0300 Subject: [PATCH 101/252] guest policy change toast --- .../waiting-users/guest-policy/component.jsx | 26 ++++++++++++++++--- bigbluebutton-html5/public/locales/en.json | 1 + 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/waiting-users/guest-policy/component.jsx b/bigbluebutton-html5/imports/ui/components/waiting-users/guest-policy/component.jsx index d4686677d2..1d24a705cb 100644 --- a/bigbluebutton-html5/imports/ui/components/waiting-users/guest-policy/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/waiting-users/guest-policy/component.jsx @@ -2,6 +2,7 @@ import React, { PureComponent } from 'react'; import { defineMessages, injectIntl } from 'react-intl'; import PropTypes from 'prop-types'; import Styled from './styles'; +import { notify } from '/imports/ui/services/notification'; const ASK_MODERATOR = 'ASK_MODERATOR'; const ALWAYS_ACCEPT = 'ALWAYS_ACCEPT'; @@ -36,6 +37,10 @@ const intlMessages = defineMessages({ id: 'app.guest-policy.button.alwaysDeny', description: 'Always deny button label', }, + feedbackMessage: { + id: 'app.guest-policy.feedbackMessage', + description: 'Feedback message for guest policy change', + }, }); const propTypes = { @@ -47,18 +52,31 @@ const propTypes = { }; class GuestPolicyComponent extends PureComponent { + constructor(props) { + super(props); + + this.handleChangePolicy = this.handleChangePolicy.bind(this); + } + componentWillUnmount() { const { setIsOpen } = this.props; setIsOpen(false); } + handleChangePolicy(policyRule, messageId) { + const { intl, changeGuestPolicy } = this.props; + + changeGuestPolicy(policyRule); + + notify(intl.formatMessage(intlMessages.feedbackMessage) + intl.formatMessage(messageId), 'success'); + } + render() { const { setIsOpen, intl, guestPolicy, - changeGuestPolicy, isOpen, onRequestClose, priority, @@ -91,7 +109,7 @@ class GuestPolicyComponent extends PureComponent { aria-pressed={guestPolicy === ASK_MODERATOR} data-test="askModerator" onClick={() => { - changeGuestPolicy(ASK_MODERATOR); + this.handleChangePolicy(ASK_MODERATOR, intlMessages.askModerator); setIsOpen(false); }} /> @@ -103,7 +121,7 @@ class GuestPolicyComponent extends PureComponent { aria-pressed={guestPolicy === ALWAYS_ACCEPT} data-test="alwaysAccept" onClick={() => { - changeGuestPolicy(ALWAYS_ACCEPT); + this.handleChangePolicy(ALWAYS_ACCEPT, intlMessages.alwaysAccept); setIsOpen(false); }} /> @@ -115,7 +133,7 @@ class GuestPolicyComponent extends PureComponent { aria-pressed={guestPolicy === ALWAYS_DENY} data-test="alwaysDeny" onClick={() => { - changeGuestPolicy(ALWAYS_DENY); + this.handleChangePolicy(ALWAYS_DENY, intlMessages.alwaysDeny); setIsOpen(false); }} /> diff --git a/bigbluebutton-html5/public/locales/en.json b/bigbluebutton-html5/public/locales/en.json index ee4d068ce3..17a99afa8b 100755 --- a/bigbluebutton-html5/public/locales/en.json +++ b/bigbluebutton-html5/public/locales/en.json @@ -968,6 +968,7 @@ "app.guest-policy.button.alwaysAccept": "Always accept", "app.guest-policy.button.alwaysDeny": "Always deny", "app.guest-policy.policyBtnDesc": "Sets meeting guest policy", + "app.guest-policy.feedbackMessage": "The guest policy is now: ", "app.connection-status.ariaTitle": "Connection status modal", "app.connection-status.title": "Connection status", "app.connection-status.description": "View users' connection status", From fa5aa182fe626af74757fd29630ce76d7eacb4be Mon Sep 17 00:00:00 2001 From: Daniel Molkentin Date: Wed, 2 Aug 2023 16:13:46 +0200 Subject: [PATCH 102/252] fix: do not escape text twice The refactoring in 838accf0158f1ecf0fdf60c6319d200a98ab9106 incorrectly replaced the wrong parseMessage function in addBulkGroupChatMsgs.js This bug is only triggered when the option public.chat.bufferChatInsertsMs != 0. --- .../api/group-chat-msg/server/modifiers/addBulkGroupChatMsgs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addBulkGroupChatMsgs.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addBulkGroupChatMsgs.js index 262e3a3f7b..372bfa0034 100644 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addBulkGroupChatMsgs.js +++ b/bigbluebutton-html5/imports/api/group-chat-msg/server/modifiers/addBulkGroupChatMsgs.js @@ -2,7 +2,7 @@ import { GroupChatMsg } from '/imports/api/group-chat-msg'; import GroupChat from '/imports/api/group-chat'; import Logger from '/imports/startup/server/logger'; import flat from 'flat'; -import { parseMessage } from '/imports/api/common/server/helpers'; +import { parseMessage } from './addGroupChatMsg'; export default async function addBulkGroupChatMsgs(msgs) { if (!msgs.length) return; From b699ac06cf39e4aa06cfa88fbb0997489360ea0a Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 17:23:44 -0300 Subject: [PATCH 103/252] Split more steps in different jobs --- .github/workflows/automated-tests.yml | 40 +++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 5651f76683..c5a10e3933 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -55,6 +55,32 @@ jobs: name: artifacts_bbb-config.tar path: | artifacts.tar + build-bbb-export-annotations: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - run: ./build/get_external_dependencies.sh + - run: ./build/setup.sh bbb-export-annotations + - run: tar cvf artifacts.tar artifacts/ + - name: Archive packages + uses: actions/upload-artifact@v3 + with: + name: artifacts_bbb-export-annotations.tar + path: | + artifacts.tar + build-bbb-learning-dashboard: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - run: ./build/get_external_dependencies.sh + - run: ./build/setup.sh bbb-learning-dashboard + - run: tar cvf artifacts.tar artifacts/ + - name: Archive packages + uses: actions/upload-artifact@v3 + with: + name: artifacts_bbb-learning-dashboard.tar + path: | + artifacts.tar build-bbb-etherpad: runs-on: ubuntu-20.04 steps: @@ -197,8 +223,6 @@ jobs: steps: - uses: actions/checkout@v3 - run: ./build/get_external_dependencies.sh - - run: ./build/setup.sh bbb-export-annotations - - run: ./build/setup.sh bbb-learning-dashboard - run: ./build/setup.sh bbb-libreoffice-docker - run: ./build/setup.sh bbb-mkclean - run: ./build/setup.sh bbb-pads @@ -231,7 +255,7 @@ jobs: # mv artifacts /home/runner/work/bigbluebutton/bigbluebutton/artifacts/ # EOF install-and-run-tests: - needs: [build-bbb-apps-akka, build-bbb-config, build-bbb-etherpad, build-bbb-bbb-web, build-bbb-fsesl-akka, build-bbb-html5, build-bbb-freeswitch, build-others] + needs: [build-bbb-apps-akka, build-bbb-config, build-export-annotations, build-learning-dashboard, build-bbb-etherpad, build-bbb-bbb-web, build-bbb-fsesl-akka, build-bbb-html5, build-bbb-freeswitch, build-others] runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 @@ -246,6 +270,16 @@ jobs: with: name: artifacts_bbb-config.tar - run: tar xf artifacts.tar + - name: Download artifacts_export-annotations + uses: actions/download-artifact@v3 + with: + name: artifacts_export-annotations.tar + - run: tar xf artifacts.tar + - name: Download artifacts_learning-dashboard + uses: actions/download-artifact@v3 + with: + name: artifacts_learning-dashboard.tar + - run: tar xf artifacts.tar - name: Download artifacts_bbb-etherpad uses: actions/download-artifact@v3 with: From 12084a64c974bd7d2d5237665d14b11ac5673aac Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 17:41:53 -0300 Subject: [PATCH 104/252] Split more steps in different jobs --- .github/workflows/automated-tests.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index c5a10e3933..2df3de5cda 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -255,7 +255,7 @@ jobs: # mv artifacts /home/runner/work/bigbluebutton/bigbluebutton/artifacts/ # EOF install-and-run-tests: - needs: [build-bbb-apps-akka, build-bbb-config, build-export-annotations, build-learning-dashboard, build-bbb-etherpad, build-bbb-bbb-web, build-bbb-fsesl-akka, build-bbb-html5, build-bbb-freeswitch, build-others] + needs: [build-bbb-apps-akka, build-bbb-config, build-bbb-export-annotations, build-bbb-learning-dashboard, build-bbb-etherpad, build-bbb-bbb-web, build-bbb-fsesl-akka, build-bbb-html5, build-bbb-freeswitch, build-others] runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 @@ -270,15 +270,15 @@ jobs: with: name: artifacts_bbb-config.tar - run: tar xf artifacts.tar - - name: Download artifacts_export-annotations + - name: Download artifacts_bbb-export-annotations uses: actions/download-artifact@v3 with: - name: artifacts_export-annotations.tar + name: artifacts_bbb-export-annotations.tar - run: tar xf artifacts.tar - - name: Download artifacts_learning-dashboard + - name: Download artifacts_bbb-learning-dashboard uses: actions/download-artifact@v3 with: - name: artifacts_learning-dashboard.tar + name: artifacts_bbb-learning-dashboard.tar - run: tar xf artifacts.tar - name: Download artifacts_bbb-etherpad uses: actions/download-artifact@v3 From e73266606c3343ed56763db7ae8e32320ae07e04 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 17:51:48 -0300 Subject: [PATCH 105/252] Split more steps in different jobs --- .github/workflows/automated-tests.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 2df3de5cda..dc5b8847e3 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -81,6 +81,19 @@ jobs: name: artifacts_bbb-learning-dashboard.tar path: | artifacts.tar + build-bbb-libreoffice-docker: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - run: ./build/get_external_dependencies.sh + - run: ./build/setup.sh bbb-libreoffice-docker + - run: tar cvf artifacts.tar artifacts/ + - name: Archive packages + uses: actions/upload-artifact@v3 + with: + name: artifacts_bbb-libreoffice-docker.tar + path: | + artifacts.tar build-bbb-etherpad: runs-on: ubuntu-20.04 steps: @@ -223,7 +236,6 @@ jobs: steps: - uses: actions/checkout@v3 - run: ./build/get_external_dependencies.sh - - run: ./build/setup.sh bbb-libreoffice-docker - run: ./build/setup.sh bbb-mkclean - run: ./build/setup.sh bbb-pads - run: ./build/setup.sh bbb-playback @@ -255,7 +267,7 @@ jobs: # mv artifacts /home/runner/work/bigbluebutton/bigbluebutton/artifacts/ # EOF install-and-run-tests: - needs: [build-bbb-apps-akka, build-bbb-config, build-bbb-export-annotations, build-bbb-learning-dashboard, build-bbb-etherpad, build-bbb-bbb-web, build-bbb-fsesl-akka, build-bbb-html5, build-bbb-freeswitch, build-others] + needs: [build-bbb-apps-akka, build-bbb-config, build-bbb-export-annotations, build-bbb-learning-dashboard, build-bbb-libreoffice-docker, build-bbb-etherpad, build-bbb-bbb-web, build-bbb-fsesl-akka, build-bbb-html5, build-bbb-freeswitch, build-others] runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 @@ -280,6 +292,11 @@ jobs: with: name: artifacts_bbb-learning-dashboard.tar - run: tar xf artifacts.tar + - name: Download artifacts_bbb-libreoffice-docker + uses: actions/download-artifact@v3 + with: + name: artifacts_bbb-libreoffice-docker.tar + - run: tar xf artifacts.tar - name: Download artifacts_bbb-etherpad uses: actions/download-artifact@v3 with: From 205d1d5f35a9d1360a93bfb0a93f89bd9c6bf557 Mon Sep 17 00:00:00 2001 From: imdt Date: Wed, 2 Aug 2023 17:59:19 -0300 Subject: [PATCH 106/252] Fix: disabled self cam tied to specific cam or all --- .../video-list/video-list-item/component.jsx | 14 +++++++++----- .../video-list/video-list-item/container.jsx | 1 + .../video-list-item/user-actions/component.jsx | 17 ++++++++--------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx index ec9b87cbe1..4aa349f92f 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx @@ -31,6 +31,7 @@ const VideoListItem = (props) => { name, voiceUser, isFullscreenContext, layoutContextDispatch, user, onHandleVideoFocus, cameraId, numOfStreams, focused, onVideoItemMount, onVideoItemUnmount, makeDragOperations, dragging, draggingOver, isRTL, isStream, settingsSelfViewDisable, + disabledCams, } = props; const intl = useIntl(); @@ -102,7 +103,7 @@ const VideoListItem = (props) => { if (!isSelfViewDisabled && videoDataLoaded) { playElement(videoTag.current); } - if (isSelfViewDisabled && user.userId === Auth.userID) { + if ((isSelfViewDisabled && user.userId === Auth.userID) || disabledCams?.includes(cameraId)) { videoTag.current.pause(); } }, [isSelfViewDisabled, videoDataLoaded]); @@ -233,7 +234,8 @@ const VideoListItem = (props) => { > { /> - {isStream && isSelfViewDisabled && user.userId === Auth.userID && ( + {isStream && ((isSelfViewDisabled && user.userId === Auth.userID) + || disabledCams.includes(cameraId)) && ( {intl.formatMessage(intlMessages.disableDesc)} @@ -254,13 +257,14 @@ const VideoListItem = (props) => { {/* eslint-disable-next-line no-nested-ternary */} - {(videoIsReady || isSelfViewDisabled) && ( + {(videoIsReady || (isSelfViewDisabled || disabledCams.includes(cameraId))) && ( isVideoSqueezed ? renderSqueezedButton() : renderDefaultButtons() )} {!videoIsReady && (!isSelfViewDisabled || !isStream) && ( isVideoSqueezed ? renderWebcamConnectingSqueezed() : renderWebcamConnecting() )} - {isSelfViewDisabled && user.userId === Auth.userID && renderWebcamConnecting()} + {((isSelfViewDisabled && user.userId === Auth.userID) || disabledCams.includes(cameraId)) + && renderWebcamConnecting()} ); }; diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/container.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/container.jsx index 3d5b4baeee..f53dcfb699 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/container.jsx @@ -56,6 +56,7 @@ export default withTracker((props) => { clientType: 1, }, }), + disabledCams: Session.get('disabledCams') || [], }; })(VideoListItemContainer); diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/user-actions/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/user-actions/component.jsx index d8500b65ff..45ec5b207b 100644 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/user-actions/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/user-actions/component.jsx @@ -7,8 +7,6 @@ import BBBMenu from '/imports/ui/components/common/menu/component'; import PropTypes from 'prop-types'; import Styled from './styles'; import Auth from '/imports/ui/services/auth'; -import Settings from '/imports/ui/services/settings'; -import { updateSettings } from '/imports/ui/components/settings/service'; const intlMessages = defineMessages({ focusLabel: { @@ -80,12 +78,13 @@ const UserActions = (props) => { const menuItems = []; const toggleDisableCam = () => { - const applicationValues = { ...Settings.application }; - applicationValues.selfViewDisable = !Settings.application.selfViewDisable; - updateSettings({ - ...Settings, - application: applicationValues, - }); + const disabledCams = Session.get('disabledCams') || []; + const isDisabled = disabledCams && disabledCams?.includes(cameraId); + if (!isDisabled) { + Session.set('disabledCams', [...disabledCams, cameraId]); + } else { + Session.set('disabledCams', disabledCams.filter((cId) => cId !== cameraId)); + } }; if (isVideoSqueezed) { @@ -108,7 +107,7 @@ const UserActions = (props) => { ); } } - if (userId === Auth.userID && isStream) { + if (userId === Auth.userID && isStream && !isSelfViewDisabled) { menuItems.push({ key: `${cameraId}-disable`, label: intl.formatMessage(intlMessages[`${enableSelfCamIntlKey}Label`]), From 4ba5f934f9db9ee2020f3596612a5b946dfd2cc4 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 18:03:47 -0300 Subject: [PATCH 107/252] Split more steps in different jobs --- .github/workflows/automated-tests.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index dc5b8847e3..199374ef14 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -81,17 +81,23 @@ jobs: name: artifacts_bbb-learning-dashboard.tar path: | artifacts.tar - build-bbb-libreoffice-docker: + build-bbb-playback-record: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - run: ./build/get_external_dependencies.sh - - run: ./build/setup.sh bbb-libreoffice-docker + - run: ./build/setup.sh bbb-playback + - run: ./build/setup.sh bbb-playback-notes + - run: ./build/setup.sh bbb-playback-podcast + - run: ./build/setup.sh bbb-playback-presentation + - run: ./build/setup.sh bbb-playback-screenshare + - run: ./build/setup.sh bbb-playback-video + - run: ./build/setup.sh bbb-record-core - run: tar cvf artifacts.tar artifacts/ - name: Archive packages uses: actions/upload-artifact@v3 with: - name: artifacts_bbb-libreoffice-docker.tar + name: artifacts_bbb-bbb-playback-record.tar path: | artifacts.tar build-bbb-etherpad: @@ -238,13 +244,7 @@ jobs: - run: ./build/get_external_dependencies.sh - run: ./build/setup.sh bbb-mkclean - run: ./build/setup.sh bbb-pads - - run: ./build/setup.sh bbb-playback - - run: ./build/setup.sh bbb-playback-notes - - run: ./build/setup.sh bbb-playback-podcast - - run: ./build/setup.sh bbb-playback-presentation - - run: ./build/setup.sh bbb-playback-screenshare - - run: ./build/setup.sh bbb-playback-video - - run: ./build/setup.sh bbb-record-core + - run: ./build/setup.sh bbb-libreoffice-docker - run: ./build/setup.sh bbb-webrtc-sfu - run: ./build/setup.sh bbb-webrtc-recorder - run: ./build/setup.sh bbb-transcription-controller @@ -267,7 +267,7 @@ jobs: # mv artifacts /home/runner/work/bigbluebutton/bigbluebutton/artifacts/ # EOF install-and-run-tests: - needs: [build-bbb-apps-akka, build-bbb-config, build-bbb-export-annotations, build-bbb-learning-dashboard, build-bbb-libreoffice-docker, build-bbb-etherpad, build-bbb-bbb-web, build-bbb-fsesl-akka, build-bbb-html5, build-bbb-freeswitch, build-others] + needs: [build-bbb-apps-akka, build-bbb-config, build-bbb-export-annotations, build-bbb-learning-dashboard, build-bbb-bbb-playback-record, build-bbb-etherpad, build-bbb-bbb-web, build-bbb-fsesl-akka, build-bbb-html5, build-bbb-freeswitch, build-others] runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 @@ -292,10 +292,10 @@ jobs: with: name: artifacts_bbb-learning-dashboard.tar - run: tar xf artifacts.tar - - name: Download artifacts_bbb-libreoffice-docker + - name: Download artifacts_bbb-bbb-playback-record uses: actions/download-artifact@v3 with: - name: artifacts_bbb-libreoffice-docker.tar + name: artifacts_bbb-bbb-playback-record.tar - run: tar xf artifacts.tar - name: Download artifacts_bbb-etherpad uses: actions/download-artifact@v3 From bee4ea73e3eb746b450e0fe8b10aa4eda11ad9b3 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 18:05:02 -0300 Subject: [PATCH 108/252] Split more steps in different jobs --- .github/workflows/automated-tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 199374ef14..294c928259 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -97,7 +97,7 @@ jobs: - name: Archive packages uses: actions/upload-artifact@v3 with: - name: artifacts_bbb-bbb-playback-record.tar + name: artifacts_bbb-playback-record.tar path: | artifacts.tar build-bbb-etherpad: @@ -267,7 +267,7 @@ jobs: # mv artifacts /home/runner/work/bigbluebutton/bigbluebutton/artifacts/ # EOF install-and-run-tests: - needs: [build-bbb-apps-akka, build-bbb-config, build-bbb-export-annotations, build-bbb-learning-dashboard, build-bbb-bbb-playback-record, build-bbb-etherpad, build-bbb-bbb-web, build-bbb-fsesl-akka, build-bbb-html5, build-bbb-freeswitch, build-others] + needs: [build-bbb-apps-akka, build-bbb-config, build-bbb-export-annotations, build-bbb-learning-dashboard, build-bbb-playback-record, build-bbb-etherpad, build-bbb-bbb-web, build-bbb-fsesl-akka, build-bbb-html5, build-bbb-freeswitch, build-others] runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 @@ -292,10 +292,10 @@ jobs: with: name: artifacts_bbb-learning-dashboard.tar - run: tar xf artifacts.tar - - name: Download artifacts_bbb-bbb-playback-record + - name: Download artifacts_bbb-playback-record uses: actions/download-artifact@v3 with: - name: artifacts_bbb-bbb-playback-record.tar + name: artifacts_bbb-playback-record.tar - run: tar xf artifacts.tar - name: Download artifacts_bbb-etherpad uses: actions/download-artifact@v3 From 403f03a1f1a1a5d36f39143895b93c867db2baf3 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 18:16:43 -0300 Subject: [PATCH 109/252] Split more steps in different jobs --- .github/workflows/automated-tests.yml | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 294c928259..64f4a5ff18 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -72,15 +72,26 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - - run: ./build/get_external_dependencies.sh - - run: ./build/setup.sh bbb-learning-dashboard - - run: tar cvf artifacts.tar artifacts/ + with: + fetch-depth: 0 # Fetch all history + - run: echo "CACHE_LEARNING_DASHBOARD_KEY=$(git log -1 --format=%H -- bbb-learning-dashboard)" >> $GITHUB_ENV + - name: Handle cache + id: cache-action + uses: actions/cache@v3 + with: + path: artifacts.tar + key: ${{ runner.os }}-bbb-learning-dashboard-${{ env.CACHE_LEARNING_DASHBOARD_KEY }} + - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} + name: Generate artifacts + run: | + ./build/get_external_dependencies.sh + ./build/setup.sh bbb-learning-dashboard + tar cvf artifacts.tar artifacts/ - name: Archive packages uses: actions/upload-artifact@v3 with: name: artifacts_bbb-learning-dashboard.tar - path: | - artifacts.tar + path: artifacts.tar build-bbb-playback-record: runs-on: ubuntu-20.04 steps: From 81a3ef00d2b722c0f9079ad6f6c473dd6a3ab97f Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 22:09:51 -0300 Subject: [PATCH 110/252] Rename packages to current commit --- .github/workflows/automated-tests.yml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 64f4a5ff18..7115ed032c 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -342,7 +342,27 @@ jobs: run: | set -e pwd - ls + echo "----ls artifacts/----" + ls artifacts/ +# renane to avoid error: Depends: bbb-apps-akka (= 2:2.7.0~beta.3+20230802T211649-git.local-build-3632c35759) but 2:2.7.0~beta.3+20230802T145515-git.local-build-53a0c6e8ad is to be installed +# "${PACKAGE}_${VERSION}_${DISTRO}" + COMMIT_DATE="$(git log -n1 --pretty='format:%cd' --date=format:'%Y%m%dT%H%M%S')" + VERSION_NUMBER="$(cat "$SOURCE/bigbluebutton-config/bigbluebutton-release" | cut -d '=' -f2 | cut -d "-" -f1)" + VERSION_ADDON="$(cat "$SOURCE/bigbluebutton-config/bigbluebutton-release" | cut -d '=' -f2 | cut -d "-" -f2)" + GIT_REV=$(git rev-parse HEAD) + GIT_REV="local-build-${GIT_REV:0:10}" + VERSION="${VERSION_NUMBER}~${VERSION_ADDON}+${COMMIT_DATE}-git.${GIT_REV}" + echo "VERSION: $VERSION" + mv artifacts/bbb-apps-akka_2* artifacts/bbb-apps-akka_2:${VERSION}_all.deb + mv artifacts/bbb-fsesl-akka_2* artifacts/bbb-fsesl-akka_2:${VERSION}_all.deb + mv artifacts/bbb-etherpad* artifacts/bbb-etherpad_${VERSION}_amd64.deb + mv artifacts/bbb-freeswitch-core* artifacts/bbb-freeswitch-core_${VERSION}_amd64.deb + mv artifacts/bbb-freeswitch-sounds* artifacts/bbb-freeswitch-sounds_${VERSION}_amd64.deb + mv artifacts/bbb-html5-nodejs* artifacts/bbb-html5-nodejs_${VERSION}_amd64.deb + mv artifacts/bbb-html5_* artifacts/bbb-html5_${VERSION}_amd64.deb + mv artifacts/bbb-web_* artifacts/bbb-web_${VERSION}_amd64.deb + mv artifacts/bbb-config_* artifacts/bbb-config_${VERSION}_amd64.deb + echo "----Renamed----" ls artifacts/ echo "Done" - name: Generate CA From 3f53c3e1e83cc63e3ff0b95eda06cb137b70eff6 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 22:11:08 -0300 Subject: [PATCH 111/252] Rename packages to current commit --- .github/workflows/automated-tests.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 7115ed032c..fdb5b8d1d3 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -344,8 +344,6 @@ jobs: pwd echo "----ls artifacts/----" ls artifacts/ -# renane to avoid error: Depends: bbb-apps-akka (= 2:2.7.0~beta.3+20230802T211649-git.local-build-3632c35759) but 2:2.7.0~beta.3+20230802T145515-git.local-build-53a0c6e8ad is to be installed -# "${PACKAGE}_${VERSION}_${DISTRO}" COMMIT_DATE="$(git log -n1 --pretty='format:%cd' --date=format:'%Y%m%dT%H%M%S')" VERSION_NUMBER="$(cat "$SOURCE/bigbluebutton-config/bigbluebutton-release" | cut -d '=' -f2 | cut -d "-" -f1)" VERSION_ADDON="$(cat "$SOURCE/bigbluebutton-config/bigbluebutton-release" | cut -d '=' -f2 | cut -d "-" -f2)" From 80690e756ba0fed9720b7666b22441ec99d33a33 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 22:17:44 -0300 Subject: [PATCH 112/252] Rename packages to current commit --- .github/workflows/automated-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index fdb5b8d1d3..5949369320 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -359,6 +359,7 @@ jobs: mv artifacts/bbb-html5-nodejs* artifacts/bbb-html5-nodejs_${VERSION}_amd64.deb mv artifacts/bbb-html5_* artifacts/bbb-html5_${VERSION}_amd64.deb mv artifacts/bbb-web_* artifacts/bbb-web_${VERSION}_amd64.deb + mv artifacts/bbb-learning-dashboard_* artifacts/bbb-learning-dashboard_${VERSION}_amd64.deb mv artifacts/bbb-config_* artifacts/bbb-config_${VERSION}_amd64.deb echo "----Renamed----" ls artifacts/ From 7dfdabe17d11bf477719144bc10905b7e488d2f2 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 22:23:44 -0300 Subject: [PATCH 113/252] Rename packages to current commit --- .github/workflows/automated-tests.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 5949369320..ccd9ebfe6c 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -345,11 +345,15 @@ jobs: echo "----ls artifacts/----" ls artifacts/ COMMIT_DATE="$(git log -n1 --pretty='format:%cd' --date=format:'%Y%m%dT%H%M%S')" - VERSION_NUMBER="$(cat "$SOURCE/bigbluebutton-config/bigbluebutton-release" | cut -d '=' -f2 | cut -d "-" -f1)" - VERSION_ADDON="$(cat "$SOURCE/bigbluebutton-config/bigbluebutton-release" | cut -d '=' -f2 | cut -d "-" -f2)" + VERSION_NUMBER="$(cat bigbluebutton-config/bigbluebutton-release | cut -d '=' -f2 | cut -d "-" -f1)" + VERSION_ADDON="$(cat bigbluebutton-config/bigbluebutton-release | cut -d '=' -f2 | cut -d "-" -f2)" GIT_REV=$(git rev-parse HEAD) GIT_REV="local-build-${GIT_REV:0:10}" VERSION="${VERSION_NUMBER}~${VERSION_ADDON}+${COMMIT_DATE}-git.${GIT_REV}" + echo "COMMIT_DATE: $COMMIT_DATE" + echo "VERSION_NUMBER: $VERSION_NUMBER" + echo "VERSION_ADDON: $VERSION_ADDON" + echo "GIT_REV: $GIT_REV" echo "VERSION: $VERSION" mv artifacts/bbb-apps-akka_2* artifacts/bbb-apps-akka_2:${VERSION}_all.deb mv artifacts/bbb-fsesl-akka_2* artifacts/bbb-fsesl-akka_2:${VERSION}_all.deb From d3eb4d65560cae515374c563b5702e2505422e39 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 2 Aug 2023 22:32:03 -0300 Subject: [PATCH 114/252] Rename packages to current commit --- .github/workflows/automated-tests.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index ccd9ebfe6c..2db9703a30 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -355,16 +355,16 @@ jobs: echo "VERSION_ADDON: $VERSION_ADDON" echo "GIT_REV: $GIT_REV" echo "VERSION: $VERSION" - mv artifacts/bbb-apps-akka_2* artifacts/bbb-apps-akka_2:${VERSION}_all.deb - mv artifacts/bbb-fsesl-akka_2* artifacts/bbb-fsesl-akka_2:${VERSION}_all.deb - mv artifacts/bbb-etherpad* artifacts/bbb-etherpad_${VERSION}_amd64.deb - mv artifacts/bbb-freeswitch-core* artifacts/bbb-freeswitch-core_${VERSION}_amd64.deb - mv artifacts/bbb-freeswitch-sounds* artifacts/bbb-freeswitch-sounds_${VERSION}_amd64.deb - mv artifacts/bbb-html5-nodejs* artifacts/bbb-html5-nodejs_${VERSION}_amd64.deb - mv artifacts/bbb-html5_* artifacts/bbb-html5_${VERSION}_amd64.deb - mv artifacts/bbb-web_* artifacts/bbb-web_${VERSION}_amd64.deb - mv artifacts/bbb-learning-dashboard_* artifacts/bbb-learning-dashboard_${VERSION}_amd64.deb - mv artifacts/bbb-config_* artifacts/bbb-config_${VERSION}_amd64.deb + mv artifacts/bbb-apps-akka_2* artifacts/bbb-apps-akka_2:${VERSION}_all.deb 2>/dev/null || true + mv artifacts/bbb-fsesl-akka_2* artifacts/bbb-fsesl-akka_2:${VERSION}_all.deb 2>/dev/null || true + mv artifacts/bbb-etherpad* artifacts/bbb-etherpad_${VERSION}_amd64.deb 2>/dev/null || true + mv artifacts/bbb-freeswitch-core* artifacts/bbb-freeswitch-core_${VERSION}_amd64.deb 2>/dev/null || true + mv artifacts/bbb-freeswitch-sounds* artifacts/bbb-freeswitch-sounds_${VERSION}_amd64.deb 2>/dev/null || true + mv artifacts/bbb-html5-nodejs* artifacts/bbb-html5-nodejs_${VERSION}_amd64.deb 2>/dev/null || true + mv artifacts/bbb-html5_* artifacts/bbb-html5_${VERSION}_amd64.deb 2>/dev/null || true + mv artifacts/bbb-web_* artifacts/bbb-web_${VERSION}_amd64.deb 2>/dev/null || true + mv artifacts/bbb-learning-dashboard_* artifacts/bbb-learning-dashboard_${VERSION}_amd64.deb 2>/dev/null || true + mv artifacts/bbb-config_* artifacts/bbb-config_${VERSION}_amd64.deb 2>/dev/null || true echo "----Renamed----" ls artifacts/ echo "Done" From 8a6dbe64ad233f28c3105c6a134b0b8eb42b9f15 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Thu, 3 Aug 2023 10:03:56 +0000 Subject: [PATCH 115/252] Translate en.json in et 100% translated source file: 'en.json' on 'et'. --- bigbluebutton-html5/public/locales/et.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bigbluebutton-html5/public/locales/et.json b/bigbluebutton-html5/public/locales/et.json index 2793da1350..4c07c50b7f 100644 --- a/bigbluebutton-html5/public/locales/et.json +++ b/bigbluebutton-html5/public/locales/et.json @@ -9,7 +9,7 @@ "app.chat.emojiButtonLabel": "Emoji valija", "app.chat.inputPlaceholder": "Sõnum: {0}", "app.chat.titlePublic": "Avalik vestlus", - "app.chat.titlePrivate": "Privaatne vestlus kasutajaga {0}", + "app.chat.titlePrivate": "Privaatne vestlus ({0})", "app.chat.partnerDisconnected": "{0} lahkus koosolekult", "app.chat.closeChatLabel": "Sulge {0}", "app.chat.hideChatLabel": "Peida {0}", @@ -425,7 +425,7 @@ "app.about.confirmLabel": "OK", "app.about.confirmDesc": "OK", "app.about.dismissLabel": "Tühista", - "app.about.dismissDesc": "Sulge informatsioon kliendi kohta", + "app.about.dismissDesc": "Sulge info kliendi kohta", "app.mobileAppModal.title": "Ava BigBlueButtoni mobiilirakendus", "app.mobileAppModal.description": "Kas BigBlueButtoni rakendus on seadmesse paigaldatud?", "app.mobileAppModal.openApp": "Jah, ava rakendus", @@ -715,9 +715,9 @@ "app.audio.captions.button.language": "Keel", "app.audio.captions.button.transcription": "Transkriptsioon", "app.audio.captions.button.transcriptionSettings": "Transkriptsiooni sätted", - "app.audio.captions.speech.title": "Automaatne transkriptsioon", + "app.audio.captions.speech.title": "Automaattranskriptsioon", "app.audio.captions.speech.disabled": "Keelatud", - "app.audio.captions.speech.unsupported": "See brauser ei toeta kõnetuvastust. Audiot ei transkribeerita.", + "app.audio.captions.speech.unsupported": "See brauser ei toeta kõnetuvastust. Sinu audiot ei transkribeerita.", "app.audio.captions.select.de-DE": "Saksa", "app.audio.captions.select.en-US": "Inglise", "app.audio.captions.select.es-ES": "Hispaania", @@ -1048,7 +1048,7 @@ "app.whiteboard.annotations.pollResult": "Küsitluse tulemus", "app.whiteboard.annotations.noResponses": "Vastuseid pole", "app.whiteboard.annotations.notAllowed": "Sul ei ole lubatud seda muudatust teha", - "app.whiteboard.annotations.numberExceeded": "Märgete arv ületas piirväärtuse ({0})", + "app.whiteboard.annotations.numberExceeded": "Märgete arv ületas lubatud piiri ({0})", "app.whiteboard.toolbar.tools": "Tööriistad", "app.whiteboard.toolbar.tools.hand": "Liiguta", "app.whiteboard.toolbar.tools.pencil": "Pliiats", @@ -1184,7 +1184,7 @@ "app.layout.modal.cancel": "Tühista", "app.layout.modal.layoutLabel": "Vali paigutus", "app.layout.modal.keepPushingLayoutLabel": "Rakenda paigutus kõigile", - "app.layout.modal.pushLayoutLabel": "Rakenda kõigile", + "app.layout.modal.pushLayoutLabel": "Rakenda kõigile kasutajatele", "app.layout.modal.layoutToastLabel": "Paigutuse sätted muudetud", "app.layout.modal.layoutSingular": "Paigutus", "app.layout.modal.layoutBtnDesc": "Määrab paigutuse valitud valikuks", From b1884af440f1358278f1d57354d7295082c307cf Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Thu, 3 Aug 2023 10:10:48 +0000 Subject: [PATCH 116/252] Translate en.json in et 100% translated source file: 'en.json' on 'et'. --- bigbluebutton-html5/public/locales/et.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/public/locales/et.json b/bigbluebutton-html5/public/locales/et.json index 4c07c50b7f..7535227151 100644 --- a/bigbluebutton-html5/public/locales/et.json +++ b/bigbluebutton-html5/public/locales/et.json @@ -9,7 +9,7 @@ "app.chat.emojiButtonLabel": "Emoji valija", "app.chat.inputPlaceholder": "Sõnum: {0}", "app.chat.titlePublic": "Avalik vestlus", - "app.chat.titlePrivate": "Privaatne vestlus ({0})", + "app.chat.titlePrivate": "Privaatne vestlus - {0}", "app.chat.partnerDisconnected": "{0} lahkus koosolekult", "app.chat.closeChatLabel": "Sulge {0}", "app.chat.hideChatLabel": "Peida {0}", From 896db7883f32f3fc269532daf6c46b8316fe480b Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Thu, 3 Aug 2023 11:20:22 +0000 Subject: [PATCH 117/252] Translate en.json in et 100% translated source file: 'en.json' on 'et'. --- bigbluebutton-html5/public/locales/et.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/public/locales/et.json b/bigbluebutton-html5/public/locales/et.json index 7535227151..589bb28f00 100644 --- a/bigbluebutton-html5/public/locales/et.json +++ b/bigbluebutton-html5/public/locales/et.json @@ -696,7 +696,7 @@ "app.audio.speakers": "Kõlarid", "app.audio.noDeviceFound": "Seadmeid ei leitud", "app.audio.audioSettings.titleLabel": "Vali oma audio sätted", - "app.audio.audioSettings.descriptionLabel": "Pane tähele, et veebilehitsejas avaneb dialoogiaken, kus palutakse nõustuda mikrofoni jagamisega.", + "app.audio.audioSettings.descriptionLabel": "Pane tähele, et brauseris avaneb dialoogiaken, kus palutakse nõustuda mikrofoni jagamisega.", "app.audio.audioSettings.microphoneSourceLabel": "Mikrofoni sisend", "app.audio.audioSettings.speakerSourceLabel": "Kõlarite sisend", "app.audio.audioSettings.testSpeakerLabel": "Testi kõlarit", From c5995b44c0c9bc35a54c8cd1ae2988e8f6d1fe9d Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Thu, 3 Aug 2023 11:21:02 +0000 Subject: [PATCH 118/252] Translate en.json in et 100% translated source file: 'en.json' on 'et'. --- bigbluebutton-html5/public/locales/et.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/public/locales/et.json b/bigbluebutton-html5/public/locales/et.json index 589bb28f00..6febc7ab1d 100644 --- a/bigbluebutton-html5/public/locales/et.json +++ b/bigbluebutton-html5/public/locales/et.json @@ -1164,7 +1164,7 @@ "app.externalVideo.autoPlayWarning": "Käivita video, et meedia sünkroniseerimine aktiveerida", "app.externalVideo.refreshLabel": "Värskenda videomängijat", "app.externalVideo.fullscreenLabel": "Videomängija", - "app.externalVideo.noteLabel": "Märkus: jagatud väliseid videoid sessiooni salvestisse ei kaasata. Toetatatakse keskkondi YouTube, Vimeo, Instructure Media, Twitch ja Dailymotion ning meediafailide URL-e (nt https://example.com/xy.mp4).", + "app.externalVideo.noteLabel": "Märkus: jagatud väliseid videoid sessiooni salvestisse ei kaasata. Toetatakse keskkondi YouTube, Vimeo, Instructure Media, Twitch ja Dailymotion ning meediafailide URL-e (nt https://example.com/xy.mp4).", "app.externalVideo.subtitlesOn": "Lülita välja", "app.externalVideo.subtitlesOff": "Lülita sisse (kui saadaval)", "app.actionsBar.actionsDropdown.shareExternalVideo": "Jaga välist videot", From c4ee41988bd58c9aafb0e53c7ca31ecabf986943 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Thu, 3 Aug 2023 09:15:53 -0300 Subject: [PATCH 119/252] Force GIT_REV and COMMIT_DATE --- .github/workflows/automated-tests.yml | 66 ++++++++++++++++++--------- build/setup.sh | 8 ++++ 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 2db9703a30..5d8009ca69 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -34,6 +34,8 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh + FORCE_GIT_REV="0" + FORCE_COMMIT_DATE="0" ./build/setup.sh bbb-apps-akka tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -46,28 +48,32 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - - run: ./build/get_external_dependencies.sh - - run: ./build/setup.sh bbb-config - - run: tar cvf artifacts.tar artifacts/ + - run: | + ./build/get_external_dependencies.sh + FORCE_GIT_REV="0" + FORCE_COMMIT_DATE="0" + ./build/setup.sh bbb-config + tar cvf artifacts.tar artifacts/ - name: Archive packages uses: actions/upload-artifact@v3 with: name: artifacts_bbb-config.tar - path: | - artifacts.tar + path: artifacts.tar build-bbb-export-annotations: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - - run: ./build/get_external_dependencies.sh - - run: ./build/setup.sh bbb-export-annotations - - run: tar cvf artifacts.tar artifacts/ + - run: | + ./build/get_external_dependencies.sh + FORCE_GIT_REV="0" + FORCE_COMMIT_DATE="0" + ./build/setup.sh bbb-export-annotations + tar cvf artifacts.tar artifacts/ - name: Archive packages uses: actions/upload-artifact@v3 with: name: artifacts_bbb-export-annotations.tar - path: | - artifacts.tar + path: artifacts.tar build-bbb-learning-dashboard: runs-on: ubuntu-20.04 steps: @@ -85,6 +91,8 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh + FORCE_GIT_REV="0" + FORCE_COMMIT_DATE="0" ./build/setup.sh bbb-learning-dashboard tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -97,6 +105,8 @@ jobs: steps: - uses: actions/checkout@v3 - run: ./build/get_external_dependencies.sh + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV - run: ./build/setup.sh bbb-playback - run: ./build/setup.sh bbb-playback-notes - run: ./build/setup.sh bbb-playback-podcast @@ -133,6 +143,8 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh + FORCE_GIT_REV="0" + FORCE_COMMIT_DATE="0" ./build/setup.sh bbb-etherpad tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -160,6 +172,8 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh + FORCE_GIT_REV="0" + FORCE_COMMIT_DATE="0" ./build/setup.sh bbb-web tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -186,6 +200,8 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh + FORCE_GIT_REV="0" + FORCE_COMMIT_DATE="0" ./build/setup.sh bbb-fsesl-akka tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -211,6 +227,8 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh + FORCE_GIT_REV="0" + FORCE_COMMIT_DATE="0" ./build/setup.sh bbb-html5-nodejs ./build/setup.sh bbb-html5 tar cvf artifacts.tar artifacts/ @@ -239,6 +257,8 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh + FORCE_GIT_REV="0" + FORCE_COMMIT_DATE="0" ./build/setup.sh bbb-freeswitch-core ./build/setup.sh bbb-freeswitch-sounds tar cvf artifacts.tar artifacts/ @@ -253,6 +273,8 @@ jobs: steps: - uses: actions/checkout@v3 - run: ./build/get_external_dependencies.sh + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV - run: ./build/setup.sh bbb-mkclean - run: ./build/setup.sh bbb-pads - run: ./build/setup.sh bbb-libreoffice-docker @@ -355,18 +377,18 @@ jobs: echo "VERSION_ADDON: $VERSION_ADDON" echo "GIT_REV: $GIT_REV" echo "VERSION: $VERSION" - mv artifacts/bbb-apps-akka_2* artifacts/bbb-apps-akka_2:${VERSION}_all.deb 2>/dev/null || true - mv artifacts/bbb-fsesl-akka_2* artifacts/bbb-fsesl-akka_2:${VERSION}_all.deb 2>/dev/null || true - mv artifacts/bbb-etherpad* artifacts/bbb-etherpad_${VERSION}_amd64.deb 2>/dev/null || true - mv artifacts/bbb-freeswitch-core* artifacts/bbb-freeswitch-core_${VERSION}_amd64.deb 2>/dev/null || true - mv artifacts/bbb-freeswitch-sounds* artifacts/bbb-freeswitch-sounds_${VERSION}_amd64.deb 2>/dev/null || true - mv artifacts/bbb-html5-nodejs* artifacts/bbb-html5-nodejs_${VERSION}_amd64.deb 2>/dev/null || true - mv artifacts/bbb-html5_* artifacts/bbb-html5_${VERSION}_amd64.deb 2>/dev/null || true - mv artifacts/bbb-web_* artifacts/bbb-web_${VERSION}_amd64.deb 2>/dev/null || true - mv artifacts/bbb-learning-dashboard_* artifacts/bbb-learning-dashboard_${VERSION}_amd64.deb 2>/dev/null || true - mv artifacts/bbb-config_* artifacts/bbb-config_${VERSION}_amd64.deb 2>/dev/null || true - echo "----Renamed----" - ls artifacts/ +# mv artifacts/bbb-apps-akka_2* artifacts/bbb-apps-akka_2:${VERSION}_all.deb 2>/dev/null || true +# mv artifacts/bbb-fsesl-akka_2* artifacts/bbb-fsesl-akka_2:${VERSION}_all.deb 2>/dev/null || true +# mv artifacts/bbb-etherpad* artifacts/bbb-etherpad_${VERSION}_amd64.deb 2>/dev/null || true +# mv artifacts/bbb-freeswitch-core* artifacts/bbb-freeswitch-core_${VERSION}_amd64.deb 2>/dev/null || true +# mv artifacts/bbb-freeswitch-sounds* artifacts/bbb-freeswitch-sounds_${VERSION}_amd64.deb 2>/dev/null || true +# mv artifacts/bbb-html5-nodejs* artifacts/bbb-html5-nodejs_${VERSION}_amd64.deb 2>/dev/null || true +# mv artifacts/bbb-html5_* artifacts/bbb-html5_${VERSION}_amd64.deb 2>/dev/null || true +# mv artifacts/bbb-web_* artifacts/bbb-web_${VERSION}_amd64.deb 2>/dev/null || true +# mv artifacts/bbb-learning-dashboard_* artifacts/bbb-learning-dashboard_${VERSION}_amd64.deb 2>/dev/null || true +# mv artifacts/bbb-config_* artifacts/bbb-config_${VERSION}_amd64.deb 2>/dev/null || true +# echo "----Renamed----" +# ls artifacts/ echo "Done" - name: Generate CA run: | diff --git a/build/setup.sh b/build/setup.sh index afc345b81f..2fe3e4e373 100755 --- a/build/setup.sh +++ b/build/setup.sh @@ -24,6 +24,14 @@ else fi COMMIT_DATE="$(git log -n1 --pretty='format:%cd' --date=format:'%Y%m%dT%H%M%S')" +if [ ! -z "$FORCE_GIT_REV" ]; then + GIT_REV=$FORCE_GIT_REV +fi + +if [ ! -z "$FORCE_COMMIT_DATE" ]; then + COMMIT_DATE=$FORCE_COMMIT_DATE +fi + # Arrange to write the docker container ID to a temp file, then run # the container detached and immediately attach it (without stdin) so # we can catch CTRL-C in this script and kill the container if so. From 7d90c173bfb7079fc03d8e40e5e602d87e71fab7 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Thu, 3 Aug 2023 09:19:59 -0300 Subject: [PATCH 120/252] Append bbb release to cache key --- .github/workflows/automated-tests.yml | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 5d8009ca69..fdde24a1e1 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -24,12 +24,13 @@ jobs: fetch-depth: 0 # Fetch all history - run: echo "CACHE_AKKA_APPS_KEY=$(git log -1 --format=%H -- akka-bbb-apps)" >> $GITHUB_ENV - run: echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV + - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - name: Handle cache id: cache-action uses: actions/cache@v3 with: path: artifacts.tar - key: ${{ runner.os }}-bbb-apps-akka-${{ env.CACHE_AKKA_APPS_KEY }}-${{ env.CACHE_COMMON_MSG_KEY }} + key: ${{ runner.os }}-bbb-apps-akka-${{ env.CACHE_AKKA_APPS_KEY }}-${{ env.CACHE_COMMON_MSG_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} name: Generate artifacts run: | @@ -81,12 +82,13 @@ jobs: with: fetch-depth: 0 # Fetch all history - run: echo "CACHE_LEARNING_DASHBOARD_KEY=$(git log -1 --format=%H -- bbb-learning-dashboard)" >> $GITHUB_ENV + - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - name: Handle cache id: cache-action uses: actions/cache@v3 with: path: artifacts.tar - key: ${{ runner.os }}-bbb-learning-dashboard-${{ env.CACHE_LEARNING_DASHBOARD_KEY }} + key: ${{ runner.os }}-bbb-learning-dashboard-${{ env.CACHE_LEARNING_DASHBOARD_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} name: Generate artifacts run: | @@ -133,12 +135,13 @@ jobs: - run: echo "CACHE_URL2_KEY=$(curl -s https://api.github.com/repos/alangecker/bbb-etherpad-plugin/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - run: echo "CACHE_URL3_KEY=$(curl -s https://api.github.com/repos/mconf/ep_redis_publisher/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - run: echo "CACHE_URL4_KEY=$(curl -s https://api.github.com/repos/alangecker/bbb-etherpad-skin/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV + - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - name: Handle cache id: cache-action uses: actions/cache@v3 with: path: artifacts.tar - key: ${{ runner.os }}-bbb-etherpad-${{ env.CACHE_ETHERPAD_VERSION_KEY }}-${{ env.CACHE_ETHERPAD_BUILD_KEY }}-${{ env.CACHE_URL1_KEY }}-${{ env.CACHE_URL2_KEY }}-${{ env.CACHE_URL3_KEY }}-${{ env.CACHE_URL4_KEY }} + key: ${{ runner.os }}-bbb-etherpad-${{ env.CACHE_ETHERPAD_VERSION_KEY }}-${{ env.CACHE_ETHERPAD_BUILD_KEY }}-${{ env.CACHE_URL1_KEY }}-${{ env.CACHE_URL2_KEY }}-${{ env.CACHE_URL3_KEY }}-${{ env.CACHE_URL4_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} name: Generate artifacts run: | @@ -162,12 +165,13 @@ jobs: - run: echo "CACHE_BBB_WEB_KEY=$(git log -1 --format=%H -- bigbluebutton-web)" >> $GITHUB_ENV - run: echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV - run: echo "CACHE_COMMON_WEB_KEY=$(git log -1 --format=%H -- bbb-common-web)" >> $GITHUB_ENV + - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - name: Handle cache id: cache-action uses: actions/cache@v3 with: path: artifacts.tar - key: ${{ runner.os }}-bbb-web-${{ env.CACHE_BBB_WEB_KEY }}-${{ env.CACHE_COMMON_MSG_KEY }}-${{ env.CACHE_COMMON_WEB_KEY }} + key: ${{ runner.os }}-bbb-web-${{ env.CACHE_BBB_WEB_KEY }}-${{ env.CACHE_COMMON_MSG_KEY }}-${{ env.CACHE_COMMON_WEB_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} name: Generate artifacts run: | @@ -190,12 +194,13 @@ jobs: fetch-depth: 0 # Fetch all history - run: echo "CACHE_AKKA_FSESL_KEY=$(git log -1 --format=%H -- akka-bbb-fsesl)" >> $GITHUB_ENV - run: echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV + - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - name: Handle cache id: cache-action uses: actions/cache@v3 with: path: artifacts.tar - key: ${{ runner.os }}-bbb-fsesl-akka-${{ env.CACHE_AKKA_FSESL_KEY }}-${{ env.CACHE_COMMON_MSG_KEY }} + key: ${{ runner.os }}-bbb-fsesl-akka-${{ env.CACHE_AKKA_FSESL_KEY }}-${{ env.CACHE_COMMON_MSG_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} name: Generate artifacts run: | @@ -217,12 +222,13 @@ jobs: with: fetch-depth: 0 # Fetch all history - run: echo "CACHE_KEY=$(git log -1 --format=%H -- bigbluebutton-html5)" >> $GITHUB_ENV + - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - name: Handle cache id: cache-action uses: actions/cache@v3 with: path: artifacts.tar - key: ${{ runner.os }}-bbb-html5-${{ env.CACHE_KEY }} + key: ${{ runner.os }}-bbb-html5-${{ env.CACHE_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} name: Generate artifacts run: | @@ -247,12 +253,13 @@ jobs: - run: echo "CACHE_FREESWITCH_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-core)" >> $GITHUB_ENV - run: echo "CACHE_FREESWITCH_SOUNDS_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-sounds)" >> $GITHUB_ENV - run: echo "CACHE_SOUNDS_KEY=$(curl -Is http://bigbluebutton.org/downloads/sounds.tar.gz | grep "Last-Modified" | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV + - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - name: Handle cache id: cache-action uses: actions/cache@v3 with: path: artifacts.tar - key: ${{ runner.os }}-bbb-freeswitch-${{ env.CACHE_FREESWITCH_KEY }}-${{ env.CACHE_FREESWITCH_SOUNDS_KEY }}-${{ env.CACHE_SOUNDS_KEY }} + key: ${{ runner.os }}-bbb-freeswitch-${{ env.CACHE_FREESWITCH_KEY }}-${{ env.CACHE_FREESWITCH_SOUNDS_KEY }}-${{ env.CACHE_SOUNDS_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} name: Generate artifacts run: | From d0121ed9ff9c4c42839cafbf3e4872f239bf6309 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Thu, 3 Aug 2023 09:22:37 -0300 Subject: [PATCH 121/252] Fix indent --- .github/workflows/automated-tests.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index fdde24a1e1..174df7f8a3 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -384,19 +384,19 @@ jobs: echo "VERSION_ADDON: $VERSION_ADDON" echo "GIT_REV: $GIT_REV" echo "VERSION: $VERSION" -# mv artifacts/bbb-apps-akka_2* artifacts/bbb-apps-akka_2:${VERSION}_all.deb 2>/dev/null || true -# mv artifacts/bbb-fsesl-akka_2* artifacts/bbb-fsesl-akka_2:${VERSION}_all.deb 2>/dev/null || true -# mv artifacts/bbb-etherpad* artifacts/bbb-etherpad_${VERSION}_amd64.deb 2>/dev/null || true -# mv artifacts/bbb-freeswitch-core* artifacts/bbb-freeswitch-core_${VERSION}_amd64.deb 2>/dev/null || true -# mv artifacts/bbb-freeswitch-sounds* artifacts/bbb-freeswitch-sounds_${VERSION}_amd64.deb 2>/dev/null || true -# mv artifacts/bbb-html5-nodejs* artifacts/bbb-html5-nodejs_${VERSION}_amd64.deb 2>/dev/null || true -# mv artifacts/bbb-html5_* artifacts/bbb-html5_${VERSION}_amd64.deb 2>/dev/null || true -# mv artifacts/bbb-web_* artifacts/bbb-web_${VERSION}_amd64.deb 2>/dev/null || true -# mv artifacts/bbb-learning-dashboard_* artifacts/bbb-learning-dashboard_${VERSION}_amd64.deb 2>/dev/null || true -# mv artifacts/bbb-config_* artifacts/bbb-config_${VERSION}_amd64.deb 2>/dev/null || true -# echo "----Renamed----" -# ls artifacts/ echo "Done" + # mv artifacts/bbb-apps-akka_2* artifacts/bbb-apps-akka_2:${VERSION}_all.deb 2>/dev/null || true + # mv artifacts/bbb-fsesl-akka_2* artifacts/bbb-fsesl-akka_2:${VERSION}_all.deb 2>/dev/null || true + # mv artifacts/bbb-etherpad* artifacts/bbb-etherpad_${VERSION}_amd64.deb 2>/dev/null || true + # mv artifacts/bbb-freeswitch-core* artifacts/bbb-freeswitch-core_${VERSION}_amd64.deb 2>/dev/null || true + # mv artifacts/bbb-freeswitch-sounds* artifacts/bbb-freeswitch-sounds_${VERSION}_amd64.deb 2>/dev/null || true + # mv artifacts/bbb-html5-nodejs* artifacts/bbb-html5-nodejs_${VERSION}_amd64.deb 2>/dev/null || true + # mv artifacts/bbb-html5_* artifacts/bbb-html5_${VERSION}_amd64.deb 2>/dev/null || true + # mv artifacts/bbb-web_* artifacts/bbb-web_${VERSION}_amd64.deb 2>/dev/null || true + # mv artifacts/bbb-learning-dashboard_* artifacts/bbb-learning-dashboard_${VERSION}_amd64.deb 2>/dev/null || true + # mv artifacts/bbb-config_* artifacts/bbb-config_${VERSION}_amd64.deb 2>/dev/null || true + # echo "----Renamed----" + # ls artifacts/ - name: Generate CA run: | sudo -i < Date: Thu, 3 Aug 2023 09:29:41 -0300 Subject: [PATCH 122/252] Fix set github vars --- .github/workflows/automated-tests.yml | 36 +++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 174df7f8a3..cb0e20bc96 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -35,8 +35,8 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh - FORCE_GIT_REV="0" - FORCE_COMMIT_DATE="0" + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV ./build/setup.sh bbb-apps-akka tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -51,8 +51,8 @@ jobs: - uses: actions/checkout@v3 - run: | ./build/get_external_dependencies.sh - FORCE_GIT_REV="0" - FORCE_COMMIT_DATE="0" + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV ./build/setup.sh bbb-config tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -66,8 +66,8 @@ jobs: - uses: actions/checkout@v3 - run: | ./build/get_external_dependencies.sh - FORCE_GIT_REV="0" - FORCE_COMMIT_DATE="0" + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV ./build/setup.sh bbb-export-annotations tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -93,8 +93,8 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh - FORCE_GIT_REV="0" - FORCE_COMMIT_DATE="0" + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV ./build/setup.sh bbb-learning-dashboard tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -146,8 +146,8 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh - FORCE_GIT_REV="0" - FORCE_COMMIT_DATE="0" + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV ./build/setup.sh bbb-etherpad tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -176,8 +176,8 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh - FORCE_GIT_REV="0" - FORCE_COMMIT_DATE="0" + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV ./build/setup.sh bbb-web tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -205,8 +205,8 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh - FORCE_GIT_REV="0" - FORCE_COMMIT_DATE="0" + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV ./build/setup.sh bbb-fsesl-akka tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -233,8 +233,8 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh - FORCE_GIT_REV="0" - FORCE_COMMIT_DATE="0" + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV ./build/setup.sh bbb-html5-nodejs ./build/setup.sh bbb-html5 tar cvf artifacts.tar artifacts/ @@ -264,8 +264,8 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh - FORCE_GIT_REV="0" - FORCE_COMMIT_DATE="0" + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV ./build/setup.sh bbb-freeswitch-core ./build/setup.sh bbb-freeswitch-sounds tar cvf artifacts.tar artifacts/ From 8920aeb170c9ec119ee9f8823385717e014465cf Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Thu, 3 Aug 2023 09:45:09 -0300 Subject: [PATCH 123/252] Fix set github vars --- .github/workflows/automated-tests.yml | 36 +++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index cb0e20bc96..aeb5b726b5 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -25,6 +25,8 @@ jobs: - run: echo "CACHE_AKKA_APPS_KEY=$(git log -1 --format=%H -- akka-bbb-apps)" >> $GITHUB_ENV - run: echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -35,8 +37,6 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV ./build/setup.sh bbb-apps-akka tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -49,10 +49,10 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV - run: | ./build/get_external_dependencies.sh - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV ./build/setup.sh bbb-config tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -64,10 +64,10 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV - run: | ./build/get_external_dependencies.sh - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV ./build/setup.sh bbb-export-annotations tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -83,6 +83,8 @@ jobs: fetch-depth: 0 # Fetch all history - run: echo "CACHE_LEARNING_DASHBOARD_KEY=$(git log -1 --format=%H -- bbb-learning-dashboard)" >> $GITHUB_ENV - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -93,8 +95,6 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV ./build/setup.sh bbb-learning-dashboard tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -136,6 +136,8 @@ jobs: - run: echo "CACHE_URL3_KEY=$(curl -s https://api.github.com/repos/mconf/ep_redis_publisher/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - run: echo "CACHE_URL4_KEY=$(curl -s https://api.github.com/repos/alangecker/bbb-etherpad-skin/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -146,8 +148,6 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV ./build/setup.sh bbb-etherpad tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -166,6 +166,8 @@ jobs: - run: echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV - run: echo "CACHE_COMMON_WEB_KEY=$(git log -1 --format=%H -- bbb-common-web)" >> $GITHUB_ENV - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -176,8 +178,6 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV ./build/setup.sh bbb-web tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -195,6 +195,8 @@ jobs: - run: echo "CACHE_AKKA_FSESL_KEY=$(git log -1 --format=%H -- akka-bbb-fsesl)" >> $GITHUB_ENV - run: echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -205,8 +207,6 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV ./build/setup.sh bbb-fsesl-akka tar cvf artifacts.tar artifacts/ - name: Archive packages @@ -223,6 +223,8 @@ jobs: fetch-depth: 0 # Fetch all history - run: echo "CACHE_KEY=$(git log -1 --format=%H -- bigbluebutton-html5)" >> $GITHUB_ENV - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -233,8 +235,6 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV ./build/setup.sh bbb-html5-nodejs ./build/setup.sh bbb-html5 tar cvf artifacts.tar artifacts/ @@ -254,6 +254,8 @@ jobs: - run: echo "CACHE_FREESWITCH_SOUNDS_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-sounds)" >> $GITHUB_ENV - run: echo "CACHE_SOUNDS_KEY=$(curl -Is http://bigbluebutton.org/downloads/sounds.tar.gz | grep "Last-Modified" | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -264,8 +266,6 @@ jobs: name: Generate artifacts run: | ./build/get_external_dependencies.sh - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV ./build/setup.sh bbb-freeswitch-core ./build/setup.sh bbb-freeswitch-sounds tar cvf artifacts.tar artifacts/ From 7cf4ab90944c144193575fd945181f4a20d8b553 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Thu, 3 Aug 2023 10:52:17 -0300 Subject: [PATCH 124/252] Small change to reprocess tests --- .github/workflows/automated-tests.yml | 34 +++++++++------------------ 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index aeb5b726b5..6ed535e461 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -373,30 +373,18 @@ jobs: pwd echo "----ls artifacts/----" ls artifacts/ - COMMIT_DATE="$(git log -n1 --pretty='format:%cd' --date=format:'%Y%m%dT%H%M%S')" - VERSION_NUMBER="$(cat bigbluebutton-config/bigbluebutton-release | cut -d '=' -f2 | cut -d "-" -f1)" - VERSION_ADDON="$(cat bigbluebutton-config/bigbluebutton-release | cut -d '=' -f2 | cut -d "-" -f2)" - GIT_REV=$(git rev-parse HEAD) - GIT_REV="local-build-${GIT_REV:0:10}" - VERSION="${VERSION_NUMBER}~${VERSION_ADDON}+${COMMIT_DATE}-git.${GIT_REV}" - echo "COMMIT_DATE: $COMMIT_DATE" - echo "VERSION_NUMBER: $VERSION_NUMBER" - echo "VERSION_ADDON: $VERSION_ADDON" - echo "GIT_REV: $GIT_REV" - echo "VERSION: $VERSION" +# COMMIT_DATE="$(git log -n1 --pretty='format:%cd' --date=format:'%Y%m%dT%H%M%S')" +# VERSION_NUMBER="$(cat bigbluebutton-config/bigbluebutton-release | cut -d '=' -f2 | cut -d "-" -f1)" +# VERSION_ADDON="$(cat bigbluebutton-config/bigbluebutton-release | cut -d '=' -f2 | cut -d "-" -f2)" +# GIT_REV=$(git rev-parse HEAD) +# GIT_REV="local-build-${GIT_REV:0:10}" +# VERSION="${VERSION_NUMBER}~${VERSION_ADDON}+${COMMIT_DATE}-git.${GIT_REV}" +# echo "COMMIT_DATE: $COMMIT_DATE" +# echo "VERSION_NUMBER: $VERSION_NUMBER" +# echo "VERSION_ADDON: $VERSION_ADDON" +# echo "GIT_REV: $GIT_REV" +# echo "VERSION: $VERSION" echo "Done" - # mv artifacts/bbb-apps-akka_2* artifacts/bbb-apps-akka_2:${VERSION}_all.deb 2>/dev/null || true - # mv artifacts/bbb-fsesl-akka_2* artifacts/bbb-fsesl-akka_2:${VERSION}_all.deb 2>/dev/null || true - # mv artifacts/bbb-etherpad* artifacts/bbb-etherpad_${VERSION}_amd64.deb 2>/dev/null || true - # mv artifacts/bbb-freeswitch-core* artifacts/bbb-freeswitch-core_${VERSION}_amd64.deb 2>/dev/null || true - # mv artifacts/bbb-freeswitch-sounds* artifacts/bbb-freeswitch-sounds_${VERSION}_amd64.deb 2>/dev/null || true - # mv artifacts/bbb-html5-nodejs* artifacts/bbb-html5-nodejs_${VERSION}_amd64.deb 2>/dev/null || true - # mv artifacts/bbb-html5_* artifacts/bbb-html5_${VERSION}_amd64.deb 2>/dev/null || true - # mv artifacts/bbb-web_* artifacts/bbb-web_${VERSION}_amd64.deb 2>/dev/null || true - # mv artifacts/bbb-learning-dashboard_* artifacts/bbb-learning-dashboard_${VERSION}_amd64.deb 2>/dev/null || true - # mv artifacts/bbb-config_* artifacts/bbb-config_${VERSION}_amd64.deb 2>/dev/null || true - # echo "----Renamed----" - # ls artifacts/ - name: Generate CA run: | sudo -i < Date: Thu, 3 Aug 2023 10:53:12 -0300 Subject: [PATCH 125/252] Small change to reprocess tests --- .github/workflows/automated-tests.yml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 6ed535e461..aa7e2eb886 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -373,18 +373,19 @@ jobs: pwd echo "----ls artifacts/----" ls artifacts/ -# COMMIT_DATE="$(git log -n1 --pretty='format:%cd' --date=format:'%Y%m%dT%H%M%S')" -# VERSION_NUMBER="$(cat bigbluebutton-config/bigbluebutton-release | cut -d '=' -f2 | cut -d "-" -f1)" -# VERSION_ADDON="$(cat bigbluebutton-config/bigbluebutton-release | cut -d '=' -f2 | cut -d "-" -f2)" -# GIT_REV=$(git rev-parse HEAD) -# GIT_REV="local-build-${GIT_REV:0:10}" -# VERSION="${VERSION_NUMBER}~${VERSION_ADDON}+${COMMIT_DATE}-git.${GIT_REV}" -# echo "COMMIT_DATE: $COMMIT_DATE" -# echo "VERSION_NUMBER: $VERSION_NUMBER" -# echo "VERSION_ADDON: $VERSION_ADDON" -# echo "GIT_REV: $GIT_REV" -# echo "VERSION: $VERSION" echo "Done" + # COMMIT_DATE="$(git log -n1 --pretty='format:%cd' --date=format:'%Y%m%dT%H%M%S')" + # VERSION_NUMBER="$(cat bigbluebutton-config/bigbluebutton-release | cut -d '=' -f2 | cut -d "-" -f1)" + # VERSION_ADDON="$(cat bigbluebutton-config/bigbluebutton-release | cut -d '=' -f2 | cut -d "-" -f2)" + # GIT_REV=$(git rev-parse HEAD) + # GIT_REV="local-build-${GIT_REV:0:10}" + # VERSION="${VERSION_NUMBER}~${VERSION_ADDON}+${COMMIT_DATE}-git.${GIT_REV}" + # echo "COMMIT_DATE: $COMMIT_DATE" + # echo "VERSION_NUMBER: $VERSION_NUMBER" + # echo "VERSION_ADDON: $VERSION_ADDON" + # echo "GIT_REV: $GIT_REV" + # echo "VERSION: $VERSION" + - name: Generate CA run: | sudo -i < Date: Thu, 3 Aug 2023 11:29:51 -0300 Subject: [PATCH 126/252] Client: Margin adjusted to better fitting size --- .../imports/ui/components/nav-bar/talking-indicator/styles.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/styles.js b/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/styles.js index 6677a1753a..d549f8255c 100644 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/styles.js +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/styles.js @@ -142,6 +142,8 @@ const IsTalkingWrapper = styled.div` flex-direction: row; position: relative; overflow: hidden; + margin-top: 0.5rem; +} `; const Speaking = styled.div` From 08de34217934907e29577026d5909ee7e762549a Mon Sep 17 00:00:00 2001 From: imdt Date: Thu, 3 Aug 2023 11:32:27 -0300 Subject: [PATCH 127/252] Adjustment --- .../imports/ui/components/nav-bar/talking-indicator/styles.js | 1 - 1 file changed, 1 deletion(-) diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/styles.js b/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/styles.js index d549f8255c..214b7d3caf 100644 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/styles.js +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/styles.js @@ -143,7 +143,6 @@ const IsTalkingWrapper = styled.div` position: relative; overflow: hidden; margin-top: 0.5rem; -} `; const Speaking = styled.div` From 44a0c2d10cf8645b8f3ba9e9fd036a0432465213 Mon Sep 17 00:00:00 2001 From: imdt Date: Thu, 3 Aug 2023 11:48:00 -0300 Subject: [PATCH 128/252] Client: Margin of timer adjusted to better fitting size --- .../imports/ui/components/timer/indicator/styles.js | 1 + 1 file changed, 1 insertion(+) diff --git a/bigbluebutton-html5/imports/ui/components/timer/indicator/styles.js b/bigbluebutton-html5/imports/ui/components/timer/indicator/styles.js index 6385f59a27..2778f18346 100644 --- a/bigbluebutton-html5/imports/ui/components/timer/indicator/styles.js +++ b/bigbluebutton-html5/imports/ui/components/timer/indicator/styles.js @@ -22,6 +22,7 @@ const TimerWrapper = styled.div` `; const Timer = styled.div` + margin-top: 1rem; display: flex; max-height: ${timerPaddingXL}); `; From bee0fdecb0dc6eedcfd0ee36d70e8751cca8550f Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Thu, 3 Aug 2023 11:58:57 -0300 Subject: [PATCH 129/252] Test if it will ignore previous cache --- .../main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala index 0399856b39..821bd6652e 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala @@ -106,6 +106,7 @@ class BigBlueButtonActor( } def handleCreateMeetingReqMsg(msg: CreateMeetingReqMsg): Unit = { + //test log.debug("RECEIVED CreateMeetingReqMsg msg {}", msg) RunningMeetings.findWithId(meetings, msg.body.props.meetingProp.intId) match { From 7abd79c8c5ac8a26c70c48ffd77fb9812ca90aaf Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Thu, 3 Aug 2023 13:28:56 -0300 Subject: [PATCH 130/252] Test if it will ignore previous cache --- .../main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala index 821bd6652e..0399856b39 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala @@ -106,7 +106,6 @@ class BigBlueButtonActor( } def handleCreateMeetingReqMsg(msg: CreateMeetingReqMsg): Unit = { - //test log.debug("RECEIVED CreateMeetingReqMsg msg {}", msg) RunningMeetings.findWithId(meetings, msg.body.props.meetingProp.intId) match { From 4318ed981fe7f8abbdb9a6349a1b99c4ecf66ba3 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Thu, 3 Aug 2023 13:35:30 -0300 Subject: [PATCH 131/252] Include comments about th new env vars --- .github/workflows/automated-tests.yml | 56 +++++++++++---------------- build/setup.sh | 3 +- 2 files changed, 24 insertions(+), 35 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index aa7e2eb886..9ae6165a18 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -25,8 +25,8 @@ jobs: - run: echo "CACHE_AKKA_APPS_KEY=$(git log -1 --format=%H -- akka-bbb-apps)" >> $GITHUB_ENV - run: echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -49,8 +49,8 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - run: | ./build/get_external_dependencies.sh ./build/setup.sh bbb-config @@ -64,8 +64,8 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - run: | ./build/get_external_dependencies.sh ./build/setup.sh bbb-export-annotations @@ -83,8 +83,8 @@ jobs: fetch-depth: 0 # Fetch all history - run: echo "CACHE_LEARNING_DASHBOARD_KEY=$(git log -1 --format=%H -- bbb-learning-dashboard)" >> $GITHUB_ENV - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -107,8 +107,8 @@ jobs: steps: - uses: actions/checkout@v3 - run: ./build/get_external_dependencies.sh - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - run: ./build/setup.sh bbb-playback - run: ./build/setup.sh bbb-playback-notes - run: ./build/setup.sh bbb-playback-podcast @@ -136,8 +136,8 @@ jobs: - run: echo "CACHE_URL3_KEY=$(curl -s https://api.github.com/repos/mconf/ep_redis_publisher/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - run: echo "CACHE_URL4_KEY=$(curl -s https://api.github.com/repos/alangecker/bbb-etherpad-skin/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -166,8 +166,8 @@ jobs: - run: echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV - run: echo "CACHE_COMMON_WEB_KEY=$(git log -1 --format=%H -- bbb-common-web)" >> $GITHUB_ENV - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -195,8 +195,8 @@ jobs: - run: echo "CACHE_AKKA_FSESL_KEY=$(git log -1 --format=%H -- akka-bbb-fsesl)" >> $GITHUB_ENV - run: echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -223,8 +223,8 @@ jobs: fetch-depth: 0 # Fetch all history - run: echo "CACHE_KEY=$(git log -1 --format=%H -- bigbluebutton-html5)" >> $GITHUB_ENV - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -254,8 +254,8 @@ jobs: - run: echo "CACHE_FREESWITCH_SOUNDS_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-sounds)" >> $GITHUB_ENV - run: echo "CACHE_SOUNDS_KEY=$(curl -Is http://bigbluebutton.org/downloads/sounds.tar.gz | grep "Last-Modified" | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -280,8 +280,8 @@ jobs: steps: - uses: actions/checkout@v3 - run: ./build/get_external_dependencies.sh - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV + - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - run: ./build/setup.sh bbb-mkclean - run: ./build/setup.sh bbb-pads - run: ./build/setup.sh bbb-libreoffice-docker @@ -374,18 +374,6 @@ jobs: echo "----ls artifacts/----" ls artifacts/ echo "Done" - # COMMIT_DATE="$(git log -n1 --pretty='format:%cd' --date=format:'%Y%m%dT%H%M%S')" - # VERSION_NUMBER="$(cat bigbluebutton-config/bigbluebutton-release | cut -d '=' -f2 | cut -d "-" -f1)" - # VERSION_ADDON="$(cat bigbluebutton-config/bigbluebutton-release | cut -d '=' -f2 | cut -d "-" -f2)" - # GIT_REV=$(git rev-parse HEAD) - # GIT_REV="local-build-${GIT_REV:0:10}" - # VERSION="${VERSION_NUMBER}~${VERSION_ADDON}+${COMMIT_DATE}-git.${GIT_REV}" - # echo "COMMIT_DATE: $COMMIT_DATE" - # echo "VERSION_NUMBER: $VERSION_NUMBER" - # echo "VERSION_ADDON: $VERSION_ADDON" - # echo "GIT_REV: $GIT_REV" - # echo "VERSION: $VERSION" - - name: Generate CA run: | sudo -i < Date: Thu, 3 Aug 2023 15:05:56 -0300 Subject: [PATCH 132/252] changes on the download presentation tests --- bigbluebutton-tests/playwright/core/settings.js | 3 ++- .../playwright/parameters/parameters.spec.js | 5 ++--- .../playwright/presentation/presentation.js | 8 ++++---- .../playwright/presentation/presentation.spec.js | 8 +++----- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/bigbluebutton-tests/playwright/core/settings.js b/bigbluebutton-tests/playwright/core/settings.js index 8398444701..dda2826a96 100644 --- a/bigbluebutton-tests/playwright/core/settings.js +++ b/bigbluebutton-tests/playwright/core/settings.js @@ -28,7 +28,8 @@ async function generateSettingsData(page) { pollEnabled: settingsData.poll.enabled, pollChatMessage: settingsData.poll.chatMessage, // Presentation - presentationDownloadable: settingsData.presentation.allowDownloadable, + originalPresentationDownloadable: settingsData.presentation.allowDownloadOriginal, + presentationWithAnnotationsDownloadable: settingsData.presentation.allowDownloadWithAnnotations, externalVideoPlayer: settingsData.externalVideoPlayer.enabled, presentationHidden: settingsData.layout.hidePresentation, // Screensharing diff --git a/bigbluebutton-tests/playwright/parameters/parameters.spec.js b/bigbluebutton-tests/playwright/parameters/parameters.spec.js index 452499f864..12dab266f9 100644 --- a/bigbluebutton-tests/playwright/parameters/parameters.spec.js +++ b/bigbluebutton-tests/playwright/parameters/parameters.spec.js @@ -247,13 +247,12 @@ test.describe.parallel('Create Parameters', () => { }); test.describe.serial(() => { - test('Download Presentation With Annotations @flaky', async ({ browser, context, page }) => { - linkIssue('18408'); + test('Download Presentation With Annotations', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); await disabledFeatures.initModPage(page, true, { createParameter: c.downloadPresentationWithAnnotationsDisabled }); await disabledFeatures.downloadPresentationWithAnnotations(); }); - test('Download Presentation With Annotations (exclude) @flaky', async ({ browser, context, page }) => { + test('Download Presentation With Annotations (exclude)', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); await disabledFeatures.initModPage(page, true, { createParameter: c.downloadPresentationWithAnnotationsExclude }); await disabledFeatures.downloadPresentationWithAnnotationsExclude(); diff --git a/bigbluebutton-tests/playwright/presentation/presentation.js b/bigbluebutton-tests/playwright/presentation/presentation.js index b13b17a6e5..0dd72ded7f 100644 --- a/bigbluebutton-tests/playwright/presentation/presentation.js +++ b/bigbluebutton-tests/playwright/presentation/presentation.js @@ -122,8 +122,8 @@ class Presentation extends MultiUsers { } async enableAndDisablePresentationDownload(testInfo) { - const { presentationDownloadable } = getSettings(); - test.fail(!presentationDownloadable, 'Presentation download is disable'); + const { originalPresentationDownloadable } = getSettings(); + test.fail(!originalPresentationDownloadable, 'Presentation download is disable'); await this.modPage.waitForSelector(e.whiteboard, ELEMENT_WAIT_LONGER_TIME); // enable original presentation download @@ -152,8 +152,8 @@ class Presentation extends MultiUsers { } async sendPresentationToDownload(testInfo) { - const { presentationDownloadable } = getSettings(); - test.fail(!presentationDownloadable, 'Presentation download is disable'); + const { presentationWithAnnotationsDownloadable } = getSettings(); + test.fail(!presentationWithAnnotationsDownloadable, 'Presentation download is disable'); await this.modPage.waitForSelector(e.whiteboard, ELEMENT_WAIT_LONGER_TIME); await this.modPage.waitAndClick(e.actions); diff --git a/bigbluebutton-tests/playwright/presentation/presentation.spec.js b/bigbluebutton-tests/playwright/presentation/presentation.spec.js index 57f83e6a40..fa1252fe4e 100644 --- a/bigbluebutton-tests/playwright/presentation/presentation.spec.js +++ b/bigbluebutton-tests/playwright/presentation/presentation.spec.js @@ -84,15 +84,13 @@ test.describe.parallel('Presentation', () => { }); // https://docs.bigbluebutton.org/2.6/release-tests.html#enabling-and-disabling-presentation-download-automated - // flaky: update is needed due to changes made on https://github.com/bigbluebutton/bigbluebutton/pull/18411 - test('Enable and disable original presentation download @flaky', async ({ browser, context, page }, testInfo) => { + test('Enable and disable original presentation download', async ({ browser, context, page }, testInfo) => { const presentation = new Presentation(browser, context); await presentation.initPages(page); await presentation.enableAndDisablePresentationDownload(testInfo); }); - - // flaky: update is needed due to changes made on https://github.com/bigbluebutton/bigbluebutton/pull/18411 - test('Send presentation in the current state (with annotations) to chat for downloading @flaky', async ({ browser, context, page }, testInfo) => { + + test('Send presentation in the current state (with annotations) to chat for downloading ', async ({ browser, context, page }, testInfo) => { const presentation = new Presentation(browser, context); await presentation.initPages(page); await presentation.sendPresentationToDownload(testInfo); From 38b353d2c859edf2a43c79c1b82c62f761840c21 Mon Sep 17 00:00:00 2001 From: Gabriel Porfirio Date: Thu, 3 Aug 2023 15:46:03 -0300 Subject: [PATCH 133/252] adding the presentation tests on ci again --- .../playwright/presentation/presentation.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-tests/playwright/presentation/presentation.spec.js b/bigbluebutton-tests/playwright/presentation/presentation.spec.js index fa1252fe4e..80753b36c7 100644 --- a/bigbluebutton-tests/playwright/presentation/presentation.spec.js +++ b/bigbluebutton-tests/playwright/presentation/presentation.spec.js @@ -84,13 +84,13 @@ test.describe.parallel('Presentation', () => { }); // https://docs.bigbluebutton.org/2.6/release-tests.html#enabling-and-disabling-presentation-download-automated - test('Enable and disable original presentation download', async ({ browser, context, page }, testInfo) => { + test('Enable and disable original presentation download @ci', async ({ browser, context, page }, testInfo) => { const presentation = new Presentation(browser, context); await presentation.initPages(page); await presentation.enableAndDisablePresentationDownload(testInfo); }); - test('Send presentation in the current state (with annotations) to chat for downloading ', async ({ browser, context, page }, testInfo) => { + test('Send presentation in the current state (with annotations) to chat for downloading @ci', async ({ browser, context, page }, testInfo) => { const presentation = new Presentation(browser, context); await presentation.initPages(page); await presentation.sendPresentationToDownload(testInfo); From 95e3bba2f194030a8b628e68333dddc6d1bc6fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Thu, 3 Aug 2023 15:48:47 -0300 Subject: [PATCH 134/252] make reactions bar accessible by keyboard --- .../reactions-button/component.jsx | 95 ++++++++++++++++--- .../reactions-button}/styles.js | 55 +++++------ .../ui/components/common/menu/component.jsx | 24 +++-- .../ui/components/common/menu/styles.js | 26 +++++ .../emoji-picker/reactions-bar/component.jsx | 94 ------------------ 5 files changed, 152 insertions(+), 142 deletions(-) rename bigbluebutton-html5/imports/ui/components/{emoji-picker/reactions-bar => actions-bar/reactions-button}/styles.js (64%) delete mode 100644 bigbluebutton-html5/imports/ui/components/emoji-picker/reactions-bar/component.jsx diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx index 406e8b9103..a71510a30a 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx @@ -2,11 +2,11 @@ import React, { useState } from 'react'; import { defineMessages } from 'react-intl'; import PropTypes from 'prop-types'; import BBBMenu from '/imports/ui/components/common/menu/component'; -import ReactionsBar from '/imports/ui/components/emoji-picker/reactions-bar/component'; import UserReactionService from '/imports/ui/components/user-reaction/service'; import UserListService from '/imports/ui/components/user-list/service'; +import { Emoji } from 'emoji-mart'; -import Styled from '../styles'; +import Styled from './styles'; const ReactionsButton = (props) => { const { @@ -25,6 +25,14 @@ const ReactionsButton = (props) => { id: 'app.actionsBar.reactions.reactionsButtonLabel', description: 'reactions Label', }, + raiseHandLabel: { + id: 'app.actionsBar.reactions.raiseHand', + description: 'raise Hand Label', + }, + notRaiseHandLabel: { + id: 'app.actionsBar.reactions.lowHand', + description: 'not Raise Hand Label', + }, }); const handleClose = () => { @@ -43,17 +51,76 @@ const ReactionsButton = (props) => { UserListService.setUserRaiseHand(userId, !raiseHand); }; - const renderReactionsBar = () => ( - - - - ); + const RaiseHandButtonLabel = () => { + if (isMobile) return null; - const customStyles = { top: '-1rem', borderRadius: '1.7rem' }; + return raiseHand + ? intl.formatMessage(intlMessages.notRaiseHandLabel) + : intl.formatMessage(intlMessages.raiseHandLabel); + }; + + const customStyles = { + top: '-1rem', + borderRadius: '1.7rem', + }; + + const actionCustomStyles = { + paddingLeft: 0, + paddingRight: 0, + paddingTop: isMobile ? '0' : '0.5rem', + paddingBottom: isMobile ? '0' : '0.5rem', + }; + + const emojiProps = { + native: true, + size: '1.5rem', + }; + + const reactions = [ + { + id: 'smiley', + native: '😃', + }, + { + id: 'neutral_face', + native: '😐', + }, + { + id: 'slightly_frowning_face', + native: '🙁', + }, + { + id: '+1', + native: '👍', + }, + { + id: '-1', + native: '👎', + }, + { + id: 'clap', + native: '👏', + }, + ]; + + let actions = []; + + reactions.forEach(({ id, native }) => { + actions.push({ + label: , + key: id, + dataTest: 'leaveAudio', + onClick: () => handleReactionSelect(native), + customStyles: actionCustomStyles, + }); + }); + + actions.push({ + label: {RaiseHandButtonLabel()}, + key: 'hand', + onClick: () => handleRaiseHandButtonClick(), + customStyles: {...actionCustomStyles, width: 'auto'}, + }); return ( { /> )} - renderOtherComponents={showEmojiPicker ? renderReactionsBar() : null} + actions={actions} onCloseCallback={() => handleClose()} customAnchorEl={!isMobile ? actionsBarRef.current : null} customStyles={customStyles} open={showEmojiPicker} hasRoundedCorners overrideMobileStyles + isHorizontal={!isMobile} + isMobile={isMobile} opts={{ id: 'reactions-dropdown-menu', keepMounted: true, diff --git a/bigbluebutton-html5/imports/ui/components/emoji-picker/reactions-bar/styles.js b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/styles.js similarity index 64% rename from bigbluebutton-html5/imports/ui/components/emoji-picker/reactions-bar/styles.js rename to bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/styles.js index a1150e8a52..2430b1e9c1 100644 --- a/bigbluebutton-html5/imports/ui/components/emoji-picker/reactions-bar/styles.js +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/styles.js @@ -1,4 +1,7 @@ import styled from 'styled-components'; +import { colorWhite } from '/imports/ui/stylesheets/styled-components/palette'; +import Button from '/imports/ui/components/common/button/component'; + import { colorOffWhite, colorGrayLightest, @@ -7,15 +10,18 @@ import { btnPrimaryBg, } from '/imports/ui/stylesheets/styled-components/palette'; -const Wrapper = styled.div` - display: flex; - padding: .5rem; +const RaiseHandButton = styled(Button)` +${({ ghost }) => ghost && ` + & > span { + box-shadow: none; + background-color: transparent !important; + border-color: ${colorWhite} !important; + } + `} +`; - ${({ isMobile }) => isMobile && ` - flex-direction: column; - align-items: center; - padding: .5rem 0; - `} +const ReactionsDropdown = styled.div` + position: relative; `; const ButtonWrapper = styled.div` @@ -38,8 +44,7 @@ const ButtonWrapper = styled.div` } & > * > span { - height: 1.8rem !important; - width: 1.8rem !important; + margin-left: 4px; } ${({ active }) => active && ` @@ -56,10 +61,18 @@ const ButtonWrapper = styled.div` `; const RaiseHandButtonWrapper = styled(ButtonWrapper)` - width: auto; - border: 1px solid ${colorGrayLightest}; + width: 2.5rem; border-radius: 1.7rem; - padding: 1rem 0.5rem; + + & > * > span { + margin-left: 5px; + } + + ${({ isMobile }) => !isMobile && ` + border: 1px solid ${colorGrayLightest}; + padding: 1rem 0.5rem; + width: auto; + `} ${({ active }) => active && ` color: ${btnPrimaryColor}; @@ -71,23 +84,11 @@ const RaiseHandButtonWrapper = styled(ButtonWrapper)` background-color: ${btnPrimaryHoverBg} !important; } `} -` - -const Separator = styled.div` - height: 2.5rem; - width: 0; - border: 1px solid ${colorGrayLightest}; - align-self: center; - opacity: .75; - - ${({ isMobile }) => isMobile && ` - display: none; - `} `; export default { - Wrapper, + RaiseHandButton, + ReactionsDropdown, ButtonWrapper, RaiseHandButtonWrapper, - Separator, }; diff --git a/bigbluebutton-html5/imports/ui/components/common/menu/component.jsx b/bigbluebutton-html5/imports/ui/components/common/menu/component.jsx index 55685416a2..931a7401fe 100644 --- a/bigbluebutton-html5/imports/ui/components/common/menu/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/common/menu/component.jsx @@ -1,8 +1,6 @@ import React from "react"; import PropTypes from "prop-types"; import { defineMessages, injectIntl } from "react-intl"; - -import Menu from "@mui/material/Menu"; import { Divider } from "@mui/material"; import Icon from "/imports/ui/components/common/icon/component"; import { SMALL_VIEWPORT_BREAKPOINT } from '/imports/ui/components/layout/enums'; @@ -48,22 +46,25 @@ class BBBMenu extends React.Component { handleKeyDown(event) { const { anchorEl } = this.state; + const { isHorizontal } = this.props; const isMenuOpen = Boolean(anchorEl); + const previousKey = isHorizontal ? KEY_CODES.ARROW_LEFT : KEY_CODES.ARROW_UP; + const nextKey = isHorizontal ? KEY_CODES.ARROW_RIGHT : KEY_CODES.ARROW_DOWN; if ([KEY_CODES.ESCAPE, KEY_CODES.TAB].includes(event.which)) { this.handleClose(); return; } - if (isMenuOpen && [KEY_CODES.ARROW_UP, KEY_CODES.ARROW_DOWN].includes(event.which)) { + if (isMenuOpen && [previousKey, nextKey].includes(event.which)) { event.preventDefault(); event.stopPropagation(); const menuItems = Array.from(document.querySelectorAll('[data-key^="menuItem-"]')); if (menuItems.length === 0) return; const focusedIndex = menuItems.findIndex(item => item === document.activeElement); - const nextIndex = event.which === KEY_CODES.ARROW_UP ? focusedIndex - 1 : focusedIndex + 1; + const nextIndex = event.which === previousKey ? focusedIndex - 1 : focusedIndex + 1; let indexToFocus = 0; if (nextIndex < 0) { indexToFocus = menuItems.length - 1; @@ -97,7 +98,7 @@ class BBBMenu extends React.Component { }; makeMenuItems() { - const { actions, selectedEmoji, intl } = this.props; + const { actions, selectedEmoji, intl, isHorizontal, isMobile } = this.props; return actions?.map(a => { const { dataTest, label, onClick, key, disabled, description, selected } = a; @@ -136,7 +137,7 @@ class BBBMenu extends React.Component { }}> {a.icon ? : null} - {label} + {label} {description &&
{`${description}${selected ? ` - ${intl.formatMessage(intlMessages.active)}` : ''}`}
} {a.iconRight ? : null}
@@ -160,6 +161,7 @@ class BBBMenu extends React.Component { customAnchorEl, hasRoundedCorners, overrideMobileStyles, + isHorizontal, } = this.props; const actionsItems = this.makeMenuItems(); @@ -170,6 +172,11 @@ class BBBMenu extends React.Component { menuStyles = { ...menuStyles, ...customStyles }; } + if (isHorizontal) { + const horizontalStyles = { display: 'flex' }; + menuStyles = { ...menuStyles, ...horizontalStyles}; + } + return ( <>
- } - + ); } diff --git a/bigbluebutton-html5/imports/ui/components/common/menu/styles.js b/bigbluebutton-html5/imports/ui/components/common/menu/styles.js index 8f7bb44314..9d8547a033 100644 --- a/bigbluebutton-html5/imports/ui/components/common/menu/styles.js +++ b/bigbluebutton-html5/imports/ui/components/common/menu/styles.js @@ -5,6 +5,26 @@ import MenuItem from "@mui/material/MenuItem"; import { colorWhite, colorPrimary } from '/imports/ui/stylesheets/styled-components/palette'; import { fontSizeLarge } from '/imports/ui/stylesheets/styled-components/typography'; import { mediumUp } from '/imports/ui/stylesheets/styled-components/breakpoints'; +import Menu from "@mui/material/Menu"; + +const MenuWrapper = styled(Menu)` + ${({ isMobile }) => isMobile && ` + flex-direction: column; + align-items: center; + padding: .5rem 0; + `} + + ${({ isHorizontal, isMobile }) => (isHorizontal || isMobile) && ` + ul { + display: flex; + } + + li:hover { + background-color: unset !important; + } + + `} +`; const MenuItemWrapper = styled.div` display: flex; @@ -25,6 +45,11 @@ const Option = styled.div` margin-right: .5rem; margin-left: 1.65rem; } + + ${({ isHorizontal, isMobile }) => (isHorizontal || isMobile) && ` + margin-right: 0; + margin-left: 0; + `} `; const CloseButton = styled(Button)` @@ -85,6 +110,7 @@ const BBBMenuItem = styled(MenuItem)` `; export default { + MenuWrapper, MenuItemWrapper, Option, CloseButton, diff --git a/bigbluebutton-html5/imports/ui/components/emoji-picker/reactions-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/emoji-picker/reactions-bar/component.jsx deleted file mode 100644 index 4589c34a99..0000000000 --- a/bigbluebutton-html5/imports/ui/components/emoji-picker/reactions-bar/component.jsx +++ /dev/null @@ -1,94 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { injectIntl, defineMessages } from 'react-intl'; -import { Emoji } from 'emoji-mart'; -import 'emoji-mart/css/emoji-mart.css'; -import Styled from './styles'; - -const propTypes = { - intl: PropTypes.shape({ - formatMessage: PropTypes.func.isRequired, - }).isRequired, - onEmojiSelect: PropTypes.func.isRequired, -}; - -const intlMessages = defineMessages({ - raiseHandLabel: { - id: 'app.actionsBar.reactions.raiseHand', - description: 'raise Hand Label', - }, - notRaiseHandLabel: { - id: 'app.actionsBar.reactions.lowHand', - description: 'not Raise Hand Label', - }, -}); - -const reactions = [ - { - id: 'smiley', - native: '😃', - }, - { - id: 'neutral_face', - native: '😐', - }, - { - id: 'slightly_frowning_face', - native: '🙁', - }, - { - id: '+1', - native: '👍', - }, - { - id: '-1', - native: '👎', - }, - { - id: 'clap', - native: '👏', - }, -]; - -const ReactionsPicker = (props) => { - const { - intl, - onReactionSelect, - onRaiseHand, - raiseHand, - isMobile, - currentUserReaction, - } = props; - - const RaiseHandButtonLabel = () => { - if (isMobile) return null; - - return raiseHand - ? intl.formatMessage(intlMessages.notRaiseHandLabel) - : intl.formatMessage(intlMessages.raiseHandLabel); - }; - - const emojiProps = { - native: true, - size: '1.5rem', - }; - - return ( - - {reactions.map(({ id, native }) => ( - - onReactionSelect(native)} {...emojiProps} /> - - ))} - - onRaiseHand()} active={raiseHand}> - - {RaiseHandButtonLabel()} - - - ); -}; - -ReactionsPicker.propTypes = propTypes; - -export default injectIntl(ReactionsPicker); From 163e18b8a355634b1b44804c014e9b0dc6a0e2bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Castro?= <36093456+Scroody@users.noreply.github.com> Date: Thu, 3 Aug 2023 16:15:46 -0300 Subject: [PATCH 135/252] Adjustments to the timer --- .../imports/ui/components/timer/indicator/styles.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/ui/components/timer/indicator/styles.js b/bigbluebutton-html5/imports/ui/components/timer/indicator/styles.js index 2778f18346..810f449c58 100644 --- a/bigbluebutton-html5/imports/ui/components/timer/indicator/styles.js +++ b/bigbluebutton-html5/imports/ui/components/timer/indicator/styles.js @@ -22,7 +22,7 @@ const TimerWrapper = styled.div` `; const Timer = styled.div` - margin-top: 1rem; + margin-top: 0.5rem; display: flex; max-height: ${timerPaddingXL}); `; From 36df64bf2cc7d130bdde2cb3e0f6ec3e368834e7 Mon Sep 17 00:00:00 2001 From: Anton Georgiev Date: Thu, 3 Aug 2023 15:59:19 -0400 Subject: [PATCH 136/252] refactor: Reworded the label for download pres+annotations --- bigbluebutton-html5/public/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/public/locales/en.json b/bigbluebutton-html5/public/locales/en.json index ee4d068ce3..727e5235f1 100755 --- a/bigbluebutton-html5/public/locales/en.json +++ b/bigbluebutton-html5/public/locales/en.json @@ -287,7 +287,7 @@ "app.presentationUploader.sent": "Sent", "app.presentationUploader.exportingTimeout": "The export is taking too long...", "app.presentationUploader.export": "Send to chat", - "app.presentationUploader.exportCurrentStatePresentation": "Send out a download link for the presentation in the current state of it", + "app.presentationUploader.exportCurrentStatePresentation": "Send out a download link for the presentation in its current state", "app.presentationUploader.enableOriginalPresentationDownload": "Enable download of the original presentation", "app.presentationUploader.disableOriginalPresentationDownload": "Disable download of the original presentation", "app.presentationUploader.dropdownExportOptions": "Export options", From dca7fe1997b1acc4b728c20e4701676b70bfc147 Mon Sep 17 00:00:00 2001 From: KDSBrowne Date: Thu, 3 Aug 2023 20:30:23 +0000 Subject: [PATCH 137/252] fix slide positon when zooming out after pan (presenter) --- .../ui/components/whiteboard/component.jsx | 38 +++---------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx index 8321daa07c..59b5c8da8c 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx @@ -104,7 +104,6 @@ export default function Whiteboard(props) { const language = mapLanguage(Settings?.application?.locale?.toLowerCase() || 'en'); const [currentTool, setCurrentTool] = React.useState(null); const [currentStyle, setCurrentStyle] = React.useState({}); - const [currentCameraPoint, setCurrentCameraPoint] = React.useState({}); const [isMoving, setIsMoving] = React.useState(false); const [isPanning, setIsPanning] = React.useState(shortcutPanning); const [panSelected, setPanSelected] = React.useState(isPanning); @@ -472,16 +471,12 @@ export default function Whiteboard(props) { } }, [tldrawAPI?.getPageState()?.camera, presentationWidth, presentationHeight]); - // change tldraw page when presentation page changes React.useEffect(() => { - if (tldrawAPI && curPageId && slidePosition) { - tldrawAPI.changePage(curPageId); - const newZoom = prevSlidePosition - ? calculateZoom(prevSlidePosition.viewBoxWidth, prevSlidePosition.viewBoxHeight) - : calculateZoom(slidePosition.viewBoxWidth, slidePosition.viewBoxHeight); - tldrawAPI?.setCamera([slidePosition.x, slidePosition.y], newZoom, 'zoomed_previous_page'); + if (isPresenter && slidePosition) { + const currentZoom = calculateZoom(slidePosition?.viewBoxWidth, slidePosition?.viewBoxHeight); + tldrawAPI?.setCamera([slidePosition?.x, slidePosition?.y], currentZoom); } - }, [curPageId]); + }, [slidePosition?.viewBoxWidth, slidePosition?.viewBoxHeight]); // change tldraw camera when slidePosition changes React.useEffect(() => { @@ -812,10 +807,6 @@ export default function Whiteboard(props) { if (reason && isPresenter && slidePosition && (reason.includes('zoomed') || reason.includes('panned'))) { const camera = tldrawAPI?.getPageState()?.camera; - const isForcePanning = tldrawAPI?.isForcePanning; - if (currentCameraPoint[curPageId] && !isPanning && !isForcePanning) { - camera.point = currentCameraPoint[curPageId]; - } // limit bounds if (tldrawAPI?.viewport.maxX > slidePosition.width) { @@ -854,20 +845,13 @@ export default function Whiteboard(props) { viewedRegionH = HUNDRED_PERCENT; } - if (e?.currentPageId == curPageId) { - setCurrentCameraPoint({ - ...currentCameraPoint, - [e?.currentPageId]: camera?.point, - }) - } - zoomSlide( parseInt(curPageId, 10), podId, viewedRegionW, viewedRegionH, - currentCameraPoint[curPageId] ? currentCameraPoint[curPageId][0] : camera.point[0], - currentCameraPoint[curPageId] ? currentCameraPoint[curPageId][1] : camera.point[1], + camera.point[0], + camera.point[1], ); } // don't allow non-presenters to pan&zoom @@ -1006,16 +990,6 @@ export default function Whiteboard(props) { setCurrentStyle({ ...currentStyle, ...command?.after?.appState?.currentStyle }); } - if (command && command?.id?.includes('change_page')) { - const camera = tldrawAPI?.getPageState()?.camera; - if (currentCameraPoint[app?.currentPageId] && camera) { - tldrawAPI?.setCamera( - [currentCameraPoint[app?.currentPageId][0], currentCameraPoint[app?.currentPageId][1]], - camera?.zoom - ); - } - } - const changedShapes = command.after?.document?.pages[app.currentPageId]?.shapes; if (!isMounting && app.currentPageId !== curPageId) { // can happen then the "move to page action" is called, or using undo after changing a page From 14c83bb6d3f233531e8f7b27a2bc54be965551dd Mon Sep 17 00:00:00 2001 From: imdt Date: Fri, 4 Aug 2023 10:41:07 -0300 Subject: [PATCH 138/252] Adjustments --- .../ui/components/settings/submenus/application/component.jsx | 4 ++-- bigbluebutton-html5/public/locales/en.json | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx index bee1c7d336..c00dd65193 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx @@ -123,8 +123,8 @@ const intlMessages = defineMessages({ description: 'label for custom layout style (push to all)', }, disableLabel: { - id: 'app.videoDock.webcamDisableLabel', - } + id: 'app.videoDock.webcamDisableLabelAllCams', + }, }); class ApplicationMenu extends BaseMenu { diff --git a/bigbluebutton-html5/public/locales/en.json b/bigbluebutton-html5/public/locales/en.json index ee4d068ce3..333906fa21 100755 --- a/bigbluebutton-html5/public/locales/en.json +++ b/bigbluebutton-html5/public/locales/en.json @@ -1155,6 +1155,7 @@ "app.videoDock.webcamUnfocusLabel": "Unfocus", "app.videoDock.webcamUnfocusDesc": "Unfocus the selected webcam", "app.videoDock.webcamDisableLabel": "Disable self-view", + "app.videoDock.webcamDisableLabelAllCams": "Disable self-view (all cameras)", "app.videoDock.webcamEnableLabel": "Enable self-view", "app.videoDock.webcamDisableDesc": "Self-view disabled", "app.videoDock.webcamPinLabel": "Pin", From 632b7764bfe1ed5a56bcb316ded04daf9da23dc0 Mon Sep 17 00:00:00 2001 From: KDSBrowne Date: Fri, 4 Aug 2023 16:41:42 +0000 Subject: [PATCH 139/252] move fitToWidth state up to app component (#17982) --- .../actions-dropdown/component.jsx | 2 ++ .../ui/components/actions-bar/component.jsx | 2 ++ .../imports/ui/components/app/component.jsx | 11 +++++++++- .../ui/components/presentation/component.jsx | 22 +++++++------------ .../presentation-area/component.jsx | 4 +++- .../presentation-area/container.jsx | 4 ++-- 6 files changed, 27 insertions(+), 18 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx index d87edbf71a..4ea3354d0c 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx @@ -249,6 +249,7 @@ class ActionsDropdown extends PureComponent { presentations, setPresentation, podIds, + setPresentationFitToWidth, } = this.props; if (!podIds || podIds.length < 1) return []; @@ -272,6 +273,7 @@ class ActionsDropdown extends PureComponent { description: "uploaded presentation file", key: `uploaded-presentation-${p.id}`, onClick: () => { + setPresentationFitToWidth(false); setPresentation(p.id, podId); }, } diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx index d0e1d262ff..d1b9b216e1 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx @@ -39,6 +39,7 @@ class ActionsBar extends PureComponent { setMeetingLayout, showPushLayout, setPushLayout, + setPresentationFitToWidth, } = this.props; const shouldShowOptionsButton = (isPresentationEnabled() && isThereCurrentPresentation) @@ -67,6 +68,7 @@ class ActionsBar extends PureComponent { setPushLayout, presentationIsOpen, showPushLayout, + setPresentationFitToWidth, }} /> {isCaptionsAvailable diff --git a/bigbluebutton-html5/imports/ui/components/app/component.jsx b/bigbluebutton-html5/imports/ui/components/app/component.jsx index 5a62095241..572465eb82 100644 --- a/bigbluebutton-html5/imports/ui/components/app/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/app/component.jsx @@ -131,8 +131,10 @@ class App extends Component { super(props); this.state = { enableResize: !window.matchMedia(MOBILE_MEDIA).matches, + presentationFitToWidth: false, }; + this.setPresentationFitToWidth = this.setPresentationFitToWidth.bind(this); this.handleWindowResize = throttle(this.handleWindowResize).bind(this); this.shouldAriaHide = this.shouldAriaHide.bind(this); @@ -283,6 +285,10 @@ class App extends Component { ConnectionStatusService.stopRoundTripTime(); } + setPresentationFitToWidth(presentationFitToWidth) { + this.setState({ presentationFitToWidth }); + } + handleWindowResize() { const { enableResize } = this.state; const shouldEnableResize = !window.matchMedia(MOBILE_MEDIA).matches; @@ -403,6 +409,7 @@ class App extends Component { setMeetingLayout={setMeetingLayout} showPushLayout={showPushLayoutButton && selectedLayout === 'custom'} presentationIsOpen={presentationIsOpen} + setPresentationFitToWidth={this.setPresentationFitToWidth} /> ); @@ -517,6 +524,8 @@ class App extends Component { darkTheme, } = this.props; + const { presentationFitToWidth } = this.state; + return ( <> @@ -540,7 +549,7 @@ class App extends Component { - {shouldShowPresentation ? : null} + {shouldShowPresentation ? : null} {shouldShowScreenshare ? : null} { shouldShowExternalVideo diff --git a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx index bf46dca8d1..1e58599077 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx @@ -74,7 +74,6 @@ class Presentation extends PureComponent { presentationWidth: 0, presentationHeight: 0, zoom: 100, - fitToWidth: false, isFullscreen: false, tldrawAPI: null, isPanning: false, @@ -86,7 +85,6 @@ class Presentation extends PureComponent { this.currentPresentationToastId = null; this.getSvgRef = this.getSvgRef.bind(this); - this.setFitToWidth = this.setFitToWidth.bind(this); this.zoomChanger = debounce({ delay: 200 }, this.zoomChanger.bind(this)); this.updateLocalPosition = this.updateLocalPosition.bind(this); this.panAndZoomChanger = this.panAndZoomChanger.bind(this); @@ -207,13 +205,13 @@ class Presentation extends PureComponent { multiUser, numPages, currentPresentationId, + fitToWidth, } = this.props; const { presentationWidth, presentationHeight, zoom, isPanning, - fitToWidth, presentationId, hadPresentation, } = this.state; @@ -505,19 +503,14 @@ class Presentation extends PureComponent { } } - setFitToWidth(fitToWidth) { - this.setState({ fitToWidth }); - } - zoomChanger(zoom) { this.setState({ zoom }); } fitToWidthHandler() { - const { fitToWidth } = this.state; - + const { setPresentationFitToWidth, fitToWidth } = this.props; + setPresentationFitToWidth(!fitToWidth) this.setState({ - fitToWidth: !fitToWidth, zoom: HUNDRED_PERCENT, }); } @@ -535,9 +528,9 @@ class Presentation extends PureComponent { } calculateSize(viewBoxDimensions) { - const { presentationHeight, presentationWidth, fitToWidth } = this.state; + const { presentationHeight, presentationWidth } = this.state; - const { userIsPresenter, currentSlide, slidePosition } = this.props; + const { userIsPresenter, currentSlide, slidePosition, fitToWidth } = this.props; if (!currentSlide || !slidePosition) { return { width: 0, height: 0 }; @@ -605,8 +598,9 @@ class Presentation extends PureComponent { removeWhiteboardGlobalAccess, multiUserSize, multiUser, + fitToWidth, } = this.props; - const { zoom, fitToWidth, isPanning } = this.state; + const { zoom, isPanning } = this.state; if (!currentSlide) return null; @@ -733,12 +727,12 @@ class Presentation extends PureComponent { layoutContextDispatch, presentationIsOpen, darkTheme, + fitToWidth, } = this.props; const { isFullscreen, localPosition, - fitToWidth, zoom, tldrawIsMounting, isPanning, diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-area/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-area/component.jsx index cd351d329f..43e4e0a515 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-area/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-area/component.jsx @@ -7,13 +7,15 @@ const PresentationArea = ({ height, presentationIsOpen, darkTheme, + setPresentationFitToWidth, + fitToWidth, }) => { const presentationAreaSize = { presentationAreaWidth: width, presentationAreaHeight: height, }; return ( - + ); }; diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-area/container.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-area/container.jsx index fc76dfad02..8b8a66004d 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-area/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-area/container.jsx @@ -3,10 +3,10 @@ import PropTypes from 'prop-types'; import { layoutSelectOutput } from '../../layout/context'; import PresentationArea from './component'; -const PresentationAreaContainer = ({ presentationIsOpen, darkTheme }) => { +const PresentationAreaContainer = ({ presentationIsOpen, darkTheme, setPresentationFitToWidth, fitToWidth }) => { const presentation = layoutSelectOutput((i) => i.presentation); - return ; + return ; }; export default PresentationAreaContainer; From 56c4657c4624416a8e4f07a99b50fa360fb969e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Fri, 4 Aug 2023 16:14:57 -0300 Subject: [PATCH 140/252] adjust styles --- .../actions-bar/reactions-button/component.jsx | 2 ++ .../imports/ui/components/common/menu/component.jsx | 5 +++-- .../imports/ui/components/common/menu/styles.js | 9 +++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx index a71510a30a..bc6a0fe8dd 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx @@ -150,6 +150,8 @@ const ReactionsButton = (props) => { overrideMobileStyles isHorizontal={!isMobile} isMobile={isMobile} + roundButtons={true} + keepOpen={true} opts={{ id: 'reactions-dropdown-menu', keepMounted: true, diff --git a/bigbluebutton-html5/imports/ui/components/common/menu/component.jsx b/bigbluebutton-html5/imports/ui/components/common/menu/component.jsx index 931a7401fe..a1e5d6e3e3 100644 --- a/bigbluebutton-html5/imports/ui/components/common/menu/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/common/menu/component.jsx @@ -98,7 +98,7 @@ class BBBMenu extends React.Component { }; makeMenuItems() { - const { actions, selectedEmoji, intl, isHorizontal, isMobile } = this.props; + const { actions, selectedEmoji, intl, isHorizontal, isMobile, roundButtons, keepOpen } = this.props; return actions?.map(a => { const { dataTest, label, onClick, key, disabled, description, selected } = a; @@ -128,9 +128,10 @@ class BBBMenu extends React.Component { disableGutters={true} disabled={disabled} style={customStyles} + roundButtons={roundButtons} onClick={(event) => { onClick(); - const close = !key?.includes('setstatus') && !key?.includes('back'); + const close = !keepOpen && !key?.includes('setstatus') && !key?.includes('back'); // prevent menu close for sub menu actions if (close) this.handleClose(event); event.stopPropagation(); diff --git a/bigbluebutton-html5/imports/ui/components/common/menu/styles.js b/bigbluebutton-html5/imports/ui/components/common/menu/styles.js index 9d8547a033..bd39933385 100644 --- a/bigbluebutton-html5/imports/ui/components/common/menu/styles.js +++ b/bigbluebutton-html5/imports/ui/components/common/menu/styles.js @@ -107,6 +107,15 @@ const BBBMenuItem = styled(MenuItem)` } } `} + ${({ roundButtons }) => roundButtons && ` + &:focus, + &:hover { + background-color: ${colorWhite} !important; + div div div { + background-color: ${colorPrimary} !important; + } + } + `} `; export default { From ce22c1894de5ffa8e50fafd958c2a82b4cac455d Mon Sep 17 00:00:00 2001 From: GuiLeme Date: Fri, 4 Aug 2023 17:38:17 -0300 Subject: [PATCH 141/252] [issue-18446] - fix download of original presentation --- .../presentationpod/MakePresentationDownloadReqMsgHdlr.scala | 2 +- .../imports/ui/components/presentation/service.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/MakePresentationDownloadReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/MakePresentationDownloadReqMsgHdlr.scala index c9bba73a77..a27c898078 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/MakePresentationDownloadReqMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/MakePresentationDownloadReqMsgHdlr.scala @@ -164,7 +164,7 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait { PresentationSender.broadcastSetPresentationDownloadableEvtMsg(bus, meetingId, "DEFAULT_PRESENTATION_POD", "not-used", presId, true, filename) - val fileURI = List("bigbluebutton", "presentation", "download", meetingId, s"${presId}?presFilename=${presId}.${presFilenameExt}&filename=${filename}").mkString("", File.separator, "") + val fileURI = List("presentation", "download", meetingId, s"${presId}?presFilename=${presId}.${presFilenameExt}&filename=${filename}").mkString("", File.separator, "") val event = buildNewPresFileAvailable(fileURI, presId, m.body.typeOfExport) handle(event, liveMeeting, bus) diff --git a/bigbluebutton-html5/imports/ui/components/presentation/service.js b/bigbluebutton-html5/imports/ui/components/presentation/service.js index 014a1ae2b8..0123e4e853 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/service.js +++ b/bigbluebutton-html5/imports/ui/components/presentation/service.js @@ -6,6 +6,7 @@ import { safeMatch } from '/imports/utils/string-utils'; const POLL_SETTINGS = Meteor.settings.public.poll; const MAX_CUSTOM_FIELDS = POLL_SETTINGS.maxCustom; const MAX_CHAR_LIMIT = POLL_SETTINGS.maxTypedAnswerLength; +const APP = Meteor.settings.public.app; const getCurrentPresentation = (podId) => Presentations.findOne({ podId, @@ -19,7 +20,7 @@ const downloadPresentationUri = (podId) => { } const { originalFileURI: uri } = currentPresentation; - return uri; + return `${APP.bbbWebBase}/${uri}`; }; const isPresentationDownloadable = (podId) => { From c2ade1a1210cacee7368e88ffb8e797ccf3d3c37 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 10:12:05 +0000 Subject: [PATCH 142/252] Translate en.json in fr 100% translated source file: 'en.json' on 'fr'. --- bigbluebutton-html5/public/locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/public/locales/fr.json b/bigbluebutton-html5/public/locales/fr.json index b82aa050af..3783ef48f0 100644 --- a/bigbluebutton-html5/public/locales/fr.json +++ b/bigbluebutton-html5/public/locales/fr.json @@ -561,7 +561,7 @@ "app.statusNotifier.raisedHandDesc": "{0} ont levé la main", "app.statusNotifier.raisedHandDescOneUser": "{0} a levé la main", "app.statusNotifier.and": "et", - "app.switch.onLabel": "Allumé", + "app.switch.onLabel": "Activé", "app.switch.offLabel": "Éteint", "app.talkingIndicator.ariaMuteDesc" : "Sélectionner pour rendre l'utilisateur silencieux", "app.talkingIndicator.isTalking" : "{0} est en train de parler", From d60aa16d4c7d1f645e5386d16391cba82889764d Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 10:12:41 +0000 Subject: [PATCH 143/252] Translate en.json in fr 100% translated source file: 'en.json' on 'fr'. --- bigbluebutton-html5/public/locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/public/locales/fr.json b/bigbluebutton-html5/public/locales/fr.json index 3783ef48f0..3dd33e0df0 100644 --- a/bigbluebutton-html5/public/locales/fr.json +++ b/bigbluebutton-html5/public/locales/fr.json @@ -562,7 +562,7 @@ "app.statusNotifier.raisedHandDescOneUser": "{0} a levé la main", "app.statusNotifier.and": "et", "app.switch.onLabel": "Activé", - "app.switch.offLabel": "Éteint", + "app.switch.offLabel": "Désactivé", "app.talkingIndicator.ariaMuteDesc" : "Sélectionner pour rendre l'utilisateur silencieux", "app.talkingIndicator.isTalking" : "{0} est en train de parler", "app.talkingIndicator.moreThanMaxIndicatorsTalking" : "{0}+ parlent", From c68da552d25e13a2873ef43641978e2e718fe5d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Mon, 7 Aug 2023 11:34:35 -0300 Subject: [PATCH 144/252] break lines in breakout room checkbox --- .../components/actions-bar/create-breakout-room/styles.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.js b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.js index 95cfbfa2ee..302a9d51a6 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.js +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/styles.js @@ -57,7 +57,7 @@ const FreeJoinLabel = styled.label` display: flex; align-items: center; font-size: ${fontSizeSmall}; - margin-bottom: 0; + margin-bottom: 0.2rem; & > * { margin: 0 .5rem 0 0; @@ -125,9 +125,9 @@ const RoomName = styled(BreakoutNameInput)` const BreakoutSettings = styled.div` display: grid; - grid-template-columns: 1fr 1fr 1fr; + grid-template-columns: 1fr 1fr 2fr; grid-template-rows: 1fr; - grid-gap: 4rem; + grid-gap: 2rem; @media ${smallOnly} { grid-template-columns: 1fr ; @@ -235,7 +235,6 @@ const AssignBtns = styled(Button)` `; const CheckBoxesContainer = styled(FlexRow)` - white-space: nowrap; display: flex; flex-flow: column; justify-content: flex-end; From aa549a0f58649a1deab5c9758946f5bc1bc075c9 Mon Sep 17 00:00:00 2001 From: Gabriel Porfirio Date: Mon, 7 Aug 2023 14:08:26 -0300 Subject: [PATCH 145/252] check for right click on whiteboard --- bigbluebutton-tests/playwright/core/elements.js | 1 + bigbluebutton-tests/playwright/whiteboard/draw.js | 5 +++++ bigbluebutton-tests/playwright/whiteboard/whiteboard.spec.js | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bigbluebutton-tests/playwright/core/elements.js b/bigbluebutton-tests/playwright/core/elements.js index 0bd13e43f6..fe9491c256 100644 --- a/bigbluebutton-tests/playwright/core/elements.js +++ b/bigbluebutton-tests/playwright/core/elements.js @@ -472,6 +472,7 @@ exports.wbColorRed = 'button[id="TD-Styles-Color-Swatch-red"]'; exports.wbFillDrawing = 'button[id="TD-Styles-Fill"]'; exports.wbDashDotted = 'div[id="TD-Styles-Dash-dotted"]'; exports.wbSizeLarge = 'div[id="TD-Styles-Dash-large"]'; +exports.wbPaste = 'button[id="TD-ContextMenu-Paste"]'; // About modal exports.showAboutModalButton = 'li[data-test="aboutModal"]'; diff --git a/bigbluebutton-tests/playwright/whiteboard/draw.js b/bigbluebutton-tests/playwright/whiteboard/draw.js index 0496383f83..163d17b436 100644 --- a/bigbluebutton-tests/playwright/whiteboard/draw.js +++ b/bigbluebutton-tests/playwright/whiteboard/draw.js @@ -16,6 +16,11 @@ class Draw extends Page { const shapes1 = await this.getOuterHtmlDrawn(); const wbBox = await this.getElementBoundingBox(e.whiteboard); + + await this.page.mouse.click(wbBox.x + 0.3 * wbBox.width, wbBox.y + 0.3 * wbBox.height,{button: 'right'}); + const pasteLocator = this.page.locator(e.wbPaste); + await expect(pasteLocator).toBeVisible(); + await this.page.mouse.move(wbBox.x + 0.3 * wbBox.width, wbBox.y + 0.3 * wbBox.height); await this.page.mouse.down(); await this.page.mouse.move(wbBox.x + 0.7 * wbBox.width, wbBox.y + 0.7 * wbBox.height); diff --git a/bigbluebutton-tests/playwright/whiteboard/whiteboard.spec.js b/bigbluebutton-tests/playwright/whiteboard/whiteboard.spec.js index 0fb16e8672..3e5d591e22 100644 --- a/bigbluebutton-tests/playwright/whiteboard/whiteboard.spec.js +++ b/bigbluebutton-tests/playwright/whiteboard/whiteboard.spec.js @@ -27,7 +27,7 @@ test.describe.parallel('Whiteboard @ci', () => { const draw = new Draw(browser, page); await draw.init(true, true); await draw.test(); - }) + }); test('Give Additional Whiteboard Access', async ({ browser, context, page }) => { const multiusers = new MultiUsers(browser, context); From cc09894924272b3210ff9585fcb256c2671a86a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Mon, 7 Aug 2023 16:49:03 -0300 Subject: [PATCH 146/252] add raise hand and away emoji in userlist --- .../user-participants/user-list-item/component.jsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx index 5fd72e1e80..7e3d0690c6 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx @@ -4,6 +4,7 @@ import { injectIntl, defineMessages } from 'react-intl'; import TooltipContainer from '/imports/ui/components/common/tooltip/container'; import { Session } from 'meteor/session'; import { findDOMNode } from 'react-dom'; +import { Emoji } from 'emoji-mart'; import UserAvatar from '/imports/ui/components/user-avatar/component'; import Icon from '/imports/ui/components/common/icon/component'; import lockContextContainer from '/imports/ui/components/lock-viewers/context/container'; @@ -631,13 +632,18 @@ class UserListItem extends PureComponent { voiceUser, } = this.props; + const emojiProps = { + native: true, + size: '1.3rem', + }; + let userAvatarFiltered = user.avatar; const getIconUser = () => { if (user.raiseHand === true) { - return ; + return ; } if (user.away === true) { - return ; + return ; } if (user.emoji !== 'none' && user.emoji !== 'notAway') { return ; } if (user.reaction !== 'none') { From 9f2276b668f5b865595f78722a470eb9dda85446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Mon, 7 Aug 2023 17:02:49 -0300 Subject: [PATCH 147/252] only display emoji if reactions are enabled --- .../user-participants/user-list-item/component.jsx | 9 +++++++-- .../user-participants/user-list-item/container.jsx | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx index 7e3d0690c6..598191108d 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/component.jsx @@ -630,6 +630,7 @@ class UserListItem extends PureComponent { breakoutSequence, meetingIsBreakout, voiceUser, + isReactionsEnabled, } = this.props; const emojiProps = { @@ -641,9 +642,13 @@ class UserListItem extends PureComponent { const getIconUser = () => { if (user.raiseHand === true) { - return ; + return isReactionsEnabled + ? + : ; } if (user.away === true) { - return ; + return isReactionsEnabled + ? + : ; } if (user.emoji !== 'none' && user.emoji !== 'notAway') { return ; } if (user.reaction !== 'none') { diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/container.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/container.jsx index e5d93a482a..0644eb124a 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/container.jsx @@ -4,6 +4,7 @@ import BreakoutService from '/imports/ui/components/breakout-room/service'; import Auth from '/imports/ui/services/auth'; import UserListItem from './component'; import UserListService from '/imports/ui/components/user-list/service'; +import UserReactionService from '/imports/ui/components/user-reaction/service'; import { layoutDispatch } from '../../../../layout/context'; const UserListItemContainer = (props) => { @@ -57,5 +58,6 @@ export default withTracker(({ user }) => { getEmoji: UserListService.getEmoji(), usersProp: UserListService.getUsersProp(), selectedUserId: Session.get('dropdownOpenUserId'), + isReactionsEnabled: UserReactionService.isEnabled(), }; })(UserListItemContainer); From 8feb934169c032d5a0ea3d377c91dde15b92fec3 Mon Sep 17 00:00:00 2001 From: prlanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Mon, 7 Aug 2023 15:28:17 -0300 Subject: [PATCH 148/252] feat(audio): add experimental transparent listen only mode This is an initial, experimental implementation of the feature proposed in https://github.com/bigbluebutton/bigbluebutton/issues/14021. The intention is to phase out the explicit listen only mode with two overarching goals: - Reduce UX friction and increase familiarity: the existence of a separate listen only mode is a source of confusion for the majority of users Reduce average server-side CPU usage while also making it possible for having full audio-only meetings. The proof-of-concept works based on the assumption that a "many concurrent active talkers" scenario is both rare and not useful. With that in mind, this including two server-side triggers: - On microphone inactivity (currently mute action that is sustained for 4 seconds, configurable): FreeSWITCH channels are held (which translates to much lower CPU usage, virtually 0%). Receiving channels are switched, server side, to a listening mode (SFU, mediasoup). * This required an extension to mediasoup two allow re-assigning producers to already established consumers. No re-negotiation is done. - On microphone activity (currently unmute action, immediate): FreeSWITCH channels are unheld, listening mode is deactivated and the mute state is updated accordingly (in this order). This is *off by default*. It needs to be enabled in two places: - `/etc/bigbluebutton/bbb-webrtc-sfu/production.yml` -> `transparentListenOnly: true` - End users: * Server wide: `/etc/bigbluebutton/bbb-html5.yml` -> `public.media.transparentListenOnly: true` * Per user: `userdata-bbb_transparent_listen_only=true` --- .../bigbluebutton/SystemConfiguration.scala | 1 + .../UserConnectedToGlobalAudioMsgHdlr.scala | 6 +- ...hannelHoldChangedVoiceConfEvtMsgHdlr.scala | 21 +++ ...ListenOnlyModeToggledInSfuEvtMsgHdlr.scala | 25 +++ .../voice/UserJoinedVoiceConfEvtMsgHdlr.scala | 4 +- .../core/apps/voice/VoiceApp.scala | 155 ++++++++++++++++-- .../core/apps/voice/VoiceApp2x.scala | 4 +- .../core/models/VoiceUsers.scala | 23 ++- .../senders/ReceivedJsonMsgHandlerActor.scala | 4 + .../core/running/MeetingActor.scala | 4 + .../bigbluebutton/core2/AnalyticsActor.scala | 4 + .../core2/FromAkkaAppsMsgSenderActor.scala | 4 + .../guests/GuestsWaitingApprovedMsgHdlr.scala | 4 +- .../core2/message/senders/MsgBuilder.scala | 30 ++++ .../core2/testdata/FakeUserGenerator.scala | 8 +- .../core2/testdata/TestDataGen.scala | 8 +- .../src/universal/conf/application.conf | 6 +- .../FreeswitchConferenceEventListener.java | 14 +- .../voice/IVoiceConferenceService.java | 9 +- .../voice/events/ChannelHoldChangedEvent.java | 51 ++++++ .../freeswitch/voice/events/ConfMember.java | 8 +- .../voice/events/VoiceUserJoinedEvent.java | 16 +- .../voice/freeswitch/ConnectionManager.java | 8 + .../voice/freeswitch/ESLEventListener.java | 109 ++++++++++-- .../freeswitch/FreeswitchApplication.java | 10 ++ .../actions/GetAllUsersCommand.java | 4 +- .../actions/GetUsersStatusCommand.java | 4 +- .../actions/HoldChannelCommand.java | 44 +++++ .../freeswitch/response/ConferenceMember.java | 4 + .../response/ConferenceMemberFlags.java | 8 + .../XMLResponseConferenceListParser.java | 2 + .../freeswitch/RxJsonMsgDeserializer.scala | 17 ++ .../freeswitch/RxJsonMsgHdlrActor.scala | 2 + .../freeswitch/VoiceConferenceService.scala | 36 +++- .../common2/msgs/VoiceConfMsgs.scala | 70 +++++++- bbb-webrtc-sfu.placeholder.sh | 2 +- .../audio/client/bridge/sfu-audio-bridge.js | 17 +- .../server/methods/addUserSettings.js | 1 + .../services/bbb-webrtc-sfu/audio-broker.js | 2 + .../imports/ui/services/webrtc-base/peer.js | 22 +++ .../private/config/settings.yml | 9 +- 41 files changed, 727 insertions(+), 53 deletions(-) create mode 100644 akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/ChannelHoldChangedVoiceConfEvtMsgHdlr.scala create mode 100644 akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/ListenOnlyModeToggledInSfuEvtMsgHdlr.scala create mode 100644 akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/ChannelHoldChangedEvent.java create mode 100644 akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/HoldChannelCommand.java diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala index d1de9769a9..0df81de5df 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala @@ -41,6 +41,7 @@ trait SystemConfiguration { lazy val syncVoiceUsersStatusInterval = Try(config.getInt("voiceConf.syncUserStatusInterval")).getOrElse(43) lazy val ejectRogueVoiceUsers = Try(config.getBoolean("voiceConf.ejectRogueVoiceUsers")).getOrElse(true) lazy val dialInApprovalAudioPath = Try(config.getString("voiceConf.dialInApprovalAudioPath")).getOrElse("ivr/ivr-please_hold_while_party_contacted.wav") + lazy val toggleListenOnlyAfterMuteTimer = Try(config.getInt("voiceConf.toggleListenOnlyAfterMuteTimer")).getOrElse(4) lazy val recordingChapterBreakLengthInMinutes = Try(config.getInt("recording.chapterBreakLengthInMinutes")).getOrElse(0) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserConnectedToGlobalAudioMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserConnectedToGlobalAudioMsgHdlr.scala index 63a6f57abb..0685f7e7f1 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserConnectedToGlobalAudioMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserConnectedToGlobalAudioMsgHdlr.scala @@ -9,9 +9,9 @@ trait UserConnectedToGlobalAudioMsgHdlr { val outGW: OutMsgRouter - def handleUserConnectedToGlobalAudioMsg(msg: UserConnectedToGlobalAudioMsg) { + def handleUserConnectedToGlobalAudioMsg(msg: UserConnectedToGlobalAudioMsg): Unit = { log.info("Handling UserConnectedToGlobalAudio: meetingId=" + props.meetingProp.intId + " userId=" + msg.body.userId) - + def broadcastEvent(vu: VoiceUserState): Unit = { val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, props.meetingProp.intId, vu.intId) @@ -44,6 +44,8 @@ trait UserConnectedToGlobalAudioMsgHdlr { System.currentTimeMillis(), floor = false, lastFloorTime = "0", + hold = false, + uuid = "unused" ) VoiceUsers.add(liveMeeting.voiceUsers, vu) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/ChannelHoldChangedVoiceConfEvtMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/ChannelHoldChangedVoiceConfEvtMsgHdlr.scala new file mode 100644 index 0000000000..41383d9c4f --- /dev/null +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/ChannelHoldChangedVoiceConfEvtMsgHdlr.scala @@ -0,0 +1,21 @@ +package org.bigbluebutton.core.apps.voice + +import org.bigbluebutton.common2.msgs._ +import org.bigbluebutton.core.running.{ MeetingActor, LiveMeeting, OutMsgRouter } + +trait ChannelHoldChangedVoiceConfEvtMsgHdlr { + this: MeetingActor => + + val liveMeeting: LiveMeeting + val outGW: OutMsgRouter + + def handleChannelHoldChangedVoiceConfEvtMsg(msg: ChannelHoldChangedVoiceConfEvtMsg): Unit = { + VoiceApp.handleChannelHoldChanged( + liveMeeting, + outGW, + msg.body.intId, + msg.body.uuid, + msg.body.hold + ) + } +} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/ListenOnlyModeToggledInSfuEvtMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/ListenOnlyModeToggledInSfuEvtMsgHdlr.scala new file mode 100644 index 0000000000..0b16a39a61 --- /dev/null +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/ListenOnlyModeToggledInSfuEvtMsgHdlr.scala @@ -0,0 +1,25 @@ +package org.bigbluebutton.core.apps.voice + +import org.bigbluebutton.common2.msgs._ +import org.bigbluebutton.core.models.VoiceUsers +import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting, OutMsgRouter } + +trait ListenOnlyModeToggledInSfuEvtMsgHdlr { + this: BaseMeetingActor => + + val liveMeeting: LiveMeeting + val outGW: OutMsgRouter + + def handleListenOnlyModeToggledInSfuEvtMsg(msg: ListenOnlyModeToggledInSfuEvtMsg): Unit = { + for { + vu <- VoiceUsers.findWithIntId(liveMeeting.voiceUsers, msg.body.userId) + } yield { + VoiceApp.holdChannelInVoiceConf( + liveMeeting, + outGW, + vu.uuid, + msg.body.enabled + ) + } + } +} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserJoinedVoiceConfEvtMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserJoinedVoiceConfEvtMsgHdlr.scala index 4f18ade3a5..3d838beba7 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserJoinedVoiceConfEvtMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserJoinedVoiceConfEvtMsgHdlr.scala @@ -94,7 +94,9 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration { userColor, msg.body.muted, msg.body.talking, - "freeswitch" + "freeswitch", + msg.body.hold, + msg.body.uuid ) } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp.scala index 96fe80b810..1b004c2867 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp.scala @@ -1,5 +1,6 @@ package org.bigbluebutton.core.apps.voice +import akka.actor.{ ActorSystem, Cancellable } import org.bigbluebutton.SystemConfiguration import org.bigbluebutton.LockSettingsUtil import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers @@ -12,9 +13,14 @@ import org.bigbluebutton.core.models._ import org.bigbluebutton.core.apps.users.UsersApp import org.bigbluebutton.core.util.ColorPicker import org.bigbluebutton.core.util.TimeUtil +import scala.collection.immutable.Map +import scala.concurrent.duration._ object VoiceApp extends SystemConfiguration { + // Key is userId + var toggleListenOnlyTasks: Map[String, Cancellable] = Map() + def genRecordPath( recordDir: String, meetingId: String, @@ -104,7 +110,7 @@ object VoiceApp extends SystemConfiguration { outGW: OutMsgRouter, voiceUserId: String, muted: Boolean - ): Unit = { + )(implicit context: akka.actor.ActorContext): Unit = { for { mutedUser <- VoiceUsers.userMuted(liveMeeting.voiceUsers, voiceUserId, muted) } yield { @@ -117,13 +123,32 @@ object VoiceApp extends SystemConfiguration { ) } - broadcastUserMutedVoiceEvtMsg( - liveMeeting.props.meetingProp.intId, - mutedUser, - liveMeeting.props.voiceProp.voiceConf, - outGW + // Ask for the audio channel to be switched to listen only mode + // if the user is muted, otherwise switch back to normal mode + // This is only effective if the "transparent listen only" mode is active + // for the target user. + toggleListenOnlyMode( + liveMeeting, + outGW, + mutedUser.intId, + muted, + toggleListenOnlyAfterMuteTimer ) + // If the user is muted or unmuted with an unheld channel, broadcast + // the event right away. + // If the user is unmuted, but channel is held, we need to wait for the + // channel to be active again to broadcast the event. See + // VoiceApp.handleChannelHoldChanged for this second case. + if (muted || (!muted && !mutedUser.hold)) { + broadcastUserMutedVoiceEvtMsg( + liveMeeting.props.meetingProp.intId, + mutedUser, + liveMeeting.props.voiceProp.voiceConf, + outGW + ) + } + } } @@ -132,7 +157,7 @@ object VoiceApp extends SystemConfiguration { outGW: OutMsgRouter, eventBus: InternalEventBus, users: Vector[ConfVoiceUser] - ): Unit = { + )(implicit context: akka.actor.ActorContext): Unit = { users foreach { cvu => VoiceUsers.findWithVoiceUserId( liveMeeting.voiceUsers, @@ -179,7 +204,9 @@ object VoiceApp extends SystemConfiguration { ColorPicker.nextColor(liveMeeting.props.meetingProp.intId), cvu.muted, cvu.talking, - cvu.calledInto + cvu.calledInto, + cvu.hold, + cvu.uuid, ) } } @@ -229,7 +256,9 @@ object VoiceApp extends SystemConfiguration { color: String, muted: Boolean, talking: Boolean, - callingInto: String + callingInto: String, + hold: Boolean, + uuid: String = "unused" ): Unit = { def broadcastEvent(voiceUserState: VoiceUserState): Unit = { @@ -289,7 +318,9 @@ object VoiceApp extends SystemConfiguration { callingInto, System.currentTimeMillis(), floor = false, - lastFloorTime = "0" + lastFloorTime = "0", + hold, + uuid ) VoiceUsers.add(liveMeeting.voiceUsers, voiceUserState) @@ -431,4 +462,108 @@ object VoiceApp extends SystemConfiguration { ) outGW.send(deafEvent) } + + def removeToggleListenOnlyTask(userId: String): Unit = { + toggleListenOnlyTasks get userId match { + case Some(task) => + task.cancel() + toggleListenOnlyTasks = toggleListenOnlyTasks - userId + case _ => + } + } + + def toggleListenOnlyMode( + liveMeeting: LiveMeeting, + outGW: OutMsgRouter, + userId: String, + enabled: Boolean, + delay: Int = 0 + )(implicit context: akka.actor.ActorContext): Unit = { + implicit def executionContext = context.system.dispatcher + def broacastEvent(): Unit = { + val event = MsgBuilder.buildToggleListenOnlyModeSysMsg( + liveMeeting.props.meetingProp.intId, + liveMeeting.props.voiceProp.voiceConf, + userId, + enabled + ) + outGW.send(event) + } + + // Guarantee there are no other tasks for this channel + removeToggleListenOnlyTask(userId) + + if (enabled && delay > 0) { + // If we are enabling listen only mode, we wait a bit before actually + // dispatching the command - the idea is that recently muted users + // are more likely to unmute themselves right after the action, so this + // should make frequent mute-unmute transitions smoother. + // This is just one of the heuristics we have to implement for this to + // work seamlessly, but it's a start. - prlanzarin Aug 04 2023 + val newTask = context.system.scheduler.scheduleOnce(delay seconds) { + broacastEvent() + removeToggleListenOnlyTask(userId) + } + + toggleListenOnlyTasks = toggleListenOnlyTasks + (userId -> newTask) + } else { + // If we are disabling listen only mode, we can broadcast the event + // right away + broacastEvent() + } + } + + def holdChannelInVoiceConf( + liveMeeting: LiveMeeting, + outGW: OutMsgRouter, + uuid: String, + hold: Boolean + ): Unit = { + val event = MsgBuilder.buildHoldChannelInVoiceConfSysMsg( + liveMeeting.props.meetingProp.intId, + liveMeeting.props.voiceProp.voiceConf, + uuid, + hold + ) + + outGW.send(event) + } + + def handleChannelHoldChanged( + liveMeeting: LiveMeeting, + outGW: OutMsgRouter, + intId: String, + uuid: String, + hold: Boolean + )(implicit context: akka.actor.ActorContext): Unit = { + VoiceUsers.holdStateChanged( + liveMeeting.voiceUsers, + intId, + uuid, + hold + ) match { + case Some(vu) => + // Mute vs hold state mismatch, enforce hold state again. + // Mute state is the predominant one here. + if (vu.muted != hold) { + toggleListenOnlyMode( + liveMeeting, + outGW, + intId, + vu.muted + ) + } + + // User unmuted and channel is not on hold, broadcast user unmuted + if (!vu.muted && !vu.hold) { + broadcastUserMutedVoiceEvtMsg( + liveMeeting.props.meetingProp.intId, + vu, + liveMeeting.props.voiceProp.voiceConf, + outGW + ) + } + case _ => + } + } } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp2x.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp2x.scala index 409f29112a..301f0041cb 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp2x.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp2x.scala @@ -20,7 +20,9 @@ trait VoiceApp2x extends UserJoinedVoiceConfEvtMsgHdlr with SyncGetVoiceUsersMsgHdlr with AudioFloorChangedVoiceConfEvtMsgHdlr with VoiceConfCallStateEvtMsgHdlr - with UserStatusVoiceConfEvtMsgHdlr { + with UserStatusVoiceConfEvtMsgHdlr + with ChannelHoldChangedVoiceConfEvtMsgHdlr + with ListenOnlyModeToggledInSfuEvtMsgHdlr { this: MeetingActor => } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/VoiceUsers.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/VoiceUsers.scala index 9886fba1c4..8b9adbb15f 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/VoiceUsers.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/VoiceUsers.scala @@ -11,6 +11,10 @@ object VoiceUsers { users.toVector.find(u => u.intId == intId) } + def findWithIntIdAndUUID(users: VoiceUsers, intId: String, uuid: String): Option[VoiceUserState] = { + users.toVector.find(u => u.uuid == uuid && u.intId == intId) + } + def findAll(users: VoiceUsers): Vector[VoiceUserState] = users.toVector def findAllNonListenOnlyVoiceUsers(users: VoiceUsers): Vector[VoiceUserState] = users.toVector.filter(u => u.listenOnly == false) @@ -91,6 +95,17 @@ object VoiceUsers { } } + def holdStateChanged(users: VoiceUsers, intId: String, uuid: String, hold: Boolean): Option[VoiceUserState] = { + for { + u <- findWithIntIdAndUUID(users, intId, uuid) + } yield { + val vu = u.modify(_.hold).setTo(hold) + .modify(_.lastStatusUpdateOn).setTo(System.currentTimeMillis()) + users.save(vu) + vu + } + } + def setLastStatusUpdate(users: VoiceUsers, user: VoiceUserState): VoiceUserState = { val vu = user.copy(lastStatusUpdateOn = System.currentTimeMillis()) users.save(vu) @@ -165,7 +180,9 @@ case class VoiceUserVO2x( callingWith: String, listenOnly: Boolean, floor: Boolean, - lastFloorTime: String + lastFloorTime: String, + hold: Boolean, + uuid: String ) case class VoiceUserState( @@ -181,5 +198,7 @@ case class VoiceUserState( calledInto: String, lastStatusUpdateOn: Long, floor: Boolean, - lastFloorTime: String + lastFloorTime: String, + hold: Boolean, + uuid: String ) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala index 8136191e4b..0897b4efd1 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala @@ -223,6 +223,10 @@ class ReceivedJsonMsgHandlerActor( routeGenericMsg[GetGlobalAudioPermissionReqMsg](envelope, jsonNode) case GetMicrophonePermissionReqMsg.NAME => routeGenericMsg[GetMicrophonePermissionReqMsg](envelope, jsonNode) + case ChannelHoldChangedVoiceConfEvtMsg.NAME => + routeVoiceMsg[ChannelHoldChangedVoiceConfEvtMsg](envelope, jsonNode) + case ListenOnlyModeToggledInSfuEvtMsg.NAME => + routeVoiceMsg[ListenOnlyModeToggledInSfuEvtMsg](envelope, jsonNode) // Breakout rooms case BreakoutRoomsListMsg.NAME => diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala index 6715a5237c..09732efe7d 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala @@ -484,6 +484,10 @@ class MeetingActor( handleGetGlobalAudioPermissionReqMsg(m) case m: GetMicrophonePermissionReqMsg => handleGetMicrophonePermissionReqMsg(m) + case m: ChannelHoldChangedVoiceConfEvtMsg => + handleChannelHoldChangedVoiceConfEvtMsg(m) + case m: ListenOnlyModeToggledInSfuEvtMsg => + handleListenOnlyModeToggledInSfuEvtMsg(m) // Layout case m: GetCurrentLayoutReqMsg => handleGetCurrentLayoutReqMsg(m) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala index f0178d229a..d4f305dda5 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala @@ -102,6 +102,10 @@ class AnalyticsActor(val includeChat: Boolean) extends Actor with ActorLogging { logMessage(msg) case m: VoiceConfCallStateEvtMsg => logMessage(msg) case m: VoiceCallStateEvtMsg => logMessage(msg) + case m: HoldChannelInVoiceConfSysMsg => logMessage(msg) + case m: ChannelHoldChangedVoiceConfEvtMsg => logMessage(msg) + case m: ToggleListenOnlyModeSysMsg => logMessage(msg) + case m: ListenOnlyModeToggledInSfuEvtMsg => logMessage(msg) // Breakout case m: BreakoutRoomEndedEvtMsg => logMessage(msg) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/FromAkkaAppsMsgSenderActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/FromAkkaAppsMsgSenderActor.scala index 31f495e4a9..7f7b005de2 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/FromAkkaAppsMsgSenderActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/FromAkkaAppsMsgSenderActor.scala @@ -67,6 +67,8 @@ class FromAkkaAppsMsgSenderActor(msgSender: MessageSender) msgSender.send(toVoiceConfRedisChannel, json) case GetUsersStatusToVoiceConfSysMsg.NAME => msgSender.send(toVoiceConfRedisChannel, json) + case HoldChannelInVoiceConfSysMsg.NAME => + msgSender.send(toVoiceConfRedisChannel, json) // Sent to SFU case EjectUserFromSfuSysMsg.NAME => @@ -75,6 +77,8 @@ class FromAkkaAppsMsgSenderActor(msgSender: MessageSender) msgSender.send(toSfuRedisChannel, json) case CamStreamUnsubscribeSysMsg.NAME => msgSender.send(toSfuRedisChannel, json) + case ToggleListenOnlyModeSysMsg.NAME => + msgSender.send(toSfuRedisChannel, json) //================================================================== // Send chat, presentation, and whiteboard in different channels so as not to diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/guests/GuestsWaitingApprovedMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/guests/GuestsWaitingApprovedMsgHdlr.scala index 491e476800..80ed7e2236 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/guests/GuestsWaitingApprovedMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/guests/GuestsWaitingApprovedMsgHdlr.scala @@ -45,7 +45,9 @@ trait GuestsWaitingApprovedMsgHdlr extends HandlerHelpers with RightsManagementT dialInUser.color, MeetingStatus2x.isMeetingMuted(liveMeeting.status), false, - "freeswitch" + "freeswitch", + false, + "unused" ) VoiceUsers.findWithIntId( liveMeeting.voiceUsers, diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala index 0f452ba146..de5e3e7389 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala @@ -618,4 +618,34 @@ object MsgBuilder { BbbCommonEnvCoreMsg(envelope, event) } + def buildHoldChannelInVoiceConfSysMsg( + meetingId: String, + voiceConf: String, + uuid: String, + hold: Boolean + ): BbbCommonEnvCoreMsg = { + val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka") + val envelope = BbbCoreEnvelope(HoldChannelInVoiceConfSysMsg.NAME, routing) + val body = HoldChannelInVoiceConfSysMsgBody(voiceConf, uuid, hold) + val header = BbbCoreHeaderWithMeetingId(HoldChannelInVoiceConfSysMsg.NAME, meetingId) + val event = HoldChannelInVoiceConfSysMsg(header, body) + + BbbCommonEnvCoreMsg(envelope, event) + } + + def buildToggleListenOnlyModeSysMsg( + meetingId: String, + voiceConf: String, + userId: String, + enabled: Boolean + ): BbbCommonEnvCoreMsg = { + val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka") + val envelope = BbbCoreEnvelope(ToggleListenOnlyModeSysMsg.NAME, routing) + val body = ToggleListenOnlyModeSysMsgBody(voiceConf, userId, enabled) + val header = BbbCoreHeaderWithMeetingId(ToggleListenOnlyModeSysMsg.NAME, meetingId) + val event = ToggleListenOnlyModeSysMsg(header, body) + + BbbCommonEnvCoreMsg(envelope, event) + } + } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/testdata/FakeUserGenerator.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/testdata/FakeUserGenerator.scala index 85fb84d61f..3ac1a2d50f 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/testdata/FakeUserGenerator.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/testdata/FakeUserGenerator.scala @@ -65,7 +65,9 @@ object FakeUserGenerator { val voiceUserId = RandomStringGenerator.randomAlphanumericString(8) val lastFloorTime = System.currentTimeMillis().toString(); VoiceUserState(intId = user.id, voiceUserId = voiceUserId, callingWith, callerName = user.name, - callerNum = user.name, "#ff6242", muted, talking, listenOnly, "freeswitch", System.currentTimeMillis(), floor, lastFloorTime) + callerNum = user.name, "#ff6242", muted, talking, listenOnly, "freeswitch", System.currentTimeMillis(), floor, lastFloorTime, + false, + "9b3f4504-275d-4315-9922-21174262d88c") } def createFakeVoiceOnlyUser(callingWith: String, muted: Boolean, talking: Boolean, @@ -75,7 +77,9 @@ object FakeUserGenerator { val name = getRandomElement(firstNames, random) + " " + getRandomElement(lastNames, random) val lastFloorTime = System.currentTimeMillis().toString(); VoiceUserState(intId, voiceUserId = voiceUserId, callingWith, callerName = name, - callerNum = name, "#ff6242", muted, talking, listenOnly, "freeswitch", System.currentTimeMillis(), floor, lastFloorTime) + callerNum = name, "#ff6242", muted, talking, listenOnly, "freeswitch", System.currentTimeMillis(), floor, lastFloorTime, + false, + "9b3f4504-275d-4315-9922-21174262d88c") } def createFakeWebcamStreamFor(userId: String, subscribers: Set[String]): WebcamStream = { diff --git a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core2/testdata/TestDataGen.scala b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core2/testdata/TestDataGen.scala index 7eabfb6bd6..285408e333 100755 --- a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core2/testdata/TestDataGen.scala +++ b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core2/testdata/TestDataGen.scala @@ -24,7 +24,9 @@ object TestDataGen { listenOnly: Boolean): VoiceUserState = { val voiceUserId = RandomStringGenerator.randomAlphanumericString(8) VoiceUserState(intId = user.id, voiceUserId = voiceUserId, callingWith, callerName = user.name, - callerNum = user.name, "#ff6242", muted, talking, listenOnly) + callerNum = user.name, "#ff6242", muted, talking, listenOnly, + false, + "9b3f4504-275d-4315-9922-21174262d88c") } def createFakeVoiceOnlyUser(callingWith: String, muted: Boolean, talking: Boolean, @@ -32,7 +34,9 @@ object TestDataGen { val voiceUserId = RandomStringGenerator.randomAlphanumericString(8) val intId = "v_" + RandomStringGenerator.randomAlphanumericString(16) VoiceUserState(intId, voiceUserId = voiceUserId, callingWith, callerName = name, - callerNum = name, "#ff6242", muted, talking, listenOnly) + callerNum = name, "#ff6242", muted, talking, listenOnly + false, + "9b3f4504-275d-4315-9922-21174262d88c") } def createFakeWebcamStreamFor(userId: String, subscribers: Set[String]): WebcamStream = { diff --git a/akka-bbb-apps/src/universal/conf/application.conf b/akka-bbb-apps/src/universal/conf/application.conf index 3f5ce4803f..59e93bfdb1 100755 --- a/akka-bbb-apps/src/universal/conf/application.conf +++ b/akka-bbb-apps/src/universal/conf/application.conf @@ -92,6 +92,10 @@ voiceConf { # Path to the audio file being played when dial-in user is waiting for # approval. This can be relative to FreeSWITCH sounds folder dialInApprovalAudioPath = "ivr/ivr-please_hold_while_party_contacted.wav" + + # Time (seconds) to wait before requesting an audio channel hold after + # muting a user. Used in the experimental, transparent listen only mode. + toggleListenOnlyAfterMuteTimer = 4 } recording { @@ -102,4 +106,4 @@ recording { transcript { words = 8 # per line lines = 2 -} \ No newline at end of file +} diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/FreeswitchConferenceEventListener.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/FreeswitchConferenceEventListener.java index 7335cab700..076d82cc54 100755 --- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/FreeswitchConferenceEventListener.java +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/FreeswitchConferenceEventListener.java @@ -57,8 +57,18 @@ public class FreeswitchConferenceEventListener implements ConferenceEventListene if (event instanceof VoiceUserJoinedEvent) { VoiceUserJoinedEvent evt = (VoiceUserJoinedEvent) event; vcs.userJoinedVoiceConf(evt.getRoom(), evt.getVoiceUserId(), evt.getUserId(), evt.getCallerIdName(), - evt.getCallerIdNum(), evt.getMuted(), evt.getSpeaking(), evt.getCallingWith()); - } else if (event instanceof VoiceConfRunningEvent) { + evt.getCallerIdNum(), evt.getMuted(), evt.getSpeaking(), evt.getCallingWith(), + evt.getHold(), + evt.getUUID()); + } else if (event instanceof ChannelHoldChangedEvent) { + ChannelHoldChangedEvent evt = (ChannelHoldChangedEvent) event; + vcs.channelHoldChanged( + evt.getRoom(), + evt.getUserId(), + evt.getUUID(), + evt.isHeld() + ); + } else if (event instanceof VoiceConfRunningEvent) { VoiceConfRunningEvent evt = (VoiceConfRunningEvent) event; vcs.voiceConfRunning(evt.getRoom(), evt.isRunning()); } else if (event instanceof VoiceUserLeftEvent) { diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/IVoiceConferenceService.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/IVoiceConferenceService.java index 1f2c362bf6..5ca5f7b4c6 100755 --- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/IVoiceConferenceService.java +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/IVoiceConferenceService.java @@ -22,7 +22,9 @@ public interface IVoiceConferenceService { String callerIdNum, Boolean muted, Boolean speaking, - String avatarURL); + String avatarURL, + Boolean hold, + String uuid); void voiceUsersStatus(String voiceConfId, java.util.List confMembers, @@ -67,4 +69,9 @@ public interface IVoiceConferenceService { Long receivedResponseTimestamp); void freeswitchHeartbeatEvent(Map heartbeat); + + void channelHoldChanged(String voiceConfId, + String userId, + String uuid, + Boolean hold); } diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/ChannelHoldChangedEvent.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/ChannelHoldChangedEvent.java new file mode 100644 index 0000000000..10aa0a3bcf --- /dev/null +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/ChannelHoldChangedEvent.java @@ -0,0 +1,51 @@ +/** + * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ + * + * Copyright (c) 2023 BigBlueButton Inc. and by respective authors (see below). + * + * This program is free software; you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation; either version 3.0 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along + * with BigBlueButton; if not, see . + * + */ +package org.bigbluebutton.freeswitch.voice.events; + +public class ChannelHoldChangedEvent extends VoiceConferenceEvent { + + private final String userId; + private final String uuid; + private final boolean hold; + + public ChannelHoldChangedEvent( + String room, + String userId, + String uuid, + boolean hold + ) { + super(room); + this.userId = userId; + this.uuid = uuid; + this.hold = hold; + } + + public String getUserId() { + return userId; + } + + public String getUUID() { + return uuid; + } + + public boolean isHeld() { + return hold; + } + +} diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/ConfMember.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/ConfMember.java index 2470355c8b..27b240e270 100755 --- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/ConfMember.java +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/ConfMember.java @@ -9,6 +9,8 @@ public class ConfMember { public final Boolean locked = false; public final String userId; public final String callingWith; + public final Boolean hold; + public final String uuid; public ConfMember(String userId, String voiceUserId, @@ -16,7 +18,9 @@ public class ConfMember { String callerIdName, Boolean muted, Boolean speaking, - String callingWith) { + String callingWith, + Boolean hold, + String uuid) { this.userId = userId; this.voiceUserId = voiceUserId; this.callerIdName = callerIdName; @@ -24,5 +28,7 @@ public class ConfMember { this.muted = muted; this.speaking = speaking; this.callingWith = callingWith; + this.hold = hold; + this.uuid = uuid; } } diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/VoiceUserJoinedEvent.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/VoiceUserJoinedEvent.java index cb06948176..7aa644baa2 100755 --- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/VoiceUserJoinedEvent.java +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/VoiceUserJoinedEvent.java @@ -28,10 +28,14 @@ public class VoiceUserJoinedEvent extends VoiceConferenceEvent { private final Boolean locked = false; private final String userId; private final String callingWith; + private final Boolean hold; + private final String uuid; public VoiceUserJoinedEvent(String userId, String voiceUserId, String room, String callerIdNum, String callerIdName, - Boolean muted, Boolean speaking, String callingWith) { + Boolean muted, Boolean speaking, String callingWith, + Boolean hold, + String uuid) { super(room); this.userId = userId; this.voiceUserId = voiceUserId; @@ -40,6 +44,8 @@ public class VoiceUserJoinedEvent extends VoiceConferenceEvent { this.muted = muted; this.speaking = speaking; this.callingWith = callingWith; + this.hold = hold; + this.uuid = uuid; } public String getUserId() { @@ -73,4 +79,12 @@ public class VoiceUserJoinedEvent extends VoiceConferenceEvent { public String getCallingWith() { return callingWith; } + + public String getUUID() { + return uuid; + } + + public Boolean getHold() { + return hold; + } } diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/ConnectionManager.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/ConnectionManager.java index f66a2e3a69..0725e7dd7f 100755 --- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/ConnectionManager.java +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/ConnectionManager.java @@ -94,6 +94,7 @@ public class ConnectionManager { //c.addEventFilter(EVENT_NAME, "background_job"); c.addEventFilter(EVENT_NAME, "CHANNEL_EXECUTE"); c.addEventFilter(EVENT_NAME, "CHANNEL_STATE"); + c.addEventFilter(EVENT_NAME, "CHANNEL_CALLSTATE"); subscribed = true; } else { // Let's check for status every minute. @@ -239,6 +240,13 @@ public class ConnectionManager { } } + public void holdChannel(HoldChannelCommand hcc) { + Client c = manager.getESLClient(); + if (c.canSend()) { + c.sendAsyncApiCommand(hcc.getCommand(), hcc.getCommandArgs()); + } + } + public void eject(EjectUserCommand mpc) { Client c = manager.getESLClient(); if (c.canSend()) { diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/ESLEventListener.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/ESLEventListener.java index 23c47386c1..a67e6e5768 100755 --- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/ESLEventListener.java +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/ESLEventListener.java @@ -26,6 +26,9 @@ public class ESLEventListener implements IEslEventListener { private static final String CONFERENCE_CREATED_EVENT = "conference-create"; private static final String CONFERENCE_DESTROYED_EVENT = "conference-destroy"; private static final String FLOOR_CHANGE_EVENT = "video-floor-change"; + private static final String CHANNEL_CALLSTATE_EVENT = "CHANNEL_CALLSTATE"; + private static final String CHANNEL_CALLSTATE_HELD = "HELD"; + private static final String CHANNEL_CALLSTATE_ACTIVE = "ACTIVE"; private final ConferenceEventListener conferenceEventListener; @@ -59,12 +62,14 @@ public class ESLEventListener implements IEslEventListener { @Override public void conferenceEventJoin(String uniqueId, String confName, int confSize, EslEvent event) { - Integer memberId = this.getMemberIdFromEvent(event); + Integer memberId = this.getMemberId(event); Map headers = event.getEventHeaders(); - String callerId = this.getCallerIdFromEvent(event); + String callerId = this.getCallerId(event); String callerIdName = this.getCallerIdNameFromEvent(event); + String channelCallState = this.getChannelCallState(headers); boolean muted = headers.get("Speak").equals("true") ? false : true; //Was inverted which was causing a State issue boolean speaking = headers.get("Talking").equals("true") ? true : false; + boolean hold = channelCallState.equals(CHANNEL_CALLSTATE_HELD); String voiceUserId = callerIdName; @@ -124,14 +129,16 @@ public class ESLEventListener implements IEslEventListener { callerIdName, muted, speaking, - "none"); + "none", + hold, + callerUUID); conferenceEventListener.handleConferenceEvent(pj); } @Override public void conferenceEventLeave(String uniqueId, String confName, int confSize, EslEvent event) { - Integer memberId = this.getMemberIdFromEvent(event); - String callerId = this.getCallerIdFromEvent(event); + Integer memberId = this.getMemberId(event); + String callerId = this.getCallerId(event); String callerIdName = this.getCallerIdNameFromEvent(event); String callerUUID = this.getMemberUUIDFromEvent(event); @@ -146,14 +153,14 @@ public class ESLEventListener implements IEslEventListener { @Override public void conferenceEventMute(String uniqueId, String confName, int confSize, EslEvent event) { - Integer memberId = this.getMemberIdFromEvent(event); + Integer memberId = this.getMemberId(event); VoiceUserMutedEvent pm = new VoiceUserMutedEvent(memberId.toString(), confName, true); conferenceEventListener.handleConferenceEvent(pm); } @Override public void conferenceEventUnMute(String uniqueId, String confName, int confSize, EslEvent event) { - Integer memberId = this.getMemberIdFromEvent(event); + Integer memberId = this.getMemberId(event); VoiceUserMutedEvent pm = new VoiceUserMutedEvent(memberId.toString(), confName, false); conferenceEventListener.handleConferenceEvent(pm); } @@ -165,11 +172,11 @@ public class ESLEventListener implements IEslEventListener { } if (action.equals(START_TALKING_EVENT)) { - Integer memberId = this.getMemberIdFromEvent(event); + Integer memberId = this.getMemberId(event); VoiceUserTalkingEvent pt = new VoiceUserTalkingEvent(memberId.toString(), confName, true); conferenceEventListener.handleConferenceEvent(pt); } else if (action.equals(STOP_TALKING_EVENT)) { - Integer memberId = this.getMemberIdFromEvent(event); + Integer memberId = this.getMemberId(event); VoiceUserTalkingEvent pt = new VoiceUserTalkingEvent(memberId.toString(), confName, false); conferenceEventListener.handleConferenceEvent(pt); } else if (action.equals(CONFERENCE_CREATED_EVENT)) { @@ -437,16 +444,92 @@ public class ESLEventListener implements IEslEventListener { ); conferenceEventListener.handleConferenceEvent(csEvent); } + } else if (event.getEventName().equals(CHANNEL_CALLSTATE_EVENT)) { + Map eventHeaders = event.getEventHeaders(); + String channelCallState = this.getChannelCallState(eventHeaders); + String originalChannelCallState = eventHeaders.get("Original-Channel-Call-State"); + if (channelCallState == null + || originalChannelCallState == null + || channelCallState.equals(originalChannelCallState) + || !(channelCallState.equals(CHANNEL_CALLSTATE_HELD) || channelCallState.equals(CHANNEL_CALLSTATE_ACTIVE))) { + // No call state info, or no change in call state, or not a call state we care about + return; + } + String intId = this.getIntId(event); + + if (intId == null) { + return; + } + + Boolean hold = channelCallState.equals(CHANNEL_CALLSTATE_HELD); + String uuid = this.getMemberUUIDFromEvent(event); + String conference = eventHeaders.get("Caller-Destination-Number"); + Matcher callerDestNumberMatcher = ECHO_TEST_DEST_PATTERN.matcher(conference); + + if (callerDestNumberMatcher.matches()) { + conference = callerDestNumberMatcher.group(1).trim(); + } + + ChannelHoldChangedEvent csEvent = new ChannelHoldChangedEvent( + conference, + intId, + uuid, + hold + ); + conferenceEventListener.handleConferenceEvent(csEvent); + } + + } + + private String getIntId(EslEvent event) { + return this.getIntId(event.getEventHeaders()); + } + + private String getIntId(Map eventHeaders) { + String origCallerIdName = this.getCallerId(eventHeaders); + Integer memberId = this.getMemberId(eventHeaders); + Matcher callerListenOnly = CALLERNAME_LISTENONLY_PATTERN.matcher(origCallerIdName); + Matcher callWithSess = CALLERNAME_WITH_SESS_INFO_PATTERN.matcher(origCallerIdName); + if (callWithSess.matches()) { + return callWithSess.group(1).trim(); + } else if (callerListenOnly.matches()) { + return callerListenOnly.group(1).trim(); + } else if (memberId != null) { + return "v_" + memberId.toString(); + } else { + return null; } } - private Integer getMemberIdFromEvent(EslEvent e) { - return new Integer(e.getEventHeaders().get("Member-ID")); + private Integer getMemberId(EslEvent event) { + return this.getMemberId(event.getEventHeaders()); } - private String getCallerIdFromEvent(EslEvent e) { - return e.getEventHeaders().get("Caller-Caller-ID-Number"); + private Integer getMemberId(Map eventHeaders) { + String memberId = eventHeaders.get("Member-ID"); + + if (memberId == null) { + return null; + } + + return Integer.valueOf(memberId); + } + + private String getCallerId(EslEvent event) { + return this.getCallerId(event.getEventHeaders()); + } + + private String getCallerId(Map eventHeaders) { + return eventHeaders.get("Caller-Caller-ID-Number"); + } + + private String getChannelCallState(EslEvent event) { + return this.getChannelCallState(event.getEventHeaders()); + } + + private String getChannelCallState(Map eventHeaders) { + return eventHeaders.get("Channel-Call-State"); } private String getMemberUUIDFromEvent(EslEvent e) { diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/FreeswitchApplication.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/FreeswitchApplication.java index 9acdcc5672..2c0627bd58 100755 --- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/FreeswitchApplication.java +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/FreeswitchApplication.java @@ -34,6 +34,7 @@ import org.bigbluebutton.freeswitch.voice.freeswitch.actions.PlaySoundCommand; import org.bigbluebutton.freeswitch.voice.freeswitch.actions.StopSoundCommand; import org.bigbluebutton.freeswitch.voice.freeswitch.actions.RecordConferenceCommand; import org.bigbluebutton.freeswitch.voice.freeswitch.actions.TransferUserToMeetingCommand; +import org.bigbluebutton.freeswitch.voice.freeswitch.actions.HoldChannelCommand; import org.bigbluebutton.freeswitch.voice.freeswitch.actions.*; import org.slf4j.Logger; @@ -157,6 +158,11 @@ public class FreeswitchApplication implements IDelayedCommandListener{ queueMessage(mpc); } + public void holdChannel(String voiceConfId, String uuid, Boolean hold) { + HoldChannelCommand hcc = new HoldChannelCommand(voiceConfId, uuid, hold, USER); + queueMessage(hcc); + } + private Long genTimestamp() { return TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); } @@ -220,6 +226,10 @@ public class FreeswitchApplication implements IDelayedCommandListener{ manager.forceEjectUser((ForceEjectUserCommand) command); } else if (command instanceof GetUsersStatusCommand) { manager.getUsersStatus((GetUsersStatusCommand) command); + } else if (command instanceof HoldChannelCommand) { + manager.holdChannel((HoldChannelCommand) command); + } else { + log.warn("Unknown command: " + command.getCommand()); } } catch (RuntimeException e) { log.warn(e.getMessage()); diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/GetAllUsersCommand.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/GetAllUsersCommand.java index 5bf6fae2b4..5ecca84455 100755 --- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/GetAllUsersCommand.java +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/GetAllUsersCommand.java @@ -108,7 +108,9 @@ public class GetAllUsersCommand extends FreeswitchCommand { } VoiceUserJoinedEvent pj = new VoiceUserJoinedEvent(voiceUserId, member.getId().toString(), confXML.getConferenceRoom(), - callerId, callerIdName, member.getMuted(), member.getSpeaking(), "none"); + callerId, callerIdName, member.getMuted(), member.getSpeaking(), "none", + member.getHold(), + uuid); eventListener.handleConferenceEvent(pj); } else if ("recording_node".equals(member.getMemberType())) { diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/GetUsersStatusCommand.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/GetUsersStatusCommand.java index 1d5c6ac8ea..be83ada4ff 100755 --- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/GetUsersStatusCommand.java +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/GetUsersStatusCommand.java @@ -106,7 +106,9 @@ public class GetUsersStatusCommand extends FreeswitchCommand { callerId, callerIdName, member.getMuted(), member.getSpeaking(), - "none"); + "none", + member.getHold(), + member.getUUID()); confMembers.add(confMember); } } else if ("recording_node".equals(member.getMemberType())) { diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/HoldChannelCommand.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/HoldChannelCommand.java new file mode 100644 index 0000000000..a2a23e8d42 --- /dev/null +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/HoldChannelCommand.java @@ -0,0 +1,44 @@ +/** + * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ + * + * Copyright (c) 2023 BigBlueButton Inc. and by respective authors (see below). + * + * This program is free software; you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation; either version 3.0 of the License, or (at your option) any later + * version. + * + * BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along + * with BigBlueButton; if not, see . + * + */ +package org.bigbluebutton.freeswitch.voice.freeswitch.actions; + +public class HoldChannelCommand extends FreeswitchCommand { + private final String uuid; + private final Boolean hold; + + public HoldChannelCommand(String room, String uuid, Boolean hold, String requesterId) { + super(room, requesterId); + this.uuid = uuid; + this.hold = hold; + } + + @Override + public String getCommand() { + return "uuid_hold"; + } + + @Override + public String getCommandArgs() { + if (hold) { + return "toggle" + SPACE + uuid; + } else { + return "off" + SPACE + uuid; + } + } +} diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/response/ConferenceMember.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/response/ConferenceMember.java index 2133b73542..1dc577f8e4 100755 --- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/response/ConferenceMember.java +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/response/ConferenceMember.java @@ -62,6 +62,10 @@ public class ConferenceMember { return flags.getIsSpeaking(); } + public boolean getHold() { + return flags.getHold(); + } + public void setFlags(ConferenceMemberFlags flags) { this.flags = flags; } diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/response/ConferenceMemberFlags.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/response/ConferenceMemberFlags.java index f571f5da4f..5f06a227e2 100755 --- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/response/ConferenceMemberFlags.java +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/response/ConferenceMemberFlags.java @@ -27,6 +27,7 @@ public class ConferenceMemberFlags { //private boolean canHear = false; private boolean canSpeak = false; private boolean talking = false; + private boolean hold = false; //private boolean hasVideo = false; //private boolean hasFloor = false; //private boolean isModerator = false; @@ -51,4 +52,11 @@ public class ConferenceMemberFlags { talking = tempVal.equals("true") ? true : false; } + void setHold(String tempVal) { + hold = tempVal.equals("true") ? true : false; + } + + boolean getHold() { + return hold; + } } diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/response/XMLResponseConferenceListParser.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/response/XMLResponseConferenceListParser.java index 29e9ec1756..b73fa9c741 100755 --- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/response/XMLResponseConferenceListParser.java +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/response/XMLResponseConferenceListParser.java @@ -131,6 +131,8 @@ public class XMLResponseConferenceListParser extends DefaultHandler { tempFlags.setCanSpeak(tempVal); }else if (qName.equalsIgnoreCase("talking")) { tempFlags.setTalking(tempVal); + } else if (qName.equalsIgnoreCase("hold")) { + tempFlags.setHold(tempVal); } }else if (qName.equalsIgnoreCase("id")) { try { diff --git a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgDeserializer.scala b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgDeserializer.scala index c6557a5317..01353e90a2 100755 --- a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgDeserializer.scala +++ b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgDeserializer.scala @@ -243,4 +243,21 @@ trait RxJsonMsgDeserializer { } } + def routeHoldChannelInVoiceConfMsg(envelope: BbbCoreEnvelope, jsonNode: JsonNode): Unit = { + def deserialize(jsonNode: JsonNode): Option[HoldChannelInVoiceConfSysMsg] = { + val (result, error) = JsonDeserializer.toBbbCommonMsg[HoldChannelInVoiceConfSysMsg](jsonNode) + result match { + case Some(msg) => Some(msg.asInstanceOf[HoldChannelInVoiceConfSysMsg]) + case None => + log.error("Failed to deserialize message: error: {} \n msg: {}", error, jsonNode) + None + } + } + + for { + m <- deserialize(jsonNode) + } yield { + fsApp.holdChannel(m.body.voiceConf, m.body.uuid, m.body.hold) + } + } } diff --git a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgHdlrActor.scala b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgHdlrActor.scala index 51a812eca0..e5857be8f0 100755 --- a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgHdlrActor.scala +++ b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgHdlrActor.scala @@ -60,6 +60,8 @@ class RxJsonMsgHdlrActor(val fsApp: FreeswitchApplication) extends Actor with Ac routeCheckRunningAndRecordingToVoiceConfSysMsg(envelope, jsonNode) case GetUsersStatusToVoiceConfSysMsg.NAME => routeGetUsersStatusToVoiceConfSysMsg(envelope, jsonNode) + case HoldChannelInVoiceConfSysMsg.NAME => + routeHoldChannelInVoiceConfMsg(envelope, jsonNode) case _ => // do nothing } } diff --git a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/VoiceConferenceService.scala b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/VoiceConferenceService.scala index 4ed6f823a3..4e520a82dc 100755 --- a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/VoiceConferenceService.scala +++ b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/VoiceConferenceService.scala @@ -90,7 +90,9 @@ class VoiceConferenceService(healthz: HealthzService, cm.muted, cm.speaking, cm.callingWith, - "freeswitch" + "freeswitch", + cm.hold, + cm.uuid ) } @@ -119,12 +121,16 @@ class VoiceConferenceService(healthz: HealthzService, callerIdNum: String, muted: java.lang.Boolean, talking: java.lang.Boolean, - callingWith: String - ) { + callingWith: String, + hold: java.lang.Boolean, + uuid: String + ): Unit = { val header = BbbCoreVoiceConfHeader(UserJoinedVoiceConfEvtMsg.NAME, voiceConfId) val body = UserJoinedVoiceConfEvtMsgBody(voiceConfId, voiceUserId, userId, callerIdName, callerIdNum, - muted.booleanValue(), talking.booleanValue(), callingWith) + muted.booleanValue(), talking.booleanValue(), callingWith, + hold, + uuid); val envelope = BbbCoreEnvelope(UserJoinedVoiceConfEvtMsg.NAME, Map("voiceConf" -> voiceConfId)) val msg = new UserJoinedVoiceConfEvtMsg(header, body) @@ -248,6 +254,28 @@ class VoiceConferenceService(healthz: HealthzService, sender.publish(fromVoiceConfRedisChannel, json) } + def channelHoldChanged( + voiceConfId: String, + voiceUserId: String, + uuid: String, + hold: java.lang.Boolean + ): Unit = { + val header = BbbCoreVoiceConfHeader(ChannelHoldChangedVoiceConfEvtMsg.NAME, voiceConfId) + val body = ChannelHoldChangedVoiceConfEvtMsgBody( + voiceConfId, + voiceUserId, + uuid, + hold + ); + val envelope = BbbCoreEnvelope(ChannelHoldChangedVoiceConfEvtMsg.NAME, Map("voiceConf" -> voiceConfId)) + + val msg = new ChannelHoldChangedVoiceConfEvtMsg(header, body) + val msgEvent = BbbCommonEnvCoreMsg(envelope, msg) + + val json = JsonUtil.toJson(msgEvent) + sender.publish(fromVoiceConfRedisChannel, json) + } + def freeswitchStatusReplyEvent( sendCommandTimestamp: java.lang.Long, status: java.util.List[String], diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/VoiceConfMsgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/VoiceConfMsgs.scala index 5532075273..86d878e6c1 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/VoiceConfMsgs.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/VoiceConfMsgs.scala @@ -387,8 +387,9 @@ case class UserStatusVoiceConfEvtMsgBody(voiceConf: String, confUsers: Vector[Co case class ConfVoiceUser(voiceUserId: String, intId: String, callerIdName: String, callerIdNum: String, muted: Boolean, talking: Boolean, callingWith: String, - calledInto: String // freeswitch, kms - ) + calledInto: String, // freeswitch, kms + hold: Boolean, + uuid: String) case class ConfVoiceRecording(recordPath: String, recordStartTime: Long) /** @@ -401,7 +402,9 @@ case class UserJoinedVoiceConfEvtMsg( ) extends VoiceStandardMsg case class UserJoinedVoiceConfEvtMsgBody(voiceConf: String, voiceUserId: String, intId: String, callerIdName: String, callerIdNum: String, muted: Boolean, - talking: Boolean, callingWith: String) + talking: Boolean, callingWith: String, + hold: Boolean, + uuid: String) /** * Sent to client that a user has joined the voice conference. @@ -639,3 +642,64 @@ case class GetMicrophonePermissionRespMsgBody( sfuSessionId: String, allowed: Boolean ) + +/** + * Sent to FS to hold an audio channel + */ +object HoldChannelInVoiceConfSysMsg { val NAME = "HoldChannelInVoiceConfSysMsg" } +case class HoldChannelInVoiceConfSysMsg( + header: BbbCoreHeaderWithMeetingId, + body: HoldChannelInVoiceConfSysMsgBody +) extends BbbCoreMsg +case class HoldChannelInVoiceConfSysMsgBody( + voiceConf: String, + uuid: String, + hold: Boolean +) + +/** + * Received from FS that the user channel hold state has changed + */ +object ChannelHoldChangedVoiceConfEvtMsg { val NAME = "ChannelHoldChangedVoiceConfEvtMsg" } +case class ChannelHoldChangedVoiceConfEvtMsg( + header: BbbCoreVoiceConfHeader, + body: ChannelHoldChangedVoiceConfEvtMsgBody +) extends VoiceStandardMsg +case class ChannelHoldChangedVoiceConfEvtMsgBody( + voiceConf: String, + intId: String, + uuid: String, + hold: Boolean +) + +/** + * Sent to bbb-webrtc-sfu to request for userId's microphone connection + * to be toggled between bidirectional and unidirectional (listen only) modes + * (enabled = unidirectional, listen only, !enabled = bidirectional); + */ +object ToggleListenOnlyModeSysMsg { val NAME = "ToggleListenOnlyModeSysMsg" } +case class ToggleListenOnlyModeSysMsg( + header: BbbCoreHeaderWithMeetingId, + body: ToggleListenOnlyModeSysMsgBody +) extends BbbCoreMsg +case class ToggleListenOnlyModeSysMsgBody( + voiceConf: String, + userId: String, + enabled: Boolean +) + +/** + * Sent from bbb-webrtc-sfu to indicate that userId's microphone channel switched + * modes (enabled = unidirectional, listen only, !enabled = bidirectional); + */ +object ListenOnlyModeToggledInSfuEvtMsg { val NAME = "ListenOnlyModeToggledInSfuEvtMsg" } +case class ListenOnlyModeToggledInSfuEvtMsg( + header: BbbCoreVoiceConfHeader, + body: ListenOnlyModeToggledInSfuEvtMsgBody +) extends VoiceStandardMsg +case class ListenOnlyModeToggledInSfuEvtMsgBody( + meetingId: String, + voiceConf: String, + userId: String, + enabled: Boolean +) diff --git a/bbb-webrtc-sfu.placeholder.sh b/bbb-webrtc-sfu.placeholder.sh index d62240a39a..6b19fc6373 100755 --- a/bbb-webrtc-sfu.placeholder.sh +++ b/bbb-webrtc-sfu.placeholder.sh @@ -1 +1 @@ -git clone --branch v2.10.0 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu +git clone --branch v2.11.0-beta.1 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/sfu-audio-bridge.js b/bigbluebutton-html5/imports/api/audio/client/bridge/sfu-audio-bridge.js index 6021ce1c2e..7246cd3898 100755 --- a/bigbluebutton-html5/imports/api/audio/client/bridge/sfu-audio-bridge.js +++ b/bigbluebutton-html5/imports/api/audio/client/bridge/sfu-audio-bridge.js @@ -8,6 +8,7 @@ import { getMappedFallbackStun, } from '/imports/utils/fetchStunTurnServers'; import getFromMeetingSettings from '/imports/ui/services/meeting-settings'; +import getFromUserSettings from '/imports/ui/services/users-settings'; import browserInfo from '/imports/utils/browserInfo'; import { getAudioSessionNumber, @@ -26,6 +27,8 @@ const MEDIA = Meteor.settings.public.media; const DEFAULT_FULLAUDIO_MEDIA_SERVER = MEDIA.audio.fullAudioMediaServer; const RETRY_THROUGH_RELAY = MEDIA.audio.retryThroughRelay || false; const LISTEN_ONLY_OFFERING = MEDIA.listenOnlyOffering; +const FULLAUDIO_OFFERING = MEDIA.fullAudioOffering; +const TRANSPARENT_LISTEN_ONLY = MEDIA.transparentListenOnly; const MEDIA_TAG = MEDIA.mediaTag.replace(/#/g, ''); const CONNECTION_TIMEOUT_MS = MEDIA.listenOnlyCallTimeout || 15000; const { audio: NETWORK_PRIORITY } = MEDIA.networkPriorities || {}; @@ -71,7 +74,18 @@ const getMediaServerAdapter = (listenOnly = false) => { ); }; +const isTransparentListenOnlyEnabled = () => getFromUserSettings( + 'bbb_transparent_listen_only', + TRANSPARENT_LISTEN_ONLY, +); + export default class SFUAudioBridge extends BaseAudioBridge { + static getOfferingRole(isListenOnly) { + return isListenOnly + ? LISTEN_ONLY_OFFERING + : (!isTransparentListenOnlyEnabled() && FULLAUDIO_OFFERING); + } + constructor(userData) { super(); this.userId = userData.userId; @@ -320,12 +334,13 @@ export default class SFUAudioBridge extends BaseAudioBridge { constraints: getAudioConstraints({ deviceId: this.inputDeviceId }), forceRelay: _forceRelay || shouldForceRelay(), stream: (inputStream && inputStream.active) ? inputStream : undefined, - offering: isListenOnly ? LISTEN_ONLY_OFFERING : true, + offering: SFUAudioBridge.getOfferingRole(this.isListenOnly), signalCandidates: SIGNAL_CANDIDATES, traceLogs: TRACE_LOGS, networkPriority: NETWORK_PRIORITY, mediaStreamFactory: this.mediaStreamFactory, gatheringTimeout: GATHERING_TIMEOUT, + transparentListenOnly: isTransparentListenOnlyEnabled(), }; this.broker = new AudioBroker( diff --git a/bigbluebutton-html5/imports/api/users-settings/server/methods/addUserSettings.js b/bigbluebutton-html5/imports/api/users-settings/server/methods/addUserSettings.js index 28bf573193..68e9682bb7 100644 --- a/bigbluebutton-html5/imports/api/users-settings/server/methods/addUserSettings.js +++ b/bigbluebutton-html5/imports/api/users-settings/server/methods/addUserSettings.js @@ -35,6 +35,7 @@ const currentParameters = [ 'bbb_skip_check_audio', 'bbb_skip_check_audio_on_first_join', 'bbb_fullaudio_bridge', + 'bbb_transparent_listen_only', // BRANDING 'bbb_display_branding_area', // SHORTCUTS diff --git a/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/audio-broker.js b/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/audio-broker.js index 7870fac95d..3d59f35c42 100644 --- a/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/audio-broker.js +++ b/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/audio-broker.js @@ -30,6 +30,7 @@ class AudioBroker extends BaseBroker { // traceLogs // networkPriority // gatheringTimeout + // transparentListenOnly Object.assign(this, options); } @@ -200,6 +201,7 @@ class AudioBroker extends BaseBroker { sdpOffer: offer, mediaServer: this.mediaServer, extension: this.extension, + transparentListenOnly: this.transparentListenOnly, }; logger.debug({ diff --git a/bigbluebutton-html5/imports/ui/services/webrtc-base/peer.js b/bigbluebutton-html5/imports/ui/services/webrtc-base/peer.js index dbe8136b31..133d7046a3 100644 --- a/bigbluebutton-html5/imports/ui/services/webrtc-base/peer.js +++ b/bigbluebutton-html5/imports/ui/services/webrtc-base/peer.js @@ -406,6 +406,28 @@ export default class WebRtcPeer extends EventEmitter2 { }); return this._setRemoteDescription(offer) + .then(async () => { + if (this.mode === 'sendonly' || this.mode === 'sendrecv') { + await this.mediaStreamFactory(); + + if (this.videoStream) { + this.videoStream.getTracks().forEach((track) => { + this.peerConnection.addTrack(track, this.videoStream); + }); + } + + if (this.audioStream) { + this.audioStream.getTracks().forEach((track) => { + this.peerConnection.addTrack(track, this.audioStream); + }); + } + + this.peerConnection.getTransceivers().forEach((transceiver) => { + // eslint-disable-next-line no-param-reassign + transceiver.direction = this.mode; + }); + } + }) .then(() => this.peerConnection.createAnswer()) .then((answer) => { this.logger.debug('BBB::WebRtcPeer::processOffer - created answer', answer); diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index c7eaaab288..40d76c6dec 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -657,8 +657,13 @@ public: callHangupMaximumRetries: 10 echoTestNumber: 'echo' listenOnlyCallTimeout: 15000 - # Experimental. True is the canonical behavior. Flip to false to reverse - # the negotiation flow for LO subscribers. + # Experimental: enables a new audio mechanism that has a server-side, + # transparent listen only mode. See issue #14021. + # bbb_userdata-transparent-listen-only supersedes this setting + transparentListenOnly: false + # Dev flag. Controls the WebRTC SDP negotiation role for SFU full audio. + fullAudioOffering: true + # Dev flag. Controls the WebRTC negotiation role for listen only. listenOnlyOffering: false #Timeout (ms) for gathering ICE candidates. When this timeout expires #the SDP is sent to the server with the candidates the browser gathered From c4a62f275d40155480b3be35d3ad8b76b87af5d6 Mon Sep 17 00:00:00 2001 From: prlanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Mon, 7 Aug 2023 20:36:37 -0300 Subject: [PATCH 149/252] docs: add info on new audio mode --- docs/docs/administration/customize.md | 1 + docs/docs/new-features.md | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/docs/docs/administration/customize.md b/docs/docs/administration/customize.md index 9fbd54d7ff..940619ebc6 100644 --- a/docs/docs/administration/customize.md +++ b/docs/docs/administration/customize.md @@ -1392,6 +1392,7 @@ Useful tools for development: | `userdata-bbb_skip_video_preview_on_first_join=` | (Introduced in BigBlueButton 2.3) If set to `true`, the user will not see a preview of their webcam before sharing it when sharing for the first time in the session. If the user stops sharing, next time they try to share webcam the video preview will be displayed, allowing for configuration changes to be made prior to sharing | `false` | | `userdata-bbb_mirror_own_webcam=` | If set to `true`, the client will see a mirrored version of their webcam. Doesn't affect the incoming video stream for other users. | `false` | | `userdata-bbb_fullaudio_bridge=` | Specifies the audio bridge to be used in the client. Supported values: `sipjs`, `fullaudio`. | `fullaudio` | +| `userdata-bbb_transparent_listen_only=` | If set to `true`, the experimental "transparent listen only" audio mode will be used | `false` | #### Presentation parameters diff --git a/docs/docs/new-features.md b/docs/docs/new-features.md index aa64302342..06511489ff 100644 --- a/docs/docs/new-features.md +++ b/docs/docs/new-features.md @@ -102,6 +102,26 @@ Issues found during testing should be reported on [BigBlueButton's issue tracker Reverting to the default recorder (Kurento) can be achieved by removing the `recordingAdapter` line from `/etc/bigbluebutton/bbb-webrtc-sfu/production.yml` and restarting `bbb-webrtc-sfu`. +#### Transparent listen only mode + +We've added a new experimental audio mode called "transparent listen only". +The goal is to pave the way for a better audio experience in BigBlueButton by +removing the need for end users to pick between listen only and microphone modes while still +providing a scalable audio solution. + +The motivation for this mode can be found in [issue 14021](https://github.com/bigbluebutton/bigbluebutton/issues/14021), +while the implementation details are available in [pull request 18461](https://github.com/bigbluebutton/bigbluebutton/pull/18461). + +In version 2.7, we present the initial iteration of this audio mode, primarily focusing on the server side. +The primary objective is to assess the viability of the proposed approach and gather community feedback. + +The new mode is *turned off by default* and is considered *experimental*. To enable it: + - Add `transparentListenOnly: true` to `/etc/bigbluebutton/bbb-webrtc-sfu/production.yml` + - Restart `bbb-webrtc-sfu` with `systemctl restart bbb-webrtc-sfu` + - To enable on clients: + * Server wide: configure `public.media.transparentListenOnly: true` in `/etc/bigbluebutton/bbb-html5.yml` + * Per user: utilize `userdata-bbb_transparent_listen_only=true` + ### Upgraded components Under the hood, BigBlueButton 2.7 installs on Ubuntu 20.04 64-bit, and the following key components have been upgraded From a73bdf5d188af6fd85fcf37ebc8b97c0122a824c Mon Sep 17 00:00:00 2001 From: prlanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Tue, 8 Aug 2023 09:36:29 -0300 Subject: [PATCH 150/252] refactor: simplify ActorContext import in VoiceApp --- .../org/bigbluebutton/core/apps/voice/VoiceApp.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp.scala index 1b004c2867..96a2b121c9 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp.scala @@ -1,6 +1,6 @@ package org.bigbluebutton.core.apps.voice -import akka.actor.{ ActorSystem, Cancellable } +import akka.actor.{ ActorContext, ActorSystem, Cancellable } import org.bigbluebutton.SystemConfiguration import org.bigbluebutton.LockSettingsUtil import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers @@ -110,7 +110,7 @@ object VoiceApp extends SystemConfiguration { outGW: OutMsgRouter, voiceUserId: String, muted: Boolean - )(implicit context: akka.actor.ActorContext): Unit = { + )(implicit context: ActorContext): Unit = { for { mutedUser <- VoiceUsers.userMuted(liveMeeting.voiceUsers, voiceUserId, muted) } yield { @@ -157,7 +157,7 @@ object VoiceApp extends SystemConfiguration { outGW: OutMsgRouter, eventBus: InternalEventBus, users: Vector[ConfVoiceUser] - )(implicit context: akka.actor.ActorContext): Unit = { + )(implicit context: ActorContext): Unit = { users foreach { cvu => VoiceUsers.findWithVoiceUserId( liveMeeting.voiceUsers, @@ -478,7 +478,7 @@ object VoiceApp extends SystemConfiguration { userId: String, enabled: Boolean, delay: Int = 0 - )(implicit context: akka.actor.ActorContext): Unit = { + )(implicit context: ActorContext): Unit = { implicit def executionContext = context.system.dispatcher def broacastEvent(): Unit = { val event = MsgBuilder.buildToggleListenOnlyModeSysMsg( @@ -535,7 +535,7 @@ object VoiceApp extends SystemConfiguration { intId: String, uuid: String, hold: Boolean - )(implicit context: akka.actor.ActorContext): Unit = { + )(implicit context: ActorContext): Unit = { VoiceUsers.holdStateChanged( liveMeeting.voiceUsers, intId, From eaf86b37a8f1b53998c02f3caef64e63931c6085 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Tue, 8 Aug 2023 11:32:52 -0300 Subject: [PATCH 151/252] Fix: Unable to choose same reaction twice in a row --- bigbluebutton-html5/imports/api/user-reaction/server/helpers.js | 1 - 1 file changed, 1 deletion(-) diff --git a/bigbluebutton-html5/imports/api/user-reaction/server/helpers.js b/bigbluebutton-html5/imports/api/user-reaction/server/helpers.js index 3a7824d41c..1efe976b01 100644 --- a/bigbluebutton-html5/imports/api/user-reaction/server/helpers.js +++ b/bigbluebutton-html5/imports/api/user-reaction/server/helpers.js @@ -16,7 +16,6 @@ const notifyExpiredReaction = (meetingId, userId) => { check(meetingId, String); const payload = { - emoji, userId, }; From 07ce5befec8ad9305fcb8448b6891ba29564a66a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Tue, 8 Aug 2023 13:05:54 -0300 Subject: [PATCH 152/252] limit cameras page in grid mode --- .../components/video-provider/container.jsx | 13 +++- .../ui/components/video-provider/service.js | 62 ++++++++++++------- .../video-provider/video-list/component.jsx | 3 - .../ui/components/webcam/container.jsx | 13 +++- .../private/config/settings.yml | 4 +- 5 files changed, 63 insertions(+), 32 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/container.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/container.jsx index e475013b33..f5480a3846 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/container.jsx @@ -2,6 +2,9 @@ import React from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import VideoProvider from './component'; import VideoService from './service'; +import { sortVideoStreams } from '/imports/ui/components/video-provider/stream-sorting'; + +const { defaultSorting: DEFAULT_SORTING } = Meteor.settings.public.kurento.cameraSortingModes; const VideoProviderContainer = ({ children, ...props }) => { const { streams, isGridEnabled } = props; @@ -16,12 +19,20 @@ export default withTracker(({ swapLayout, ...rest }) => { // } const { streams, + gridUsers, totalNumberOfStreams, } = VideoService.getVideoStreams(); + let usersVideo = streams; + + if(gridUsers.length > 0) { + const items = usersVideo.concat(gridUsers); + usersVideo = sortVideoStreams(items, DEFAULT_SORTING); + } + return { swapLayout, - streams, + streams: usersVideo, totalNumberOfStreams, isUserLocked: VideoService.isUserLocked(), currentVideoPageIndex: VideoService.getCurrentVideoPageIndex(), diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/service.js b/bigbluebutton-html5/imports/ui/components/video-provider/service.js index cab6c179a5..3039b8ef98 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/service.js +++ b/bigbluebutton-html5/imports/ui/components/video-provider/service.js @@ -319,11 +319,7 @@ class VideoService { getPageSizeDictionary () { // Dynamic page sizes are disabled. Fetch the stock page sizes. if (!PAGINATION_THRESHOLDS_ENABLED || PAGINATION_THRESHOLDS.length <= 0) { - if (this.isGridEnabled()) { - return !this.isMobile ? DESKTOP_GRID_PAGE_SIZES : MOBILE_GRID_PAGE_SIZES; - } else { - return !this.isMobile ? DESKTOP_PAGE_SIZES : MOBILE_PAGE_SIZES; - } + return !this.isMobile ? DESKTOP_PAGE_SIZES : MOBILE_PAGE_SIZES; } // Dynamic page sizes are enabled. Get the user count, isolate the @@ -382,6 +378,23 @@ class VideoService { return this.setPageSize(size); } + getGridSize () { + let size; + const myRole = this.getMyRole(); + const pageSizes = !this.isMobile ? DESKTOP_GRID_PAGE_SIZES : MOBILE_GRID_PAGE_SIZES; + + switch (myRole) { + case ROLE_MODERATOR: + size = pageSizes.moderator; + break; + case ROLE_VIEWER: + default: + size = pageSizes.viewer + } + + return size; + } + getVideoPage (streams, pageSize) { // Publishers are taken into account for the page size calculations. They // also appear on every page. Same for pinned user. @@ -443,24 +456,6 @@ class VideoService { { fields: neededDataTypes }, ).fetch(); - if (isGridEnabled) { - const users = Users.find( - { meetingId: Auth.meetingID }, - { fields: { loggedOut: 1, left: 1, ...neededDataTypes} }, - ).fetch(); - - const streamUsers = streams.map((stream) => stream.userId); - - const filteredUsers = users.filter( - (user) => !user.loggedOut && !user.left && !streamUsers.includes(user.userId) - ).map((user) => ({ - isGridItem: true, - ...user, - })); - - streams = sortVideoStreams(streams.concat(filteredUsers), DEFAULT_SORTING); - } - // Data savings enabled will only show local streams const { viewParticipantsWebcams } = Settings.dataSaving; if (!viewParticipantsWebcams) streams = this.filterLocalOnly(streams); @@ -482,7 +477,26 @@ class VideoService { const paginatedStreams = this.getVideoPage(streams, pageSize); - return { streams: paginatedStreams, totalNumberOfStreams: streams.length }; + let gridUsers = []; + + if (isGridEnabled) { + const users = Users.find( + { meetingId: Auth.meetingID }, + { fields: { loggedOut: 1, left: 1, ...neededDataTypes} }, + ).fetch(); + + const streamUsers = paginatedStreams.map((stream) => stream.userId); + + gridUsers = users.filter( + (user) => !user.loggedOut && !user.left && !streamUsers.includes(user.userId) + ).map((user) => ({ + isGridItem: true, + ...user, + })); + + } + + return { streams: paginatedStreams, gridUsers, totalNumberOfStreams: streams.length }; } stopConnectingStream () { diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/component.jsx index 3852c898fa..45a96b3d29 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/component.jsx @@ -10,9 +10,6 @@ import logger from '/imports/startup/client/logger'; import playAndRetry from '/imports/utils/mediaElementPlayRetry'; import VideoService from '/imports/ui/components/video-provider/service'; import { ACTIONS } from '../../layout/enums'; -import { sortVideoStreams } from '/imports/ui/components/video-provider/stream-sorting'; - -const { defaultSorting: DEFAULT_SORTING } = Meteor.settings.public.kurento.cameraSortingModes; const propTypes = { streams: PropTypes.arrayOf(PropTypes.object).isRequired, diff --git a/bigbluebutton-html5/imports/ui/components/webcam/container.jsx b/bigbluebutton-html5/imports/ui/components/webcam/container.jsx index 4ccc2e8b02..60bc1d9f30 100644 --- a/bigbluebutton-html5/imports/ui/components/webcam/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/webcam/container.jsx @@ -13,6 +13,9 @@ import { } from '../layout/context'; import WebcamComponent from '/imports/ui/components/webcam/component'; import { LAYOUT_TYPE } from '../layout/enums'; +import { sortVideoStreams } from '/imports/ui/components/video-provider/stream-sorting'; + +const { defaultSorting: DEFAULT_SORTING } = Meteor.settings.public.kurento.cameraSortingModes; const WebcamContainer = ({ audioModalIsOpen, @@ -66,8 +69,14 @@ export default withTracker((props) => { isMeteorConnected: Meteor.status().connected, }; - const { streams: usersVideo } = VideoService.getVideoStreams(); - data.usersVideo = usersVideo; + const { streams: usersVideo, gridUsers } = VideoService.getVideoStreams(); + + if(gridUsers.length > 0) { + const items = usersVideo.concat(gridUsers); + data.usersVideo = sortVideoStreams(items, DEFAULT_SORTING); + } else { + data.usersVideo = usersVideo; + } data.swapLayout = !hasPresentation || props.isLayoutSwapped; if (data.swapLayout) { diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index e051decf55..8d462aa998 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -476,11 +476,11 @@ public: mobilePageSizes: moderator: 2 viewer: 2 - # video page sizes for DESKTOP endpoints in GRID mode + # grid size for DESKTOP endpoints desktopGridPageSizes: moderator: 48 viewer: 48 - # video page sizes for MOBILE endpoints in GRID mode + # grid size for MOBILE endpoints mobileGridPageSizes: moderator: 14 viewer: 14 From 26787aa5c81b72467b5565f4b668c9f222bec103 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Tue, 8 Aug 2023 13:34:30 -0300 Subject: [PATCH 153/252] Fix: setting away=true in akka-apps when user raised his hand --- .../src/main/scala/org/bigbluebutton/core/models/Users2x.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala index 92a69040ca..039499520c 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala @@ -199,7 +199,7 @@ object Users2x { for { u <- findWithIntId(users, intId) } yield { - val newUserState = u.modify(_.away).setTo(raiseHand) + val newUserState = u.modify(_.raiseHand).setTo(raiseHand) users.save(newUserState) newUserState } From ea9c687b07919502bea73ac654ab27de161dfa62 Mon Sep 17 00:00:00 2001 From: KDSBrowne Date: Tue, 8 Aug 2023 17:32:07 +0000 Subject: [PATCH 154/252] prevent zooming out at 100% --- .../imports/ui/components/whiteboard/component.jsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx index 59b5c8da8c..1a30a59523 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx @@ -822,6 +822,11 @@ export default function Whiteboard(props) { camera.point[1] = 0; } + if (camera.point[0] === 0 && camera.point[1] === 0) { + const newZoom = calculateZoom(slidePosition.viewBoxWidth, slidePosition.viewBoxHeight); + e?.setCamera([slidePosition.x, slidePosition.y], newZoom); + } + const zoomFitSlide = calculateZoom(slidePosition.width, slidePosition.height); if (camera.zoom < zoomFitSlide) { camera.zoom = zoomFitSlide; From b68dbc68321dbf310f64ac42de022b694a6728f6 Mon Sep 17 00:00:00 2001 From: Anton B Date: Tue, 8 Aug 2023 15:13:32 -0300 Subject: [PATCH 155/252] test: re-add zoom test --- .../playwright/presentation/presentation.spec.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bigbluebutton-tests/playwright/presentation/presentation.spec.js b/bigbluebutton-tests/playwright/presentation/presentation.spec.js index 9e9c633cef..b10241e464 100644 --- a/bigbluebutton-tests/playwright/presentation/presentation.spec.js +++ b/bigbluebutton-tests/playwright/presentation/presentation.spec.js @@ -52,11 +52,7 @@ test.describe.parallel('Presentation', () => { await presentation.hidePresentationToolbar(); }); - /** - * temporally skipped because it's currently failing the screenshot comparisons - * due to https://github.com/bigbluebutton/bigbluebutton/issues/18232 - */ - test.skip('Zoom In, Zoom Out, Reset Zoom @ci', async ({ browser, context, page }) => { + test('Zoom In, Zoom Out, Reset Zoom @ci', async ({ browser, context, page }) => { const presentation = new Presentation(browser, context); await presentation.initPages(page); await presentation.zoom(); From e767a39799e8c91f2137b450b125506a7d01712f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Tue, 8 Aug 2023 15:28:05 -0300 Subject: [PATCH 156/252] centralize emoji, add focus color --- .../reactions-button/component.jsx | 1 + .../actions-bar/reactions-button/styles.js | 26 +++++++------------ .../ui/components/common/menu/styles.js | 1 + 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx index bc6a0fe8dd..1e1e43a6c8 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx @@ -74,6 +74,7 @@ const ReactionsButton = (props) => { const emojiProps = { native: true, size: '1.5rem', + padding: '4px', }; const reactions = [ diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/styles.js b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/styles.js index 2430b1e9c1..b115f6fbae 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/styles.js +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/styles.js @@ -3,11 +3,10 @@ import { colorWhite } from '/imports/ui/stylesheets/styled-components/palette'; import Button from '/imports/ui/components/common/button/component'; import { - colorOffWhite, + colorGrayDark, colorGrayLightest, - btnPrimaryHoverBg, btnPrimaryColor, - btnPrimaryBg, + btnPrimaryActiveBg, } from '/imports/ui/stylesheets/styled-components/palette'; const RaiseHandButton = styled(Button)` @@ -25,17 +24,16 @@ const ReactionsDropdown = styled.div` `; const ButtonWrapper = styled.div` - border: none; + border: 1px solid transparent; cursor: pointer; height: 2.5rem; - width: 2.5rem; display: flex; align-items: center; border-radius: 50%; margin: 0 .5rem; - &:hover { - background-color: ${colorOffWhite}; + &:focus { + background-color: ${colorGrayDark}; } & > button { @@ -44,18 +42,17 @@ const ButtonWrapper = styled.div` } & > * > span { - margin-left: 4px; + padding: 4px; } ${({ active }) => active && ` color: ${btnPrimaryColor}; - background-color: ${btnPrimaryBg}; - border: none; + background-color: ${btnPrimaryActiveBg}; &:hover{ filter: brightness(90%); color: ${btnPrimaryColor}; - background-color: ${btnPrimaryHoverBg} !important; + background-color: ${btnPrimaryActiveBg} !important; } `} `; @@ -64,9 +61,6 @@ const RaiseHandButtonWrapper = styled(ButtonWrapper)` width: 2.5rem; border-radius: 1.7rem; - & > * > span { - margin-left: 5px; - } ${({ isMobile }) => !isMobile && ` border: 1px solid ${colorGrayLightest}; @@ -76,12 +70,12 @@ const RaiseHandButtonWrapper = styled(ButtonWrapper)` ${({ active }) => active && ` color: ${btnPrimaryColor}; - background-color: ${btnPrimaryBg}; + background-color: ${btnPrimaryActiveBg}; &:hover{ filter: brightness(90%); color: ${btnPrimaryColor}; - background-color: ${btnPrimaryHoverBg} !important; + background-color: ${btnPrimaryActiveBg} !important; } `} `; diff --git a/bigbluebutton-html5/imports/ui/components/common/menu/styles.js b/bigbluebutton-html5/imports/ui/components/common/menu/styles.js index bd39933385..2db238dc6d 100644 --- a/bigbluebutton-html5/imports/ui/components/common/menu/styles.js +++ b/bigbluebutton-html5/imports/ui/components/common/menu/styles.js @@ -113,6 +113,7 @@ const BBBMenuItem = styled(MenuItem)` background-color: ${colorWhite} !important; div div div { background-color: ${colorPrimary} !important; + border: 1px solid ${colorPrimary} !important; } } `} From cd2c85474ebe116dadac6cc8d292913d644484be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Tue, 8 Aug 2023 16:49:15 -0300 Subject: [PATCH 157/252] change default value for wake lock --- bigbluebutton-html5/private/config/settings.yml | 4 ++-- docs/docs/new-features.md | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 117bd15be4..3c7e3d8a49 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -60,7 +60,7 @@ public: # in some cases we want only custom logoutUrl to be used when provided on meeting create. Default value: true askForConfirmationOnLeave: true wakeLock: - enabled: false + enabled: true allowDefaultLogoutUrl: true allowUserLookup: false dynamicGuestPolicy: true @@ -188,7 +188,7 @@ public: raiseHandPushAlerts: true guestWaitingAudioAlerts: true guestWaitingPushAlerts: true - wakeLock: false + wakeLock: true paginationEnabled: true whiteboardToolbarAutoHide: false darkTheme: false diff --git a/docs/docs/new-features.md b/docs/docs/new-features.md index c33dd4d9c7..8d2447b80a 100644 --- a/docs/docs/new-features.md +++ b/docs/docs/new-features.md @@ -155,6 +155,10 @@ If you are using bbb-install to configure your servers, be aware that starting w Starting with BigBlueButton 2.7.0-beta.3 we are hiding by default a couple extra options in the guest approve panel. 'Allow all authenticated users' and 'Allow all guests' options will be hidden unless you override the option `app.public.guestPolicyExtraAllowOptions` in `bbb-html5` config file `settings.yml`. These extra options were not relevant to the vast majority of the use cases and when hidden, the interface becomes much simpler. +#### Changing the default setting `wakeLock` + +Starting with BigBlueButton 2.7.0-beta.3 we are enabling wake lock feature by default. It can be disabled by overriding the option `public.app.wakeLock.enabled` in `bbb-html5` config file `settings.yml`. + ### Development For information on developing in BigBlueButton, see [setting up a development environment for 2.7](/development/guide). From 849e0ccc42a748c685cfc315610594432c239644 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Wed, 9 Aug 2023 07:46:46 -0400 Subject: [PATCH 158/252] Updates for file bigbluebutton-html5/public/locales/en.json in gl on branch v2.6.x-release (#18467) * Translate en.json in gl 100% translated source file: 'en.json' on 'gl'. --------- Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- bigbluebutton-html5/public/locales/gl.json | 254 ++++++++++----------- 1 file changed, 127 insertions(+), 127 deletions(-) diff --git a/bigbluebutton-html5/public/locales/gl.json b/bigbluebutton-html5/public/locales/gl.json index 7b61b54088..e92f0d87b5 100644 --- a/bigbluebutton-html5/public/locales/gl.json +++ b/bigbluebutton-html5/public/locales/gl.json @@ -1,42 +1,42 @@ { "app.home.greeting": "A súa presentación comezará en breve…", "app.chat.submitLabel": "Enviar mensaxe", - "app.chat.loading": "Mensaxes de conversa cargadas: {0}%", + "app.chat.loading": "Mensaxes de parola cargadas: {0}%", "app.chat.errorMaxMessageLength": "A mensaxe é demasiado longa, superou o máximo de {0} caracteres", - "app.chat.disconnected": "Esta desconectado, non é posíbel enviar as mensaxes", - "app.chat.locked": "Conversa bloqueado, anon é posíbel enviar as mensaxes", - "app.chat.inputLabel": "Entrada de mensaxe para a conversa {0}", - "app.chat.emojiButtonLabel": "Selector de emojis", + "app.chat.disconnected": "Vde. esta desconectado, non é posíbel enviar as mensaxes", + "app.chat.locked": "Parola bloqueada, non é posíbel enviar as mensaxes", + "app.chat.inputLabel": "Entrada de mensaxe para a parola {0}", + "app.chat.emojiButtonLabel": "Selector de «emojis»", "app.chat.inputPlaceholder": "Mensaxe {0}", - "app.chat.titlePublic": "Conversa pública", - "app.chat.titlePrivate": "Conversa privada con {0}.", + "app.chat.titlePublic": "Parola pública", + "app.chat.titlePrivate": "Parola privada con {0}.", "app.chat.partnerDisconnected": "{0} saíu da xuntanza", "app.chat.closeChatLabel": "Pechar {0}", "app.chat.hideChatLabel": "Agochar {0}", "app.chat.moreMessages": "Máis mensaxes a seguir", - "app.chat.dropdown.options": "Opcións da conversa", + "app.chat.dropdown.options": "Opcións da parola", "app.chat.dropdown.clear": "Limpar", "app.chat.dropdown.copy": "Copiar", "app.chat.dropdown.save": "Gardar", - "app.chat.label": "Conversa", + "app.chat.label": "Parola", "app.chat.offline": "Sen conexión", "app.chat.pollResult": "Resultados da enquisa", "app.chat.breakoutDurationUpdated": "O tempo restante da sala parcial é agora de {0} minutos", - "app.chat.breakoutDurationUpdatedModerator": "O tempo das salas de descanso é agora de {0} minutos, e enviouse unha notificación.", - "app.chat.emptyLogLabel": "Rexistro da conversa baleiro", - "app.chat.clearPublicChatMessage": "A conversa publica foi retirada por un moderador", + "app.chat.breakoutDurationUpdatedModerator": "O tempo das salas parciais é agora de {0} minutos, e enviouse unha notificación.", + "app.chat.emptyLogLabel": "O rexistro da parola está baleiro", + "app.chat.clearPublicChatMessage": "A parola publica foi retirada por un moderador", "app.chat.multi.typing": "Varios usuarios están a escribir", "app.chat.one.typing": "{0} está a escribir", "app.chat.two.typing": "{0} e {1} están a escribir", - "app.chat.copySuccess": "A conversa transcrita foi copiada", - "app.chat.copyErr": "Produciuse un erro ao copiar a conversa transcrita", + "app.chat.copySuccess": "A parola transcrita foi copiada", + "app.chat.copyErr": "Produciuse un erro ao copiar a parola transcrita", "app.emojiPicker.search": "Buscar", - "app.emojiPicker.notFound": "Non se atoparon Emojis", + "app.emojiPicker.notFound": "Non se atopou ningún «emoji»", "app.emojiPicker.skintext": "Escolla o seu ton de pel predeterminado", - "app.emojiPicker.clear": "Borrar", - "app.emojiPicker.categories.label": "Categorías de Emoji", - "app.emojiPicker.categories.people": "Persoas e Corpo", - "app.emojiPicker.categories.nature": "Animais e Natureza", + "app.emojiPicker.clear": "Limpar", + "app.emojiPicker.categories.label": "Categorías de «emoji»", + "app.emojiPicker.categories.people": "Persoas e corpo", + "app.emojiPicker.categories.nature": "Animais e natureza", "app.emojiPicker.categories.foods": "Comida e bebida", "app.emojiPicker.categories.places": "Viaxes e lugares", "app.emojiPicker.categories.activity": "Actividades", @@ -47,9 +47,9 @@ "app.emojiPicker.categories.search": "Buscar resultados", "app.emojiPicker.skintones.1": "Ton de pel predeterminado", "app.emojiPicker.skintones.2": "Ton de pel claro", - "app.emojiPicker.skintones.3": "Ton de pel medio-claro", + "app.emojiPicker.skintones.3": "Ton de pel medio claro", "app.emojiPicker.skintones.4": "Ton de pel medio", - "app.emojiPicker.skintones.5": "Ton de pel medio-escuro", + "app.emojiPicker.skintones.5": "Ton de pel medio escuro", "app.emojiPicker.skintones.6": "Ton de pel escuro", "app.captions.label": "Subtítulos", "app.captions.menu.close": "Pechar", @@ -76,7 +76,7 @@ "app.captions.speech.start": "Comezou o recoñecemento de voz", "app.captions.speech.stop": "O recoñecemento de voz detívose", "app.captions.speech.error": "O recoñecemento de voz detívose por mor da incompatibilidade do navegador ou dalgún tempo de silencio", - "app.confirmation.skipConfirm": "Non preguntar de novo", + "app.confirmation.skipConfirm": "Non volver preguntar", "app.confirmation.virtualBackground.title": "Comezar novo fondo virtual", "app.confirmation.virtualBackground.description": "{0} engadirase como fondo virtual. Continuar?", "app.confirmationModal.yesLabel": "Sí", @@ -89,9 +89,9 @@ "app.notes.hide": "Agochar as notas", "app.notes.locked": "Bloqueado", "app.notes.disabled": "Fixado na área multimedia", - "app.notes.notesDropdown.covertAndUpload": "Converter notas en presentación", - "app.notes.notesDropdown.pinNotes": "Fixar notas na pizarra", - "app.notes.notesDropdown.unpinNotes": "Retirar fixación das notas", + "app.notes.notesDropdown.covertAndUpload": "Converter as notas en presentación", + "app.notes.notesDropdown.pinNotes": "Fixar as notas no encerado", + "app.notes.notesDropdown.unpinNotes": "Soltar as notas", "app.notes.notesDropdown.notesOptions": "Opcións das notas", "app.pads.hint": "Prema Esc para enfocar a barra de ferramentas do caderno de notas", "app.user.activityCheck": "Comprobar a actividade do usuario", @@ -116,11 +116,11 @@ "app.userList.menuTitleContext": "Opcións dispoñíbeis", "app.userList.chatListItem.unreadSingular": "Unha nova mensaxe", "app.userList.chatListItem.unreadPlural": "{0} novas mensaxes", - "app.userList.menu.chat.label": "Iniciar a conversa privada", + "app.userList.menu.chat.label": "Iniciar a parola privada", "app.userList.menu.clearStatus.label": "Limpar o estado", "app.userList.menu.removeUser.label": "Retirar o usuario", "app.userList.menu.removeConfirmation.label": "Retirar o usuario ({0})", - "app.userlist.menu.removeConfirmation.desc": " Impedir que este usuario se reincorpore á sesión.", + "app.userlist.menu.removeConfirmation.desc": "Impedir que este usuario volva unirse á sesión.", "app.userList.menu.muteUserAudio.label": "Desactivar o son do usuario", "app.userList.menu.unmuteUserAudio.label": "Devolverlle o son ao usuario", "app.userList.menu.webcamPin.label": " Fixar a cámara web do usuario", @@ -145,21 +145,21 @@ "app.userList.userOptions.unmuteAllLabel": "Desactivar o silencio na xuntanza", "app.userList.userOptions.unmuteAllDesc": "Devolverlle o son á xuntanza", "app.userList.userOptions.lockViewersLabel": "Bloquear espectadores", - "app.userList.userOptions.lockViewersDesc": "Bloquear certas funcionalidades para os asistentes ao encontro", + "app.userList.userOptions.lockViewersDesc": "Bloquear certas funcionalidades para os asistentes á xuntanza", "app.userList.userOptions.guestPolicyLabel": "Normas para os convidados", "app.userList.userOptions.guestPolicyDesc": "Cambiar os axustes das normas de convidados á xuntanza", "app.userList.userOptions.disableCam": "As cámaras web dos espectadores están desactivadas", "app.userList.userOptions.disableMic": "Os micrófonos dos espectadores están desactivados", - "app.userList.userOptions.disablePrivChat": "A conversa privada está desactivada", - "app.userList.userOptions.disablePubChat": "A conversa pública está desactivada", + "app.userList.userOptions.disablePrivChat": "A parola privada está desactivada", + "app.userList.userOptions.disablePubChat": "A parola pública está desactivada", "app.userList.userOptions.disableNotes": "As notas compartidas agora están bloqueadas", "app.userList.userOptions.hideUserList": "A lista de usuarios agora está agochada para os espectadores", - "app.userList.userOptions.webcamsOnlyForModerator": "Só os moderadores poden ver as cámaras web dos convidados (por mor da configuración de bloqueo)", + "app.userList.userOptions.webcamsOnlyForModerator": "Só os moderadores poden ver as cámaras web dos convidados (por mor dos axustes de bloqueo)", "app.userList.content.participants.options.clearedStatus": "Limparonse todos os estados de usuario", "app.userList.userOptions.enableCam": "As cámaras web dos espectadores están activadas", "app.userList.userOptions.enableMic": "Os micrófonos dos espectadores están activados", - "app.userList.userOptions.enablePrivChat": "A conversa privada está activada", - "app.userList.userOptions.enablePubChat": "A conversa pública está activada", + "app.userList.userOptions.enablePrivChat": "A parola privada está activada", + "app.userList.userOptions.enablePubChat": "A parola pública está activada", "app.userList.userOptions.enableNotes": "As notas compartidas agora están activadas", "app.userList.userOptions.showUserList": "A lista de usuarios agora amosase aos espectadores", "app.userList.userOptions.enableOnlyModeratorWebcam": "Pode activar agora a súaúa cámara web, todos poderán velo", @@ -183,14 +183,14 @@ "app.screenshare.screenshareFinalError": "Código {0}. Non foi posíbel compartir a pantalla.", "app.screenshare.screenshareRetryError": "Código {0}. Tente compartir a pantalla de novo.", "app.screenshare.screenshareRetryOtherEnvError": "Código {0}. Non foi posíbel compartir a pantalla. Ténteo de novo usando outro navegador ou dispositivo.", - "app.screenshare.screenshareUnsupportedEnv": "Código {0}. O navegador non é compatible. Ténteo de novo usando outro navegador ou dispositivo.", + "app.screenshare.screenshareUnsupportedEnv": "Código {0}. O navegador non é compatíbel. Ténteo de novo usando outro navegador ou dispositivo.", "app.screenshare.screensharePermissionError": "Código {0}. É necesario conceder o permiso para capturar a pantalla.", "app.meeting.ended": "Rematou a sesión", "app.meeting.meetingTimeRemaining": "Tempo restante da xuntanza: {0}", "app.meeting.meetingTimeHasEnded": "Rematou o tempo. A xuntanza pecharase en breve", "app.meeting.endedByUserMessage": "Esta sesión foi rematada por {0}", - "app.meeting.endedByNoModeratorMessageSingular": "A reunión rematou porque non estivo presente ningún moderador durante un minuto", - "app.meeting.endedByNoModeratorMessagePlural": "A reunión rematou porque ningún moderador estivo presente durante {0} minutos", + "app.meeting.endedByNoModeratorMessageSingular": "A xuntanza rematou porque non estivo presente ningún moderador durante un minuto", + "app.meeting.endedByNoModeratorMessagePlural": "A xuntanza rematou porque ningún moderador estivo presente durante {0} minutos", "app.meeting.endedMessage": "Será reenviado á pantalla de inicio", "app.meeting.alertMeetingEndsUnderMinutesSingular": "A xuntanza pecharase nun minuto.", "app.meeting.alertMeetingEndsUnderMinutesPlural": "A xuntanza pecharase en {0} minutos.", @@ -235,23 +235,23 @@ "app.presentation.presentationToolbar.fitToWidth": "Axustar ao largo", "app.presentation.presentationToolbar.fitToPage": "Axustar á páxina", "app.presentation.presentationToolbar.goToSlide": "Diapositiva {0}", - "app.presentation.presentationToolbar.hideToolsDesc": "Agochar barras de ferramentas", - "app.presentation.presentationToolbar.showToolsDesc": "Amosar barras de ferramentas", + "app.presentation.presentationToolbar.hideToolsDesc": "Agochar as barras de ferramentas", + "app.presentation.presentationToolbar.showToolsDesc": "Amosar as barras de ferramentas", "app.presentation.placeholder": "Non hai ningunha presentación activa actualmente", "app.presentationUploder.title": "Presentación", - "app.presentationUploder.message": "Como presentador tes a posibilidade de cargar calquera documento de Office ou ficheiro PDF. Recomendamos o ficheiro PDF para obter os mellores resultados. Asegúrate de seleccionar unha presentación usando a caixa de verificación do círculo do lado esquerdo.", - "app.presentationUploader.exportHint": "Ao seleccionar \"Enviar ao chat\" proporcionarase aos usuarios unha ligazón para descargar con anotacións no chat público.", - "app.presentationUploader.exportToastHeader": "Enviando ao chat (elemento {0})", - "app.presentationUploader.exportToastHeaderPlural": "Enviando ao chat ({0} elementos)", - "app.presentationUploader.exporting": "Enviando ao chat", + "app.presentationUploder.message": "Como presentador ten a posibilidade de enviar calquera documento de Office ou ficheiro PDF. Recomendamos o ficheiro PDF para obter os mellores resultados. Asegúrese de seleccionar unha presentación usando a caixa de selección do círculo do lado esquerdo.", + "app.presentationUploader.exportHint": "Ao seleccionar \"Enviando á parola\" fornéceselle aos usuarios unha ligazón para descargar con anotacións na parola pública.", + "app.presentationUploader.exportToastHeader": "Enviando á parola (elemento {0})", + "app.presentationUploader.exportToastHeaderPlural": "Enviando á parola ({0} elementos)", + "app.presentationUploader.exporting": "Enviando á parola", "app.presentationUploader.sending": "Enviando...", "app.presentationUploader.collecting": "Extraendo a diapositiva {0} de {1}...", - "app.presentationUploader.processing": "Anotando diapositiva {0} de {1}...", + "app.presentationUploader.processing": "Anotando a diapositiva {0} de {1}...", "app.presentationUploader.sent": "Enviado", - "app.presentationUploader.exportingTimeout": "A exportación está tardando demasiado...", - "app.presentationUploader.export": "Enviar para o chat", - "app.presentationUploader.export.linkAvailable": "Ligazón para descargar {0} dispoñible no chat público.", - "app.presentationUploader.export.notAccessibleWarning": "pode non cumprir as normas de accesibilidade", + "app.presentationUploader.exportingTimeout": "A exportación está a tardar demasiado…", + "app.presentationUploader.export": "Enviar para a parola", + "app.presentationUploader.export.linkAvailable": "Ligazón para descargar {0} dispoñíbel na parola pública.", + "app.presentationUploader.export.notAccessibleWarning": "é posíbel que non cumpra as normas de accesibilidade", "app.presentationUploader.currentPresentationLabel": "Presentación actual", "app.presentationUploder.extraHint": "IMPORTANTE: cada ficheiro non pode exceder {0} MB e {1} páxinas.", "app.presentationUploder.uploadLabel": "Enviar", @@ -263,8 +263,8 @@ "app.presentationUploder.dropzoneImagesLabel": "Arrastre as imaxes aquí para envialas", "app.presentationUploder.browseFilesLabel": "ou busque os ficheiros", "app.presentationUploder.browseImagesLabel": "ou busque as imaxes", - "app.presentationUploder.externalUploadTitle": "Engade contido dunha aplicación de terceiros", - "app.presentationUploder.externalUploadLabel": "Explorar ficheiros", + "app.presentationUploder.externalUploadTitle": "Engadir contido de aplicacións de terceiros", + "app.presentationUploder.externalUploadLabel": "Examinar os ficheiros", "app.presentationUploder.fileToUpload": "Para ser enviado…", "app.presentationUploder.currentBadge": "Actual", "app.presentationUploder.rejectedError": "O(s) ficheiro(s) seleccionado(s) foi(foron) rexeitado(s). Revise o(os) tipo(s) de ficheiro.", @@ -283,9 +283,9 @@ "app.presentationUploder.conversion.generatingSvg": "Xerando imaxes SVG…", "app.presentationUploder.conversion.pageCountExceeded": "O número de páxinas superou o máximo de {0}", "app.presentationUploder.conversion.invalidMimeType": "Detectouse un formato non válido (extensión={0}, tipo de contido={1})", - "app.presentationUploder.conversion.conversionTimeout": "Non se puido procesar a diapositiva {0} en {1} intentos.", - "app.presentationUploder.conversion.officeDocConversionInvalid": "Non se puido procesar o documento de Office. Cargue un PDF no seu lugar.", - "app.presentationUploder.conversion.officeDocConversionFailed": "Non se puido procesar o documento de Office. Cargue un PDF no seu lugar.", + "app.presentationUploder.conversion.conversionTimeout": "Non foi posíbel procesar a diapositiva {0} en {1} intentos.", + "app.presentationUploder.conversion.officeDocConversionInvalid": "Non foi posíbel procesar o documento de Office. Envíe un PDF no seu lugar.", + "app.presentationUploder.conversion.officeDocConversionFailed": "Non foi posíbel procesar o documento de Office. Envíe un PDF no seu lugar.", "app.presentationUploder.conversion.pdfHasBigPage": "Non foi posíbel converter o ficheiro PDF. Tente optimizalo. Tamaño máximo de páxina {0}", "app.presentationUploder.conversion.timeout": " Ouh! a conversión tomou demasiado tempo", "app.presentationUploder.conversion.pageCountFailed": "Produciuse un fallo ao determinar o número de páxinas.", @@ -305,7 +305,7 @@ "app.presentationUploder.uploadViewTitle": "Enviar presentación", "app.poll.questionAndoptions.label" : "Texto da pregunta que se amosará.\nA. Opción de enquisa *\nB. Opción de enquisa (opcional)\nC. Opción de enquisa (opcional)\nD. Opción de enquisa (opcional)\nE. Opción de enquisa (opcional)", "app.poll.customInput.label": "Entrada personalizada", - "app.poll.customInputInstructions.label": "A entrada personalizada está activada: escribe a pregunta de enquisa e as opcións nun formato determinado ou arrastra e solta un ficheiro de texto no mesmo formato.", + "app.poll.customInputInstructions.label": "A entrada personalizada está activada: escriba a pregunta de enquisa e a(s) opción(s) no formato indicado ou arrastre e solte un ficheiro de texto no mesmo formato.", "app.poll.maxOptionsWarning.label": "Só se poden usar as 5 primeiras opcións!", "app.poll.pollPaneTitle": "Enquisa", "app.poll.enableMultipleResponseLabel": "Permitir varias respostas por enquisado?", @@ -385,7 +385,7 @@ "app.muteWarning.disableMessage": "Alertas de silenciamento desactivadas ata que se active o son", "app.muteWarning.tooltip": "Prema para pechar e desactivar a advertencia ata a próxima vez que se desactive o silenciamento", "app.navBar.settingsDropdown.optionsLabel": "Opcións", - "app.navBar.settingsDropdown.fullscreenLabel": "Presentación en pantalla completa", + "app.navBar.settingsDropdown.fullscreenLabel": "Aplicación de pantalla completa", "app.navBar.settingsDropdown.settingsLabel": "Axustes", "app.navBar.settingsDropdown.aboutLabel": "Sobre", "app.navBar.settingsDropdown.leaveSessionLabel": "Abandonar a xuntanza", @@ -400,7 +400,7 @@ "app.navBar.settingsDropdown.helpLabel": "Axuda", "app.navBar.settingsDropdown.openAppLabel": "Abrir na aplicación BigBlueButton Tablet", "app.navBar.settingsDropdown.helpDesc": "Vincular o usuario a vídeo titoriais (abre unha nova lapela)", - "app.navBar.settingsDropdown.endMeetingDesc": "Finaliza a sesión actual", + "app.navBar.settingsDropdown.endMeetingDesc": "Finaliza a xuntanza en curso", "app.navBar.settingsDropdown.endMeetingLabel": "Rematar a xuntanza", "app.navBar.userListToggleBtnLabel": "Abrir/pechar a lista de usuarios", "app.navBar.toggleUserList.ariaLabel": "Os usuarios e as mensaxes se alternan", @@ -415,7 +415,7 @@ "app.endMeeting.title": "Rematar {0}", "app.endMeeting.description": "Esta acción rematará a sesión para {0} usuarios activos. Confirma que que quere rematar esta sesión?", "app.endMeeting.noUserDescription": "Confirma que quere rematar esta sesión?", - "app.endMeeting.contentWarning": "As mensaxes da conversa, as notas compartidas, o contido do encerado e os documentos compartidos desta sesión deixarán de ser accesibles directamente", + "app.endMeeting.contentWarning": "As mensaxes da parola, as notas compartidas, o contido do encerado e os documentos compartidos desta sesión deixarán de ser accesíbeis directamente", "app.endMeeting.yesLabel": "Finalizar a sesión para todos os usuarios", "app.endMeeting.noLabel": "Non", "app.about.title": "Sobre", @@ -427,14 +427,14 @@ "app.about.dismissLabel": "Cancelar", "app.about.dismissDesc": "Pechar a información sobre o cliente", "app.mobileAppModal.title": "Abrir a aplicación BigBlueButton Tablet", - "app.mobileAppModal.description": "Tes a aplicación BigBlueButton Tablet instalada no teu dispositivo?", - "app.mobileAppModal.openApp": "Si, abre a aplicación agora", - "app.mobileAppModal.obtainUrlMsg": "Obtendo da URL da reunión", - "app.mobileAppModal.obtainUrlErrorMsg": "Produciuse un erro ao tentar obter a URL da reunión", - "app.mobileAppModal.openStore": "Non, abre a App Store para descargar", + "app.mobileAppModal.description": "Ten a aplicación BigBlueButton Tablet instalada no seu dispositivo?", + "app.mobileAppModal.openApp": "Si, abrir a aplicación agora", + "app.mobileAppModal.obtainUrlMsg": "Obtendo o URL da xuntanza", + "app.mobileAppModal.obtainUrlErrorMsg": "Produciuse un erro ao tentar obter a URL da xuntanza", + "app.mobileAppModal.openStore": "Non, abrir a App Store para descargala", "app.mobileAppModal.dismissLabel": "Cancelar", "app.mobileAppModal.dismissDesc": "Pechar", - "app.mobileAppModal.userConnectedWithSameId": "O usuario {0} acaba de conectarse co mesmo ID que ti.", + "app.mobileAppModal.userConnectedWithSameId": "O usuario {0} vén de conectarse co mesmo ID que Vde.", "app.actionsBar.changeStatusLabel": "Cambiar o estado", "app.actionsBar.muteLabel": "Desactivar o son", "app.actionsBar.unmuteLabel": "Devolver o son ", @@ -445,12 +445,12 @@ "app.actionsBar.actionsDropdown.restorePresentationDesc": "Botón para restaurar a presentación após ser minimizada", "app.actionsBar.actionsDropdown.minimizePresentationLabel": "Minimizar a presentación", "app.actionsBar.actionsDropdown.minimizePresentationDesc": "Botón usado para minimizar a presentación", - "app.actionsBar.actionsDropdown.layoutModal": "Configuración do deseño", + "app.actionsBar.actionsDropdown.layoutModal": "Axustes do deseño modal", "app.screenshare.screenShareLabel" : "Compartir pantalla", "app.submenu.application.applicationSectionTitle": "Aplicación", "app.submenu.application.animationsLabel": "Animacións", "app.submenu.application.audioFilterLabel": " Filtros de son para o micrófono", - "app.submenu.application.wbToolbarsAutoHideLabel": "Ocultar automaticamente as barras de ferramentas do encerado", + "app.submenu.application.wbToolbarsAutoHideLabel": "Agochar automaticamente as barras de ferramentas do encerado", "app.submenu.application.darkThemeLabel": "Modo escuro", "app.submenu.application.fontSizeControlLabel": "Tamaño da letra", "app.submenu.application.increaseFontBtnLabel": "Incrementar o tamaño da letra", @@ -461,7 +461,7 @@ "app.submenu.application.noLocaleOptionLabel": "Non hai locais activos", "app.submenu.application.paginationEnabledLabel": "Paxinación do vídeo", "app.submenu.application.layoutOptionLabel": "Tipo de disposición", - "app.submenu.application.pushLayoutLabel": "Forzar deseño", + "app.submenu.application.pushLayoutLabel": "Aplicar o deseño", "app.submenu.application.localeDropdown.af": "Afrikaans", "app.submenu.application.localeDropdown.ar": "Árabe", "app.submenu.application.localeDropdown.az": "Azerbaiyano", @@ -528,7 +528,7 @@ "app.submenu.notification.Desc": "Definir como e que se lle notificará.", "app.submenu.notification.audioAlertLabel": "Avisos sonoros", "app.submenu.notification.pushAlertLabel": "Avisos emerxentes", - "app.submenu.notification.messagesLabel": "Mensaxe de conversa", + "app.submenu.notification.messagesLabel": "Mensaxe de parola", "app.submenu.notification.userJoinLabel": "Uniuse un usuario", "app.submenu.notification.userLeaveLabel": "Abandono da persoa usuaria", "app.submenu.notification.guestWaitingLabel": "Convidado agardando pola aprobación", @@ -569,7 +569,7 @@ "app.talkingIndicator.moreThanMaxIndicatorsWereTalking" : "{0}+ estiveron falando", "app.talkingIndicator.wasTalking" : "{0} deixou de chamar", "app.actionsBar.actionsDropdown.actionsLabel": "Accións", - "app.actionsBar.actionsDropdown.presentationLabel": "Cargar / Xestionar presentacións", + "app.actionsBar.actionsDropdown.presentationLabel": "Enviar/xestionar as presentacións", "app.actionsBar.actionsDropdown.initPollLabel": "Iniciar unha enquisa", "app.actionsBar.actionsDropdown.desktopShareLabel": "Compartir a súa pantalla", "app.actionsBar.actionsDropdown.stopDesktopShareLabel": "Deixar de compartir a súa pantalla", @@ -628,7 +628,7 @@ "app.audioNotification.audioFailedError1012": "Conexión pechada (erro ICE 1012)", "app.audioNotification.audioFailedMessage": "Produciuse un fallo na súa conexión de son", "app.audioNotification.mediaFailedMessage": "Produciuse un fallo en getUserMicMedia xa que só se permiten as orixes seguras", - "app.audioNotification.deviceChangeFailed": "Produciuse un erro no cambio do dispositivo de audio. Comproba se o dispositivo escollido está correctamente configurado e dispoñible", + "app.audioNotification.deviceChangeFailed": "Produciuse un erro no cambio do dispositivo de son. Comprobe que o dispositivo escollido está correctamente definido e está dispoñíbel", "app.audioNotification.closeLabel": "Pechar", "app.audioNotificaion.reconnectingAsListenOnly": "O micrófono bloqueouse para os espectadores, vostede está conectado só como oínte", "app.breakoutJoinConfirmation.title": "Unirse á sala parcial", @@ -642,7 +642,7 @@ "app.breakout.dropdown.manageDuration": "Cambiar a duración", "app.breakout.dropdown.destroyAll": "Remate das salas parciais", "app.breakout.dropdown.options": "Opcións das salas parciais", - "app.breakout.dropdown.manageUsers": "Xestionar usuarios", + "app.breakout.dropdown.manageUsers": "Xestionar os usuarios", "app.calculatingBreakoutTimeRemaining": "Calculando tempo restante…", "app.audioModal.ariaTitle": " Xanela modal para unirse ao son", "app.audioModal.microphoneLabel": "Micrófono", @@ -663,7 +663,7 @@ "app.audioModal.echoTestTitle": "Esta é unha proba de eco privada. Diga unhas palabras. Escoitou o son?", "app.audioModal.settingsTitle": "Cambiar os seus axustes do son", "app.audioModal.helpTitle": "Houbo un problema cos seus dispositivos multimedia", - "app.audioModal.helpText": "Deu permiso para acceder ao seu micrófono? Teña en conta que debería aparecer un diálogo cando tente unirse ao son, solicitando os permisos do seu dispositivo multimedia, acépteos para unirse á conferencia de son. Se non é así, tente cambiar os permisos do micrófono na configuración do seu navegador.", + "app.audioModal.helpText": "Deu permiso para acceder ao seu micrófono? Teña en conta que debería aparecer un diálogo cando tente unirse ao son, solicitando os permisos do seu dispositivo multimedia, acépteos para unirse á conferencia de son. Se non é así, tente cambiar os permisos do micrófono nos axustes do seu navegador.", "app.audioModal.help.noSSL": "Esta páxina non é segura. Para poder acceder ao micrófono a páxina ten que ser servida mediante HTTPS. Contacte co administrador do servidor.", "app.audioModal.help.macNotAllowed": "Parece que as preferencias do teu sistema Mac están a bloquear o acceso ao mícrofono. Abra Preferencias do sistema > Seguridade e privacidade > Privacidade > Micrófono, e verifique que o navegador que está a usar está marcado.", "app.audioModal.audioDialTitle": "Unirse usando o seu teléfono", @@ -689,7 +689,7 @@ "app.audio.changeAudioDevice": "Cambiar o dispositivo de son", "app.audio.enterSessionLabel": "Entrar na sesión", "app.audio.playSoundLabel": "Reproducir son", - "app.audio.stopAudioFeedback": "Deter a retroalimentación de audio", + "app.audio.stopAudioFeedback": "Deter a retroalimentación do son", "app.audio.backLabel": "Atrás", "app.audio.loading": "Cargando", "app.audio.microphones": "Micrófonos", @@ -702,22 +702,22 @@ "app.audio.audioSettings.testSpeakerLabel": "Probar o seu altofalante", "app.audio.audioSettings.microphoneStreamLabel": "O seu volume do fluxo de son", "app.audio.audioSettings.retryLabel": "Tentar de novo", - "app.audio.audioSettings.fallbackInputLabel": "Entrada de audio {0}", - "app.audio.audioSettings.fallbackOutputLabel": "Saída de audio {0}", - "app.audio.audioSettings.defaultOutputDeviceLabel": "Por defecto", - "app.audio.audioSettings.findingDevicesLabel": "Buscando dispositivos...", + "app.audio.audioSettings.fallbackInputLabel": "Entrada de son {0}", + "app.audio.audioSettings.fallbackOutputLabel": "Saída de son {0}", + "app.audio.audioSettings.defaultOutputDeviceLabel": "Predeterminado", + "app.audio.audioSettings.findingDevicesLabel": "Buscando dispositivos…", "app.audio.listenOnly.backLabel": "Atrás", "app.audio.listenOnly.closeLabel": "Pechar", "app.audio.permissionsOverlay.title": "Permitir o acceso ao seu micrófono", "app.audio.permissionsOverlay.hint": "Necesitamos que nos permita usar os seus dispositivos multimedia para unilo á conferencia de voz :)", - "app.audio.captions.button.start": "Iniciar subtítulos", + "app.audio.captions.button.start": "Iniciar os subtítulos", "app.audio.captions.button.stop": "Deter os subtítulos", "app.audio.captions.button.language": "Idioma", "app.audio.captions.button.transcription": "Transcrición", - "app.audio.captions.button.transcriptionSettings": "Configuración de transcrición", + "app.audio.captions.button.transcriptionSettings": "Axustes da transcrición", "app.audio.captions.speech.title": "Transcrición automática", "app.audio.captions.speech.disabled": "Desactivado", - "app.audio.captions.speech.unsupported": "O teu navegador non admite o recoñecemento de voz. O teu audio non se transcribirá", + "app.audio.captions.speech.unsupported": "O seu navegador non admite o recoñecemento de voz. O seu son non se transcribirá", "app.audio.captions.select.de-DE": "Alemán", "app.audio.captions.select.en-US": "Inglés", "app.audio.captions.select.es-ES": "Español", @@ -735,7 +735,7 @@ "app.meeting.logout.ejectedFromMeeting": "Vostede foi retirado/a da xuntanza", "app.meeting.logout.validateTokenFailedEjectReason": "Produciuse un erro ao validar o testemuño de autorización", "app.meeting.logout.userInactivityEjectReason": "Usuario inactivo durante demasiado tempo", - "app.meeting.logout.maxParticipantsReached": "Alcanzouse o número máximo de participantes permitido para esta reunión", + "app.meeting.logout.maxParticipantsReached": "Acadouse o número máximo de participantes permitido para esta xuntanza", "app.meeting-ended.rating.legendLabel": "Valoración de comentarios", "app.meeting-ended.rating.starLabel": "Estrela", "app.modal.close": "Pechar", @@ -760,8 +760,8 @@ "app.error.409": "Conflito", "app.error.410": "Rematou a xuntanza", "app.error.500": "Ouh! algo foi mal", - "app.error.503": "Desconectácheste", - "app.error.disconnected.rejoin": "Podes actualizar a páxina para unirte de novo.", + "app.error.503": "Vde. foi desconectado", + "app.error.disconnected.rejoin": "Pode actualizar a páxina para unirse de novo.", "app.error.userLoggedOut": "O usuario ten un testemuño de sesión non válido por mor do peche da sesión", "app.error.ejectedUser": "O usuario ten un testemuño de sesión non válido por mor da expulsión", "app.error.joinedAnotherWindow": "Semella que esta sesión está aberta noutra xanela do navegador.", @@ -807,16 +807,16 @@ "app.userList.guest.feedbackMessage": "Acción aplicada:", "app.user-info.title": "Atopar directorio", "app.toast.breakoutRoomEnded": "A sala parcial rematou. Volva incorporarse ao son.", - "app.toast.chat.public": "Nova mensaxe na conversa pública", - "app.toast.chat.private": "Nova mensaxe na conversa privada", + "app.toast.chat.public": "Nova mensaxe na parola pública", + "app.toast.chat.private": "Nova mensaxe na parola privada", "app.toast.chat.system": "Sistema", "app.toast.chat.poll": "Resultados da enquisa", - "app.toast.chat.pollClick": "Publicáronse os resultados das enquisas. Fai clic aquí para ver.", + "app.toast.chat.pollClick": "Publicáronse os resultados das enquisas. Prema aquí para velos.", "app.toast.clearedEmoji.label": "Estado do emoji limpo", "app.toast.setEmoji.label": "Estado do emoji cambiado a {0}", "app.toast.meetingMuteOn.label": "Todos os usuarios foron silenciados", "app.toast.meetingMuteOnViewers.label": "Todos os espectadores foron silenciados", - "app.toast.meetingMuteOff.label": "Desactivouse a reunión silenciosa", + "app.toast.meetingMuteOff.label": "Desactivouse o silencio da xuntanza", "app.toast.setEmoji.raiseHand": "Vostede ergueu a man", "app.toast.setEmoji.lowerHand": "A túa man foi baixada", "app.toast.promotedLabel": "Vostede foi promovido a moderador", @@ -838,9 +838,9 @@ "app.shortcut-help.openOptions": "Abrir opcións", "app.shortcut-help.toggleUserList": "Alternar a lista de usuarios", "app.shortcut-help.toggleMute": "Silenciar/Devolver o son", - "app.shortcut-help.togglePublicChat": "Alternar a conversa pública (a lista de usuarios debe estar aberta)", - "app.shortcut-help.hidePrivateChat": "Agochar a conversa privada", - "app.shortcut-help.closePrivateChat": "Pechar a conversa privada", + "app.shortcut-help.togglePublicChat": "Alternar a parola pública (a lista de usuarios debe estar aberta)", + "app.shortcut-help.hidePrivateChat": "Agochar a parola privada", + "app.shortcut-help.closePrivateChat": "Pechar a parola privada", "app.shortcut-help.openActions": "Abrir o menú de accións", "app.shortcut-help.raiseHand": "Alternar erguer a man", "app.shortcut-help.openDebugWindow": "Abrir a xanela de depuración", @@ -853,7 +853,7 @@ "app.shortcut-help.toggleFullscreenKey": "Intro", "app.shortcut-help.nextSlideKey": "Frecha dereita", "app.shortcut-help.previousSlideKey": "Frecha esquerda", - "app.shortcut-help.select": "Selecciona Ferramenta", + "app.shortcut-help.select": "Seleccionar a ferramenta", "app.shortcut-help.pencil": "Lapis", "app.shortcut-help.eraser": "Borrador", "app.shortcut-help.rectangle": "Rectángulo", @@ -869,12 +869,12 @@ "app.shortcut-help.zoomIn": "Achegarse", "app.shortcut-help.zoomOut": "Afastarse", "app.shortcut-help.zoomFit": "Restaurar o zoom", - "app.shortcut-help.zoomSelect": "Zoom á selección", - "app.shortcut-help.flipH": "Volteo horizontal", - "app.shortcut-help.flipV": "Volteo vertical", + "app.shortcut-help.zoomSelect": "Zoom sobre a selección", + "app.shortcut-help.flipH": "Voltear en horizontal", + "app.shortcut-help.flipV": "Voltear en vertical", "app.shortcut-help.lock": "Bloquear / Desbloquear", - "app.shortcut-help.moveToFront": "Mover ao fronte", - "app.shortcut-help.moveToBack": "Mover cara atrás", + "app.shortcut-help.moveToFront": "Mover para a fronte", + "app.shortcut-help.moveToBack": "Mover para o fondo", "app.shortcut-help.moveForward": "Mover cara adiante", "app.shortcut-help.moveBackward": "Mover cara atrás", "app.shortcut-help.undo": "Desfacer", @@ -892,8 +892,8 @@ "app.lock-viewers.webcamLabel": "Compartir a cámara web", "app.lock-viewers.otherViewersWebcamLabel": "Ver a cámara web doutros espectadores", "app.lock-viewers.microphoneLable": "Compartir o micrófono", - "app.lock-viewers.PublicChatLabel": "Enviar mensaxes á conversa pública ", - "app.lock-viewers.PrivateChatLable": "Enviar mensaxes á conversa privada", + "app.lock-viewers.PublicChatLabel": "Enviar mensaxes á parola pública ", + "app.lock-viewers.PrivateChatLable": "Enviar mensaxes á parola privada", "app.lock-viewers.notesLabel": "Editar notas compartidas", "app.lock-viewers.userListLabel": "Ver outros espectadores na lista de usuarios", "app.lock-viewers.ariaTitle": "Bloquear a xanela modal de axustes dos espectadores", @@ -907,7 +907,7 @@ "app.guest-policy.button.askModerator": "Pregunta ao moderador", "app.guest-policy.button.alwaysAccept": "Aceptar sempre", "app.guest-policy.button.alwaysDeny": "Denegar sempre", - "app.guest-policy.policyBtnDesc": "Establece a política de convidados á xuntanza", + "app.guest-policy.policyBtnDesc": "Estabelece a política de convidados á xuntanza", "app.connection-status.ariaTitle": "Estado da conexión modal", "app.connection-status.title": "Estado da conexión", "app.connection-status.description": "Ver o estado de conexión dos usuarios", @@ -943,11 +943,11 @@ "app.recording.startDescription": "Máis tarde pode usar o botón de gravación para deter a gravación.", "app.recording.stopDescription": "Confirma que quere deter a gravación? Pode continuala premendo de novo o botón de gravación.", "app.recording.notify.title": "Comezou a gravación", - "app.recording.notify.description": "Haberá unha gravación dispoñible en función do resto desta sesión", + "app.recording.notify.description": "Haberá unha gravación dispoñíbel en función do resto desta sesión", "app.recording.notify.continue": "Continuar", - "app.recording.notify.leave": "Abandonar a xuntanza", + "app.recording.notify.leave": "Abandonar a sesión", "app.recording.notify.continueLabel" : "Aceptar a gravación e continuar", - "app.recording.notify.leaveLabel" : "Non aceptar a gravación e saír da reunión", + "app.recording.notify.leaveLabel" : "Non aceptar a gravación e saír da xuntanza", "app.videoPreview.cameraLabel": "Cámara web", "app.videoPreview.profileLabel": "Calidade", "app.videoPreview.quality.low": "Baixa", @@ -976,7 +976,7 @@ "app.video.joinVideo": "Compartir a cámara web", "app.video.connecting": "Esta a comezar o uso compartido da cámara web…", "app.video.leaveVideo": "Deixar de compartir a cámara web", - "app.video.videoSettings": "Configuración do vídeo", + "app.video.videoSettings": "Axustes do vídeo", "app.video.visualEffects": "Efectos visuais", "app.video.advancedVideo": "Abrir os axustes avanzados", "app.video.iceCandidateError": "Produciuse un erro ao engadir un candidato ICE", @@ -993,7 +993,7 @@ "app.video.notReadableError": "Non foi posíbel obter vídeo da cámara web. Asegúrese de que outro programa non estea a usar a cámara web", "app.video.timeoutError": "O navegador non respondeu a tempo.", "app.video.genericError": "Produciuse un erro descoñecido co dispositivo (erro {0})", - "app.video.inactiveError": "A túa cámara web parouse inesperadamente. Revisa os permisos do teu navegador", + "app.video.inactiveError": "A súa cámara web detívose inesperadamente. Revise os permisos do seu navegador", "app.video.mediaTimedOutError": "A transmisión da súa cámara web foi interrompida. Tente compartila de novo", "app.video.mediaFlowTimeout1020": "Os recursos multimedia non foron quen de acadar o servidor (erro 1020)", "app.video.suggestWebcamLock": "Forzar os axustes de bloqueo para as cámaras web dos espectadores?", @@ -1006,7 +1006,7 @@ "app.video.videoButtonDesc": "Compartir a cámara web", "app.video.videoMenu": "Menú de vídeo", "app.video.videoMenuDisabled": "O menú de vídeo da cámara web está desactivada nos axustes", - "app.video.videoMenuDesc": "Abrir o menú despregable de vídeo", + "app.video.videoMenuDesc": "Abrir o menú despregábel de vídeo", "app.video.pagination.prevPage": "Ver os vídeos anteriores", "app.video.pagination.nextPage": "Ver os seguintes vídeos", "app.video.clientDisconnected": "Non é posíbel compartir a cámara web por mor de problemas de conexión", @@ -1017,15 +1017,15 @@ "app.video.virtualBackground.coffeeshop": "Cafetería", "app.video.virtualBackground.background": "Fondo", "app.video.virtualBackground.backgroundWithIndex": "Fondo {0}", - "app.video.virtualBackground.custom": "Cargar desde o teu ordenador", - "app.video.virtualBackground.remove": "Eliminar a imaxe engadida", + "app.video.virtualBackground.custom": "Enviar dende o seu computador", + "app.video.virtualBackground.remove": "Retirar a imaxe engadida", "app.video.virtualBackground.genericError": "Produciuse un fallo ao aplicar o efecto da cámara. Ténteo de novo.", - "app.video.virtualBackground.camBgAriaDesc": "Establece o fondo virtual da cámara web a {0}", + "app.video.virtualBackground.camBgAriaDesc": "Estabelece o fondo virtual da cámara web a {0}", "app.video.virtualBackground.maximumFileSizeExceeded": "Superouse o tamaño máximo do ficheiro. ({0}MB)", "app.video.virtualBackground.typeNotAllowed": "Non se permite o tipo de ficheiro.", "app.video.virtualBackground.errorOnRead": "Produciuse un erro ao ler o ficheiro.", - "app.video.virtualBackground.uploaded": "Cargado", - "app.video.virtualBackground.uploading": "Cargando...", + "app.video.virtualBackground.uploaded": "Enviado", + "app.video.virtualBackground.uploading": "Enviando…", "app.video.virtualBackground.button.customDesc": "Engade unha nova imaxe de fondo virtual", "app.video.camCapReached": "Non pode compartir máis cámaras", "app.video.meetingCamCapReached": "A xuntanza acadou o seu límite de cámaras simultáneas", @@ -1047,7 +1047,7 @@ "app.whiteboard.annotations.poll": "Os resultados da enquisa foron publicados", "app.whiteboard.annotations.pollResult": "Resultado da enquisa", "app.whiteboard.annotations.noResponses": "Sen respostas", - "app.whiteboard.annotations.notAllowed": "Non tes permiso para facer este cambio", + "app.whiteboard.annotations.notAllowed": "Non ten permiso para facer este cambio", "app.whiteboard.annotations.numberExceeded": "O número de anotacións superou o límite ({0})", "app.whiteboard.toolbar.tools": "Ferramentas", "app.whiteboard.toolbar.tools.hand": "Panorama", @@ -1075,7 +1075,7 @@ "app.whiteboard.toolbar.color.silver": "Prata", "app.whiteboard.toolbar.undo": "Desfacer as anotacións", "app.whiteboard.toolbar.clear": "Limpar todas as anotacións", - "app.whiteboard.toolbar.clearConfirmation": "Estás seguro de que queres borrar todas as anotacións?", + "app.whiteboard.toolbar.clearConfirmation": "Confirma que quere limpar todas as anotacións?", "app.whiteboard.toolbar.multiUserOn": "Activar o modo multiusuario do encerado", "app.whiteboard.toolbar.multiUserOff": "Desactivar o modo multiusuario do encerado", "app.whiteboard.toolbar.palmRejectionOn": "Activar o rexeitamento da palma da man", @@ -1086,7 +1086,7 @@ "app.feedback.subtitle": "Encantaríanos saber cal foi a súa experiencia con BigBlueButton (opcional)", "app.feedback.textarea": "Como podemos mellorar BigBlueButton?", "app.feedback.sendFeedback": "Enviar comentarios", - "app.feedback.sendFeedbackDesc": "Enviar comentarios e abandonar a sesión", + "app.feedback.sendFeedbackDesc": "Enviar comentarios e abandonar a xuntanza", "app.videoDock.webcamMirrorLabel": "Espello", "app.videoDock.webcamMirrorDesc": "Inverter a cámara web seleccionada", "app.videoDock.webcamFocusLabel": "Poñer en foco", @@ -1133,8 +1133,8 @@ "app.createBreakoutRoom.addRoomTime": "Incrementar o tempo da sala parcial", "app.createBreakoutRoom.addParticipantLabel": "+ Engadir participante", "app.createBreakoutRoom.freeJoin": "Permitirlle aos usuarios escoller a sala parcial á que incorporarse", - "app.createBreakoutRoom.captureNotes": "Captura notas compartidas cando rematen as salas de grupos", - "app.createBreakoutRoom.captureSlides": "Captura o encerado cando rematen as salas de grupos", + "app.createBreakoutRoom.captureNotes": "Capturar as notas compartidas cando rematen as salas parciais", + "app.createBreakoutRoom.captureSlides": "Capturar o encerado cando rematen as salas parciais", "app.createBreakoutRoom.leastOneWarnBreakout": "Debe poñer polo menos un usuario nunha sala parcial", "app.createBreakoutRoom.minimumDurationWarnBreakout": "A duración mínima para unha sala parcial é de {0} minutos.", "app.createBreakoutRoom.modalDesc": "Consello: pode arrastrar e soltar o nome dun usuario para asignalo a unha sala parcial específica.", @@ -1142,19 +1142,19 @@ "app.createBreakoutRoom.numberOfRoomsError": "O número de salas non é válido", "app.createBreakoutRoom.duplicatedRoomNameError": "Non é posíbel duplicar o nome da sala.", "app.createBreakoutRoom.emptyRoomNameError": "O nome da sala non pode estar baleiro.", - "app.createBreakoutRoom.setTimeInMinutes": "Establecer a duración en (minutos)", + "app.createBreakoutRoom.setTimeInMinutes": "Estabelecer a duración en (minutos)", "app.createBreakoutRoom.setTimeLabel": "Aplicar", "app.createBreakoutRoom.setTimeCancel": "Cancelar", "app.createBreakoutRoom.setTimeHigherThanMeetingTimeError": "A duración das salas parciais non pode exceder o tempo restante da xuntanza.", "app.createBreakoutRoom.roomNameInputDesc": "Actualiza o nome da sala parcial", - "app.createBreakoutRoom.movedUserLabel": "Moveuse {0} á sala {1}", - "app.updateBreakoutRoom.modalDesc": "Para actualizar ou invitar un usuario, só tes que arrástralo á sala desexada.", + "app.createBreakoutRoom.movedUserLabel": "{0} foi movido cara á sala {1}", + "app.updateBreakoutRoom.modalDesc": "Para actualizar ou convidar a un usuario, só ten que arrástralo cara á sala desexada.", "app.updateBreakoutRoom.cancelLabel": "Cancelar", - "app.updateBreakoutRoom.title": "Actualizar salas de grupos", + "app.updateBreakoutRoom.title": "Actualizar as salas parciais", "app.updateBreakoutRoom.confirm": "Aplicar", - "app.updateBreakoutRoom.userChangeRoomNotification": "Trasladáronte á sala {0}.", + "app.updateBreakoutRoom.userChangeRoomNotification": "Vde. foi trasladado á sala {0}.", "app.smartMediaShare.externalVideo": "Vídeo(s) externo(s)", - "app.update.resetRoom": "Restablecer a sala de usuarios", + "app.update.resetRoom": "Restabelecer a sala de usuarios", "app.externalVideo.start": "Compartir un novo vídeo", "app.externalVideo.title": "Compartir un vídeo externo", "app.externalVideo.input": "URL do vídeo externo", @@ -1177,17 +1177,17 @@ "app.debugWindow.form.button.copy": "Copiar", "app.debugWindow.form.enableAutoarrangeLayoutLabel": "Activar a disposición de organización automática", "app.debugWindow.form.enableAutoarrangeLayoutDescription": "(desactivarase se arrastra ou redimensiona a área de cámaras web)", - "app.debugWindow.form.chatLoggerLabel": "Probar os niveis de rexistro da conversa", + "app.debugWindow.form.chatLoggerLabel": "Probar os niveis de rexistro da parola", "app.debugWindow.form.button.apply": "Aplicar", "app.layout.modal.title": "Deseños", "app.layout.modal.confirm": "Confirmar", "app.layout.modal.cancel": "Cancelar", "app.layout.modal.layoutLabel": "Selecciona o teu deseño", - "app.layout.modal.keepPushingLayoutLabel": "Forzar o deseño para todos", - "app.layout.modal.pushLayoutLabel": "Forzar para todos", - "app.layout.modal.layoutToastLabel": "Cambiouse a configuración de deseño", + "app.layout.modal.keepPushingLayoutLabel": "Aplicar o deseño para todos", + "app.layout.modal.pushLayoutLabel": "Aplicar para todos", + "app.layout.modal.layoutToastLabel": "Cambiaron os axustes de deseño", "app.layout.modal.layoutSingular": "Deseño", - "app.layout.modal.layoutBtnDesc": "Establece o deseño como opción seleccionada", + "app.layout.modal.layoutBtnDesc": "Estabelece o deseño como opción seleccionada", "app.layout.style.custom": "Personalizado", "app.layout.style.smart": "Disposición intelixente", "app.layout.style.presentationFocus": "Poñer o foco na presentación", @@ -1228,7 +1228,7 @@ "playback.player.chat.message.poll.option.true": "Verdadeiro", "playback.player.chat.message.poll.option.false": "Falso", "playback.player.chat.message.video.name": "Vídeo externo", - "playback.player.chat.wrapper.aria": "Área de conversa", + "playback.player.chat.wrapper.aria": "Área de parola", "playback.player.notes.wrapper.aria": "Área de notas", "playback.player.presentation.wrapper.aria": "Área de presentación", "playback.player.screenshare.wrapper.aria": "Área de pantalla compartida", @@ -1297,7 +1297,7 @@ "app.learningDashboard.statusTimelineTable.thumbnail": "Miniatura da presentación", "app.learningDashboard.statusTimelineTable.presentation": "Presentación", "app.learningDashboard.statusTimelineTable.pageNumber": "Páxina", - "app.learningDashboard.statusTimelineTable.setAt": "Establecido en", + "app.learningDashboard.statusTimelineTable.setAt": "Estabelecido en", "app.learningDashboard.errors.invalidToken": "O testemuño de sesión non é válido", "app.learningDashboard.errors.dataUnavailable": "Os datos xa non están dispoñíbeis", "mobileApp.portals.list.empty.addFirstPortal.label": "Engada o seu primeiro portal usando o botón anterior,", From 02ba4c6ff8e78a0f4384ad1b7c7367c5a90376e8 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 9 Aug 2023 09:54:38 -0300 Subject: [PATCH 159/252] Fix: Getting final Url (from redirect) on presentation upload --- .../PresentationUrlDownloadService.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PresentationUrlDownloadService.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PresentationUrlDownloadService.java index 9c67a8484a..6fe5e1014f 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PresentationUrlDownloadService.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PresentationUrlDownloadService.java @@ -12,14 +12,12 @@ import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.stream.Stream; - -import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.config.RequestConfig; import org.apache.http.entity.ContentType; import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; import org.apache.http.impl.nio.client.HttpAsyncClients; @@ -197,6 +195,7 @@ public class PresentationUrlDownloadService { conn.setReadTimeout(60000); conn.addRequestProperty("Accept-Language", "en-US,en;q=0.8"); conn.addRequestProperty("User-Agent", "Mozilla"); + conn.setInstanceFollowRedirects(false); // normally, 3xx is redirect int status = conn.getResponseCode(); @@ -287,10 +286,21 @@ public class PresentationUrlDownloadService { String finalUrl = followRedirect(meetingId, urlString, 0, urlString); if (finalUrl == null) return false; + if(!finalUrl.equals(urlString)) { + log.info("Redirected to Final URL [{}]", finalUrl); + } boolean success = false; - CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault(); + //Disable follow redirect since finalUrl already did it + RequestConfig requestConfig = RequestConfig.custom() + .setRedirectsEnabled(false) + .build(); + + CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom() + .setDefaultRequestConfig(requestConfig) + .build(); + try { httpclient.start(); File download = new File(filename); From b8a1b881c51e3af851f82666019773f8d5013844 Mon Sep 17 00:00:00 2001 From: prlanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Wed, 9 Aug 2023 11:09:27 -0300 Subject: [PATCH 160/252] fix(audio): clear connection timeout on autoplay failures If the autoplay block is triggered in listen only, the connection timer keeps ticking even if the user correctly accepts the audio play prompt. That causes an audio re-connect once the timeout expires. Clear the connection timer if the audio bridge starts with NotAllowedError as a soft error. For connection purposes, the audio join procedure worked. The autoplay thing is at the UI/UX level, not WebRTC. --- .../imports/api/audio/client/bridge/sfu-audio-bridge.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/sfu-audio-bridge.js b/bigbluebutton-html5/imports/api/audio/client/bridge/sfu-audio-bridge.js index 7246cd3898..e26fec57c7 100755 --- a/bigbluebutton-html5/imports/api/audio/client/bridge/sfu-audio-bridge.js +++ b/bigbluebutton-html5/imports/api/audio/client/bridge/sfu-audio-bridge.js @@ -280,6 +280,11 @@ export default class SFUAudioBridge extends BaseAudioBridge { }, }, 'SFU audio media play failed due to autoplay error'); this.dispatchAutoplayHandlingEvent(mediaElement); + // For connection purposes, this worked - the autoplay thing is a client + // side soft issue to be handled at the UI/UX level, not WebRTC/negotiation + // So: clear the connection timer + this.clearConnectionTimeout(); + this.reconnecting = false; } else { const normalizedError = { errorCode: 1004, From c55a9b43b331447bc5210c92666f0527756c84ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Wed, 9 Aug 2023 13:06:27 -0300 Subject: [PATCH 161/252] add debounce function --- bigbluebutton-html5/imports/utils/debounce.js | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 bigbluebutton-html5/imports/utils/debounce.js diff --git a/bigbluebutton-html5/imports/utils/debounce.js b/bigbluebutton-html5/imports/utils/debounce.js new file mode 100644 index 0000000000..116c76d998 --- /dev/null +++ b/bigbluebutton-html5/imports/utils/debounce.js @@ -0,0 +1,43 @@ +export function debounce(func, delay, options = {}) { + let timeoutId; + let lastArgs; + let lastThis; + let calledOnce = false; + + const { leading = false, trailing = true } = options; + + function invokeFunc() { + func.apply(lastThis, lastArgs); + lastArgs = null; + lastThis = null; + } + + return function (...args) { + lastArgs = args; + lastThis = this; + + if (!timeoutId) { + if (leading && !calledOnce) { + invokeFunc(); + calledOnce = true; + } + + timeoutId = setTimeout(() => { + if (!trailing) { + clearTimeout(timeoutId); + timeoutId = null; + } else { + invokeFunc(); + timeoutId = null; + } + calledOnce = false; + }, delay); + } else if (trailing) { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + invokeFunc(); + timeoutId = null; + }, delay); + } + }; +} From 7fee4d5f953f32de6d4e34893a6a9b94ca033926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Wed, 9 Aug 2023 13:06:57 -0300 Subject: [PATCH 162/252] replace mute debounce --- bigbluebutton-html5/imports/ui/components/audio/service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/audio/service.js b/bigbluebutton-html5/imports/ui/components/audio/service.js index 5e79bca7e6..559b91517f 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/service.js +++ b/bigbluebutton-html5/imports/ui/components/audio/service.js @@ -1,7 +1,7 @@ import Users from '/imports/api/users'; import Auth from '/imports/ui/services/auth'; import { throttle } from '/imports/utils/throttle'; -import { debounce } from 'radash'; +import { debounce } from '/imports/utils/debounce'; import AudioManager from '/imports/ui/services/audio-manager'; import Meetings from '/imports/api/meetings'; import { makeCall } from '/imports/ui/services/api'; @@ -123,7 +123,7 @@ export default { joinListenOnly: () => AudioManager.joinListenOnly(), joinMicrophone: () => AudioManager.joinMicrophone(), joinEchoTest: () => AudioManager.joinEchoTest(), - toggleMuteMicrophone: debounce({ delay: 500 }, toggleMuteMicrophone), + toggleMuteMicrophone: debounce(toggleMuteMicrophone, 500, { leading: true, trailing: false }), changeInputDevice: (inputDeviceId) => AudioManager.changeInputDevice(inputDeviceId), changeInputStream: (newInputStream) => { AudioManager.inputStream = newInputStream; }, liveChangeInputDevice: (inputDeviceId) => AudioManager.liveChangeInputDevice(inputDeviceId), From 80094581f61a0c39fa802c0d9cfaf19150e39e25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Wed, 9 Aug 2023 13:12:29 -0300 Subject: [PATCH 163/252] replace leading:true, trailing: false debounces --- .../ui/components/nav-bar/talking-indicator/container.jsx | 6 +++--- .../imports/ui/components/polling/service.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/container.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/container.jsx index 8e11def2ce..72897e4c34 100644 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/container.jsx @@ -2,7 +2,7 @@ import React, { useContext } from 'react'; import { withTracker } from 'meteor/react-meteor-data'; import VoiceUsers from '/imports/api/voice-users'; import Auth from '/imports/ui/services/auth'; -import { debounce } from 'radash'; +import { debounce } from '/imports/utils/debounce'; import TalkingIndicator from './component'; import { makeCall } from '/imports/ui/services/api'; import { meetingIsBreakout } from '/imports/ui/components/app/service'; @@ -80,7 +80,7 @@ export default withTracker(() => { } } - const muteUser = debounce({ delay: TALKING_INDICATOR_MUTE_INTERVAL }, (id) => { + const muteUser = debounce((id) => { const user = VoiceUsers.findOne({ meetingId, intId: id }, { fields: { muted: 1, @@ -88,7 +88,7 @@ export default withTracker(() => { }); if (user.muted) return; makeCall('toggleVoice', id); - }); + }, TALKING_INDICATOR_MUTE_INTERVAL, { leading: true, trailing: false }); return { talkers, diff --git a/bigbluebutton-html5/imports/ui/components/polling/service.js b/bigbluebutton-html5/imports/ui/components/polling/service.js index 1dae359fa9..b2a2388246 100644 --- a/bigbluebutton-html5/imports/ui/components/polling/service.js +++ b/bigbluebutton-html5/imports/ui/components/polling/service.js @@ -1,6 +1,6 @@ import { makeCall } from '/imports/ui/services/api'; import Polls from '/imports/api/polls'; -import { debounce } from 'radash'; +import { debounce } from '/imports/utils/debounce'; const MAX_CHAR_LENGTH = 5; @@ -43,8 +43,8 @@ const mapPolls = () => { }, pollExists: true, amIRequester, - handleVote: debounce({ delay: 500 }, handleVote), - handleTypedVote: debounce({ delay: 500 }, handleTypedVote), + handleVote: debounce(handleVote, 500, { leading: true, trailing: false }), + handleTypedVote: debounce(handleTypedVote, 500, { leading: true, trailing: false }), }; }; From 835bbd4733b5b85c987edbd87cb012f9afd6fe0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Wed, 9 Aug 2023 13:26:42 -0300 Subject: [PATCH 164/252] replace unaffected debounce --- .../ui/components/chat/time-window-list/component.jsx | 4 ++-- .../time-window-chat-item/message-chat-item/component.jsx | 4 ++-- .../imports/ui/components/presentation/component.jsx | 6 +++--- .../imports/ui/components/screenshare/component.jsx | 5 +++-- .../ui/components/user-list/chat-list-item/component.jsx | 6 +++--- .../imports/ui/components/video-provider/component.jsx | 7 ++++--- .../imports/ui/components/video-provider/service.js | 7 ++++--- .../components/video-provider/video-button/component.jsx | 6 +++--- 8 files changed, 24 insertions(+), 21 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/chat/time-window-list/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/time-window-list/component.jsx index f140e0e374..44a2b07aeb 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/time-window-list/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/time-window-list/component.jsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import { findDOMNode } from 'react-dom'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; -import { debounce } from 'radash'; +import { debounce } from '/imports/utils/debounce'; import { AutoSizer,CellMeasurer, CellMeasurerCache } from 'react-virtualized'; import Styled from './styles'; import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger'; @@ -69,7 +69,7 @@ class TimeWindowList extends PureComponent { }, }); this.userScrolledBack = false; - this.handleScrollUpdate = debounce({ delay: 150 }, this.handleScrollUpdate.bind(this)); + this.handleScrollUpdate = debounce(this.handleScrollUpdate.bind(this), 150); this.rowRender = this.rowRender.bind(this); this.forceCacheUpdate = this.forceCacheUpdate.bind(this); this.systemMessagesResized = {}; diff --git a/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/message-chat-item/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/message-chat-item/component.jsx index ac7dfdb430..d85bce85dc 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/message-chat-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/time-window-list/time-window-chat-item/message-chat-item/component.jsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import { debounce } from 'radash'; +import { debounce } from '/imports/utils/debounce'; import fastdom from 'fastdom'; import { injectIntl } from 'react-intl'; import ChatLogger from '/imports/ui/components/chat/chat-logger/ChatLogger'; @@ -41,7 +41,7 @@ class MessageChatItem extends PureComponent { this.ticking = false; - this.handleMessageInViewport = debounce({ delay: 50 }, this.handleMessageInViewport.bind(this)); + this.handleMessageInViewport = debounce(this.handleMessageInViewport.bind(this), 50); } componentDidMount() { diff --git a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx index 62d23e57ab..a0fef0d38d 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx @@ -19,7 +19,7 @@ import { colorContentBackground } from '/imports/ui/stylesheets/styled-component import browserInfo from '/imports/utils/browserInfo'; import { addNewAlert } from '../screenreader-alert/service'; import { clearCursors } from '/imports/ui/components/whiteboard/cursors/service'; -import { debounce } from 'radash'; +import { debounce } from '/imports/utils/debounce'; const intlMessages = defineMessages({ presentationLabel: { @@ -86,13 +86,13 @@ class Presentation extends PureComponent { this.getSvgRef = this.getSvgRef.bind(this); this.setFitToWidth = this.setFitToWidth.bind(this); - this.zoomChanger = debounce({ delay: 200 }, this.zoomChanger.bind(this)); + this.zoomChanger = debounce(this.zoomChanger.bind(this), 200); this.updateLocalPosition = this.updateLocalPosition.bind(this); this.panAndZoomChanger = this.panAndZoomChanger.bind(this); this.fitToWidthHandler = this.fitToWidthHandler.bind(this); this.onFullscreenChange = this.onFullscreenChange.bind(this); this.getPresentationSizesAvailable = this.getPresentationSizesAvailable.bind(this); - this.handleResize = debounce({ delay: 200 }, this.handleResize.bind(this)); + this.handleResize = debounce(this.handleResize.bind(this), 200); this.setTldrawAPI = this.setTldrawAPI.bind(this); this.setIsPanning = this.setIsPanning.bind(this); this.setIsToolbarVisible = this.setIsToolbarVisible.bind(this); diff --git a/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx b/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx index bf7a2ac6bd..94af6b95a3 100755 --- a/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { injectIntl } from 'react-intl'; import PropTypes from 'prop-types'; -import { debounce } from 'radash'; +import { debounce } from '/imports/utils/debounce'; import { Session } from 'meteor/session'; import FullscreenButtonContainer from '/imports/ui/components/common/fullscreen-button/container'; import SwitchButtonContainer from './switch-button/container'; @@ -72,8 +72,9 @@ class ScreenshareComponent extends React.Component { this.handleOnMuted = this.handleOnMuted.bind(this); this.dispatchScreenShareSize = this.dispatchScreenShareSize.bind(this); this.debouncedDispatchScreenShareSize = debounce( - { delay: SCREEN_SIZE_DISPATCH_INTERVAL }, this.dispatchScreenShareSize, + SCREEN_SIZE_DISPATCH_INTERVAL, + { leading: false, trailing: true }, ); const { locales, icon } = props; diff --git a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx index bec13d6a08..85f7e7b3a5 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; -import { debounce } from 'radash'; +import { debounce } from '/imports/utils/debounce'; import withShortcutHelper from '/imports/ui/components/shortcut-help/service'; import Styled from './styles'; import UserAvatar from '/imports/ui/components/user-avatar/component'; @@ -14,9 +14,9 @@ const PUBLIC_CHAT_KEY = CHAT_CONFIG.public_id; let globalAppplyStateToProps = () => {}; -const throttledFunc = debounce({ delay: DEBOUNCE_TIME }, () => { +const throttledFunc = debounce(() => { globalAppplyStateToProps(); -}); +}, DEBOUNCE_TIME, { trailing: true, leading: true }); const intlMessages = defineMessages({ titlePublic: { diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx index 4ac4b7740c..8f37b0dc3a 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx @@ -3,7 +3,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import ReconnectingWebSocket from 'reconnecting-websocket'; import { defineMessages, injectIntl } from 'react-intl'; -import { debounce } from 'radash'; +import { debounce } from '/imports/utils/debounce'; import VideoService from './service'; import VideoListContainer from './video-list/container'; import { @@ -176,8 +176,9 @@ class VideoProvider extends Component { this.onWsMessage = this.onWsMessage.bind(this); this.updateStreams = this.updateStreams.bind(this); this.debouncedConnectStreams = debounce( - { delay: VideoService.getPageChangeDebounceTime() }, - this.connectStreams + this.connectStreams, + VideoService.getPageChangeDebounceTime(), + { leading: false, trailing: true }, ); this.startVirtualBackgroundByDrop = this.startVirtualBackgroundByDrop.bind(this); } diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/service.js b/bigbluebutton-html5/imports/ui/components/video-provider/service.js index ddcf92f9d1..4b2c742913 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/service.js +++ b/bigbluebutton-html5/imports/ui/components/video-provider/service.js @@ -16,7 +16,7 @@ import VideoPreviewService from '../video-preview/service'; import Storage from '/imports/ui/services/storage/session'; import BBBStorage from '/imports/ui/services/storage'; import logger from '/imports/startup/client/logger'; -import { debounce } from 'radash'; +import { debounce } from '/imports/utils/debounce'; import { partition } from '/imports/utils/array-utils'; import { getSortingMethod, @@ -1006,8 +1006,9 @@ export default { notify: message => notify(message, 'error', 'video'), updateNumberOfDevices: devices => videoService.updateNumberOfDevices(devices), applyCameraProfile: debounce( - { delay: CAMERA_QUALITY_THR_DEBOUNCE }, - videoService.applyCameraProfile.bind(videoService) + videoService.applyCameraProfile.bind(videoService), + CAMERA_QUALITY_THR_DEBOUNCE, + { leading: false, trailing: true }, ), getThreshold: (numberOfPublishers) => videoService.getThreshold(numberOfPublishers), isPaginationEnabled: () => videoService.isPaginationEnabled(), diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-button/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-button/component.jsx index 2fdd97496a..01932e9ca5 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-button/component.jsx @@ -5,7 +5,7 @@ import VideoService from '../service'; import { defineMessages, injectIntl } from 'react-intl'; import Styled from './styles'; import deviceInfo from '/imports/utils/deviceInfo'; -import { debounce } from 'radash'; +import { debounce } from '/imports/utils/debounce'; import BBBMenu from '/imports/ui/components/common/menu/component'; import { isVirtualBackgroundsEnabled } from '/imports/ui/services/features'; import Button from '/imports/ui/components/common/button/component'; @@ -99,7 +99,7 @@ const JoinVideoButton = ({ } }, [isVideoPreviewModalOpen]); - const handleOnClick = debounce({ delay: JOIN_VIDEO_DELAY_MILLISECONDS }, () => { + const handleOnClick = debounce(() => { switch (status) { case 'videoConnecting': VideoService.stopVideo(); @@ -113,7 +113,7 @@ const JoinVideoButton = ({ setVideoPreviewModalIsOpen(true); } } - }); + }, JOIN_VIDEO_DELAY_MILLISECONDS); const handleOpenAdvancedOptions = (callback) => { if (callback) callback(); From 80bb6752a79cfc5325dde963d14023bfd0f4b457 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Wed, 9 Aug 2023 13:55:43 -0400 Subject: [PATCH 165/252] Updates for project BigBlueButton v2.7 HTML5 client and lanuage eu on branch v2.7.x-release (#18501) * Translate en.json in eu 100% translated source file: 'en.json' on 'eu'. --------- Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- bigbluebutton-html5/public/locales/eu.json | 75 ++++++++++++++++++++-- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/bigbluebutton-html5/public/locales/eu.json b/bigbluebutton-html5/public/locales/eu.json index 0db51bf186..2404fe1ac0 100644 --- a/bigbluebutton-html5/public/locales/eu.json +++ b/bigbluebutton-html5/public/locales/eu.json @@ -7,6 +7,7 @@ "app.chat.locked": "Txata blokeatuta dago, mezuak ezin dira bidali", "app.chat.inputLabel": "Txatean {0} mezu sartu dira", "app.chat.emojiButtonLabel": "Emoji hautatzailea", + "app.chat.loadMoreButtonLabel": "Kargatu gehiago", "app.chat.inputPlaceholder": "Mezuak {0}", "app.chat.titlePublic": "Txat publikoa", "app.chat.titlePrivate": "Txat pribatua {0}-ekin", @@ -24,8 +25,11 @@ "app.chat.breakoutDurationUpdated": "Azpitaldeen denbora {0} minutukoa da orain", "app.chat.breakoutDurationUpdatedModerator": "Azpitaldeen gelen denbora {0} minutukoa da orain, eta jakinarazpen bat bidali da.", "app.chat.emptyLogLabel": "Txataren erregistroa hutsik dago", + "app.chat.away": "Kanpoan dago", + "app.chat.notAway": "Jada ez dago kanpoan", "app.chat.clearPublicChatMessage": "Moderatzaile batek txat publikoaren historia garbitu du", "app.chat.multi.typing": "Hainbat erabiltzaile idazten ari da", + "app.chat.someone.typing": "Norbait idazten ari da", "app.chat.one.typing": "{0} idazten ari da", "app.chat.two.typing": "{0} eta {1} idazten ari dira", "app.chat.copySuccess": "Kopiatutako txataren transkripzioa", @@ -36,6 +40,7 @@ "app.emojiPicker.clear": "Garbitu", "app.emojiPicker.categories.label": "Emoji kategoriak", "app.emojiPicker.categories.people": "Jendea eta gorputza", + "app.emojiPicker.categories.reactions": "Erreakzioak", "app.emojiPicker.categories.nature": "Animaliak eta natura", "app.emojiPicker.categories.foods": "Janaria eta edaria", "app.emojiPicker.categories.places": "Bidaiak eta tokiak", @@ -51,6 +56,23 @@ "app.emojiPicker.skintones.4": "Azaleko tonu tartekoa", "app.emojiPicker.skintones.5": "Azaleko tonu tartekoa-iluna", "app.emojiPicker.skintones.6": "Azaleko tonu iluna", + "app.timer.title": "Denbora", + "app.timer.stopwatch.title": "Kronometroa", + "app.timer.timer.title": "Tenporizadorea", + "app.timer.hideTimerLabel": "Ezkutatu denbora", + "app.timer.button.stopwatch": "Kronometroa", + "app.timer.button.timer": "Tenporizadorea", + "app.timer.button.start": "Hasi", + "app.timer.button.stop": "Gelditu", + "app.timer.button.reset": "Berrezarri", + "app.timer.hours": "ordu", + "app.timer.minutes": "minutu", + "app.timer.seconds": "segundo", + "app.timer.songs": "Soinuak", + "app.timer.noTrack": "Soinurik gabe", + "app.timer.track1": "Erlaxazioa", + "app.timer.track2": "Lasaitasuna", + "app.timer.track3": "Zoriontasuna", "app.captions.label": "Azpitituluak", "app.captions.menu.close": "Itxi", "app.captions.menu.start": "Hasi", @@ -102,6 +124,7 @@ "app.userList.messagesTitle": "Mezuak", "app.userList.notesTitle": "Oharrak", "app.userList.notesListItem.unreadContent": "Ohar partekatuetan eduki berria dago eskuragarri", + "app.userList.timerTitle": "Denbora", "app.userList.captionsTitle": "Azpitituluak", "app.userList.presenter": "Aurkezlea", "app.userList.you": "Zu", @@ -116,6 +139,8 @@ "app.userList.menuTitleContext": "Aukera erabilgarriak", "app.userList.chatListItem.unreadSingular": "Mezu berri bat", "app.userList.chatListItem.unreadPlural": "{0} mezu berri", + "app.userList.menu.away": "Adierazi kanpoan zaudela", + "app.userList.menu.notAway": "Adierazi aktibo zaudela", "app.userList.menu.chat.label": "Hasi txat pribatu bat", "app.userList.menu.clearStatus.label": "Garbitu egoera", "app.userList.menu.removeUser.label": "Kendu erabiltzailea", @@ -140,6 +165,8 @@ "app.userList.userOptions.muteAllDesc": "Isilarazi bilerako erabiltzaile guztiak", "app.userList.userOptions.clearAllLabel": "Garbitu egoera-ikono guztiak", "app.userList.userOptions.clearAllDesc": "Garbitu erabiltzaileen egoera-ikono guztiak", + "app.userList.userOptions.clearAllReactionsLabel": "Garbitu erreakzio guztiak", + "app.userList.userOptions.clearAllReactionsDesc": "Erabiltzaileen erreakzio-emotikono guztiak garbitzen ditu", "app.userList.userOptions.muteAllExceptPresenterLabel": "Isilarazi erabiltzaile guztiak aurkezlea izan ezik", "app.userList.userOptions.muteAllExceptPresenterDesc": "Isilarazi bilerako erabiltzaile guztiak aurkezlea izan ezik", "app.userList.userOptions.unmuteAllLabel": "Itzali bilera isila", @@ -156,6 +183,7 @@ "app.userList.userOptions.hideUserList": "Erabiltzaileen zerrenda ezkutuan dago ikusleentzat", "app.userList.userOptions.webcamsOnlyForModerator": "Moderatzaileek soilik ikus ditzakete ikusleen web-kamerak (blokeo ezarpenak direla medio)", "app.userList.content.participants.options.clearedStatus": "Garbitu erabiltzaileen egoera guztiak", + "app.userList.content.participants.options.clearedReactions": "Erabiltzaileen erreakzio guztiak garbitu dira", "app.userList.userOptions.enableCam": "Ikusleen web-kamerak gaituta daude", "app.userList.userOptions.enableMic": "Ikusleen mikrofonoak gaituta daude", "app.userList.userOptions.enablePrivChat": "Txat pribatua gaituta dago", @@ -177,6 +205,11 @@ "app.media.screenshare.notSupported": "Nabigatzaile honek ez du pantaila-partekatzea onartzen.", "app.media.screenshare.autoplayBlockedDesc": "Zure baimena behar dugu aurkezlearen pantaila zuri erakusteko.", "app.media.screenshare.autoplayAllowLabel": "Ikusi partekatutako pantaila", + "app.media.cameraAsContent.start": "Uneko kamera hasi da", + "app.media.cameraAsContent.end": "uneko kamera gelditu da", + "app.media.cameraAsContent.endDueToDataSaving": "Oraingo kamera gelditu da datuak aurrezteko", + "app.media.cameraAsContent.autoplayBlockedDesc": "Zure baimena behar dugu aurkezlearen kamera erakusteko.", + "app.media.cameraAsContent.autoplayAllowLabel": "Ikusi uneko kamera", "app.screenshare.presenterLoadingLabel": "Zure pantaila partekatzea kargatzen ari da", "app.screenshare.viewerLoadingLabel": "Aurkezlearen pantaila kargatzen ari da", "app.screenshare.presenterSharingLabel": "Une honetan zure pantaila partekatzen ari zara", @@ -185,6 +218,9 @@ "app.screenshare.screenshareRetryOtherEnvError": "{0} kodea. Ezin izan da pantaila partekatu. Saiatu berriro beste nabigatzaile bat edo beste gailu bat erabiliz.", "app.screenshare.screenshareUnsupportedEnv": "{0} kodea. Nabigatzaile hau ez da onartzen. Saiatu berriro beste nabigatzaile batez.", "app.screenshare.screensharePermissionError": "{0} kodea. Pantaila argazkia ateratzeko baimena behar da.", + "app.cameraAsContent.presenterLoadingLabel": "Zure kamera kargatzen ari da", + "app.cameraAsContent.viewerLoadingLabel": "Aurkezlearen kamera kargatzen ari da", + "app.cameraAsContent.presenterSharingLabel": "Une honetan zure kamera aurkezten ari zara", "app.meeting.ended": "Saio hau bukatu da.", "app.meeting.meetingTimeRemaining": "Bilerari geratzen zaion denbora: {0}", "app.meeting.meetingTimeHasEnded": "Denbora agortu da. Bilera laster itxiko da.", @@ -199,6 +235,7 @@ "app.presentation.hide": "Ezkutatu aurkezpena", "app.presentation.notificationLabel": "Uneko aurkezpena", "app.presentation.downloadLabel": "Deskargatu", + "app.presentation.actionsLabel": "Ekintzak", "app.presentation.slideContent": "Diapositiba aurkezpena", "app.presentation.startSlideContent": "Diapositiba aurkezpenaren hasiera", "app.presentation.endSlideContent": "Diapositiba aurkezpenaren bukaera", @@ -250,8 +287,15 @@ "app.presentationUploader.sent": "Bidalita", "app.presentationUploader.exportingTimeout": "Esportazioa gehiegi luzatzen ari da...", "app.presentationUploader.export": "Bidali txatera", + "app.presentationUploader.exportCurrentStatePresentation": "Bidali aurkezpena deskargatzeko esteka bere uneko egoeran", + "app.presentationUploader.enableOriginalPresentationDownload": "Gaitu jatorrizko aurkezpena deskargatzea", + "app.presentationUploader.disableOriginalPresentationDownload": "Desgaitu jatorrizko aurkezpenaren deskarga", + "app.presentationUploader.dropdownExportOptions": "Esportatu aukerak", "app.presentationUploader.export.linkAvailable": "Txat publikoan eskuragarri dagoen {0} deskargatzeko esteka.", + "app.presentationUploader.export.downloadButtonAvailable": "{0} aurkezpenerako deskargatzeko botoia eskuragarri dago.", "app.presentationUploader.export.notAccessibleWarning": "baliteke irisgarria ez izatea", + "app.presentationUploader.export.originalLabel": "Jatorrizkoa", + "app.presentationUploader.export.inCurrentStateLabel": "Une honetako egoeran", "app.presentationUploader.currentPresentationLabel": "Uneko aurkezpena", "app.presentationUploder.extraHint": "GARRANTZITSUA: fitxategi bakoitzak ezin ditu {0} MB eta {1} orrialde baino gehiago izan.", "app.presentationUploder.uploadLabel": "Kargatu", @@ -446,7 +490,10 @@ "app.actionsBar.actionsDropdown.minimizePresentationLabel": "Minimizatu aurkezpena", "app.actionsBar.actionsDropdown.minimizePresentationDesc": "Aurkezpena minimizatzeko botoia", "app.actionsBar.actionsDropdown.layoutModal": "Diseinuko ezarpen modua", + "app.actionsBar.actionsDropdown.shareCameraAsContent": "Partekatu kamera eduki gisa", + "app.actionsBar.actionsDropdown.unshareCameraAsContent": "Gelditu kamera eduki gisa", "app.screenshare.screenShareLabel" : "Pantaila partekatzea", + "app.cameraAsContent.cameraAsContentLabel" : "Uneko kamera", "app.submenu.application.applicationSectionTitle": "Aplikazioa", "app.submenu.application.animationsLabel": "Animazioak", "app.submenu.application.audioFilterLabel": "Mikrofonorako audio iragazkiak", @@ -460,6 +507,7 @@ "app.submenu.application.languageOptionLabel": "Aukeratu hizkuntza", "app.submenu.application.noLocaleOptionLabel": "Ez dago lokal aktiborik", "app.submenu.application.paginationEnabledLabel": "Bideo orrialdeak", + "app.submenu.application.wakeLockEnabledLabel": "Pantaila beti aktiboa", "app.submenu.application.layoutOptionLabel": "Deseinu mota", "app.submenu.application.pushLayoutLabel": "Zabaldu diseinua", "app.submenu.application.localeDropdown.af": "Afrikarra", @@ -569,6 +617,8 @@ "app.talkingIndicator.moreThanMaxIndicatorsWereTalking" : "{0}+ hitz egiten ari ziren", "app.talkingIndicator.wasTalking" : "{0}-k hitz egiteari utzi dio", "app.actionsBar.actionsDropdown.actionsLabel": "Ekintzak", + "app.actionsBar.actionsDropdown.activateTimerStopwatchLabel": "Aktibatu tenporizadorea/kronometroa", + "app.actionsBar.actionsDropdown.deactivateTimerStopwatchLabel": "Desaktibatu tenporizadorea/kronometroa", "app.actionsBar.actionsDropdown.presentationLabel": "Kargatu/Kudeatu aurkezpenak", "app.actionsBar.actionsDropdown.initPollLabel": "Hasi inkesta bat", "app.actionsBar.actionsDropdown.desktopShareLabel": "Partekatu zure pantaila", @@ -588,7 +638,9 @@ "app.actionsBar.actionsDropdown.takePresenterDesc": "Zure burua aurkezle berria izendatu", "app.actionsBar.actionsDropdown.selectRandUserLabel": "Hautatu erabiltzaile bat ausaz", "app.actionsBar.actionsDropdown.selectRandUserDesc": "Aukeratzen du ausaz erabiltzaile bat ikusleen artean", - "app.actionsBar.actionsDropdown.propagateLayoutLabel": "Hedatu diseinua", + "app.actionsBar.reactions.reactionsButtonLabel": "Erreakzioen barra", + "app.actionsBar.reactions.raiseHand": "Altxatu eskua", + "app.actionsBar.reactions.lowHand": "Jaitsi eskua", "app.actionsBar.emojiMenu.statusTriggerLabel": "Ezarri egoera", "app.actionsBar.emojiMenu.awayLabel": "Kanpoan", "app.actionsBar.emojiMenu.awayDesc": "Aldatu zure egoera: kanpoan", @@ -817,8 +869,15 @@ "app.toast.meetingMuteOn.label": "Erabiltzaile guztiak isilarazi dira", "app.toast.meetingMuteOnViewers.label": "Ikusle guztiak isilarazi dira", "app.toast.meetingMuteOff.label": "Bileraren isilaraztea itzali da", + "app.toast.wakeLock.offerTitle": "Bileran zehar zure gailuaren pantaila aktibo mantendu nahi duzu?", + "app.toast.wakeLock.offerAccept": "Bai!", + "app.toast.wakeLock.offerDecline": "Orain ez", + "app.toast.wakeLock.acquireSuccess": "Pantaila beti aktiboa martxan! Ezarpenen menuan desaktibatu dezakezu.", + "app.toast.wakeLock.acquireFailed": "Errore bat gertatu da pantaila beti aktiboa eskuratzean.", "app.toast.setEmoji.raiseHand": "Eskua altxatu duzu", "app.toast.setEmoji.lowerHand": "Zure eskua jaitsi dute", + "app.toast.setEmoji.away": "Adierazi duzu kanpoan zaudela", + "app.toast.setEmoji.notAway": "Kanpoan zaudela adierazpena kendu duzu", "app.toast.promotedLabel": "Moderatzaile rola eman dizute", "app.toast.demotedLabel": "Ikusle rolera jaitsi zaituzte", "app.notification.recordingStart": "Saio hau grabatzen ari da", @@ -901,6 +960,7 @@ "app.lock-viewers.button.cancel": "Utzi", "app.lock-viewers.locked": "Blokeatua", "app.lock-viewers.hideViewersCursor": "Ikusi beste ikusleen kurtsoreak", + "app.lock-viewers.hideAnnotationsLabel": "Ikusi beste ikusleen oharrak", "app.guest-policy.ariaTitle": "Gonbidatuen gidalerroen ezarpen modala", "app.guest-policy.title": "Gonbidapen gidalerroak", "app.guest-policy.description": "Aldatu bileraren gonbidapen gidalerroen ezarpenak", @@ -908,6 +968,7 @@ "app.guest-policy.button.alwaysAccept": "Onartu beti", "app.guest-policy.button.alwaysDeny": "Ukatu beti", "app.guest-policy.policyBtnDesc": "Bileraren gonbidapen politika ezartzen du", + "app.guest-policy.feedbackMessage": "Gonbidatuen politika orain hau da:", "app.connection-status.ariaTitle": "Konexioaren egoeraren modala", "app.connection-status.title": "Konexioaren egoera", "app.connection-status.description": "Ikusi erabiltzaileen konexio egoera", @@ -965,6 +1026,7 @@ "app.videoPreview.webcamPreviewLabel": "Web-kameraren aurrebista", "app.videoPreview.webcamSettingsTitle": "Web-kameraren ezarpenak", "app.videoPreview.webcamEffectsTitle": "Web-kamera efektu bisualak", + "app.videoPreview.cameraAsContentSettingsTitle": "Uneko kamera", "app.videoPreview.webcamVirtualBackgroundLabel": "Atzeko plano birtualaren ezarpenak", "app.videoPreview.webcamVirtualBackgroundDisabledLabel": "Zerbitzu honek ez du onartzen atzeko plano birtualik", "app.videoPreview.webcamNotFoundLabel": "Ez da web-kamerarik aurkitu", @@ -1093,6 +1155,10 @@ "app.videoDock.webcamFocusDesc": "Fokuratu hautatutako web-kamera", "app.videoDock.webcamUnfocusLabel": "Ez fokuratu", "app.videoDock.webcamUnfocusDesc": "Ez fokuratu hautatutako web-kamera", + "app.videoDock.webcamDisableLabel": "Desgaitu norberaren ikuspegia", + "app.videoDock.webcamDisableLabelAllCams": "Desgaitu norberaren ikuspegia (kamera guztiak)", + "app.videoDock.webcamEnableLabel": "Gaitu norberaren ikuspegia", + "app.videoDock.webcamDisableDesc": "Norberaren ikuspegia desaktibatu da", "app.videoDock.webcamPinLabel": "Ainguratu", "app.videoDock.webcamPinDesc": "Ainguratu hautatutako webkamera", "app.videoDock.webcamFullscreenLabel": "Pantaila osoko web-kamera", @@ -1133,8 +1199,10 @@ "app.createBreakoutRoom.addRoomTime": "Zabaldu azpitaldeen gelen denbora hona:", "app.createBreakoutRoom.addParticipantLabel": "+ Gehitu partaidea", "app.createBreakoutRoom.freeJoin": "Baimendu erabiltzaileei zein azpitalderen gelan sartu aukeratzea ", + "app.createBreakoutRoom.manageRoomsLabel": "Kudeatu gelak", "app.createBreakoutRoom.captureNotes": "Hartu partekatutako oharrak atsedenaldi-gelak amaitzen direnean", "app.createBreakoutRoom.captureSlides": "Hartu arbela azpitaldeen gelak amaitzen direnean", + "app.createBreakoutRoom.sendInvitationToMods": "Bidali gonbidapena esleitutako moderatzaileei", "app.createBreakoutRoom.leastOneWarnBreakout": "Azpitalde gela bakoitzean gutxienez erabiltzaile bana kokatu behar duzu.", "app.createBreakoutRoom.minimumDurationWarnBreakout": "Azpitalde baten gelaren iraupena gutxienez {0} minutukoa izan behar du.", "app.createBreakoutRoom.modalDesc": "Aholkua: erabiltzaile baten izena arrastatu eta jaregin dezakezu azpitalde zehatz baten gela esleitzeko.", @@ -1180,10 +1248,9 @@ "app.debugWindow.form.chatLoggerLabel": "Probatu txataren erregistro mailak", "app.debugWindow.form.button.apply": "Aplikatu", "app.layout.modal.title": "Diseinuak", - "app.layout.modal.confirm": "Berretsi", - "app.layout.modal.cancel": "Utzi", + "app.layout.modal.update": "Eguneratu", + "app.layout.modal.updateAll": "Eguneratu denak", "app.layout.modal.layoutLabel": "Hautatu zure diseinua", - "app.layout.modal.keepPushingLayoutLabel": "Zabaldu diseinua guztiei", "app.layout.modal.pushLayoutLabel": "Zabaldu denei", "app.layout.modal.layoutToastLabel": "Diseinu-ezarpenak aldatu dira", "app.layout.modal.layoutSingular": "Diseinua", From eacdfa79c5dd8c5faaa74e73456e47e86cd9a2fd Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Wed, 9 Aug 2023 14:00:40 -0400 Subject: [PATCH 166/252] Updates for project BigBlueButton v2.7 HTML5 client and lanuage zh_TW on branch v2.7.x-release (#18499) * Translate en.json in zh_TW 100% translated source file: 'en.json' on 'zh_TW'. --------- Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- bigbluebutton-html5/public/locales/zh_TW.json | 1415 +++++++++-------- 1 file changed, 741 insertions(+), 674 deletions(-) diff --git a/bigbluebutton-html5/public/locales/zh_TW.json b/bigbluebutton-html5/public/locales/zh_TW.json index 459ef9deca..5703329b21 100644 --- a/bigbluebutton-html5/public/locales/zh_TW.json +++ b/bigbluebutton-html5/public/locales/zh_TW.json @@ -1,19 +1,20 @@ { "app.home.greeting": "您的演示即將開始...", "app.chat.submitLabel": "發送訊息", - "app.chat.loading": "聊天訊息已載入:{0}%", - "app.chat.errorMaxMessageLength": "訊息太長,超過了最大長度限制 {0} 個字元", + "app.chat.loading": "聊天訊息載入中:{0}%", + "app.chat.errorMaxMessageLength": "訊息太長,超過最大長度{0}個字元", "app.chat.disconnected": "您已斷線,無法發送訊息", "app.chat.locked": "聊天已鎖定,無法發送訊息", "app.chat.inputLabel": "聊天 {0} 的訊息輸入", "app.chat.emojiButtonLabel": "表情符號選擇器", + "app.chat.loadMoreButtonLabel": "載入更多", "app.chat.inputPlaceholder": "{0} 的訊息", - "app.chat.titlePublic": "公共聊天", + "app.chat.titlePublic": "公開聊天", "app.chat.titlePrivate": "與 {0} 的私人聊天", "app.chat.partnerDisconnected": "{0} 已離開會議", "app.chat.closeChatLabel": "關閉 {0}", "app.chat.hideChatLabel": "隱藏 {0}", - "app.chat.moreMessages": "以下還有更多訊息", + "app.chat.moreMessages": "下方還有更多訊息", "app.chat.dropdown.options": "聊天選項", "app.chat.dropdown.clear": "清除", "app.chat.dropdown.copy": "複製", @@ -22,23 +23,27 @@ "app.chat.offline": "離線", "app.chat.pollResult": "投票結果", "app.chat.breakoutDurationUpdated": "分組討論時間已設定為 {0} 分鐘", - "app.chat.breakoutDurationUpdatedModerator": "分組討論室時間已設定為 {0} 分鐘,並已發送通知。", + "app.chat.breakoutDurationUpdatedModerator": "分組討論時間已設定為 {0} 分鐘,並已發送通知。", "app.chat.emptyLogLabel": "聊天記錄為空", - "app.chat.clearPublicChatMessage": "公共聊天歷史紀錄已被管理員清除", - "app.chat.multi.typing": "多個使用者正在輸入", + "app.chat.away": "暫離", + "app.chat.notAway": "不再暫離", + "app.chat.clearPublicChatMessage": "主持人已清除公開聊天歷史記錄", + "app.chat.multi.typing": "多名使用者正在輸入", + "app.chat.someone.typing": "有人正在輸入", "app.chat.one.typing": "{0} 正在輸入", "app.chat.two.typing": "{0} 和 {1} 正在輸入", - "app.chat.copySuccess": "已複製聊天記錄", + "app.chat.copySuccess": "複製聊天記錄成功", "app.chat.copyErr": "複製聊天記錄失敗", "app.emojiPicker.search": "搜尋", - "app.emojiPicker.notFound": "找不到表情符號", + "app.emojiPicker.notFound": "未找到表情符號", "app.emojiPicker.skintext": "選擇您的預設膚色", "app.emojiPicker.clear": "清除", "app.emojiPicker.categories.label": "表情符號分類", - "app.emojiPicker.categories.people": "人物與身體", - "app.emojiPicker.categories.nature": "動物與自然", - "app.emojiPicker.categories.foods": "食物與飲料", - "app.emojiPicker.categories.places": "旅遊與地點", + "app.emojiPicker.categories.people": "人物和身體", + "app.emojiPicker.categories.reactions": "反應", + "app.emojiPicker.categories.nature": "動物和自然", + "app.emojiPicker.categories.foods": "食物和飲料", + "app.emojiPicker.categories.places": "旅遊和地點", "app.emojiPicker.categories.activity": "活動", "app.emojiPicker.categories.objects": "物品", "app.emojiPicker.categories.symbols": "符號", @@ -46,19 +51,36 @@ "app.emojiPicker.categories.recent": "最近使用", "app.emojiPicker.categories.search": "搜尋結果", "app.emojiPicker.skintones.1": "預設膚色", - "app.emojiPicker.skintones.2": "淺色膚色", - "app.emojiPicker.skintones.3": "中淺色膚色", - "app.emojiPicker.skintones.4": "中等膚色", - "app.emojiPicker.skintones.5": "中暗膚色", - "app.emojiPicker.skintones.6": "深色膚色", + "app.emojiPicker.skintones.2": "淺膚色", + "app.emojiPicker.skintones.3": "中淺膚色", + "app.emojiPicker.skintones.4": "中膚色", + "app.emojiPicker.skintones.5": "中深膚色", + "app.emojiPicker.skintones.6": "深膚色", + "app.timer.title": "時間", + "app.timer.stopwatch.title": "碼錶", + "app.timer.timer.title": "倒數計時器", + "app.timer.hideTimerLabel": "隱藏時間", + "app.timer.button.stopwatch": "碼錶", + "app.timer.button.timer": "倒數計時器", + "app.timer.button.start": "開始", + "app.timer.button.stop": "停止", + "app.timer.button.reset": "重設", + "app.timer.hours": "小時", + "app.timer.minutes": "分鐘", + "app.timer.seconds": "秒", + "app.timer.songs": "歌曲", + "app.timer.noTrack": "無歌曲", + "app.timer.track1": "輕鬆", + "app.timer.track2": "平靜", + "app.timer.track3": "快樂", "app.captions.label": "字幕", "app.captions.menu.close": "關閉", "app.captions.menu.start": "開始", "app.captions.menu.ariaStart": "開始編寫字幕", - "app.captions.menu.ariaStartDesc": "打開字幕編輯器並關閉彈出窗口", - "app.captions.menu.select": "選擇可用的語言", + "app.captions.menu.ariaStartDesc": "打開字幕編輯器並關閉彈出視窗", + "app.captions.menu.select": "選擇可用語言", "app.captions.menu.ariaSelect": "字幕語言", - "app.captions.menu.subtitle": "請在會議中選擇字幕的語言和樣式。", + "app.captions.menu.subtitle": "請選擇會議中的語言和閉路字幕的樣式。", "app.captions.menu.title": "閉路字幕", "app.captions.menu.fontSize": "字型大小", "app.captions.menu.fontColor": "文字顏色", @@ -68,192 +90,214 @@ "app.captions.menu.cancelLabel": "取消", "app.captions.hide": "隱藏閉路字幕", "app.captions.ownership": "接管", - "app.captions.ownershipTooltip": "您將被指定為 {0} 字幕的擁有者", - "app.captions.dictationStart": "開始聽寫", - "app.captions.dictationStop": "停止聽寫", + "app.captions.ownershipTooltip": "您將被指派為 {0} 的字幕擁有者", + "app.captions.dictationStart": "開始口述", + "app.captions.dictationStop": "停止口述", "app.captions.dictationOnDesc": "開啟語音識別", "app.captions.dictationOffDesc": "關閉語音識別", - "app.captions.speech.start": "開始語音識別", - "app.captions.speech.stop": "停止語音識別", - "app.captions.speech.error": "由於瀏覽器不兼容或靜音時間過長,語音識別已停止", + "app.captions.speech.start": "已開始語音識別", + "app.captions.speech.stop": "已停止語音識別", + "app.captions.speech.error": "語音識別已停止,可能是瀏覽器不兼容或因靜默一段時間", "app.confirmation.skipConfirm": "不再詢問", - "app.confirmation.virtualBackground.title": "開始新的虛擬背景", - "app.confirmation.virtualBackground.description": "將 {0} 添加為虛擬背景。繼續嗎?", + "app.confirmation.virtualBackground.title": "開始新虛擬背景", + "app.confirmation.virtualBackground.description": "{0} 將被添加為虛擬背景。是否繼續?", "app.confirmationModal.yesLabel": "是", "app.textInput.sendLabel": "發送", - "app.title.defaultViewLabel": "預設簡報視圖", + "app.title.defaultViewLabel": "預設演示視圖", "app.notes.title": "共享筆記", "app.notes.titlePinned": "共享筆記(已釘選)", - "app.notes.pinnedNotification": "共享筆記已釘選在白板上。", + "app.notes.pinnedNotification": "共享筆記已固定在白板上。", "app.notes.label": "筆記", "app.notes.hide": "隱藏筆記", "app.notes.locked": "已鎖定", - "app.notes.disabled": "釘在媒體區域上", - "app.notes.notesDropdown.covertAndUpload": "將筆記轉換為簡報", - "app.notes.notesDropdown.pinNotes": "將筆記釘在白板上", - "app.notes.notesDropdown.unpinNotes": "取消釘選筆記", + "app.notes.disabled": "已固定在媒體區域", + "app.notes.notesDropdown.covertAndUpload": "將筆記轉換為演示", + "app.notes.notesDropdown.pinNotes": "將筆記固定在白板上", + "app.notes.notesDropdown.unpinNotes": "取消固定筆記", "app.notes.notesDropdown.notesOptions": "筆記選項", - "app.pads.hint": "按Esc鍵聚焦到工具欄", - "app.user.activityCheck": "用戶活動檢查", - "app.user.activityCheck.label": "檢查用戶是否仍在會議中({0})", + "app.pads.hint": "按 Esc 鍵以聚焦筆記工具欄", + "app.user.activityCheck": "使用者活動檢查", + "app.user.activityCheck.label": "檢查使用者是否仍在會議中({0})", "app.user.activityCheck.check": "檢查", "app.userList.usersTitle": "使用者", "app.userList.participantsTitle": "參與者", "app.userList.messagesTitle": "訊息", "app.userList.notesTitle": "筆記", - "app.userList.notesListItem.unreadContent": "共享筆記區域有新內容可用", + "app.userList.notesListItem.unreadContent": "共享筆記區域有新內容可供閱讀", + "app.userList.timerTitle": "時間", "app.userList.captionsTitle": "字幕", - "app.userList.presenter": "主持人", - "app.userList.you": "你", + "app.userList.presenter": "演示者", + "app.userList.you": "您", "app.userList.locked": "已鎖定", "app.userList.byModerator": "由(主持人)", - "app.userList.label": "使用者清單", - "app.userList.toggleCompactView.label": "切換簡潔檢視模式", + "app.userList.label": "使用者列表", + "app.userList.toggleCompactView.label": "切換緊湊檢視模式", "app.userList.moderator": "主持人", - "app.userList.mobile": "手機", + "app.userList.mobile": "行動裝置", "app.userList.guest": "訪客", - "app.userList.sharingWebcam": "攝像頭", + "app.userList.sharingWebcam": "視訊鏡頭", "app.userList.menuTitleContext": "可用選項", - "app.userList.chatListItem.unreadSingular": "有一條新訊息", - "app.userList.chatListItem.unreadPlural": "有 {0} 條新訊息", + "app.userList.chatListItem.unreadSingular": "一則新訊息", + "app.userList.chatListItem.unreadPlural": "{0} 則新訊息", + "app.userList.menu.away": "設定為暫離", + "app.userList.menu.notAway": "設定為活動", "app.userList.menu.chat.label": "開始私人聊天", "app.userList.menu.clearStatus.label": "清除狀態", "app.userList.menu.removeUser.label": "移除使用者", "app.userList.menu.removeConfirmation.label": "移除使用者({0})", - "app.userlist.menu.removeConfirmation.desc": "防止該使用者重新加入會議。", + "app.userlist.menu.removeConfirmation.desc": "防止此使用者重新加入會議。", "app.userList.menu.muteUserAudio.label": "靜音使用者", - "app.userList.menu.unmuteUserAudio.label": "取消靜音使用者", - "app.userList.menu.webcamPin.label": "釘選使用者的攝像頭", - "app.userList.menu.webcamUnpin.label": "取消釘選使用者的攝像頭", - "app.userList.menu.giveWhiteboardAccess.label" : "授予白板訪問權限", - "app.userList.menu.removeWhiteboardAccess.label": "移除白板訪問權限", - "app.userList.menu.ejectUserCameras.label": "關閉攝像頭", - "app.userList.userAriaLabel": "{0} {1} {2} 狀態 {3}", - "app.userList.menu.promoteUser.label": "提升為主持人", - "app.userList.menu.demoteUser.label": "降級為觀眾", - "app.userList.menu.unlockUser.label": "解鎖 {0}", + "app.userList.menu.unmuteUserAudio.label": "解除使用者的靜音", + "app.userList.menu.webcamPin.label": "固定使用者的視訊鏡頭", + "app.userList.menu.webcamUnpin.label": "取消固定使用者的視訊鏡頭", + "app.userList.menu.giveWhiteboardAccess.label" : "授予白板存取權", + "app.userList.menu.removeWhiteboardAccess.label": "移除白板存取權", + "app.userList.menu.ejectUserCameras.label": "關閉視訊鏡頭", + "app.userList.userAriaLabel": "{0} {1} {2} 狀態 {3}", + "app.userList.menu.promoteUser.label": "晉升為主持人", + "app.userList.menu.demoteUser.label": "降為觀眾", + "app.userList.menu.unlockUser.label": "解除 {0} 的鎖定", "app.userList.menu.lockUser.label": "鎖定 {0}", - "app.userList.menu.directoryLookup.label": "目錄查找", + "app.userList.menu.directoryLookup.label": "目錄查閱", "app.userList.menu.makePresenter.label": "設為主持人", "app.userList.userOptions.manageUsersLabel": "管理使用者", - "app.userList.userOptions.muteAllLabel": "對所有使用者進行靜音", - "app.userList.userOptions.muteAllDesc": "對會議中的所有使用者進行靜音", + "app.userList.userOptions.muteAllLabel": "將所有使用者靜音", + "app.userList.userOptions.muteAllDesc": "將會議中的所有使用者靜音", "app.userList.userOptions.clearAllLabel": "清除所有狀態圖示", - "app.userList.userOptions.clearAllDesc": "從使用者中清除所有狀態圖示", - "app.userList.userOptions.muteAllExceptPresenterLabel": "對除主持人外的所有使用者進行靜音", - "app.userList.userOptions.muteAllExceptPresenterDesc": "對會議中除主持人外的所有使用者進行靜音", - "app.userList.userOptions.unmuteAllLabel": "關閉會議靜音", - "app.userList.userOptions.unmuteAllDesc": "取消會議靜音", + "app.userList.userOptions.clearAllDesc": "從使用者清除所有狀態圖示", + "app.userList.userOptions.clearAllReactionsLabel": "清除所有反應", + "app.userList.userOptions.clearAllReactionsDesc": "從使用者清除所有反應表情符號", + "app.userList.userOptions.muteAllExceptPresenterLabel": "將所有使用者靜音,除了主持人", + "app.userList.userOptions.muteAllExceptPresenterDesc": "將會議中的所有使用者靜音,除了主持人", + "app.userList.userOptions.unmuteAllLabel": "解除會議靜音", + "app.userList.userOptions.unmuteAllDesc": "取消會議的靜音", "app.userList.userOptions.lockViewersLabel": "鎖定觀眾", - "app.userList.userOptions.lockViewersDesc": "鎖定會議參與者的某些功能", - "app.userList.userOptions.guestPolicyLabel": "訪客策略", - "app.userList.userOptions.guestPolicyDesc": "更改會議的訪客策略設定", - "app.userList.userOptions.disableCam": "觀眾的攝像頭已停用", + "app.userList.userOptions.lockViewersDesc": "為會議的參與者鎖定某些功能", + "app.userList.userOptions.guestPolicyLabel": "訪客政策", + "app.userList.userOptions.guestPolicyDesc": "更改會議的訪客政策設定", + "app.userList.userOptions.disableCam": "觀眾的視訊鏡頭已停用", "app.userList.userOptions.disableMic": "觀眾的麥克風已停用", "app.userList.userOptions.disablePrivChat": "私人聊天已停用", - "app.userList.userOptions.disablePubChat": "公共聊天已停用", - "app.userList.userOptions.disableNotes": "共享筆記現已鎖定", - "app.userList.userOptions.hideUserList": "觀眾現在看不到使用者清單", - "app.userList.userOptions.webcamsOnlyForModerator": "僅主持人可以看到觀眾的攝像頭(因鎖定設定)", - "app.userList.content.participants.options.clearedStatus": "已清除所有使用者的狀態", - "app.userList.userOptions.enableCam": "觀眾的攝像頭已啟用", + "app.userList.userOptions.disablePubChat": "公開聊天已停用", + "app.userList.userOptions.disableNotes": "共享筆記已鎖定", + "app.userList.userOptions.hideUserList": "對觀眾隱藏使用者列表", + "app.userList.userOptions.webcamsOnlyForModerator": "只有主持人能看到觀眾的視訊鏡頭(因鎖定設定)", + "app.userList.content.participants.options.clearedStatus": "已清除所有使用者狀態", + "app.userList.content.participants.options.clearedReactions": "已清除所有使用者反應", + "app.userList.userOptions.enableCam": "觀眾的視訊鏡頭已啟用", "app.userList.userOptions.enableMic": "觀眾的麥克風已啟用", "app.userList.userOptions.enablePrivChat": "私人聊天已啟用", - "app.userList.userOptions.enablePubChat": "公共聊天已啟用", - "app.userList.userOptions.enableNotes": "共享筆記現已啟用", - "app.userList.userOptions.showUserList": "觀眾現在可以看到使用者清單", - "app.userList.userOptions.enableOnlyModeratorWebcam": "您現在可以啟用您的攝像頭,所有人都會看到您", - "app.userList.userOptions.savedNames.title": "會議 {0} 於 {1} 的使用者清單", + "app.userList.userOptions.enablePubChat": "公開聊天已啟用", + "app.userList.userOptions.enableNotes": "共享筆記已啟用", + "app.userList.userOptions.showUserList": "對觀眾顯示使用者列表", + "app.userList.userOptions.enableOnlyModeratorWebcam": "您可以啟用您的視訊鏡頭,所有人都將看到您", + "app.userList.userOptions.savedNames.title": "在會議 {0} 於 {1} 的使用者列表", "app.userList.userOptions.sortedFirstName.heading": "按名字排序:", "app.userList.userOptions.sortedLastName.heading": "按姓氏排序:", "app.userList.userOptions.hideViewersCursor": "觀眾的游標已鎖定", "app.userList.userOptions.showViewersCursor": "觀眾的游標已解鎖", "app.media.label": "媒體", "app.media.autoplayAlertDesc": "允許存取", - "app.media.screenshare.start": "已開始屏幕分享", + "app.media.screenshare.start": "屏幕分享已開始", "app.media.screenshare.end": "屏幕分享已結束", - "app.media.screenshare.endDueToDataSaving": "由於數據節省,屏幕分享已停止", - "app.media.screenshare.unavailable": "無法使用屏幕分享", + "app.media.screenshare.endDueToDataSaving": "因數據節省而停止屏幕分享", + "app.media.screenshare.unavailable": "屏幕分享不可用", "app.media.screenshare.notSupported": "此瀏覽器不支援屏幕分享。", "app.media.screenshare.autoplayBlockedDesc": "我們需要您的許可來顯示主持人的螢幕。", - "app.media.screenshare.autoplayAllowLabel": "查看共享螢幕", - "app.screenshare.presenterLoadingLabel": "正在載入您的屏幕分享", - "app.screenshare.viewerLoadingLabel": "正在載入主持人的螢幕", - "app.screenshare.presenterSharingLabel": "您現在正在分享您的屏幕", + "app.media.screenshare.autoplayAllowLabel": "查看分享的螢幕", + "app.media.cameraAsContent.start": "展示攝影機已開始", + "app.media.cameraAsContent.end": "展示攝影機已結束", + "app.media.cameraAsContent.endDueToDataSaving": "因數據節省而停止展示攝影機", + "app.media.cameraAsContent.autoplayBlockedDesc": "我們需要您的許可來顯示主持人的攝影機。", + "app.media.cameraAsContent.autoplayAllowLabel": "查看展示攝影機", + "app.screenshare.presenterLoadingLabel": "您的屏幕分享正在載入中", + "app.screenshare.viewerLoadingLabel": "主持人的屏幕正在載入中", + "app.screenshare.presenterSharingLabel": "您正在分享您的屏幕", "app.screenshare.screenshareFinalError": "代碼 {0}。無法分享螢幕。", - "app.screenshare.screenshareRetryError": "代碼 {0}。請重試分享螢幕。", - "app.screenshare.screenshareRetryOtherEnvError": "代碼 {0}。無法分享螢幕。請嘗試使用其他瀏覽器或設備重試。", - "app.screenshare.screenshareUnsupportedEnv": "代碼 {0}。不支援該瀏覽器。請嘗試使用其他瀏覽器或設備。", - "app.screenshare.screensharePermissionError": "代碼 {0}。需要授權進行螢幕捕捉。", + "app.screenshare.screenshareRetryError": "代碼 {0}。請嘗試重新分享螢幕。", + "app.screenshare.screenshareRetryOtherEnvError": "代碼 {0}。無法分享螢幕。請嘗試使用其他瀏覽器或設備。", + "app.screenshare.screenshareUnsupportedEnv": "代碼 {0}。瀏覽器不受支援。請嘗試使用其他瀏覽器或設備。", + "app.screenshare.screensharePermissionError": "代碼 {0}。需要授予捕捉螢幕的許可權。", + "app.cameraAsContent.presenterLoadingLabel": "您的攝影機正在載入中", + "app.cameraAsContent.viewerLoadingLabel": "主持人的攝影機正在載入中", + "app.cameraAsContent.presenterSharingLabel": "您正在展示您的攝影機", "app.meeting.ended": "此會議已結束", "app.meeting.meetingTimeRemaining": "剩餘會議時間:{0}", - "app.meeting.meetingTimeHasEnded": "時間已結束。會議即將關閉", + "app.meeting.meetingTimeHasEnded": "時間已結束。會議將很快關閉", "app.meeting.endedByUserMessage": "此會議已由 {0} 結束", - "app.meeting.endedByNoModeratorMessageSingular": "由於一分鐘內沒有主持人在場,會議已結束", - "app.meeting.endedByNoModeratorMessagePlural": "由於 {0} 分鐘內沒有主持人在場,會議已結束", - "app.meeting.endedMessage": "您將被重新導向回主畫面", - "app.meeting.alertMeetingEndsUnderMinutesSingular": "會議將在一分鐘內關閉。", - "app.meeting.alertMeetingEndsUnderMinutesPlural": "會議將在 {0} 分鐘內關閉。", - "app.meeting.alertBreakoutEndsUnderMinutesPlural": "分組討論將在 {0} 分鐘內關閉。", - "app.meeting.alertBreakoutEndsUnderMinutesSingular": "分組討論將在一分鐘內關閉。", + "app.meeting.endedByNoModeratorMessageSingular": "會議已結束,因為一分鐘內未出現主持人", + "app.meeting.endedByNoModeratorMessagePlural": "會議已結束,因為 {0} 分鐘內未出現主持人", + "app.meeting.endedMessage": "按下按鈕以繼續到主畫面。", + "app.meeting.alertMeetingEndsUnderMinutesSingular": "會議將於一分鐘後關閉。", + "app.meeting.alertMeetingEndsUnderMinutesPlural": "會議將於 {0} 分鐘後關閉。", + "app.meeting.alertBreakoutEndsUnderMinutesPlural": "分組會議將於 {0} 分鐘後關閉。", + "app.meeting.alertBreakoutEndsUnderMinutesSingular": "分組會議將於一分鐘後關閉。", "app.presentation.hide": "隱藏簡報", - "app.presentation.notificationLabel": "當前簡報", + "app.presentation.notificationLabel": "目前的簡報", "app.presentation.downloadLabel": "下載", - "app.presentation.slideContent": "幻燈片內容", - "app.presentation.startSlideContent": "開始幻燈片內容", - "app.presentation.endSlideContent": "結束幻燈片內容", - "app.presentation.changedSlideContent": "簡報已更改至幻燈片:{0}", - "app.presentation.emptySlideContent": "當前幻燈片沒有內容", - "app.presentation.options.fullscreen": "全屏簡報", - "app.presentation.options.exitFullscreen": "退出全屏", + "app.presentation.actionsLabel": "操作", + "app.presentation.slideContent": "投影片內容", + "app.presentation.startSlideContent": "開始投影片內容", + "app.presentation.endSlideContent": "結束投影片內容", + "app.presentation.changedSlideContent": "簡報變更為投影片:{0}", + "app.presentation.emptySlideContent": "目前的投影片無內容", + "app.presentation.options.fullscreen": "全螢幕簡報", + "app.presentation.options.exitFullscreen": "退出全螢幕", "app.presentation.options.minimize": "最小化", - "app.presentation.options.snapshot": "當前幻燈片快照", - "app.presentation.options.downloading": "下載中...", - "app.presentation.options.downloaded": "已下載當前簡報", - "app.presentation.options.downloadFailed": "無法下載當前簡報", + "app.presentation.options.snapshot": "目前投影片的快照", + "app.presentation.options.downloading": "正在下載...", + "app.presentation.options.downloaded": "目前的簡報已下載", + "app.presentation.options.downloadFailed": "無法下載目前的簡報", "app.presentation.presentationToolbar.noNextSlideDesc": "簡報結束", "app.presentation.presentationToolbar.noPrevSlideDesc": "簡報開始", - "app.presentation.presentationToolbar.selectLabel": "選擇幻燈片", - "app.presentation.presentationToolbar.prevSlideLabel": "上一個幻燈片", - "app.presentation.presentationToolbar.prevSlideDesc": "切換至上一個幻燈片", - "app.presentation.presentationToolbar.nextSlideLabel": "下一個幻燈片", - "app.presentation.presentationToolbar.nextSlideDesc": "切換至下一個幻燈片", - "app.presentation.presentationToolbar.skipSlideLabel": "跳轉至幻燈片", - "app.presentation.presentationToolbar.skipSlideDesc": "跳轉至指定幻燈片", + "app.presentation.presentationToolbar.selectLabel": "選擇投影片", + "app.presentation.presentationToolbar.prevSlideLabel": "前一個投影片", + "app.presentation.presentationToolbar.prevSlideDesc": "將簡報變更為前一個投影片", + "app.presentation.presentationToolbar.nextSlideLabel": "下一個投影片", + "app.presentation.presentationToolbar.nextSlideDesc": "將簡報變更為下一個投影片", + "app.presentation.presentationToolbar.skipSlideLabel": "跳至投影片", + "app.presentation.presentationToolbar.skipSlideDesc": "將簡報變更為特定的投影片", "app.presentation.presentationToolbar.fitWidthLabel": "適應寬度", - "app.presentation.presentationToolbar.fitWidthDesc": "顯示整個幻燈片的寬度", - "app.presentation.presentationToolbar.fitScreenLabel": "適應屏幕", - "app.presentation.presentationToolbar.fitScreenDesc": "顯示整個幻燈片", + "app.presentation.presentationToolbar.fitWidthDesc": "顯示投影片的整個寬度", + "app.presentation.presentationToolbar.fitScreenLabel": "適應螢幕", + "app.presentation.presentationToolbar.fitScreenDesc": "顯示整個投影片", "app.presentation.presentationToolbar.zoomLabel": "縮放", - "app.presentation.presentationToolbar.zoomDesc": "更改簡報的縮放級別", + "app.presentation.presentationToolbar.zoomDesc": "變更簡報的縮放等級", "app.presentation.presentationToolbar.zoomInLabel": "放大", - "app.presentation.presentationToolbar.zoomInDesc": "放大簡報", + "app.presentation.presentationToolbar.zoomInDesc": "將簡報放大", "app.presentation.presentationToolbar.zoomOutLabel": "縮小", - "app.presentation.presentationToolbar.zoomOutDesc": "縮小簡報", - "app.presentation.presentationToolbar.zoomReset": "重置縮放", - "app.presentation.presentationToolbar.zoomIndicator": "當前縮放百分比", + "app.presentation.presentationToolbar.zoomOutDesc": "將簡報縮小", + "app.presentation.presentationToolbar.zoomReset": "重設縮放", + "app.presentation.presentationToolbar.zoomIndicator": "目前縮放百分比", "app.presentation.presentationToolbar.fitToWidth": "適應寬度", "app.presentation.presentationToolbar.fitToPage": "適應頁面", - "app.presentation.presentationToolbar.goToSlide": "幻燈片 {0}", - "app.presentation.presentationToolbar.hideToolsDesc": "隱藏工具欄", - "app.presentation.presentationToolbar.showToolsDesc": "顯示工具欄", + "app.presentation.presentationToolbar.goToSlide": "投影片 {0}", + "app.presentation.presentationToolbar.hideToolsDesc": "隱藏工具列", + "app.presentation.presentationToolbar.showToolsDesc": "顯示工具列", "app.presentation.placeholder": "目前沒有活動的簡報", "app.presentationUploder.title": "簡報", - "app.presentationUploder.message": "作為主持人,您可以上傳任何 Office 文件或 PDF 文件作為簡報。我們建議使用 PDF 文件以獲得最佳效果。請確保選擇左側的圓形複選框來選擇簡報。", - "app.presentationUploader.exportHint": "選擇「發送到聊天」將為用戶提供帶有公共聊天中註釋的可下載鏈接。", - "app.presentationUploader.exportToastHeader": "正在發送到聊天({0} 項)", - "app.presentationUploader.exportToastHeaderPlural": "正在發送到聊天({0} 項)", - "app.presentationUploader.exporting": "正在發送到聊天", - "app.presentationUploader.sending": "正在發送...", - "app.presentationUploader.collecting": "正在提取幻燈片 {0}/{1}...", - "app.presentationUploader.processing": "正在註釋幻燈片 {0}/{1}...", - "app.presentationUploader.sent": "已發送", + "app.presentationUploder.message": "作為主持人,您可以上傳任何 Office 文件或 PDF 檔案。我們建議使用 PDF 檔案以獲得最佳效果。請確保使用左側的圓形核取方塊選擇了一個簡報。", + "app.presentationUploader.exportHint": "在「匯出選項」選單中,您可以啟用下載原始簡報和在公開聊天中提供帶有註釋的可下載連結。", + "app.presentationUploader.exportToastHeader": "傳送到聊天中({0} 項)", + "app.presentationUploader.exportToastHeaderPlural": "傳送到聊天中({0} 項)", + "app.presentationUploader.exporting": "傳送至聊天中", + "app.presentationUploader.sending": "傳送中...", + "app.presentationUploader.collecting": "正在提取投影片 {0}/{1}...", + "app.presentationUploader.processing": "正在註釋投影片 {0}/{1}...", + "app.presentationUploader.sent": "已傳送", "app.presentationUploader.exportingTimeout": "匯出時間過長...", - "app.presentationUploader.export": "發送到聊天", - "app.presentationUploader.export.linkAvailable": "可在公共聊天中下載 {0} 的連結。", - "app.presentationUploader.export.notAccessibleWarning": "可能不符合無障礙要求", + "app.presentationUploader.export": "傳送至聊天中", + "app.presentationUploader.exportCurrentStatePresentation": "傳送目前簡報的下載連結", + "app.presentationUploader.enableOriginalPresentationDownload": "啟用下載原始簡報", + "app.presentationUploader.disableOriginalPresentationDownload": "停用下載原始簡報", + "app.presentationUploader.dropdownExportOptions": "匯出選項", + "app.presentationUploader.export.linkAvailable": "供公開聊天使用的 {0} 下載連結可用。", + "app.presentationUploader.export.downloadButtonAvailable": "可用的簡報 {0} 下載按鈕。", + "app.presentationUploader.export.notAccessibleWarning": "可能不符合可訪問性標準", + "app.presentationUploader.export.originalLabel": "原始", + "app.presentationUploader.export.inCurrentStateLabel": "目前狀態", "app.presentationUploader.currentPresentationLabel": "目前簡報", - "app.presentationUploder.extraHint": "重要提示:每個文件的大小不能超過 {0} MB,頁數不能超過 {1} 頁。", + "app.presentationUploder.extraHint": "重要提示:每個檔案不得超過 {0} MB 和 {1} 頁。", "app.presentationUploder.uploadLabel": "上傳", "app.presentationUploder.confirmLabel": "確認", "app.presentationUploder.confirmDesc": "保存更改並開始簡報", @@ -263,15 +307,15 @@ "app.presentationUploder.dropzoneImagesLabel": "將圖像拖到此處上傳", "app.presentationUploder.browseFilesLabel": "或瀏覽文件", "app.presentationUploder.browseImagesLabel": "或瀏覽/捕獲圖像", - "app.presentationUploder.externalUploadTitle": "從第3方應用添加內容", + "app.presentationUploder.externalUploadTitle": "從第3方應用程式添加內容", "app.presentationUploder.externalUploadLabel": "瀏覽文件", - "app.presentationUploder.fileToUpload": "待上傳...", - "app.presentationUploder.currentBadge": "目前", + "app.presentationUploder.fileToUpload": "即將上傳...", + "app.presentationUploder.currentBadge": "當前", "app.presentationUploder.rejectedError": "所選文件(s)已被拒絕。請檢查文件類型(s)。", - "app.presentationUploder.connectionClosedError": "因連接不良而中斷。請重試。", + "app.presentationUploder.connectionClosedError": "連接中斷,請重試。", "app.presentationUploder.upload.progress": "正在上傳({0}%)", "app.presentationUploder.conversion.204": "無內容可捕獲", - "app.presentationUploder.upload.413": "文件太大,超過了 {0} MB 的最大限制", + "app.presentationUploder.upload.413": "文件太大,超出了 {0} MB 的最大限制", "app.presentationUploder.genericError": "哎呀,出了些問題...", "app.presentationUploder.upload.408": "請求上傳令牌超時。", "app.presentationUploder.upload.404": "404:無效的上傳令牌", @@ -279,43 +323,43 @@ "app.presentationUploder.conversion.conversionProcessingSlides": "正在處理第 {0}/{1} 頁", "app.presentationUploder.conversion.genericConversionStatus": "正在轉換文件...", "app.presentationUploder.conversion.generatingThumbnail": "正在生成縮略圖...", - "app.presentationUploder.conversion.generatedSlides": "生成幻燈片...", + "app.presentationUploder.conversion.generatedSlides": "已生成簡報頁面...", "app.presentationUploder.conversion.generatingSvg": "正在生成 SVG 圖像...", "app.presentationUploder.conversion.pageCountExceeded": "頁數超過最大限制 {0}", - "app.presentationUploder.conversion.invalidMimeType": "檢測到無效格式(擴展名={0},內容類型={1})", - "app.presentationUploder.conversion.conversionTimeout": "無法在 {1} 次嘗試內處理第 {0} 頁。", - "app.presentationUploder.conversion.officeDocConversionInvalid": "處理 Office 文件失敗。請上傳 PDF 文件。", - "app.presentationUploder.conversion.officeDocConversionFailed": "處理 Office 文件失敗。請上傳 PDF 文件。", - "app.presentationUploder.conversion.pdfHasBigPage": "無法轉換 PDF 文件,請優化後再試。最大頁面大小為 {0}", - "app.presentationUploder.conversion.timeout": "哎呀,轉換時間太長了", + "app.presentationUploder.conversion.invalidMimeType": "檢測到無效的格式(擴展名={0},內容類型={1})", + "app.presentationUploder.conversion.conversionTimeout": "第 {0} 頁無法在 {1} 次嘗試內處理。", + "app.presentationUploder.conversion.officeDocConversionInvalid": "無法處理 Office 文件。請上傳 PDF 文件。", + "app.presentationUploder.conversion.officeDocConversionFailed": "無法處理 Office 文件。請上傳 PDF 文件。", + "app.presentationUploder.conversion.pdfHasBigPage": "無法轉換 PDF 文件,請優化它。最大頁面大小 {0}", + "app.presentationUploder.conversion.timeout": "哎呀,轉換時間太長", "app.presentationUploder.conversion.pageCountFailed": "無法確定頁數。", - "app.presentationUploder.conversion.unsupportedDocument": "不支持的文件擴展名", + "app.presentationUploder.conversion.unsupportedDocument": "不支援的文件擴展名", "app.presentationUploder.removePresentationLabel": "移除簡報", - "app.presentationUploder.setAsCurrentPresentation": "設置簡報為目前簡報", + "app.presentationUploder.setAsCurrentPresentation": "設為當前簡報", "app.presentationUploder.tableHeading.filename": "文件名", "app.presentationUploder.tableHeading.options": "選項", "app.presentationUploder.tableHeading.status": "狀態", "app.presentationUploder.uploading": "正在上傳 {0} {1}", - "app.presentationUploder.uploadStatus": "已完成 {0} 個文件的上傳,共 {1} 個", - "app.presentationUploder.completed": "已完成 {0} 個文件的上傳", + "app.presentationUploder.uploadStatus": "已完成 {0}/{1} 個上傳", + "app.presentationUploder.completed": "已完成 {0} 個上傳", "app.presentationUploder.item" : "項目", "app.presentationUploder.itemPlural" : "項目", "app.presentationUploder.clearErrors": "清除錯誤", - "app.presentationUploder.clearErrorsDesc": "清除上傳簡報失敗的錯誤", + "app.presentationUploder.clearErrorsDesc": "清除失敗的簡報上傳", "app.presentationUploder.uploadViewTitle": "上傳簡報", - "app.poll.questionAndoptions.label" : "顯示的問題文字。\nA. 調查選項 *\nB. 調查選項(可選)\nC. 調查選項(可選)\nD. 調查選項(可選)\nE. 調查選項(可選)", + "app.poll.questionAndoptions.label" : "顯示的問題文字。\nA. 投票選項 *\nB. 投票選項(可選)\nC. 投票選項(可選)\nD. 投票選項(可選)\nE. 投票選項(可選)", "app.poll.customInput.label": "自定義輸入", - "app.poll.customInputInstructions.label": "啟用自定義輸入 - 以指定的格式編寫問題和選項(s)或將文本文件以相同格式拖放到此處。", - "app.poll.maxOptionsWarning.label": "僅能使用前 5 個選項!", + "app.poll.customInputInstructions.label": "啟用自定義輸入 - 以給定的格式撰寫投票問題和選項(s),或將文件拖放到相同的格式中。", + "app.poll.maxOptionsWarning.label": "只能使用前5個選項!", "app.poll.pollPaneTitle": "投票", - "app.poll.enableMultipleResponseLabel": "允許每個受訪者選擇多個答案?", + "app.poll.enableMultipleResponseLabel": "允許每個受訪者多個答案嗎?", "app.poll.quickPollTitle": "快速投票", "app.poll.hidePollDesc": "隱藏投票選單窗格", - "app.poll.quickPollInstruction": "選擇下面的選項以開始投票。", - "app.poll.activePollInstruction": "保持此窗格打開以查看投票的即時回應。當您準備好時,選擇“發佈投票結果”以發佈結果並結束投票。", - "app.poll.dragDropPollInstruction": "將具有投票值的文本文件拖到突出顯示的字段上以填充投票值", + "app.poll.quickPollInstruction": "選擇下面的選項來開始投票。", + "app.poll.activePollInstruction": "保持此面板打開,以查看投票的實時回應。準備好後,選擇“發布投票結果”以發布結果並結束投票。", + "app.poll.dragDropPollInstruction": "要填寫投票值,將具有投票值的文本文件拖放到突出顯示的字段上", "app.poll.customPollTextArea": "填寫投票值", - "app.poll.publishLabel": "發佈投票", + "app.poll.publishLabel": "發布投票", "app.poll.cancelPollLabel": "取消", "app.poll.backLabel": "開始投票", "app.poll.closeLabel": "關閉", @@ -326,24 +370,24 @@ "app.poll.clickHereToSelect": "點擊此處選擇", "app.poll.question.label" : "撰寫您的問題...", "app.poll.optionalQuestion.label" : "撰寫您的問題(可選)...", - "app.poll.userResponse.label" : "輸入的回應", + "app.poll.userResponse.label" : "鍵入的回應", "app.poll.responseTypes.label" : "回應類型", "app.poll.optionDelete.label" : "刪除", "app.poll.responseChoices.label" : "回應選項", - "app.poll.typedResponse.desc" : "使用者將被呈現一個文本框填寫他們的回應。", - "app.poll.addItem.label" : "新增項目", + "app.poll.typedResponse.desc" : "用戶將被提供一個文本框來填寫他們的回應。", + "app.poll.addItem.label" : "添加項目", "app.poll.start.label" : "開始投票", "app.poll.secretPoll.label" : "匿名投票", - "app.poll.secretPoll.isSecretLabel": "此投票是匿名的 - 您將無法看到個別的回應。", - "app.poll.questionErr": "需要提供問題。", - "app.poll.optionErr": "輸入投票選項", + "app.poll.secretPoll.isSecretLabel": "此投票是匿名的 - 您將無法查看個別回應。", + "app.poll.questionErr": "必須提供問題。", + "app.poll.optionErr": "請輸入投票選項", "app.poll.startPollDesc": "開始投票", "app.poll.showRespDesc": "顯示回應配置", "app.poll.addRespDesc": "添加投票回應輸入", "app.poll.deleteRespDesc": "刪除選項 {0}", - "app.poll.t": "真", - "app.poll.f": "假", - "app.poll.tf": "真 / 假", + "app.poll.t": "正確", + "app.poll.f": "錯誤", + "app.poll.tf": "正確 / 錯誤", "app.poll.y": "是", "app.poll.n": "否", "app.poll.abstention": "棄權", @@ -352,8 +396,8 @@ "app.poll.a3": "A / B / C", "app.poll.a4": "A / B / C / D", "app.poll.a5": "A / B / C / D / E", - "app.poll.answer.true": "真", - "app.poll.answer.false": "假", + "app.poll.answer.true": "正確", + "app.poll.answer.false": "錯誤", "app.poll.answer.yes": "是", "app.poll.answer.no": "否", "app.poll.answer.abstention": "棄權", @@ -362,123 +406,127 @@ "app.poll.answer.c": "C", "app.poll.answer.d": "D", "app.poll.answer.e": "E", - "app.poll.liveResult.usersTitle": "使用者", + "app.poll.liveResult.usersTitle": "用戶", "app.poll.liveResult.responsesTitle": "回應", - "app.poll.liveResult.secretLabel": "這是一個匿名投票。個別回應不會顯示。", - "app.poll.removePollOpt": "刪除投票選項 {0}", + "app.poll.liveResult.secretLabel": "這是一個匿名投票。不會顯示個別回應。", + "app.poll.removePollOpt": "已刪除投票選項 {0}", "app.poll.emptyPollOpt": "空白", "app.polling.pollingTitle": "投票選項", "app.polling.pollQuestionTitle": "投票問題", "app.polling.submitLabel": "提交", "app.polling.submitAriaLabel": "提交投票回應", "app.polling.responsePlaceholder": "輸入答案", - "app.polling.responseSecret": "匿名投票 - 主持人無法看到您的答案。", - "app.polling.responseNotSecret": "普通投票 - 主持人可以看到您的答案。", - "app.polling.pollAnswerLabel": "投票答案 {0}", - "app.polling.pollAnswerDesc": "選擇此選項以投票給 {0}", + "app.polling.responseSecret": "匿名投票 - 主持人無法查看您的答案。", + "app.polling.responseNotSecret": "常規投票 - 主持人可以看到您的答案。", + "app.polling.pollAnswerLabel": "投票選項 {0}", + "app.polling.pollAnswerDesc": "選擇此選項投票給 {0}", "app.failedMessage": "抱歉,無法連接到伺服器。", "app.downloadPresentationButton.label": "下載原始簡報", - "app.connectingMessage": "連線中...", - "app.waitingMessage": "斷線了。在 {0} 秒後嘗試重新連接...", + "app.connectingMessage": "連接中...", + "app.waitingMessage": "連接已斷開。將在 {0} 秒後嘗試重新連接...", "app.retryNow": "立即重試", - "app.muteWarning.label": "點擊 {0} 解除靜音。", - "app.muteWarning.disableMessage": "解除靜音後禁用警告", - "app.muteWarning.tooltip": "點擊關閉並禁用警告,直到下次解除靜音", + "app.muteWarning.label": "單擊 {0} 以取消靜音。", + "app.muteWarning.disableMessage": "取消靜音警告已禁用,直到解除靜音", + "app.muteWarning.tooltip": "單擊以關閉並禁用警告,直到下次解除靜音", "app.navBar.settingsDropdown.optionsLabel": "選項", "app.navBar.settingsDropdown.fullscreenLabel": "全螢幕應用程式", "app.navBar.settingsDropdown.settingsLabel": "設定", "app.navBar.settingsDropdown.aboutLabel": "關於", "app.navBar.settingsDropdown.leaveSessionLabel": "離開會議", "app.navBar.settingsDropdown.exitFullscreenLabel": "退出全螢幕", - "app.navBar.settingsDropdown.fullscreenDesc": "將設定選單設為全螢幕", - "app.navBar.settingsDropdown.settingsDesc": "更改一般設定", - "app.navBar.settingsDropdown.aboutDesc": "顯示有關客戶端的資訊", + "app.navBar.settingsDropdown.fullscreenDesc": "使設定菜單全屏顯示", + "app.navBar.settingsDropdown.settingsDesc": "更改通用設定", + "app.navBar.settingsDropdown.aboutDesc": "顯示關於客戶端的信息", "app.navBar.settingsDropdown.leaveSessionDesc": "離開會議", - "app.navBar.settingsDropdown.exitFullscreenDesc": "退出全螢幕模式", + "app.navBar.settingsDropdown.exitFullscreenDesc": "退出全屏模式", "app.navBar.settingsDropdown.hotkeysLabel": "鍵盤快捷鍵", "app.navBar.settingsDropdown.hotkeysDesc": "列出可用的鍵盤快捷鍵", "app.navBar.settingsDropdown.helpLabel": "幫助", - "app.navBar.settingsDropdown.openAppLabel": "在BigBlueButton平板應用中打開", - "app.navBar.settingsDropdown.helpDesc": "連結到視頻教程(在新標籤中打開)", + "app.navBar.settingsDropdown.openAppLabel": "在 BigBlueButton 平板應用中打開", + "app.navBar.settingsDropdown.helpDesc": "將用戶鏈接到視頻教程(在新選項卡中打開)", "app.navBar.settingsDropdown.endMeetingDesc": "結束當前會議", "app.navBar.settingsDropdown.endMeetingLabel": "結束會議", "app.navBar.userListToggleBtnLabel": "用戶列表切換", - "app.navBar.toggleUserList.ariaLabel": "用戶和消息切換", - "app.navBar.toggleUserList.newMessages": "有新消息通知", - "app.navBar.toggleUserList.newMsgAria": "{0}有新消息", - "app.navBar.recording": "此會話正在錄製中", - "app.navBar.recording.on": "正在錄製", + "app.navBar.toggleUserList.ariaLabel": "用戶和訊息切換", + "app.navBar.toggleUserList.newMessages": "包含新訊息通知", + "app.navBar.toggleUserList.newMsgAria": "{0} 有新訊息", + "app.navBar.recording": "本次會議正在錄製中", + "app.navBar.recording.on": "錄製中", "app.navBar.recording.off": "未錄製", - "app.navBar.emptyAudioBrdige": "沒有活動的麥克風。請分享您的麥克風以將音頻添加到此錄製中。", + "app.navBar.emptyAudioBrdige": "沒有使用中的麥克風。分享您的麥克風以將音頻添加到此錄製中。", "app.leaveConfirmation.confirmLabel": "離開", "app.leaveConfirmation.confirmDesc": "登出會議", "app.endMeeting.title": "結束 {0}", - "app.endMeeting.description": "此操作將結束 {0} 個活動用戶(s)的會議。您確定要結束此會議嗎?", + "app.endMeeting.description": "此操作將結束 {0} 個活躍使用者(s)的會議。您確定要結束此會議嗎?", "app.endMeeting.noUserDescription": "您確定要結束此會議嗎?", - "app.endMeeting.contentWarning": "此會話的聊天消息、共享筆記、白板內容和共享文件將不再直接訪問", - "app.endMeeting.yesLabel": "結束所有用戶的會議", + "app.endMeeting.contentWarning": "本次會議的聊天訊息、共享筆記、白板內容和共享文件將不再直接存取", + "app.endMeeting.yesLabel": "結束所有使用者的會議", "app.endMeeting.noLabel": "否", "app.about.title": "關於", "app.about.version": "客戶端版本:", - "app.about.version_label": "BigBlueButton版本:", + "app.about.version_label": "BigBlueButton 版本:", "app.about.copyright": "版權:", "app.about.confirmLabel": "確定", "app.about.confirmDesc": "確定", "app.about.dismissLabel": "取消", "app.about.dismissDesc": "關閉關於客戶端資訊", - "app.mobileAppModal.title": "在BigBlueButton平板應用中打開", - "app.mobileAppModal.description": "您的設備上是否已安裝BigBlueButton平板應用程式?", - "app.mobileAppModal.openApp": "是的,立即打開應用程式", - "app.mobileAppModal.obtainUrlMsg": "獲取會議URL", - "app.mobileAppModal.obtainUrlErrorMsg": "嘗試獲取會議URL時出錯", - "app.mobileAppModal.openStore": "否,打開App Store進行下載", + "app.mobileAppModal.title": "開啟 BigBlueButton 平板應用程式", + "app.mobileAppModal.description": "您的設備上是否已安裝 BigBlueButton 平板應用程式?", + "app.mobileAppModal.openApp": "是的,現在開啟應用程式", + "app.mobileAppModal.obtainUrlMsg": "獲取會議網址中", + "app.mobileAppModal.obtainUrlErrorMsg": "嘗試獲取會議網址時出錯", + "app.mobileAppModal.openStore": "否,開啟應用商店下載", "app.mobileAppModal.dismissLabel": "取消", "app.mobileAppModal.dismissDesc": "關閉", - "app.mobileAppModal.userConnectedWithSameId": "用戶{0}剛剛使用與您相同的ID連接。", + "app.mobileAppModal.userConnectedWithSameId": "使用者 {0} 剛剛使用相同的 ID 連線。", "app.actionsBar.changeStatusLabel": "更改狀態", "app.actionsBar.muteLabel": "靜音", - "app.actionsBar.unmuteLabel": "取消靜音", - "app.actionsBar.camOffLabel": "關閉攝像頭", + "app.actionsBar.unmuteLabel": "解除靜音", + "app.actionsBar.camOffLabel": "關閉攝影機", "app.actionsBar.raiseLabel": "舉手", - "app.actionsBar.label": "操作欄", - "app.actionsBar.actionsDropdown.restorePresentationLabel": "恢復演示文稿", - "app.actionsBar.actionsDropdown.restorePresentationDesc": "在最小化後恢復演示文稿的按鈕", - "app.actionsBar.actionsDropdown.minimizePresentationLabel": "最小化演示文稿", - "app.actionsBar.actionsDropdown.minimizePresentationDesc": "用於最小化演示文稿的按鈕", - "app.actionsBar.actionsDropdown.layoutModal": "佈局設定對話框", - "app.screenshare.screenShareLabel" : "屏幕共享", + "app.actionsBar.label": "動作列", + "app.actionsBar.actionsDropdown.restorePresentationLabel": "還原簡報", + "app.actionsBar.actionsDropdown.restorePresentationDesc": "按鈕以在最小化後還原簡報", + "app.actionsBar.actionsDropdown.minimizePresentationLabel": "最小化簡報", + "app.actionsBar.actionsDropdown.minimizePresentationDesc": "按鈕用於最小化簡報", + "app.actionsBar.actionsDropdown.layoutModal": "管理版面配置", + "app.actionsBar.actionsDropdown.shareCameraAsContent": "將攝影機分享為內容", + "app.actionsBar.actionsDropdown.unshareCameraAsContent": "停止將攝影機分享為內容", + "app.screenshare.screenShareLabel" : "螢幕分享", + "app.cameraAsContent.cameraAsContentLabel" : "呈現攝影機", "app.submenu.application.applicationSectionTitle": "應用程式", "app.submenu.application.animationsLabel": "動畫", - "app.submenu.application.audioFilterLabel": "麥克風音頻過濾器", - "app.submenu.application.wbToolbarsAutoHideLabel": "自動隱藏白板工具欄", + "app.submenu.application.audioFilterLabel": "麥克風的音頻濾波器", + "app.submenu.application.wbToolbarsAutoHideLabel": "自動隱藏白板工具列", "app.submenu.application.darkThemeLabel": "深色模式", "app.submenu.application.fontSizeControlLabel": "字體大小", "app.submenu.application.increaseFontBtnLabel": "增加應用程式字體大小", "app.submenu.application.decreaseFontBtnLabel": "減小應用程式字體大小", - "app.submenu.application.currentSize": "目前為 {0}", + "app.submenu.application.currentSize": "目前 {0}", "app.submenu.application.languageLabel": "應用程式語言", "app.submenu.application.languageOptionLabel": "選擇語言", - "app.submenu.application.noLocaleOptionLabel": "無有效地區", - "app.submenu.application.paginationEnabledLabel": "視頻分頁", - "app.submenu.application.layoutOptionLabel": "佈局類型", - "app.submenu.application.pushLayoutLabel": "推送佈局", + "app.submenu.application.noLocaleOptionLabel": "無活動的語言地區", + "app.submenu.application.paginationEnabledLabel": "視訊分頁", + "app.submenu.application.wakeLockEnabledLabel": "喚醒鎖定", + "app.submenu.application.layoutOptionLabel": "版面類型", + "app.submenu.application.pushLayoutLabel": "推送版面", "app.submenu.application.localeDropdown.af": "南非荷蘭語", "app.submenu.application.localeDropdown.ar": "阿拉伯語", - "app.submenu.application.localeDropdown.az": "阿塞拜疆語", + "app.submenu.application.localeDropdown.az": "亞塞拜然語", "app.submenu.application.localeDropdown.bg-BG": "保加利亞語", "app.submenu.application.localeDropdown.bn": "孟加拉語", - "app.submenu.application.localeDropdown.ca": "加泰羅尼亞語", + "app.submenu.application.localeDropdown.ca": "加泰隆尼亞語", "app.submenu.application.localeDropdown.cs-CZ": "捷克語", "app.submenu.application.localeDropdown.da": "丹麥語", "app.submenu.application.localeDropdown.de": "德語", - "app.submenu.application.localeDropdown.dv": "迪維希語", - "app.submenu.application.localeDropdown.el-GR": "希臘語(希臘)", + "app.submenu.application.localeDropdown.dv": "迪維西語", + "app.submenu.application.localeDropdown.el-GR": "希臘語 (希臘)", "app.submenu.application.localeDropdown.en": "英語", "app.submenu.application.localeDropdown.eo": "世界語", "app.submenu.application.localeDropdown.es": "西班牙語", - "app.submenu.application.localeDropdown.es-419": "西班牙語(拉丁美洲)", - "app.submenu.application.localeDropdown.es-ES": "西班牙語(西班牙)", - "app.submenu.application.localeDropdown.es-MX": "西班牙語(墨西哥)", + "app.submenu.application.localeDropdown.es-419": "西班牙語 (拉丁美洲)", + "app.submenu.application.localeDropdown.es-ES": "西班牙語 (西班牙)", + "app.submenu.application.localeDropdown.es-MX": "西班牙語 (墨西哥)", "app.submenu.application.localeDropdown.et": "愛沙尼亞語", "app.submenu.application.localeDropdown.eu": "巴斯克語", "app.submenu.application.localeDropdown.fa-IR": "波斯語", @@ -486,8 +534,8 @@ "app.submenu.application.localeDropdown.fr": "法語", "app.submenu.application.localeDropdown.gl": "加利西亞語", "app.submenu.application.localeDropdown.he": "希伯來語", - "app.submenu.application.localeDropdown.hi-IN": "印地語", - "app.submenu.application.localeDropdown.hr": "克羅地亞語", + "app.submenu.application.localeDropdown.hi-IN": "印度語", + "app.submenu.application.localeDropdown.hr": "克羅埃西亞語", "app.submenu.application.localeDropdown.hu-HU": "匈牙利語", "app.submenu.application.localeDropdown.hy": "亞美尼亞語", "app.submenu.application.localeDropdown.id": "印度尼西亞語", @@ -496,216 +544,220 @@ "app.submenu.application.localeDropdown.ka": "格魯吉亞語", "app.submenu.application.localeDropdown.km": "高棉語", "app.submenu.application.localeDropdown.kn": "坎納達語", - "app.submenu.application.localeDropdown.ko-KR": "韓語(韓國)", + "app.submenu.application.localeDropdown.ko-KR": "韓語 (韓國)", "app.submenu.application.localeDropdown.lo-LA": "老撾語", "app.submenu.application.localeDropdown.lt-LT": "立陶宛語", "app.submenu.application.localeDropdown.lv": "拉脫維亞語", "app.submenu.application.localeDropdown.ml": "馬拉雅拉姆語", "app.submenu.application.localeDropdown.mn-MN": "蒙古語", - "app.submenu.application.localeDropdown.nb-NO": "挪威語(伯克梅爾)", + "app.submenu.application.localeDropdown.nb-NO": "挪威語 (博克馬爾)", "app.submenu.application.localeDropdown.nl": "荷蘭語", - "app.submenu.application.localeDropdown.oc": "奧克西坦語", + "app.submenu.application.localeDropdown.oc": "奧克語", "app.submenu.application.localeDropdown.pl-PL": "波蘭語", "app.submenu.application.localeDropdown.pt": "葡萄牙語", - "app.submenu.application.localeDropdown.pt-BR": "葡萄牙語(巴西)", + "app.submenu.application.localeDropdown.pt-BR": "葡萄牙語 (巴西)", "app.submenu.application.localeDropdown.ro-RO": "羅馬尼亞語", "app.submenu.application.localeDropdown.ru": "俄語", - "app.submenu.application.localeDropdown.sk-SK": "斯洛伐克語(斯洛伐克)", - "app.submenu.application.localeDropdown.sl": "斯洛文尼亞語", + "app.submenu.application.localeDropdown.sk-SK": "斯洛伐克語 (斯洛伐克)", + "app.submenu.application.localeDropdown.sl": "斯洛維尼亞語", "app.submenu.application.localeDropdown.sr": "塞爾維亞語", "app.submenu.application.localeDropdown.sv-SE": "瑞典語", "app.submenu.application.localeDropdown.ta": "泰米爾語", "app.submenu.application.localeDropdown.te": "泰盧固語", "app.submenu.application.localeDropdown.th": "泰語", "app.submenu.application.localeDropdown.tr": "土耳其語", - "app.submenu.application.localeDropdown.tr-TR": "土耳其語(土耳其)", + "app.submenu.application.localeDropdown.tr-TR": "土耳其語 (土耳其)", "app.submenu.application.localeDropdown.uk-UA": "烏克蘭語", "app.submenu.application.localeDropdown.vi": "越南語", - "app.submenu.application.localeDropdown.vi-VN": "越南語(越南)", - "app.submenu.application.localeDropdown.zh-CN": "中文簡體(中國)", - "app.submenu.application.localeDropdown.zh-TW": "中文繁體(台灣)", + "app.submenu.application.localeDropdown.vi-VN": "越南語 (越南)", + "app.submenu.application.localeDropdown.zh-CN": "簡體中文 (中國)", + "app.submenu.application.localeDropdown.zh-TW": "繁體中文 (台灣)", "app.submenu.notification.SectionTitle": "通知", - "app.submenu.notification.Desc": "設置通知方式和內容。", - "app.submenu.notification.audioAlertLabel": "音頻警報", - "app.submenu.notification.pushAlertLabel": "彈出警報", - "app.submenu.notification.messagesLabel": "聊天消息", - "app.submenu.notification.userJoinLabel": "用戶加入", - "app.submenu.notification.userLeaveLabel": "用戶離開", - "app.submenu.notification.guestWaitingLabel": "等待批准的訪客", - "app.submenu.audio.micSourceLabel": "麥克風源", - "app.submenu.audio.speakerSourceLabel": "揚聲器源", - "app.submenu.audio.streamVolumeLabel": "您的音頻流量", - "app.submenu.video.title": "視頻", - "app.submenu.video.videoSourceLabel": "視圖源", - "app.submenu.video.videoOptionLabel": "選擇視圖源", - "app.submenu.video.videoQualityLabel": "視頻質量", - "app.submenu.video.qualityOptionLabel": "選擇視頻質量", - "app.submenu.video.participantsCamLabel": "查看參與者的網絡攝像頭", + "app.submenu.notification.Desc": "定義通知方式和內容。", + "app.submenu.notification.audioAlertLabel": "音頻警示", + "app.submenu.notification.pushAlertLabel": "彈出警示", + "app.submenu.notification.messagesLabel": "聊天訊息", + "app.submenu.notification.userJoinLabel": "使用者加入", + "app.submenu.notification.userLeaveLabel": "使用者離開", + "app.submenu.notification.guestWaitingLabel": "等待審批的訪客", + "app.submenu.audio.micSourceLabel": "麥克風來源", + "app.submenu.audio.speakerSourceLabel": "揚聲器來源", + "app.submenu.audio.streamVolumeLabel": "您的音頻串流音量", + "app.submenu.video.title": "視訊", + "app.submenu.video.videoSourceLabel": "檢視來源", + "app.submenu.video.videoOptionLabel": "選擇檢視來源", + "app.submenu.video.videoQualityLabel": "視訊品質", + "app.submenu.video.qualityOptionLabel": "選擇視訊品質", + "app.submenu.video.participantsCamLabel": "查看參與者的網路攝影機", "app.settings.applicationTab.label": "應用程式", "app.settings.audioTab.label": "音頻", - "app.settings.videoTab.label": "視頻", + "app.settings.videoTab.label": "視訊", "app.settings.usersTab.label": "參與者", - "app.settings.main.label": "設置", + "app.settings.main.label": "設定", "app.settings.main.cancel.label": "取消", - "app.settings.main.cancel.label.description": "放棄更改並關閉設置菜單", - "app.settings.main.save.label": "保存", - "app.settings.main.save.label.description": "保存更改並關閉設置菜單", - "app.settings.dataSavingTab.label": "數據節省", - "app.settings.dataSavingTab.webcam": "啟用其他參與者的網絡攝像頭", + "app.settings.main.cancel.label.description": "放棄變更並關閉設定選單", + "app.settings.main.save.label": "儲存", + "app.settings.main.save.label.description": "儲存變更並關閉設定選單", + "app.settings.dataSavingTab.label": "節省數據", + "app.settings.dataSavingTab.webcam": "啟用其他參與者的攝像頭", "app.settings.dataSavingTab.screenShare": "啟用其他參與者的桌面共享", - "app.settings.dataSavingTab.description": "為節省帶寬,調整當前顯示內容。", - "app.settings.save-notification.label": "設置已保存", - "app.statusNotifier.lowerHands": "放下手", - "app.statusNotifier.lowerHandDescOneUser": "放下 {0} 的手", + "app.settings.dataSavingTab.description": "為節省您的頻寬,調整目前的顯示內容。", + "app.settings.save-notification.label": "設定已保存", + "app.statusNotifier.lowerHands": "降低舉手", + "app.statusNotifier.lowerHandDescOneUser": "降低 {0} 的舉手", "app.statusNotifier.raisedHandsTitle": "舉手", "app.statusNotifier.raisedHandDesc": "{0} 舉手", "app.statusNotifier.raisedHandDescOneUser": "{0} 舉手", "app.statusNotifier.and": "和", "app.switch.onLabel": "開啟", "app.switch.offLabel": "關閉", - "app.talkingIndicator.ariaMuteDesc" : "選擇將用戶靜音", - "app.talkingIndicator.isTalking" : "{0} 正在說話", - "app.talkingIndicator.moreThanMaxIndicatorsTalking" : "有 {0}+ 人正在說話", - "app.talkingIndicator.moreThanMaxIndicatorsWereTalking" : "有 {0}+ 人曾經說話", - "app.talkingIndicator.wasTalking" : "{0} 停止說話", + "app.talkingIndicator.ariaMuteDesc" : "選擇以靜音用戶", + "app.talkingIndicator.isTalking" : "{0} 正在發言", + "app.talkingIndicator.moreThanMaxIndicatorsTalking" : "有 {0} 人正在發言", + "app.talkingIndicator.moreThanMaxIndicatorsWereTalking" : "曾有 {0} 人正在發言", + "app.talkingIndicator.wasTalking" : "{0} 停止發言", "app.actionsBar.actionsDropdown.actionsLabel": "操作", + "app.actionsBar.actionsDropdown.activateTimerStopwatchLabel": "啟用計時器/碼表", + "app.actionsBar.actionsDropdown.deactivateTimerStopwatchLabel": "停用計時器/碼表", "app.actionsBar.actionsDropdown.presentationLabel": "上傳/管理簡報", - "app.actionsBar.actionsDropdown.initPollLabel": "發起投票", - "app.actionsBar.actionsDropdown.desktopShareLabel": "分享屏幕", - "app.actionsBar.actionsDropdown.stopDesktopShareLabel": "停止分享屏幕", + "app.actionsBar.actionsDropdown.initPollLabel": "啟動投票", + "app.actionsBar.actionsDropdown.desktopShareLabel": "分享您的螢幕", + "app.actionsBar.actionsDropdown.stopDesktopShareLabel": "停止分享您的螢幕", "app.actionsBar.actionsDropdown.presentationDesc": "上傳您的簡報", - "app.actionsBar.actionsDropdown.initPollDesc": "發起投票", - "app.actionsBar.actionsDropdown.desktopShareDesc": "與他人分享您的屏幕", - "app.actionsBar.actionsDropdown.stopDesktopShareDesc": "停止與他人分享您的屏幕", + "app.actionsBar.actionsDropdown.initPollDesc": "啟動投票", + "app.actionsBar.actionsDropdown.desktopShareDesc": "與其他人分享您的螢幕", + "app.actionsBar.actionsDropdown.stopDesktopShareDesc": "停止分享您的螢幕", "app.actionsBar.actionsDropdown.pollBtnLabel": "開始投票", "app.actionsBar.actionsDropdown.pollBtnDesc": "切換投票窗格", - "app.actionsBar.actionsDropdown.saveUserNames": "保存用戶名", - "app.actionsBar.actionsDropdown.createBreakoutRoom": "創建分組會議室", - "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "為當前會議創建分組會議室", - "app.actionsBar.actionsDropdown.captionsLabel": "書寫閉路字幕", + "app.actionsBar.actionsDropdown.saveUserNames": "儲存使用者名稱", + "app.actionsBar.actionsDropdown.createBreakoutRoom": "建立分組會議室", + "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "為拆分目前的會議創建分組", + "app.actionsBar.actionsDropdown.captionsLabel": "編寫閉路字幕", "app.actionsBar.actionsDropdown.captionsDesc": "切換字幕窗格", - "app.actionsBar.actionsDropdown.takePresenter": "接管演示者", - "app.actionsBar.actionsDropdown.takePresenterDesc": "將自己指定為新的演示者", - "app.actionsBar.actionsDropdown.selectRandUserLabel": "隨機選擇用戶", - "app.actionsBar.actionsDropdown.selectRandUserDesc": "從可用觀眾中隨機選擇一個用戶", - "app.actionsBar.actionsDropdown.propagateLayoutLabel": "傳播布局", - "app.actionsBar.emojiMenu.statusTriggerLabel": "設置狀態", - "app.actionsBar.emojiMenu.awayLabel": "離開", - "app.actionsBar.emojiMenu.awayDesc": "將您的狀態設置為離開", + "app.actionsBar.actionsDropdown.takePresenter": "取得簡報者身份", + "app.actionsBar.actionsDropdown.takePresenterDesc": "將自己指定為新的簡報者", + "app.actionsBar.actionsDropdown.selectRandUserLabel": "選擇隨機使用者", + "app.actionsBar.actionsDropdown.selectRandUserDesc": "從可用的觀眾中隨機選擇一位使用者", + "app.actionsBar.reactions.reactionsButtonLabel": "反應工具列", + "app.actionsBar.reactions.raiseHand": "舉手", + "app.actionsBar.reactions.lowHand": "降低手", + "app.actionsBar.emojiMenu.statusTriggerLabel": "設定狀態", + "app.actionsBar.emojiMenu.awayLabel": "暫離", + "app.actionsBar.emojiMenu.awayDesc": "將您的狀態更改為暫離", "app.actionsBar.emojiMenu.raiseHandLabel": "舉手", - "app.actionsBar.emojiMenu.lowerHandLabel": "放下手", - "app.actionsBar.emojiMenu.raiseHandDesc": "舉手提問", - "app.actionsBar.emojiMenu.neutralLabel": "猶豫不決", - "app.actionsBar.emojiMenu.neutralDesc": "將您的狀態設置為猶豫不決", + "app.actionsBar.emojiMenu.lowerHandLabel": "降低手", + "app.actionsBar.emojiMenu.raiseHandDesc": "舉手提出問題", + "app.actionsBar.emojiMenu.neutralLabel": "未決定", + "app.actionsBar.emojiMenu.neutralDesc": "將您的狀態更改為未決定", "app.actionsBar.emojiMenu.confusedLabel": "困惑", - "app.actionsBar.emojiMenu.confusedDesc": "將您的狀態設置為困惑", + "app.actionsBar.emojiMenu.confusedDesc": "將您的狀態更改為困惑", "app.actionsBar.emojiMenu.sadLabel": "傷心", - "app.actionsBar.emojiMenu.sadDesc": "將您的狀態設置為傷心", + "app.actionsBar.emojiMenu.sadDesc": "將您的狀態更改為傷心", "app.actionsBar.emojiMenu.happyLabel": "快樂", - "app.actionsBar.emojiMenu.happyDesc": "將您的狀態設置為快樂", + "app.actionsBar.emojiMenu.happyDesc": "將您的狀態更改為快樂", "app.actionsBar.emojiMenu.noneLabel": "清除狀態", "app.actionsBar.emojiMenu.noneDesc": "清除您的狀態", "app.actionsBar.emojiMenu.applauseLabel": "鼓掌", - "app.actionsBar.emojiMenu.applauseDesc": "將您的狀態設置為鼓掌", - "app.actionsBar.emojiMenu.thumbsUpLabel": "大拇指向上", - "app.actionsBar.emojiMenu.thumbsUpDesc": "將您的狀態設置為大拇指向上", - "app.actionsBar.emojiMenu.thumbsDownLabel": "大拇指向下", - "app.actionsBar.emojiMenu.thumbsDownDesc": "將您的狀態設置為大拇指向下", + "app.actionsBar.emojiMenu.applauseDesc": "將您的狀態更改為鼓掌", + "app.actionsBar.emojiMenu.thumbsUpLabel": "讚", + "app.actionsBar.emojiMenu.thumbsUpDesc": "將您的狀態更改為讚", + "app.actionsBar.emojiMenu.thumbsDownLabel": "不喜歡", + "app.actionsBar.emojiMenu.thumbsDownDesc": "將您的狀態更改為不喜歡", "app.actionsBar.currentStatusDesc": "當前狀態 {0}", - "app.actionsBar.captions.start": "開始查看閉路字幕", - "app.actionsBar.captions.stop": "停止查看閉路字幕", - "app.audioNotification.audioFailedError1001": "WebSocket 斷開連接(錯誤 1001)", - "app.audioNotification.audioFailedError1002": "無法建立 WebSocket 連接(錯誤 1002)", - "app.audioNotification.audioFailedError1003": "不支持的瀏覽器版本(錯誤 1003)", - "app.audioNotification.audioFailedError1004": "通話失敗(原因={0})(錯誤 1004)", - "app.audioNotification.audioFailedError1005": "通話意外結束(錯誤 1005)", - "app.audioNotification.audioFailedError1006": "通話超時(錯誤 1006)", - "app.audioNotification.audioFailedError1007": "連接失敗(ICE 錯誤 1007)", - "app.audioNotification.audioFailedError1008": "傳輸失敗(錯誤 1008)", - "app.audioNotification.audioFailedError1009": "無法獲取 STUN/TURN 服務器信息(錯誤 1009)", - "app.audioNotification.audioFailedError1010": "連接協商超時(ICE 錯誤 1010)", - "app.audioNotification.audioFailedError1011": "連接超時(ICE 錯誤 1011)", - "app.audioNotification.audioFailedError1012": "連接已關閉(ICE 錯誤 1012)", - "app.audioNotification.audioFailedMessage": "無法連接您的音頻", - "app.audioNotification.mediaFailedMessage": "getUserMicMedia 失敗,僅允許使用安全來源", - "app.audioNotification.deviceChangeFailed": "音頻設備更改失敗。請檢查所選設備是否正確設置並可用", + "app.actionsBar.captions.start": "開始檢視閉路字幕", + "app.actionsBar.captions.stop": "停止檢視閉路字幕", + "app.audioNotification.audioFailedError1001": "WebSocket 斷開連接 (錯誤 1001)", + "app.audioNotification.audioFailedError1002": "無法建立 WebSocket 連接 (錯誤 1002)", + "app.audioNotification.audioFailedError1003": "不支援的瀏覽器版本 (錯誤 1003)", + "app.audioNotification.audioFailedError1004": "呼叫失敗 (原因={0}) (錯誤 1004)", + "app.audioNotification.audioFailedError1005": "呼叫意外結束 (錯誤 1005)", + "app.audioNotification.audioFailedError1006": "呼叫超時 (錯誤 1006)", + "app.audioNotification.audioFailedError1007": "連接失敗 (ICE 錯誤 1007)", + "app.audioNotification.audioFailedError1008": "傳輸失敗 (錯誤 1008)", + "app.audioNotification.audioFailedError1009": "無法獲取 STUN/TURN 伺服器資訊 (錯誤 1009)", + "app.audioNotification.audioFailedError1010": "連接協商超時 (ICE 錯誤 1010)", + "app.audioNotification.audioFailedError1011": "連接超時 (ICE 錯誤 1011)", + "app.audioNotification.audioFailedError1012": "連接已關閉 (ICE 錯誤 1012)", + "app.audioNotification.audioFailedMessage": "您的音訊連接無法連接", + "app.audioNotification.mediaFailedMessage": "getUserMicMedia 失敗,僅允許安全來源", + "app.audioNotification.deviceChangeFailed": "音訊裝置更改失敗。請檢查所選裝置是否正確設置並可用", "app.audioNotification.closeLabel": "關閉", - "app.audioNotificaion.reconnectingAsListenOnly": "麥克風已被鎖定供觀眾使用,您將以僅聽模式連接", - "app.breakoutJoinConfirmation.title": "加入分組房間", - "app.breakoutJoinConfirmation.message": "您要加入嗎", - "app.breakoutJoinConfirmation.confirmDesc": "加入分組房間", + "app.audioNotificaion.reconnectingAsListenOnly": "麥克風已被鎖定為僅供觀眾使用,您正在以僅聽模式連接", + "app.breakoutJoinConfirmation.title": "加入分組會議室", + "app.breakoutJoinConfirmation.message": "您是否要加入", + "app.breakoutJoinConfirmation.confirmDesc": "將您加入分組會議室", "app.breakoutJoinConfirmation.dismissLabel": "取消", - "app.breakoutJoinConfirmation.dismissDesc": "關閉並拒絕加入分組房間", - "app.breakoutJoinConfirmation.freeJoinMessage": "選擇要加入的分組房間", - "app.breakoutTimeRemainingMessage": "分組房間剩餘時間:{0}", - "app.breakoutWillCloseMessage": "時間結束。分組房間將很快關閉", - "app.breakout.dropdown.manageDuration": "更改持續時間", - "app.breakout.dropdown.destroyAll": "結束分組房間", + "app.breakoutJoinConfirmation.dismissDesc": "關閉並拒絕加入分組會議室", + "app.breakoutJoinConfirmation.freeJoinMessage": "選擇要加入的分組會議室", + "app.breakoutTimeRemainingMessage": "分組會議室剩餘時間:{0}", + "app.breakoutWillCloseMessage": "時間已到。分組會議室將很快關閉", + "app.breakout.dropdown.manageDuration": "變更持續時間", + "app.breakout.dropdown.destroyAll": "結束分組會議室", "app.breakout.dropdown.options": "分組選項", - "app.breakout.dropdown.manageUsers": "管理用戶", + "app.breakout.dropdown.manageUsers": "管理使用者", "app.calculatingBreakoutTimeRemaining": "正在計算剩餘時間...", - "app.audioModal.ariaTitle": "加入音頻模式", + "app.audioModal.ariaTitle": "加入音訊對話模式", "app.audioModal.microphoneLabel": "麥克風", - "app.audioModal.listenOnlyLabel": "僅聽", - "app.audioModal.microphoneDesc": "使用麥克風加入音頻會議", - "app.audioModal.listenOnlyDesc": "以僅聽模式加入音頻會議", - "app.audioModal.audioChoiceLabel": "您希望如何加入音頻?", - "app.audioModal.iOSBrowser": "不支持音頻/視頻", - "app.audioModal.iOSErrorDescription": "目前在 iOS 的 Chrome 不支持音頻和視頻。", - "app.audioModal.iOSErrorRecommendation": "我們建議使用 iOS 的 Safari。", - "app.audioModal.audioChoiceDesc": "選擇如何加入此會議的音頻", - "app.audioModal.unsupportedBrowserLabel": "看起來您正在使用不完全支持的瀏覽器。請使用 {0} 或 {1} 以獲得完全支持。", + "app.audioModal.listenOnlyLabel": "僅聽模式", + "app.audioModal.microphoneDesc": "以麥克風加入音訊會議", + "app.audioModal.listenOnlyDesc": "以僅聽模式加入音訊會議", + "app.audioModal.audioChoiceLabel": "您希望如何加入音訊?", + "app.audioModal.iOSBrowser": "不支援音/視訊", + "app.audioModal.iOSErrorDescription": "目前 Chrome iOS 不支援音訊和視訊。", + "app.audioModal.iOSErrorRecommendation": "我們建議在 iOS 上使用 Safari。", + "app.audioModal.audioChoiceDesc": "選擇在此會議中如何加入音訊", + "app.audioModal.unsupportedBrowserLabel": "看起來您正在使用不完全支援的瀏覽器。請選擇 {0} 或 {1} 以取得完整支援。", "app.audioModal.closeLabel": "關閉", "app.audioModal.yes": "是", "app.audioModal.no": "否", - "app.audioModal.yes.arialabel" : "可以聽到回聲", - "app.audioModal.no.arialabel" : "無法聽到回聲", - "app.audioModal.echoTestTitle": "這是一個私人回聲測試。請講幾個詞。您聽到聲音了嗎?", - "app.audioModal.settingsTitle": "更改您的音頻設置", - "app.audioModal.helpTitle": "您的媒體設備出現問題", - "app.audioModal.helpText": "您是否允許訪問麥克風的權限?注意,當您嘗試加入音頻時,應該會出現一個對話框,要求您授予媒體設備的權限,請接受以加入音頻會議。如果不是這種情況,請嘗試在瀏覽器的設置中更改麥克風權限。", - "app.audioModal.help.noSSL": "此頁面未安全。為了允許麥克風訪問,頁面必須通過 HTTPS 進行服務。請聯繫服務器管理員。", - "app.audioModal.help.macNotAllowed": "看起來您的 Mac 系統偏好設置阻止訪問您的麥克風。打開系統偏好設置 > 安全性與隱私 > 隱私 > 麥克風,並確認您正在使用的瀏覽器被勾選。", - "app.audioModal.audioDialTitle": "使用您的電話加入", + "app.audioModal.yes.arialabel" : "回聲可聽見", + "app.audioModal.no.arialabel" : "回聲不可聽見", + "app.audioModal.echoTestTitle": "這是私人回聲測試。說幾個詞。您聽到音訊了嗎?", + "app.audioModal.settingsTitle": "變更您的音訊設定", + "app.audioModal.helpTitle": "您的媒體裝置有問題", + "app.audioModal.helpText": "您是否同意存取您的麥克風?請注意,當您嘗試加入音訊時,應出現一個對話方塊,詢問您的媒體裝置權限,請接受以加入音訊會議。如果不是這種情況,請嘗試在瀏覽器設定中更改麥克風權限。", + "app.audioModal.help.noSSL": "此頁面未經保證。為了允許麥克風存取,頁面必須使用 HTTPS 來進行服務。請聯繫伺服器管理員。", + "app.audioModal.help.macNotAllowed": "看起來您的 Mac 系統偏好設定阻止了對麥克風的存取。打開系統偏好設定 > 安全性與隱私 > 隱私 > 麥克風,並確認您使用的瀏覽器已勾選。", + "app.audioModal.audioDialTitle": "使用您的手機加入", "app.audioDial.audioDialDescription": "撥打", - "app.audioDial.audioDialConfrenceText": "並輸入會議 PIN 碼:", - "app.audioModal.autoplayBlockedDesc": "我們需要您的許可才能播放音頻。", - "app.audioModal.playAudio": "播放音頻", - "app.audioModal.playAudio.arialabel" : "播放音頻", + "app.audioDial.audioDialConfrenceText": "並輸入會議的 PIN 碼:", + "app.audioModal.autoplayBlockedDesc": "我們需要您的許可才能播放音訊。", + "app.audioModal.playAudio": "播放音訊", + "app.audioModal.playAudio.arialabel" : "播放音訊", "app.audioDial.tipIndicator": "提示", - "app.audioDial.tipMessage": "在您的電話上按下“0”鍵以靜音/取消靜音自己。", - "app.audioModal.connecting": "正在建立音頻連接", - "app.audioManager.joinedAudio": "您已加入音頻會議", + "app.audioDial.tipMessage": "在您的手機上按下 '0' 鍵來靜音/取消靜音。", + "app.audioModal.connecting": "正在建立音訊連接", + "app.audioManager.joinedAudio": "您已加入音訊會議", "app.audioManager.joinedEcho": "您已加入回聲測試", - "app.audioManager.leftAudio": "您已退出音頻會議", - "app.audioManager.reconnectingAudio": "正在嘗試重新連接音頻", + "app.audioManager.leftAudio": "您已退出音訊會議", + "app.audioManager.reconnectingAudio": "正在嘗試重新連接音訊", "app.audioManager.genericError": "錯誤:發生錯誤,請重試", "app.audioManager.connectionError": "錯誤:連接錯誤", "app.audioManager.requestTimeout": "錯誤:請求超時", - "app.audioManager.invalidTarget": "錯誤:嘗試向無效目標發出請求", - "app.audioManager.mediaError": "錯誤:獲取媒體設備時出現問題", - "app.audio.joinAudio": "加入音頻", - "app.audio.leaveAudio": "離開音頻", - "app.audio.changeAudioDevice": "更改音頻設備", - "app.audio.enterSessionLabel": "進入會議", - "app.audio.playSoundLabel": "播放聲音", - "app.audio.stopAudioFeedback": "停止音頻回饋", + "app.audioManager.invalidTarget": "錯誤:嘗試向無效的目標發出請求", + "app.audioManager.mediaError": "錯誤:取得媒體裝置時出現問題", + "app.audio.joinAudio": "加入音訊", + "app.audio.leaveAudio": "離開音訊", + "app.audio.changeAudioDevice": "變更音訊裝置", + "app.audio.enterSessionLabel": "進入會話", + "app.audio.playSoundLabel": "播放音效", + "app.audio.stopAudioFeedback": "停止音效回饋", "app.audio.backLabel": "返回", "app.audio.loading": "正在載入", "app.audio.microphones": "麥克風", "app.audio.speakers": "揚聲器", - "app.audio.noDeviceFound": "未找到設備", - "app.audio.audioSettings.titleLabel": "選擇您的音頻設置", - "app.audio.audioSettings.descriptionLabel": "請注意,您的瀏覽器將出現一個對話框,要求您接受共享麥克風。", + "app.audio.noDeviceFound": "找不到裝置", + "app.audio.audioSettings.titleLabel": "選擇您的音訊設定", + "app.audio.audioSettings.descriptionLabel": "請注意,瀏覽器將彈出對話框,要求您允許分享麥克風。", "app.audio.audioSettings.microphoneSourceLabel": "麥克風來源", "app.audio.audioSettings.speakerSourceLabel": "揚聲器來源", "app.audio.audioSettings.testSpeakerLabel": "測試您的揚聲器", "app.audio.audioSettings.microphoneStreamLabel": "您的音頻流音量", "app.audio.audioSettings.retryLabel": "重試", - "app.audio.audioSettings.fallbackInputLabel": "音頻輸入 {0}", - "app.audio.audioSettings.fallbackOutputLabel": "音頻輸出 {0}", - "app.audio.audioSettings.defaultOutputDeviceLabel": "默認", - "app.audio.audioSettings.findingDevicesLabel": "正在查找設備...", + "app.audio.audioSettings.fallbackInputLabel": "音訊輸入 {0}", + "app.audio.audioSettings.fallbackOutputLabel": "音訊輸出 {0}", + "app.audio.audioSettings.defaultOutputDeviceLabel": "預設", + "app.audio.audioSettings.findingDevicesLabel": "尋找裝置...", "app.audio.listenOnly.backLabel": "返回", "app.audio.listenOnly.closeLabel": "關閉", "app.audio.permissionsOverlay.title": "允許訪問您的麥克風", @@ -714,10 +766,10 @@ "app.audio.captions.button.stop": "停止閉路字幕", "app.audio.captions.button.language": "語言", "app.audio.captions.button.transcription": "轉錄", - "app.audio.captions.button.transcriptionSettings": "轉錄設置", + "app.audio.captions.button.transcriptionSettings": "轉錄設定", "app.audio.captions.speech.title": "自動轉錄", "app.audio.captions.speech.disabled": "已禁用", - "app.audio.captions.speech.unsupported": "您的瀏覽器不支持語音識別。您的音頻將不會被轉錄", + "app.audio.captions.speech.unsupported": "您的瀏覽器不支援語音識別。您的音頻將不會被轉錄", "app.audio.captions.select.de-DE": "德語", "app.audio.captions.select.en-US": "英語", "app.audio.captions.select.es-ES": "西班牙語", @@ -727,129 +779,136 @@ "app.audio.captions.select.ja-JP": "日語", "app.audio.captions.select.pt-BR": "葡萄牙語", "app.audio.captions.select.ru-RU": "俄語", - "app.audio.captions.select.zh-CN": "中文", - "app.error.removed": "您已從會議中被移除", + "app.audio.captions.select.zh-CN": "中文普通話", + "app.error.removed": "您已被移出會議", "app.error.meeting.ended": "您已登出會議", - "app.meeting.logout.duplicateUserEjectReason": "重複使用者試圖加入會議", - "app.meeting.logout.permissionEjectReason": "由於違反權限而被踢出", - "app.meeting.logout.ejectedFromMeeting": "您已從會議中被移除", - "app.meeting.logout.validateTokenFailedEjectReason": "驗證授權令牌失敗", - "app.meeting.logout.userInactivityEjectReason": "使用者閒置時間過長", - "app.meeting.logout.maxParticipantsReached": "已達到此會議允許的最大參與人數", + "app.meeting.logout.duplicateUserEjectReason": "重複的使用者嘗試加入會議", + "app.meeting.logout.permissionEjectReason": "因權限違規而被驅逐", + "app.meeting.logout.ejectedFromMeeting": "您已從會議中移除", + "app.meeting.logout.validateTokenFailedEjectReason": "無法驗證授權令牌", + "app.meeting.logout.userInactivityEjectReason": "使用者閒置太久", + "app.meeting.logout.maxParticipantsReached": "此會議已達到允許的最大參與人數", "app.meeting-ended.rating.legendLabel": "反饋評分", "app.meeting-ended.rating.starLabel": "星級", "app.modal.close": "關閉", - "app.modal.close.description": "放棄更改並關閉彈出窗口", + "app.modal.close.description": "放棄更改並關閉對話框", "app.modal.confirm": "完成", - "app.modal.newTab": "(在新標籤中打開)", - "app.modal.confirm.description": "保存更改並關閉彈出窗口", - "app.modal.randomUser.noViewers.description": "沒有可供隨機選擇的觀眾", + "app.modal.newTab": "(在新分頁中打開)", + "app.modal.confirm.description": "保存更改並關閉對話框", + "app.modal.randomUser.noViewers.description": "目前沒有可供隨機選擇的觀眾", "app.modal.randomUser.selected.description": "您已被隨機選中", - "app.modal.randomUser.title": "隨機選擇的使用者", - "app.modal.randomUser.who": "將選中誰..?", - "app.modal.randomUser.alone": "只有一位觀眾", + "app.modal.randomUser.title": "隨機選中的使用者", + "app.modal.randomUser.who": "將選擇誰......?", + "app.modal.randomUser.alone": "只有一名觀眾", "app.modal.randomUser.reselect.label": "重新選擇", - "app.modal.randomUser.ariaLabel.title": "隨機選擇的使用者彈出窗口", + "app.modal.randomUser.ariaLabel.title": "隨機選中的使用者對話框", "app.dropdown.close": "關閉", "app.dropdown.list.item.activeLabel": "活動", "app.error.400": "錯誤的請求", "app.error.401": "未經授權", - "app.error.403": "您已從會議中被移除", + "app.error.403": "您已被從會議中移除", "app.error.404": "找不到", "app.error.408": "驗證失敗", "app.error.409": "衝突", "app.error.410": "會議已結束", - "app.error.500": "糟糕,出了一些問題", + "app.error.500": "哎呀,出現了一些問題", "app.error.503": "您已斷開連接", "app.error.disconnected.rejoin": "您可以刷新頁面重新加入。", - "app.error.userLoggedOut": "用戶的 sessionToken 無效,因為已登出", - "app.error.ejectedUser": "用戶的 sessionToken 無效,因為已被踢出", - "app.error.joinedAnotherWindow": "該會話似乎已在另一個瀏覽器窗口中打開。", - "app.error.userBanned": "用戶已被封禁", - "app.error.leaveLabel": "重新登錄", + "app.error.userLoggedOut": "使用者因登出而具有無效的 sessionToken", + "app.error.ejectedUser": "因被驅逐而具有無效的 sessionToken", + "app.error.joinedAnotherWindow": "此會話似乎在另一個瀏覽器窗口中打開。", + "app.error.userBanned": "使用者已被封鎖", + "app.error.leaveLabel": "重新登入", "app.error.fallback.presentation.title": "發生錯誤", - "app.error.fallback.presentation.description": "該錯誤已被記錄。請嘗試重新加載頁面。", - "app.error.fallback.presentation.reloadButton": "重新加載", - "app.guest.errorSeeConsole": "錯誤:詳情請參閱控制台。", - "app.guest.noModeratorResponse": "未收到主持人的回應。", - "app.guest.noSessionToken": "未收到會話令牌。", - "app.guest.windowTitle": "BigBlueButton - 訪客候補區", - "app.guest.missingToken": "缺少訪客的會話令牌。", - "app.guest.missingSession": "缺少訪客的會話。", + "app.error.fallback.presentation.description": "已記錄。請嘗試重新載入頁面。", + "app.error.fallback.presentation.reloadButton": "重新載入", + "app.guest.errorSeeConsole": "錯誤:詳細信息請查看控制台。", + "app.guest.noModeratorResponse": "主持人無回應。", + "app.guest.noSessionToken": "未收到 sessionToken。", + "app.guest.windowTitle": "BigBlueButton - 訪客大廳", + "app.guest.missingToken": "訪客缺少 session token。", + "app.guest.missingSession": "訪客缺少 session。", "app.guest.missingMeeting": "會議不存在。", "app.guest.meetingEnded": "會議已結束。", "app.guest.guestWait": "請等待主持人批准您加入會議。", - "app.guest.guestDeny": "禁止訪客加入會議。", - "app.guest.seatWait": "訪客正在等待加入會議。", - "app.guest.allow": "訪客已被批准,正在重新導向到會議。", - "app.guest.firstPositionInWaitingQueue": "您是第一位排隊等候的人!", - "app.guest.positionInWaitingQueue": "您在等候隊列中的當前位置:", - "app.guest.guestInvalid": "訪客用戶無效", - "app.guest.meetingForciblyEnded": "您無法加入已被強制結束的會議", + "app.guest.guestDeny": "訪客被拒絕加入會議。", + "app.guest.seatWait": "訪客等待在會議中佔用席位。", + "app.guest.allow": "訪客已獲准,正在重新導向至會議。", + "app.guest.firstPositionInWaitingQueue": "您排在第一位!", + "app.guest.positionInWaitingQueue": "您目前在等候佇列中的位置:", + "app.guest.guestInvalid": "訪客使用者無效", + "app.guest.meetingForciblyEnded": "您不能加入已強制結束的會議", "app.userList.guest.waitingUsers": "等待中的使用者", "app.userList.guest.waitingUsersTitle": "使用者管理", - "app.userList.guest.optionTitle": "審核待審核的使用者", - "app.userList.guest.allowAllAuthenticated": "允許所有已驗證的使用者", + "app.userList.guest.optionTitle": "審核待處理的使用者", + "app.userList.guest.allowAllAuthenticated": "允許所有已驗證使用者", "app.userList.guest.allowAllGuests": "允許所有訪客", "app.userList.guest.allowEveryone": "允許所有人", - "app.userList.guest.denyEveryone": "禁止所有人", - "app.userList.guest.pendingUsers": "{0} 個待審核使用者", - "app.userList.guest.noPendingUsers": "當前沒有待審核的使用者...", - "app.userList.guest.pendingGuestUsers": "{0} 個待審核訪客使用者", - "app.userList.guest.pendingGuestAlert": "已加入會話,正在等待您的批准。", + "app.userList.guest.denyEveryone": "拒絕所有人", + "app.userList.guest.pendingUsers": "{0} 位待處理的使用者", + "app.userList.guest.noPendingUsers": "目前沒有待處理的使用者...", + "app.userList.guest.pendingGuestUsers": "{0} 位待處理的訪客使用者", + "app.userList.guest.pendingGuestAlert": "已加入會議,正在等待您的批准。", "app.userList.guest.rememberChoice": "記住選擇", "app.userList.guest.emptyMessage": "目前沒有訊息", - "app.userList.guest.inputPlaceholder": "發送訊息到訪客候補區", - "app.userList.guest.privateInputPlaceholder": "發送訊息給 {0}", + "app.userList.guest.inputPlaceholder": "訊息寄送至訪客大廳", + "app.userList.guest.privateInputPlaceholder": "訊息寄送至 {0}", "app.userList.guest.privateMessageLabel": "訊息", "app.userList.guest.acceptLabel": "接受", "app.userList.guest.denyLabel": "拒絕", - "app.userList.guest.feedbackMessage": "已應用動作:", + "app.userList.guest.feedbackMessage": "已套用操作:", "app.user-info.title": "目錄查找", - "app.toast.breakoutRoomEnded": "分組討論室已結束。請重新加入音訊。", - "app.toast.chat.public": "新的公開聊天訊息", + "app.toast.breakoutRoomEnded": "分組討論室已結束。請重新加入語音。", + "app.toast.chat.public": "新的公共聊天訊息", "app.toast.chat.private": "新的私人聊天訊息", "app.toast.chat.system": "系統", "app.toast.chat.poll": "投票結果", - "app.toast.chat.pollClick": "投票結果已發佈。點擊此處查看。", - "app.toast.clearedEmoji.label": "已清除表情狀態", - "app.toast.setEmoji.label": "表情狀態設置為 {0}", - "app.toast.meetingMuteOn.label": "所有使用者已靜音", - "app.toast.meetingMuteOnViewers.label": "所有觀眾已靜音", + "app.toast.chat.pollClick": "已發布投票結果。按一下這裡查看。", + "app.toast.clearedEmoji.label": "清除表情狀態", + "app.toast.setEmoji.label": "表情狀態設為 {0}", + "app.toast.meetingMuteOn.label": "所有使用者已被靜音", + "app.toast.meetingMuteOnViewers.label": "所有觀眾已被靜音", "app.toast.meetingMuteOff.label": "會議靜音已關閉", + "app.toast.wakeLock.offerTitle": "您是否想在會議期間保持裝置屏幕處於活動狀態?", + "app.toast.wakeLock.offerAccept": "是的!", + "app.toast.wakeLock.offerDecline": "暫時不需要", + "app.toast.wakeLock.acquireSuccess": "活動鎖定已啟用!您可以在設定選單下關閉它。", + "app.toast.wakeLock.acquireFailed": "錯誤:無法獲取活動鎖定。", "app.toast.setEmoji.raiseHand": "您已舉手", - "app.toast.setEmoji.lowerHand": "您的舉手已取消", + "app.toast.setEmoji.lowerHand": "您的手已放下", + "app.toast.setEmoji.away": "您已設置狀態為離開", + "app.toast.setEmoji.notAway": "您已取消離開狀態", "app.toast.promotedLabel": "您已晉升為主持人", "app.toast.demotedLabel": "您已降級為觀眾", - "app.notification.recordingStart": "本次會議正在錄製", - "app.notification.recordingStop": "本次會議未進行錄製", - "app.notification.recordingPaused": "本次會議不再進行錄製", + "app.notification.recordingStart": "此會話正在被錄製", + "app.notification.recordingStop": "此會話未被錄製", + "app.notification.recordingPaused": "此會話不再被錄製", "app.notification.recordingAriaLabel": "錄製時間 ", - "app.notification.userJoinPushAlert": "{0} 加入了會議", - "app.notification.userLeavePushAlert": "{0} 離開了會議", + "app.notification.userJoinPushAlert": "{0} 加入了會話", + "app.notification.userLeavePushAlert": "{0} 離開了會話", "app.submenu.notification.raiseHandLabel": "舉手", "app.shortcut-help.title": "鍵盤快捷鍵", - "app.shortcut-help.accessKeyNotAvailable": "無法使用存取鍵", + "app.shortcut-help.accessKeyNotAvailable": "無法使用快捷鍵", "app.shortcut-help.comboLabel": "組合鍵", "app.shortcut-help.alternativeLabel": "替代鍵", "app.shortcut-help.functionLabel": "功能鍵", "app.shortcut-help.closeLabel": "關閉", - "app.shortcut-help.closeDesc": "關閉鍵盤快捷鍵彈出窗口", + "app.shortcut-help.closeDesc": "關閉鍵盤快捷鍵對話框", "app.shortcut-help.openOptions": "打開選項", "app.shortcut-help.toggleUserList": "切換使用者列表", - "app.shortcut-help.toggleMute": "靜音 / 解除靜音", - "app.shortcut-help.togglePublicChat": "切換公開聊天(必須打開使用者列表)", + "app.shortcut-help.toggleMute": "靜音 / 取消靜音", + "app.shortcut-help.togglePublicChat": "切換公共聊天(必須打開使用者列表)", "app.shortcut-help.hidePrivateChat": "隱藏私人聊天", "app.shortcut-help.closePrivateChat": "關閉私人聊天", - "app.shortcut-help.openActions": "打開操作選單", + "app.shortcut-help.openActions": "開啟操作選單", "app.shortcut-help.raiseHand": "切換舉手", - "app.shortcut-help.openDebugWindow": "打開調試窗口", - "app.shortcut-help.openStatus": "打開狀態選單", + "app.shortcut-help.openDebugWindow": "開啟除錯視窗", + "app.shortcut-help.openStatus": "開啟狀態選單", "app.shortcut-help.togglePan": "啟用平移工具(演示者)", "app.shortcut-help.toggleFullscreen": "切換全螢幕(演示者)", "app.shortcut-help.nextSlideDesc": "下一張投影片(演示者)", "app.shortcut-help.previousSlideDesc": "上一張投影片(演示者)", - "app.shortcut-help.togglePanKey": "空白鍵", + "app.shortcut-help.togglePanKey": "空格鍵", "app.shortcut-help.toggleFullscreenKey": "Enter 鍵", "app.shortcut-help.nextSlideKey": "右箭頭鍵", "app.shortcut-help.previousSlideKey": "左箭頭鍵", @@ -869,7 +928,7 @@ "app.shortcut-help.zoomIn": "放大", "app.shortcut-help.zoomOut": "縮小", "app.shortcut-help.zoomFit": "重設縮放", - "app.shortcut-help.zoomSelect": "縮放到選擇區域", + "app.shortcut-help.zoomSelect": "縮放至選擇範圍", "app.shortcut-help.flipH": "水平翻轉", "app.shortcut-help.flipV": "垂直翻轉", "app.shortcut-help.lock": "鎖定 / 解鎖", @@ -877,38 +936,40 @@ "app.shortcut-help.moveToBack": "移到最後面", "app.shortcut-help.moveForward": "向前移動", "app.shortcut-help.moveBackward": "向後移動", - "app.shortcut-help.undo": "撤銷", + "app.shortcut-help.undo": "復原", "app.shortcut-help.redo": "重做", - "app.shortcut-help.cut": "剪切", + "app.shortcut-help.cut": "剪下", "app.shortcut-help.copy": "複製", "app.shortcut-help.paste": "貼上", "app.shortcut-help.selectAll": "全選", "app.shortcut-help.delete": "刪除", "app.shortcut-help.duplicate": "複製", "app.lock-viewers.title": "鎖定觀眾", - "app.lock-viewers.description": "這些選項可讓您限制觀眾使用特定功能。", + "app.lock-viewers.description": "這些選項可以讓您限制觀眾使用特定功能。", "app.lock-viewers.featuresLable": "功能", "app.lock-viewers.lockStatusLabel": "狀態", - "app.lock-viewers.webcamLabel": "分享攝影機", - "app.lock-viewers.otherViewersWebcamLabel": "查看其他觀眾的攝影機", + "app.lock-viewers.webcamLabel": "分享網路攝影機", + "app.lock-viewers.otherViewersWebcamLabel": "查看其他觀眾的網路攝影機", "app.lock-viewers.microphoneLable": "分享麥克風", "app.lock-viewers.PublicChatLabel": "發送公開聊天訊息", "app.lock-viewers.PrivateChatLable": "發送私人聊天訊息", - "app.lock-viewers.notesLabel": "編輯共享筆記", - "app.lock-viewers.userListLabel": "在使用者列表中查看其他觀眾", - "app.lock-viewers.ariaTitle": "鎖定觀眾設定視窗", - "app.lock-viewers.button.apply": "應用", + "app.lock-viewers.notesLabel": "編輯共用筆記", + "app.lock-viewers.userListLabel": "在使用者清單中查看其他觀眾", + "app.lock-viewers.ariaTitle": "鎖定觀眾設定對話框", + "app.lock-viewers.button.apply": "套用", "app.lock-viewers.button.cancel": "取消", "app.lock-viewers.locked": "已鎖定", "app.lock-viewers.hideViewersCursor": "查看其他觀眾的游標", - "app.guest-policy.ariaTitle": "訪客政策設定視窗", + "app.lock-viewers.hideAnnotationsLabel": "查看其他觀眾的註解", + "app.guest-policy.ariaTitle": "訪客政策設定對話框", "app.guest-policy.title": "訪客政策", "app.guest-policy.description": "更改會議訪客政策設定", - "app.guest-policy.button.askModerator": "向主持人詢問", + "app.guest-policy.button.askModerator": "請求主持人", "app.guest-policy.button.alwaysAccept": "始終接受", "app.guest-policy.button.alwaysDeny": "始終拒絕", "app.guest-policy.policyBtnDesc": "設定會議訪客政策", - "app.connection-status.ariaTitle": "連線狀態視窗", + "app.guest-policy.feedbackMessage": "訪客政策現在是:", + "app.connection-status.ariaTitle": "連線狀態對話框", "app.connection-status.title": "連線狀態", "app.connection-status.description": "查看使用者的連線狀態", "app.connection-status.empty": "目前沒有報告的連線問題", @@ -919,19 +980,19 @@ "app.connection-status.label": "連線狀態", "app.connection-status.settings": "調整您的設定", "app.connection-status.no": "否", - "app.connection-status.notification": "檢測到您的連線中斷", + "app.connection-status.notification": "偵測到您的連線中斷", "app.connection-status.offline": "離線", "app.connection-status.clientNotRespondingWarning": "客戶端未回應", "app.connection-status.audioUploadRate": "音訊上傳速率", "app.connection-status.audioDownloadRate": "音訊下載速率", - "app.connection-status.videoUploadRate": "視訊上傳速率", - "app.connection-status.videoDownloadRate": "視訊下載速率", + "app.connection-status.videoUploadRate": "影像上傳速率", + "app.connection-status.videoDownloadRate": "影像下載速率", "app.connection-status.lostPackets": "遺失封包", - "app.connection-status.usingTurn": "使用 TURN", + "app.connection-status.usingTurn": "正在使用 TURN", "app.connection-status.yes": "是", - "app.connection-status.connectionStats": "連線統計資料", - "app.connection-status.myLogs": "我的記錄", - "app.connection-status.sessionLogs": "會議記錄", + "app.connection-status.connectionStats": "連線統計", + "app.connection-status.myLogs": "我的日誌", + "app.connection-status.sessionLogs": "會議日誌", "app.connection-status.next": "下一頁", "app.connection-status.prev": "上一頁", "app.learning-dashboard.label": "學習分析儀表板", @@ -939,16 +1000,16 @@ "app.learning-dashboard.clickHereToOpen": "開啟學習分析儀表板", "app.recording.startTitle": "開始錄製", "app.recording.stopTitle": "暫停錄製", - "app.recording.resumeTitle": "繼續錄製", - "app.recording.startDescription": "您可以稍後再次選擇錄製按鈕以暫停錄製。", - "app.recording.stopDescription": "您確定要暫停錄製嗎?您可以再次選擇錄製按鈕來繼續錄製。", + "app.recording.resumeTitle": "恢復錄製", + "app.recording.startDescription": "您稍後可以再次選擇錄製按鈕以暫停錄製。", + "app.recording.stopDescription": "您確定要暫停錄製嗎?您可以再次選擇錄製按鈕來恢復錄製。", "app.recording.notify.title": "錄製已開始", - "app.recording.notify.description": "基於本次會議剩餘的內容,將提供一個錄製版本。", + "app.recording.notify.description": "根據此會話的其餘部分,將提供錄製", "app.recording.notify.continue": "繼續", "app.recording.notify.leave": "離開會議", "app.recording.notify.continueLabel" : "接受錄製並繼續", "app.recording.notify.leaveLabel" : "不接受錄製並離開會議", - "app.videoPreview.cameraLabel": "相機", + "app.videoPreview.cameraLabel": "攝影機", "app.videoPreview.profileLabel": "品質", "app.videoPreview.quality.low": "低", "app.videoPreview.quality.medium": "中", @@ -956,188 +1017,195 @@ "app.videoPreview.quality.hd": "高清", "app.videoPreview.cancelLabel": "取消", "app.videoPreview.closeLabel": "關閉", - "app.videoPreview.findingWebcamsLabel": "正在尋找攝像頭", + "app.videoPreview.findingWebcamsLabel": "正在尋找網路攝影機", "app.videoPreview.startSharingLabel": "開始分享", "app.videoPreview.stopSharingLabel": "停止分享", - "app.videoPreview.stopSharingAllLabel": "停止所有", - "app.videoPreview.sharedCameraLabel": "此相機已在分享中", - "app.videoPreview.webcamOptionLabel": "選擇攝像頭", - "app.videoPreview.webcamPreviewLabel": "攝像頭預覽", - "app.videoPreview.webcamSettingsTitle": "攝像頭設定", - "app.videoPreview.webcamEffectsTitle": "攝像頭視覺效果", + "app.videoPreview.stopSharingAllLabel": "全部停止", + "app.videoPreview.sharedCameraLabel": "此攝影機已經在分享中", + "app.videoPreview.webcamOptionLabel": "選擇網路攝影機", + "app.videoPreview.webcamPreviewLabel": "網路攝影機預覽", + "app.videoPreview.webcamSettingsTitle": "網路攝影機設定", + "app.videoPreview.webcamEffectsTitle": "網路攝影機視覺效果", + "app.videoPreview.cameraAsContentSettingsTitle": "呈現攝影機", "app.videoPreview.webcamVirtualBackgroundLabel": "虛擬背景設定", "app.videoPreview.webcamVirtualBackgroundDisabledLabel": "此設備不支援虛擬背景", - "app.videoPreview.webcamNotFoundLabel": "找不到攝像頭", - "app.videoPreview.profileNotFoundLabel": "沒有支援的攝像頭配置", + "app.videoPreview.webcamNotFoundLabel": "找不到網路攝影機", + "app.videoPreview.profileNotFoundLabel": "無支援的攝影機設定", "app.videoPreview.brightness": "亮度", - "app.videoPreview.wholeImageBrightnessLabel": "整張圖片", - "app.videoPreview.wholeImageBrightnessDesc": "將亮度應用於流和背景圖片", - "app.videoPreview.sliderDesc": "增加或降低亮度水平", - "app.video.joinVideo": "分享攝像頭", - "app.video.connecting": "正在開始分享攝像頭...", - "app.video.leaveVideo": "停止分享攝像頭", - "app.video.videoSettings": "視頻設定", + "app.videoPreview.wholeImageBrightnessLabel": "整體影像", + "app.videoPreview.wholeImageBrightnessDesc": "將亮度應用於串流和背景影像", + "app.videoPreview.sliderDesc": "增加或減少亮度級別", + "app.video.joinVideo": "分享網路攝影機", + "app.video.connecting": "正在啟動網路攝影機分享...", + "app.video.leaveVideo": "停止分享網路攝影機", + "app.video.videoSettings": "網路攝影機設定", "app.video.visualEffects": "視覺效果", - "app.video.advancedVideo": "開啟高級設定", - "app.video.iceCandidateError": "添加 ICE candidate 時出錯", - "app.video.iceConnectionStateError": "連接失敗(ICE 錯誤 1107)", - "app.video.permissionError": "分享攝像頭時出錯。請檢查權限", - "app.video.sharingError": "分享攝像頭時出錯", - "app.video.abortError": "未知問題導致無法使用攝像頭", - "app.video.overconstrainedError": "您的攝像頭不支援此品質配置", - "app.video.securityError": "您的瀏覽器已禁用攝像頭使用。請嘗試使用其他瀏覽器", - "app.video.typeError": "無效的攝像頭品質配置。請聯繫您的管理員", - "app.video.notFoundError": "找不到攝像頭。請確保已連接攝像頭", - "app.video.notAllowed": "需要授予訪問攝像頭的權限", - "app.video.notSupportedError": "瀏覽器不支援。請嘗試使用其他瀏覽器或設備", - "app.video.notReadableError": "無法獲取攝像頭視頻。請確保沒有其他程序正在使用攝像頭", - "app.video.timeoutError": "瀏覽器未及時響應。", - "app.video.genericError": "設備出現未知錯誤({0})", - "app.video.inactiveError": "您的攝像頭意外停止工作。請檢查瀏覽器的權限設置", - "app.video.mediaTimedOutError": "攝像頭流已中斷。請重新分享", - "app.video.mediaFlowTimeout1020": "媒體無法到達服務器(錯誤 1020)", - "app.video.suggestWebcamLock": "強制鎖定觀眾攝像頭設置?", - "app.video.suggestWebcamLockReason": "(這將改善會議的穩定性)", + "app.video.advancedVideo": "開啟進階設定", + "app.video.iceCandidateError": "新增 ICE 候選者時發生錯誤", + "app.video.iceConnectionStateError": "連線失敗(ICE 錯誤 1107)", + "app.video.permissionError": "分享網路攝影機時發生錯誤。請檢查權限", + "app.video.sharingError": "分享網路攝影機時發生錯誤", + "app.video.abortError": "無法使用攝影機,發生未知問題", + "app.video.overconstrainedError": "您的攝影機不支援此品質設定", + "app.video.securityError": "您的瀏覽器已禁用攝影機使用。請嘗試不同的瀏覽器", + "app.video.typeError": "無效的攝影機品質設定。請聯繫您的管理員", + "app.video.notFoundError": "找不到網路攝影機。請確保有一台攝影機已連接", + "app.video.notAllowed": "需要授權訪問網路攝影機", + "app.video.notSupportedError": "不支援的瀏覽器。請嘗試使用其他瀏覽器或裝置", + "app.video.notReadableError": "無法獲取攝影機影像。請確保其他程式未使用該攝影機 ", + "app.video.timeoutError": "瀏覽器未在時間內回應。", + "app.video.genericError": "使用裝置發生未知錯誤({0})", + "app.video.inactiveError": "您的攝影機意外停止。請檢查瀏覽器權限", + "app.video.mediaTimedOutError": "您的攝影機串流已中斷。請重新分享", + "app.video.mediaFlowTimeout1020": "媒體無法達到伺服器(錯誤 1020)", + "app.video.suggestWebcamLock": "強制鎖定觀看者攝影機設定?", + "app.video.suggestWebcamLockReason": "(這將提高會議的穩定性)", "app.video.enable": "啟用", "app.video.cancel": "取消", "app.video.swapCam": "交換", - "app.video.swapCamDesc": "交換攝像頭的方向", - "app.video.videoLocked": "攝像頭分享已鎖定", - "app.video.videoButtonDesc": "分享攝像頭", - "app.video.videoMenu": "視頻選單", - "app.video.videoMenuDisabled": "視頻選單:攝像頭在設定中被禁用", - "app.video.videoMenuDesc": "打開視頻選單下拉", - "app.video.pagination.prevPage": "查看上一個視頻", - "app.video.pagination.nextPage": "查看下一個視頻", - "app.video.clientDisconnected": "由於連接問題,無法分享攝像頭", + "app.video.swapCamDesc": "交換攝影機方向", + "app.video.videoLocked": "攝影機分享已鎖定", + "app.video.videoButtonDesc": "分享攝影機", + "app.video.videoMenu": "攝影機選單", + "app.video.videoMenuDisabled": "攝影機選單已在設定中停用", + "app.video.videoMenuDesc": "打開攝影機選單下拉式選單", + "app.video.pagination.prevPage": "查看上一個影片", + "app.video.pagination.nextPage": "查看下一個影片", + "app.video.clientDisconnected": "由於連線問題,無法分享攝影機", "app.video.virtualBackground.none": "無", "app.video.virtualBackground.blur": "模糊", "app.video.virtualBackground.home": "家庭", - "app.video.virtualBackground.board": "白板", + "app.video.virtualBackground.board": "黑板", "app.video.virtualBackground.coffeeshop": "咖啡廳", "app.video.virtualBackground.background": "背景", "app.video.virtualBackground.backgroundWithIndex": "背景 {0}", "app.video.virtualBackground.custom": "從電腦上傳", - "app.video.virtualBackground.remove": "移除添加的圖片", - "app.video.virtualBackground.genericError": "應用攝像頭效果失敗。請重試。", - "app.video.virtualBackground.camBgAriaDesc": "將攝像頭虛擬背景設置為 {0}", - "app.video.virtualBackground.maximumFileSizeExceeded": "超出最大文件大小限制({0}MB)", - "app.video.virtualBackground.typeNotAllowed": "不允許的文件類型", - "app.video.virtualBackground.errorOnRead": "讀取文件時出現問題", + "app.video.virtualBackground.remove": "移除已添加的圖片", + "app.video.virtualBackground.genericError": "套用攝影機效果失敗。請再試一次。", + "app.video.virtualBackground.camBgAriaDesc": "將攝影機虛擬背景設定為 {0}", + "app.video.virtualBackground.maximumFileSizeExceeded": "超出最大文件大小。 ({0}MB)", + "app.video.virtualBackground.typeNotAllowed": "不允許的檔案類型。", + "app.video.virtualBackground.errorOnRead": "讀取檔案時出現問題。", "app.video.virtualBackground.uploaded": "已上傳", "app.video.virtualBackground.uploading": "正在上傳...", "app.video.virtualBackground.button.customDesc": "添加新的虛擬背景圖片", - "app.video.camCapReached": "您無法分享更多攝像頭", - "app.video.meetingCamCapReached": "會議已達到同時攝像頭的限制", - "app.video.dropZoneLabel": "拖放到此處", - "app.fullscreenButton.label": "將 {0} 設為全屏", - "app.fullscreenUndoButton.label": "取消 {0} 全屏", - "app.switchButton.expandLabel": "展開屏幕共享視頻", - "app.switchButton.shrinkLabel": "縮小屏幕共享視頻", - "app.sfu.mediaServerConnectionError2000": "無法連接到媒體服務器(錯誤 2000)", - "app.sfu.mediaServerOffline2001": "媒體服務器離線。請稍後再試(錯誤 2001)", - "app.sfu.mediaServerNoResources2002": "媒體服務器沒有可用資源(錯誤 2002)", - "app.sfu.mediaServerRequestTimeout2003": "媒體服務器請求超時(錯誤 2003)", - "app.sfu.serverIceGatheringFailed2021": "媒體服務器無法收集連接候選者(ICE 錯誤 2021)", - "app.sfu.serverIceGatheringFailed2022": "媒體服務器連接失敗(ICE 錯誤 2022)", - "app.sfu.mediaGenericError2200": "媒體服務器無法處理請求(錯誤 2200)", - "app.sfu.invalidSdp2202":"客戶端生成了無效的媒體請求(SDP 錯誤 2202)", - "app.sfu.noAvailableCodec2203": "服務器找不到合適的編解碼器(錯誤 2203)", + "app.video.camCapReached": "您無法分享更多攝影機", + "app.video.meetingCamCapReached": "會議已達到同時攝影機上限", + "app.video.dropZoneLabel": "拖放至此處", + "app.fullscreenButton.label": "使 {0} 全屏", + "app.fullscreenUndoButton.label": "撤銷 {0} 全屏", + "app.switchButton.expandLabel": "展開屏幕分享視頻", + "app.switchButton.shrinkLabel": "縮小屏幕分享視頻", + "app.sfu.mediaServerConnectionError2000": "無法連接到媒體伺服器(錯誤 2000)", + "app.sfu.mediaServerOffline2001": "媒體伺服器離線。請稍後再試(錯誤 2001)", + "app.sfu.mediaServerNoResources2002": "媒體伺服器沒有可用資源(錯誤 2002)", + "app.sfu.mediaServerRequestTimeout2003": "媒體伺服器請求超時(錯誤 2003)", + "app.sfu.serverIceGatheringFailed2021": "媒體伺服器無法收集連接候選者(ICE 錯誤 2021)", + "app.sfu.serverIceGatheringFailed2022": "媒體伺服器連接失敗(ICE 錯誤 2022)", + "app.sfu.mediaGenericError2200": "媒體伺服器無法處理請求(錯誤 2200)", + "app.sfu.invalidSdp2202":"用戶生成了無效的媒體請求(SDP 錯誤 2202)", + "app.sfu.noAvailableCodec2203": "伺服器找不到合適的編解碼器(錯誤 2203)", "app.meeting.endNotification.ok.label": "確定", - "app.whiteboard.annotations.poll": "已發佈投票結果", + "app.whiteboard.annotations.poll": "投票結果已發佈", "app.whiteboard.annotations.pollResult": "投票結果", "app.whiteboard.annotations.noResponses": "無回應", "app.whiteboard.annotations.notAllowed": "您無權進行此更改", - "app.whiteboard.annotations.numberExceeded": "注釋數量超過限制({0})", + "app.whiteboard.annotations.numberExceeded": "註釋數量超過限制({0})", "app.whiteboard.toolbar.tools": "工具", - "app.whiteboard.toolbar.tools.hand": "平移", + "app.whiteboard.toolbar.tools.hand": "手指", "app.whiteboard.toolbar.tools.pencil": "鉛筆", "app.whiteboard.toolbar.tools.rectangle": "矩形", "app.whiteboard.toolbar.tools.triangle": "三角形", "app.whiteboard.toolbar.tools.ellipse": "橢圓", "app.whiteboard.toolbar.tools.line": "直線", - "app.whiteboard.toolbar.tools.text": "文本", - "app.whiteboard.toolbar.thickness": "繪圖粗細", - "app.whiteboard.toolbar.thicknessDisabled": "繪圖粗細已禁用", + "app.whiteboard.toolbar.tools.text": "文字", + "app.whiteboard.toolbar.thickness": "畫筆粗細", + "app.whiteboard.toolbar.thicknessDisabled": "畫筆粗細已停用", "app.whiteboard.toolbar.color": "顏色", - "app.whiteboard.toolbar.colorDisabled": "顏色已禁用", + "app.whiteboard.toolbar.colorDisabled": "顏色已停用", "app.whiteboard.toolbar.color.black": "黑色", "app.whiteboard.toolbar.color.white": "白色", "app.whiteboard.toolbar.color.red": "紅色", "app.whiteboard.toolbar.color.orange": "橙色", - "app.whiteboard.toolbar.color.eletricLime": "電子青檸色", - "app.whiteboard.toolbar.color.lime": "青檸色", - "app.whiteboard.toolbar.color.cyan": "青色", - "app.whiteboard.toolbar.color.dodgerBlue": "道奇藍色", + "app.whiteboard.toolbar.color.eletricLime": "亮青", + "app.whiteboard.toolbar.color.lime": "青色", + "app.whiteboard.toolbar.color.cyan": "藍綠色", + "app.whiteboard.toolbar.color.dodgerBlue": "深藍色", "app.whiteboard.toolbar.color.blue": "藍色", - "app.whiteboard.toolbar.color.violet": "紫色", - "app.whiteboard.toolbar.color.magenta": "洋紅色", + "app.whiteboard.toolbar.color.violet": "紫羅蘭色", + "app.whiteboard.toolbar.color.magenta": "品紅色", "app.whiteboard.toolbar.color.silver": "銀色", - "app.whiteboard.toolbar.undo": "撤銷注釋", - "app.whiteboard.toolbar.clear": "清除所有注釋", - "app.whiteboard.toolbar.clearConfirmation": "確定要清除所有注釋嗎?", - "app.whiteboard.toolbar.multiUserOn": "打開多用戶白板", + "app.whiteboard.toolbar.undo": "還原註釋", + "app.whiteboard.toolbar.clear": "清除所有註釋", + "app.whiteboard.toolbar.clearConfirmation": "您確定要清除所有註釋嗎?", + "app.whiteboard.toolbar.multiUserOn": "開啟多用戶白板", "app.whiteboard.toolbar.multiUserOff": "關閉多用戶白板", - "app.whiteboard.toolbar.palmRejectionOn": "打開手掌屏蔽", - "app.whiteboard.toolbar.palmRejectionOff": "關閉手掌屏蔽", + "app.whiteboard.toolbar.palmRejectionOn": "開啟手掌過濾", + "app.whiteboard.toolbar.palmRejectionOff": "關閉手掌過濾", "app.whiteboard.toolbar.fontSize": "字體大小列表", "app.whiteboard.toolbarAriaLabel": "演示工具", - "app.feedback.title": "您已退出會議", - "app.feedback.subtitle": "我們希望聽到您對BigBlueButton的使用體驗(選填)", - "app.feedback.textarea": "如何讓BigBlueButton變得更好?", + "app.feedback.title": "您已登出會議", + "app.feedback.subtitle": "我們很樂意聽取您對 BigBlueButton 的體驗(選填)", + "app.feedback.textarea": "您如何能夠讓 BigBlueButton 更好?", "app.feedback.sendFeedback": "發送反饋", "app.feedback.sendFeedbackDesc": "發送反饋並離開會議", "app.videoDock.webcamMirrorLabel": "鏡像", - "app.videoDock.webcamMirrorDesc": "鏡像選中的攝像頭", - "app.videoDock.webcamFocusLabel": "聚焦", - "app.videoDock.webcamFocusDesc": "聚焦選中的攝像頭", - "app.videoDock.webcamUnfocusLabel": "取消聚焦", - "app.videoDock.webcamUnfocusDesc": "取消聚焦選中的攝像頭", - "app.videoDock.webcamPinLabel": "固定", - "app.videoDock.webcamPinDesc": "固定選中的攝像頭", - "app.videoDock.webcamFullscreenLabel": "全屏顯示攝像頭", - "app.videoDock.webcamSqueezedButtonLabel": "攝像頭選項", - "app.videoDock.webcamUnpinLabel": "取消固定", - "app.videoDock.webcamUnpinLabelDisabled": "只有主持人可以取消固定使用者", - "app.videoDock.webcamUnpinDesc": "取消固定選取的攝影機", - "app.videoDock.autoplayBlockedDesc": "我們需要您的許可才能顯示其他使用者的攝影機。", + "app.videoDock.webcamMirrorDesc": "鏡像選定的攝影機", + "app.videoDock.webcamFocusLabel": "對焦", + "app.videoDock.webcamFocusDesc": "對焦選定的攝影機", + "app.videoDock.webcamUnfocusLabel": "取消對焦", + "app.videoDock.webcamUnfocusDesc": "取消對焦選定的攝影機", + "app.videoDock.webcamDisableLabel": "停用自視", + "app.videoDock.webcamDisableLabelAllCams": "停用自視(所有攝影機)", + "app.videoDock.webcamEnableLabel": "啟用自視", + "app.videoDock.webcamDisableDesc": "自視已停用", + "app.videoDock.webcamPinLabel": "鎖定", + "app.videoDock.webcamPinDesc": "鎖定選定的攝影機", + "app.videoDock.webcamFullscreenLabel": "攝影機全屏", + "app.videoDock.webcamSqueezedButtonLabel": "攝影機選項", + "app.videoDock.webcamUnpinLabel": "取消鎖定", + "app.videoDock.webcamUnpinLabelDisabled": "只有主持人可以取消鎖定使用者", + "app.videoDock.webcamUnpinDesc": "取消鎖定選定的攝影機", + "app.videoDock.autoplayBlockedDesc": "我們需要您的許可才能顯示其他用戶的攝影機。", "app.videoDock.autoplayAllowLabel": "查看攝影機", "app.createBreakoutRoom.title": "分組討論室", "app.createBreakoutRoom.ariaTitle": "隱藏分組討論室", "app.createBreakoutRoom.breakoutRoomLabel": "分組討論室 {0}", - "app.createBreakoutRoom.askToJoin": "請求加入", - "app.createBreakoutRoom.generatingURL": "產生連結", - "app.createBreakoutRoom.generatingURLMessage": "我們正在為選定的分組討論室產生加入連結。這可能需要幾秒鐘...", + "app.createBreakoutRoom.askToJoin": "要求加入", + "app.createBreakoutRoom.generatingURL": "生成 URL", + "app.createBreakoutRoom.generatingURLMessage": "我們正在為所選的分組討論室生成加入 URL。可能需要幾秒鐘...", "app.createBreakoutRoom.duration": "持續時間 {0}", "app.createBreakoutRoom.room": "房間 {0}", - "app.createBreakoutRoom.notAssigned": "未分配 ({0})", + "app.createBreakoutRoom.notAssigned": "未分配({0})", "app.createBreakoutRoom.join": "加入房間", "app.createBreakoutRoom.joinAudio": "加入音訊", "app.createBreakoutRoom.returnAudio": "返回音訊", "app.createBreakoutRoom.alreadyConnected": "已在房間中", "app.createBreakoutRoom.confirm": "建立", - "app.createBreakoutRoom.record": "錄製", + "app.createBreakoutRoom.record": "記錄", "app.createBreakoutRoom.numberOfRooms": "房間數量", "app.createBreakoutRoom.durationInMinutes": "持續時間(分鐘)", "app.createBreakoutRoom.randomlyAssign": "隨機分配", - "app.createBreakoutRoom.randomlyAssignDesc": "隨機將使用者分配到分組討論室", - "app.createBreakoutRoom.resetAssignments": "重設分配", - "app.createBreakoutRoom.resetAssignmentsDesc": "重設所有使用者的房間分配", + "app.createBreakoutRoom.randomlyAssignDesc": "隨機分配使用者到分組討論室", + "app.createBreakoutRoom.resetAssignments": "取消分配使用者", + "app.createBreakoutRoom.resetAssignmentsDesc": "重設所有使用者房間分配", "app.createBreakoutRoom.endAllBreakouts": "結束所有分組討論室", "app.createBreakoutRoom.chatTitleMsgAllRooms": "所有房間", - "app.createBreakoutRoom.msgToBreakoutsSent": "訊息已傳送至 {0} 個分組討論室", + "app.createBreakoutRoom.msgToBreakoutsSent": "訊息已發送至 {0} 個分組討論室", "app.createBreakoutRoom.roomName": "{0}(房間 - {1})", "app.createBreakoutRoom.doneLabel": "完成", "app.createBreakoutRoom.nextLabel": "下一步", - "app.createBreakoutRoom.minusRoomTime": "將分組討論室時間減少到", - "app.createBreakoutRoom.addRoomTime": "將分組討論室時間增加到", - "app.createBreakoutRoom.addParticipantLabel": "+ 新增參與者", - "app.createBreakoutRoom.freeJoin": "允許使用者選擇加入的分組討論室", - "app.createBreakoutRoom.captureNotes": "在分組討論室結束時捕獲共享筆記", - "app.createBreakoutRoom.captureSlides": "在分組討論室結束時捕獲白板", - "app.createBreakoutRoom.leastOneWarnBreakout": "您必須至少將一位使用者放置在分組討論室中。", - "app.createBreakoutRoom.minimumDurationWarnBreakout": "分組討論室的最短持續時間為 {0} 分鐘。", - "app.createBreakoutRoom.modalDesc": "提示:您可以拖放使用者的名稱以將其指派到特定的分組討論室。", + "app.createBreakoutRoom.minusRoomTime": "減少分組討論室時間至", + "app.createBreakoutRoom.addRoomTime": "增加分組討論室時間至", + "app.createBreakoutRoom.addParticipantLabel": "+ 添加參與者", + "app.createBreakoutRoom.freeJoin": "允許使用者選擇房間", + "app.createBreakoutRoom.manageRoomsLabel": "管理房間", + "app.createBreakoutRoom.captureNotes": "保存共享筆記", + "app.createBreakoutRoom.captureSlides": "保存白板", + "app.createBreakoutRoom.sendInvitationToMods": "發送邀請給指定的主持人", + "app.createBreakoutRoom.leastOneWarnBreakout": "您必須至少將一名使用者放入分組房間。", + "app.createBreakoutRoom.minimumDurationWarnBreakout": "分組房間的最短持續時間為 {0} 分鐘。", + "app.createBreakoutRoom.modalDesc": "按照以下步驟在您的會議中創建分組房間。要將參與者添加到房間,只需將其名稱拖動到所需的房間。", "app.createBreakoutRoom.roomTime": "{0} 分鐘", "app.createBreakoutRoom.numberOfRoomsError": "房間數量無效。", "app.createBreakoutRoom.duplicatedRoomNameError": "房間名稱不能重複。", @@ -1145,65 +1213,64 @@ "app.createBreakoutRoom.setTimeInMinutes": "設定持續時間(分鐘)", "app.createBreakoutRoom.setTimeLabel": "套用", "app.createBreakoutRoom.setTimeCancel": "取消", - "app.createBreakoutRoom.setTimeHigherThanMeetingTimeError": "分組討論室的持續時間不能超過會議剩餘時間。", - "app.createBreakoutRoom.roomNameInputDesc": "更新分組討論室名稱", - "app.createBreakoutRoom.movedUserLabel": "將 {0} 移至 {1} 房間", - "app.updateBreakoutRoom.modalDesc": "要更新或邀請使用者,只需將他們拖放到所需的房間中。", + "app.createBreakoutRoom.setTimeHigherThanMeetingTimeError": "分組房間的持續時間不能超過會議剩餘時間。", + "app.createBreakoutRoom.roomNameInputDesc": "更新分組房間名稱", + "app.createBreakoutRoom.movedUserLabel": "將 {0} 移動到房間 {1}", + "app.updateBreakoutRoom.modalDesc": "要更新或邀請使用者,只需將其拖動到所需的房間。", "app.updateBreakoutRoom.cancelLabel": "取消", - "app.updateBreakoutRoom.title": "更新分組討論室", + "app.updateBreakoutRoom.title": "更新分組房間", "app.updateBreakoutRoom.confirm": "套用", - "app.updateBreakoutRoom.userChangeRoomNotification": "您已移至 {0} 房間。", + "app.updateBreakoutRoom.userChangeRoomNotification": "您已被移至房間 {0}。", "app.smartMediaShare.externalVideo": "外部影片(s)", - "app.update.resetRoom": "重設使用者所在房間", + "app.update.resetRoom": "重置使用者房間", "app.externalVideo.start": "分享新影片", "app.externalVideo.title": "分享外部影片", "app.externalVideo.input": "外部影片網址", - "app.externalVideo.urlInput": "新增影片網址", - "app.externalVideo.urlError": "不支援此影片網址", + "app.externalVideo.urlInput": "添加影片網址", + "app.externalVideo.urlError": "此影片網址不受支援", "app.externalVideo.close": "關閉", "app.externalVideo.autoPlayWarning": "播放影片以啟用媒體同步", - "app.externalVideo.refreshLabel": "重新整理影片播放器", + "app.externalVideo.refreshLabel": "刷新影片播放器", "app.externalVideo.fullscreenLabel": "影片播放器", - "app.externalVideo.noteLabel": "注意:分享的外部影片將不會出現在錄製中。支援 YouTube、Vimeo、Instructure Media、Twitch、Dailymotion 和媒體檔案網址 ( e.g. https://example.com/xy.mp4 )。", + "app.externalVideo.noteLabel": "備註:共享的外部影片不會顯示在錄製中。支援 YouTube、Vimeo、Instructure Media、Twitch、Dailymotion 和媒體檔案網址 ( 例如 https://example.com/xy.mp4 )。", "app.externalVideo.subtitlesOn": "關閉字幕", - "app.externalVideo.subtitlesOff": "開啟字幕(如果有提供)", + "app.externalVideo.subtitlesOff": "開啟字幕(如果可用)", "app.actionsBar.actionsDropdown.shareExternalVideo": "分享外部影片", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "停止分享外部影片", - "app.legacy.unsupportedBrowser": "您正在使用不支援的瀏覽器。請使用 {0} 或 {1} 以獲得完整支援。", - "app.legacy.upgradeBrowser": "您正在使用的是舊版支援的瀏覽器。請升級您的瀏覽器以獲得完整支援。", - "app.legacy.criosBrowser": "在 iOS 上,請使用 Safari 以獲得完整支援。", - "app.debugWindow.windowTitle": "偵錯", + "app.legacy.unsupportedBrowser": "看起來您正在使用一個不受支援的瀏覽器。請使用 {0} 或 {1} 以獲得完整支援。", + "app.legacy.upgradeBrowser": "看起來您正在使用支援的瀏覽器的舊版本。請升級您的瀏覽器以獲得完整支援。", + "app.legacy.criosBrowser": "在 iOS 上請使用 Safari 以獲得完整支援。", + "app.debugWindow.windowTitle": "除錯", "app.debugWindow.form.userAgentLabel": "使用者代理", "app.debugWindow.form.button.copy": "複製", - "app.debugWindow.form.enableAutoarrangeLayoutLabel": "啟用自動排列佈局", - "app.debugWindow.form.enableAutoarrangeLayoutDescription": "(如果您拖放或調整攝影機區域,將停用此功能)", - "app.debugWindow.form.chatLoggerLabel": "測試聊天記錄層級", + "app.debugWindow.form.enableAutoarrangeLayoutLabel": "啟用自動排列版面", + "app.debugWindow.form.enableAutoarrangeLayoutDescription": "(如果您拖動或調整網路攝影機區域,它將被停用)", + "app.debugWindow.form.chatLoggerLabel": "測試聊天記錄器層級", "app.debugWindow.form.button.apply": "套用", - "app.layout.modal.title": "佈局", - "app.layout.modal.confirm": "確認", - "app.layout.modal.cancel": "取消", - "app.layout.modal.layoutLabel": "選擇您的佈局", - "app.layout.modal.keepPushingLayoutLabel": "將佈局套用給所有人", - "app.layout.modal.pushLayoutLabel": "傳送給所有人", - "app.layout.modal.layoutToastLabel": "佈局設定已變更", - "app.layout.modal.layoutSingular": "佈局", - "app.layout.modal.layoutBtnDesc": "將佈局設定為選擇的選項", - "app.layout.style.custom": "自訂", - "app.layout.style.smart": "智慧佈局", - "app.layout.style.presentationFocus": "焦點在演示", - "app.layout.style.videoFocus": "焦點在影片", - "app.layout.style.customPush": "自訂(傳送給所有人)", - "app.layout.style.smartPush": "智慧佈局(傳送給所有人)", - "app.layout.style.presentationFocusPush": "焦點在演示(傳送給所有人)", - "app.layout.style.videoFocusPush": "焦點在影片(傳送給所有人)", + "app.layout.modal.title": "版面配置", + "app.layout.modal.update": "更新", + "app.layout.modal.updateAll": "更新所有人", + "app.layout.modal.layoutLabel": "選擇您的版面配置", + "app.layout.modal.pushLayoutLabel": "推送給所有人", + "app.layout.modal.layoutToastLabel": "版面配置設定已變更", + "app.layout.modal.layoutSingular": "版面配置", + "app.layout.modal.layoutBtnDesc": "將版面配置設定為所選選項", + "app.layout.style.custom": "自定版面配置", + "app.layout.style.smart": "智能版面配置", + "app.layout.style.presentationFocus": "焦點在演示上", + "app.layout.style.videoFocus": "網格版面配置", + "app.layout.style.customPush": "自定(將版面配置推送給所有人)", + "app.layout.style.smartPush": "智能(將版面配置推送給所有人)", + "app.layout.style.presentationFocusPush": "焦點在演示上(將版面配置推送給所有人)", + "app.layout.style.videoFocusPush": "焦點在影片上(將版面配置推送給所有人)", "playback.button.about.aria": "關於", "playback.button.clear.aria": "清除搜尋", - "playback.button.close.aria": "關閉對話框", + "playback.button.close.aria": "關閉視窗", "playback.button.fullscreen.aria": "全螢幕內容", "playback.button.restore.aria": "還原內容", "playback.button.search.aria": "搜尋", "playback.button.section.aria": "側邊區域", - "playback.button.swap.aria": "切換內容", + "playback.button.swap.aria": "交換內容", "playback.button.theme.aria": "切換主題", "playback.error.wrapper.aria": "錯誤區域", "playback.loader.wrapper.aria": "載入區域", @@ -1218,28 +1285,28 @@ "playback.player.about.modal.shortcuts.seek.forward": "快進", "playback.player.about.modal.shortcuts.skip.next": "下一張投影片", "playback.player.about.modal.shortcuts.skip.previous": "上一張投影片", - "playback.player.about.modal.shortcuts.swap": "切換內容", + "playback.player.about.modal.shortcuts.swap": "交換內容", "playback.player.chat.message.poll.name": "投票結果", "playback.player.chat.message.poll.question": "問題", "playback.player.chat.message.poll.options": "選項", "playback.player.chat.message.poll.option.yes": "是", "playback.player.chat.message.poll.option.no": "否", "playback.player.chat.message.poll.option.abstention": "棄權", - "playback.player.chat.message.poll.option.true": "是", - "playback.player.chat.message.poll.option.false": "否", + "playback.player.chat.message.poll.option.true": "真", + "playback.player.chat.message.poll.option.false": "假", "playback.player.chat.message.video.name": "外部影片", "playback.player.chat.wrapper.aria": "聊天區域", "playback.player.notes.wrapper.aria": "筆記區域", - "playback.player.presentation.wrapper.aria": "投影片區域", + "playback.player.presentation.wrapper.aria": "演示區域", "playback.player.screenshare.wrapper.aria": "螢幕分享區域", "playback.player.search.modal.title": "搜尋", "playback.player.search.modal.subtitle": "尋找投影片內容", "playback.player.thumbnails.wrapper.aria": "縮圖區域", - "playback.player.webcams.wrapper.aria": "攝影機區域", + "playback.player.webcams.wrapper.aria": "網路攝影機區域", "app.learningDashboard.dashboardTitle": "學習分析儀表板", "app.learningDashboard.bigbluebuttonTitle": "BigBlueButton", - "app.learningDashboard.downloadSessionDataLabel": "下載會議資料", - "app.learningDashboard.lastUpdatedLabel": "最後更新於", + "app.learningDashboard.downloadSessionDataLabel": "下載會議數據", + "app.learningDashboard.lastUpdatedLabel": "上次更新於", "app.learningDashboard.sessionDataDownloadedLabel": "下載完成!", "app.learningDashboard.shareButton": "與他人分享", "app.learningDashboard.shareLinkCopied": "連結已成功複製!", @@ -1247,68 +1314,68 @@ "app.learningDashboard.indicators.meetingStatusEnded": "已結束", "app.learningDashboard.indicators.meetingStatusActive": "進行中", "app.learningDashboard.indicators.usersOnline": "活躍使用者", - "app.learningDashboard.indicators.usersTotal": "總使用者數量", + "app.learningDashboard.indicators.usersTotal": "總使用者數", "app.learningDashboard.indicators.polls": "投票", "app.learningDashboard.indicators.timeline": "時間軸", - "app.learningDashboard.indicators.activityScore": "活動得分", + "app.learningDashboard.indicators.activityScore": "活動分數", "app.learningDashboard.indicators.duration": "持續時間", "app.learningDashboard.userDetails.startTime": "開始時間", "app.learningDashboard.userDetails.endTime": "結束時間", "app.learningDashboard.userDetails.joined": "加入", - "app.learningDashboard.userDetails.category": "類別", - "app.learningDashboard.userDetails.average": "平均", + "app.learningDashboard.userDetails.category": "分類", + "app.learningDashboard.userDetails.average": "平均值", "app.learningDashboard.userDetails.activityPoints": "活動點數", "app.learningDashboard.userDetails.poll": "投票", "app.learningDashboard.userDetails.response": "回應", - "app.learningDashboard.userDetails.mostCommonAnswer": "最常見答案", + "app.learningDashboard.userDetails.mostCommonAnswer": "最常見的答案", "app.learningDashboard.userDetails.anonymousAnswer": "匿名投票", "app.learningDashboard.userDetails.talkTime": "發言時間", - "app.learningDashboard.userDetails.messages": "訊息數", - "app.learningDashboard.userDetails.emojis": "表情符號數", + "app.learningDashboard.userDetails.messages": "訊息", + "app.learningDashboard.userDetails.emojis": "表情符號", "app.learningDashboard.userDetails.raiseHands": "舉手次數", "app.learningDashboard.userDetails.pollVotes": "投票數", - "app.learningDashboard.userDetails.onlineIndicator": "線上時間 {0}", + "app.learningDashboard.userDetails.onlineIndicator": "{0} 的線上時間", "app.learningDashboard.usersTable.title": "概覽", "app.learningDashboard.usersTable.colOnline": "線上時間", "app.learningDashboard.usersTable.colTalk": "發言時間", - "app.learningDashboard.usersTable.colWebcam": "攝影機使用時間", + "app.learningDashboard.usersTable.colWebcam": "網路攝影機時間", "app.learningDashboard.usersTable.colMessages": "訊息數", "app.learningDashboard.usersTable.colEmojis": "表情符號數", "app.learningDashboard.usersTable.colRaiseHands": "舉手次數", - "app.learningDashboard.usersTable.colActivityScore": "活動得分", + "app.learningDashboard.usersTable.colActivityScore": "活動分數", "app.learningDashboard.usersTable.colStatus": "狀態", "app.learningDashboard.usersTable.userStatusOnline": "線上", "app.learningDashboard.usersTable.userStatusOffline": "離線", - "app.learningDashboard.usersTable.noUsers": "尚無使用者", - "app.learningDashboard.usersTable.name": "名稱", + "app.learningDashboard.usersTable.noUsers": "目前沒有使用者", + "app.learningDashboard.usersTable.name": "姓名", "app.learningDashboard.usersTable.moderator": "主持人", "app.learningDashboard.usersTable.pollVotes": "投票數", "app.learningDashboard.usersTable.join": "加入", "app.learningDashboard.usersTable.left": "離開", - "app.learningDashboard.usersTable.notAvailable": "不可用", + "app.learningDashboard.usersTable.notAvailable": "無資料", "app.learningDashboard.pollsTable.title": "投票", - "app.learningDashboard.pollsTable.anonymousAnswer": "匿名投票(答案顯示於最後一行)", + "app.learningDashboard.pollsTable.anonymousAnswer": "匿名投票(答案在最後一行)", "app.learningDashboard.pollsTable.anonymousRowName": "匿名", - "app.learningDashboard.pollsTable.noPollsCreatedHeading": "尚未建立投票", - "app.learningDashboard.pollsTable.noPollsCreatedMessage": "當投票傳送給使用者後,他們的結果將顯示於此清單中。", + "app.learningDashboard.pollsTable.noPollsCreatedHeading": "尚未建立任何投票", + "app.learningDashboard.pollsTable.noPollsCreatedMessage": "一旦投票已發送給用戶,他們的結果將顯示在此列表中。", "app.learningDashboard.pollsTable.answerTotal": "總計", - "app.learningDashboard.pollsTable.userLabel": "使用者", + "app.learningDashboard.pollsTable.userLabel": "用戶", "app.learningDashboard.statusTimelineTable.title": "時間軸", - "app.learningDashboard.statusTimelineTable.thumbnail": "簡報縮圖", - "app.learningDashboard.statusTimelineTable.presentation": "簡報", - "app.learningDashboard.statusTimelineTable.pageNumber": "頁數", + "app.learningDashboard.statusTimelineTable.thumbnail": "演示文稿縮圖", + "app.learningDashboard.statusTimelineTable.presentation": "演示文稿", + "app.learningDashboard.statusTimelineTable.pageNumber": "頁碼", "app.learningDashboard.statusTimelineTable.setAt": "設定於", "app.learningDashboard.errors.invalidToken": "無效的會話令牌", - "app.learningDashboard.errors.dataUnavailable": "數據不再可用", - "mobileApp.portals.list.empty.addFirstPortal.label": "使用上方的按鈕添加您的第一個門戶,", + "app.learningDashboard.errors.dataUnavailable": "資料已不可用", + "mobileApp.portals.list.empty.addFirstPortal.label": "使用上面的按鈕添加您的第一個入口,", "mobileApp.portals.list.empty.orUseOurDemoServer.label": "或使用我們的演示伺服器。", - "mobileApp.portals.list.add.button.label": "添加門戶", - "mobileApp.portals.fields.name.label": "門戶名稱", - "mobileApp.portals.fields.name.placeholder": "BigBlueButton演示", - "mobileApp.portals.fields.url.label": "伺服器URL", + "mobileApp.portals.list.add.button.label": "添加入口", + "mobileApp.portals.fields.name.label": "入口名稱", + "mobileApp.portals.fields.name.placeholder": "BigBlueButton 演示", + "mobileApp.portals.fields.url.label": "伺服器網址", "mobileApp.portals.addPortalPopup.confirm.button.label": "保存", - "mobileApp.portals.drawerNavigation.button.label": "門戶", - "mobileApp.portals.addPortalPopup.validation.emptyFields": "必填項目", - "mobileApp.portals.addPortalPopup.validation.portalNameAlreadyExists": "名稱已被使用", - "mobileApp.portals.addPortalPopup.validation.urlInvalid": "加載頁面時發生錯誤 - 請檢查URL和網絡連接" + "mobileApp.portals.drawerNavigation.button.label": "入口", + "mobileApp.portals.addPortalPopup.validation.emptyFields": "必填欄位", + "mobileApp.portals.addPortalPopup.validation.portalNameAlreadyExists": "名稱已在使用中", + "mobileApp.portals.addPortalPopup.validation.urlInvalid": "嘗試加載頁面時出錯 - 請檢查網址和網路連接" } From 0c0182db6b83f0a8f86f093c0d12303ecf7e9e52 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Wed, 9 Aug 2023 14:02:01 -0400 Subject: [PATCH 167/252] Translate en.json in fr (#18500) 100% translated source file: 'en.json' on 'fr'. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- bigbluebutton-html5/public/locales/fr.json | 75 ++++++++++++++++++++-- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/bigbluebutton-html5/public/locales/fr.json b/bigbluebutton-html5/public/locales/fr.json index b82aa050af..306a285182 100644 --- a/bigbluebutton-html5/public/locales/fr.json +++ b/bigbluebutton-html5/public/locales/fr.json @@ -7,6 +7,7 @@ "app.chat.locked": "La discussion est verrouillée, les messages ne peuvent pas être envoyés", "app.chat.inputLabel": "Saisie des messages pour la discussion {0}", "app.chat.emojiButtonLabel": "Sélecteur d'émoticônes", + "app.chat.loadMoreButtonLabel": "En savoir plus", "app.chat.inputPlaceholder": "Message {0}", "app.chat.titlePublic": "Discussion publique", "app.chat.titlePrivate": "Discussion privée avec {0}", @@ -24,8 +25,11 @@ "app.chat.breakoutDurationUpdated": "Le temps restant aux groupes de travail est désormais de {0} minutes", "app.chat.breakoutDurationUpdatedModerator": "Le temps en groupe de travail est de {0} minutes; une notification a été envoyée", "app.chat.emptyLogLabel": "Journal de discussion vide", + "app.chat.away": "Est absent", + "app.chat.notAway": "N'est plus absent", "app.chat.clearPublicChatMessage": "L'historique des discussions publiques a été effacé par un modérateur", "app.chat.multi.typing": "Plusieurs utilisateurs sont en train d'écrire", + "app.chat.someone.typing": "Quelqu'un est en train d'écrire", "app.chat.one.typing": "{0} est en train d'écrire", "app.chat.two.typing": "{0} et {1} sont en train d'écrire", "app.chat.copySuccess": "La transcription de la discussion est copiée", @@ -36,6 +40,7 @@ "app.emojiPicker.clear": "Clair", "app.emojiPicker.categories.label": "Catégories d'émoticônes", "app.emojiPicker.categories.people": "Personnages et corps", + "app.emojiPicker.categories.reactions": "Réactions", "app.emojiPicker.categories.nature": "Nature et animaux", "app.emojiPicker.categories.foods": "Nourriture et boisson", "app.emojiPicker.categories.places": "Lieux et voyage", @@ -51,6 +56,23 @@ "app.emojiPicker.skintones.4": "Ton mat", "app.emojiPicker.skintones.5": "Ton mat-foncé", "app.emojiPicker.skintones.6": "Ton foncé", + "app.timer.title": "Temps", + "app.timer.stopwatch.title": "Chronomètre", + "app.timer.timer.title": "Minuterie", + "app.timer.hideTimerLabel": "Cacher le temps", + "app.timer.button.stopwatch": "Chronomètre", + "app.timer.button.timer": "Minuterie", + "app.timer.button.start": "Démarrer", + "app.timer.button.stop": "Arrêter", + "app.timer.button.reset": "Réinitialiser", + "app.timer.hours": "heures", + "app.timer.minutes": "minutes", + "app.timer.seconds": "secondes", + "app.timer.songs": "Chansons", + "app.timer.noTrack": "Pas de chansons", + "app.timer.track1": "Détente", + "app.timer.track2": "Calme", + "app.timer.track3": "Heureux", "app.captions.label": "Sous-titres", "app.captions.menu.close": "Fermer", "app.captions.menu.start": "Démarrer", @@ -102,6 +124,7 @@ "app.userList.messagesTitle": "Messages", "app.userList.notesTitle": "Notes", "app.userList.notesListItem.unreadContent": "Nouveau contenu disponible dans la section des notes partagées", + "app.userList.timerTitle": "Temps", "app.userList.captionsTitle": "Sous-titres", "app.userList.presenter": "Présentateur", "app.userList.you": "Vous", @@ -116,6 +139,8 @@ "app.userList.menuTitleContext": "Options disponibles", "app.userList.chatListItem.unreadSingular": "Un nouveau message", "app.userList.chatListItem.unreadPlural": "{0} nouveaux messages", + "app.userList.menu.away": "Se définir comme parti", + "app.userList.menu.notAway": "Se définir comme actif", "app.userList.menu.chat.label": "Démarrer une discussion privée", "app.userList.menu.clearStatus.label": "Effacer le statut", "app.userList.menu.removeUser.label": "Retirer l'utilisateur", @@ -140,6 +165,8 @@ "app.userList.userOptions.muteAllDesc": "Rendre tous les utilisateurs de la réunion silencieux", "app.userList.userOptions.clearAllLabel": "Effacer toutes les icônes de statut", "app.userList.userOptions.clearAllDesc": "Effacer toutes les icônes de statut des utilisateurs", + "app.userList.userOptions.clearAllReactionsLabel": "Effacer toutes les réactions", + "app.userList.userOptions.clearAllReactionsDesc": "Supprime tous les émoticônes de réaction des utilisateurs", "app.userList.userOptions.muteAllExceptPresenterLabel": "Rendre silencieux tous les utilisateurs sauf le présentateur", "app.userList.userOptions.muteAllExceptPresenterDesc": "Rend silencieux tous les utilisateurs de la réunion sauf le présentateur", "app.userList.userOptions.unmuteAllLabel": "Désactiver le mode silencieux de la réunion", @@ -156,6 +183,7 @@ "app.userList.userOptions.hideUserList": "La liste des utilisateurs est maintenant masquée pour les participants", "app.userList.userOptions.webcamsOnlyForModerator": "Seuls les modérateurs peuvent voir les webcams des participants (selon les paramètres de verrouillage)", "app.userList.content.participants.options.clearedStatus": "Tous les statuts d'utilisateurs sont effacés", + "app.userList.content.participants.options.clearedReactions": "Toutes les réactions effacées", "app.userList.userOptions.enableCam": "Les webcams des participants sont activées", "app.userList.userOptions.enableMic": "Les microphones des participants sont activés", "app.userList.userOptions.enablePrivChat": "La discussion privée est activée", @@ -177,6 +205,11 @@ "app.media.screenshare.notSupported": "Le partage d'écran n'est pas disponible pour ce navigateur.", "app.media.screenshare.autoplayBlockedDesc": "Nous avons besoin de votre permission pour vous montrer l'écran du présentateur.", "app.media.screenshare.autoplayAllowLabel": "Voir l'écran partagé", + "app.media.cameraAsContent.start": "La caméra actuelle a commencé à fonctionner", + "app.media.cameraAsContent.end": "La caméra actuelle a cessé de fonctionner", + "app.media.cameraAsContent.endDueToDataSaving": "La caméra actuelle a cessé de fonctionner en raison de l'économie de données", + "app.media.cameraAsContent.autoplayBlockedDesc": "Nous avons besoin de votre autorisation pour vous montrer la caméra du présentateur.", + "app.media.cameraAsContent.autoplayAllowLabel": "Voir la caméra actuelle", "app.screenshare.presenterLoadingLabel": "Votre partage d'écran est en cours de chargement", "app.screenshare.viewerLoadingLabel": "L'écran du présentateur est en cours de chargement", "app.screenshare.presenterSharingLabel": "Vous partagez désormais votre écran", @@ -185,6 +218,9 @@ "app.screenshare.screenshareRetryOtherEnvError": "Code {0}. Impossible de partager l'écran. Essayez à nouveau en utilisant un autre navigateur ou un appareil différent.", "app.screenshare.screenshareUnsupportedEnv": "Code {0}. Le navigateur n'est pas pris en charge. Essayez à nouveau en utilisant un autre navigateur ou un appareil différent.", "app.screenshare.screensharePermissionError": "Code {0}. L'autorisation de capturer d'écran doit être accordée.", + "app.cameraAsContent.presenterLoadingLabel": "Votre caméra est en cours de chargement", + "app.cameraAsContent.viewerLoadingLabel": "La caméra du présentateur est en cours de chargement", + "app.cameraAsContent.presenterSharingLabel": "Vous présentez maintenant votre caméra", "app.meeting.ended": "Cette réunion est terminée", "app.meeting.meetingTimeRemaining": "Temps de réunion restant : {0}", "app.meeting.meetingTimeHasEnded": "Le temps s'est écoulé. La réunion sera bientôt close", @@ -199,6 +235,7 @@ "app.presentation.hide": "Masquer la présentation", "app.presentation.notificationLabel": "Présentation courante", "app.presentation.downloadLabel": "Télécharger", + "app.presentation.actionsLabel": "Actions", "app.presentation.slideContent": "Diaporama", "app.presentation.startSlideContent": "Le diaporama démarre", "app.presentation.endSlideContent": "Fin du diaporama", @@ -250,8 +287,15 @@ "app.presentationUploader.sent": "Envoyé", "app.presentationUploader.exportingTimeout": "L'export dure trop longtemps...", "app.presentationUploader.export": "Envoi dans la discussion", + "app.presentationUploader.exportCurrentStatePresentation": "Envoyer un lien de téléchargement pour la présentation dans son état actuel", + "app.presentationUploader.enableOriginalPresentationDownload": "Permettre le téléchargement de la présentation originale", + "app.presentationUploader.disableOriginalPresentationDownload": "Ne pas permettre le téléchargement de la présentation originale", + "app.presentationUploader.dropdownExportOptions": "Exporter les options", "app.presentationUploader.export.linkAvailable": "Le lien de téléchargement {0} est disponible dans la discussion publique", + "app.presentationUploader.export.downloadButtonAvailable": "Le bouton de téléchargement de la présentation {0} est disponible.", "app.presentationUploader.export.notAccessibleWarning": "Probablement non conforme aux règles d'accessibilité", + "app.presentationUploader.export.originalLabel": "Original", + "app.presentationUploader.export.inCurrentStateLabel": "Dans l'état actuel", "app.presentationUploader.currentPresentationLabel": "Présentation courante", "app.presentationUploder.extraHint": "IMPORTANT: Le volume d'un fichier ne doit pas excéder {0} MB et {1} pages.", "app.presentationUploder.uploadLabel": "Téléverser", @@ -446,7 +490,10 @@ "app.actionsBar.actionsDropdown.minimizePresentationLabel": "Réduire la fenêtre de présentation", "app.actionsBar.actionsDropdown.minimizePresentationDesc": "Bouton utilisé pour réduire la fenêtre de présentation", "app.actionsBar.actionsDropdown.layoutModal": "Fenêtre de paramétrage de la mise en page", + "app.actionsBar.actionsDropdown.shareCameraAsContent": "Partager la caméra en tant que contenu", + "app.actionsBar.actionsDropdown.unshareCameraAsContent": "Cesser de partager l'appareil photo en tant que contenu", "app.screenshare.screenShareLabel" : "Partage d'écran", + "app.cameraAsContent.cameraAsContentLabel" : "Caméra actuelle", "app.submenu.application.applicationSectionTitle": "Application", "app.submenu.application.animationsLabel": "Animations", "app.submenu.application.audioFilterLabel": "Filtres audios pour microphone", @@ -460,6 +507,7 @@ "app.submenu.application.languageOptionLabel": "Choisir la langue", "app.submenu.application.noLocaleOptionLabel": "Aucune langue d'application détectée", "app.submenu.application.paginationEnabledLabel": "Mise en page de la vidéo", + "app.submenu.application.wakeLockEnabledLabel": "Verrouillage du réveil", "app.submenu.application.layoutOptionLabel": "Type de mise en page", "app.submenu.application.pushLayoutLabel": "Appliquer la mise en page", "app.submenu.application.localeDropdown.af": "Afrikaans", @@ -569,6 +617,8 @@ "app.talkingIndicator.moreThanMaxIndicatorsWereTalking" : "{0}+ parlaient", "app.talkingIndicator.wasTalking" : "{0} a cessé de parler", "app.actionsBar.actionsDropdown.actionsLabel": "Actions", + "app.actionsBar.actionsDropdown.activateTimerStopwatchLabel": "Activer la minuterie/le chronomètre", + "app.actionsBar.actionsDropdown.deactivateTimerStopwatchLabel": "Désactiver la minuterie/le chronomètre", "app.actionsBar.actionsDropdown.presentationLabel": "Charger/Gérer les documents de présentation", "app.actionsBar.actionsDropdown.initPollLabel": "Lancer un sondage", "app.actionsBar.actionsDropdown.desktopShareLabel": "Partager votre écran", @@ -588,7 +638,9 @@ "app.actionsBar.actionsDropdown.takePresenterDesc": "S'assigner comme nouveau présentateur", "app.actionsBar.actionsDropdown.selectRandUserLabel": "Sélectionner un utilisateur aléatoirement", "app.actionsBar.actionsDropdown.selectRandUserDesc": "Sélectionne aléatoirement un utilisateur parmi les participants disponibles", - "app.actionsBar.actionsDropdown.propagateLayoutLabel": "Diffuser la mise en page", + "app.actionsBar.reactions.reactionsButtonLabel": "Barre de réactions", + "app.actionsBar.reactions.raiseHand": "Lever la main", + "app.actionsBar.reactions.lowHand": "Baisser la main", "app.actionsBar.emojiMenu.statusTriggerLabel": "Définir votre statut", "app.actionsBar.emojiMenu.awayLabel": "Absent", "app.actionsBar.emojiMenu.awayDesc": "Passer votre statut à « absent »", @@ -817,8 +869,15 @@ "app.toast.meetingMuteOn.label": "Tous les utilisateurs ont été rendus silencieux", "app.toast.meetingMuteOnViewers.label": "Les spectateurs sont en mode silencieux", "app.toast.meetingMuteOff.label": "Mode silencieux désactivé", + "app.toast.wakeLock.offerTitle": "Souhaitez-vous que l'écran de votre appareil reste actif pendant la réunion ?", + "app.toast.wakeLock.offerAccept": "Oui !", + "app.toast.wakeLock.offerDecline": "Pas maintenant", + "app.toast.wakeLock.acquireSuccess": "Verrouillage du réveil actif ! Vous pouvez le désactiver dans le menu des paramètres.", + "app.toast.wakeLock.acquireFailed": "Erreur d'acquisition du verrouillage de réveil.", "app.toast.setEmoji.raiseHand": "Vous avez levé la main", "app.toast.setEmoji.lowerHand": "Votre main a été abaissée", + "app.toast.setEmoji.away": "Vous avez défini votre statut comme étant parti", + "app.toast.setEmoji.notAway": "Vous avez supprimé votre statut « parti »", "app.toast.promotedLabel": "Vous avez été promu comme modérateur", "app.toast.demotedLabel": "Vous avez été rétrogradé comme participant", "app.notification.recordingStart": "Cette réunion est maintenant enregistrée", @@ -901,6 +960,7 @@ "app.lock-viewers.button.cancel": "Annuler", "app.lock-viewers.locked": "Verrouillé", "app.lock-viewers.hideViewersCursor": "Voir les pointeurs des autres participants", + "app.lock-viewers.hideAnnotationsLabel": "Voir les annotations des autres spectateurs", "app.guest-policy.ariaTitle": "Fenêtre des paramètres de gestion des accès", "app.guest-policy.title": "Gestion des accès", "app.guest-policy.description": "Modifier le paramétrage de la gestion des accès à la réunion ", @@ -908,6 +968,7 @@ "app.guest-policy.button.alwaysAccept": "Toujours accepter", "app.guest-policy.button.alwaysDeny": "Toujours refuser", "app.guest-policy.policyBtnDesc": "Défini le paramétrage pour la gestion des accès à la réunion", + "app.guest-policy.feedbackMessage": "La politique à l'égard des invités est maintenant :", "app.connection-status.ariaTitle": "État de la connexion visible", "app.connection-status.title": "État de la connexion", "app.connection-status.description": "Voir l'état de la connexion des utilisateurs", @@ -965,6 +1026,7 @@ "app.videoPreview.webcamPreviewLabel": "Aperçu de la webcam", "app.videoPreview.webcamSettingsTitle": "Paramètres de la webcam", "app.videoPreview.webcamEffectsTitle": "Réglages de l'image pour la webcam", + "app.videoPreview.cameraAsContentSettingsTitle": "Caméra présente", "app.videoPreview.webcamVirtualBackgroundLabel": "Paramètres d'arrière-plan virtuel", "app.videoPreview.webcamVirtualBackgroundDisabledLabel": "Le périphérique ne prend pas en charge les arrière-plans virtuels", "app.videoPreview.webcamNotFoundLabel": "Webcam introuvable", @@ -1093,6 +1155,10 @@ "app.videoDock.webcamFocusDesc": "Centrer sur la webcam sélectionnée", "app.videoDock.webcamUnfocusLabel": "Arrêter le centrage", "app.videoDock.webcamUnfocusDesc": "Arrêter de centrer sur la webcam sélectionnée", + "app.videoDock.webcamDisableLabel": "Désactiver la vision de soi", + "app.videoDock.webcamDisableLabelAllCams": "Désactiver l'affichage de soi (toutes les caméras)", + "app.videoDock.webcamEnableLabel": "Activer la vision de soi", + "app.videoDock.webcamDisableDesc": "Vision de soi désactivée", "app.videoDock.webcamPinLabel": "Épingler", "app.videoDock.webcamPinDesc": "Épingler la caméra sélectionnée", "app.videoDock.webcamFullscreenLabel": "Webcam en plein écran", @@ -1133,8 +1199,10 @@ "app.createBreakoutRoom.addRoomTime": "Augmenter la durée des groupes de travail à", "app.createBreakoutRoom.addParticipantLabel": "+ Ajouter un participant", "app.createBreakoutRoom.freeJoin": "Autoriser les participants à choisir la salle de réunion privée qu'ils souhaitent rejoindre", + "app.createBreakoutRoom.manageRoomsLabel": "Gérer les salles", "app.createBreakoutRoom.captureNotes": "Faire une capture des notes partagées lors de la fermeture des groupes de travail", "app.createBreakoutRoom.captureSlides": "Faire une capture du tableau blanc lors de la fermeture des groupes de travail", + "app.createBreakoutRoom.sendInvitationToMods": "Envoyer l'invitation aux modérateurs désignés", "app.createBreakoutRoom.leastOneWarnBreakout": "Vous devez placer au moins un participant dans chaque groupe de travail", "app.createBreakoutRoom.minimumDurationWarnBreakout": "La durée minimum d'une session en groupes de travail est de {0} minutes.", "app.createBreakoutRoom.modalDesc": "Conseil : vous pouvez glisser-déposer le nom d'un utilisateur pour l'affecter à un groupe de travail spécifique.", @@ -1180,10 +1248,9 @@ "app.debugWindow.form.chatLoggerLabel": "Tester les niveaux de logs de la discussion.", "app.debugWindow.form.button.apply": "Appliquer", "app.layout.modal.title": "Mise en page", - "app.layout.modal.confirm": "Confirmer", - "app.layout.modal.cancel": "Annuler", + "app.layout.modal.update": "Actualiser", + "app.layout.modal.updateAll": "Actualiser tout le monde", "app.layout.modal.layoutLabel": "Choisir votre mise en page", - "app.layout.modal.keepPushingLayoutLabel": "Appliquer la mise en page à tous", "app.layout.modal.pushLayoutLabel": "Appliquer la mise en page à tout le monde", "app.layout.modal.layoutToastLabel": "Le paramétrage de la mise en page a changé", "app.layout.modal.layoutSingular": "Mise en page", From 2bd6a19533bd18e1006aac94261ab4fbf73c5415 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Wed, 9 Aug 2023 14:03:48 -0400 Subject: [PATCH 168/252] Updates for file bigbluebutton-html5/public/locales/en.json in zh_TW on branch v2.6.x-release (#18504) * Translate en.json in zh_TW 100% translated source file: 'en.json' on 'zh_TW'. --------- Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- bigbluebutton-html5/public/locales/zh_TW.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bigbluebutton-html5/public/locales/zh_TW.json b/bigbluebutton-html5/public/locales/zh_TW.json index 459ef9deca..29121edf03 100644 --- a/bigbluebutton-html5/public/locales/zh_TW.json +++ b/bigbluebutton-html5/public/locales/zh_TW.json @@ -24,7 +24,7 @@ "app.chat.breakoutDurationUpdated": "分組討論時間已設定為 {0} 分鐘", "app.chat.breakoutDurationUpdatedModerator": "分組討論室時間已設定為 {0} 分鐘,並已發送通知。", "app.chat.emptyLogLabel": "聊天記錄為空", - "app.chat.clearPublicChatMessage": "公共聊天歷史紀錄已被管理員清除", + "app.chat.clearPublicChatMessage": "主持人已清除公開聊天歷史記錄", "app.chat.multi.typing": "多個使用者正在輸入", "app.chat.one.typing": "{0} 正在輸入", "app.chat.two.typing": "{0} 和 {1} 正在輸入", @@ -123,8 +123,8 @@ "app.userlist.menu.removeConfirmation.desc": "防止該使用者重新加入會議。", "app.userList.menu.muteUserAudio.label": "靜音使用者", "app.userList.menu.unmuteUserAudio.label": "取消靜音使用者", - "app.userList.menu.webcamPin.label": "釘選使用者的攝像頭", - "app.userList.menu.webcamUnpin.label": "取消釘選使用者的攝像頭", + "app.userList.menu.webcamPin.label": "固定使用者的視訊鏡頭", + "app.userList.menu.webcamUnpin.label": "取消固定使用者的視訊鏡頭", "app.userList.menu.giveWhiteboardAccess.label" : "授予白板訪問權限", "app.userList.menu.removeWhiteboardAccess.label": "移除白板訪問權限", "app.userList.menu.ejectUserCameras.label": "關閉攝像頭", @@ -727,7 +727,7 @@ "app.audio.captions.select.ja-JP": "日語", "app.audio.captions.select.pt-BR": "葡萄牙語", "app.audio.captions.select.ru-RU": "俄語", - "app.audio.captions.select.zh-CN": "中文", + "app.audio.captions.select.zh-CN": "中文普通話", "app.error.removed": "您已從會議中被移除", "app.error.meeting.ended": "您已登出會議", "app.meeting.logout.duplicateUserEjectReason": "重複使用者試圖加入會議", @@ -1135,7 +1135,7 @@ "app.createBreakoutRoom.freeJoin": "允許使用者選擇加入的分組討論室", "app.createBreakoutRoom.captureNotes": "在分組討論室結束時捕獲共享筆記", "app.createBreakoutRoom.captureSlides": "在分組討論室結束時捕獲白板", - "app.createBreakoutRoom.leastOneWarnBreakout": "您必須至少將一位使用者放置在分組討論室中。", + "app.createBreakoutRoom.leastOneWarnBreakout": "您必須至少將一名使用者放入分組房間。", "app.createBreakoutRoom.minimumDurationWarnBreakout": "分組討論室的最短持續時間為 {0} 分鐘。", "app.createBreakoutRoom.modalDesc": "提示:您可以拖放使用者的名稱以將其指派到特定的分組討論室。", "app.createBreakoutRoom.roomTime": "{0} 分鐘", From 38c6da7c2924e7545d2570eca37db339f79a86bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Wed, 9 Aug 2023 15:42:21 -0300 Subject: [PATCH 169/252] add comment about debounce function --- bigbluebutton-html5/imports/utils/debounce.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bigbluebutton-html5/imports/utils/debounce.js b/bigbluebutton-html5/imports/utils/debounce.js index 116c76d998..bd78c8afc1 100644 --- a/bigbluebutton-html5/imports/utils/debounce.js +++ b/bigbluebutton-html5/imports/utils/debounce.js @@ -1,3 +1,12 @@ +/** + * Debounce function, includes leading and trailing options (lodash-like) + * @param {Function} func - function to be debounced + * @param {Number} delay - delay in milliseconds + * @param {Object} options - options object + * @param {Boolean} options.leading - whether to invoke the function on the leading edge + * @param {Boolean} options.trailing - whether to invoke the function on the trailing edge + * @returns {Function} - debounced function + */ export function debounce(func, delay, options = {}) { let timeoutId; let lastArgs; From 81b1483a8a2f703accbcb9b88092d77473f7e382 Mon Sep 17 00:00:00 2001 From: Anton Georgiev Date: Wed, 9 Aug 2023 15:37:27 -0400 Subject: [PATCH 170/252] chore: upgrade Meteor 2.12 to 2.13 --- bigbluebutton-html5/.meteor/packages | 2 +- bigbluebutton-html5/.meteor/release | 2 +- bigbluebutton-html5/.meteor/versions | 6 +++--- build/packages-template/bbb-html5-nodejs/build.sh | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bigbluebutton-html5/.meteor/packages b/bigbluebutton-html5/.meteor/packages index d527b25b1d..ecee23ab0c 100644 --- a/bigbluebutton-html5/.meteor/packages +++ b/bigbluebutton-html5/.meteor/packages @@ -5,7 +5,7 @@ meteor-base@1.5.1 mobile-experience@1.1.0 -mongo@1.16.6 +mongo@1.16.7 reactive-var@1.0.12 standard-minifier-css@1.9.2 diff --git a/bigbluebutton-html5/.meteor/release b/bigbluebutton-html5/.meteor/release index e8cfc7ec4c..7c40889daf 100644 --- a/bigbluebutton-html5/.meteor/release +++ b/bigbluebutton-html5/.meteor/release @@ -1 +1 @@ -METEOR@2.12 +METEOR@2.13 diff --git a/bigbluebutton-html5/.meteor/versions b/bigbluebutton-html5/.meteor/versions index 5dd4636be1..4cefbe6d7f 100644 --- a/bigbluebutton-html5/.meteor/versions +++ b/bigbluebutton-html5/.meteor/versions @@ -13,7 +13,7 @@ check@1.3.2 ddp@1.4.1 ddp-client@2.6.1 ddp-common@1.4.0 -ddp-server@2.6.1 +ddp-server@2.6.2 diff-sequence@1.1.2 dynamic-import@0.7.3 ecmascript@0.16.7 @@ -33,7 +33,7 @@ inter-process-messaging@0.1.1 launch-screen@1.3.0 lmieulet:meteor-coverage@4.1.0 logging@1.3.2 -meteor@1.11.2 +meteor@1.11.3 meteor-base@1.5.1 meteortesting:browser-tests@1.3.5 meteortesting:mocha@2.0.3 @@ -46,7 +46,7 @@ mobile-status-bar@1.1.0 modern-browsers@0.1.9 modules@0.19.0 modules-runtime@0.13.1 -mongo@1.16.6 +mongo@1.16.7 mongo-decimal@0.1.3 mongo-dev-server@1.1.0 mongo-id@1.0.8 diff --git a/build/packages-template/bbb-html5-nodejs/build.sh b/build/packages-template/bbb-html5-nodejs/build.sh index 6195eb380f..d1c0648193 100755 --- a/build/packages-template/bbb-html5-nodejs/build.sh +++ b/build/packages-template/bbb-html5-nodejs/build.sh @@ -7,7 +7,7 @@ PACKAGE=$(echo $TARGET | cut -d'_' -f1) VERSION=$(echo $TARGET | cut -d'_' -f2) DISTRO=$(echo $TARGET | cut -d'_' -f3) -NODE_VERSION="14.21.3" +NODE_VERSION="14.21.4" NODE_DIRNAME="node-v${NODE_VERSION}-linux-x64" # From 3c6dfb86cbf96eafcf7c5985bfb1e96095270304 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 9 Aug 2023 17:59:17 -0300 Subject: [PATCH 171/252] ci(tests): cancel previous actions from the same PR --- .github/workflows/automated-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 9ae6165a18..829b44354e 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -15,6 +15,9 @@ on: - '**/*.md' permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: build-bbb-apps-akka: runs-on: ubuntu-20.04 From 1e19db3f552fef86cdc05591373356da62bb730e Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 9 Aug 2023 18:08:09 -0300 Subject: [PATCH 172/252] Checkout master instead of PR branch --- .github/workflows/automated-tests.yml | 246 ++++++++++++++++++++------ 1 file changed, 187 insertions(+), 59 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 9ae6165a18..b713fe4238 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -19,14 +19,24 @@ jobs: build-bbb-apps-akka: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - name: Checkout ${{ github.event.pull_request.base.ref }} + uses: actions/checkout@v3 with: + ref: ${{ github.event.pull_request.base.ref }} fetch-depth: 0 # Fetch all history - - run: echo "CACHE_AKKA_APPS_KEY=$(git log -1 --format=%H -- akka-bbb-apps)" >> $GITHUB_ENV - - run: echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV - - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh + - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} + run: | + git config user.name "BBB Automated Tests" + git config user.email "tests@bigbluebutton.org" + git config pull.rebase false + git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} + - name: Set cache-key vars + run: | + echo "CACHE_AKKA_APPS_KEY=$(git log -1 --format=%H -- akka-bbb-apps)" >> $GITHUB_ENV + echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV + echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -48,9 +58,21 @@ jobs: build-bbb-config: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh + - name: Checkout ${{ github.event.pull_request.base.ref }} + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.base.ref }} + fetch-depth: 0 # Fetch all history + - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} + run: | + git config user.name "BBB Automated Tests" + git config user.email "tests@bigbluebutton.org" + git config pull.rebase false + git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} + - name: Set cache-key vars + run: | + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - run: | ./build/get_external_dependencies.sh ./build/setup.sh bbb-config @@ -63,9 +85,21 @@ jobs: build-bbb-export-annotations: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh + - name: Checkout ${{ github.event.pull_request.base.ref }} + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.base.ref }} + fetch-depth: 0 # Fetch all history + - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} + run: | + git config user.name "BBB Automated Tests" + git config user.email "tests@bigbluebutton.org" + git config pull.rebase false + git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} + - name: Set cache-key vars + run: | + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - run: | ./build/get_external_dependencies.sh ./build/setup.sh bbb-export-annotations @@ -78,13 +112,23 @@ jobs: build-bbb-learning-dashboard: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - name: Checkout ${{ github.event.pull_request.base.ref }} + uses: actions/checkout@v3 with: + ref: ${{ github.event.pull_request.base.ref }} fetch-depth: 0 # Fetch all history - - run: echo "CACHE_LEARNING_DASHBOARD_KEY=$(git log -1 --format=%H -- bbb-learning-dashboard)" >> $GITHUB_ENV - - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh + - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} + run: | + git config user.name "BBB Automated Tests" + git config user.email "tests@bigbluebutton.org" + git config pull.rebase false + git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} + - name: Set cache-key vars + run: | + echo "CACHE_LEARNING_DASHBOARD_KEY=$(git log -1 --format=%H -- bbb-learning-dashboard)" >> $GITHUB_ENV + echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -105,10 +149,22 @@ jobs: build-bbb-playback-record: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - name: Checkout ${{ github.event.pull_request.base.ref }} + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.base.ref }} + fetch-depth: 0 # Fetch all history + - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} + run: | + git config user.name "BBB Automated Tests" + git config user.email "tests@bigbluebutton.org" + git config pull.rebase false + git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} - run: ./build/get_external_dependencies.sh - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh + - name: Set cache-key vars + run: | + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - run: ./build/setup.sh bbb-playback - run: ./build/setup.sh bbb-playback-notes - run: ./build/setup.sh bbb-playback-podcast @@ -126,18 +182,28 @@ jobs: build-bbb-etherpad: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - name: Checkout ${{ github.event.pull_request.base.ref }} + uses: actions/checkout@v3 with: + ref: ${{ github.event.pull_request.base.ref }} fetch-depth: 0 # Fetch all history - - run: echo "CACHE_ETHERPAD_VERSION_KEY=$(git log -1 --format=%H -- bbb-etherpad.placeholder.sh)" >> $GITHUB_ENV - - run: echo "CACHE_ETHERPAD_BUILD_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-etherpad)" >> $GITHUB_ENV - - run: echo "CACHE_URL1_KEY=$(curl -s https://api.github.com/repos/mconf/ep_pad_ttl/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - - run: echo "CACHE_URL2_KEY=$(curl -s https://api.github.com/repos/alangecker/bbb-etherpad-plugin/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - - run: echo "CACHE_URL3_KEY=$(curl -s https://api.github.com/repos/mconf/ep_redis_publisher/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - - run: echo "CACHE_URL4_KEY=$(curl -s https://api.github.com/repos/alangecker/bbb-etherpad-skin/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh + - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} + run: | + git config user.name "BBB Automated Tests" + git config user.email "tests@bigbluebutton.org" + git config pull.rebase false + git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} + - name: Set cache-key vars + run: | + echo "CACHE_ETHERPAD_VERSION_KEY=$(git log -1 --format=%H -- bbb-etherpad.placeholder.sh)" >> $GITHUB_ENV + echo "CACHE_ETHERPAD_BUILD_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-etherpad)" >> $GITHUB_ENV + echo "CACHE_URL1_KEY=$(curl -s https://api.github.com/repos/mconf/ep_pad_ttl/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV + echo "CACHE_URL2_KEY=$(curl -s https://api.github.com/repos/alangecker/bbb-etherpad-plugin/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV + echo "CACHE_URL3_KEY=$(curl -s https://api.github.com/repos/mconf/ep_redis_publisher/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV + echo "CACHE_URL4_KEY=$(curl -s https://api.github.com/repos/alangecker/bbb-etherpad-skin/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV + echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -159,15 +225,25 @@ jobs: build-bbb-bbb-web: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - name: Checkout ${{ github.event.pull_request.base.ref }} + uses: actions/checkout@v3 with: + ref: ${{ github.event.pull_request.base.ref }} fetch-depth: 0 # Fetch all history - - run: echo "CACHE_BBB_WEB_KEY=$(git log -1 --format=%H -- bigbluebutton-web)" >> $GITHUB_ENV - - run: echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV - - run: echo "CACHE_COMMON_WEB_KEY=$(git log -1 --format=%H -- bbb-common-web)" >> $GITHUB_ENV - - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh + - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} + run: | + git config user.name "BBB Automated Tests" + git config user.email "tests@bigbluebutton.org" + git config pull.rebase false + git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} + - name: Set cache-key vars + run: | + echo "CACHE_BBB_WEB_KEY=$(git log -1 --format=%H -- bigbluebutton-web)" >> $GITHUB_ENV + echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV + echo "CACHE_COMMON_WEB_KEY=$(git log -1 --format=%H -- bbb-common-web)" >> $GITHUB_ENV + echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -189,14 +265,24 @@ jobs: build-bbb-fsesl-akka: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - name: Checkout ${{ github.event.pull_request.base.ref }} + uses: actions/checkout@v3 with: + ref: ${{ github.event.pull_request.base.ref }} fetch-depth: 0 # Fetch all history - - run: echo "CACHE_AKKA_FSESL_KEY=$(git log -1 --format=%H -- akka-bbb-fsesl)" >> $GITHUB_ENV - - run: echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV - - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh + - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} + run: | + git config user.name "BBB Automated Tests" + git config user.email "tests@bigbluebutton.org" + git config pull.rebase false + git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} + - name: Set cache-key vars + run: | + echo "CACHE_AKKA_FSESL_KEY=$(git log -1 --format=%H -- akka-bbb-fsesl)" >> $GITHUB_ENV + echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV + echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -218,13 +304,23 @@ jobs: build-bbb-html5: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - name: Checkout ${{ github.event.pull_request.base.ref }} + uses: actions/checkout@v3 with: + ref: ${{ github.event.pull_request.base.ref }} fetch-depth: 0 # Fetch all history - - run: echo "CACHE_KEY=$(git log -1 --format=%H -- bigbluebutton-html5)" >> $GITHUB_ENV - - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh + - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} + run: | + git config user.name "BBB Automated Tests" + git config user.email "tests@bigbluebutton.org" + git config pull.rebase false + git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} + - name: Set cache-key vars + run: | + echo "CACHE_KEY=$(git log -1 --format=%H -- bigbluebutton-html5)" >> $GITHUB_ENV + echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -247,15 +343,25 @@ jobs: build-bbb-freeswitch: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - name: Checkout ${{ github.event.pull_request.base.ref }} + uses: actions/checkout@v3 with: + ref: ${{ github.event.pull_request.base.ref }} fetch-depth: 0 # Fetch all history - - run: echo "CACHE_FREESWITCH_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-core)" >> $GITHUB_ENV - - run: echo "CACHE_FREESWITCH_SOUNDS_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-sounds)" >> $GITHUB_ENV - - run: echo "CACHE_SOUNDS_KEY=$(curl -Is http://bigbluebutton.org/downloads/sounds.tar.gz | grep "Last-Modified" | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh + - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} + run: | + git config user.name "BBB Automated Tests" + git config user.email "tests@bigbluebutton.org" + git config pull.rebase false + git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} + - name: Set cache-key vars + run: | + echo "CACHE_FREESWITCH_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-core)" >> $GITHUB_ENV + echo "CACHE_FREESWITCH_SOUNDS_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-sounds)" >> $GITHUB_ENV + echo "CACHE_SOUNDS_KEY=$(curl -Is http://bigbluebutton.org/downloads/sounds.tar.gz | grep "Last-Modified" | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV + echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - name: Handle cache id: cache-action uses: actions/cache@v3 @@ -278,10 +384,22 @@ jobs: build-others: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - name: Checkout ${{ github.event.pull_request.base.ref }} + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.base.ref }} + fetch-depth: 0 # Fetch all history + - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} + run: | + git config user.name "BBB Automated Tests" + git config user.email "tests@bigbluebutton.org" + git config pull.rebase false + git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} - run: ./build/get_external_dependencies.sh - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh + - name: Set cache-key vars + run: | + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - run: ./build/setup.sh bbb-mkclean - run: ./build/setup.sh bbb-pads - run: ./build/setup.sh bbb-libreoffice-docker @@ -310,7 +428,17 @@ jobs: needs: [build-bbb-apps-akka, build-bbb-config, build-bbb-export-annotations, build-bbb-learning-dashboard, build-bbb-playback-record, build-bbb-etherpad, build-bbb-bbb-web, build-bbb-fsesl-akka, build-bbb-html5, build-bbb-freeswitch, build-others] runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - name: Checkout ${{ github.event.pull_request.base.ref }} + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.base.ref }} + fetch-depth: 0 # Fetch all history + - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} + run: | + git config user.name "BBB Automated Tests" + git config user.email "tests@bigbluebutton.org" + git config pull.rebase false + git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} - run: ./build/get_external_dependencies.sh - name: Download artifacts_bbb-apps-akka uses: actions/download-artifact@v3 From 0f9b9e19323af0b9c90eae28ca9f896e189a9ed0 Mon Sep 17 00:00:00 2001 From: prlanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Wed, 9 Aug 2023 22:46:03 -0300 Subject: [PATCH 173/252] build(bbb-webrtc-sfu): v2.11.0-beta.2 A few adjustments to the transparent listen only mechanism New Prometheus metrics to validate transparent listen only --- bbb-webrtc-sfu.placeholder.sh | 2 +- docs/docs/new-features.md | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/bbb-webrtc-sfu.placeholder.sh b/bbb-webrtc-sfu.placeholder.sh index 6b19fc6373..cc0b179671 100755 --- a/bbb-webrtc-sfu.placeholder.sh +++ b/bbb-webrtc-sfu.placeholder.sh @@ -1 +1 @@ -git clone --branch v2.11.0-beta.1 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu +git clone --branch v2.11.0-beta.2 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu diff --git a/docs/docs/new-features.md b/docs/docs/new-features.md index c33dd4d9c7..a5d9784163 100644 --- a/docs/docs/new-features.md +++ b/docs/docs/new-features.md @@ -116,10 +116,8 @@ In version 2.7, we present the initial iteration of this audio mode, primarily f The primary objective is to assess the viability of the proposed approach and gather community feedback. The new mode is *turned off by default* and is considered *experimental*. To enable it: - - Add `transparentListenOnly: true` to `/etc/bigbluebutton/bbb-webrtc-sfu/production.yml` - - Restart `bbb-webrtc-sfu` with `systemctl restart bbb-webrtc-sfu` - To enable on clients: - * Server wide: configure `public.media.transparentListenOnly: true` in `/etc/bigbluebutton/bbb-html5.yml` + * Server wide: configure `public.media.transparentListenOnly: true` in `/etc/bigbluebutton/bbb-html5.yml`, then restart `bbb-html5` (`systemctl restart bbb-html5`) * Per user: utilize `userdata-bbb_transparent_listen_only=true` ### Upgraded components From fadd035bea981e4790b564810d381accc908427e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Thu, 10 Aug 2023 09:02:08 -0300 Subject: [PATCH 174/252] do not display reactions button if reactions are disabled --- .../imports/ui/components/actions-bar/container.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx index 7cc7f8a840..2075d3c0a0 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx @@ -51,8 +51,10 @@ const RAISE_HAND_BUTTON_ENABLED = Meteor.settings.public.app.raiseHandActionButt const RAISE_HAND_BUTTON_CENTERED = Meteor.settings.public.app.raiseHandActionButton.centered; const isReactionsButtonEnabled = () => { + const USER_REACTIONS_ENABLED = Meteor.settings.public.userReaction.enabled; const REACTIONS_BUTTON_ENABLED = Meteor.settings.public.app.reactionsButton.enabled; - return getFromUserSettings('enable-reactions-button', REACTIONS_BUTTON_ENABLED); + + return USER_REACTIONS_ENABLED && REACTIONS_BUTTON_ENABLED; }; export default withTracker(() => ({ From 06e451234e70c50789e8a502a0de1f76c481426d Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Thu, 10 Aug 2023 09:43:43 -0400 Subject: [PATCH 175/252] Updates for file bigbluebutton-html5/public/locales/en.json in gl on branch v2.6.x-release (#18512) * Translate en.json in gl 100% translated source file: 'en.json' on 'gl'. --------- Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- bigbluebutton-html5/public/locales/gl.json | 60 +++++++++++----------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/bigbluebutton-html5/public/locales/gl.json b/bigbluebutton-html5/public/locales/gl.json index e92f0d87b5..a9f6b6c857 100644 --- a/bigbluebutton-html5/public/locales/gl.json +++ b/bigbluebutton-html5/public/locales/gl.json @@ -91,7 +91,7 @@ "app.notes.disabled": "Fixado na área multimedia", "app.notes.notesDropdown.covertAndUpload": "Converter as notas en presentación", "app.notes.notesDropdown.pinNotes": "Fixar as notas no encerado", - "app.notes.notesDropdown.unpinNotes": "Soltar as notas", + "app.notes.notesDropdown.unpinNotes": "Desprender as notas", "app.notes.notesDropdown.notesOptions": "Opcións das notas", "app.pads.hint": "Prema Esc para enfocar a barra de ferramentas do caderno de notas", "app.user.activityCheck": "Comprobar a actividade do usuario", @@ -123,8 +123,8 @@ "app.userlist.menu.removeConfirmation.desc": "Impedir que este usuario volva unirse á sesión.", "app.userList.menu.muteUserAudio.label": "Desactivar o son do usuario", "app.userList.menu.unmuteUserAudio.label": "Devolverlle o son ao usuario", - "app.userList.menu.webcamPin.label": " Fixar a cámara web do usuario", - "app.userList.menu.webcamUnpin.label": "Soltar a cámara web do usuario", + "app.userList.menu.webcamPin.label": "Fixar a cámara web do usuario", + "app.userList.menu.webcamUnpin.label": "Desprender a cámara web do usuario", "app.userList.menu.giveWhiteboardAccess.label" : "Dar acceso ao encerado", "app.userList.menu.removeWhiteboardAccess.label": "Retirar o acceso ao encerado", "app.userList.menu.ejectUserCameras.label": "Pechar as cámaras", @@ -155,14 +155,14 @@ "app.userList.userOptions.disableNotes": "As notas compartidas agora están bloqueadas", "app.userList.userOptions.hideUserList": "A lista de usuarios agora está agochada para os espectadores", "app.userList.userOptions.webcamsOnlyForModerator": "Só os moderadores poden ver as cámaras web dos convidados (por mor dos axustes de bloqueo)", - "app.userList.content.participants.options.clearedStatus": "Limparonse todos os estados de usuario", + "app.userList.content.participants.options.clearedStatus": "Limpáronse todos os estados dos usuarios", "app.userList.userOptions.enableCam": "As cámaras web dos espectadores están activadas", "app.userList.userOptions.enableMic": "Os micrófonos dos espectadores están activados", "app.userList.userOptions.enablePrivChat": "A parola privada está activada", "app.userList.userOptions.enablePubChat": "A parola pública está activada", "app.userList.userOptions.enableNotes": "As notas compartidas agora están activadas", "app.userList.userOptions.showUserList": "A lista de usuarios agora amosase aos espectadores", - "app.userList.userOptions.enableOnlyModeratorWebcam": "Pode activar agora a súaúa cámara web, todos poderán velo", + "app.userList.userOptions.enableOnlyModeratorWebcam": "Pode activar agora a súaúa cámara web, todos poderán vela", "app.userList.userOptions.savedNames.title": "Lista de usuarios en xuntanza {0} en {1}", "app.userList.userOptions.sortedFirstName.heading": "Ordenado por nome:", "app.userList.userOptions.sortedLastName.heading": "Ordenado por apelido:", @@ -172,7 +172,7 @@ "app.media.autoplayAlertDesc": "Permitir acceso", "app.media.screenshare.start": "Comezou a pantalla compartida", "app.media.screenshare.end": "Rematou a pantalla compartida", - "app.media.screenshare.endDueToDataSaving": "Detívose a compartición de pantalla por mor do aforro de datos", + "app.media.screenshare.endDueToDataSaving": "A compartición de pantalla foi detida por mor do aforro de datos", "app.media.screenshare.unavailable": "A pantalla compartida non está dispoñíbel", "app.media.screenshare.notSupported": "Este navegador non admite a compartición de pantalla.", "app.media.screenshare.autoplayBlockedDesc": "Necesitamos o seu permiso para amosarlle a pantalla do presentador", @@ -191,7 +191,7 @@ "app.meeting.endedByUserMessage": "Esta sesión foi rematada por {0}", "app.meeting.endedByNoModeratorMessageSingular": "A xuntanza rematou porque non estivo presente ningún moderador durante un minuto", "app.meeting.endedByNoModeratorMessagePlural": "A xuntanza rematou porque ningún moderador estivo presente durante {0} minutos", - "app.meeting.endedMessage": "Será reenviado á pantalla de inicio", + "app.meeting.endedMessage": "Vai ser reenviado á pantalla de inicio", "app.meeting.alertMeetingEndsUnderMinutesSingular": "A xuntanza pecharase nun minuto.", "app.meeting.alertMeetingEndsUnderMinutesPlural": "A xuntanza pecharase en {0} minutos.", "app.meeting.alertBreakoutEndsUnderMinutesPlural": "A sala parcial pecharase en {0} minutos.", @@ -230,7 +230,7 @@ "app.presentation.presentationToolbar.zoomInDesc": "Ampliar a presentación", "app.presentation.presentationToolbar.zoomOutLabel": "Afastarse", "app.presentation.presentationToolbar.zoomOutDesc": "Reducir a presentación", - "app.presentation.presentationToolbar.zoomReset": "Restaurar o zoom", + "app.presentation.presentationToolbar.zoomReset": "Restabelecer o zoom", "app.presentation.presentationToolbar.zoomIndicator": "Porcentaxe de zoom actual", "app.presentation.presentationToolbar.fitToWidth": "Axustar ao largo", "app.presentation.presentationToolbar.fitToPage": "Axustar á páxina", @@ -240,7 +240,7 @@ "app.presentation.placeholder": "Non hai ningunha presentación activa actualmente", "app.presentationUploder.title": "Presentación", "app.presentationUploder.message": "Como presentador ten a posibilidade de enviar calquera documento de Office ou ficheiro PDF. Recomendamos o ficheiro PDF para obter os mellores resultados. Asegúrese de seleccionar unha presentación usando a caixa de selección do círculo do lado esquerdo.", - "app.presentationUploader.exportHint": "Ao seleccionar \"Enviando á parola\" fornéceselle aos usuarios unha ligazón para descargar con anotacións na parola pública.", + "app.presentationUploader.exportHint": "Ao seleccionar «Enviar á parola» fornéceselle aos usuarios unha ligazón para descargar con anotacións na parola pública.", "app.presentationUploader.exportToastHeader": "Enviando á parola (elemento {0})", "app.presentationUploader.exportToastHeaderPlural": "Enviando á parola ({0} elementos)", "app.presentationUploader.exporting": "Enviando á parola", @@ -274,9 +274,9 @@ "app.presentationUploder.upload.413": "O ficheiro é demasiado grande, superou o máximo de {0} MB", "app.presentationUploder.genericError": "Ouh! algo foi mal…", "app.presentationUploder.upload.408": "Solicitar o tempo de espera do testemuño de envío.", - "app.presentationUploder.upload.404": "404: o testemuño de envío non válido", + "app.presentationUploder.upload.404": "404: o testemuño de envío non é válido", "app.presentationUploder.upload.401": "Produciuse un fallo na solicitude do tempo de espera do testemuño de envío.", - "app.presentationUploder.conversion.conversionProcessingSlides": "Procesando páxina {0} de {1}", + "app.presentationUploder.conversion.conversionProcessingSlides": "Procesando a páxina {0} de {1}", "app.presentationUploder.conversion.genericConversionStatus": "Convertendo ficheiros…", "app.presentationUploder.conversion.generatingThumbnail": "Xerando miniaturas…", "app.presentationUploder.conversion.generatedSlides": "Presentacións xeradas…", @@ -287,11 +287,11 @@ "app.presentationUploder.conversion.officeDocConversionInvalid": "Non foi posíbel procesar o documento de Office. Envíe un PDF no seu lugar.", "app.presentationUploder.conversion.officeDocConversionFailed": "Non foi posíbel procesar o documento de Office. Envíe un PDF no seu lugar.", "app.presentationUploder.conversion.pdfHasBigPage": "Non foi posíbel converter o ficheiro PDF. Tente optimizalo. Tamaño máximo de páxina {0}", - "app.presentationUploder.conversion.timeout": " Ouh! a conversión tomou demasiado tempo", + "app.presentationUploder.conversion.timeout": "Ouh! a conversión tomou demasiado tempo", "app.presentationUploder.conversion.pageCountFailed": "Produciuse un fallo ao determinar o número de páxinas.", - "app.presentationUploder.conversion.unsupportedDocument": "Extensión de ficheiro non se admitida", + "app.presentationUploder.conversion.unsupportedDocument": "A extensión de ficheiro non está admitida", "app.presentationUploder.removePresentationLabel": "Retirar a presentación", - "app.presentationUploder.setAsCurrentPresentation": "Estabelecer a presentación como actual", + "app.presentationUploder.setAsCurrentPresentation": "Definir a presentación como actual", "app.presentationUploder.tableHeading.filename": "Nome de ficheiro", "app.presentationUploder.tableHeading.options": "Opcións", "app.presentationUploder.tableHeading.status": "Estado", @@ -315,14 +315,14 @@ "app.poll.activePollInstruction": "Deixe este panel aberto para ver as respostas en tempo real da súa enquisa. Cando estea listo prema en «Publicar os resultados da enquisa» para publicar os resultados e rematar a enquisa.", "app.poll.dragDropPollInstruction": "Para encher os valores da enquisa, arrastre un ficheiro de texto cos valores da enquisa ao campo resaltado", "app.poll.customPollTextArea": "Encher os valores da enquisa", - "app.poll.publishLabel": "Publicar enquisa", + "app.poll.publishLabel": "Publicar a enquisa", "app.poll.cancelPollLabel": "Cancelar", "app.poll.backLabel": "Iniciar unha enquisa", "app.poll.closeLabel": "Pechar", "app.poll.waitingLabel": "Agardando respostas ({0}/{1})", "app.poll.ariaInputCount": "Opción da enquisa personalizada {0} de {1}", "app.poll.customPlaceholder": "Engadir opción de enquisa", - "app.poll.noPresentationSelected": "Non se seleccionou ningunha presentación Seleccione unha.", + "app.poll.noPresentationSelected": "Non foi seleccionada ningunha presentación Seleccione unha.", "app.poll.clickHereToSelect": "Prema aquí para seleccionar", "app.poll.question.label" : "Escriba a súa pregunta...", "app.poll.optionalQuestion.label" : "Escriba a súa pregunta (opcional)...", @@ -365,7 +365,7 @@ "app.poll.liveResult.usersTitle": "Usuarios", "app.poll.liveResult.responsesTitle": "Resposta", "app.poll.liveResult.secretLabel": "Esta é unha enquisa anónima. Non se amosan as respostas individuais.", - "app.poll.removePollOpt": "Retirouse a opción de enquisa {0}", + "app.poll.removePollOpt": "Foi retirada a opción de enquisa {0}", "app.poll.emptyPollOpt": "Baleiro", "app.polling.pollingTitle": "Opcións da enquisa", "app.polling.pollQuestionTitle": "Pregunta da enquisa", @@ -375,10 +375,10 @@ "app.polling.responseSecret": "Enquisa anónima: o presentador non pode ver a súa resposta.", "app.polling.responseNotSecret": "Enquisa normal: o presentador pode ver a súa resposta.", "app.polling.pollAnswerLabel": "Resposta á enquisa {0}", - "app.polling.pollAnswerDesc": "Selecciona esta opción para votar por {0}", + "app.polling.pollAnswerDesc": "Seleccione esta opción para votar por {0}", "app.failedMessage": "Desculpas, hai problemas para conectar co servidor.", "app.downloadPresentationButton.label": "Descargar a presentación orixinal", - "app.connectingMessage": "Conectandose…", + "app.connectingMessage": "Conectando…", "app.waitingMessage": "Desconectado. Tentando volver conectar en {0} segundos…", "app.retryNow": "Volver tentalo agora", "app.muteWarning.label": "Prema en {0} para silenciarse a vostede mesmo.", @@ -589,7 +589,7 @@ "app.actionsBar.actionsDropdown.selectRandUserLabel": "Seleccionar un usuario ao chou", "app.actionsBar.actionsDropdown.selectRandUserDesc": "Escoller ao chou un usuario entre os espectadores dispoñíbeis", "app.actionsBar.actionsDropdown.propagateLayoutLabel": "Propagación do deseño", - "app.actionsBar.emojiMenu.statusTriggerLabel": "Estabelecer o estado", + "app.actionsBar.emojiMenu.statusTriggerLabel": "Definir o estado", "app.actionsBar.emojiMenu.awayLabel": "Ausente", "app.actionsBar.emojiMenu.awayDesc": "Cambiar o seu estado a ausente", "app.actionsBar.emojiMenu.raiseHandLabel": "Erguer a man", @@ -812,8 +812,8 @@ "app.toast.chat.system": "Sistema", "app.toast.chat.poll": "Resultados da enquisa", "app.toast.chat.pollClick": "Publicáronse os resultados das enquisas. Prema aquí para velos.", - "app.toast.clearedEmoji.label": "Estado do emoji limpo", - "app.toast.setEmoji.label": "Estado do emoji cambiado a {0}", + "app.toast.clearedEmoji.label": "O «emoji» de estado foi limpado", + "app.toast.setEmoji.label": "O «emoji» de estado foi definido como {0}", "app.toast.meetingMuteOn.label": "Todos os usuarios foron silenciados", "app.toast.meetingMuteOnViewers.label": "Todos os espectadores foron silenciados", "app.toast.meetingMuteOff.label": "Desactivouse o silencio da xuntanza", @@ -868,7 +868,7 @@ "app.shortcut-help.whiteboard": "Encerado", "app.shortcut-help.zoomIn": "Achegarse", "app.shortcut-help.zoomOut": "Afastarse", - "app.shortcut-help.zoomFit": "Restaurar o zoom", + "app.shortcut-help.zoomFit": "Restabelecer o zoom", "app.shortcut-help.zoomSelect": "Zoom sobre a selección", "app.shortcut-help.flipH": "Voltear en horizontal", "app.shortcut-help.flipV": "Voltear en vertical", @@ -907,7 +907,7 @@ "app.guest-policy.button.askModerator": "Pregunta ao moderador", "app.guest-policy.button.alwaysAccept": "Aceptar sempre", "app.guest-policy.button.alwaysDeny": "Denegar sempre", - "app.guest-policy.policyBtnDesc": "Estabelece a política de convidados á xuntanza", + "app.guest-policy.policyBtnDesc": "Define a directiva de convidados á xuntanza", "app.connection-status.ariaTitle": "Estado da conexión modal", "app.connection-status.title": "Estado da conexión", "app.connection-status.description": "Ver o estado de conexión dos usuarios", @@ -1020,7 +1020,7 @@ "app.video.virtualBackground.custom": "Enviar dende o seu computador", "app.video.virtualBackground.remove": "Retirar a imaxe engadida", "app.video.virtualBackground.genericError": "Produciuse un fallo ao aplicar o efecto da cámara. Ténteo de novo.", - "app.video.virtualBackground.camBgAriaDesc": "Estabelece o fondo virtual da cámara web a {0}", + "app.video.virtualBackground.camBgAriaDesc": "Define o fondo virtual da cámara web a {0}", "app.video.virtualBackground.maximumFileSizeExceeded": "Superouse o tamaño máximo do ficheiro. ({0}MB)", "app.video.virtualBackground.typeNotAllowed": "Non se permite o tipo de ficheiro.", "app.video.virtualBackground.errorOnRead": "Produciuse un erro ao ler o ficheiro.", @@ -1142,7 +1142,7 @@ "app.createBreakoutRoom.numberOfRoomsError": "O número de salas non é válido", "app.createBreakoutRoom.duplicatedRoomNameError": "Non é posíbel duplicar o nome da sala.", "app.createBreakoutRoom.emptyRoomNameError": "O nome da sala non pode estar baleiro.", - "app.createBreakoutRoom.setTimeInMinutes": "Estabelecer a duración en (minutos)", + "app.createBreakoutRoom.setTimeInMinutes": "Definir a duración en (minutos)", "app.createBreakoutRoom.setTimeLabel": "Aplicar", "app.createBreakoutRoom.setTimeCancel": "Cancelar", "app.createBreakoutRoom.setTimeHigherThanMeetingTimeError": "A duración das salas parciais non pode exceder o tempo restante da xuntanza.", @@ -1187,7 +1187,7 @@ "app.layout.modal.pushLayoutLabel": "Aplicar para todos", "app.layout.modal.layoutToastLabel": "Cambiaron os axustes de deseño", "app.layout.modal.layoutSingular": "Deseño", - "app.layout.modal.layoutBtnDesc": "Estabelece o deseño como opción seleccionada", + "app.layout.modal.layoutBtnDesc": "Define o deseño como opción seleccionada", "app.layout.style.custom": "Personalizado", "app.layout.style.smart": "Disposición intelixente", "app.layout.style.presentationFocus": "Poñer o foco na presentación", @@ -1264,7 +1264,7 @@ "app.learningDashboard.userDetails.anonymousAnswer": "Enquisa anónima", "app.learningDashboard.userDetails.talkTime": "Tempo de conversa", "app.learningDashboard.userDetails.messages": "Mensaxes", - "app.learningDashboard.userDetails.emojis": "Emojis", + "app.learningDashboard.userDetails.emojis": "«Emojis»", "app.learningDashboard.userDetails.raiseHands": "Erguer as mans", "app.learningDashboard.userDetails.pollVotes": "Votos da enquisa", "app.learningDashboard.userDetails.onlineIndicator": "{0} tempo en liña", @@ -1273,7 +1273,7 @@ "app.learningDashboard.usersTable.colTalk": "Tempo de conversa", "app.learningDashboard.usersTable.colWebcam": "Tempo da cámara web", "app.learningDashboard.usersTable.colMessages": "Mensaxes", - "app.learningDashboard.usersTable.colEmojis": "Emojis", + "app.learningDashboard.usersTable.colEmojis": "«Emojis»", "app.learningDashboard.usersTable.colRaiseHands": "Erguer as mans", "app.learningDashboard.usersTable.colActivityScore": "Puntuación da actividade", "app.learningDashboard.usersTable.colStatus": "Estado", @@ -1297,7 +1297,7 @@ "app.learningDashboard.statusTimelineTable.thumbnail": "Miniatura da presentación", "app.learningDashboard.statusTimelineTable.presentation": "Presentación", "app.learningDashboard.statusTimelineTable.pageNumber": "Páxina", - "app.learningDashboard.statusTimelineTable.setAt": "Estabelecido en", + "app.learningDashboard.statusTimelineTable.setAt": "Definir en", "app.learningDashboard.errors.invalidToken": "O testemuño de sesión non é válido", "app.learningDashboard.errors.dataUnavailable": "Os datos xa non están dispoñíbeis", "mobileApp.portals.list.empty.addFirstPortal.label": "Engada o seu primeiro portal usando o botón anterior,", From dc1066e247a1f2cf4c4a6a58c6e36fbe09491f8e Mon Sep 17 00:00:00 2001 From: Paulo Lanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Thu, 10 Aug 2023 11:10:47 -0300 Subject: [PATCH 176/252] build(bbb-webrtc-sfu): v2.11.0-beta.3 Adjust naming of an environment variable configuration (`AUDIO_HOLD_HIST`) --- bbb-webrtc-sfu.placeholder.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbb-webrtc-sfu.placeholder.sh b/bbb-webrtc-sfu.placeholder.sh index cc0b179671..099e75d507 100755 --- a/bbb-webrtc-sfu.placeholder.sh +++ b/bbb-webrtc-sfu.placeholder.sh @@ -1 +1 @@ -git clone --branch v2.11.0-beta.2 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu +git clone --branch v2.11.0-beta.3 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu From 34c153a424d01697d9e6bae67b56d01d179bed4b Mon Sep 17 00:00:00 2001 From: Anton Georgiev Date: Thu, 10 Aug 2023 11:15:13 -0400 Subject: [PATCH 177/252] chore: Bump FreeSWITCH to 'master' from end of July 2023 --- freeswitch.placeholder.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/freeswitch.placeholder.sh b/freeswitch.placeholder.sh index 15a4d75f4e..d81d9b6016 100755 --- a/freeswitch.placeholder.sh +++ b/freeswitch.placeholder.sh @@ -2,5 +2,7 @@ mkdir freeswitch cd freeswitch git init git remote add origin https://github.com/signalwire/freeswitch.git -git fetch --depth 1 origin v1.10.9 -git checkout FETCH_HEAD +#git fetch --depth 1 origin v1.10.9 +#git checkout FETCH_HEAD +git fetch origin master +git checkout 67840823c178153cb013014c4fa780fe233612cb # branch 'master' on Aug 10, 2023 From 67141471d851de9384732b837aa5c0022311c6b5 Mon Sep 17 00:00:00 2001 From: Anton Georgiev Date: Thu, 10 Aug 2023 13:38:06 -0400 Subject: [PATCH 178/252] chore: Bump BBB version to 2.7.0-rc.1 --- bigbluebutton-config/bigbluebutton-release | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bigbluebutton-config/bigbluebutton-release b/bigbluebutton-config/bigbluebutton-release index 9ac823cff6..8d977171d1 100644 --- a/bigbluebutton-config/bigbluebutton-release +++ b/bigbluebutton-config/bigbluebutton-release @@ -1,2 +1 @@ -BIGBLUEBUTTON_RELEASE=2.7.0-beta.3 - +BIGBLUEBUTTON_RELEASE=2.7.0-rc.1 From dfd8f5b4a42773e1f67037e1160a392379e66148 Mon Sep 17 00:00:00 2001 From: Anton Georgiev Date: Thu, 10 Aug 2023 14:21:54 -0400 Subject: [PATCH 179/252] build: Update the descriptions for bbb-playback packages --- build/packages-template/bbb-playback-notes/build.sh | 2 +- build/packages-template/bbb-playback-podcast/build.sh | 2 +- build/packages-template/bbb-playback-presentation/build.sh | 2 +- build/packages-template/bbb-playback-screenshare/build.sh | 2 +- build/packages-template/bbb-playback-video/build.sh | 2 +- build/packages-template/bbb-playback/build.sh | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/packages-template/bbb-playback-notes/build.sh b/build/packages-template/bbb-playback-notes/build.sh index e228e9729e..78f0293818 100755 --- a/build/packages-template/bbb-playback-notes/build.sh +++ b/build/packages-template/bbb-playback-notes/build.sh @@ -40,7 +40,7 @@ fpm -s dir -C ./staging -n $PACKAGE \ --version $VERSION --epoch $EPOCH \ --post-install before-install.sh \ --after-install after-install.sh \ - --description "BigBluebutton playback of notes" \ + --description "BigBlueButton notes recording format" \ $DIRECTORIES \ $OPTS diff --git a/build/packages-template/bbb-playback-podcast/build.sh b/build/packages-template/bbb-playback-podcast/build.sh index 9da0a94297..f81a28491e 100755 --- a/build/packages-template/bbb-playback-podcast/build.sh +++ b/build/packages-template/bbb-playback-podcast/build.sh @@ -40,7 +40,7 @@ fpm -s dir -C ./staging -n $PACKAGE \ --version $VERSION --epoch $EPOCH \ --post-install before-install.sh \ --after-install after-install.sh \ - --description "BigBluebutton playback in podcast" \ + --description "BigBlueButton podcast recording format" \ $DIRECTORIES \ $OPTS \ -d 'yq (>= 3)' -d 'yq (<< 4)' diff --git a/build/packages-template/bbb-playback-presentation/build.sh b/build/packages-template/bbb-playback-presentation/build.sh index 3cc6a059e2..4ac5040b0f 100755 --- a/build/packages-template/bbb-playback-presentation/build.sh +++ b/build/packages-template/bbb-playback-presentation/build.sh @@ -40,7 +40,7 @@ fpm -s dir -C ./staging -n $PACKAGE \ --version $VERSION --epoch $EPOCH \ --post-install before-install.sh \ --after-install after-install.sh \ - --description "BigBluebutton playback of presentation" \ + --description "BigBlueButton presentation recording format" \ $DIRECTORIES \ $OPTS \ -d 'yq (>= 3)' -d 'yq (<< 4)' diff --git a/build/packages-template/bbb-playback-screenshare/build.sh b/build/packages-template/bbb-playback-screenshare/build.sh index 96a8adcbf8..e4d12aa64f 100755 --- a/build/packages-template/bbb-playback-screenshare/build.sh +++ b/build/packages-template/bbb-playback-screenshare/build.sh @@ -43,7 +43,7 @@ fpm -s dir -C ./staging -n $PACKAGE \ --version $VERSION --epoch $EPOCH \ --post-install before-install.sh \ --after-install after-install.sh \ - --description "BigBluebutton playback of screenshare" \ + --description "BigBlueButton screenshare recording format" \ $DIRECTORIES \ $OPTS diff --git a/build/packages-template/bbb-playback-video/build.sh b/build/packages-template/bbb-playback-video/build.sh index 34f7e174a3..c7a0fbcf48 100755 --- a/build/packages-template/bbb-playback-video/build.sh +++ b/build/packages-template/bbb-playback-video/build.sh @@ -38,7 +38,7 @@ fpm -s dir -C ./staging -n $PACKAGE \ --version $VERSION --epoch $EPOCH \ --post-install before-install.sh \ --after-install after-install.sh \ - --description "BigBluebutton playback of presentation" \ + --description "BigBlueButton video recording format" \ $DIRECTORIES \ $OPTS \ -d 'yq (>= 3)' -d 'yq (<< 4)' diff --git a/build/packages-template/bbb-playback/build.sh b/build/packages-template/bbb-playback/build.sh index 5a639dd8ac..d808f856b4 100755 --- a/build/packages-template/bbb-playback/build.sh +++ b/build/packages-template/bbb-playback/build.sh @@ -46,6 +46,6 @@ cp playback.nginx staging/usr/share/bigbluebutton/nginx fpm -s dir -C ./staging -n $PACKAGE \ --version $VERSION --epoch $EPOCH \ --after-install after-install.sh \ - --description "BigBlueButton playback" \ + --description "Player for BigBlueButton presentation format recordings" \ $DIRECTORIES \ $OPTS From b6c73b4a04a7289e35ff7ff0fc99b833113f250b Mon Sep 17 00:00:00 2001 From: Anton B Date: Thu, 10 Aug 2023 16:14:33 -0300 Subject: [PATCH 180/252] fix: remove leaveAudio data-test from the reaction buttons --- .../ui/components/actions-bar/reactions-button/component.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx index 1e1e43a6c8..80f8505b91 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx @@ -110,7 +110,6 @@ const ReactionsButton = (props) => { actions.push({ label: , key: id, - dataTest: 'leaveAudio', onClick: () => handleReactionSelect(native), customStyles: actionCustomStyles, }); From 6d4c140f76e0a7e7b92ddcd6fa3aceb9c2e5a5d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Thu, 10 Aug 2023 16:28:21 -0300 Subject: [PATCH 181/252] auto close reactions settings --- .../reactions-button/component.jsx | 3 ++- .../reactions-button/container.jsx | 2 ++ .../submenus/application/component.jsx | 25 +++++++++++++++++++ .../private/config/settings.yml | 1 + bigbluebutton-html5/public/locales/en.json | 1 + 5 files changed, 31 insertions(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx index 1e1e43a6c8..ba8112ad9f 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx @@ -16,6 +16,7 @@ const ReactionsButton = (props) => { raiseHand, isMobile, currentUserReaction, + autoCloseReactionsBar, } = props; const [showEmojiPicker, setShowEmojiPicker] = useState(false); @@ -152,7 +153,7 @@ const ReactionsButton = (props) => { isHorizontal={!isMobile} isMobile={isMobile} roundButtons={true} - keepOpen={true} + keepOpen={!autoCloseReactionsBar} opts={{ id: 'reactions-dropdown-menu', keepMounted: true, diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/container.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/container.jsx index 04bcd94965..0d1ab2ef03 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/container.jsx @@ -6,6 +6,7 @@ import ReactionsButton from './component'; import actionsBarService from '../service'; import UserReactionService from '/imports/ui/components/user-reaction/service'; import { SMALL_VIEWPORT_BREAKPOINT } from '/imports/ui/components/layout/enums'; +import SettingsService from '/imports/ui/services/settings'; const ReactionsButtonContainer = ({ ...props }) => { const layoutContextDispatch = layoutDispatch(); @@ -32,6 +33,7 @@ export default injectIntl(withTracker(() => { emoji: currentUser.emoji, currentUserReaction: currentUserReaction.reaction, raiseHand: currentUser.raiseHand, + autoCloseReactionsBar: SettingsService?.application?.autoCloseReactionsBar, }; })(ReactionsButtonContainer)); diff --git a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx index c00dd65193..f2d9a6a459 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx @@ -125,6 +125,9 @@ const intlMessages = defineMessages({ disableLabel: { id: 'app.videoDock.webcamDisableLabelAllCams', }, + autoCloseReactionsBarLabel: { + id: 'app.actionsBar.reactions.autoCloseReactionsBarLabel', + }, }); class ApplicationMenu extends BaseMenu { @@ -510,6 +513,28 @@ class ApplicationMenu extends BaseMenu { + + + + + {displaySettingsStatus(settings.autoCloseReactionsBar)} + this.handleToggle('autoCloseReactionsBar')} + ariaLabel={`${intl.formatMessage(intlMessages.autoCloseReactionsBarLabel)} - ${displaySettingsStatus(settings.autoCloseReactionsBar, false)}`} + showToggleLabel={showToggleLabel} + /> + + + + diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 3c7e3d8a49..1cf2fe8649 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -191,6 +191,7 @@ public: wakeLock: true paginationEnabled: true whiteboardToolbarAutoHide: false + autoCloseReactionsBar: false darkTheme: false # fallbackLocale: if the locale the client is loaded in does not have a # translation a string, it will use the translation from the locale diff --git a/bigbluebutton-html5/public/locales/en.json b/bigbluebutton-html5/public/locales/en.json index 7782b67c39..b290d7953a 100755 --- a/bigbluebutton-html5/public/locales/en.json +++ b/bigbluebutton-html5/public/locales/en.json @@ -641,6 +641,7 @@ "app.actionsBar.reactions.reactionsButtonLabel": "Reactions bar", "app.actionsBar.reactions.raiseHand": "Raise your hand", "app.actionsBar.reactions.lowHand": "Lower your hand", + "app.actionsBar.reactions.autoCloseReactionsBarLabel": "Auto close the reactions bar", "app.actionsBar.emojiMenu.statusTriggerLabel": "Set status", "app.actionsBar.emojiMenu.awayLabel": "Away", "app.actionsBar.emojiMenu.awayDesc": "Change your status to away", From dfa359cae00ad8158e22cd971a86aeaf54729b5e Mon Sep 17 00:00:00 2001 From: prlanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Thu, 10 Aug 2023 22:29:45 -0300 Subject: [PATCH 182/252] build(bbb-webrtc-sfu): v2.11.0-beta.4 - Added: mediasoup_rtp_scores (histogram, off by default) --- bbb-webrtc-sfu.placeholder.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbb-webrtc-sfu.placeholder.sh b/bbb-webrtc-sfu.placeholder.sh index 099e75d507..46bc6d83ec 100755 --- a/bbb-webrtc-sfu.placeholder.sh +++ b/bbb-webrtc-sfu.placeholder.sh @@ -1 +1 @@ -git clone --branch v2.11.0-beta.3 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu +git clone --branch v2.11.0-beta.4 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu From a3264bb1526b6e98a31f4050bd4ac1e20718c15a Mon Sep 17 00:00:00 2001 From: prlanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Thu, 10 Aug 2023 22:46:21 -0300 Subject: [PATCH 183/252] build(freeswitch): bump to signalwire/freeswitch/master@41507363 See https://github.com/signalwire/freeswitch/commit/41507363f3fffcdad547b168e55fbe3383a24c3d --- freeswitch.placeholder.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freeswitch.placeholder.sh b/freeswitch.placeholder.sh index d81d9b6016..8c172b0532 100755 --- a/freeswitch.placeholder.sh +++ b/freeswitch.placeholder.sh @@ -5,4 +5,4 @@ git remote add origin https://github.com/signalwire/freeswitch.git #git fetch --depth 1 origin v1.10.9 #git checkout FETCH_HEAD git fetch origin master -git checkout 67840823c178153cb013014c4fa780fe233612cb # branch 'master' on Aug 10, 2023 +git checkout 41507363f3fffcdad547b168e55fbe3383a24c3d # branch 'master' on Aug 10, 2023 From 35dcdd6407609fe938a32cfb5e93fb7aa9695a2c Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Fri, 11 Aug 2023 04:59:51 +0000 Subject: [PATCH 184/252] Translate en.json in ja 100% translated source file: 'en.json' on 'ja'. --- bigbluebutton-html5/public/locales/ja.json | 81 ++++++++++++++++++++-- 1 file changed, 74 insertions(+), 7 deletions(-) diff --git a/bigbluebutton-html5/public/locales/ja.json b/bigbluebutton-html5/public/locales/ja.json index 6fadc0153d..ab87747b74 100644 --- a/bigbluebutton-html5/public/locales/ja.json +++ b/bigbluebutton-html5/public/locales/ja.json @@ -7,6 +7,7 @@ "app.chat.locked": "チャットがロック状態のため、メッセージを送ることができません", "app.chat.inputLabel": "チャット {0} へメッセージ入力", "app.chat.emojiButtonLabel": "絵文字選択", + "app.chat.loadMoreButtonLabel": "もっと見る", "app.chat.inputPlaceholder": "メッセージ {0}", "app.chat.titlePublic": "公開チャット", "app.chat.titlePrivate": "{0} との非公開チャット", @@ -24,8 +25,11 @@ "app.chat.breakoutDurationUpdated": "小会議の時間は現在{0}分です", "app.chat.breakoutDurationUpdatedModerator": "小会議の時間は現在{0}分です。通知が送られました。", "app.chat.emptyLogLabel": "チャットログは空です", + "app.chat.away": "は席を外しています", + "app.chat.notAway": "はすでに在席です", "app.chat.clearPublicChatMessage": "チャット履歴は司会者により消去されました", "app.chat.multi.typing": "複数の人が入力中", + "app.chat.someone.typing": "誰かが入力中", "app.chat.one.typing": "{0}が入力中", "app.chat.two.typing": "{0}と{1}が入力中", "app.chat.copySuccess": "チャットのやりとりがコピーされました", @@ -36,6 +40,7 @@ "app.emojiPicker.clear": "消す", "app.emojiPicker.categories.label": "絵文字カテゴリー", "app.emojiPicker.categories.people": "人物&からだ", + "app.emojiPicker.categories.reactions": "リアクション", "app.emojiPicker.categories.nature": "動物&自然", "app.emojiPicker.categories.foods": "食べ物&飲み物", "app.emojiPicker.categories.places": "旅行&場所", @@ -51,6 +56,23 @@ "app.emojiPicker.skintones.4": "中間の肌色", "app.emojiPicker.skintones.5": "少し濃い肌色", "app.emojiPicker.skintones.6": "濃い肌色", + "app.timer.title": "タイマー", + "app.timer.stopwatch.title": "ストップウォッチ", + "app.timer.timer.title": "タイマー", + "app.timer.hideTimerLabel": "時間を隠す", + "app.timer.button.stopwatch": "ストップウォッチ", + "app.timer.button.timer": "タイマー", + "app.timer.button.start": "スタート", + "app.timer.button.stop": "ストップ", + "app.timer.button.reset": "リセット", + "app.timer.hours": "時間", + "app.timer.minutes": "分", + "app.timer.seconds": "秒", + "app.timer.songs": "音楽", + "app.timer.noTrack": "音楽なし", + "app.timer.track1": "リラックス", + "app.timer.track2": "安らぎ", + "app.timer.track3": "ハッピー", "app.captions.label": "字幕", "app.captions.menu.close": "閉じる", "app.captions.menu.start": "開始", @@ -102,6 +124,7 @@ "app.userList.messagesTitle": "メッセージ", "app.userList.notesTitle": "メモ", "app.userList.notesListItem.unreadContent": "共有ノートに新しいコンテンツがあります", + "app.userList.timerTitle": "時間", "app.userList.captionsTitle": "字幕", "app.userList.presenter": "発表者", "app.userList.you": "自分", @@ -116,6 +139,8 @@ "app.userList.menuTitleContext": "使用可能オプション", "app.userList.chatListItem.unreadSingular": "1 通の新規メッセージ", "app.userList.chatListItem.unreadPlural": "{0} 通の新規メッセージ", + "app.userList.menu.away": "自分を退席中にする", + "app.userList.menu.notAway": "自分を出席中にする", "app.userList.menu.chat.label": "非公開チャット", "app.userList.menu.clearStatus.label": "ステータスを消去する", "app.userList.menu.removeUser.label": "このユーザーを退室させる", @@ -140,6 +165,8 @@ "app.userList.userOptions.muteAllDesc": "会議の全ユーザーをミュートする", "app.userList.userOptions.clearAllLabel": "全てのステータスを消す", "app.userList.userOptions.clearAllDesc": "ユーザーの全ステータスアイコンを消去する", + "app.userList.userOptions.clearAllReactionsLabel": "全てのリアクションを消去", + "app.userList.userOptions.clearAllReactionsDesc": "全てのリアクション絵文字をユーザーから消去する", "app.userList.userOptions.muteAllExceptPresenterLabel": "発表者以外全員ミュート", "app.userList.userOptions.muteAllExceptPresenterDesc": "この会議の発表者以外の全ユーザーをミュートにする", "app.userList.userOptions.unmuteAllLabel": "会議のミュートを解除", @@ -156,6 +183,7 @@ "app.userList.userOptions.hideUserList": "ユーザーリストは視聴者に表示されません", "app.userList.userOptions.webcamsOnlyForModerator": "司会者のみが視聴者のウェブカメラを見ることができます(ロック設定による)", "app.userList.content.participants.options.clearedStatus": "全てのユーザーのステータスをクリアしました", + "app.userList.content.participants.options.clearedReactions": "全てのユーザーのリアクションをクリアしました", "app.userList.userOptions.enableCam": "視聴者のウェブカメラが利用可能です", "app.userList.userOptions.enableMic": "視聴者のマイクが利用可能です", "app.userList.userOptions.enablePrivChat": "非公開チャットが利用可能です", @@ -177,6 +205,11 @@ "app.media.screenshare.notSupported": "画面共有はこのブラウザではサポートされていません。", "app.media.screenshare.autoplayBlockedDesc": "発表者の画面の共有を許可してください", "app.media.screenshare.autoplayAllowLabel": "共有画面を見る", + "app.media.cameraAsContent.start": "プレゼンカメラを開始しました", + "app.media.cameraAsContent.end": "プレゼンカメラを停止しました", + "app.media.cameraAsContent.endDueToDataSaving": "通信量制限のためプレゼンカメラを停止しました", + "app.media.cameraAsContent.autoplayBlockedDesc": "発表者のカメラ画像の表示を許可してください", + "app.media.cameraAsContent.autoplayAllowLabel": "プレゼンカメラをみる", "app.screenshare.presenterLoadingLabel": "あなたの画面を共有しようとしています", "app.screenshare.viewerLoadingLabel": "発表者の画面を共有しようとしています", "app.screenshare.presenterSharingLabel": "現在あなたの画面を共有中です", @@ -185,6 +218,9 @@ "app.screenshare.screenshareRetryOtherEnvError": "コード{0} 画面をシェアできませんでした。別のブラウザや端末を使ってもう一度試してみてください。", "app.screenshare.screenshareUnsupportedEnv": "コード{0} ブラウザがサポートされていません。別のブラウザや端末を使ってもう一度試してみてください。", "app.screenshare.screensharePermissionError": "コード{0} 画面キャプチャーの許可を与える必要があります。", + "app.cameraAsContent.presenterLoadingLabel": "あなたのカメラを準備しています", + "app.cameraAsContent.viewerLoadingLabel": "発表者のカメラを準備しています", + "app.cameraAsContent.presenterSharingLabel": "カメラ画像で発表中です", "app.meeting.ended": "この会議は終了しました", "app.meeting.meetingTimeRemaining": "会議の残り時間:{0}", "app.meeting.meetingTimeHasEnded": "時間終了。会議はまもなく終了します。", @@ -199,6 +235,7 @@ "app.presentation.hide": "プレゼンテーションを非表示", "app.presentation.notificationLabel": "現在のプレゼンテーション", "app.presentation.downloadLabel": "ダウンロード", + "app.presentation.actionsLabel": "操作", "app.presentation.slideContent": "スライドコンテンツ", "app.presentation.startSlideContent": "スライドコンテンツ開始", "app.presentation.endSlideContent": "スライドコンテンツ終了", @@ -240,7 +277,7 @@ "app.presentation.placeholder": "現在アクティブなプレゼンテーションはありません", "app.presentationUploder.title": "プレゼンテーション", "app.presentationUploder.message": "発表者はOffice書類やPDFを自由にアップロードできますが、BigBlueButtonはPDFファイルを最も再現性良く表示できます。また、左側の丸いチェックボックスによってプレゼンファイルが選択されていることを確認してください。", - "app.presentationUploader.exportHint": "「チャットへ送信」を選ぶと、ホワイトボードへの書き込みをダウンロードするためのリンクが、公開チャットを通じてユーザーに提供されます。", + "app.presentationUploader.exportHint": "「書き出しオプション」メニューから、書き込みのない元のプレゼンファイルのダウンロードを可能にしたり、書き込み付きのファイルをダウンロードするためのリンクを公開チャットでユーザーに提供したりできます。", "app.presentationUploader.exportToastHeader": "チャットへ送信中 ({0} 個)", "app.presentationUploader.exportToastHeaderPlural": "チャットへ送信中 ({0} 個)", "app.presentationUploader.exporting": "チャットへ送信中", @@ -248,10 +285,17 @@ "app.presentationUploader.collecting": "{1}枚のスライドのうち{0}番目を抽出中...", "app.presentationUploader.processing": "{1}枚のスライドのうち{0}番目に書き込み中...", "app.presentationUploader.sent": "送信終了", - "app.presentationUploader.exportingTimeout": "エクスポートに時間がかかりすぎています...", + "app.presentationUploader.exportingTimeout": "書き出しに時間がかかりすぎています...", "app.presentationUploader.export": "チャットへ送信", + "app.presentationUploader.exportCurrentStatePresentation": "書き込みを含めてプレゼンをダウンロードするためのリンクを送付", + "app.presentationUploader.enableOriginalPresentationDownload": "書き込みのない元のプレゼンをダウンロード可能にする", + "app.presentationUploader.disableOriginalPresentationDownload": "プレゼンのダウンロードを許可しない", + "app.presentationUploader.dropdownExportOptions": "書き出しオプション", "app.presentationUploader.export.linkAvailable": "{0}をダウンロードするためのリンクが、公開チャットから利用できます。", + "app.presentationUploader.export.downloadButtonAvailable": "プレゼン {0}をダウンロードするためのボタンが利用できます。", "app.presentationUploader.export.notAccessibleWarning": "一部の視聴者には、ファイルが読み取りづらい可能性があります", + "app.presentationUploader.export.originalLabel": "元ファイル", + "app.presentationUploader.export.inCurrentStateLabel": "書き込み付きファイル", "app.presentationUploader.currentPresentationLabel": "プレゼンテーションファイル", "app.presentationUploder.extraHint": "重要:それぞれのファイルが{0}MB、{1}ページを超えないようにしてください。", "app.presentationUploder.uploadLabel": "アップロード", @@ -446,7 +490,10 @@ "app.actionsBar.actionsDropdown.minimizePresentationLabel": "プレゼンパネルを最小化", "app.actionsBar.actionsDropdown.minimizePresentationDesc": "プレゼンパネルを最小化するボタン", "app.actionsBar.actionsDropdown.layoutModal": "レイアウト設定モーダル", + "app.actionsBar.actionsDropdown.shareCameraAsContent": "カメラ画像をプレゼンコンテンツとして共有", + "app.actionsBar.actionsDropdown.unshareCameraAsContent": "カメラの共有をやめる", "app.screenshare.screenShareLabel" : "画面共有", + "app.cameraAsContent.cameraAsContentLabel" : "プレゼン用カメラ", "app.submenu.application.applicationSectionTitle": "アプリケーション", "app.submenu.application.animationsLabel": "アニメーション効果", "app.submenu.application.audioFilterLabel": "マイク音声の自動補正", @@ -458,8 +505,9 @@ "app.submenu.application.currentSize": "現在 {0}", "app.submenu.application.languageLabel": "使用言語", "app.submenu.application.languageOptionLabel": "言語を選択", - "app.submenu.application.noLocaleOptionLabel": "アクティブなロケールがありません", + "app.submenu.application.noLocaleOptionLabel": "アクティブな言語がありません", "app.submenu.application.paginationEnabledLabel": "ビデオのページ付け", + "app.submenu.application.wakeLockEnabledLabel": "スリープ停止", "app.submenu.application.layoutOptionLabel": "レイアウトのタイプ", "app.submenu.application.pushLayoutLabel": "レイアウトを強制", "app.submenu.application.localeDropdown.af": "アフリカーンス語", @@ -569,6 +617,8 @@ "app.talkingIndicator.moreThanMaxIndicatorsWereTalking" : "{0}+人が話していました。", "app.talkingIndicator.wasTalking" : "{0}人が話し終えました", "app.actionsBar.actionsDropdown.actionsLabel": "アクション", + "app.actionsBar.actionsDropdown.activateTimerStopwatchLabel": "タイマー / ストップウォッチを使用", + "app.actionsBar.actionsDropdown.deactivateTimerStopwatchLabel": "タイマー / ストップウォッチを消す", "app.actionsBar.actionsDropdown.presentationLabel": "プレゼンファイルをアップロード/管理", "app.actionsBar.actionsDropdown.initPollLabel": "投票を初期化", "app.actionsBar.actionsDropdown.desktopShareLabel": "画面を共有", @@ -588,7 +638,9 @@ "app.actionsBar.actionsDropdown.takePresenterDesc": "自分を新しい発表者とする", "app.actionsBar.actionsDropdown.selectRandUserLabel": "参加者を無作為に指名", "app.actionsBar.actionsDropdown.selectRandUserDesc": "現視聴者の中からランダムに一人あてます", - "app.actionsBar.actionsDropdown.propagateLayoutLabel": "レイアウトを全体で統一", + "app.actionsBar.reactions.reactionsButtonLabel": "リアクションバー", + "app.actionsBar.reactions.raiseHand": "手をあげる", + "app.actionsBar.reactions.lowHand": "手をおろす", "app.actionsBar.emojiMenu.statusTriggerLabel": "ステータス設定", "app.actionsBar.emojiMenu.awayLabel": "不在", "app.actionsBar.emojiMenu.awayDesc": "スタータスを「不在」にする", @@ -817,8 +869,15 @@ "app.toast.meetingMuteOn.label": "全てのユーザーがミュートされました", "app.toast.meetingMuteOnViewers.label": "すべての視聴者はミュートされました", "app.toast.meetingMuteOff.label": "会議のミュートを解除しました", + "app.toast.wakeLock.offerTitle": "会議中、画面スリープ機能を停止しますか?", + "app.toast.wakeLock.offerAccept": "はい!", + "app.toast.wakeLock.offerDecline": "今はやめておく", + "app.toast.wakeLock.acquireSuccess": "画面スリープを停止しています!設定メニューから再開することができます。", + "app.toast.wakeLock.acquireFailed": "画面スリープの停止に失敗しました。", "app.toast.setEmoji.raiseHand": "手をあげました", "app.toast.setEmoji.lowerHand": "手をおろしました", + "app.toast.setEmoji.away": "ステータスを退席中にしました", + "app.toast.setEmoji.notAway": "退席中のステータスをはずしました", "app.toast.promotedLabel": "あなたは司会者になりました", "app.toast.demotedLabel": "あなたは視聴者になりました", "app.notification.recordingStart": "この会議は録画されています", @@ -901,6 +960,7 @@ "app.lock-viewers.button.cancel": "キャンセル", "app.lock-viewers.locked": "禁止", "app.lock-viewers.hideViewersCursor": "他の視聴者のカーソルを表示する", + "app.lock-viewers.hideAnnotationsLabel": "他のビューアーの書き込みを見る", "app.guest-policy.ariaTitle": "入室許可設定モーダル", "app.guest-policy.title": "入室許可設定", "app.guest-policy.description": "会議室の入室許可設定を変更する", @@ -908,6 +968,7 @@ "app.guest-policy.button.alwaysAccept": "常に許可", "app.guest-policy.button.alwaysDeny": "常に拒否", "app.guest-policy.policyBtnDesc": "会議のゲストポリシーを設定", + "app.guest-policy.feedbackMessage": "現在のゲストポリシー:", "app.connection-status.ariaTitle": "接続状況モーダル", "app.connection-status.title": "接続状況", "app.connection-status.description": "ユーザーの接続状況の閲覧", @@ -965,6 +1026,7 @@ "app.videoPreview.webcamPreviewLabel": "ウェブカメラのプレビュー", "app.videoPreview.webcamSettingsTitle": "ウェブカメラ設定", "app.videoPreview.webcamEffectsTitle": "ウェブカメラの視覚効果", + "app.videoPreview.cameraAsContentSettingsTitle": "プレゼン用カメラ", "app.videoPreview.webcamVirtualBackgroundLabel": "バーチャル背景の設定", "app.videoPreview.webcamVirtualBackgroundDisabledLabel": "このデバイスはバーチャル背景をサポートしていません", "app.videoPreview.webcamNotFoundLabel": "ウェブカメラが見つかりません", @@ -1093,6 +1155,10 @@ "app.videoDock.webcamFocusDesc": "選択したウェブカメラにフォーカスする", "app.videoDock.webcamUnfocusLabel": "フォーカスを外す", "app.videoDock.webcamUnfocusDesc": "選択したウェブカメラのフォーカスを外す", + "app.videoDock.webcamDisableLabel": "セルフィーモードを停止", + "app.videoDock.webcamDisableLabelAllCams": "セルフィーモードを停止(全てのカメラ)", + "app.videoDock.webcamEnableLabel": "セルフィーモードを開始", + "app.videoDock.webcamDisableDesc": "セルフィーモードが停止中です", "app.videoDock.webcamPinLabel": "ピン止め", "app.videoDock.webcamPinDesc": "選択したウェブカメラをピン止めする", "app.videoDock.webcamFullscreenLabel": "ウェブカメラを全画面表示にする", @@ -1133,8 +1199,10 @@ "app.createBreakoutRoom.addRoomTime": "小会議室の制限時間を増やす", "app.createBreakoutRoom.addParticipantLabel": "+参加者を追加", "app.createBreakoutRoom.freeJoin": "ユーザーに小会議室を選択させる", + "app.createBreakoutRoom.manageRoomsLabel": "会議室の管理", "app.createBreakoutRoom.captureNotes": "小会議室終了時に共有ノートを保存", "app.createBreakoutRoom.captureSlides": "小会議室終了時にホワイトボードを保存", + "app.createBreakoutRoom.sendInvitationToMods": "司会者を割り当てられた人に招待を送る", "app.createBreakoutRoom.leastOneWarnBreakout": "小会議室には最低1人のユーザーが必要です。", "app.createBreakoutRoom.minimumDurationWarnBreakout": "小会議室の最小利用時間は{0}分です。", "app.createBreakoutRoom.modalDesc": "やり方:ユーザーの名前をドラッグ&ドロップして、特定の小会議室に割りふってください。", @@ -1180,10 +1248,9 @@ "app.debugWindow.form.chatLoggerLabel": "チャットロガーレベルをテストする", "app.debugWindow.form.button.apply": "設定", "app.layout.modal.title": "レイアウト", - "app.layout.modal.confirm": "確認", - "app.layout.modal.cancel": "キャンセル", + "app.layout.modal.update": "更新", + "app.layout.modal.updateAll": "全員を更新", "app.layout.modal.layoutLabel": "レイアウトを選択してください", - "app.layout.modal.keepPushingLayoutLabel": "レイアウトを全員に強制する", "app.layout.modal.pushLayoutLabel": "全員に強制", "app.layout.modal.layoutToastLabel": "レイアウトの設定が変更されました", "app.layout.modal.layoutSingular": "レイアウト", From 8a7d18383e5774a75ac5cc4b4b1bf73fcb44832a Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Fri, 11 Aug 2023 12:56:15 +0000 Subject: [PATCH 185/252] Translate en.json in tr 100% translated source file: 'en.json' on 'tr'. --- bigbluebutton-html5/public/locales/tr.json | 117 ++++++++++++++++----- 1 file changed, 92 insertions(+), 25 deletions(-) diff --git a/bigbluebutton-html5/public/locales/tr.json b/bigbluebutton-html5/public/locales/tr.json index 74c66e06ed..0df9985114 100644 --- a/bigbluebutton-html5/public/locales/tr.json +++ b/bigbluebutton-html5/public/locales/tr.json @@ -7,6 +7,7 @@ "app.chat.locked": "Sohbet kilitli, ileti gönderilemez", "app.chat.inputLabel": "{0} sohbeti için ileti girişi", "app.chat.emojiButtonLabel": "Emoji seçici", + "app.chat.loadMoreButtonLabel": "Daha Fazla", "app.chat.inputPlaceholder": "İleti {0}", "app.chat.titlePublic": "Herkese açık sohbet", "app.chat.titlePrivate": "{0} ile özel sohbet", @@ -19,13 +20,16 @@ "app.chat.dropdown.copy": "Kopyala", "app.chat.dropdown.save": "Kaydet", "app.chat.label": "Sohbet", - "app.chat.offline": "Çevrimdışı", + "app.chat.offline": "Çevrim dışı", "app.chat.pollResult": "Oylama sonuçları", "app.chat.breakoutDurationUpdated": "Çalışma grubu zamanı {0} dakika", "app.chat.breakoutDurationUpdatedModerator": "Çalışma grubu odaları {0} dakika için açıldı ve bir bildirim gönderildi.", "app.chat.emptyLogLabel": "Sohbet günlüğü boş", + "app.chat.away": "Uzakta", + "app.chat.notAway": "Artık uzakta değil", "app.chat.clearPublicChatMessage": "Herkese açık sohbet geçmişi sorumlu tarafından temizlendi", "app.chat.multi.typing": "Birkaç kullanıcı yazıyor", + "app.chat.someone.typing": "Birisi yazıyor", "app.chat.one.typing": "{0} yazıyor...", "app.chat.two.typing": "{0} ve {1} yazıyor...", "app.chat.copySuccess": "Sohbet yazışmaları kopyalandı", @@ -36,6 +40,7 @@ "app.emojiPicker.clear": "Temizle", "app.emojiPicker.categories.label": "Emoji kategorileri", "app.emojiPicker.categories.people": "Kişiler ve beden", + "app.emojiPicker.categories.reactions": "Etkileşimler", "app.emojiPicker.categories.nature": "Hayvanlar ve doğa", "app.emojiPicker.categories.foods": "Yiyecek ve içecek", "app.emojiPicker.categories.places": "Gezi ve yerler", @@ -51,6 +56,23 @@ "app.emojiPicker.skintones.4": "Orta ten rengi", "app.emojiPicker.skintones.5": "Orta koyu ten rengi", "app.emojiPicker.skintones.6": "Koyu ten rengi", + "app.timer.title": "Süre", + "app.timer.stopwatch.title": "Sayaç", + "app.timer.timer.title": "Zamanlayıcı", + "app.timer.hideTimerLabel": "Süreyi gizle", + "app.timer.button.stopwatch": "Sayaç", + "app.timer.button.timer": "Zamanlayıcı", + "app.timer.button.start": "Başlat", + "app.timer.button.stop": "Durdur", + "app.timer.button.reset": "Sıfırla", + "app.timer.hours": "saat", + "app.timer.minutes": "dakika", + "app.timer.seconds": "saniye", + "app.timer.songs": "Şarkılar", + "app.timer.noTrack": "Şarkı yok", + "app.timer.track1": "Rahatlatıcı", + "app.timer.track2": "Sakin", + "app.timer.track3": "Mutlu", "app.captions.label": "Alt yazılar", "app.captions.menu.close": "Kapat", "app.captions.menu.start": "Başlat", @@ -75,7 +97,7 @@ "app.captions.dictationOffDesc": " Konuşma tanımayı kapatır", "app.captions.speech.start": "Konuşma tanıma başlatıldı", "app.captions.speech.stop": "Konuşma tanıma durduruldu", - "app.captions.speech.error": "Sesli algılama web tarayıcı uyumsuzluğu ya da belirli bir süre sessizlikten dolayı durduruldu.", + "app.captions.speech.error": "Sesli algılama tarayıcı uyumsuzluğu ya da belirli bir süre sessizlikten dolayı durduruldu.", "app.confirmation.skipConfirm": "Bir daha sorulmasın", "app.confirmation.virtualBackground.title": "Yeni bir sanal arka plan başlat", "app.confirmation.virtualBackground.description": "{0} anal arka plan olarak eklenecek. İlerlemek istiyor musunuz?", @@ -102,6 +124,7 @@ "app.userList.messagesTitle": "İletiler", "app.userList.notesTitle": "Notlar", "app.userList.notesListItem.unreadContent": "Paylaşılmış notlar bölümünde yeni içerik var", + "app.userList.timerTitle": "Süre", "app.userList.captionsTitle": "Alt yazılar", "app.userList.presenter": "Sunucu", "app.userList.you": "Siz", @@ -116,6 +139,8 @@ "app.userList.menuTitleContext": "Kullanılabilecek seçenekler", "app.userList.chatListItem.unreadSingular": "Bir yeni ileti", "app.userList.chatListItem.unreadPlural": "{0} yeni ileti", + "app.userList.menu.away": "Kendinizi uzakta olarak ayarlayın", + "app.userList.menu.notAway": "Kendinizi etkin olarak ayarlayın", "app.userList.menu.chat.label": "Özel sohbet başlat", "app.userList.menu.clearStatus.label": "Durumu temizle", "app.userList.menu.removeUser.label": "Kullanıcıyı sil", @@ -140,6 +165,8 @@ "app.userList.userOptions.muteAllDesc": "Toplantıdaki tüm kullanıcıların sesini kapatır", "app.userList.userOptions.clearAllLabel": "Tüm durum simgelerini temizle", "app.userList.userOptions.clearAllDesc": "Kullanıcıların tüm durum simgelerini temizler", + "app.userList.userOptions.clearAllReactionsLabel": "Tüm tepkileri temizle", + "app.userList.userOptions.clearAllReactionsDesc": "Kullanıcıların tüm emoji simgelerini temizler", "app.userList.userOptions.muteAllExceptPresenterLabel": "Sunucu dışındaki tüm kullanıcıların sesini kapat", "app.userList.userOptions.muteAllExceptPresenterDesc": "Toplantıda sunum yapan kişi dışındaki tüm kullanıcıların sesini kapatır", "app.userList.userOptions.unmuteAllLabel": "Toplantının sesini aç", @@ -156,6 +183,7 @@ "app.userList.userOptions.hideUserList": "Kullanıcı listesi izleyicilerden gizlendi", "app.userList.userOptions.webcamsOnlyForModerator": "İzleyicilerin kamerasını yalnızca sorumlular görebilir (kilit ayarları nedeniyle).", "app.userList.content.participants.options.clearedStatus": "Tüm kullanıcı durumları temizlendi", + "app.userList.content.participants.options.clearedReactions": "Tüm kullanıcı tepkileri temizlendi", "app.userList.userOptions.enableCam": "İzleyicilerin kameraları açık", "app.userList.userOptions.enableMic": "İzleyicilerin mikrofonları açık", "app.userList.userOptions.enablePrivChat": "Özel sohbet açık", @@ -174,17 +202,25 @@ "app.media.screenshare.end": "Ekran paylaşımı kapatıldı", "app.media.screenshare.endDueToDataSaving": "Veri tasarrufu nedeniyle ekran paylaşımı durduruldu", "app.media.screenshare.unavailable": "Ekran paylaşımı kullanılamıyor", - "app.media.screenshare.notSupported": "Bu web tarayıcıda ekran paylaşımı desteklenmiyor.", + "app.media.screenshare.notSupported": "Bu tarayıcıda ekran paylaşımı desteklenmiyor.", "app.media.screenshare.autoplayBlockedDesc": "Sunucunun ekranını görüntüleyebilmemiz için izin vermeniz gerekiyor.", "app.media.screenshare.autoplayAllowLabel": "Paylaşılan ekranı görüntüle", + "app.media.cameraAsContent.start": "Kamera sunulmaya açıldı", + "app.media.cameraAsContent.end": "Kameranın sunulması kapatıldı", + "app.media.cameraAsContent.endDueToDataSaving": "Kameranın sunulması veri harcamamak için kapatıldı", + "app.media.cameraAsContent.autoplayBlockedDesc": "Sunucunun kamerasını size gösterebilmemiz için izin vermeniz gerekiyor.", + "app.media.cameraAsContent.autoplayAllowLabel": "Sunulan kamerayı görüntüle", "app.screenshare.presenterLoadingLabel": "Ekran paylaşımınız yükleniyor", "app.screenshare.viewerLoadingLabel": "Sunucunun ekranı yükleniyor", "app.screenshare.presenterSharingLabel": "Ekranınız şu anda paylaşılıyor", "app.screenshare.screenshareFinalError": "Kod {0}. Ekran paylaşılamadı.", "app.screenshare.screenshareRetryError": "Kod {0}. Ekranı paylaşmayı yeniden deneyin.", - "app.screenshare.screenshareRetryOtherEnvError": "Kod {0}. Ekran paylaşılamadı. Farklı bir web tarayıcı veya aygıt kullanarak yeniden deneyin.", - "app.screenshare.screenshareUnsupportedEnv": "Kod {0}. Web tarayıcı desteklenmiyor. Farklı bir web tarayıcı ya da aygıt kullanarak yeniden deneyin.", + "app.screenshare.screenshareRetryOtherEnvError": "Kod {0}. Ekran paylaşılamadı. Farklı bir tarayıcı veya aygıt kullanarak yeniden deneyin.", + "app.screenshare.screenshareUnsupportedEnv": "Kod {0}. Tarayıcı desteklenmiyor. Farklı bir tarayıcı ya da aygıt kullanarak yeniden deneyin.", "app.screenshare.screensharePermissionError": "Kod {0}. Ekran yakalama izninin verilmesi gerekiyor.", + "app.cameraAsContent.presenterLoadingLabel": "Kameranız yükleniyor", + "app.cameraAsContent.viewerLoadingLabel": "Sunucunun kamerası yükleniyor", + "app.cameraAsContent.presenterSharingLabel": "Kameranızı sunuyorsunuz", "app.meeting.ended": "Bu oturum kapatıldı", "app.meeting.meetingTimeRemaining": "Toplantının bitmesine kalan süre: {0}", "app.meeting.meetingTimeHasEnded": "Zaman doldu. Toplantı birazdan sona erecek", @@ -199,6 +235,7 @@ "app.presentation.hide": "Sunumu gizle", "app.presentation.notificationLabel": "Geçerli sunum", "app.presentation.downloadLabel": "İndir", + "app.presentation.actionsLabel": "Eylemler", "app.presentation.slideContent": "Slayt içeriği", "app.presentation.startSlideContent": "Slayt içeriği başlangıcı", "app.presentation.endSlideContent": "Slayt içeriği bitişi", @@ -250,8 +287,15 @@ "app.presentationUploader.sent": "Gönderildi", "app.presentationUploader.exportingTimeout": "Dışa aktarma işlemi çok uzun sürüyor...", "app.presentationUploader.export": "Sohbete gönder", + "app.presentationUploader.exportCurrentStatePresentation": "Sunumun geçerli durumundaki indirme bağlantısını gönder", + "app.presentationUploader.enableOriginalPresentationDownload": "Özgün sunum indirilebilsin", + "app.presentationUploader.disableOriginalPresentationDownload": "Özgün sunum indirilemesin", + "app.presentationUploader.dropdownExportOptions": "Dışa aktarma seçenekleri", "app.presentationUploader.export.linkAvailable": "{0} indirmek için bağlantıyı herkese açık sohbette bulabilirsiniz.", + "app.presentationUploader.export.downloadButtonAvailable": "{0} sunumu için indirme düğmesi kullanılabilir.", "app.presentationUploader.export.notAccessibleWarning": "erişilebilirlik uyumluluğu olmayabilir", + "app.presentationUploader.export.originalLabel": "Özgün", + "app.presentationUploader.export.inCurrentStateLabel": "Geçerli durumda", "app.presentationUploader.currentPresentationLabel": "Geçerli sunum", "app.presentationUploder.extraHint": "ÖNEMLİ: Her bir dosya {0} MB boyutundan ve {1} sayfadan küçük olmalıdır.", "app.presentationUploder.uploadLabel": "Yükle", @@ -446,7 +490,10 @@ "app.actionsBar.actionsDropdown.minimizePresentationLabel": "Sunumu simge durumuna küçült", "app.actionsBar.actionsDropdown.minimizePresentationDesc": "Sunumu simge durumuna küçülten düğme", "app.actionsBar.actionsDropdown.layoutModal": "Ekran düzeni ayarları üste açılan penceresi", + "app.actionsBar.actionsDropdown.shareCameraAsContent": "Kamerayı içerik olarak aç", + "app.actionsBar.actionsDropdown.unshareCameraAsContent": "Kamerayı içerik olarak kapat", "app.screenshare.screenShareLabel" : "Ekran paylaşımı", + "app.cameraAsContent.cameraAsContentLabel" : "Kamerayı sun", "app.submenu.application.applicationSectionTitle": "Uygulama", "app.submenu.application.animationsLabel": "Canlandırmalar", "app.submenu.application.audioFilterLabel": "Mikrofon için ses süzgeçleri", @@ -460,6 +507,7 @@ "app.submenu.application.languageOptionLabel": "Dil seçin", "app.submenu.application.noLocaleOptionLabel": "Etkin bir dil bulunamadı", "app.submenu.application.paginationEnabledLabel": "Görüntü sayfalandırma", + "app.submenu.application.wakeLockEnabledLabel": "Uyandırma kilidi", "app.submenu.application.layoutOptionLabel": "Ekran düzeni türü", "app.submenu.application.pushLayoutLabel": "Ekran düzenini gönder", "app.submenu.application.localeDropdown.af": "Afrikaanca", @@ -569,6 +617,8 @@ "app.talkingIndicator.moreThanMaxIndicatorsWereTalking" : "{0}+ konuşuyordu", "app.talkingIndicator.wasTalking" : "{0} sustu", "app.actionsBar.actionsDropdown.actionsLabel": "İşlemler", + "app.actionsBar.actionsDropdown.activateTimerStopwatchLabel": "Zamanlayıcıyı aç", + "app.actionsBar.actionsDropdown.deactivateTimerStopwatchLabel": "Zamanlayıcıyı kapat", "app.actionsBar.actionsDropdown.presentationLabel": "Sunumları yükle/yönet", "app.actionsBar.actionsDropdown.initPollLabel": "Oylama başlat", "app.actionsBar.actionsDropdown.desktopShareLabel": "Ekranını paylaş", @@ -588,7 +638,9 @@ "app.actionsBar.actionsDropdown.takePresenterDesc": "Kendinizi yeni sunucu olarak atayın", "app.actionsBar.actionsDropdown.selectRandUserLabel": "Rastgele kullanıcı seç", "app.actionsBar.actionsDropdown.selectRandUserDesc": "Var olan izleyicilerden rastgele bir kullanıcı seçer", - "app.actionsBar.actionsDropdown.propagateLayoutLabel": "Ekran düzenini yayınla", + "app.actionsBar.reactions.reactionsButtonLabel": "Tepki çubuğu", + "app.actionsBar.reactions.raiseHand": "El kaldır", + "app.actionsBar.reactions.lowHand": "Eli indir", "app.actionsBar.emojiMenu.statusTriggerLabel": "Durumu ayarla", "app.actionsBar.emojiMenu.awayLabel": "Uzakta", "app.actionsBar.emojiMenu.awayDesc": "Durumunuzu uzakta yapar", @@ -616,7 +668,7 @@ "app.actionsBar.captions.stop": "Alt yazıları görüntülemeyi durdur", "app.audioNotification.audioFailedError1001": "WebSocket bağlantısı kesildi (hata 1001)", "app.audioNotification.audioFailedError1002": "WebSocket bağlantısı kurulamadı (hata 1002)", - "app.audioNotification.audioFailedError1003": "Web tarayıcı sürümü desteklenmiyor (hata 1003)", + "app.audioNotification.audioFailedError1003": "Tarayıcı sürümü desteklenmiyor (hata 1003)", "app.audioNotification.audioFailedError1004": "Çağrı sırasında sorun çıktı (neden={0}) (hata 1004)", "app.audioNotification.audioFailedError1005": "Çağrı beklenmedik bir şekilde sona erdi (hata 1005)", "app.audioNotification.audioFailedError1006": "Çağrı zaman aşımına uğradı (hata 1006)", @@ -654,7 +706,7 @@ "app.audioModal.iOSErrorDescription": "Şu anda iOS için Chrome üzerinde ses ve görüntü desteklenmiyor.", "app.audioModal.iOSErrorRecommendation": "iOS için Safari kullanmanız önerilir.", "app.audioModal.audioChoiceDesc": "Bu toplantıya katılacağınız ses ayarını seçin", - "app.audioModal.unsupportedBrowserLabel": "Tam olarak desteklenmeyen bir web tarayıcı kullanıyorsunuz. Lütfen tam destek almak için {0} ya da {1} kullanın.", + "app.audioModal.unsupportedBrowserLabel": "Tam olarak desteklenmeyen bir tarayıcı kullanıyorsunuz. Lütfen tam destek almak için {0} ya da {1} kullanın.", "app.audioModal.closeLabel": "Kapat", "app.audioModal.yes": "Evet", "app.audioModal.no": "Hayır", @@ -663,9 +715,9 @@ "app.audioModal.echoTestTitle": "Bu size özel bir yankı testidir. Biraz konuşun. Ses duydunuz mu?", "app.audioModal.settingsTitle": "Ses ayarlarınızı değiştirin", "app.audioModal.helpTitle": "Ortam aygıtlarınızla ilgili bir sorun çıktı", - "app.audioModal.helpText": "Mikrofonunuza erişme izni verdiniz mi? Görüşmeye sesinizle katılmak istediğinizde, ortam aygıtlarınıza erişme izni vermeniz için bir pencere görüntülenir. Sesli konferansa katılabilmek için onay vermeniz gerekir. İzin penceresi görüntülenmediyse, web tarayıcınızın ayarlarından mikrofon izinlerini değiştirmeyi deneyin.", + "app.audioModal.helpText": "Mikrofonunuza erişme izni verdiniz mi? Görüşmeye sesinizle katılmak istediğinizde, ortam aygıtlarınıza erişme izni vermeniz için bir pencere görüntülenir. Sesli konferansa katılabilmek için onay vermeniz gerekir. İzin penceresi görüntülenmediyse, tarayıcınızın ayarlarından mikrofon izinlerini değiştirmeyi deneyin.", "app.audioModal.help.noSSL": "Bu sayfa güvenli bağlantı kullanmıyor. Mikrofon erişimine izin verilebilmesi için sayfa bağlantısı HTTPS . ile kurulmalıdır. Lütfen sunucu yöneticisi ile görüşün.", - "app.audioModal.help.macNotAllowed": "Mac sistem tercihleriniz mikrofonunuza erişimi engelliyor gibi görünüyor. Sistem Tercihleri > ​​Güvenlik ve Gizlilik > Gizlilik > Mikrofon bölümünü açın ve kullandığınız web tarayıcının işaretlenmiş olduğunu doğrulayın.", + "app.audioModal.help.macNotAllowed": "Mac sistem tercihleriniz mikrofonunuza erişimi engelliyor gibi görünüyor. Sistem Tercihleri > ​​Güvenlik ve Gizlilik > Gizlilik > Mikrofon bölümünü açın ve kullandığınız tarayıcının işaretlenmiş olduğunu doğrulayın.", "app.audioModal.audioDialTitle": "Telefonunuz ile katılın", "app.audioDial.audioDialDescription": "Ara", "app.audioDial.audioDialConfrenceText": "ve konferansa katılmak için PIN numarasını yazın:", @@ -696,7 +748,7 @@ "app.audio.speakers": "Hoparlörler", "app.audio.noDeviceFound": "Herhangi bir aygıt bulunamadı", "app.audio.audioSettings.titleLabel": "Ses ayarlarınızı seçin", - "app.audio.audioSettings.descriptionLabel": "Lütfen web tarayıcınızda mikrofonunuzu paylaşmanızı isteyen bir pencere görüntüleneceğini unutmayın.", + "app.audio.audioSettings.descriptionLabel": "Lütfen tarayıcınızda mikrofonunuzu paylaşmanızı isteyen bir pencere görüntüleneceğini unutmayın.", "app.audio.audioSettings.microphoneSourceLabel": "Mikrofon kaynağı", "app.audio.audioSettings.speakerSourceLabel": "Hoparlör kaynağı", "app.audio.audioSettings.testSpeakerLabel": "Hoparlörünüzü deneyin", @@ -717,7 +769,7 @@ "app.audio.captions.button.transcriptionSettings": "Yazıya dökme ayarları", "app.audio.captions.speech.title": "Otomatik yazıya dökme", "app.audio.captions.speech.disabled": "Devre dışı", - "app.audio.captions.speech.unsupported": "Web tarayıcınız konuşma tanıma özelliğini desteklemiyor. Sesiniz yazıya dökülemeyecek", + "app.audio.captions.speech.unsupported": "Tarayıcınız konuşma tanıma özelliğini desteklemiyor. Sesiniz yazıya dökülemeyecek", "app.audio.captions.select.de-DE": "Almanca", "app.audio.captions.select.en-US": "İngilizce", "app.audio.captions.select.es-ES": "İspanyolca", @@ -764,7 +816,7 @@ "app.error.disconnected.rejoin": "Yeniden katılmak için sayfayı yenileyin.", "app.error.userLoggedOut": "Kullanıcı oturumu kapattığından sessionToken geçersiz", "app.error.ejectedUser": "Kullanıcı oturumdan çıkarıldığından sessionToken geçersiz", - "app.error.joinedAnotherWindow": "Bu oturum başka bir web tarayıcı penceresinde açık gibi görünüyor.", + "app.error.joinedAnotherWindow": "Bu oturum başka bir tarayıcı penceresinde açık gibi görünüyor.", "app.error.userBanned": "Kullanıcı yasaklandı", "app.error.leaveLabel": "Yeniden oturum aç", "app.error.fallback.presentation.title": "Bir sorun çıktı", @@ -817,8 +869,15 @@ "app.toast.meetingMuteOn.label": "Tüm kullanıcıların sesi kapatıldı", "app.toast.meetingMuteOnViewers.label": "Tüm izleyicilerin sesi kapatıldı", "app.toast.meetingMuteOff.label": "Toplantının sesi açıldı", + "app.toast.wakeLock.offerTitle": "Toplantı süresince aygıtınızın ekranı açık kalsın mı?", + "app.toast.wakeLock.offerAccept": "Evet!", + "app.toast.wakeLock.offerDecline": "Şimdi değil", + "app.toast.wakeLock.acquireSuccess": "Uyandurma kilidi açık! Ayarlar menüsünden kapatabilirsiniz.", + "app.toast.wakeLock.acquireFailed": "Uyandırma kilidi açılırken sorun çıktı.", "app.toast.setEmoji.raiseHand": "Elinizi kaldırdınız", "app.toast.setEmoji.lowerHand": "Eliniz indirildi", + "app.toast.setEmoji.away": "Durumunuzu uzakta olarak ayarladınız", + "app.toast.setEmoji.notAway": "Uzakta durumunuzu kaldırdınız", "app.toast.promotedLabel": "Rolünüz Sorumlu olarak güncellendi", "app.toast.demotedLabel": "Rolünüz İzleyici olarak güncellendi", "app.notification.recordingStart": "Bu oturum şu anda kaydediliyor", @@ -901,6 +960,7 @@ "app.lock-viewers.button.cancel": "Vazgeç", "app.lock-viewers.locked": "Kilitli", "app.lock-viewers.hideViewersCursor": "Diğer izleyicilerin imleçlerine bakın", + "app.lock-viewers.hideAnnotationsLabel": "Diğer izleyicilerin notlarına bakın", "app.guest-policy.ariaTitle": "Konuk ilkesi ayarları üste açılan penceresi", "app.guest-policy.title": "Konuk ilkesi", "app.guest-policy.description": "Toplantı konuk ilkesi ayarını değiştir", @@ -908,6 +968,7 @@ "app.guest-policy.button.alwaysAccept": "Her zaman kabul et", "app.guest-policy.button.alwaysDeny": "Her zaman reddet", "app.guest-policy.policyBtnDesc": "Toplantı konuk ilkesini ayarla", + "app.guest-policy.feedbackMessage": "Konuk ilkesi:", "app.connection-status.ariaTitle": "Bağlantı durumu üste açılan penceresi", "app.connection-status.title": "Bağlantı durumu", "app.connection-status.description": "Kullanıcıların bağlantı durumunu görüntüle", @@ -920,7 +981,7 @@ "app.connection-status.settings": "Ayarlarınız yapılıyor", "app.connection-status.no": "Hayır", "app.connection-status.notification": "Bağlantınızda kayıp algılandı", - "app.connection-status.offline": "çevrimdışı", + "app.connection-status.offline": "çevrim dışı", "app.connection-status.clientNotRespondingWarning": "İstemci yanıt vermiyor", "app.connection-status.audioUploadRate": "Ses yükleme hızı", "app.connection-status.audioDownloadRate": "Ses indirme hızı", @@ -965,6 +1026,7 @@ "app.videoPreview.webcamPreviewLabel": "Kamera ön izlemesi", "app.videoPreview.webcamSettingsTitle": "Kamera ayarları", "app.videoPreview.webcamEffectsTitle": "Kamera görsel etkileri", + "app.videoPreview.cameraAsContentSettingsTitle": "Kamerayı sun", "app.videoPreview.webcamVirtualBackgroundLabel": "Sanal arka plan ayarları", "app.videoPreview.webcamVirtualBackgroundDisabledLabel": "Bu aygıt sanal arka planları desteklemiyor", "app.videoPreview.webcamNotFoundLabel": "Kamera bulunamadı", @@ -985,15 +1047,15 @@ "app.video.sharingError": "Kamera paylaşılırken sorun çıktı", "app.video.abortError": "Kameranız bilinmeyen bir sorun nedeniyle kullanılamıyor", "app.video.overconstrainedError": "Kameranız bu kalite profilini desteklemiyor", - "app.video.securityError": "Web tarayıcınızda kamera kullanımını devre dışı bırakılmış. Farklı bir web tarayıcısı deneyin", + "app.video.securityError": "Tarayıcınızda kamera kullanımını devre dışı bırakılmış. Farklı bir tarayıcı deneyin", "app.video.typeError": "Kamera kalitesi profili geçersiz. Yöneticiniz ile görüşün", "app.video.notFoundError": "Kamera bulunamadı. Lütfen bağlı olduğunu denetleyin", - "app.video.notAllowed": "Kamera paylaşma izni verilmemiş, lütfen web tarayıcı izinlerini verdiğinizden emin olun", + "app.video.notAllowed": "Kamera paylaşma izni verilmemiş, lütfen tarayıcı izinlerini verdiğinizden emin olun", "app.video.notSupportedError": "Kamera görüntüsü yalnızca güvenli kaynaklar ile paylaşabilir, SSL sertifikanızın geçerli olduğundan emin olun", "app.video.notReadableError": "Kamera görüntüsü alınamadı. Lütfen kamerayı başka bir uygulamanın kullanmadığından emin olun", "app.video.timeoutError": "Tarayıcı zamanında yanıt vermedi.", "app.video.genericError": "Aygıtta bilinmeyen bir sorun çıktı ({0})", - "app.video.inactiveError": "Kameranız beklenmedik şekilde durdu. Lütfen web tarayıcınızın izinlerini gözden geçirin", + "app.video.inactiveError": "Kameranız beklenmedik şekilde durdu. Lütfen tarayıcınızın izinlerini gözden geçirin", "app.video.mediaTimedOutError": "Kamera yayın akışı kesildi. Yeniden paylaşmayı deneyin", "app.video.mediaFlowTimeout1020": "Ortam sunucuya ulaşamadı (hata 1020)", "app.video.suggestWebcamLock": "İzleyicilerin kameraları zorla kilitlensin mi?", @@ -1035,7 +1097,7 @@ "app.switchButton.expandLabel": "Ekran paylaşımı görüntüsünü genişlet", "app.switchButton.shrinkLabel": "Ekran paylaşımı görüntüsünü daralt", "app.sfu.mediaServerConnectionError2000": "Ortam sunucusu ile bağlantı kurulamadı (hata 2000)", - "app.sfu.mediaServerOffline2001": "Ortam sunucusu çevrimdışı. Lütfen bir süre sonra yeniden deneyin (hata 2001)", + "app.sfu.mediaServerOffline2001": "Ortam sunucusu çevrim dışı. Lütfen bir süre sonra yeniden deneyin (hata 2001)", "app.sfu.mediaServerNoResources2002": "Ortam sunucunda kullanılabilecek kaynak yok (hata 2002)", "app.sfu.mediaServerRequestTimeout2003": "Ortam sunucu istekleri zaman aşımına uğradı (hata 2003)", "app.sfu.serverIceGatheringFailed2021": "Ortam sunucusu bağlantı adaylarını alamadı (ICE hatası 2021)", @@ -1093,6 +1155,10 @@ "app.videoDock.webcamFocusDesc": "Seçilmiş kamera görüntüsüne odaklanır", "app.videoDock.webcamUnfocusLabel": "Odaktan kaldır", "app.videoDock.webcamUnfocusDesc": "Seçilmiş kamera görüntüsünü odaktan kaldırır", + "app.videoDock.webcamDisableLabel": "Kendini görmeyi kapat", + "app.videoDock.webcamDisableLabelAllCams": "Kendini görmeyi kapat (tüm kameralar)", + "app.videoDock.webcamEnableLabel": "Kendini görmeyi aç", + "app.videoDock.webcamDisableDesc": "Kendini görme kapalı", "app.videoDock.webcamPinLabel": "Sabitle", "app.videoDock.webcamPinDesc": "Seçilmiş kamera görüntüsünü sabitler", "app.videoDock.webcamFullscreenLabel": "Kamerayı tam ekran görüntüle", @@ -1133,8 +1199,10 @@ "app.createBreakoutRoom.addRoomTime": "Çalışma grubu odası süresini uzat", "app.createBreakoutRoom.addParticipantLabel": "+ Katılımcı ekle", "app.createBreakoutRoom.freeJoin": "Kullanıcılar katılacakları çalışma grubu odasını seçebilsin", + "app.createBreakoutRoom.manageRoomsLabel": "Oda yönetimi", "app.createBreakoutRoom.captureNotes": "Çalışma grubu odaları kapatıldığında paylaşılan notları al", "app.createBreakoutRoom.captureSlides": "Çalışma grubu odaları kapatıldığında beyaz tahtayı al", + "app.createBreakoutRoom.sendInvitationToMods": "İlgili sorumlulara çağrı gönder", "app.createBreakoutRoom.leastOneWarnBreakout": "Bir çalışma grubu odasına en az bir kullanıcı göndermelisiniz..", "app.createBreakoutRoom.minimumDurationWarnBreakout": "En kısa çalışma grubu odası süresi {0} dakikadır.", "app.createBreakoutRoom.modalDesc": "İpucu: Herhangi bir çalışma grubu odasına atamak için kullanıcıların adını sürükleyip bırakabilirsiniz.", @@ -1169,8 +1237,8 @@ "app.externalVideo.subtitlesOff": "Aç (olabiliyorsa)", "app.actionsBar.actionsDropdown.shareExternalVideo": "Dışarıdan bir görüntü paylaş", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Dışarından görüntü paylaşımını durdur", - "app.legacy.unsupportedBrowser": "Desteklenmeyen bir web tarayıcı kullanıyorsunuz. Lütfen tam destek almak için {0} ya da {1} kullanın.", - "app.legacy.upgradeBrowser": "Desteklenen bir web tarayıcının eski bir sürümünü kullanıyor gibi görünüyorsunuz. Lütfen tam destek almak için web tarayıcınızı güncelleyin.", + "app.legacy.unsupportedBrowser": "Desteklenmeyen bir tarayıcı kullanıyorsunuz. Lütfen tam destek almak için {0} ya da {1} kullanın.", + "app.legacy.upgradeBrowser": "Desteklenen bir tarayıcının eski bir sürümünü kullanıyor gibi görünüyorsunuz. Lütfen tam destek almak için tarayıcınızı güncelleyin.", "app.legacy.criosBrowser": "Lütfen iOS üzerinde tam destek almak için Safari kullanın.", "app.debugWindow.windowTitle": "Hata ayıklama", "app.debugWindow.form.userAgentLabel": "Kullanıcı uygulaması", @@ -1180,10 +1248,9 @@ "app.debugWindow.form.chatLoggerLabel": "Sohbet kaydedici düzeylerini sına", "app.debugWindow.form.button.apply": "Uygula", "app.layout.modal.title": "Ekran düzenleri", - "app.layout.modal.confirm": "Onayla", - "app.layout.modal.cancel": "İptal", + "app.layout.modal.update": "Güncelle", + "app.layout.modal.updateAll": "Herkesi güncelle", "app.layout.modal.layoutLabel": "Ekran düzeninizi seçin", - "app.layout.modal.keepPushingLayoutLabel": "Ekran düzenini herkese gönder", "app.layout.modal.pushLayoutLabel": "Herkese gönder", "app.layout.modal.layoutToastLabel": "Ekran düzeni ayarları değişti", "app.layout.modal.layoutSingular": "Ekran düzeni", @@ -1277,8 +1344,8 @@ "app.learningDashboard.usersTable.colRaiseHands": "El kaldırma", "app.learningDashboard.usersTable.colActivityScore": "Etkinlik puanı", "app.learningDashboard.usersTable.colStatus": "Durum", - "app.learningDashboard.usersTable.userStatusOnline": "Çevrimiçi", - "app.learningDashboard.usersTable.userStatusOffline": "Çevrimdışı", + "app.learningDashboard.usersTable.userStatusOnline": "Çevrim içi", + "app.learningDashboard.usersTable.userStatusOffline": "Çevrim dışı", "app.learningDashboard.usersTable.noUsers": "Henüz bir kullanıcı yok", "app.learningDashboard.usersTable.name": "Ad", "app.learningDashboard.usersTable.moderator": "Sorumlu", From 94cb2d5d97f6066b8cd99f8488e5c5c8fa4e30f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Fri, 11 Aug 2023 10:14:16 -0300 Subject: [PATCH 186/252] only display reactions bar toggle if reactions are enabled --- .../ui/components/settings/component.jsx | 3 ++ .../ui/components/settings/container.jsx | 2 + .../submenus/application/component.jsx | 50 +++++++++++-------- .../imports/ui/services/features/index.js | 5 +- 4 files changed, 37 insertions(+), 23 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/settings/component.jsx b/bigbluebutton-html5/imports/ui/components/settings/component.jsx index 51a8007f0e..a8ba55ebe4 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/settings/component.jsx @@ -94,6 +94,7 @@ const propTypes = { updateSettings: PropTypes.func.isRequired, availableLocales: PropTypes.objectOf(PropTypes.array).isRequired, showToggleLabel: PropTypes.bool.isRequired, + isReactionsEnabled: PropTypes.bool.isRequired, }; class Settings extends Component { @@ -173,6 +174,7 @@ class Settings extends Component { selectedLayout, isScreenSharingEnabled, isVideoEnabled, + isReactionsEnabled, } = this.props; const { @@ -225,6 +227,7 @@ class Settings extends Component { layoutContextDispatch={layoutContextDispatch} selectedLayout={selectedLayout} isPresenter={isPresenter} + isReactionsEnabled={isReactionsEnabled} /> diff --git a/bigbluebutton-html5/imports/ui/components/settings/container.jsx b/bigbluebutton-html5/imports/ui/components/settings/container.jsx index db3bc3989b..ac2df1e635 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/settings/container.jsx @@ -4,6 +4,7 @@ import SettingsService from '/imports/ui/services/settings'; import Settings from './component'; import { layoutDispatch } from '../layout/context'; import { isScreenSharingEnabled } from '/imports/ui/services/features'; +import UserReactionService from '/imports/ui/components/user-reaction/service'; import { getUserRoles, @@ -32,4 +33,5 @@ export default withTracker((props) => ({ showToggleLabel: false, isScreenSharingEnabled: isScreenSharingEnabled(), isVideoEnabled: Meteor.settings.public.kurento.enableVideo, + isReactionsEnabled: UserReactionService.isEnabled(), }))(SettingsContainer); diff --git a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx index f2d9a6a459..8a3a4308a8 100644 --- a/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/settings/submenus/application/component.jsx @@ -411,7 +411,11 @@ class ApplicationMenu extends BaseMenu { render() { const { - allLocales, intl, showToggleLabel, displaySettingsStatus, + allLocales, + intl, + showToggleLabel, + displaySettingsStatus, + isReactionsEnabled, } = this.props; const { isLargestFontSize, isSmallestFontSize, settings, @@ -513,27 +517,29 @@ class ApplicationMenu extends BaseMenu { - - - - - {displaySettingsStatus(settings.autoCloseReactionsBar)} - this.handleToggle('autoCloseReactionsBar')} - ariaLabel={`${intl.formatMessage(intlMessages.autoCloseReactionsBarLabel)} - ${displaySettingsStatus(settings.autoCloseReactionsBar, false)}`} - showToggleLabel={showToggleLabel} - /> - - - + {isReactionsEnabled && ( + + + + + {displaySettingsStatus(settings.autoCloseReactionsBar)} + this.handleToggle('autoCloseReactionsBar')} + ariaLabel={`${intl.formatMessage(intlMessages.autoCloseReactionsBarLabel)} - ${displaySettingsStatus(settings.autoCloseReactionsBar, false)}`} + showToggleLabel={showToggleLabel} + /> + + + + )} diff --git a/bigbluebutton-html5/imports/ui/services/features/index.js b/bigbluebutton-html5/imports/ui/services/features/index.js index 80674336df..422ec9152f 100644 --- a/bigbluebutton-html5/imports/ui/services/features/index.js +++ b/bigbluebutton-html5/imports/ui/services/features/index.js @@ -85,7 +85,10 @@ export function isPresentationEnabled() { } export function isReactionsEnabled() { - return getDisabledFeatures().indexOf('reactions') === -1; + const USER_REACTIONS_ENABLED = Meteor.settings.public.userReaction.enabled; + const REACTIONS_BUTTON_ENABLED = Meteor.settings.public.app.reactionsButton.enabled; + + return getDisabledFeatures().indexOf('reactions') === -1 && USER_REACTIONS_ENABLED && REACTIONS_BUTTON_ENABLED; } export function isTimerFeatureEnabled() { From f3bd84e81f030edf5d187c31aa3043cf6e6ee6ff Mon Sep 17 00:00:00 2001 From: Paulo Lanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Fri, 11 Aug 2023 10:57:39 -0300 Subject: [PATCH 187/252] refator(video): guarantee connectStreams scope is valid The original debounce implementation (lodash) preserved the caller's context - radash didn't, so it was failing and it wasn't noticed. The new debounce implementation with the native function seems to preserve caller's context, but as a safety measure this commit binds the method to its appropriate scope. --- .../imports/ui/components/video-provider/component.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx index 8f37b0dc3a..a518ff8454 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx @@ -175,6 +175,7 @@ class VideoProvider extends Component { this.onWsClose = this.onWsClose.bind(this); this.onWsMessage = this.onWsMessage.bind(this); this.updateStreams = this.updateStreams.bind(this); + this.connectStreams = this.connectStreams.bind(this); this.debouncedConnectStreams = debounce( this.connectStreams, VideoService.getPageChangeDebounceTime(), From c598db9c7af287ceb979973295df1746e942e2ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Fri, 11 Aug 2023 11:01:50 -0300 Subject: [PATCH 188/252] display selected emoji in reactions bar button --- .../actions-bar/reactions-button/component.jsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx index 8e06aeca5c..afa7fc5d78 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/reactions-button/component.jsx @@ -123,19 +123,24 @@ const ReactionsButton = (props) => { customStyles: {...actionCustomStyles, width: 'auto'}, }); + const icon = currentUserReaction === 'none' ? 'hand' : null; + const currentUserReactionEmoji = reactions.find(({ native }) => native === currentUserReaction); + const customIcon = !icon ? : null; + return ( {}} onClick={() => setShowEmojiPicker(true)} - color={showEmojiPicker ? 'primary' : 'default'} + color={showEmojiPicker || customIcon ? 'primary' : 'default'} hideLabel circle size="lg" From 8269c840a7f377d64d3eb5365fca1853d1ef2d16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Fri, 11 Aug 2023 11:04:10 -0300 Subject: [PATCH 189/252] change default value --- bigbluebutton-html5/private/config/settings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 1cf2fe8649..b96cad19bf 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -191,7 +191,7 @@ public: wakeLock: true paginationEnabled: true whiteboardToolbarAutoHide: false - autoCloseReactionsBar: false + autoCloseReactionsBar: true darkTheme: false # fallbackLocale: if the locale the client is loaded in does not have a # translation a string, it will use the translation from the locale From b0643f7b10739e275e21c8b8f02c56704b9d1fa3 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Fri, 11 Aug 2023 11:04:21 -0400 Subject: [PATCH 190/252] Updates for project BigBlueButton v2.7 HTML5 client and lanuage gl on branch v2.7.x-release (#18532) * Translate en.json in gl 100% translated source file: 'en.json' on 'gl'. --------- Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- bigbluebutton-html5/public/locales/gl.json | 505 ++++++++++++--------- 1 file changed, 286 insertions(+), 219 deletions(-) diff --git a/bigbluebutton-html5/public/locales/gl.json b/bigbluebutton-html5/public/locales/gl.json index 7b61b54088..8033fc8dad 100644 --- a/bigbluebutton-html5/public/locales/gl.json +++ b/bigbluebutton-html5/public/locales/gl.json @@ -1,42 +1,47 @@ { "app.home.greeting": "A súa presentación comezará en breve…", "app.chat.submitLabel": "Enviar mensaxe", - "app.chat.loading": "Mensaxes de conversa cargadas: {0}%", + "app.chat.loading": "Mensaxes de parola cargadas: {0}%", "app.chat.errorMaxMessageLength": "A mensaxe é demasiado longa, superou o máximo de {0} caracteres", - "app.chat.disconnected": "Esta desconectado, non é posíbel enviar as mensaxes", - "app.chat.locked": "Conversa bloqueado, anon é posíbel enviar as mensaxes", - "app.chat.inputLabel": "Entrada de mensaxe para a conversa {0}", + "app.chat.disconnected": "Vde. esta desconectado, non é posíbel enviar as mensaxes", + "app.chat.locked": "Parola bloqueado, anon é posíbel enviar as mensaxes", + "app.chat.inputLabel": "Entrada de mensaxe para a parola {0}", "app.chat.emojiButtonLabel": "Selector de emojis", + "app.chat.loadMoreButtonLabel": "Cargar máis", "app.chat.inputPlaceholder": "Mensaxe {0}", - "app.chat.titlePublic": "Conversa pública", - "app.chat.titlePrivate": "Conversa privada con {0}.", + "app.chat.titlePublic": "Parola pública", + "app.chat.titlePrivate": "Parola privada con {0}.", "app.chat.partnerDisconnected": "{0} saíu da xuntanza", "app.chat.closeChatLabel": "Pechar {0}", "app.chat.hideChatLabel": "Agochar {0}", "app.chat.moreMessages": "Máis mensaxes a seguir", - "app.chat.dropdown.options": "Opcións da conversa", + "app.chat.dropdown.options": "Opcións da parola", "app.chat.dropdown.clear": "Limpar", "app.chat.dropdown.copy": "Copiar", "app.chat.dropdown.save": "Gardar", - "app.chat.label": "Conversa", + "app.chat.label": "Parola", "app.chat.offline": "Sen conexión", "app.chat.pollResult": "Resultados da enquisa", "app.chat.breakoutDurationUpdated": "O tempo restante da sala parcial é agora de {0} minutos", - "app.chat.breakoutDurationUpdatedModerator": "O tempo das salas de descanso é agora de {0} minutos, e enviouse unha notificación.", - "app.chat.emptyLogLabel": "Rexistro da conversa baleiro", - "app.chat.clearPublicChatMessage": "A conversa publica foi retirada por un moderador", + "app.chat.breakoutDurationUpdatedModerator": "O tempo das salas parciais é agora de {0} minutos, e enviouse unha notificación.", + "app.chat.emptyLogLabel": "Rexistro da parola baleiro", + "app.chat.away": "Está ausente", + "app.chat.notAway": "Xa non está ausente", + "app.chat.clearPublicChatMessage": "A parola publica foi retirada por un moderador", "app.chat.multi.typing": "Varios usuarios están a escribir", + "app.chat.someone.typing": "Alguén está a escribir", "app.chat.one.typing": "{0} está a escribir", "app.chat.two.typing": "{0} e {1} están a escribir", - "app.chat.copySuccess": "A conversa transcrita foi copiada", - "app.chat.copyErr": "Produciuse un erro ao copiar a conversa transcrita", + "app.chat.copySuccess": "A parola transcrita foi copiada", + "app.chat.copyErr": "Produciuse un erro ao copiar a parola transcrita", "app.emojiPicker.search": "Buscar", - "app.emojiPicker.notFound": "Non se atoparon Emojis", + "app.emojiPicker.notFound": "Non se atopou ningún «emoji»", "app.emojiPicker.skintext": "Escolla o seu ton de pel predeterminado", - "app.emojiPicker.clear": "Borrar", - "app.emojiPicker.categories.label": "Categorías de Emoji", - "app.emojiPicker.categories.people": "Persoas e Corpo", - "app.emojiPicker.categories.nature": "Animais e Natureza", + "app.emojiPicker.clear": "Limpar", + "app.emojiPicker.categories.label": "Categorías de «emoji»", + "app.emojiPicker.categories.people": "Persoas e corpo", + "app.emojiPicker.categories.reactions": "Reaccións", + "app.emojiPicker.categories.nature": "Animais e natureza", "app.emojiPicker.categories.foods": "Comida e bebida", "app.emojiPicker.categories.places": "Viaxes e lugares", "app.emojiPicker.categories.activity": "Actividades", @@ -47,10 +52,27 @@ "app.emojiPicker.categories.search": "Buscar resultados", "app.emojiPicker.skintones.1": "Ton de pel predeterminado", "app.emojiPicker.skintones.2": "Ton de pel claro", - "app.emojiPicker.skintones.3": "Ton de pel medio-claro", + "app.emojiPicker.skintones.3": "Ton de pel medio claro", "app.emojiPicker.skintones.4": "Ton de pel medio", - "app.emojiPicker.skintones.5": "Ton de pel medio-escuro", + "app.emojiPicker.skintones.5": "Ton de pel medio escuro", "app.emojiPicker.skintones.6": "Ton de pel escuro", + "app.timer.title": "Tempo", + "app.timer.stopwatch.title": "Cronómetro", + "app.timer.timer.title": "Temporizador", + "app.timer.hideTimerLabel": "Agochar o tempo", + "app.timer.button.stopwatch": "Cronómetro", + "app.timer.button.timer": "Temporizador", + "app.timer.button.start": "Comezar", + "app.timer.button.stop": "Deter", + "app.timer.button.reset": "Restabelecer", + "app.timer.hours": "horas", + "app.timer.minutes": "minutos", + "app.timer.seconds": "segundos", + "app.timer.songs": "Cancións", + "app.timer.noTrack": "Ningunha canción", + "app.timer.track1": "Relaxante", + "app.timer.track2": "Calma", + "app.timer.track3": "Feliz", "app.captions.label": "Subtítulos", "app.captions.menu.close": "Pechar", "app.captions.menu.start": "Comezar", @@ -76,7 +98,7 @@ "app.captions.speech.start": "Comezou o recoñecemento de voz", "app.captions.speech.stop": "O recoñecemento de voz detívose", "app.captions.speech.error": "O recoñecemento de voz detívose por mor da incompatibilidade do navegador ou dalgún tempo de silencio", - "app.confirmation.skipConfirm": "Non preguntar de novo", + "app.confirmation.skipConfirm": "Non volver preguntar", "app.confirmation.virtualBackground.title": "Comezar novo fondo virtual", "app.confirmation.virtualBackground.description": "{0} engadirase como fondo virtual. Continuar?", "app.confirmationModal.yesLabel": "Sí", @@ -89,9 +111,9 @@ "app.notes.hide": "Agochar as notas", "app.notes.locked": "Bloqueado", "app.notes.disabled": "Fixado na área multimedia", - "app.notes.notesDropdown.covertAndUpload": "Converter notas en presentación", - "app.notes.notesDropdown.pinNotes": "Fixar notas na pizarra", - "app.notes.notesDropdown.unpinNotes": "Retirar fixación das notas", + "app.notes.notesDropdown.covertAndUpload": "Converter as notas en presentación", + "app.notes.notesDropdown.pinNotes": "Fixar as notas no encerado", + "app.notes.notesDropdown.unpinNotes": "Desprender as notas", "app.notes.notesDropdown.notesOptions": "Opcións das notas", "app.pads.hint": "Prema Esc para enfocar a barra de ferramentas do caderno de notas", "app.user.activityCheck": "Comprobar a actividade do usuario", @@ -102,13 +124,14 @@ "app.userList.messagesTitle": "Mensaxes", "app.userList.notesTitle": "Notas", "app.userList.notesListItem.unreadContent": "Contido novo dispoñíbel na sección de notas compartidas", + "app.userList.timerTitle": "Tempo", "app.userList.captionsTitle": "Subtítulos", "app.userList.presenter": "Presentador", "app.userList.you": "Vostede", "app.userList.locked": "Bloqueado", "app.userList.byModerator": "por (Moderador)", "app.userList.label": "Lista de usuarios", - "app.userList.toggleCompactView.label": "Alternar o modo de vista compacta", + "app.userList.toggleCompactView.label": "Amosar/agochar o modo de vista compacta", "app.userList.moderator": "Moderador", "app.userList.mobile": "Móbil", "app.userList.guest": "Convidado", @@ -116,15 +139,17 @@ "app.userList.menuTitleContext": "Opcións dispoñíbeis", "app.userList.chatListItem.unreadSingular": "Unha nova mensaxe", "app.userList.chatListItem.unreadPlural": "{0} novas mensaxes", - "app.userList.menu.chat.label": "Iniciar a conversa privada", + "app.userList.menu.away": "Defínase como ausente", + "app.userList.menu.notAway": "Defínase como activo", + "app.userList.menu.chat.label": "Iniciar a parola privada", "app.userList.menu.clearStatus.label": "Limpar o estado", "app.userList.menu.removeUser.label": "Retirar o usuario", "app.userList.menu.removeConfirmation.label": "Retirar o usuario ({0})", - "app.userlist.menu.removeConfirmation.desc": " Impedir que este usuario se reincorpore á sesión.", + "app.userlist.menu.removeConfirmation.desc": "Impedir que este usuario volva unirse á sesión.", "app.userList.menu.muteUserAudio.label": "Desactivar o son do usuario", "app.userList.menu.unmuteUserAudio.label": "Devolverlle o son ao usuario", - "app.userList.menu.webcamPin.label": " Fixar a cámara web do usuario", - "app.userList.menu.webcamUnpin.label": "Soltar a cámara web do usuario", + "app.userList.menu.webcamPin.label": "Fixar a cámara web do usuario", + "app.userList.menu.webcamUnpin.label": "Desprender a cámara web do usuario", "app.userList.menu.giveWhiteboardAccess.label" : "Dar acceso ao encerado", "app.userList.menu.removeWhiteboardAccess.label": "Retirar o acceso ao encerado", "app.userList.menu.ejectUserCameras.label": "Pechar as cámaras", @@ -140,29 +165,32 @@ "app.userList.userOptions.muteAllDesc": "Desactivar o son de todos os usuarios na xuntanza", "app.userList.userOptions.clearAllLabel": "Limpar todas as iconas de estado", "app.userList.userOptions.clearAllDesc": "Limpar todas as iconas de estado dos usuarios", + "app.userList.userOptions.clearAllReactionsLabel": "Limpar todas as reaccións", + "app.userList.userOptions.clearAllReactionsDesc": "Limpar todos os «emojis» de reacción dos usuarios", "app.userList.userOptions.muteAllExceptPresenterLabel": "Silenciar a todos os usuarios agás o presentador", "app.userList.userOptions.muteAllExceptPresenterDesc": "Silenciar a todos os usuarios na xuntanza agás o presentador", "app.userList.userOptions.unmuteAllLabel": "Desactivar o silencio na xuntanza", "app.userList.userOptions.unmuteAllDesc": "Devolverlle o son á xuntanza", "app.userList.userOptions.lockViewersLabel": "Bloquear espectadores", - "app.userList.userOptions.lockViewersDesc": "Bloquear certas funcionalidades para os asistentes ao encontro", + "app.userList.userOptions.lockViewersDesc": "Bloquear certas funcionalidades para os asistentes á xuntanza", "app.userList.userOptions.guestPolicyLabel": "Normas para os convidados", "app.userList.userOptions.guestPolicyDesc": "Cambiar os axustes das normas de convidados á xuntanza", "app.userList.userOptions.disableCam": "As cámaras web dos espectadores están desactivadas", "app.userList.userOptions.disableMic": "Os micrófonos dos espectadores están desactivados", - "app.userList.userOptions.disablePrivChat": "A conversa privada está desactivada", - "app.userList.userOptions.disablePubChat": "A conversa pública está desactivada", + "app.userList.userOptions.disablePrivChat": "A parola privada está desactivada", + "app.userList.userOptions.disablePubChat": "A parola pública está desactivada", "app.userList.userOptions.disableNotes": "As notas compartidas agora están bloqueadas", "app.userList.userOptions.hideUserList": "A lista de usuarios agora está agochada para os espectadores", - "app.userList.userOptions.webcamsOnlyForModerator": "Só os moderadores poden ver as cámaras web dos convidados (por mor da configuración de bloqueo)", + "app.userList.userOptions.webcamsOnlyForModerator": "Só os moderadores poden ver as cámaras web dos espectadores (por mor dos axustes de bloqueo)", "app.userList.content.participants.options.clearedStatus": "Limparonse todos os estados de usuario", + "app.userList.content.participants.options.clearedReactions": "Limpáronse todas as reaccións dos usuarios", "app.userList.userOptions.enableCam": "As cámaras web dos espectadores están activadas", "app.userList.userOptions.enableMic": "Os micrófonos dos espectadores están activados", - "app.userList.userOptions.enablePrivChat": "A conversa privada está activada", - "app.userList.userOptions.enablePubChat": "A conversa pública está activada", + "app.userList.userOptions.enablePrivChat": "A parola privada está activada", + "app.userList.userOptions.enablePubChat": "A parola pública está activada", "app.userList.userOptions.enableNotes": "As notas compartidas agora están activadas", "app.userList.userOptions.showUserList": "A lista de usuarios agora amosase aos espectadores", - "app.userList.userOptions.enableOnlyModeratorWebcam": "Pode activar agora a súaúa cámara web, todos poderán velo", + "app.userList.userOptions.enableOnlyModeratorWebcam": "Pode activar agora a súaúa cámara web, todos poderán vela", "app.userList.userOptions.savedNames.title": "Lista de usuarios en xuntanza {0} en {1}", "app.userList.userOptions.sortedFirstName.heading": "Ordenado por nome:", "app.userList.userOptions.sortedLastName.heading": "Ordenado por apelido:", @@ -172,26 +200,34 @@ "app.media.autoplayAlertDesc": "Permitir acceso", "app.media.screenshare.start": "Comezou a pantalla compartida", "app.media.screenshare.end": "Rematou a pantalla compartida", - "app.media.screenshare.endDueToDataSaving": "Detívose a compartición de pantalla por mor do aforro de datos", + "app.media.screenshare.endDueToDataSaving": "A compartición de pantalla foi detida por mor do aforro de datos", "app.media.screenshare.unavailable": "A pantalla compartida non está dispoñíbel", "app.media.screenshare.notSupported": "Este navegador non admite a compartición de pantalla.", "app.media.screenshare.autoplayBlockedDesc": "Necesitamos o seu permiso para amosarlle a pantalla do presentador", "app.media.screenshare.autoplayAllowLabel": "Ver a pantalla compartida", + "app.media.cameraAsContent.start": "Comezou a cámara de presentar", + "app.media.cameraAsContent.end": "Rematou a cámara de presentar", + "app.media.cameraAsContent.endDueToDataSaving": "A cámara de presentar foi detida por mor do aforro de datos", + "app.media.cameraAsContent.autoplayBlockedDesc": "Necesitamos o seu permiso para amosarlle a cámara do presentador.", + "app.media.cameraAsContent.autoplayAllowLabel": "Ver a cámara de presentar", "app.screenshare.presenterLoadingLabel": "A súa pantalla compartida está a cargar", "app.screenshare.viewerLoadingLabel": "A pantalla do presentador está a cargar", "app.screenshare.presenterSharingLabel": "Agora está a compartir a súa pantalla", "app.screenshare.screenshareFinalError": "Código {0}. Non foi posíbel compartir a pantalla.", "app.screenshare.screenshareRetryError": "Código {0}. Tente compartir a pantalla de novo.", "app.screenshare.screenshareRetryOtherEnvError": "Código {0}. Non foi posíbel compartir a pantalla. Ténteo de novo usando outro navegador ou dispositivo.", - "app.screenshare.screenshareUnsupportedEnv": "Código {0}. O navegador non é compatible. Ténteo de novo usando outro navegador ou dispositivo.", + "app.screenshare.screenshareUnsupportedEnv": "Código {0}. O navegador non é compatíbel. Ténteo de novo usando outro navegador ou dispositivo.", "app.screenshare.screensharePermissionError": "Código {0}. É necesario conceder o permiso para capturar a pantalla.", + "app.cameraAsContent.presenterLoadingLabel": "A súa cámara está a cargar", + "app.cameraAsContent.viewerLoadingLabel": "A cámara do presentador está a cargar", + "app.cameraAsContent.presenterSharingLabel": "Agora está a presentar a súa cámara", "app.meeting.ended": "Rematou a sesión", "app.meeting.meetingTimeRemaining": "Tempo restante da xuntanza: {0}", "app.meeting.meetingTimeHasEnded": "Rematou o tempo. A xuntanza pecharase en breve", "app.meeting.endedByUserMessage": "Esta sesión foi rematada por {0}", - "app.meeting.endedByNoModeratorMessageSingular": "A reunión rematou porque non estivo presente ningún moderador durante un minuto", - "app.meeting.endedByNoModeratorMessagePlural": "A reunión rematou porque ningún moderador estivo presente durante {0} minutos", - "app.meeting.endedMessage": "Será reenviado á pantalla de inicio", + "app.meeting.endedByNoModeratorMessageSingular": "A xuntanza rematou porque non estivo presente ningún moderador durante un minuto", + "app.meeting.endedByNoModeratorMessagePlural": "A xuntanza rematou porque ningún moderador estivo presente durante {0} minutos", + "app.meeting.endedMessage": "Vai ser reenviado á pantalla de inicio", "app.meeting.alertMeetingEndsUnderMinutesSingular": "A xuntanza pecharase nun minuto.", "app.meeting.alertMeetingEndsUnderMinutesPlural": "A xuntanza pecharase en {0} minutos.", "app.meeting.alertBreakoutEndsUnderMinutesPlural": "A sala parcial pecharase en {0} minutos.", @@ -199,6 +235,7 @@ "app.presentation.hide": "Agochar a presentación", "app.presentation.notificationLabel": "Presentación actual", "app.presentation.downloadLabel": "Descargar", + "app.presentation.actionsLabel": "Accións", "app.presentation.slideContent": "Contido da diapositiva", "app.presentation.startSlideContent": "Inicio do contido das diapositivas", "app.presentation.endSlideContent": "Fin do contido das diapositivas", @@ -230,28 +267,35 @@ "app.presentation.presentationToolbar.zoomInDesc": "Ampliar a presentación", "app.presentation.presentationToolbar.zoomOutLabel": "Afastarse", "app.presentation.presentationToolbar.zoomOutDesc": "Reducir a presentación", - "app.presentation.presentationToolbar.zoomReset": "Restaurar o zoom", + "app.presentation.presentationToolbar.zoomReset": "Restabelecer o zoom", "app.presentation.presentationToolbar.zoomIndicator": "Porcentaxe de zoom actual", "app.presentation.presentationToolbar.fitToWidth": "Axustar ao largo", "app.presentation.presentationToolbar.fitToPage": "Axustar á páxina", "app.presentation.presentationToolbar.goToSlide": "Diapositiva {0}", - "app.presentation.presentationToolbar.hideToolsDesc": "Agochar barras de ferramentas", - "app.presentation.presentationToolbar.showToolsDesc": "Amosar barras de ferramentas", + "app.presentation.presentationToolbar.hideToolsDesc": "Agochar as barras de ferramentas", + "app.presentation.presentationToolbar.showToolsDesc": "Amosar as barras de ferramentas", "app.presentation.placeholder": "Non hai ningunha presentación activa actualmente", "app.presentationUploder.title": "Presentación", - "app.presentationUploder.message": "Como presentador tes a posibilidade de cargar calquera documento de Office ou ficheiro PDF. Recomendamos o ficheiro PDF para obter os mellores resultados. Asegúrate de seleccionar unha presentación usando a caixa de verificación do círculo do lado esquerdo.", - "app.presentationUploader.exportHint": "Ao seleccionar \"Enviar ao chat\" proporcionarase aos usuarios unha ligazón para descargar con anotacións no chat público.", - "app.presentationUploader.exportToastHeader": "Enviando ao chat (elemento {0})", - "app.presentationUploader.exportToastHeaderPlural": "Enviando ao chat ({0} elementos)", - "app.presentationUploader.exporting": "Enviando ao chat", - "app.presentationUploader.sending": "Enviando...", - "app.presentationUploader.collecting": "Extraendo a diapositiva {0} de {1}...", - "app.presentationUploader.processing": "Anotando diapositiva {0} de {1}...", + "app.presentationUploder.message": "Como presentador ten a posibilidade de enviar calquera documento de Office ou ficheiro PDF. Recomendamos o ficheiro PDF para obter os mellores resultados. Asegúrese de seleccionar unha presentación usando a caixa de selección do círculo do lado esquerdo.", + "app.presentationUploader.exportHint": "No menú «Opcións de exportación» ten a opción de activar a descarga da presentación orixinal e de fornecer aos usuarios unha ligazón de descarga con anotacións na parola pública.", + "app.presentationUploader.exportToastHeader": "Enviando á parola (elemento {0})", + "app.presentationUploader.exportToastHeaderPlural": "Enviando á parola ({0} elementos)", + "app.presentationUploader.exporting": "Enviando á parola", + "app.presentationUploader.sending": "Enviando…", + "app.presentationUploader.collecting": "Extraendo a diapositiva {0} de {1}…", + "app.presentationUploader.processing": "Anotando diapositiva {0} de {1}…", "app.presentationUploader.sent": "Enviado", - "app.presentationUploader.exportingTimeout": "A exportación está tardando demasiado...", - "app.presentationUploader.export": "Enviar para o chat", - "app.presentationUploader.export.linkAvailable": "Ligazón para descargar {0} dispoñible no chat público.", - "app.presentationUploader.export.notAccessibleWarning": "pode non cumprir as normas de accesibilidade", + "app.presentationUploader.exportingTimeout": "A exportación está a tardar demasiado…", + "app.presentationUploader.export": "Enviar para a parola", + "app.presentationUploader.exportCurrentStatePresentation": "Enviar unha ligazón de descarga da presentación no seu estado actual", + "app.presentationUploader.enableOriginalPresentationDownload": "Activar a descarga da presentación orixinal", + "app.presentationUploader.disableOriginalPresentationDownload": "Desactivar a descarga da presentación orixinal", + "app.presentationUploader.dropdownExportOptions": "Opcións de exportación", + "app.presentationUploader.export.linkAvailable": "Ligazón para descargar {0} dispoñíbel na parola pública.", + "app.presentationUploader.export.downloadButtonAvailable": "O botón de descarga para a presentación {0} está dispoñíbel", + "app.presentationUploader.export.notAccessibleWarning": "é posíbel que non cumpra as normas de accesibilidade", + "app.presentationUploader.export.originalLabel": "Orixinal", + "app.presentationUploader.export.inCurrentStateLabel": "No estado actual", "app.presentationUploader.currentPresentationLabel": "Presentación actual", "app.presentationUploder.extraHint": "IMPORTANTE: cada ficheiro non pode exceder {0} MB e {1} páxinas.", "app.presentationUploder.uploadLabel": "Enviar", @@ -263,8 +307,8 @@ "app.presentationUploder.dropzoneImagesLabel": "Arrastre as imaxes aquí para envialas", "app.presentationUploder.browseFilesLabel": "ou busque os ficheiros", "app.presentationUploder.browseImagesLabel": "ou busque as imaxes", - "app.presentationUploder.externalUploadTitle": "Engade contido dunha aplicación de terceiros", - "app.presentationUploder.externalUploadLabel": "Explorar ficheiros", + "app.presentationUploder.externalUploadTitle": "Engadir contido de aplicacións de terceiros", + "app.presentationUploder.externalUploadLabel": "Examinar os ficheiros", "app.presentationUploder.fileToUpload": "Para ser enviado…", "app.presentationUploder.currentBadge": "Actual", "app.presentationUploder.rejectedError": "O(s) ficheiro(s) seleccionado(s) foi(foron) rexeitado(s). Revise o(os) tipo(s) de ficheiro.", @@ -274,24 +318,24 @@ "app.presentationUploder.upload.413": "O ficheiro é demasiado grande, superou o máximo de {0} MB", "app.presentationUploder.genericError": "Ouh! algo foi mal…", "app.presentationUploder.upload.408": "Solicitar o tempo de espera do testemuño de envío.", - "app.presentationUploder.upload.404": "404: o testemuño de envío non válido", + "app.presentationUploder.upload.404": "404: o testemuño de envío non é válido", "app.presentationUploder.upload.401": "Produciuse un fallo na solicitude do tempo de espera do testemuño de envío.", - "app.presentationUploder.conversion.conversionProcessingSlides": "Procesando páxina {0} de {1}", + "app.presentationUploder.conversion.conversionProcessingSlides": "Procesando a páxina {0} de {1}", "app.presentationUploder.conversion.genericConversionStatus": "Convertendo ficheiros…", "app.presentationUploder.conversion.generatingThumbnail": "Xerando miniaturas…", "app.presentationUploder.conversion.generatedSlides": "Presentacións xeradas…", "app.presentationUploder.conversion.generatingSvg": "Xerando imaxes SVG…", "app.presentationUploder.conversion.pageCountExceeded": "O número de páxinas superou o máximo de {0}", "app.presentationUploder.conversion.invalidMimeType": "Detectouse un formato non válido (extensión={0}, tipo de contido={1})", - "app.presentationUploder.conversion.conversionTimeout": "Non se puido procesar a diapositiva {0} en {1} intentos.", - "app.presentationUploder.conversion.officeDocConversionInvalid": "Non se puido procesar o documento de Office. Cargue un PDF no seu lugar.", - "app.presentationUploder.conversion.officeDocConversionFailed": "Non se puido procesar o documento de Office. Cargue un PDF no seu lugar.", + "app.presentationUploder.conversion.conversionTimeout": "Non foi posíbel procesar a diapositiva {0} en {1} intentos.", + "app.presentationUploder.conversion.officeDocConversionInvalid": "Non foi posíbel procesar o documento de Office. Envíe un PDF no seu lugar.", + "app.presentationUploder.conversion.officeDocConversionFailed": "Non foi posíbel procesar o documento de Office. Envíe un PDF no seu lugar.", "app.presentationUploder.conversion.pdfHasBigPage": "Non foi posíbel converter o ficheiro PDF. Tente optimizalo. Tamaño máximo de páxina {0}", - "app.presentationUploder.conversion.timeout": " Ouh! a conversión tomou demasiado tempo", + "app.presentationUploder.conversion.timeout": "Ouh! a conversión tomou demasiado tempo", "app.presentationUploder.conversion.pageCountFailed": "Produciuse un fallo ao determinar o número de páxinas.", - "app.presentationUploder.conversion.unsupportedDocument": "Extensión de ficheiro non se admitida", + "app.presentationUploder.conversion.unsupportedDocument": "A extensión de ficheiro non está admitida", "app.presentationUploder.removePresentationLabel": "Retirar a presentación", - "app.presentationUploder.setAsCurrentPresentation": "Estabelecer a presentación como actual", + "app.presentationUploder.setAsCurrentPresentation": "Definir a presentación como actual", "app.presentationUploder.tableHeading.filename": "Nome de ficheiro", "app.presentationUploder.tableHeading.options": "Opcións", "app.presentationUploder.tableHeading.status": "Estado", @@ -305,27 +349,27 @@ "app.presentationUploder.uploadViewTitle": "Enviar presentación", "app.poll.questionAndoptions.label" : "Texto da pregunta que se amosará.\nA. Opción de enquisa *\nB. Opción de enquisa (opcional)\nC. Opción de enquisa (opcional)\nD. Opción de enquisa (opcional)\nE. Opción de enquisa (opcional)", "app.poll.customInput.label": "Entrada personalizada", - "app.poll.customInputInstructions.label": "A entrada personalizada está activada: escribe a pregunta de enquisa e as opcións nun formato determinado ou arrastra e solta un ficheiro de texto no mesmo formato.", + "app.poll.customInputInstructions.label": "A entrada personalizada está activada: escriba a pregunta de enquisa e a(s) opción(s) no formato indicado ou arrastre e solte un ficheiro de texto no mesmo formato.", "app.poll.maxOptionsWarning.label": "Só se poden usar as 5 primeiras opcións!", "app.poll.pollPaneTitle": "Enquisa", "app.poll.enableMultipleResponseLabel": "Permitir varias respostas por enquisado?", "app.poll.quickPollTitle": "Enquisa rápida", "app.poll.hidePollDesc": "Agochar o panel do menú da enquisa", - "app.poll.quickPollInstruction": "Selecciona unha das seguintes opcións para iniciar a súa enquisa.", + "app.poll.quickPollInstruction": "Seleccione unha das seguintes opcións para iniciar a súa enquisa.", "app.poll.activePollInstruction": "Deixe este panel aberto para ver as respostas en tempo real da súa enquisa. Cando estea listo prema en «Publicar os resultados da enquisa» para publicar os resultados e rematar a enquisa.", "app.poll.dragDropPollInstruction": "Para encher os valores da enquisa, arrastre un ficheiro de texto cos valores da enquisa ao campo resaltado", "app.poll.customPollTextArea": "Encher os valores da enquisa", - "app.poll.publishLabel": "Publicar enquisa", + "app.poll.publishLabel": "Publicar a enquisa", "app.poll.cancelPollLabel": "Cancelar", "app.poll.backLabel": "Iniciar unha enquisa", "app.poll.closeLabel": "Pechar", "app.poll.waitingLabel": "Agardando respostas ({0}/{1})", "app.poll.ariaInputCount": "Opción da enquisa personalizada {0} de {1}", "app.poll.customPlaceholder": "Engadir opción de enquisa", - "app.poll.noPresentationSelected": "Non se seleccionou ningunha presentación Seleccione unha.", + "app.poll.noPresentationSelected": "Non foi seleccionada ningunha presentación Seleccione unha.", "app.poll.clickHereToSelect": "Prema aquí para seleccionar", - "app.poll.question.label" : "Escriba a súa pregunta...", - "app.poll.optionalQuestion.label" : "Escriba a súa pregunta (opcional)...", + "app.poll.question.label" : "Escriba a súa pregunta", + "app.poll.optionalQuestion.label" : "Escriba a súa pregunta (opcional)…", "app.poll.userResponse.label" : "Resposta escrita", "app.poll.responseTypes.label" : "Tipos de resposta", "app.poll.optionDelete.label" : "Eliminar", @@ -365,7 +409,7 @@ "app.poll.liveResult.usersTitle": "Usuarios", "app.poll.liveResult.responsesTitle": "Resposta", "app.poll.liveResult.secretLabel": "Esta é unha enquisa anónima. Non se amosan as respostas individuais.", - "app.poll.removePollOpt": "Retirouse a opción de enquisa {0}", + "app.poll.removePollOpt": "Foi retirada a opción de enquisa {0}", "app.poll.emptyPollOpt": "Baleiro", "app.polling.pollingTitle": "Opcións da enquisa", "app.polling.pollQuestionTitle": "Pregunta da enquisa", @@ -375,17 +419,17 @@ "app.polling.responseSecret": "Enquisa anónima: o presentador non pode ver a súa resposta.", "app.polling.responseNotSecret": "Enquisa normal: o presentador pode ver a súa resposta.", "app.polling.pollAnswerLabel": "Resposta á enquisa {0}", - "app.polling.pollAnswerDesc": "Selecciona esta opción para votar por {0}", + "app.polling.pollAnswerDesc": "Seleccione esta opción para votar por {0}", "app.failedMessage": "Desculpas, hai problemas para conectar co servidor.", "app.downloadPresentationButton.label": "Descargar a presentación orixinal", - "app.connectingMessage": "Conectandose…", + "app.connectingMessage": "Conectando…", "app.waitingMessage": "Desconectado. Tentando volver conectar en {0} segundos…", "app.retryNow": "Volver tentalo agora", - "app.muteWarning.label": "Prema en {0} para silenciarse a vostede mesmo.", + "app.muteWarning.label": "Prema en {0} para silenciarse a Vde. mesmo.", "app.muteWarning.disableMessage": "Alertas de silenciamento desactivadas ata que se active o son", "app.muteWarning.tooltip": "Prema para pechar e desactivar a advertencia ata a próxima vez que se desactive o silenciamento", "app.navBar.settingsDropdown.optionsLabel": "Opcións", - "app.navBar.settingsDropdown.fullscreenLabel": "Presentación en pantalla completa", + "app.navBar.settingsDropdown.fullscreenLabel": "Aplicación de pantalla completa", "app.navBar.settingsDropdown.settingsLabel": "Axustes", "app.navBar.settingsDropdown.aboutLabel": "Sobre", "app.navBar.settingsDropdown.leaveSessionLabel": "Abandonar a xuntanza", @@ -400,9 +444,9 @@ "app.navBar.settingsDropdown.helpLabel": "Axuda", "app.navBar.settingsDropdown.openAppLabel": "Abrir na aplicación BigBlueButton Tablet", "app.navBar.settingsDropdown.helpDesc": "Vincular o usuario a vídeo titoriais (abre unha nova lapela)", - "app.navBar.settingsDropdown.endMeetingDesc": "Finaliza a sesión actual", + "app.navBar.settingsDropdown.endMeetingDesc": "Finaliza a xuntanza en curso", "app.navBar.settingsDropdown.endMeetingLabel": "Rematar a xuntanza", - "app.navBar.userListToggleBtnLabel": "Abrir/pechar a lista de usuarios", + "app.navBar.userListToggleBtnLabel": "Amosar/agochar a lista de usuarios", "app.navBar.toggleUserList.ariaLabel": "Os usuarios e as mensaxes se alternan", "app.navBar.toggleUserList.newMessages": "con nova notificación de mensaxes", "app.navBar.toggleUserList.newMsgAria": "Nova mensaxe de {0}", @@ -415,7 +459,7 @@ "app.endMeeting.title": "Rematar {0}", "app.endMeeting.description": "Esta acción rematará a sesión para {0} usuarios activos. Confirma que que quere rematar esta sesión?", "app.endMeeting.noUserDescription": "Confirma que quere rematar esta sesión?", - "app.endMeeting.contentWarning": "As mensaxes da conversa, as notas compartidas, o contido do encerado e os documentos compartidos desta sesión deixarán de ser accesibles directamente", + "app.endMeeting.contentWarning": "As mensaxes da parola, as notas compartidas, o contido do encerado e os documentos compartidos desta sesión deixarán de ser accesíbeis directamente", "app.endMeeting.yesLabel": "Finalizar a sesión para todos os usuarios", "app.endMeeting.noLabel": "Non", "app.about.title": "Sobre", @@ -427,14 +471,14 @@ "app.about.dismissLabel": "Cancelar", "app.about.dismissDesc": "Pechar a información sobre o cliente", "app.mobileAppModal.title": "Abrir a aplicación BigBlueButton Tablet", - "app.mobileAppModal.description": "Tes a aplicación BigBlueButton Tablet instalada no teu dispositivo?", - "app.mobileAppModal.openApp": "Si, abre a aplicación agora", - "app.mobileAppModal.obtainUrlMsg": "Obtendo da URL da reunión", - "app.mobileAppModal.obtainUrlErrorMsg": "Produciuse un erro ao tentar obter a URL da reunión", - "app.mobileAppModal.openStore": "Non, abre a App Store para descargar", + "app.mobileAppModal.description": "Ten a aplicación BigBlueButton Tablet instalada no seu dispositivo?", + "app.mobileAppModal.openApp": "Si, abrir a aplicación agora", + "app.mobileAppModal.obtainUrlMsg": "Obtendo o URL da xuntanza", + "app.mobileAppModal.obtainUrlErrorMsg": "Produciuse un erro ao tentar obter o URL da xuntanza", + "app.mobileAppModal.openStore": "Non, abrir a App Store para descargala", "app.mobileAppModal.dismissLabel": "Cancelar", "app.mobileAppModal.dismissDesc": "Pechar", - "app.mobileAppModal.userConnectedWithSameId": "O usuario {0} acaba de conectarse co mesmo ID que ti.", + "app.mobileAppModal.userConnectedWithSameId": "O usuario {0} vén de conectarse co mesmo ID que Vde.", "app.actionsBar.changeStatusLabel": "Cambiar o estado", "app.actionsBar.muteLabel": "Desactivar o son", "app.actionsBar.unmuteLabel": "Devolver o son ", @@ -445,26 +489,30 @@ "app.actionsBar.actionsDropdown.restorePresentationDesc": "Botón para restaurar a presentación após ser minimizada", "app.actionsBar.actionsDropdown.minimizePresentationLabel": "Minimizar a presentación", "app.actionsBar.actionsDropdown.minimizePresentationDesc": "Botón usado para minimizar a presentación", - "app.actionsBar.actionsDropdown.layoutModal": "Configuración do deseño", + "app.actionsBar.actionsDropdown.layoutModal": "Xestionar o deseño", + "app.actionsBar.actionsDropdown.shareCameraAsContent": "Compartir a cámara como contido", + "app.actionsBar.actionsDropdown.unshareCameraAsContent": "Detén a cámara como contido", "app.screenshare.screenShareLabel" : "Compartir pantalla", + "app.cameraAsContent.cameraAsContentLabel" : "Cámara de presentar", "app.submenu.application.applicationSectionTitle": "Aplicación", "app.submenu.application.animationsLabel": "Animacións", "app.submenu.application.audioFilterLabel": " Filtros de son para o micrófono", - "app.submenu.application.wbToolbarsAutoHideLabel": "Ocultar automaticamente as barras de ferramentas do encerado", + "app.submenu.application.wbToolbarsAutoHideLabel": "Agochar automaticamente as barras de ferramentas do encerado", "app.submenu.application.darkThemeLabel": "Modo escuro", "app.submenu.application.fontSizeControlLabel": "Tamaño da letra", "app.submenu.application.increaseFontBtnLabel": "Incrementar o tamaño da letra", "app.submenu.application.decreaseFontBtnLabel": "Diminuír o tamaño da letra", "app.submenu.application.currentSize": "actualmente {0}", "app.submenu.application.languageLabel": "Idioma da aplicación", - "app.submenu.application.languageOptionLabel": "Escoller idioma", + "app.submenu.application.languageOptionLabel": "Escoller o idioma", "app.submenu.application.noLocaleOptionLabel": "Non hai locais activos", "app.submenu.application.paginationEnabledLabel": "Paxinación do vídeo", + "app.submenu.application.wakeLockEnabledLabel": "Bloqueo de apagado da pantalla", "app.submenu.application.layoutOptionLabel": "Tipo de disposición", - "app.submenu.application.pushLayoutLabel": "Forzar deseño", + "app.submenu.application.pushLayoutLabel": "Aplicar o deseño", "app.submenu.application.localeDropdown.af": "Afrikaans", "app.submenu.application.localeDropdown.ar": "Árabe", - "app.submenu.application.localeDropdown.az": "Azerbaiyano", + "app.submenu.application.localeDropdown.az": "Azarí", "app.submenu.application.localeDropdown.bg-BG": "Búlgaro", "app.submenu.application.localeDropdown.bn": "Bengalí", "app.submenu.application.localeDropdown.ca": "Catalán", @@ -495,7 +543,7 @@ "app.submenu.application.localeDropdown.ja": "Xaponés", "app.submenu.application.localeDropdown.ka": "Xeorxiano", "app.submenu.application.localeDropdown.km": "Khmer", - "app.submenu.application.localeDropdown.kn": "Kannada", + "app.submenu.application.localeDropdown.kn": "Kanarés", "app.submenu.application.localeDropdown.ko-KR": "Coreano (Corea)", "app.submenu.application.localeDropdown.lo-LA": "Laosiano", "app.submenu.application.localeDropdown.lt-LT": "Lituano", @@ -528,16 +576,16 @@ "app.submenu.notification.Desc": "Definir como e que se lle notificará.", "app.submenu.notification.audioAlertLabel": "Avisos sonoros", "app.submenu.notification.pushAlertLabel": "Avisos emerxentes", - "app.submenu.notification.messagesLabel": "Mensaxe de conversa", + "app.submenu.notification.messagesLabel": "Mensaxe de parola", "app.submenu.notification.userJoinLabel": "Uniuse un usuario", - "app.submenu.notification.userLeaveLabel": "Abandono da persoa usuaria", + "app.submenu.notification.userLeaveLabel": "Abandono do usuario", "app.submenu.notification.guestWaitingLabel": "Convidado agardando pola aprobación", - "app.submenu.audio.micSourceLabel": "Fonte de micrófono", - "app.submenu.audio.speakerSourceLabel": "Fonte de altofalante", + "app.submenu.audio.micSourceLabel": "Entrada de micrófono", + "app.submenu.audio.speakerSourceLabel": "Saída de altofalante", "app.submenu.audio.streamVolumeLabel": "Volume do fluxo de son", "app.submenu.video.title": "Vídeo", - "app.submenu.video.videoSourceLabel": "Fonte de vídeo", - "app.submenu.video.videoOptionLabel": "Escoller ver a fonte", + "app.submenu.video.videoSourceLabel": "Escolla do vídeo", + "app.submenu.video.videoOptionLabel": "Escolla a entrada de vídeo", "app.submenu.video.videoQualityLabel": "Calidade do vídeo", "app.submenu.video.qualityOptionLabel": "Escoller a calidade do vídeo", "app.submenu.video.participantsCamLabel": "Vista das cámaras web dos participantes", @@ -552,7 +600,7 @@ "app.settings.main.save.label.description": "Gardar os cambios e pechar o menú de axustes", "app.settings.dataSavingTab.label": "Aforro de datos", "app.settings.dataSavingTab.webcam": "Activar as cámaras web", - "app.settings.dataSavingTab.screenShare": "Activa o uso compartido do escritorio doutros participantes", + "app.settings.dataSavingTab.screenShare": "Activar o uso compartido do escritorio doutros participantes", "app.settings.dataSavingTab.description": "Para aforrar largo de banda axuste o que se se está a amosar", "app.settings.save-notification.label": "Gardáronse os axustes", "app.statusNotifier.lowerHands": "Mans baixadas", @@ -569,7 +617,9 @@ "app.talkingIndicator.moreThanMaxIndicatorsWereTalking" : "{0}+ estiveron falando", "app.talkingIndicator.wasTalking" : "{0} deixou de chamar", "app.actionsBar.actionsDropdown.actionsLabel": "Accións", - "app.actionsBar.actionsDropdown.presentationLabel": "Cargar / Xestionar presentacións", + "app.actionsBar.actionsDropdown.activateTimerStopwatchLabel": "Activar o temporizador/cronómetro", + "app.actionsBar.actionsDropdown.deactivateTimerStopwatchLabel": "Desactivar o temporizador/cronómetro", + "app.actionsBar.actionsDropdown.presentationLabel": "Enviar/xestionar as presentacións", "app.actionsBar.actionsDropdown.initPollLabel": "Iniciar unha enquisa", "app.actionsBar.actionsDropdown.desktopShareLabel": "Compartir a súa pantalla", "app.actionsBar.actionsDropdown.stopDesktopShareLabel": "Deixar de compartir a súa pantalla", @@ -578,18 +628,20 @@ "app.actionsBar.actionsDropdown.desktopShareDesc": "Compartir a súa pantalla con outros", "app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Deixar de compartir a súa pantalla con outros", "app.actionsBar.actionsDropdown.pollBtnLabel": "Iniciar unha enquisa", - "app.actionsBar.actionsDropdown.pollBtnDesc": "Alternar o panel de enquisa", + "app.actionsBar.actionsDropdown.pollBtnDesc": "Amosar/agochar o panel de enquisa", "app.actionsBar.actionsDropdown.saveUserNames": "Gardar os nomes de usuario", "app.actionsBar.actionsDropdown.createBreakoutRoom": "Crear salas parciais", "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "crear salas parciais para dividir a xuntanza actual", "app.actionsBar.actionsDropdown.captionsLabel": "Escribir subtítulos", - "app.actionsBar.actionsDropdown.captionsDesc": "Alternar o panel de subtítulos", + "app.actionsBar.actionsDropdown.captionsDesc": "Amosar/agochar o panel de subtítulos", "app.actionsBar.actionsDropdown.takePresenter": "Tomar o rol de presentador", "app.actionsBar.actionsDropdown.takePresenterDesc": "Asignarse a un mesmo como novo presentador", "app.actionsBar.actionsDropdown.selectRandUserLabel": "Seleccionar un usuario ao chou", "app.actionsBar.actionsDropdown.selectRandUserDesc": "Escoller ao chou un usuario entre os espectadores dispoñíbeis", - "app.actionsBar.actionsDropdown.propagateLayoutLabel": "Propagación do deseño", - "app.actionsBar.emojiMenu.statusTriggerLabel": "Estabelecer o estado", + "app.actionsBar.reactions.reactionsButtonLabel": "Barra de reaccións", + "app.actionsBar.reactions.raiseHand": "Erguer a súa man", + "app.actionsBar.reactions.lowHand": "Baixar a súa man", + "app.actionsBar.emojiMenu.statusTriggerLabel": "Definir o estado", "app.actionsBar.emojiMenu.awayLabel": "Ausente", "app.actionsBar.emojiMenu.awayDesc": "Cambiar o seu estado a ausente", "app.actionsBar.emojiMenu.raiseHandLabel": "Erguer a man", @@ -628,9 +680,9 @@ "app.audioNotification.audioFailedError1012": "Conexión pechada (erro ICE 1012)", "app.audioNotification.audioFailedMessage": "Produciuse un fallo na súa conexión de son", "app.audioNotification.mediaFailedMessage": "Produciuse un fallo en getUserMicMedia xa que só se permiten as orixes seguras", - "app.audioNotification.deviceChangeFailed": "Produciuse un erro no cambio do dispositivo de audio. Comproba se o dispositivo escollido está correctamente configurado e dispoñible", + "app.audioNotification.deviceChangeFailed": "Produciuse un erro no cambio do dispositivo de son. Comprobe que o dispositivo escollido está correctamente definido e está dispoñíbel", "app.audioNotification.closeLabel": "Pechar", - "app.audioNotificaion.reconnectingAsListenOnly": "O micrófono bloqueouse para os espectadores, vostede está conectado só como oínte", + "app.audioNotificaion.reconnectingAsListenOnly": "O micrófono foi bloqueado para os espectadores, Vde. está conectado só como oínte", "app.breakoutJoinConfirmation.title": "Unirse á sala parcial", "app.breakoutJoinConfirmation.message": "Quere unirse?", "app.breakoutJoinConfirmation.confirmDesc": "Unirse a unha sala parcial", @@ -647,14 +699,14 @@ "app.audioModal.ariaTitle": " Xanela modal para unirse ao son", "app.audioModal.microphoneLabel": "Micrófono", "app.audioModal.listenOnlyLabel": "Só escoitar", - "app.audioModal.microphoneDesc": "Únese á xuntanza de son co micrófono", - "app.audioModal.listenOnlyDesc": "Únese á conferencia de son só como ointe", + "app.audioModal.microphoneDesc": "Únase á xuntanza de son co micrófono", + "app.audioModal.listenOnlyDesc": "Únase á conferencia de son só como ointe", "app.audioModal.audioChoiceLabel": "Gustaríalle unirse ao son?", "app.audioModal.iOSBrowser": "Son/vídeo non admitido", "app.audioModal.iOSErrorDescription": "Neste momento o son e o vídeo non son compatíbeis co Chrome para iOS.", "app.audioModal.iOSErrorRecommendation": "Recomendámoslle empregar Safari iOS.", "app.audioModal.audioChoiceDesc": "Seleccionar como unirse ao son nesta xuntanza", - "app.audioModal.unsupportedBrowserLabel": "Parece que estás a usar un navegador que non é totalmente compatíbel. Utilice {0} ou {1} para obter unha compatibilidade completa.", + "app.audioModal.unsupportedBrowserLabel": "Semella que está a usar un navegador que non é totalmente compatíbel. Utilice {0} ou {1} para obter unha compatibilidade completa.", "app.audioModal.closeLabel": "Pechar", "app.audioModal.yes": "Si", "app.audioModal.no": "Non", @@ -663,9 +715,9 @@ "app.audioModal.echoTestTitle": "Esta é unha proba de eco privada. Diga unhas palabras. Escoitou o son?", "app.audioModal.settingsTitle": "Cambiar os seus axustes do son", "app.audioModal.helpTitle": "Houbo un problema cos seus dispositivos multimedia", - "app.audioModal.helpText": "Deu permiso para acceder ao seu micrófono? Teña en conta que debería aparecer un diálogo cando tente unirse ao son, solicitando os permisos do seu dispositivo multimedia, acépteos para unirse á conferencia de son. Se non é así, tente cambiar os permisos do micrófono na configuración do seu navegador.", + "app.audioModal.helpText": "Deu permiso para acceder ao seu micrófono? Teña en conta que debería aparecer un diálogo cando tente unirse ao son, solicitando os permisos do seu dispositivo multimedia, acépteos para unirse á conferencia de son. Se non é así, tente cambiar os permisos do micrófono nos axustes do seu navegador.", "app.audioModal.help.noSSL": "Esta páxina non é segura. Para poder acceder ao micrófono a páxina ten que ser servida mediante HTTPS. Contacte co administrador do servidor.", - "app.audioModal.help.macNotAllowed": "Parece que as preferencias do teu sistema Mac están a bloquear o acceso ao mícrofono. Abra Preferencias do sistema > Seguridade e privacidade > Privacidade > Micrófono, e verifique que o navegador que está a usar está marcado.", + "app.audioModal.help.macNotAllowed": "Semella que as preferencias do seu sistema Mac están a bloquear o acceso ao mícrofono. Abra Preferencias do sistema > Seguridade e privacidade > Privacidade > Micrófono, e verifique que o navegador que está a usar está marcado.", "app.audioModal.audioDialTitle": "Unirse usando o seu teléfono", "app.audioDial.audioDialDescription": "Marcar", "app.audioDial.audioDialConfrenceText": "e introduza o número de PIN da conferencia:", @@ -675,9 +727,9 @@ "app.audioDial.tipIndicator": "Consello", "app.audioDial.tipMessage": "Prema a tecla «0» no seu teléfono para silenciar/devolver o seu propio son.", "app.audioModal.connecting": "Estabelecendo a conexión co son", - "app.audioManager.joinedAudio": "Vostede uniuse á conferencia de son", - "app.audioManager.joinedEcho": "Vostede uniuse á proba de eco", - "app.audioManager.leftAudio": "Vostede saíu da conferencia de son", + "app.audioManager.joinedAudio": "Vde. uniuse á conferencia de son", + "app.audioManager.joinedEcho": "Vde. uniuse á proba de eco", + "app.audioManager.leftAudio": "Vde. saíu da conferencia de son", "app.audioManager.reconnectingAudio": "Tentando volver conectar o son", "app.audioManager.genericError": "Erro: produciuse un erro, tenteo de novo", "app.audioManager.connectionError": "Erro: Produciuse un erro de conexión", @@ -689,23 +741,23 @@ "app.audio.changeAudioDevice": "Cambiar o dispositivo de son", "app.audio.enterSessionLabel": "Entrar na sesión", "app.audio.playSoundLabel": "Reproducir son", - "app.audio.stopAudioFeedback": "Deter a retroalimentación de audio", + "app.audio.stopAudioFeedback": "Deter o retorno de son", "app.audio.backLabel": "Atrás", "app.audio.loading": "Cargando", "app.audio.microphones": "Micrófonos", - "app.audio.speakers": "Altufalantes", - "app.audio.noDeviceFound": "Non se atopou o dispositivo", + "app.audio.speakers": "Altofalantes", + "app.audio.noDeviceFound": "Non se atopou ningún dispositivo", "app.audio.audioSettings.titleLabel": "Escolla os seus axustes do son", - "app.audio.audioSettings.descriptionLabel": "Teña en conta que aparecerá un diálogo no navegador que lle requerira que acepte compartir o seu micrófono", - "app.audio.audioSettings.microphoneSourceLabel": "Fonte de micrófono", - "app.audio.audioSettings.speakerSourceLabel": "Fonte de altofalante", + "app.audio.audioSettings.descriptionLabel": "Teña en conta que aparecerá un diálogo no navegador que lle solicitará que acepte compartir o seu micrófono", + "app.audio.audioSettings.microphoneSourceLabel": "Entrada de micrófono", + "app.audio.audioSettings.speakerSourceLabel": "Saída de altofalante", "app.audio.audioSettings.testSpeakerLabel": "Probar o seu altofalante", "app.audio.audioSettings.microphoneStreamLabel": "O seu volume do fluxo de son", "app.audio.audioSettings.retryLabel": "Tentar de novo", - "app.audio.audioSettings.fallbackInputLabel": "Entrada de audio {0}", - "app.audio.audioSettings.fallbackOutputLabel": "Saída de audio {0}", - "app.audio.audioSettings.defaultOutputDeviceLabel": "Por defecto", - "app.audio.audioSettings.findingDevicesLabel": "Buscando dispositivos...", + "app.audio.audioSettings.fallbackInputLabel": "Entrada de son {0}", + "app.audio.audioSettings.fallbackOutputLabel": "Saída de son {0}", + "app.audio.audioSettings.defaultOutputDeviceLabel": "Predeterminado", + "app.audio.audioSettings.findingDevicesLabel": "Buscando dispositivos…", "app.audio.listenOnly.backLabel": "Atrás", "app.audio.listenOnly.closeLabel": "Pechar", "app.audio.permissionsOverlay.title": "Permitir o acceso ao seu micrófono", @@ -714,10 +766,10 @@ "app.audio.captions.button.stop": "Deter os subtítulos", "app.audio.captions.button.language": "Idioma", "app.audio.captions.button.transcription": "Transcrición", - "app.audio.captions.button.transcriptionSettings": "Configuración de transcrición", + "app.audio.captions.button.transcriptionSettings": "Axustes da transcrición", "app.audio.captions.speech.title": "Transcrición automática", "app.audio.captions.speech.disabled": "Desactivado", - "app.audio.captions.speech.unsupported": "O teu navegador non admite o recoñecemento de voz. O teu audio non se transcribirá", + "app.audio.captions.speech.unsupported": "O seu navegador non admite o recoñecemento de voz. O seu son non se transcribirá", "app.audio.captions.select.de-DE": "Alemán", "app.audio.captions.select.en-US": "Inglés", "app.audio.captions.select.es-ES": "Español", @@ -728,23 +780,23 @@ "app.audio.captions.select.pt-BR": "Portugués", "app.audio.captions.select.ru-RU": "Ruso", "app.audio.captions.select.zh-CN": "Chinés", - "app.error.removed": "Vostede foi retirado/a da conferencia", - "app.error.meeting.ended": "Vostede desconectouse da conferencia", + "app.error.removed": "Vde. foi retirado/a da conferencia", + "app.error.meeting.ended": "Vde. desconectouse da conferencia", "app.meeting.logout.duplicateUserEjectReason": "Usuario duplicado tentando unirse á xuntanza", "app.meeting.logout.permissionEjectReason": "Expulsado por violación de permiso", - "app.meeting.logout.ejectedFromMeeting": "Vostede foi retirado/a da xuntanza", + "app.meeting.logout.ejectedFromMeeting": "Vde. foi retirado/a da xuntanza", "app.meeting.logout.validateTokenFailedEjectReason": "Produciuse un erro ao validar o testemuño de autorización", "app.meeting.logout.userInactivityEjectReason": "Usuario inactivo durante demasiado tempo", - "app.meeting.logout.maxParticipantsReached": "Alcanzouse o número máximo de participantes permitido para esta reunión", + "app.meeting.logout.maxParticipantsReached": "Acadouse o número máximo de participantes permitido para esta xuntanza", "app.meeting-ended.rating.legendLabel": "Valoración de comentarios", "app.meeting-ended.rating.starLabel": "Estrela", "app.modal.close": "Pechar", - "app.modal.close.description": "Ignora os cambios e pecha a xanela modal.", + "app.modal.close.description": "Ignorar os cambios e pechar a xanela modal.", "app.modal.confirm": "Feito", "app.modal.newTab": "(abre unha nova lapela)", - "app.modal.confirm.description": "Garda os cambios e pecha a xanela modal", + "app.modal.confirm.description": "Gardar os cambios e pechar a xanela modal", "app.modal.randomUser.noViewers.description": "Non hai espectadores dispoñíbeis para seleccionar ao chou", - "app.modal.randomUser.selected.description": "Vostede foi seleccionado ao chou", + "app.modal.randomUser.selected.description": "Vde. foi seleccionado ao chou", "app.modal.randomUser.title": "Usuario seleccionado ao chou", "app.modal.randomUser.who": "Quen será seleccionado…?", "app.modal.randomUser.alone": "Só hai un espectador", @@ -754,14 +806,14 @@ "app.dropdown.list.item.activeLabel": "Activo", "app.error.400": "Solicitude incorrecta", "app.error.401": "Non autorizado", - "app.error.403": "Vostede foi retirado/a da xuntanza", + "app.error.403": "Vde. foi retirado/a da xuntanza", "app.error.404": "Non se atopou", "app.error.408": "Produciuse un fallo de autenticación", "app.error.409": "Conflito", "app.error.410": "Rematou a xuntanza", "app.error.500": "Ouh! algo foi mal", - "app.error.503": "Desconectácheste", - "app.error.disconnected.rejoin": "Podes actualizar a páxina para unirte de novo.", + "app.error.503": "Vde. foi desconectado", + "app.error.disconnected.rejoin": "Pode actualizar a páxina para volver unirse", "app.error.userLoggedOut": "O usuario ten un testemuño de sesión non válido por mor do peche da sesión", "app.error.ejectedUser": "O usuario ten un testemuño de sesión non válido por mor da expulsión", "app.error.joinedAnotherWindow": "Semella que esta sesión está aberta noutra xanela do navegador.", @@ -782,7 +834,7 @@ "app.guest.guestDeny": "O convidado negouse a unirse á xuntanza.", "app.guest.seatWait": "O convidado está a agardar por unha praza na xuntanza.", "app.guest.allow": "Convidado aprobado e redirixindo á xuntanza.", - "app.guest.firstPositionInWaitingQueue": "Vostede é o primeiro da fila!", + "app.guest.firstPositionInWaitingQueue": "Vde. é o primeiro da fila!", "app.guest.positionInWaitingQueue": "A súa posición actual na cola de espera:", "app.guest.guestInvalid": "O usuario convidado non é válido", "app.guest.meetingForciblyEnded": "Non é posíbel unirse a unha xuntanza que xa foi finalizada á forza", @@ -807,20 +859,27 @@ "app.userList.guest.feedbackMessage": "Acción aplicada:", "app.user-info.title": "Atopar directorio", "app.toast.breakoutRoomEnded": "A sala parcial rematou. Volva incorporarse ao son.", - "app.toast.chat.public": "Nova mensaxe na conversa pública", - "app.toast.chat.private": "Nova mensaxe na conversa privada", + "app.toast.chat.public": "Nova mensaxe na parola pública", + "app.toast.chat.private": "Nova mensaxe na parola privada", "app.toast.chat.system": "Sistema", "app.toast.chat.poll": "Resultados da enquisa", - "app.toast.chat.pollClick": "Publicáronse os resultados das enquisas. Fai clic aquí para ver.", - "app.toast.clearedEmoji.label": "Estado do emoji limpo", - "app.toast.setEmoji.label": "Estado do emoji cambiado a {0}", + "app.toast.chat.pollClick": "Publicáronse os resultados das enquisas. Prema aquí para velos.", + "app.toast.clearedEmoji.label": "O estado do «emoji» está limpo", + "app.toast.setEmoji.label": "O «emoji» de estado foi definido como {0}", "app.toast.meetingMuteOn.label": "Todos os usuarios foron silenciados", "app.toast.meetingMuteOnViewers.label": "Todos os espectadores foron silenciados", - "app.toast.meetingMuteOff.label": "Desactivouse a reunión silenciosa", - "app.toast.setEmoji.raiseHand": "Vostede ergueu a man", - "app.toast.setEmoji.lowerHand": "A túa man foi baixada", - "app.toast.promotedLabel": "Vostede foi promovido a moderador", - "app.toast.demotedLabel": "Vostede foi relegado a espectador", + "app.toast.meetingMuteOff.label": "Desactivouse o silencio da xuntanza", + "app.toast.wakeLock.offerTitle": "Quere manter activa a pantalla do eu dispositivo durante a xuntanza?", + "app.toast.wakeLock.offerAccept": "Si!", + "app.toast.wakeLock.offerDecline": "Agora non", + "app.toast.wakeLock.acquireSuccess": "O «bloqueo de apagado da pantalla» está activo! Pode desactivalo no menú de axustes.", + "app.toast.wakeLock.acquireFailed": "Produciuse un erro ao obter o «bloqueo de apagado da pantalla».", + "app.toast.setEmoji.raiseHand": "Vde. ergueu a man", + "app.toast.setEmoji.lowerHand": "A súa man foi baixada", + "app.toast.setEmoji.away": "Vde. definiu o seu estado como ausente", + "app.toast.setEmoji.notAway": "Vde. retirou o seu estado de ausente", + "app.toast.promotedLabel": "Vde. foi promovido a moderador", + "app.toast.demotedLabel": "Vde. foi relegado a espectador", "app.notification.recordingStart": "A sesión esta a ser gravada", "app.notification.recordingStop": "Esta sesión non está a ser gravada", "app.notification.recordingPaused": "Xa non se está a gravar esta sesión", @@ -834,18 +893,18 @@ "app.shortcut-help.alternativeLabel": "Alternativa", "app.shortcut-help.functionLabel": "Función", "app.shortcut-help.closeLabel": "Pechar", - "app.shortcut-help.closeDesc": " Pecha a xanela modal de atallos do teclado", + "app.shortcut-help.closeDesc": "Pecha a xanela modal de atallos do teclado", "app.shortcut-help.openOptions": "Abrir opcións", - "app.shortcut-help.toggleUserList": "Alternar a lista de usuarios", + "app.shortcut-help.toggleUserList": "Amosar/agochar a lista de usuarios", "app.shortcut-help.toggleMute": "Silenciar/Devolver o son", - "app.shortcut-help.togglePublicChat": "Alternar a conversa pública (a lista de usuarios debe estar aberta)", - "app.shortcut-help.hidePrivateChat": "Agochar a conversa privada", - "app.shortcut-help.closePrivateChat": "Pechar a conversa privada", + "app.shortcut-help.togglePublicChat": "Amosar/agochar a parola pública (a lista de usuarios debe estar aberta)", + "app.shortcut-help.hidePrivateChat": "Agochar a parola privada", + "app.shortcut-help.closePrivateChat": "Pechar a parola privada", "app.shortcut-help.openActions": "Abrir o menú de accións", - "app.shortcut-help.raiseHand": "Alternar erguer a man", + "app.shortcut-help.raiseHand": "Amosar/agochar as mans ergueitas", "app.shortcut-help.openDebugWindow": "Abrir a xanela de depuración", "app.shortcut-help.openStatus": "Abrir o menú de estados", - "app.shortcut-help.togglePan": "Activar a ferramenta Pan (Presentador)", + "app.shortcut-help.togglePan": "Activar a ferramenta «Panorama» (Presentador)", "app.shortcut-help.toggleFullscreen": "Alternar a pantalla completa (presentador)", "app.shortcut-help.nextSlideDesc": "Diapositiva seguinte (Presentador)", "app.shortcut-help.previousSlideDesc": "Diapositiva anterior (Presentador)", @@ -853,7 +912,7 @@ "app.shortcut-help.toggleFullscreenKey": "Intro", "app.shortcut-help.nextSlideKey": "Frecha dereita", "app.shortcut-help.previousSlideKey": "Frecha esquerda", - "app.shortcut-help.select": "Selecciona Ferramenta", + "app.shortcut-help.select": "Seleccionar a ferramenta", "app.shortcut-help.pencil": "Lapis", "app.shortcut-help.eraser": "Borrador", "app.shortcut-help.rectangle": "Rectángulo", @@ -868,13 +927,13 @@ "app.shortcut-help.whiteboard": "Encerado", "app.shortcut-help.zoomIn": "Achegarse", "app.shortcut-help.zoomOut": "Afastarse", - "app.shortcut-help.zoomFit": "Restaurar o zoom", - "app.shortcut-help.zoomSelect": "Zoom á selección", - "app.shortcut-help.flipH": "Volteo horizontal", - "app.shortcut-help.flipV": "Volteo vertical", + "app.shortcut-help.zoomFit": "Restabelecer o zoom", + "app.shortcut-help.zoomSelect": "Zoom sobre a selección", + "app.shortcut-help.flipH": "Voltear en horizontal", + "app.shortcut-help.flipV": "Voltear en vertical", "app.shortcut-help.lock": "Bloquear / Desbloquear", - "app.shortcut-help.moveToFront": "Mover ao fronte", - "app.shortcut-help.moveToBack": "Mover cara atrás", + "app.shortcut-help.moveToFront": "Mover para a fronte", + "app.shortcut-help.moveToBack": "Mover para o fondo", "app.shortcut-help.moveForward": "Mover cara adiante", "app.shortcut-help.moveBackward": "Mover cara atrás", "app.shortcut-help.undo": "Desfacer", @@ -892,8 +951,8 @@ "app.lock-viewers.webcamLabel": "Compartir a cámara web", "app.lock-viewers.otherViewersWebcamLabel": "Ver a cámara web doutros espectadores", "app.lock-viewers.microphoneLable": "Compartir o micrófono", - "app.lock-viewers.PublicChatLabel": "Enviar mensaxes á conversa pública ", - "app.lock-viewers.PrivateChatLable": "Enviar mensaxes á conversa privada", + "app.lock-viewers.PublicChatLabel": "Enviar mensaxes á parola pública ", + "app.lock-viewers.PrivateChatLable": "Enviar mensaxes á parola privada", "app.lock-viewers.notesLabel": "Editar notas compartidas", "app.lock-viewers.userListLabel": "Ver outros espectadores na lista de usuarios", "app.lock-viewers.ariaTitle": "Bloquear a xanela modal de axustes dos espectadores", @@ -901,13 +960,15 @@ "app.lock-viewers.button.cancel": "Cancelar", "app.lock-viewers.locked": "Bloqueado", "app.lock-viewers.hideViewersCursor": "Ver os cursores doutros espectadores", + "app.lock-viewers.hideAnnotationsLabel": "Ver as anotacións doutros espectadores", "app.guest-policy.ariaTitle": "Axustes das normas de convidados modais", "app.guest-policy.title": "Normas para os convidados", "app.guest-policy.description": "Cambiar os axustes das normas de convidados á xuntanza", "app.guest-policy.button.askModerator": "Pregunta ao moderador", "app.guest-policy.button.alwaysAccept": "Aceptar sempre", "app.guest-policy.button.alwaysDeny": "Denegar sempre", - "app.guest-policy.policyBtnDesc": "Establece a política de convidados á xuntanza", + "app.guest-policy.policyBtnDesc": "Define a directiva de convidados á xuntanza", + "app.guest-policy.feedbackMessage": "As normas para os convidados agora son:", "app.connection-status.ariaTitle": "Estado da conexión modal", "app.connection-status.title": "Estado da conexión", "app.connection-status.description": "Ver o estado de conexión dos usuarios", @@ -940,14 +1001,14 @@ "app.recording.startTitle": "Iniciar a gravación", "app.recording.stopTitle": "Pausar a gravación", "app.recording.resumeTitle": "Continuar coa gravación", - "app.recording.startDescription": "Máis tarde pode usar o botón de gravación para deter a gravación.", + "app.recording.startDescription": "Máis adiante pode usar o botón de gravación para deter a gravación.", "app.recording.stopDescription": "Confirma que quere deter a gravación? Pode continuala premendo de novo o botón de gravación.", "app.recording.notify.title": "Comezou a gravación", - "app.recording.notify.description": "Haberá unha gravación dispoñible en función do resto desta sesión", + "app.recording.notify.description": "Haberá unha gravación dispoñíbel en función do resto desta sesión", "app.recording.notify.continue": "Continuar", "app.recording.notify.leave": "Abandonar a xuntanza", "app.recording.notify.continueLabel" : "Aceptar a gravación e continuar", - "app.recording.notify.leaveLabel" : "Non aceptar a gravación e saír da reunión", + "app.recording.notify.leaveLabel" : "Non aceptar a gravación e saír da xuntanza", "app.videoPreview.cameraLabel": "Cámara web", "app.videoPreview.profileLabel": "Calidade", "app.videoPreview.quality.low": "Baixa", @@ -965,6 +1026,7 @@ "app.videoPreview.webcamPreviewLabel": "Vista preliminar de cámara web", "app.videoPreview.webcamSettingsTitle": "Axustes da cámara web", "app.videoPreview.webcamEffectsTitle": "Efectos visuais da cámara web", + "app.videoPreview.cameraAsContentSettingsTitle": "Cámara de presentar", "app.videoPreview.webcamVirtualBackgroundLabel": "Axustes do fondo virtual", "app.videoPreview.webcamVirtualBackgroundDisabledLabel": "Este dispositivo non admite fondos virtuais", "app.videoPreview.webcamNotFoundLabel": "Non se atopou a cámara web", @@ -976,7 +1038,7 @@ "app.video.joinVideo": "Compartir a cámara web", "app.video.connecting": "Esta a comezar o uso compartido da cámara web…", "app.video.leaveVideo": "Deixar de compartir a cámara web", - "app.video.videoSettings": "Configuración do vídeo", + "app.video.videoSettings": "Axustes do vídeo", "app.video.visualEffects": "Efectos visuais", "app.video.advancedVideo": "Abrir os axustes avanzados", "app.video.iceCandidateError": "Produciuse un erro ao engadir un candidato ICE", @@ -988,12 +1050,12 @@ "app.video.securityError": "A compatibilidade multimedia está desactivada no documento", "app.video.typeError": "A lista de restricións especificada está baleira ou ten todas as restricións estabelecidas a falso", "app.video.notFoundError": "Non se atopou a cámara web. Asegúrese de que estea conectada", - "app.video.notAllowed": "Fallo o permiso para a cámara web compartida, asegúrese de que os permisos do seu navegador son correctos", + "app.video.notAllowed": "É necesario conceder permiso para acceder ás cámaras web", "app.video.notSupportedError": "Só é posíbel compartir cámaras web de fontes seguras, asegúrese de que o certificado SSL sexa valido", "app.video.notReadableError": "Non foi posíbel obter vídeo da cámara web. Asegúrese de que outro programa non estea a usar a cámara web", "app.video.timeoutError": "O navegador non respondeu a tempo.", "app.video.genericError": "Produciuse un erro descoñecido co dispositivo (erro {0})", - "app.video.inactiveError": "A túa cámara web parouse inesperadamente. Revisa os permisos do teu navegador", + "app.video.inactiveError": "A súa cámara web detívose inesperadamente. Revise os permisos do seu navegador", "app.video.mediaTimedOutError": "A transmisión da súa cámara web foi interrompida. Tente compartila de novo", "app.video.mediaFlowTimeout1020": "Os recursos multimedia non foron quen de acadar o servidor (erro 1020)", "app.video.suggestWebcamLock": "Forzar os axustes de bloqueo para as cámaras web dos espectadores?", @@ -1005,8 +1067,8 @@ "app.video.videoLocked": "Compartir cámara web bloqueada", "app.video.videoButtonDesc": "Compartir a cámara web", "app.video.videoMenu": "Menú de vídeo", - "app.video.videoMenuDisabled": "O menú de vídeo da cámara web está desactivada nos axustes", - "app.video.videoMenuDesc": "Abrir o menú despregable de vídeo", + "app.video.videoMenuDisabled": "O menú de vídeo da cámara web está desactivado nos axustes", + "app.video.videoMenuDesc": "Abrir o menú despregábel de vídeo", "app.video.pagination.prevPage": "Ver os vídeos anteriores", "app.video.pagination.nextPage": "Ver os seguintes vídeos", "app.video.clientDisconnected": "Non é posíbel compartir a cámara web por mor de problemas de conexión", @@ -1017,21 +1079,21 @@ "app.video.virtualBackground.coffeeshop": "Cafetería", "app.video.virtualBackground.background": "Fondo", "app.video.virtualBackground.backgroundWithIndex": "Fondo {0}", - "app.video.virtualBackground.custom": "Cargar desde o teu ordenador", - "app.video.virtualBackground.remove": "Eliminar a imaxe engadida", + "app.video.virtualBackground.custom": "Enviar dende o seu computador", + "app.video.virtualBackground.remove": "Retirar a imaxe engadida", "app.video.virtualBackground.genericError": "Produciuse un fallo ao aplicar o efecto da cámara. Ténteo de novo.", - "app.video.virtualBackground.camBgAriaDesc": "Establece o fondo virtual da cámara web a {0}", + "app.video.virtualBackground.camBgAriaDesc": "Define o fondo virtual da cámara web a {0}", "app.video.virtualBackground.maximumFileSizeExceeded": "Superouse o tamaño máximo do ficheiro. ({0}MB)", - "app.video.virtualBackground.typeNotAllowed": "Non se permite o tipo de ficheiro.", + "app.video.virtualBackground.typeNotAllowed": "Este tipo de ficheiro non está permitido.", "app.video.virtualBackground.errorOnRead": "Produciuse un erro ao ler o ficheiro.", - "app.video.virtualBackground.uploaded": "Cargado", - "app.video.virtualBackground.uploading": "Cargando...", + "app.video.virtualBackground.uploaded": "Enviado", + "app.video.virtualBackground.uploading": "Enviando…", "app.video.virtualBackground.button.customDesc": "Engade unha nova imaxe de fondo virtual", "app.video.camCapReached": "Non pode compartir máis cámaras", "app.video.meetingCamCapReached": "A xuntanza acadou o seu límite de cámaras simultáneas", "app.video.dropZoneLabel": "Soltar aquí", "app.fullscreenButton.label": "Poñer {0} a pantalla completa", - "app.fullscreenUndoButton.label": "Desfacer a pantalla completa de {0}", + "app.fullscreenUndoButton.label": "Saír da pantalla completa de {0}", "app.switchButton.expandLabel": "Ampliar a pantalla compartida de vídeo", "app.switchButton.shrinkLabel": "Reducir a pantalla compartida de vídeo", "app.sfu.mediaServerConnectionError2000": "Non foi posíbel conectar co servidor multimedia (erro 2000)", @@ -1047,7 +1109,7 @@ "app.whiteboard.annotations.poll": "Os resultados da enquisa foron publicados", "app.whiteboard.annotations.pollResult": "Resultado da enquisa", "app.whiteboard.annotations.noResponses": "Sen respostas", - "app.whiteboard.annotations.notAllowed": "Non tes permiso para facer este cambio", + "app.whiteboard.annotations.notAllowed": "Non ten permiso para facer este cambio", "app.whiteboard.annotations.numberExceeded": "O número de anotacións superou o límite ({0})", "app.whiteboard.toolbar.tools": "Ferramentas", "app.whiteboard.toolbar.tools.hand": "Panorama", @@ -1057,8 +1119,8 @@ "app.whiteboard.toolbar.tools.ellipse": "Elipse", "app.whiteboard.toolbar.tools.line": "Liña", "app.whiteboard.toolbar.tools.text": "Texto", - "app.whiteboard.toolbar.thickness": "Grosor da liña", - "app.whiteboard.toolbar.thicknessDisabled": "O grosor da liña está desactivado", + "app.whiteboard.toolbar.thickness": "Grosor do debuxo", + "app.whiteboard.toolbar.thicknessDisabled": "O grosor do debuxo está desactivado", "app.whiteboard.toolbar.color": "Cores", "app.whiteboard.toolbar.colorDisabled": "As cores están desactivadas", "app.whiteboard.toolbar.color.black": "Negro", @@ -1075,24 +1137,28 @@ "app.whiteboard.toolbar.color.silver": "Prata", "app.whiteboard.toolbar.undo": "Desfacer as anotacións", "app.whiteboard.toolbar.clear": "Limpar todas as anotacións", - "app.whiteboard.toolbar.clearConfirmation": "Estás seguro de que queres borrar todas as anotacións?", + "app.whiteboard.toolbar.clearConfirmation": "Confirma que quere limpar todas as anotacións?", "app.whiteboard.toolbar.multiUserOn": "Activar o modo multiusuario do encerado", "app.whiteboard.toolbar.multiUserOff": "Desactivar o modo multiusuario do encerado", "app.whiteboard.toolbar.palmRejectionOn": "Activar o rexeitamento da palma da man", "app.whiteboard.toolbar.palmRejectionOff": "Desctivar o rexeitamento da palma da man", "app.whiteboard.toolbar.fontSize": "Lista de tamaño de letras", "app.whiteboard.toolbarAriaLabel": "Ferramentas de presentación", - "app.feedback.title": "Vostede desconectouse da conferencia", + "app.feedback.title": "Vde. desconectouse da conferencia", "app.feedback.subtitle": "Encantaríanos saber cal foi a súa experiencia con BigBlueButton (opcional)", "app.feedback.textarea": "Como podemos mellorar BigBlueButton?", "app.feedback.sendFeedback": "Enviar comentarios", - "app.feedback.sendFeedbackDesc": "Enviar comentarios e abandonar a sesión", + "app.feedback.sendFeedbackDesc": "Enviar comentarios e deixar a xuntanza", "app.videoDock.webcamMirrorLabel": "Espello", "app.videoDock.webcamMirrorDesc": "Inverter a cámara web seleccionada", "app.videoDock.webcamFocusLabel": "Poñer en foco", "app.videoDock.webcamFocusDesc": "Poñer en foco a cámara web seleccionada", "app.videoDock.webcamUnfocusLabel": "Retirar do foco", "app.videoDock.webcamUnfocusDesc": "Retirar do foco a camara web seleccionada", + "app.videoDock.webcamDisableLabel": "Desactivar a súa propia vista", + "app.videoDock.webcamDisableLabelAllCams": "Desactivar a súa propia vista (todas as cámaras)", + "app.videoDock.webcamEnableLabel": "Activar a súa propia vista", + "app.videoDock.webcamDisableDesc": "A súa propia vista está desactivada", "app.videoDock.webcamPinLabel": "Fixar", "app.videoDock.webcamPinDesc": "Fixar a cámara web seleccionada", "app.videoDock.webcamFullscreenLabel": "Cámara web a pantalla completa", @@ -1100,7 +1166,7 @@ "app.videoDock.webcamUnpinLabel": "Desprender", "app.videoDock.webcamUnpinLabelDisabled": "Só os moderadores poden desprender aos usuarios", "app.videoDock.webcamUnpinDesc": "Desprender a cámara web seleccionada", - "app.videoDock.autoplayBlockedDesc": "Necesitamos o seu permiso para mostrarlle as cámaras web doutros usuarios.", + "app.videoDock.autoplayBlockedDesc": "Necesitamos o seu permiso para amosarlle as cámaras web doutros usuarios.", "app.videoDock.autoplayAllowLabel": "Ver cámaras web", "app.createBreakoutRoom.title": "Salas parciais", "app.createBreakoutRoom.ariaTitle": "Agochar as salas parciais", @@ -1113,7 +1179,7 @@ "app.createBreakoutRoom.notAssigned": "Sen asignar ({0})", "app.createBreakoutRoom.join": "Unirse á sala", "app.createBreakoutRoom.joinAudio": "Unirse ao son", - "app.createBreakoutRoom.returnAudio": "Regresar ao son", + "app.createBreakoutRoom.returnAudio": "Tornar ao son", "app.createBreakoutRoom.alreadyConnected": "Xa está na sala", "app.createBreakoutRoom.confirm": "Crear", "app.createBreakoutRoom.record": "Gravar", @@ -1122,10 +1188,10 @@ "app.createBreakoutRoom.randomlyAssign": "Asignado aleatóriamente", "app.createBreakoutRoom.randomlyAssignDesc": "Asigna usuarios ao chou a salas parciais", "app.createBreakoutRoom.resetAssignments": "Restabelecer as asignacións", - "app.createBreakoutRoom.resetAssignmentsDesc": "Restabelece todas as asignacións de salas de usuarios", + "app.createBreakoutRoom.resetAssignmentsDesc": "Restabelece todas as asignacións de usuarios a salas", "app.createBreakoutRoom.endAllBreakouts": "Rematar todas as salas parciais", "app.createBreakoutRoom.chatTitleMsgAllRooms": "todas as salas", - "app.createBreakoutRoom.msgToBreakoutsSent": "Enviouse a mensaxe a {0} salas parciais", + "app.createBreakoutRoom.msgToBreakoutsSent": "A mensaxe foi enviada a {0} salas parciais", "app.createBreakoutRoom.roomName": "{0} (Sala - {1})", "app.createBreakoutRoom.doneLabel": "Feito", "app.createBreakoutRoom.nextLabel": "Seguinte", @@ -1133,28 +1199,30 @@ "app.createBreakoutRoom.addRoomTime": "Incrementar o tempo da sala parcial", "app.createBreakoutRoom.addParticipantLabel": "+ Engadir participante", "app.createBreakoutRoom.freeJoin": "Permitirlle aos usuarios escoller a sala parcial á que incorporarse", - "app.createBreakoutRoom.captureNotes": "Captura notas compartidas cando rematen as salas de grupos", - "app.createBreakoutRoom.captureSlides": "Captura o encerado cando rematen as salas de grupos", + "app.createBreakoutRoom.manageRoomsLabel": "Xestionar salas", + "app.createBreakoutRoom.captureNotes": "Gardar as notas compartidas", + "app.createBreakoutRoom.captureSlides": "Gardar o encerado", + "app.createBreakoutRoom.sendInvitationToMods": "Enviar convite aos moderadores asignados", "app.createBreakoutRoom.leastOneWarnBreakout": "Debe poñer polo menos un usuario nunha sala parcial", "app.createBreakoutRoom.minimumDurationWarnBreakout": "A duración mínima para unha sala parcial é de {0} minutos.", - "app.createBreakoutRoom.modalDesc": "Consello: pode arrastrar e soltar o nome dun usuario para asignalo a unha sala parcial específica.", + "app.createBreakoutRoom.modalDesc": "Complete os pasos a continuación para crear salas parciais na súa sesión. Para engadir participantes a unha sala, só ten que arrastrar o nome do usuario á sala desexada.", "app.createBreakoutRoom.roomTime": "{0} minutos", "app.createBreakoutRoom.numberOfRoomsError": "O número de salas non é válido", "app.createBreakoutRoom.duplicatedRoomNameError": "Non é posíbel duplicar o nome da sala.", "app.createBreakoutRoom.emptyRoomNameError": "O nome da sala non pode estar baleiro.", - "app.createBreakoutRoom.setTimeInMinutes": "Establecer a duración en (minutos)", + "app.createBreakoutRoom.setTimeInMinutes": "Definir a duración en (minutos)", "app.createBreakoutRoom.setTimeLabel": "Aplicar", "app.createBreakoutRoom.setTimeCancel": "Cancelar", "app.createBreakoutRoom.setTimeHigherThanMeetingTimeError": "A duración das salas parciais non pode exceder o tempo restante da xuntanza.", "app.createBreakoutRoom.roomNameInputDesc": "Actualiza o nome da sala parcial", - "app.createBreakoutRoom.movedUserLabel": "Moveuse {0} á sala {1}", - "app.updateBreakoutRoom.modalDesc": "Para actualizar ou invitar un usuario, só tes que arrástralo á sala desexada.", + "app.createBreakoutRoom.movedUserLabel": "{0} foi movido cara á sala {1}", + "app.updateBreakoutRoom.modalDesc": "Para actualizar ou convidar un usuario, só ten que arrastrar o seu nome cara á sala desexada.", "app.updateBreakoutRoom.cancelLabel": "Cancelar", - "app.updateBreakoutRoom.title": "Actualizar salas de grupos", + "app.updateBreakoutRoom.title": "Actualizar as salas parciais", "app.updateBreakoutRoom.confirm": "Aplicar", - "app.updateBreakoutRoom.userChangeRoomNotification": "Trasladáronte á sala {0}.", + "app.updateBreakoutRoom.userChangeRoomNotification": "Vde. foi trasladado á sala {0}.", "app.smartMediaShare.externalVideo": "Vídeo(s) externo(s)", - "app.update.resetRoom": "Restablecer a sala de usuarios", + "app.update.resetRoom": "Restabelecer os usuarios da sala", "app.externalVideo.start": "Compartir un novo vídeo", "app.externalVideo.title": "Compartir un vídeo externo", "app.externalVideo.input": "URL do vídeo externo", @@ -1169,33 +1237,32 @@ "app.externalVideo.subtitlesOff": "Acender (se está dispoñíbel)", "app.actionsBar.actionsDropdown.shareExternalVideo": "Compartir un vídeo externo", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Deixar de compartir o vídeo externo", - "app.legacy.unsupportedBrowser": "Parece que estás a usar un navegador que non é compatíbel. Utilice {0} ou {1} para obter unha compatibilidade completa.", - "app.legacy.upgradeBrowser": "Parece que estás a usar unha versión antiga dun navegador compatíbel. Actualice o navegador para obter unha compatibilidade completa. ", + "app.legacy.unsupportedBrowser": "Semella que está a usar un navegador que non é compatíbel. Utilice {0} ou {1} para obter unha compatibilidade completa.", + "app.legacy.upgradeBrowser": "Semella que está a usar unha versión antiga dun navegador compatíbel. Actualice o navegador para obter unha compatibilidade completa. ", "app.legacy.criosBrowser": "En iOS, use Safari para ter una compatibilidade completa.", "app.debugWindow.windowTitle": "Depurar", "app.debugWindow.form.userAgentLabel": "Axente de usuario", "app.debugWindow.form.button.copy": "Copiar", "app.debugWindow.form.enableAutoarrangeLayoutLabel": "Activar a disposición de organización automática", "app.debugWindow.form.enableAutoarrangeLayoutDescription": "(desactivarase se arrastra ou redimensiona a área de cámaras web)", - "app.debugWindow.form.chatLoggerLabel": "Probar os niveis de rexistro da conversa", + "app.debugWindow.form.chatLoggerLabel": "Probar os niveis de rexistro da parola", "app.debugWindow.form.button.apply": "Aplicar", "app.layout.modal.title": "Deseños", - "app.layout.modal.confirm": "Confirmar", - "app.layout.modal.cancel": "Cancelar", - "app.layout.modal.layoutLabel": "Selecciona o teu deseño", - "app.layout.modal.keepPushingLayoutLabel": "Forzar o deseño para todos", - "app.layout.modal.pushLayoutLabel": "Forzar para todos", - "app.layout.modal.layoutToastLabel": "Cambiouse a configuración de deseño", + "app.layout.modal.update": "Actualizar", + "app.layout.modal.updateAll": "Actualizar a todos", + "app.layout.modal.layoutLabel": "Seleccione o seu deseño", + "app.layout.modal.pushLayoutLabel": "Aplicar para todos", + "app.layout.modal.layoutToastLabel": "Cambiaron os axustes de deseño", "app.layout.modal.layoutSingular": "Deseño", - "app.layout.modal.layoutBtnDesc": "Establece o deseño como opción seleccionada", + "app.layout.modal.layoutBtnDesc": "Define o deseño como opción seleccionada", "app.layout.style.custom": "Personalizado", "app.layout.style.smart": "Disposición intelixente", "app.layout.style.presentationFocus": "Poñer o foco na presentación", "app.layout.style.videoFocus": "Poñer o foco no vídeo", - "app.layout.style.customPush": "Personalizar (aplicar o deseño a todos os usuarios)", - "app.layout.style.smartPush": "Disposición intelixente (aplicar o deseño a todos os usuarios)", - "app.layout.style.presentationFocusPush": "Centrar na presentación (aplicar o deseño a todos os usuarios)", - "app.layout.style.videoFocusPush": "Centrar no vídeo (aplicar o deseño a todos os usuarios)", + "app.layout.style.customPush": "Personalizar (aplicar o deseño para todos)", + "app.layout.style.smartPush": "Disposición intelixente (aplicar o deseño para todos)", + "app.layout.style.presentationFocusPush": "Centrar na presentación (aplicar o deseño para todos)", + "app.layout.style.videoFocusPush": "Centrar no vídeo (aplicar o deseño para todos)", "playback.button.about.aria": "Sobre", "playback.button.clear.aria": "Limpar a busca", "playback.button.close.aria": "Pechar a xanela modal", @@ -1212,7 +1279,7 @@ "playback.player.about.modal.shortcuts.alt": "Alt", "playback.player.about.modal.shortcuts.shift": "Maiús", "playback.player.about.modal.shortcuts.fullscreen": "Poñer/retirar a pantalla completa", - "playback.player.about.modal.shortcuts.play": "Reproducir/Parar", + "playback.player.about.modal.shortcuts.play": "Reproducir/parar", "playback.player.about.modal.shortcuts.section": "Contraer/expandir a sección lateral", "playback.player.about.modal.shortcuts.seek.backward": "Buscar cara atrás", "playback.player.about.modal.shortcuts.seek.forward": "Buscar cara adiante", @@ -1228,7 +1295,7 @@ "playback.player.chat.message.poll.option.true": "Verdadeiro", "playback.player.chat.message.poll.option.false": "Falso", "playback.player.chat.message.video.name": "Vídeo externo", - "playback.player.chat.wrapper.aria": "Área de conversa", + "playback.player.chat.wrapper.aria": "Área de parola", "playback.player.notes.wrapper.aria": "Área de notas", "playback.player.presentation.wrapper.aria": "Área de presentación", "playback.player.screenshare.wrapper.aria": "Área de pantalla compartida", @@ -1242,7 +1309,7 @@ "app.learningDashboard.lastUpdatedLabel": "Última actualización en", "app.learningDashboard.sessionDataDownloadedLabel": "Descargado!", "app.learningDashboard.shareButton": "Compartir con outros", - "app.learningDashboard.shareLinkCopied": "A ligazón copiouse correctamente!", + "app.learningDashboard.shareLinkCopied": "A ligazón foi copiada correctamente!", "app.learningDashboard.user": "Usuario", "app.learningDashboard.indicators.meetingStatusEnded": "Rematado", "app.learningDashboard.indicators.meetingStatusActive": "Activo", @@ -1264,7 +1331,7 @@ "app.learningDashboard.userDetails.anonymousAnswer": "Enquisa anónima", "app.learningDashboard.userDetails.talkTime": "Tempo de conversa", "app.learningDashboard.userDetails.messages": "Mensaxes", - "app.learningDashboard.userDetails.emojis": "Emojis", + "app.learningDashboard.userDetails.emojis": "«Emojis»", "app.learningDashboard.userDetails.raiseHands": "Erguer as mans", "app.learningDashboard.userDetails.pollVotes": "Votos da enquisa", "app.learningDashboard.userDetails.onlineIndicator": "{0} tempo en liña", @@ -1273,7 +1340,7 @@ "app.learningDashboard.usersTable.colTalk": "Tempo de conversa", "app.learningDashboard.usersTable.colWebcam": "Tempo da cámara web", "app.learningDashboard.usersTable.colMessages": "Mensaxes", - "app.learningDashboard.usersTable.colEmojis": "Emojis", + "app.learningDashboard.usersTable.colEmojis": "«Emojis»", "app.learningDashboard.usersTable.colRaiseHands": "Erguer as mans", "app.learningDashboard.usersTable.colActivityScore": "Puntuación da actividade", "app.learningDashboard.usersTable.colStatus": "Estado", @@ -1289,7 +1356,7 @@ "app.learningDashboard.pollsTable.title": "Enquisas", "app.learningDashboard.pollsTable.anonymousAnswer": "Enquisa anónima (respostas na última fila)", "app.learningDashboard.pollsTable.anonymousRowName": "Anónima", - "app.learningDashboard.pollsTable.noPollsCreatedHeading": "Non se creou ningunha enquisa", + "app.learningDashboard.pollsTable.noPollsCreatedHeading": "Non foi creada ningunha enquisa", "app.learningDashboard.pollsTable.noPollsCreatedMessage": "Unha vez enviada unha enquisa aos usuarios, os seus resultados aparecerán nesta lista.", "app.learningDashboard.pollsTable.answerTotal": "Total", "app.learningDashboard.pollsTable.userLabel": "Usuario/a", @@ -1297,7 +1364,7 @@ "app.learningDashboard.statusTimelineTable.thumbnail": "Miniatura da presentación", "app.learningDashboard.statusTimelineTable.presentation": "Presentación", "app.learningDashboard.statusTimelineTable.pageNumber": "Páxina", - "app.learningDashboard.statusTimelineTable.setAt": "Establecido en", + "app.learningDashboard.statusTimelineTable.setAt": "Definir en", "app.learningDashboard.errors.invalidToken": "O testemuño de sesión non é válido", "app.learningDashboard.errors.dataUnavailable": "Os datos xa non están dispoñíbeis", "mobileApp.portals.list.empty.addFirstPortal.label": "Engada o seu primeiro portal usando o botón anterior,", From 09461caa848e19279c48fad1ded3b5eab21e1fca Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Porfirio Date: Fri, 11 Aug 2023 13:16:10 -0300 Subject: [PATCH 191/252] Update bigbluebutton-tests/playwright/whiteboard/draw.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Anton Barboza de Sá --- bigbluebutton-tests/playwright/whiteboard/draw.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-tests/playwright/whiteboard/draw.js b/bigbluebutton-tests/playwright/whiteboard/draw.js index 163d17b436..0f44baf634 100644 --- a/bigbluebutton-tests/playwright/whiteboard/draw.js +++ b/bigbluebutton-tests/playwright/whiteboard/draw.js @@ -17,7 +17,7 @@ class Draw extends Page { const wbBox = await this.getElementBoundingBox(e.whiteboard); - await this.page.mouse.click(wbBox.x + 0.3 * wbBox.width, wbBox.y + 0.3 * wbBox.height,{button: 'right'}); + await this.page.mouse.click(wbBox.x + 0.4 * wbBox.width, wbBox.y + 0.4 * wbBox.height, { button: 'right' }); const pasteLocator = this.page.locator(e.wbPaste); await expect(pasteLocator).toBeVisible(); From e615233ae85769b346dc9d07310174fac88031d7 Mon Sep 17 00:00:00 2001 From: Paul Trudel Date: Fri, 11 Aug 2023 18:11:18 +0000 Subject: [PATCH 192/252] Removed addSlidePositionsPubMsg --- .../AddSlidePositionsPubMsgHdlr.scala | 20 ------------------ .../PresentationPodHdlrs.scala | 1 - .../presentationpod/PresentationPodsApp.scala | 4 +++- .../bigbluebutton/core/db/PresPageDAO.scala | 13 ------------ .../core/db/PresPresentationDAO.scala | 4 ++-- .../core/models/PresentationPods.scala | 4 +++- .../senders/ReceivedJsonMsgHandlerActor.scala | 2 -- .../core/running/MeetingActor.scala | 1 - .../common2/domain/Presentation.scala | 2 +- .../common2/msgs/PresentationPodsMsgs.scala | 5 ----- .../server/modifiers/addPresentation.js | 7 +++++-- .../api/slides/server/modifiers/addSlide.js | 20 ++++++++++-------- .../server/modifiers/addSlidePositions.js | 21 ------------------- 13 files changed, 25 insertions(+), 79 deletions(-) delete mode 100755 akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/AddSlidePositionsPubMsgHdlr.scala diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/AddSlidePositionsPubMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/AddSlidePositionsPubMsgHdlr.scala deleted file mode 100755 index 3681c68866..0000000000 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/AddSlidePositionsPubMsgHdlr.scala +++ /dev/null @@ -1,20 +0,0 @@ -package org.bigbluebutton.core.apps.presentationpod - -import org.bigbluebutton.common2.msgs._ -import org.bigbluebutton.core.apps.RightsManagementTrait -import org.bigbluebutton.core.bus.MessageBus -import org.bigbluebutton.core.db.PresPageDAO -import org.bigbluebutton.core.domain.MeetingState2x -import org.bigbluebutton.core.running.LiveMeeting - -trait AddSlidePositionsPubMsgHdlr extends RightsManagementTrait { - - this: PresentationPodHdlrs => - - def handle(msg: AddSlidePositionsPubMsg, state: MeetingState2x, - liveMeeting: LiveMeeting, bus: MessageBus) = { - PresPageDAO.addSlidePosition(msg.body.slideId, msg.body.width, msg.body.height, - msg.body.viewBoxWidth, msg.body.viewBoxHeight) - state - } -} diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPodHdlrs.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPodHdlrs.scala index 9fd5414543..d640dda323 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPodHdlrs.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPodHdlrs.scala @@ -21,7 +21,6 @@ class PresentationPodHdlrs(implicit val context: ActorContext) with PresentationUploadTokenReqMsgHdlr with MakePresentationDownloadReqMsgHdlr with ResizeAndMovePagePubMsgHdlr - with AddSlidePositionsPubMsgHdlr with SlideResizedPubMsgHdlr with SyncGetPresentationPodsMsgHdlr with RemovePresentationPodPubMsgHdlr diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPodsApp.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPodsApp.scala index 56979c8921..ad9a07e6a8 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPodsApp.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPodsApp.scala @@ -86,7 +86,9 @@ object PresentationPodsApp { xOffset = page.xOffset, yOffset = page.yOffset, widthRatio = page.widthRatio, - heightRatio = page.heightRatio + heightRatio = page.heightRatio, + width = page.width, + height = page.height ) } PresentationVO(pres.id, temporaryPresentationId, pres.name, pres.current, pages.toVector, pres.downloadable, diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/PresPageDAO.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/PresPageDAO.scala index f253e36bcc..2a486271f5 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/PresPageDAO.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/PresPageDAO.scala @@ -74,19 +74,6 @@ object PresPageDAO { } } - def addSlidePosition(slideId: String, width: Double, height: Double, - viewBoxWidth: Double, viewBoxHeight: Double) = { - DatabaseConnection.db.run( - TableQuery[PresPageDbTableDef] - .filter(_.pageId === slideId) - .map(p => (p.width, p.height, p.viewBoxWidth, p.viewBoxHeight)) - .update((width, height, viewBoxWidth, viewBoxHeight)) - ).onComplete { - case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) added slide position on PresPage table") - case Failure(e) => DatabaseConnection.logger.debug(s"Error updating slide position on PresPage: $e") - } - } - def updateSlidePosition(pageId: String, width: Double, height: Double, xOffset: Double, yOffset: Double, widthRatio: Double, heightRatio: Double) = { DatabaseConnection.db.run( diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/PresPresentationDAO.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/PresPresentationDAO.scala index 2dfb7dcbe4..08b426f1e1 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/PresPresentationDAO.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/PresPresentationDAO.scala @@ -58,8 +58,8 @@ object PresPresentationDAO { yOffset = page._2.yOffset, widthRatio = page._2.widthRatio, heightRatio = page._2.heightRatio, - width = 1, - height = 1, + width = page._2.width, + height = page._2.height, viewBoxWidth = 1, viewBoxHeight = 1, maxImageWidth = 1440, diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/PresentationPods.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/PresentationPods.scala index 4be33b19ad..0c12696c8e 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/PresentationPods.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/PresentationPods.scala @@ -29,7 +29,9 @@ case class PresentationPage( xOffset: Double = 0, yOffset: Double = 0, widthRatio: Double = 100D, - heightRatio: Double = 100D + heightRatio: Double = 100D, + width: Double = 1440, + height: Double = 1080 ) object PresentationInPod { diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala index 084bbfbdd3..dda0c9714a 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala @@ -292,8 +292,6 @@ class ReceivedJsonMsgHandlerActor( routeGenericMsg[SetCurrentPagePubMsg](envelope, jsonNode) case ResizeAndMovePagePubMsg.NAME => routeGenericMsg[ResizeAndMovePagePubMsg](envelope, jsonNode) - case AddSlidePositionsPubMsg.NAME => - routeGenericMsg[AddSlidePositionsPubMsg](envelope, jsonNode) case SlideResizedPubMsg.NAME => routeGenericMsg[SlideResizedPubMsg](envelope, jsonNode) case RemovePresentationPubMsg.NAME => diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala index c0125f9f51..120291fe79 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala @@ -536,7 +536,6 @@ class MeetingActor( case m: PresentationPageCountErrorSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus) case m: PresentationUploadTokenReqMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus) case m: ResizeAndMovePagePubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus) - case m: AddSlidePositionsPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus) case m: SlideResizedPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus) case m: PresentationPageConvertedSysMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus) case m: PresentationPageConversionStartedSysMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus) diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Presentation.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Presentation.scala index 2eefc67aef..11590cb337 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Presentation.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Presentation.scala @@ -6,7 +6,7 @@ case class PresentationVO(id: String, temporaryPresentationId: String, name: Str case class PageVO(id: String, num: Int, thumbUri: String = "", txtUri: String, svgUri: String, current: Boolean = false, xOffset: Double = 0, - yOffset: Double = 0, widthRatio: Double = 100D, heightRatio: Double = 100D) + yOffset: Double = 0, widthRatio: Double = 100D, heightRatio: Double = 100D, width: Double = 1440D, height: Double = 1080D) case class PresentationPodVO(id: String, currentPresenter: String, presentations: Vector[PresentationVO]) diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/PresentationPodsMsgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/PresentationPodsMsgs.scala index 05e55ae8e6..eddac57c93 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/PresentationPodsMsgs.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/PresentationPodsMsgs.scala @@ -45,11 +45,6 @@ case class SlideResizedPubMsg(header: BbbClientMsgHeader, body: SlideResizedPubM case class SlideResizedPubMsgBody(pageId: String, width: Double, height: Double, xOffset: Double, yOffset: Double, widthRatio: Double, heightRatio: Double) -object AddSlidePositionsPubMsg { val NAME = "AddSlidePositionsPubMsg" } -case class AddSlidePositionsPubMsg(header: BbbClientMsgHeader, body: AddSlidePositionsPubMsgBody) extends StandardMsg -case class AddSlidePositionsPubMsgBody(slideId: String, width: Double, - height: Double, viewBoxWidth: Double, viewBoxHeight: Double) - object SetCurrentPresentationPubMsg { val NAME = "SetCurrentPresentationPubMsg" } case class SetCurrentPresentationPubMsg(header: BbbClientMsgHeader, body: SetCurrentPresentationPubMsgBody) extends StandardMsg case class SetCurrentPresentationPubMsgBody(podId: String, presentationId: String) diff --git a/bigbluebutton-html5/imports/api/presentations/server/modifiers/addPresentation.js b/bigbluebutton-html5/imports/api/presentations/server/modifiers/addPresentation.js index 06819c9c29..1716091b63 100755 --- a/bigbluebutton-html5/imports/api/presentations/server/modifiers/addPresentation.js +++ b/bigbluebutton-html5/imports/api/presentations/server/modifiers/addPresentation.js @@ -47,6 +47,8 @@ export default async function addPresentation(meetingId, podId, presentation) { yOffset: Number, widthRatio: Number, heightRatio: Number, + width: Number, + height: Number, }, ], downloadable: Boolean, @@ -62,13 +64,14 @@ export default async function addPresentation(meetingId, podId, presentation) { }; const modifier = { - $set: Object.assign({ + $set: { meetingId, podId, 'conversion.done': true, 'conversion.error': false, 'exportation.status': null, - }, flat(presentation, { safe: true })), + ...flat(presentation, { safe: true }), + }, }; try { diff --git a/bigbluebutton-html5/imports/api/slides/server/modifiers/addSlide.js b/bigbluebutton-html5/imports/api/slides/server/modifiers/addSlide.js index e785af5343..ce4f3b67c7 100755 --- a/bigbluebutton-html5/imports/api/slides/server/modifiers/addSlide.js +++ b/bigbluebutton-html5/imports/api/slides/server/modifiers/addSlide.js @@ -57,6 +57,8 @@ export default async function addSlide(meetingId, podId, presentationId, slide) yOffset: Number, widthRatio: Number, heightRatio: Number, + width: Number, + height: Number, content: String, }); @@ -79,15 +81,15 @@ export default async function addSlide(meetingId, podId, presentationId, slide) const imageUri = slide.svgUri || slide.pngUri; const modifier = { - $set: Object.assign( - { meetingId }, - { podId }, - { presentationId }, - { id: slideId }, - { imageUri }, - flat(restSlide), - { safe: true }, - ), + $set: { + meetingId, + podId, + presentationId, + id: slideId, + imageUri, + ...flat(restSlide), + safe: true, + }, }; const imageSizeUri = (loadSlidesFromHttpAlways ? imageUri.replace(/^https/i, 'http') : imageUri); diff --git a/bigbluebutton-html5/imports/api/slides/server/modifiers/addSlidePositions.js b/bigbluebutton-html5/imports/api/slides/server/modifiers/addSlidePositions.js index 11c66eecf2..9d59252552 100755 --- a/bigbluebutton-html5/imports/api/slides/server/modifiers/addSlidePositions.js +++ b/bigbluebutton-html5/imports/api/slides/server/modifiers/addSlidePositions.js @@ -1,6 +1,4 @@ import { SlidePositions } from '/imports/api/slides'; -import { Meteor } from 'meteor/meteor'; -import RedisPubSub from '/imports/startup/server/redis'; import Logger from '/imports/startup/server/logger'; import { check } from 'meteor/check'; import flat from 'flat'; @@ -12,10 +10,6 @@ export default async function addSlidePositions( slideId, slidePosition, ) { - const REDIS_CONFIG = Meteor.settings.private.redis; - const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; - const EVENT_NAME = 'AddSlidePositionsPubMsg'; - check(meetingId, String); check(podId, String); check(presentationId, String); @@ -56,21 +50,6 @@ export default async function addSlidePositions( } else { Logger.info(`Upserted slide position id=${slideId} pod=${podId} presentation=${presentationId}`); } - - const { - width, height, viewBoxWidth, viewBoxHeight, - } = slidePosition; - - const payload = { - slideId, - width, - height, - viewBoxWidth, - viewBoxHeight, - }; - - Logger.info('Sending slide position data to backen'); - RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, '', payload); } catch (err) { Logger.error(`Adding slide position to collection: ${err}`); } From d8bce66098ec5ad5bb904b75d2e891a8fc9fe645 Mon Sep 17 00:00:00 2001 From: Paul Trudel Date: Fri, 11 Aug 2023 18:18:36 +0000 Subject: [PATCH 193/252] Explicitly declare width and height as doubles --- .../org/bigbluebutton/core/models/PresentationPods.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/PresentationPods.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/PresentationPods.scala index 0c12696c8e..abb10b549d 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/PresentationPods.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/PresentationPods.scala @@ -30,8 +30,8 @@ case class PresentationPage( yOffset: Double = 0, widthRatio: Double = 100D, heightRatio: Double = 100D, - width: Double = 1440, - height: Double = 1080 + width: Double = 1440D, + height: Double = 1080D ) object PresentationInPod { From 822aabe6d18b3106632c496917275a7249353664 Mon Sep 17 00:00:00 2001 From: KDSBrowne Date: Fri, 11 Aug 2023 16:10:16 -0400 Subject: [PATCH 194/252] fix: Move Presentation Option Dropdown To The Left Side (#18538) * prevent duplication of presentation menu dropdown when visible * move presentation options dropdown to the left (out of tldraw UI) * adjust style menu UI in RTL --- .../ui/components/presentation/component.jsx | 3 - .../presentation-menu/component.jsx | 4 +- .../presentation/presentation-menu/styles.js | 11 +- .../ui/components/whiteboard/component.jsx | 81 +---- .../ui/components/whiteboard/container.jsx | 2 - .../presentation-ops-injector/component.jsx | 317 ------------------ .../presentation-ops-injector/container.jsx | 53 --- .../menu/component.jsx | 253 -------------- 8 files changed, 8 insertions(+), 716 deletions(-) delete mode 100644 bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/component.jsx delete mode 100644 bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/container.jsx delete mode 100644 bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/menu/component.jsx diff --git a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx index 0fbd7e72fb..0a6a5862f8 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx @@ -707,12 +707,9 @@ class Presentation extends PureComponent { intl, fullscreenElementId, layoutContextDispatch, - userIsPresenter, } = this.props; const { tldrawAPI, isToolbarVisible } = this.state; - if (userIsPresenter && isToolbarVisible) return null; - return ( { } return ( - + @@ -365,7 +365,7 @@ const PresentationMenu = (props) => { }} actions={options} /> - + ); }; diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-menu/styles.js b/bigbluebutton-html5/imports/ui/components/presentation/presentation-menu/styles.js index 98a3f37387..709a903d3f 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-menu/styles.js +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-menu/styles.js @@ -33,11 +33,10 @@ const DropdownButton = styled.button` } `; -const Right = styled.div` +const Left = styled.div` cursor: pointer; position: absolute; - left: auto; - right: 0px; + left: 0px; z-index: 999; box-shadow: 0 4px 2px -2px rgba(0, 0, 0, 0.05); border-bottom: 1px solid ${colorWhite}; @@ -56,8 +55,8 @@ const Right = styled.div` } [dir="rtl"] & { - right: auto; - left : ${borderSize}; + right: 0px; + left: auto; } `; @@ -141,7 +140,7 @@ const ButtonIcon = styled(Icon)` export default { DropdownButton, - Right, + Left, ToastText, StatusIcon, ToastIcon, diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx index e8c83fd9ac..9073e09d49 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import { TldrawApp, Tldraw } from '@tldraw/tldraw'; import SlideCalcUtil, { HUNDRED_PERCENT } from '/imports/utils/slideCalcUtils'; @@ -19,9 +18,7 @@ import PanToolInjector from './pan-tool-injector/component'; import { findRemoved, filterInvalidShapes, mapLanguage, sendShapeChanges, usePrevious, } from './utils'; -import { throttle } from '/imports/utils/throttle'; import { isEqual } from 'radash'; -import PresentationOpsContainer from './presentation-ops-injector/container'; const SMALL_HEIGHT = 435; const SMALLEST_DOCK_HEIGHT = 475; @@ -80,8 +77,6 @@ export default function Whiteboard(props) { isToolbarVisible, isModerator, fullscreenRef, - fullscreen, - setIsToolbarVisible, fullscreenElementId, layoutContextDispatch, } = props; @@ -112,37 +107,6 @@ export default function Whiteboard(props) { const isMountedRef = React.useRef(true); const [isToolLocked, setIsToolLocked] = React.useState(tldrawAPI?.appState?.isToolLocked); const [bgShape, setBgShape] = React.useState(null); - const [labels, setLabels] = React.useState({ - closeLabel: '', - activeLable: '', - optionsLabel: '', - fullscreenLabel: '', - exitFullscreenLabel: '', - hideToolsDesc: '', - showToolsDesc: '', - downloading: '', - downloaded: '', - downloadFailed: '', - snapshotLabel: '', - whiteboardLabel: '', - }); - - React.useEffect(() => { - setLabels({ - closeLabel: intl.formatMessage({id: 'app.dropdown.close', description: 'Close button label'}), - activeLable: intl.formatMessage({id: 'app.dropdown.list.item.activeLabel', description: 'active item label'}), - optionsLabel: intl.formatMessage({id: 'app.navBar.settingsDropdown.optionsLabel', description: 'Options button label'}), - fullscreenLabel: intl.formatMessage({id: 'app.presentation.options.fullscreen', description: 'Fullscreen label'}), - exitFullscreenLabel: intl.formatMessage({id: 'app.presentation.options.exitFullscreen', description: 'Exit fullscreen label'}), - hideToolsDesc: intl.formatMessage({id: 'app.presentation.presentationToolbar.hideToolsDesc', description: 'Hide toolbar label'}), - showToolsDesc: intl.formatMessage({id: 'app.presentation.presentationToolbar.showToolsDesc', description: 'Show toolbar label'}), - downloading: intl.formatMessage({id: 'app.presentation.options.downloading', description: 'Downloading label'}), - downloaded: intl.formatMessage({id: 'app.presentation.options.downloaded', description: 'Downloaded label'}), - downloadFailed: intl.formatMessage({id: 'app.presentation.options.downloadFailed', description: 'Downloaded failed label'}), - snapshotLabel: intl.formatMessage({id: 'app.presentation.options.snapshot', description: 'Snapshot of current slide label'}), - whiteboardLabel: intl.formatMessage({id: 'app.shortcut-help.whiteboard', description: 'used for aria whiteboard options button label'}), - }); - }, [intl?.locale]); // eslint-disable-next-line arrow-body-style React.useEffect(() => { @@ -660,15 +624,9 @@ export default function Whiteboard(props) { } if (menu) { - const MENU_OFFSET = '48px'; menu.style.position = 'relative'; menu.style.height = presentationMenuHeight; menu.setAttribute('id', 'TD-Styles-Parent'); - if (isRTL) { - menu.style.left = MENU_OFFSET; - } else { - menu.style.right = MENU_OFFSET; - } [...menu.children] .sort((a, b) => (a?.id > b?.id ? -1 : 1)) @@ -723,43 +681,6 @@ export default function Whiteboard(props) { const onPatch = (e, t, reason) => { if (!e?.pageState || !reason) return; - // Append Presentation Options to Tldraw - const tdStylesParent = document.getElementById('TD-Styles-Parent'); - if (tdStylesParent) { - tdStylesParent.style.right = '0px'; - tdStylesParent.style.width = '17.75rem'; - let presentationMenuNode = document.getElementById('PresentationMenuId'); - if (!presentationMenuNode) { - presentationMenuNode = document.createElement('div'); - presentationMenuNode.setAttribute('id', 'PresentationMenuId'); - tdStylesParent.appendChild(presentationMenuNode); - } - - ReactDOM.render( - , presentationMenuNode - ); - } if (((isPanning || panSelected) && (reason === 'selected' || reason === 'set_hovered_id'))) { e.patchState( @@ -1123,7 +1044,7 @@ export default function Whiteboard(props) { const menuOffset = menuOffsetValues[isRTL][isIphone]; return ( -
+
{ const isModerator = currentUser.role === ROLE_MODERATOR; const { maxStickyNoteLength, maxNumberOfAnnotations } = WHITEBOARD_CONFIG; const fontFamily = WHITEBOARD_CONFIG.styles.text.family; - const fullscreen = layoutSelect((i) => i.fullscreen); const handleToggleFullScreen = (ref) => FullscreenService.toggleFullScreen(ref); const layoutContextDispatch = layoutDispatch(); @@ -84,7 +83,6 @@ const WhiteboardContainer = (props) => { maxStickyNoteLength, maxNumberOfAnnotations, fontFamily, - fullscreen, hasShapeAccess, handleToggleFullScreen, sidebarNavigationWidth, diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/component.jsx deleted file mode 100644 index afbe440b10..0000000000 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/component.jsx +++ /dev/null @@ -1,317 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react'; -import PropTypes from 'prop-types'; -import { toPng } from 'html-to-image'; -import { toast } from 'react-toastify'; -import logger from '/imports/startup/client/logger'; -import Styled from '/imports/ui/components/presentation/presentation-menu/styles'; -import BBBMenu from './menu/component'; -import TooltipContainer from '/imports/ui/components/common/tooltip/container'; -import { ACTIONS } from '/imports/ui/components/layout/enums'; -import browserInfo from '/imports/utils/browserInfo'; -import AppService from '/imports/ui/components/app/service'; - -const propTypes = { - handleToggleFullscreen: PropTypes.func.isRequired, - isFullscreen: PropTypes.bool, - elementName: PropTypes.string, - fullscreenRef: PropTypes.instanceOf(Element), - meetingName: PropTypes.string, - isIphone: PropTypes.bool, - elementId: PropTypes.string, - elementGroup: PropTypes.string, - currentElement: PropTypes.string, - currentGroup: PropTypes.string, - layoutContextDispatch: PropTypes.func.isRequired, - isRTL: PropTypes.bool, - tldrawAPI: PropTypes.shape({ - copySvg: PropTypes.func.isRequired, - getShapes: PropTypes.func.isRequired, - currentPageId: PropTypes.string.isRequired, - }), -}; - -const defaultProps = { - isIphone: false, - isFullscreen: false, - isRTL: false, - elementName: '', - meetingName: '', - fullscreenRef: null, - elementId: '', - elementGroup: '', - currentElement: '', - currentGroup: '', - tldrawAPI: null, -}; - -const PresentationOps = (props) => { - const { - isFullscreen, - elementId, - elementName, - elementGroup, - currentElement, - currentGroup, - fullscreenRef, - tldrawAPI, - handleToggleFullscreen, - layoutContextDispatch, - meetingName, - isIphone, - isRTL, - isToolbarVisible, - setIsToolbarVisible, - exitFullscreenLabel, - fullscreenLabel, - hideToolsDesc, - showToolsDesc, - downloading, - downloaded, - downloadFailed, - snapshotLabel, - hasWBAccess, - amIPresenter, - optionsLabel, - whiteboardLabel, - } = props; - - const [state, setState] = useState({ - hasError: false, - loading: false, - }); - - const [isDropdownOpen, setIsDropdownOpen] = useState(false); - const toastId = useRef(null); - const dropdownRef = useRef(null); - - const formattedLabel = (fullscreen) => (fullscreen - ? exitFullscreenLabel - : fullscreenLabel - ); - - const formattedVisibilityLabel = (visible) => (visible - ? hideToolsDesc - : showToolsDesc - ); - - function renderToastContent() { - const { loading, hasError } = state; - - let icon = loading ? 'blank' : 'check'; - if (hasError) icon = 'circle_close'; - - return ( - - - - {loading && !hasError && downloading} - {!loading && !hasError && downloaded} - {!loading && hasError && downloadFailed} - - - - - - - ); - } - - function getAvailableOptions() { - const menuItems = []; - - if (!isIphone) { - menuItems.push( - { - key: 'list-item-fullscreen', - dataTest: 'presentationFullscreen', - label: formattedLabel(isFullscreen), - icon: isFullscreen ? 'exit_fullscreen' : 'fullscreen', - onClick: () => { - handleToggleFullscreen(fullscreenRef); - const newElement = (elementId === currentElement) ? '' : elementId; - const newGroup = (elementGroup === currentGroup) ? '' : elementGroup; - - layoutContextDispatch({ - type: ACTIONS.SET_FULLSCREEN_ELEMENT, - value: { - element: newElement, - group: newGroup, - }, - }); - }, - }, - ); - } - - const { isSafari } = browserInfo; - - if (!isSafari) { - menuItems.push( - { - key: 'list-item-screenshot', - label: snapshotLabel, - dataTest: 'presentationSnapshot', - icon: 'video', - onClick: async () => { - setState({ - loading: true, - hasError: false, - }); - - toastId.current = toast.info(renderToastContent(), { - hideProgressBar: true, - autoClose: false, - newestOnTop: true, - closeOnClick: true, - onClose: () => { - toastId.current = null; - }, - }); - - // This is a workaround to a conflict of the - // dark mode's styles and the html-to-image lib. - // Issue: - // https://github.com/bubkoo/html-to-image/issues/370 - const darkThemeState = AppService.isDarkThemeEnabled(); - AppService.setDarkTheme(false); - - try { - const { copySvg, getShapes, currentPageId } = tldrawAPI; - const svgString = await copySvg(getShapes(currentPageId).map((shape) => shape.id)); - const container = document.createElement('div'); - container.innerHTML = svgString; - const svgElem = container.firstChild; - const width = svgElem?.width?.baseVal?.value ?? window.screen.width; - const height = svgElem?.height?.baseVal?.value ?? window.screen.height; - - const data = await toPng(svgElem, { width, height, backgroundColor: '#FFF' }); - - const anchor = document.createElement('a'); - anchor.href = data; - anchor.setAttribute( - 'download', - `${elementName}_${meetingName}_${new Date().toISOString()}.png`, - ); - anchor.click(); - - setState({ - loading: false, - hasError: false, - }); - } catch (e) { - setState({ - loading: false, - hasError: true, - }); - - logger.warn({ - logCode: 'presentation_snapshot_error', - extraInfo: e, - }); - } finally { - // Workaround - AppService.setDarkTheme(darkThemeState); - } - }, - }, - ); - } - - const tools = document.querySelector('#TD-Tools'); - if (tools && (hasWBAccess || amIPresenter)){ - menuItems.push( - { - key: 'list-item-toolvisibility', - dataTest: 'toolVisibility', - label: formattedVisibilityLabel(isToolbarVisible), - icon: isToolbarVisible ? 'close' : 'pen_tool', - onClick: () => { - setIsToolbarVisible(!isToolbarVisible); - }, - }, - ); - } - - return menuItems; - } - - useEffect(() => { - if (toastId.current) { - toast.update(toastId.current, { - render: renderToastContent(), - hideProgressBar: state.loading, - autoClose: state.loading ? false : 3000, - newestOnTop: true, - closeOnClick: true, - onClose: () => { - toastId.current = null; - }, - }); - } - - if (dropdownRef.current) { - document.activeElement.blur(); - dropdownRef.current.focus(); - } - }); - - const options = getAvailableOptions(); - - if (options.length === 0) { - const undoCtrls = document.getElementById('TD-Styles')?.nextSibling; - if (undoCtrls?.style) { - undoCtrls.style = 'padding:0px'; - } - const styleTool = document.getElementById('TD-Styles')?.parentNode; - if (styleTool?.style) { - styleTool.style = 'right:0px'; - } - return null; - } - - return ( - - - { - setIsDropdownOpen((isOpen) => !isOpen); - }} - > - - - - )} - opts={{ - id: 'presentation-dropdown-menu', - keepMounted: true, - transitionDuration: 0, - elevation: 3, - getContentAnchorEl: null, - fullwidth: 'true', - anchorOrigin: { vertical: 'bottom', horizontal: isRTL ? 'right' : 'left' }, - transformOrigin: { vertical: 'top', horizontal: isRTL ? 'right' : 'left' }, - container: fullscreenRef, - }} - actions={options} - /> - - ); -}; - -PresentationOps.propTypes = propTypes; -PresentationOps.defaultProps = defaultProps; - -export default PresentationOps; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/container.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/container.jsx deleted file mode 100644 index a54adfe070..0000000000 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/container.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { withTracker } from 'meteor/react-meteor-data'; -import PresentationOps from './component'; -import FullscreenService from '/imports/ui/components/common/fullscreen-button/service'; -import Auth from '/imports/ui/services/auth'; -import Meetings from '/imports/api/meetings'; -import WhiteboardService from '/imports/ui/components/whiteboard/service'; -import UserService from '/imports/ui/components/user-list/service'; - -const PresentationOpsContainer = (props) => { - const { elementId, isRTL, layoutContextDispatch, fullscreen } = props; - const { element: currentElement, group: currentGroup } = fullscreen; - const isFullscreen = currentElement === elementId; - - return ( - - ); -}; - -export default withTracker((props) => { - const handleToggleFullscreen = (ref) => FullscreenService.toggleFullScreen(ref); - const isIphone = !!(navigator.userAgent.match(/iPhone/i)); - const meetingId = Auth.meetingID; - const meetingObject = Meetings.findOne({ meetingId }, { fields: { 'meetingProp.name': 1 } }); - const hasWBAccess = WhiteboardService.hasMultiUserAccess( - WhiteboardService.getCurrentWhiteboardId(), - Auth.userID - ); - const amIPresenter = UserService.isUserPresenter(Auth.userID); - return { - ...props, - handleToggleFullscreen, - isIphone, - isDropdownOpen: Session.get('dropdownOpen'), - meetingName: meetingObject.meetingProp.name, - hasWBAccess, - amIPresenter, - }; -})(PresentationOpsContainer); - -PresentationOpsContainer.propTypes = { - elementId: PropTypes.string.isRequired, -}; diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/menu/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/menu/component.jsx deleted file mode 100644 index 5ddadf95f6..0000000000 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/presentation-ops-injector/menu/component.jsx +++ /dev/null @@ -1,253 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; - -import Menu from "@mui/material/Menu"; -import { Divider } from "@mui/material"; -import Icon from "/imports/ui/components/common/icon/component"; -import { SMALL_VIEWPORT_BREAKPOINT } from '/imports/ui/components/layout/enums'; -import KEY_CODES from '/imports/utils/keyCodes'; - -import { ENTER } from "/imports/utils/keyCodes"; - -import Styled from '/imports/ui/components/common/menu/styles'; - -class BBBMenu extends React.Component { - constructor(props) { - super(props); - this.state = { - anchorEl: null, - }; - - this.optsToMerge = {}; - this.autoFocus = false; - - this.handleKeyDown = this.handleKeyDown.bind(this); - this.handleClick = this.handleClick.bind(this); - this.handleClose = this.handleClose.bind(this); - } - - componentDidUpdate() { - const { anchorEl } = this.state; - const { open } = this.props; - if (open === false && anchorEl) { - this.setState({ anchorEl: null }); - } else if (open === true && !anchorEl) { - this.setState({ anchorEl: this.anchorElRef }); - } - } - - handleKeyDown(event) { - const { anchorEl } = this.state; - const isMenuOpen = Boolean(anchorEl); - - - if ([KEY_CODES.ESCAPE, KEY_CODES.TAB].includes(event.which)) { - this.handleClose(); - return; - } - - if (isMenuOpen && [KEY_CODES.ARROW_UP, KEY_CODES.ARROW_DOWN].includes(event.which)) { - event.preventDefault(); - event.stopPropagation(); - const menuItems = Array.from(document.querySelectorAll('[data-key^="menuItem-"]')); - if (menuItems.length === 0) return; - - const focusedIndex = menuItems.findIndex(item => item === document.activeElement); - const nextIndex = event.which === KEY_CODES.ARROW_UP ? focusedIndex - 1 : focusedIndex + 1; - let indexToFocus = 0; - if (nextIndex < 0) { - indexToFocus = menuItems.length - 1; - } else if (nextIndex >= menuItems.length) { - indexToFocus = 0; - } else { - indexToFocus = nextIndex; - } - - menuItems[indexToFocus].focus(); - } - }; - - handleClick(event) { - this.setState({ anchorEl: event.currentTarget }); - }; - - handleClose(event) { - const { onCloseCallback } = this.props; - this.setState({ anchorEl: null }, onCloseCallback()); - - if (event) { - event.persist(); - - if (event.type === 'click') { - setTimeout(() => { - document.activeElement.blur(); - }, 0); - } - } - }; - - makeMenuItems() { - const { actions, selectedEmoji, activeLabel } = this.props; - - return actions?.map(a => { - const { dataTest, label, onClick, key, disabled, description, selected } = a; - const emojiSelected = key?.toLowerCase()?.includes(selectedEmoji?.toLowerCase()); - - let customStyles = { - paddingLeft: '16px', - paddingRight: '16px', - paddingTop: '12px', - paddingBottom: '12px', - marginLeft: '0px', - marginRight: '0px', - }; - - if (a.customStyles) { - customStyles = { ...customStyles, ...a.customStyles }; - } - - return [ - a.dividerTop && , - { - onClick(); - const close = !key?.includes('setstatus') && !key?.includes('back'); - // prevent menu close for sub menu actions - if (close) this.handleClose(event); - event.stopPropagation(); - }}> - - {a.icon ? : null} - {label} - {description &&
{`${description}${selected ? ` - ${activeLabel}` : ''}`}
} - {a.iconRight ? : null} -
-
, - a.divider && - ]; - }); - } - - render() { - const { anchorEl } = this.state; - const { trigger, customStyles, dataTest, opts, accessKey, closeLabel } = this.props; - const actionsItems = this.makeMenuItems(); - - let menuStyles = { zIndex: 9999 }; - - if (customStyles) { - menuStyles = { ...menuStyles, ...customStyles }; - } - - return ( - <> -
{ - e.persist(); - // 1 = mouse, 5 = touch (firefox only) - const firefoxInputSource = !([1, 5].includes(e.nativeEvent.mozInputSource)); - const chromeInputSource = !(['mouse', 'touch'].includes(e.nativeEvent.pointerType)); - this.optsToMerge.autoFocus = firefoxInputSource && chromeInputSource; - this.handleClick(e); - }} - onKeyPress={(e) => { - e.persist(); - if (e.which !== ENTER) return null; - return this.handleClick(e); - }} - accessKey={accessKey} - ref={(ref) => { this.anchorElRef = ref; return null; }} - role="button" - tabIndex={-1} - > - {trigger} -
- - - {actionsItems} - {anchorEl && window.innerWidth < SMALL_VIEWPORT_BREAKPOINT && - - } - - - ); - } -} - -export default BBBMenu; - -BBBMenu.defaultProps = { - opts: { - id: "default-dropdown-menu", - autoFocus: false, - keepMounted: true, - transitionDuration: 0, - elevation: 3, - getContentAnchorEl: null, - fullwidth: "true", - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - transformorigin: { vertical: 'top', horizontal: 'right' }, - }, - dataTest: "", - onCloseCallback: () => { }, -}; - -BBBMenu.propTypes = { - trigger: PropTypes.element.isRequired, - - actions: PropTypes.arrayOf(PropTypes.shape({ - key: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - onClick: PropTypes.func, - icon: PropTypes.string, - iconRight: PropTypes.string, - disabled: PropTypes.bool, - divider: PropTypes.bool, - dividerTop: PropTypes.bool, - accessKey: PropTypes.string, - dataTest: PropTypes.string, - })).isRequired, - - opts: PropTypes.shape({ - id: PropTypes.string, - autoFocus: PropTypes.bool, - keepMounted: PropTypes.bool, - transitionDuration: PropTypes.number, - elevation: PropTypes.number, - getContentAnchorEl: PropTypes.element, - fullwidth: PropTypes.string, - anchorOrigin: PropTypes.shape({ - vertical: PropTypes.string, - horizontal: PropTypes.string - }), - transformorigin: PropTypes.shape({ - vertical: PropTypes.string, - horizontal: PropTypes.string - }), - }), - - onCloseCallback: PropTypes.func, - dataTest: PropTypes.string, -}; \ No newline at end of file From c52a35834ebd80a7c96238e5d284c3661a3a792c Mon Sep 17 00:00:00 2001 From: Arthurk12 Date: Fri, 11 Aug 2023 16:57:11 -0300 Subject: [PATCH 195/252] feat(wake-lock): enable implicit activation based on the default setting This commit removes the wake lock activation toast along with the enable/disable buttons, implementing the wake lock implicit activation behavior. The wake lock feature is implicitly activated if the `defaultSettings.application.wakeLock` in the `settings.yml` file is set true. For mobile devices that do not support the API or fail when requesting a wake lock, toasts are raised explaining that calls will be dropped when the screen turns off. In cases where `defaultSettings.application.wakeLock` is set false, users can enable the wake lock manually through the settings menu. --- .../ui/components/wake-lock/component.jsx | 117 +++++++----------- .../ui/components/wake-lock/container.jsx | 5 +- .../ui/components/wake-lock/service.js | 35 +++++- .../imports/ui/components/wake-lock/styles.js | 37 ------ bigbluebutton-html5/public/locales/en.json | 7 +- 5 files changed, 79 insertions(+), 122 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/wake-lock/component.jsx b/bigbluebutton-html5/imports/ui/components/wake-lock/component.jsx index 8e0ae75653..310b68e9d0 100644 --- a/bigbluebutton-html5/imports/ui/components/wake-lock/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/wake-lock/component.jsx @@ -1,7 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; -import { toast } from 'react-toastify'; import { notify } from '/imports/ui/services/notification'; import Settings from '/imports/ui/services/settings'; import Styled from './styles'; @@ -10,18 +9,18 @@ const intlMessages = defineMessages({ wakeLockOfferTitle: { id: 'app.toast.wakeLock.offerTitle', }, - wakeLockOfferAcceptButton: { - id: 'app.toast.wakeLock.offerAccept', - }, - wakeLockOfferDeclineButton: { - id: 'app.toast.wakeLock.offerDecline', - }, wakeLockAcquireSuccess: { id: 'app.toast.wakeLock.acquireSuccess', }, wakeLockAcquireFailed: { id: 'app.toast.wakeLock.acquireFailed', }, + wakeLockNotSupported: { + id: 'app.toast.wakeLock.notSupported', + }, + wakeLockDisclaimer: { + id: 'app.toast.wakeLock.disclaimer', + } }); const propTypes = { @@ -34,96 +33,68 @@ const propTypes = { class WakeLock extends Component { constructor() { super(); - this.notification = null; } componentDidMount() { - const { intl, request } = this.props; + const { wakeLockSettings } = this.props; - const toastProps = { - closeOnClick: false, - autoClose: false, - closeButton: false, - }; - - const closeNotification = () => { - toast.dismiss(this.notification); - this.notification = null; - }; - - const declineButton = () => ( - - { intl.formatMessage(intlMessages.wakeLockOfferDeclineButton) } - - ); - - const acceptButton = () => ( - { - closeNotification(); - const error = await request(); - if (!error) { - Settings.application.wakeLock = true; - Settings.save(); - } - this.feedbackToast(error); - }} - > - { intl.formatMessage(intlMessages.wakeLockOfferAcceptButton) } - - ); - - const toastContent = this.getToast('wakeLockOffer', 'wakeLockOfferTitle', acceptButton(), declineButton()); - this.notification = notify(toastContent, 'default', 'lock', toastProps, null, true); + if (wakeLockSettings) { + this.requestWakeLock(); + } } - getToast(id, title, acceptButton, declineButton) { - const { intl } = this.props; + componentDidUpdate(prevProps) { + const { wakeLockSettings, release } = this.props; + if (wakeLockSettings !== prevProps.wakeLockSettings) { + if (wakeLockSettings) { + this.requestWakeLock(); + } else { + release(); + } + } + } + getToast(id, message) { return (
- { intl.formatMessage(intlMessages[title]) } + { message } - - { acceptButton } - { declineButton } -
); } - feedbackToast(error) { + feedbackToast(result) { + const { intl } = this.props; + const feedbackToastProps = { closeOnClick: true, autoClose: true, closeButton: false, }; - const feedbackToast = this.getToast(error ? 'wakeLockFailed' : 'wakeLockSuccess', - error ? 'wakeLockAcquireFailed' : 'wakeLockAcquireSuccess', null, null); - setTimeout(() => { - notify(feedbackToast, error ? 'error' : 'success', 'lock', feedbackToastProps, null, true); - }, 800); + const toastType = result.error ? 'error' : 'success'; + const message = result.error + ? intl.formatMessage(intlMessages.wakeLockDisclaimer,{ + 0: intl.formatMessage(intlMessages[result.locale]) + } ) + : intl.formatMessage(intlMessages.wakeLockAcquireSuccess); + const feedbackToast = this.getToast('wakeLockToast', message); + notify(feedbackToast, toastType, 'lock', feedbackToastProps, null, true); + } + + requestWakeLock () { + const { request } = this.props; + request().then((result) => { + if (result && result.error) { + Settings.application.wakeLock = false; + Settings.save(); + } + this.feedbackToast(result); + }); } render() { - const { wakeLockSettings, request, release } = this.props; - if (wakeLockSettings) { - request().then((error) => { - if (error) { - this.feedbackToast(error); - Settings.application.wakeLock = false; - Settings.save(); - } - }); - } else { - release(); - } return null; } } diff --git a/bigbluebutton-html5/imports/ui/components/wake-lock/container.jsx b/bigbluebutton-html5/imports/ui/components/wake-lock/container.jsx index 223c70f2f3..d31866fc05 100644 --- a/bigbluebutton-html5/imports/ui/components/wake-lock/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/wake-lock/container.jsx @@ -26,7 +26,7 @@ function usePrevious(value) { } const WakeLockContainer = (props) => { - if (!Service.isSupported()) return null; + if (!Service.isMobile()) return null; const { areAudioModalsOpen, autoJoin } = props; const wereAudioModalsOpen = usePrevious(areAudioModalsOpen); @@ -45,11 +45,10 @@ WakeLockContainer.propTypes = propTypes; WakeLockContainer.defaultProps = defaultProps; export default withTracker(() => { - const wakeLockSettings = Settings.application.wakeLock; return { request: Service.request, release: Service.release, - wakeLockSettings, + wakeLockSettings: Settings.application.wakeLock, areAudioModalsOpen: Session.get('audioModalIsOpen') || Session.get('inEchoTest'), autoJoin: getFromUserSettings('bbb_auto_join_audio', APP_CONFIG.autoJoin), }; diff --git a/bigbluebutton-html5/imports/ui/components/wake-lock/service.js b/bigbluebutton-html5/imports/ui/components/wake-lock/service.js index 2c0a9f55f0..55f48c2019 100644 --- a/bigbluebutton-html5/imports/ui/components/wake-lock/service.js +++ b/bigbluebutton-html5/imports/ui/components/wake-lock/service.js @@ -3,6 +3,17 @@ import deviceInfo from '/imports/utils/deviceInfo'; const WAKELOCK_ENABLED = Meteor.settings.public.app.wakeLock.enabled; +const WAKELOCK_ERRORS = { + NOT_SUPPORTED: { + locale: 'wakeLockNotSupported', + error: 'wake_lock_not_supported', + }, + REQUEST_FAILED: { + locale: 'wakeLockAcquireFailed', + error: 'wake_lock_request_error', + } +} + class WakeLock { constructor() { this.sentinel = null; @@ -18,6 +29,11 @@ class WakeLock { return WakeLock.isEnabled() && this.apiSupport && isMobile; } + isMobile() { + const { isMobile } = deviceInfo; + return isMobile; + } + isActive() { return this.sentinel !== null; } @@ -36,9 +52,12 @@ class WakeLock { async request() { if (!this.isSupported()) { logger.warn({ - logCode: 'wake_lock_request_error', + logCode: WAKELOCK_ERRORS.NOT_SUPPORTED, }, 'Wake lock API not supported'); - return true; + return { + ...WAKELOCK_ERRORS.NOT_SUPPORTED, + msg: 'Wake lock API not supported', + }; } try { @@ -48,15 +67,20 @@ class WakeLock { document.addEventListener('fullscreenchange', this.handleVisibilityChanged.bind(this)); } catch (err) { logger.warn({ - logCode: 'wake_lock_request_error', + logCode: WAKELOCK_ERRORS.REQUEST_FAILED, extraInfo: { errorName: err.name, errorMessage: err.message, }, }, 'Error requesting wake lock.'); - return true; + return { + ...WAKELOCK_ERRORS.REQUEST_FAILED, + msg: `${err.name} - ${err.message}`, + }; } - return false; + return { + error: false, + }; } release() { @@ -69,6 +93,7 @@ const wakeLock = new WakeLock(); export default { isEnabled: () => wakeLock.isEnabled(), isSupported: () => wakeLock.isSupported(), + isMobile: () => wakeLock.isMobile(), isActive: () => wakeLock.isActive(), request: () => wakeLock.request(), release: () => wakeLock.release(), diff --git a/bigbluebutton-html5/imports/ui/components/wake-lock/styles.js b/bigbluebutton-html5/imports/ui/components/wake-lock/styles.js index 9f655ec272..6552841774 100644 --- a/bigbluebutton-html5/imports/ui/components/wake-lock/styles.js +++ b/bigbluebutton-html5/imports/ui/components/wake-lock/styles.js @@ -1,46 +1,9 @@ import styled from 'styled-components'; -import { - colorLink, - colorWhite, -} from '/imports/ui/stylesheets/styled-components/palette'; - -const ToastButtons = styled.div` - display: inline; -`; const Title = styled.h3` margin: 0; `; -const AcceptButton = styled.button` - align-self: center; - &:focus { - outline: none !important; - } - color: ${colorWhite} !important; - background-color: ${colorLink} !important; - margin: 0; - border: none; - padding: 0.3em 1em 0.3em 1em; - margin-top: 1em; - float: left; -`; - -const CloseButton = styled.button` - align-self: center; - &:focus { - outline: none !important; - } - margin: 0; - border: none; - padding: 0.3em 1em 0.3em 1em; - margin-top: 1em; - float: right; -`; - export default { - ToastButtons, Title, - AcceptButton, - CloseButton, }; \ No newline at end of file diff --git a/bigbluebutton-html5/public/locales/en.json b/bigbluebutton-html5/public/locales/en.json index 7782b67c39..171c422459 100755 --- a/bigbluebutton-html5/public/locales/en.json +++ b/bigbluebutton-html5/public/locales/en.json @@ -869,11 +869,10 @@ "app.toast.meetingMuteOn.label": "All users have been muted", "app.toast.meetingMuteOnViewers.label": "All viewers have been muted", "app.toast.meetingMuteOff.label": "Meeting mute turned off", - "app.toast.wakeLock.offerTitle": "Would you like to keep your device's screen active during the meeting?", - "app.toast.wakeLock.offerAccept": "Yes!", - "app.toast.wakeLock.offerDecline": "Not now", "app.toast.wakeLock.acquireSuccess": "Wake lock active! You can turn it off under the settings menu.", - "app.toast.wakeLock.acquireFailed": "Error acquiring wake lock.", + "app.toast.wakeLock.acquireFailed": "Error acquiring wake lock", + "app.toast.wakeLock.notSupported": "Wake lock not supported", + "app.toast.wakeLock.disclaimer": "{0}. You will be dropped from the call if the screen turns off.", "app.toast.setEmoji.raiseHand": "You have raised your hand", "app.toast.setEmoji.lowerHand": "Your hand has been lowered", "app.toast.setEmoji.away": "You have set your status to away", From 23cfc80a326cd87f55f3d68ee96952a1fada645c Mon Sep 17 00:00:00 2001 From: prlanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Mon, 14 Aug 2023 08:53:44 -0300 Subject: [PATCH 196/252] build(freeswitch): v1.10.10 See: https://github.com/signalwire/freeswitch/releases/tag/v1.10.10 --- freeswitch.placeholder.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/freeswitch.placeholder.sh b/freeswitch.placeholder.sh index 8c172b0532..10a1af09c5 100755 --- a/freeswitch.placeholder.sh +++ b/freeswitch.placeholder.sh @@ -2,7 +2,5 @@ mkdir freeswitch cd freeswitch git init git remote add origin https://github.com/signalwire/freeswitch.git -#git fetch --depth 1 origin v1.10.9 -#git checkout FETCH_HEAD -git fetch origin master -git checkout 41507363f3fffcdad547b168e55fbe3383a24c3d # branch 'master' on Aug 10, 2023 +git fetch --depth 1 origin v1.10.10 +git checkout FETCH_HEAD From 0b8c493381b23ece720f2bc8741fc74ee595f90c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Mon, 14 Aug 2023 09:30:53 -0300 Subject: [PATCH 197/252] return gridUsers when pagination is disabled --- .../imports/ui/components/video-provider/service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/service.js b/bigbluebutton-html5/imports/ui/components/video-provider/service.js index 3039b8ef98..3bd05e0330 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/service.js +++ b/bigbluebutton-html5/imports/ui/components/video-provider/service.js @@ -450,6 +450,7 @@ class VideoService { ? getSortingMethod(DEFAULT_SORTING) : getSortingMethod(PAGINATION_SORTING); const isGridEnabled = this.isGridEnabled(); + let gridUsers = []; let streams = VideoStreams.find( { meetingId: Auth.meetingID }, @@ -471,14 +472,13 @@ class VideoService { if (isPaginationDisabled) { return { streams: sortVideoStreams(streams, DEFAULT_SORTING), + gridUsers, totalNumberOfStreams: streams.length }; } const paginatedStreams = this.getVideoPage(streams, pageSize); - let gridUsers = []; - if (isGridEnabled) { const users = Users.find( { meetingId: Auth.meetingID }, From c5872e9cd3cc0a6d99a7112175f99997f0268586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Mon, 14 Aug 2023 09:46:05 -0300 Subject: [PATCH 198/252] rename grid page sizes -> grid sizes --- .../imports/ui/components/video-provider/service.js | 10 +++++----- bigbluebutton-html5/private/config/settings.yml | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/service.js b/bigbluebutton-html5/imports/ui/components/video-provider/service.js index 3bd05e0330..c4ab7e6e33 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/service.js +++ b/bigbluebutton-html5/imports/ui/components/video-provider/service.js @@ -42,8 +42,8 @@ const { pageChangeDebounceTime: PAGE_CHANGE_DEBOUNCE_TIME, desktopPageSizes: DESKTOP_PAGE_SIZES, mobilePageSizes: MOBILE_PAGE_SIZES, - desktopGridPageSizes: DESKTOP_GRID_PAGE_SIZES, - mobileGridPageSizes: MOBILE_GRID_PAGE_SIZES, + desktopGridSizes: DESKTOP_GRID_SIZES, + mobileGridSizes: MOBILE_GRID_SIZES, } = Meteor.settings.public.kurento.pagination; const PAGINATION_THRESHOLDS_CONF = Meteor.settings.public.kurento.paginationThresholds; const PAGINATION_THRESHOLDS = PAGINATION_THRESHOLDS_CONF.thresholds.sort((t1, t2) => t1.users - t2.users); @@ -381,15 +381,15 @@ class VideoService { getGridSize () { let size; const myRole = this.getMyRole(); - const pageSizes = !this.isMobile ? DESKTOP_GRID_PAGE_SIZES : MOBILE_GRID_PAGE_SIZES; + const gridSizes = !this.isMobile ? DESKTOP_GRID_SIZES : MOBILE_GRID_SIZES; switch (myRole) { case ROLE_MODERATOR: - size = pageSizes.moderator; + size = gridSizes.moderator; break; case ROLE_VIEWER: default: - size = pageSizes.viewer + size = gridSizes.viewer } return size; diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 8d462aa998..a38624536a 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -477,11 +477,11 @@ public: moderator: 2 viewer: 2 # grid size for DESKTOP endpoints - desktopGridPageSizes: + desktopGridSizes: moderator: 48 viewer: 48 # grid size for MOBILE endpoints - mobileGridPageSizes: + mobileGridSizes: moderator: 14 viewer: 14 paginationThresholds: From 5bf6d0489a483d5c088352ab74bb290efefcd970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Mon, 14 Aug 2023 09:52:52 -0300 Subject: [PATCH 199/252] fix empty grid users object when pagination is disabled --- .../ui/components/video-provider/service.js | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/service.js b/bigbluebutton-html5/imports/ui/components/video-provider/service.js index c4ab7e6e33..2a8bd453cd 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/service.js +++ b/bigbluebutton-html5/imports/ui/components/video-provider/service.js @@ -451,6 +451,14 @@ class VideoService { : getSortingMethod(PAGINATION_SORTING); const isGridEnabled = this.isGridEnabled(); let gridUsers = []; + let users = []; + + if (isGridEnabled) { + users = Users.find( + { meetingId: Auth.meetingID }, + { fields: { loggedOut: 1, left: 1, ...neededDataTypes} }, + ).fetch(); + } let streams = VideoStreams.find( { meetingId: Auth.meetingID }, @@ -470,6 +478,17 @@ class VideoService { // is equivalent to disabling it), so return the mapped streams as they are // which produces the original non paginated behaviour if (isPaginationDisabled) { + if (isGridEnabled) { + const streamUsers = streams.map((stream) => stream.userId); + + gridUsers = users.filter( + (user) => !user.loggedOut && !user.left && !streamUsers.includes(user.userId) + ).map((user) => ({ + isGridItem: true, + ...user, + })); + } + return { streams: sortVideoStreams(streams, DEFAULT_SORTING), gridUsers, @@ -480,11 +499,6 @@ class VideoService { const paginatedStreams = this.getVideoPage(streams, pageSize); if (isGridEnabled) { - const users = Users.find( - { meetingId: Auth.meetingID }, - { fields: { loggedOut: 1, left: 1, ...neededDataTypes} }, - ).fetch(); - const streamUsers = paginatedStreams.map((stream) => stream.userId); gridUsers = users.filter( @@ -493,7 +507,6 @@ class VideoService { isGridItem: true, ...user, })); - } return { streams: paginatedStreams, gridUsers, totalNumberOfStreams: streams.length }; From 2e2b2d5b31ba72e0ff84c371f0cca8bda88d7959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Mon, 14 Aug 2023 10:47:23 -0300 Subject: [PATCH 200/252] fix prop warnings --- .../imports/ui/components/common/menu/component.jsx | 4 ++-- .../imports/ui/components/common/menu/styles.js | 4 ++-- .../presentation/presentation-toolbar/component.jsx | 4 +--- .../ui/components/presentation/presentation-toolbar/styles.js | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/common/menu/component.jsx b/bigbluebutton-html5/imports/ui/components/common/menu/component.jsx index a1e5d6e3e3..a4fb0f7e11 100644 --- a/bigbluebutton-html5/imports/ui/components/common/menu/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/common/menu/component.jsx @@ -128,7 +128,7 @@ class BBBMenu extends React.Component { disableGutters={true} disabled={disabled} style={customStyles} - roundButtons={roundButtons} + $roundButtons={roundButtons} onClick={(event) => { onClick(); const close = !keepOpen && !key?.includes('setstatus') && !key?.includes('back'); @@ -211,7 +211,7 @@ class BBBMenu extends React.Component { style={menuStyles} data-test={dataTest} onKeyDownCapture={this.handleKeyDown} - isHorizontal={isHorizontal} + $isHorizontal={isHorizontal} PaperProps={{ style: hasRoundedCorners ? roundedCornersStyles : {}, className: overrideMobileStyles ? 'override-mobile-styles' : 'MuiPaper-root-mobile', diff --git a/bigbluebutton-html5/imports/ui/components/common/menu/styles.js b/bigbluebutton-html5/imports/ui/components/common/menu/styles.js index 2db238dc6d..6f586b3353 100644 --- a/bigbluebutton-html5/imports/ui/components/common/menu/styles.js +++ b/bigbluebutton-html5/imports/ui/components/common/menu/styles.js @@ -14,7 +14,7 @@ const MenuWrapper = styled(Menu)` padding: .5rem 0; `} - ${({ isHorizontal, isMobile }) => (isHorizontal || isMobile) && ` + ${({ $isHorizontal, isMobile }) => ($isHorizontal || isMobile) && ` ul { display: flex; } @@ -107,7 +107,7 @@ const BBBMenuItem = styled(MenuItem)` } } `} - ${({ roundButtons }) => roundButtons && ` + ${({ $roundButtons }) => $roundButtons && ` &:focus, &:hover { background-color: ${colorWhite} !important; diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx index 27e7a4924c..190080fd26 100755 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx @@ -423,9 +423,7 @@ class PresentationToolbar extends PureComponent { ? intl.formatMessage(intlMessages.fitToPage) : intl.formatMessage(intlMessages.fitToWidth)} hideLabel - {...{ - fitToWidth, - }} + $fitToWidth={fitToWidth} /> diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/styles.js b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/styles.js index d6eb3bea82..21b52c0767 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/styles.js +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/styles.js @@ -199,7 +199,7 @@ const FitToWidthButton = styled(Button)` box-shadow: none !important; border: 0; - ${({ fitToWidth }) => fitToWidth && ` + ${({ $fitToWidth }) => $fitToWidth && ` & > span { border: solid ${borderSizeLarge} ${colorGrayDark}; } From 02419426d887c6e191acc6d683a274043dc29961 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 15:12:27 +0000 Subject: [PATCH 201/252] Translate en.json in de 100% translated source file: 'en.json' on 'de'. --- bigbluebutton-html5/public/locales/de.json | 75 ++++++++++++++++++++-- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/bigbluebutton-html5/public/locales/de.json b/bigbluebutton-html5/public/locales/de.json index 834504da43..f9ef01849b 100644 --- a/bigbluebutton-html5/public/locales/de.json +++ b/bigbluebutton-html5/public/locales/de.json @@ -7,6 +7,7 @@ "app.chat.locked": "Der Chat ist gesperrt. Es können keine Nachrichten gesendet werden.", "app.chat.inputLabel": "Chatnachricht eingeben für {0}", "app.chat.emojiButtonLabel": "Emoji-Auswahl", + "app.chat.loadMoreButtonLabel": "Weitere laden", "app.chat.inputPlaceholder": "Nachricht {0}", "app.chat.titlePublic": "Öffentlicher Chat", "app.chat.titlePrivate": "Privater Chat mit {0}", @@ -24,8 +25,11 @@ "app.chat.breakoutDurationUpdated": "Gruppenzeit beträgt jetzt {0} Minuten", "app.chat.breakoutDurationUpdatedModerator": "Gruppenraumzeit beträgt nun {0} Minuten und eine Benachrichtigung wurde versendet.", "app.chat.emptyLogLabel": "Chatprotokoll ist leer", + "app.chat.away": "Abwesend", + "app.chat.notAway": "Wieder anwesend", "app.chat.clearPublicChatMessage": "Der öffentliche Chatverlauf wurde durch einen Moderator gelöscht", "app.chat.multi.typing": "Mehrere Teilnehmer tippen", + "app.chat.someone.typing": "Jemand schreibt gerade", "app.chat.one.typing": "{0} tippt", "app.chat.two.typing": "{0} und {1} tippen", "app.chat.copySuccess": "Chatprotokoll kopiert", @@ -36,6 +40,7 @@ "app.emojiPicker.clear": "Leeren", "app.emojiPicker.categories.label": "Emoji-Kategorien", "app.emojiPicker.categories.people": "Menschen & Körper", + "app.emojiPicker.categories.reactions": "Reaktionen", "app.emojiPicker.categories.nature": "Tiere & Natur", "app.emojiPicker.categories.foods": "Essen & Trinken", "app.emojiPicker.categories.places": "Reisen & Orte", @@ -51,6 +56,23 @@ "app.emojiPicker.skintones.4": "Mittlerer Hautton", "app.emojiPicker.skintones.5": "Mitteldunkler Hautton", "app.emojiPicker.skintones.6": "Dunkler Hautton", + "app.timer.title": "Zeit", + "app.timer.stopwatch.title": "Stoppuhr", + "app.timer.timer.title": "Kurzzeitwecker", + "app.timer.hideTimerLabel": "Zeit verbergen", + "app.timer.button.stopwatch": "Stoppuhr", + "app.timer.button.timer": "Kurzzeitwecker", + "app.timer.button.start": "Start", + "app.timer.button.stop": "Stopp", + "app.timer.button.reset": "Zurücksetzen", + "app.timer.hours": "Stunden", + "app.timer.minutes": "Minuten", + "app.timer.seconds": "Sekunden", + "app.timer.songs": "Lieder", + "app.timer.noTrack": "Kein Lied", + "app.timer.track1": "am entspannen", + "app.timer.track2": "ruhig", + "app.timer.track3": "fröhlich", "app.captions.label": "Untertitel", "app.captions.menu.close": "Schließen", "app.captions.menu.start": "Start", @@ -102,6 +124,7 @@ "app.userList.messagesTitle": "Nachrichten", "app.userList.notesTitle": "Notizen", "app.userList.notesListItem.unreadContent": "Neue Inhalte sind im Bereich der geteilten Notizen verfügbar", + "app.userList.timerTitle": "Zeit", "app.userList.captionsTitle": "Untertitel", "app.userList.presenter": "Präsentator", "app.userList.you": "Ich", @@ -116,6 +139,8 @@ "app.userList.menuTitleContext": "Verfügbare Optionen", "app.userList.chatListItem.unreadSingular": "Eine neue Nachricht", "app.userList.chatListItem.unreadPlural": "{0} neue Nachrichten", + "app.userList.menu.away": "Sich selber als abwesend anzeigen", + "app.userList.menu.notAway": "Sich selber als aktiv anzeigen", "app.userList.menu.chat.label": "Privaten Chat starten", "app.userList.menu.clearStatus.label": "Status zurücksetzen", "app.userList.menu.removeUser.label": "Teilnehmer entfernen", @@ -140,6 +165,8 @@ "app.userList.userOptions.muteAllDesc": "Alle Teilnehmer der Konferenz werden stumm geschaltet", "app.userList.userOptions.clearAllLabel": "Alle Statusicons löschen", "app.userList.userOptions.clearAllDesc": "Alle Statusicons der Teilnehmer löschen", + "app.userList.userOptions.clearAllReactionsLabel": "Alle Reaktionen löschen", + "app.userList.userOptions.clearAllReactionsDesc": "Löscht alle Reaktions-Emojis der Nutzenden", "app.userList.userOptions.muteAllExceptPresenterLabel": "Alle Teilnehmer außer den Präsentator stummschalten", "app.userList.userOptions.muteAllExceptPresenterDesc": "Alle Teilnehmer der Konferenz außer dem Präsentator werden stumm geschaltet", "app.userList.userOptions.unmuteAllLabel": "Konferenz-Stummschaltung aufheben", @@ -156,6 +183,7 @@ "app.userList.userOptions.hideUserList": "Teilnehmerliste ist jetzt für die Teilnehmer ausgeblendet", "app.userList.userOptions.webcamsOnlyForModerator": "Nur Moderatoren können die Teilnehmerwebcams sehen (wegen eingeschränkter Rechteeinstellungen)", "app.userList.content.participants.options.clearedStatus": "Status aller Teilnehmer zurückgesetzt", + "app.userList.content.participants.options.clearedReactions": "Reaktionen aller Nutzenden gelöscht", "app.userList.userOptions.enableCam": "Teilnehmer dürfen ihre Webcams verwenden", "app.userList.userOptions.enableMic": "Teilnehmer dürfen ihre Mikrofone verwenden", "app.userList.userOptions.enablePrivChat": "Privater Chat ist erlaubt", @@ -177,6 +205,11 @@ "app.media.screenshare.notSupported": "Bildschirmfreigabe wird in diesem Browser nicht unterstützt.", "app.media.screenshare.autoplayBlockedDesc": "Wir benötigen Ihre Zustimmung, um den Bildschirm des Präsentators zu zeigen.", "app.media.screenshare.autoplayAllowLabel": "Geteilten Bildschirm ansehen", + "app.media.cameraAsContent.start": "Gegenwärtige Kamera ist gestartet", + "app.media.cameraAsContent.end": "Gegenwärtige Kamera ist beendet", + "app.media.cameraAsContent.endDueToDataSaving": "Gegenwärtige Kamera wurde aufgrund von Dateneinsparungen eingestellt", + "app.media.cameraAsContent.autoplayBlockedDesc": "Wir benötigen Ihre Zustimmung um Ihnen die Präsentationskamera zu zeigen.", + "app.media.cameraAsContent.autoplayAllowLabel": "Gegenwärtige Kamera anzeigen", "app.screenshare.presenterLoadingLabel": "Ihr Bildschirm wird freigegeben", "app.screenshare.viewerLoadingLabel": "Der Bildschirm des Präsentators wird geladen", "app.screenshare.presenterSharingLabel": "Der Bildschirm wird nun geteilt.", @@ -185,6 +218,9 @@ "app.screenshare.screenshareRetryOtherEnvError": "Fehler {0}. Der Bildschirm konnte nicht freigegeben werden. Bitte mit einem anderen Browser oder einem anderen Gerät probieren.", "app.screenshare.screenshareUnsupportedEnv": "Fehler {0}. Dieser Browser wird nicht unterstützt. Bitte einen anderen Browser oder ein anderes Gerät probieren.", "app.screenshare.screensharePermissionError": "Fehler {0}. Die Berechtigung zur Bildschirmfreigabe muss erteilt werden.", + "app.cameraAsContent.presenterLoadingLabel": "Ihre Kamera lädt", + "app.cameraAsContent.viewerLoadingLabel": "Die Präsentationskamera lädt", + "app.cameraAsContent.presenterSharingLabel": "Sie zeigen jetzt Ihre Kamera", "app.meeting.ended": "Diese Konferenz wurde beendet", "app.meeting.meetingTimeRemaining": "Verbleibende Konferenzzeit: {0}", "app.meeting.meetingTimeHasEnded": "Die Zeit ist abgelaufen. Die Konferenz wird in Kürze beendet", @@ -199,6 +235,7 @@ "app.presentation.hide": "Präsentation verbergen", "app.presentation.notificationLabel": "Aktuelle Präsentation", "app.presentation.downloadLabel": "Download", + "app.presentation.actionsLabel": "Aktionen", "app.presentation.slideContent": "Folieninhalt", "app.presentation.startSlideContent": "Beginn des Folieninhalts", "app.presentation.endSlideContent": "Ende des Folieninhalts", @@ -250,8 +287,15 @@ "app.presentationUploader.sent": "Gesendet", "app.presentationUploader.exportingTimeout": "Der Export dauert zu lange…", "app.presentationUploader.export": "An Chat senden", + "app.presentationUploader.exportCurrentStatePresentation": "Einen Downloadlink für die Präsentation im aktuellen Stand versenden", + "app.presentationUploader.enableOriginalPresentationDownload": "Download der Originalpräsentation ermöglichen", + "app.presentationUploader.disableOriginalPresentationDownload": "Download der Originalpräsentation verhindern", + "app.presentationUploader.dropdownExportOptions": "Exportoptionen", "app.presentationUploader.export.linkAvailable": "Der Link zum Download von {0} steht im allgemeinen Chat zur Verfügung.", + "app.presentationUploader.export.downloadButtonAvailable": "Der Download-Button für die Präsentation {0} ist verfügbar.", "app.presentationUploader.export.notAccessibleWarning": "ist möglicherweise nicht barrierefrei", + "app.presentationUploader.export.originalLabel": "Original", + "app.presentationUploader.export.inCurrentStateLabel": "Im aktuellen Status", "app.presentationUploader.currentPresentationLabel": "Aktuelle Präsentation", "app.presentationUploder.extraHint": "WICHTIG: Jede Datei darf {0} MB und {1} Seiten nicht überschreiten.", "app.presentationUploder.uploadLabel": "Hochladen", @@ -446,7 +490,10 @@ "app.actionsBar.actionsDropdown.minimizePresentationLabel": "Präsentation minimieren", "app.actionsBar.actionsDropdown.minimizePresentationDesc": "Schaltfläche zum Minimieren der Präsentation", "app.actionsBar.actionsDropdown.layoutModal": "Layout-Einstellungen", + "app.actionsBar.actionsDropdown.shareCameraAsContent": "Kamera als Inhalt teilen", + "app.actionsBar.actionsDropdown.unshareCameraAsContent": "Kamera nicht mehr als Inhalt teilen", "app.screenshare.screenShareLabel" : "Bildschirmfreigabe", + "app.cameraAsContent.cameraAsContentLabel" : "Gegenwärtige Kamera", "app.submenu.application.applicationSectionTitle": "Anwendung", "app.submenu.application.animationsLabel": "Animationen", "app.submenu.application.audioFilterLabel": "Audiofilter für das Mikrofon", @@ -460,6 +507,7 @@ "app.submenu.application.languageOptionLabel": "Sprache auswählen", "app.submenu.application.noLocaleOptionLabel": "Keine Sprachschemata verfügbar", "app.submenu.application.paginationEnabledLabel": "Seitenweises Anzeigen von Webcams", + "app.submenu.application.wakeLockEnabledLabel": "Sperre aufheben", "app.submenu.application.layoutOptionLabel": "Layout-Modus", "app.submenu.application.pushLayoutLabel": "Layout verteilen", "app.submenu.application.localeDropdown.af": "Afrikaans", @@ -569,6 +617,8 @@ "app.talkingIndicator.moreThanMaxIndicatorsWereTalking" : "{0}+ sprachen", "app.talkingIndicator.wasTalking" : "{0} spricht nicht mehr", "app.actionsBar.actionsDropdown.actionsLabel": "Aktionen", + "app.actionsBar.actionsDropdown.activateTimerStopwatchLabel": "Kurzzeitwecker/Stoppuhr aktivieren", + "app.actionsBar.actionsDropdown.deactivateTimerStopwatchLabel": "Kurzzeitwecker/Stoppuhr abschalten", "app.actionsBar.actionsDropdown.presentationLabel": "Präsentationen hochladen/verwalten", "app.actionsBar.actionsDropdown.initPollLabel": "Eine Umfrage starten", "app.actionsBar.actionsDropdown.desktopShareLabel": "Bildschirm freigeben", @@ -588,7 +638,9 @@ "app.actionsBar.actionsDropdown.takePresenterDesc": "Sich selbst zum neuen Präsentator machen", "app.actionsBar.actionsDropdown.selectRandUserLabel": "Zufälligen Teilnehmer auswählen", "app.actionsBar.actionsDropdown.selectRandUserDesc": "Wählt einen Teilnehmer nach dem Zufallsprinzip aus", - "app.actionsBar.actionsDropdown.propagateLayoutLabel": "Layout verbreiten", + "app.actionsBar.reactions.reactionsButtonLabel": "Reaktionenleiste", + "app.actionsBar.reactions.raiseHand": "Hand heben", + "app.actionsBar.reactions.lowHand": "Hand senken", "app.actionsBar.emojiMenu.statusTriggerLabel": "Status setzen", "app.actionsBar.emojiMenu.awayLabel": "Abwesend", "app.actionsBar.emojiMenu.awayDesc": "Ihren Status auf abwesend setzen", @@ -817,8 +869,15 @@ "app.toast.meetingMuteOn.label": "Alle Teilnehmer wurden stummgeschaltet", "app.toast.meetingMuteOnViewers.label": "Alle Teilnehmer wurden stummgeschaltet", "app.toast.meetingMuteOff.label": "Konferenz-Stummschaltung ausgeschaltet", + "app.toast.wakeLock.offerTitle": "Möchten Sie, dass der Bildschirm Ihres Geräts während der Besprechung aktiv bleibt?", + "app.toast.wakeLock.offerAccept": "Ja!", + "app.toast.wakeLock.offerDecline": "Jetzt gerade nicht", + "app.toast.wakeLock.acquireSuccess": "Sperre aktiv! Sie können diese Funktion im Einstellungsmenü deaktivieren.", + "app.toast.wakeLock.acquireFailed": "Fehler beim Erfassen der Aufwach-Sperre.", "app.toast.setEmoji.raiseHand": "Sie haben Ihre Hand gehoben", "app.toast.setEmoji.lowerHand": "Ihre Hand wurde gesenkt", + "app.toast.setEmoji.away": "Sie haben Ihren Status auf abwesend gesetzt", + "app.toast.setEmoji.notAway": "Sie haben Ihren Abwesenheitsstatus entfernt", "app.toast.promotedLabel": "Sie sind zum Moderator ernannt worden", "app.toast.demotedLabel": "Sie sind nun wieder in der Rolle eines Teilnehmers", "app.notification.recordingStart": "Diese Konferenz wird jetzt aufgezeichnet", @@ -901,6 +960,7 @@ "app.lock-viewers.button.cancel": "Abbrechen", "app.lock-viewers.locked": "Gesperrt", "app.lock-viewers.hideViewersCursor": "Cursor anderer Teilnehmer anzeigen", + "app.lock-viewers.hideAnnotationsLabel": "Anmerkungen anderer Personen anzeigen", "app.guest-policy.ariaTitle": "Einstellungsdialog für Gastzugangsregelung", "app.guest-policy.title": "Gastzugang regeln", "app.guest-policy.description": "Grundregel für den Gastzugang ändern", @@ -908,6 +968,7 @@ "app.guest-policy.button.alwaysAccept": "Immer akzeptieren", "app.guest-policy.button.alwaysDeny": "Immer verweigern", "app.guest-policy.policyBtnDesc": "Grundregel für den Gastzugang ändern", + "app.guest-policy.feedbackMessage": "Die Gäste Berechtigung ist jetzt:", "app.connection-status.ariaTitle": "Verbindungsstatus", "app.connection-status.title": "Verbindungsstatus", "app.connection-status.description": "Verbindungsstatus der Teilnehmer anzeigen", @@ -965,6 +1026,7 @@ "app.videoPreview.webcamPreviewLabel": "Webcamvorschau", "app.videoPreview.webcamSettingsTitle": "Webcameinstellungen", "app.videoPreview.webcamEffectsTitle": "Grafische Effekte für Webcam", + "app.videoPreview.cameraAsContentSettingsTitle": "Aktuelle Kamera", "app.videoPreview.webcamVirtualBackgroundLabel": "Einstellungen zum virtuellen Hintergrund", "app.videoPreview.webcamVirtualBackgroundDisabledLabel": "Dieses Gerät unterstützt keine virtuellen Hintergründe", "app.videoPreview.webcamNotFoundLabel": "Keine Webcam gefunden", @@ -1093,6 +1155,10 @@ "app.videoDock.webcamFocusDesc": "Ausgewählte Webcam vergrößern", "app.videoDock.webcamUnfocusLabel": "Normalgröße", "app.videoDock.webcamUnfocusDesc": "Ausgewählte Webcam auf Normalgröße verkleinern", + "app.videoDock.webcamDisableLabel": "Selbstansicht deaktivieren", + "app.videoDock.webcamDisableLabelAllCams": "Selbstansicht deaktivieren (alle Kameras)", + "app.videoDock.webcamEnableLabel": "Selbstansicht aktivieren", + "app.videoDock.webcamDisableDesc": "Selbstansicht deaktiviert", "app.videoDock.webcamPinLabel": "Anheften", "app.videoDock.webcamPinDesc": "Ausgewählte Webcam anheften", "app.videoDock.webcamFullscreenLabel": "Webcam als Vollbild", @@ -1133,8 +1199,10 @@ "app.createBreakoutRoom.addRoomTime": "Gruppenraumzeit erhöhen auf", "app.createBreakoutRoom.addParticipantLabel": "+ Teilnehmer hinzufügen", "app.createBreakoutRoom.freeJoin": "Den Teilnehmern erlauben, sich selbst einen Gruppenraum auszusuchen.", + "app.createBreakoutRoom.manageRoomsLabel": "Räume verwalten", "app.createBreakoutRoom.captureNotes": "Übertragen der gemeinsamen Notizen nach Beendigung der Arbeitsgruppenräume", "app.createBreakoutRoom.captureSlides": "Whiteboard aufnehmen, wenn Gruppenräume enden", + "app.createBreakoutRoom.sendInvitationToMods": "Einladung an zugewiesene Moderatoren senden", "app.createBreakoutRoom.leastOneWarnBreakout": "Jedem Gruppenraum muss wenigstens ein Teilnehmer zugeordnet sein.", "app.createBreakoutRoom.minimumDurationWarnBreakout": "Die Mindestdauer für einen Gruppenraum beträgt {0} Minuten.", "app.createBreakoutRoom.modalDesc": "Tipp: Sie können die Teilnehmer per Drag-and-Drop einem bestimmten Gruppenraum zuweisen.", @@ -1180,10 +1248,9 @@ "app.debugWindow.form.chatLoggerLabel": "Test der Chatprotokollierungsstufen", "app.debugWindow.form.button.apply": "Anwenden", "app.layout.modal.title": "Layouts", - "app.layout.modal.confirm": "Bestätigen", - "app.layout.modal.cancel": "Abbrechen", + "app.layout.modal.update": "Update", + "app.layout.modal.updateAll": "Alle aktualisieren", "app.layout.modal.layoutLabel": "Layout auswählen", - "app.layout.modal.keepPushingLayoutLabel": "Layout bei allen anwenden", "app.layout.modal.pushLayoutLabel": "An alle verteilen", "app.layout.modal.layoutToastLabel": "Layouteinstellungen geändert", "app.layout.modal.layoutSingular": "Layout", From 01e33670998e9493b1893ec48062928fbc5f0944 Mon Sep 17 00:00:00 2001 From: Anton Georgiev Date: Mon, 14 Aug 2023 17:24:25 -0400 Subject: [PATCH 202/252] docs: added link to 2.7.0-rc.1 --- docs/docs/new-features.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/new-features.md b/docs/docs/new-features.md index 083e301c5e..1ba694e7a6 100644 --- a/docs/docs/new-features.md +++ b/docs/docs/new-features.md @@ -136,6 +136,7 @@ For full details on what is new in BigBlueButton 2.7, see the release notes. Recent releases: +- [2.7.0-rc.1](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.7.0-rc.1) - [2.7.0-beta.3](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.7.0-beta.3) - [2.7.0-beta.2](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.7.0-beta.2) - [2.7.0-beta.1](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.7.0-beta.1) From 8adec98c2606c6ee4dba1bedf8951d095cbf97d6 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Tue, 15 Aug 2023 09:18:48 -0300 Subject: [PATCH 203/252] Make the build phase as a reusable workflow --- .../automated-tests-build-package-job.yml | 53 ++ .github/workflows/automated-tests.yml | 460 +++--------------- 2 files changed, 114 insertions(+), 399 deletions(-) create mode 100644 .github/workflows/automated-tests-build-package-job.yml diff --git a/.github/workflows/automated-tests-build-package-job.yml b/.github/workflows/automated-tests-build-package-job.yml new file mode 100644 index 0000000000..d9a91d5fc0 --- /dev/null +++ b/.github/workflows/automated-tests-build-package-job.yml @@ -0,0 +1,53 @@ +name: Build service and cache +on: + workflow_call: + inputs: + build-name: + required: true + type: string + build-list: + type: string + cache-files-list: + type: string + cache-urls-list: + type: string +jobs: + build-and-cache: + runs-on: ubuntu-20.04 + steps: + - name: Checkout ${{ github.event.pull_request.base.ref }} + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.base.ref }} + fetch-depth: 0 # Fetch all history + - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} + run: | + git config user.name "BBB Automated Tests" + git config user.email "tests@bigbluebutton.org" + git config pull.rebase false + git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} + - name: Set cache-key vars + run: | + echo "CACHE_KEY_FILES=$(echo '${{ inputs.cache-files-list }}' | xargs -n1 git log -1 --format=%h -- | tr '\n' '-' | sed 's/-$//')" >> $GITHUB_ENV + echo "CACHE_KEY_URLS=$(echo '${{ inputs.cache-urls-list }}' | xargs -r -n 1 curl -Is | grep -i 'Last-Modified' | md5sum | cut -c1-10)" >> $GITHUB_ENV + cat bigbluebutton-config/bigbluebutton-release >> $GITHUB_ENV + echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh + echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh + - name: Handle cache + if: inputs.cache-files-list != '' + id: cache-action + uses: actions/cache@v3 + with: + path: artifacts.tar + key: ${{ runner.os }}-${{ inputs.build-name }}-${{ env.BIGBLUEBUTTON_RELEASE }}-commits-${{ env.CACHE_KEY_FILES }}-urls-${{ env.CACHE_KEY_URLS }} + - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} + name: Generate artifacts + run: | + ./build/get_external_dependencies.sh + echo "${{ inputs.build-list || inputs.build-name }}" | xargs -n 1 ./build/setup.sh + tar cvf artifacts.tar artifacts/ + - name: Archive packages + uses: actions/upload-artifact@v3 + with: + name: artifacts_${{ inputs.build-name }}.tar + path: artifacts.tar diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 96d8eb87bf..da35205d92 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -20,415 +20,72 @@ concurrency: cancel-in-progress: true jobs: build-bbb-apps-akka: - runs-on: ubuntu-20.04 - steps: - - name: Checkout ${{ github.event.pull_request.base.ref }} - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.base.ref }} - fetch-depth: 0 # Fetch all history - - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} - run: | - git config user.name "BBB Automated Tests" - git config user.email "tests@bigbluebutton.org" - git config pull.rebase false - git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} - - name: Set cache-key vars - run: | - echo "CACHE_AKKA_APPS_KEY=$(git log -1 --format=%H -- akka-bbb-apps)" >> $GITHUB_ENV - echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV - echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - name: Handle cache - id: cache-action - uses: actions/cache@v3 - with: - path: artifacts.tar - key: ${{ runner.os }}-bbb-apps-akka-${{ env.CACHE_AKKA_APPS_KEY }}-${{ env.CACHE_COMMON_MSG_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} - name: Generate artifacts - run: | - ./build/get_external_dependencies.sh - ./build/setup.sh bbb-apps-akka - tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-apps-akka.tar - path: | - artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-apps-akka + cache-files-list: akka-bbb-apps bbb-common-message build-bbb-config: - runs-on: ubuntu-20.04 - steps: - - name: Checkout ${{ github.event.pull_request.base.ref }} - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.base.ref }} - fetch-depth: 0 # Fetch all history - - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} - run: | - git config user.name "BBB Automated Tests" - git config user.email "tests@bigbluebutton.org" - git config pull.rebase false - git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} - - name: Set cache-key vars - run: | - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - run: | - ./build/get_external_dependencies.sh - ./build/setup.sh bbb-config - tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-config.tar - path: artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-config + cache-files-list: bigbluebutton-config build-bbb-export-annotations: - runs-on: ubuntu-20.04 - steps: - - name: Checkout ${{ github.event.pull_request.base.ref }} - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.base.ref }} - fetch-depth: 0 # Fetch all history - - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} - run: | - git config user.name "BBB Automated Tests" - git config user.email "tests@bigbluebutton.org" - git config pull.rebase false - git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} - - name: Set cache-key vars - run: | - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - run: | - ./build/get_external_dependencies.sh - ./build/setup.sh bbb-export-annotations - tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-export-annotations.tar - path: artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-export-annotations + cache-files-list: bbb-export-annotations build-bbb-learning-dashboard: - runs-on: ubuntu-20.04 - steps: - - name: Checkout ${{ github.event.pull_request.base.ref }} - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.base.ref }} - fetch-depth: 0 # Fetch all history - - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} - run: | - git config user.name "BBB Automated Tests" - git config user.email "tests@bigbluebutton.org" - git config pull.rebase false - git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} - - name: Set cache-key vars - run: | - echo "CACHE_LEARNING_DASHBOARD_KEY=$(git log -1 --format=%H -- bbb-learning-dashboard)" >> $GITHUB_ENV - echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - name: Handle cache - id: cache-action - uses: actions/cache@v3 - with: - path: artifacts.tar - key: ${{ runner.os }}-bbb-learning-dashboard-${{ env.CACHE_LEARNING_DASHBOARD_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} - name: Generate artifacts - run: | - ./build/get_external_dependencies.sh - ./build/setup.sh bbb-learning-dashboard - tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-learning-dashboard.tar - path: artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-learning-dashboard + cache-files-list: bbb-learning-dashboard build-bbb-playback-record: - runs-on: ubuntu-20.04 - steps: - - name: Checkout ${{ github.event.pull_request.base.ref }} - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.base.ref }} - fetch-depth: 0 # Fetch all history - - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} - run: | - git config user.name "BBB Automated Tests" - git config user.email "tests@bigbluebutton.org" - git config pull.rebase false - git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} - - run: ./build/get_external_dependencies.sh - - name: Set cache-key vars - run: | - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - run: ./build/setup.sh bbb-playback - - run: ./build/setup.sh bbb-playback-notes - - run: ./build/setup.sh bbb-playback-podcast - - run: ./build/setup.sh bbb-playback-presentation - - run: ./build/setup.sh bbb-playback-screenshare - - run: ./build/setup.sh bbb-playback-video - - run: ./build/setup.sh bbb-record-core - - run: tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-playback-record.tar - path: | - artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-playback-record + build-list: bbb-playback bbb-playback-notes bbb-playback-podcast bbb-playback-presentation bbb-playback-screenshare bbb-playback-video bbb-record-core build-bbb-etherpad: - runs-on: ubuntu-20.04 - steps: - - name: Checkout ${{ github.event.pull_request.base.ref }} - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.base.ref }} - fetch-depth: 0 # Fetch all history - - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} - run: | - git config user.name "BBB Automated Tests" - git config user.email "tests@bigbluebutton.org" - git config pull.rebase false - git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} - - name: Set cache-key vars - run: | - echo "CACHE_ETHERPAD_VERSION_KEY=$(git log -1 --format=%H -- bbb-etherpad.placeholder.sh)" >> $GITHUB_ENV - echo "CACHE_ETHERPAD_BUILD_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-etherpad)" >> $GITHUB_ENV - echo "CACHE_URL1_KEY=$(curl -s https://api.github.com/repos/mconf/ep_pad_ttl/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - echo "CACHE_URL2_KEY=$(curl -s https://api.github.com/repos/alangecker/bbb-etherpad-plugin/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - echo "CACHE_URL3_KEY=$(curl -s https://api.github.com/repos/mconf/ep_redis_publisher/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - echo "CACHE_URL4_KEY=$(curl -s https://api.github.com/repos/alangecker/bbb-etherpad-skin/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - name: Handle cache - id: cache-action - uses: actions/cache@v3 - with: - path: artifacts.tar - key: ${{ runner.os }}-bbb-etherpad-${{ env.CACHE_ETHERPAD_VERSION_KEY }}-${{ env.CACHE_ETHERPAD_BUILD_KEY }}-${{ env.CACHE_URL1_KEY }}-${{ env.CACHE_URL2_KEY }}-${{ env.CACHE_URL3_KEY }}-${{ env.CACHE_URL4_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} - name: Generate artifacts - run: | - ./build/get_external_dependencies.sh - ./build/setup.sh bbb-etherpad - tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-etherpad.tar - path: | - artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-etherpad + cache-files-list: bbb-etherpad.placeholder.sh build/packages-template/bbb-etherpad + cache-urls-list: https://api.github.com/repos/mconf/ep_pad_ttl/commits https://api.github.com/repos/alangecker/bbb-etherpad-plugin/commits https://api.github.com/repos/mconf/ep_redis_publisher/commits https://api.github.com/repos/alangecker/bbb-etherpad-skin/commits build-bbb-bbb-web: - runs-on: ubuntu-20.04 - steps: - - name: Checkout ${{ github.event.pull_request.base.ref }} - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.base.ref }} - fetch-depth: 0 # Fetch all history - - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} - run: | - git config user.name "BBB Automated Tests" - git config user.email "tests@bigbluebutton.org" - git config pull.rebase false - git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} - - name: Set cache-key vars - run: | - echo "CACHE_BBB_WEB_KEY=$(git log -1 --format=%H -- bigbluebutton-web)" >> $GITHUB_ENV - echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV - echo "CACHE_COMMON_WEB_KEY=$(git log -1 --format=%H -- bbb-common-web)" >> $GITHUB_ENV - echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - name: Handle cache - id: cache-action - uses: actions/cache@v3 - with: - path: artifacts.tar - key: ${{ runner.os }}-bbb-web-${{ env.CACHE_BBB_WEB_KEY }}-${{ env.CACHE_COMMON_MSG_KEY }}-${{ env.CACHE_COMMON_WEB_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} - name: Generate artifacts - run: | - ./build/get_external_dependencies.sh - ./build/setup.sh bbb-web - tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-web.tar - path: | - artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-web + cache-files-list: bigbluebutton-web bbb-common-message bbb-common-web build-bbb-fsesl-akka: - runs-on: ubuntu-20.04 - steps: - - name: Checkout ${{ github.event.pull_request.base.ref }} - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.base.ref }} - fetch-depth: 0 # Fetch all history - - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} - run: | - git config user.name "BBB Automated Tests" - git config user.email "tests@bigbluebutton.org" - git config pull.rebase false - git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} - - name: Set cache-key vars - run: | - echo "CACHE_AKKA_FSESL_KEY=$(git log -1 --format=%H -- akka-bbb-fsesl)" >> $GITHUB_ENV - echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV - echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - name: Handle cache - id: cache-action - uses: actions/cache@v3 - with: - path: artifacts.tar - key: ${{ runner.os }}-bbb-fsesl-akka-${{ env.CACHE_AKKA_FSESL_KEY }}-${{ env.CACHE_COMMON_MSG_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} - name: Generate artifacts - run: | - ./build/get_external_dependencies.sh - ./build/setup.sh bbb-fsesl-akka - tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-fsesl-akka.tar - path: | - artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-fsesl-akka + cache-files-list: akka-bbb-fsesl bbb-common-message build-bbb-html5: - runs-on: ubuntu-20.04 - steps: - - name: Checkout ${{ github.event.pull_request.base.ref }} - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.base.ref }} - fetch-depth: 0 # Fetch all history - - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} - run: | - git config user.name "BBB Automated Tests" - git config user.email "tests@bigbluebutton.org" - git config pull.rebase false - git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} - - name: Set cache-key vars - run: | - echo "CACHE_KEY=$(git log -1 --format=%H -- bigbluebutton-html5)" >> $GITHUB_ENV - echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - name: Handle cache - id: cache-action - uses: actions/cache@v3 - with: - path: artifacts.tar - key: ${{ runner.os }}-bbb-html5-${{ env.CACHE_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} - name: Generate artifacts - run: | - ./build/get_external_dependencies.sh - ./build/setup.sh bbb-html5-nodejs - ./build/setup.sh bbb-html5 - tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-html5.tar - path: | - artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-html5 + build-list: bbb-html5-nodejs bbb-html5 + cache-files-list: bigbluebutton-html5 build-bbb-freeswitch: - runs-on: ubuntu-20.04 - steps: - - name: Checkout ${{ github.event.pull_request.base.ref }} - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.base.ref }} - fetch-depth: 0 # Fetch all history - - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} - run: | - git config user.name "BBB Automated Tests" - git config user.email "tests@bigbluebutton.org" - git config pull.rebase false - git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} - - name: Set cache-key vars - run: | - echo "CACHE_FREESWITCH_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-core)" >> $GITHUB_ENV - echo "CACHE_FREESWITCH_SOUNDS_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-sounds)" >> $GITHUB_ENV - echo "CACHE_SOUNDS_KEY=$(curl -Is http://bigbluebutton.org/downloads/sounds.tar.gz | grep "Last-Modified" | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - name: Handle cache - id: cache-action - uses: actions/cache@v3 - with: - path: artifacts.tar - key: ${{ runner.os }}-bbb-freeswitch-${{ env.CACHE_FREESWITCH_KEY }}-${{ env.CACHE_FREESWITCH_SOUNDS_KEY }}-${{ env.CACHE_SOUNDS_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} - name: Generate artifacts - run: | - ./build/get_external_dependencies.sh - ./build/setup.sh bbb-freeswitch-core - ./build/setup.sh bbb-freeswitch-sounds - tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-freeswitch.tar - path: | - artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-freeswitch + build-list: bbb-freeswitch-core bbb-freeswitch-sounds + cache-files-list: freeswitch.placeholder.sh build/packages-template/bbb-freeswitch-core build/packages-template/bbb-freeswitch-sounds + cache-urls-list: http://bigbluebutton.org/downloads/sounds.tar.gz + build-bbb-webrtc: + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-webrtc + build-list: bbb-webrtc-sfu bbb-webrtc-recorder + cache-files-list: bbb-webrtc-sfu.placeholder.sh bbb-webrtc-recorder.placeholder.sh build/packages-template/bbb-webrtc-sfu build/packages-template/bbb-webrtc-recorder build-others: - runs-on: ubuntu-20.04 - steps: - - name: Checkout ${{ github.event.pull_request.base.ref }} - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.base.ref }} - fetch-depth: 0 # Fetch all history - - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} - run: | - git config user.name "BBB Automated Tests" - git config user.email "tests@bigbluebutton.org" - git config pull.rebase false - git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} - - run: ./build/get_external_dependencies.sh - - name: Set cache-key vars - run: | - echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - run: ./build/setup.sh bbb-mkclean - - run: ./build/setup.sh bbb-pads - - run: ./build/setup.sh bbb-libreoffice-docker - - run: ./build/setup.sh bbb-webrtc-sfu - - run: ./build/setup.sh bbb-webrtc-recorder - - run: ./build/setup.sh bbb-transcription-controller - - run: ./build/setup.sh bigbluebutton - - run: tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts.tar - path: | - artifacts.tar - # - name: Fake package build - # run: | - # sudo -i < Date: Tue, 15 Aug 2023 09:53:29 -0300 Subject: [PATCH 204/252] fix: Checkout failing when it's not a Pull Request (push event) --- .github/workflows/automated-tests-build-package-job.yml | 5 +++-- .github/workflows/automated-tests.yml | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/automated-tests-build-package-job.yml b/.github/workflows/automated-tests-build-package-job.yml index d9a91d5fc0..d4655aa073 100644 --- a/.github/workflows/automated-tests-build-package-job.yml +++ b/.github/workflows/automated-tests-build-package-job.yml @@ -15,12 +15,13 @@ jobs: build-and-cache: runs-on: ubuntu-20.04 steps: - - name: Checkout ${{ github.event.pull_request.base.ref }} + - name: Checkout ${{ github.event.pull_request.base.ref || 'master' }} uses: actions/checkout@v3 with: - ref: ${{ github.event.pull_request.base.ref }} + ref: ${{ github.event.pull_request.base.ref || '' }} fetch-depth: 0 # Fetch all history - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} + if: github.event_name == 'pull_request' run: | git config user.name "BBB Automated Tests" git config user.email "tests@bigbluebutton.org" diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index da35205d92..6a7a226640 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -88,12 +88,13 @@ jobs: needs: [build-bbb-apps-akka, build-bbb-config, build-bbb-export-annotations, build-bbb-learning-dashboard, build-bbb-playback-record, build-bbb-etherpad, build-bbb-bbb-web, build-bbb-fsesl-akka, build-bbb-html5, build-bbb-freeswitch, build-bbb-webrtc, build-others] runs-on: ubuntu-20.04 steps: - - name: Checkout ${{ github.event.pull_request.base.ref }} + - name: Checkout ${{ github.event.pull_request.base.ref || 'master' }} uses: actions/checkout@v3 with: - ref: ${{ github.event.pull_request.base.ref }} + ref: ${{ github.event.pull_request.base.ref || '' }} fetch-depth: 0 # Fetch all history - name: Merge pr-${{ github.event.number }} into ${{ github.event.pull_request.base.ref }} + if: github.event_name == 'pull_request' run: | git config user.name "BBB Automated Tests" git config user.email "tests@bigbluebutton.org" From b6732594efd4c2d4999f40c77357ee4b7d041113 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Tue, 15 Aug 2023 10:09:12 -0300 Subject: [PATCH 205/252] Reduce job name to improve visualization of jobs on Summary screen --- .github/workflows/automated-tests-build-package-job.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/automated-tests-build-package-job.yml b/.github/workflows/automated-tests-build-package-job.yml index d4655aa073..d91109eef6 100644 --- a/.github/workflows/automated-tests-build-package-job.yml +++ b/.github/workflows/automated-tests-build-package-job.yml @@ -12,7 +12,7 @@ on: cache-urls-list: type: string jobs: - build-and-cache: + b: runs-on: ubuntu-20.04 steps: - name: Checkout ${{ github.event.pull_request.base.ref || 'master' }} From efd1e559e54902391f588b3f6dcbcdc0fe1ce077 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Tue, 15 Aug 2023 10:09:41 -0300 Subject: [PATCH 206/252] Add .gitlab-ci.yml to cache-key of all packages --- .github/workflows/automated-tests-build-package-job.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/automated-tests-build-package-job.yml b/.github/workflows/automated-tests-build-package-job.yml index d91109eef6..75cdc823e8 100644 --- a/.github/workflows/automated-tests-build-package-job.yml +++ b/.github/workflows/automated-tests-build-package-job.yml @@ -29,7 +29,7 @@ jobs: git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} - name: Set cache-key vars run: | - echo "CACHE_KEY_FILES=$(echo '${{ inputs.cache-files-list }}' | xargs -n1 git log -1 --format=%h -- | tr '\n' '-' | sed 's/-$//')" >> $GITHUB_ENV + echo "CACHE_KEY_FILES=$(echo '${{ inputs.cache-files-list }} .gitlab-ci.yml' | xargs -n1 git log -1 --format=%h -- | tr '\n' '-' | sed 's/-$//')" >> $GITHUB_ENV echo "CACHE_KEY_URLS=$(echo '${{ inputs.cache-urls-list }}' | xargs -r -n 1 curl -Is | grep -i 'Last-Modified' | md5sum | cut -c1-10)" >> $GITHUB_ENV cat bigbluebutton-config/bigbluebutton-release >> $GITHUB_ENV echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh From 55ca2363a458f7dd3ac54a317a960632cc4118a8 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Tue, 15 Aug 2023 09:22:27 -0400 Subject: [PATCH 207/252] Updates for project BigBlueButton v2.7 HTML5 client and lanuage ja on branch v2.7.x-release (#18552) * Translate en.json in ja 100% translated source file: 'en.json' on 'ja'. --------- Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- bigbluebutton-html5/public/locales/ja.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bigbluebutton-html5/public/locales/ja.json b/bigbluebutton-html5/public/locales/ja.json index ab87747b74..935846c81f 100644 --- a/bigbluebutton-html5/public/locales/ja.json +++ b/bigbluebutton-html5/public/locales/ja.json @@ -140,7 +140,7 @@ "app.userList.chatListItem.unreadSingular": "1 通の新規メッセージ", "app.userList.chatListItem.unreadPlural": "{0} 通の新規メッセージ", "app.userList.menu.away": "自分を退席中にする", - "app.userList.menu.notAway": "自分を出席中にする", + "app.userList.menu.notAway": "自分を出席にする", "app.userList.menu.chat.label": "非公開チャット", "app.userList.menu.clearStatus.label": "ステータスを消去する", "app.userList.menu.removeUser.label": "このユーザーを退室させる", @@ -489,7 +489,7 @@ "app.actionsBar.actionsDropdown.restorePresentationDesc": "最小化したプレゼンパネルを復活させるボタン", "app.actionsBar.actionsDropdown.minimizePresentationLabel": "プレゼンパネルを最小化", "app.actionsBar.actionsDropdown.minimizePresentationDesc": "プレゼンパネルを最小化するボタン", - "app.actionsBar.actionsDropdown.layoutModal": "レイアウト設定モーダル", + "app.actionsBar.actionsDropdown.layoutModal": "レイアウト設定", "app.actionsBar.actionsDropdown.shareCameraAsContent": "カメラ画像をプレゼンコンテンツとして共有", "app.actionsBar.actionsDropdown.unshareCameraAsContent": "カメラの共有をやめる", "app.screenshare.screenShareLabel" : "画面共有", @@ -619,7 +619,7 @@ "app.actionsBar.actionsDropdown.actionsLabel": "アクション", "app.actionsBar.actionsDropdown.activateTimerStopwatchLabel": "タイマー / ストップウォッチを使用", "app.actionsBar.actionsDropdown.deactivateTimerStopwatchLabel": "タイマー / ストップウォッチを消す", - "app.actionsBar.actionsDropdown.presentationLabel": "プレゼンファイルをアップロード/管理", + "app.actionsBar.actionsDropdown.presentationLabel": "プレゼンファイルをアップロード / 管理", "app.actionsBar.actionsDropdown.initPollLabel": "投票を初期化", "app.actionsBar.actionsDropdown.desktopShareLabel": "画面を共有", "app.actionsBar.actionsDropdown.stopDesktopShareLabel": "画面共有をやめる", @@ -1158,7 +1158,7 @@ "app.videoDock.webcamDisableLabel": "セルフィーモードを停止", "app.videoDock.webcamDisableLabelAllCams": "セルフィーモードを停止(全てのカメラ)", "app.videoDock.webcamEnableLabel": "セルフィーモードを開始", - "app.videoDock.webcamDisableDesc": "セルフィーモードが停止中です", + "app.videoDock.webcamDisableDesc": "セルフィーモード停止中", "app.videoDock.webcamPinLabel": "ピン止め", "app.videoDock.webcamPinDesc": "選択したウェブカメラをピン止めする", "app.videoDock.webcamFullscreenLabel": "ウェブカメラを全画面表示にする", @@ -1185,8 +1185,8 @@ "app.createBreakoutRoom.record": "録画", "app.createBreakoutRoom.numberOfRooms": "会議室数", "app.createBreakoutRoom.durationInMinutes": "利用時間(分)", - "app.createBreakoutRoom.randomlyAssign": "ランダムに割りふる", - "app.createBreakoutRoom.randomlyAssignDesc": "ユーザーをランダムに各部屋に割りふる", + "app.createBreakoutRoom.randomlyAssign": "ランダムに振り分ける", + "app.createBreakoutRoom.randomlyAssignDesc": "ユーザーを各部屋にランダムに振り分ける", "app.createBreakoutRoom.resetAssignments": "割りふりを解除", "app.createBreakoutRoom.resetAssignmentsDesc": "全ての小会議室の割りふりを解除", "app.createBreakoutRoom.endAllBreakouts": "全ての小会議室を終了する", @@ -1237,8 +1237,8 @@ "app.externalVideo.subtitlesOff": "(あれば)表示する", "app.actionsBar.actionsDropdown.shareExternalVideo": "インターネット上の動画を共有", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "動画の共有停止", - "app.legacy.unsupportedBrowser": "サポート対象外のブラウザを使用している可能性があります。サポート対象の{0}または{1}をお使いください。", - "app.legacy.upgradeBrowser": "ブラウザのバージョンが古い可能性があります。サポート対象のブラウザへアップグレードしてください。", + "app.legacy.unsupportedBrowser": "サポート対象外のブラウザを使用されているようです。サポート対象の{0}または{1}をお使いください。", + "app.legacy.upgradeBrowser": "ブラウザのバージョンが古いようです。サポート対象のブラウザへアップグレードしてください。", "app.legacy.criosBrowser": "iOSをお使いの場合、Safariを使ってください", "app.debugWindow.windowTitle": "デバッグ", "app.debugWindow.form.userAgentLabel": "ユーザーエージェント", From d6316aa213285bb6da65fe9cd3f7a2aa5d35727e Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Tue, 15 Aug 2023 14:55:50 -0400 Subject: [PATCH 208/252] Translate en.json in et (#18558) 100% translated source file: 'en.json' on 'et'. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- bigbluebutton-html5/public/locales/et.json | 807 +++++++++++---------- 1 file changed, 437 insertions(+), 370 deletions(-) diff --git a/bigbluebutton-html5/public/locales/et.json b/bigbluebutton-html5/public/locales/et.json index d35d374225..d6b84a2ac1 100644 --- a/bigbluebutton-html5/public/locales/et.json +++ b/bigbluebutton-html5/public/locales/et.json @@ -2,14 +2,15 @@ "app.home.greeting": "Esitlus algab peatselt...", "app.chat.submitLabel": "Saada sõnum", "app.chat.loading": "Vestluse sõnumeid laaditud: {0}%", - "app.chat.errorMaxMessageLength": "Sõnum on liiga pikk, maksimaalne sümbolite arv on {0}.", + "app.chat.errorMaxMessageLength": "Sõnum on liiga pikk, maksimaalne pikkus on {0} tähemärki.", "app.chat.disconnected": "Ühendus on katkenud, sõnumeid ei saa saata", - "app.chat.locked": "Vestlus on lukus, sõnumeid ei saa saata", - "app.chat.inputLabel": "Sõnum vestluse {0} jaoks", - "app.chat.emojiButtonLabel": "Emojide valija", - "app.chat.inputPlaceholder": "Sõnum {0}", + "app.chat.locked": "Vestlus on lukustatud, sõnumeid ei saa saata", + "app.chat.inputLabel": "Sõnum vestluse jaoks: {0}", + "app.chat.emojiButtonLabel": "Emoji valija", + "app.chat.loadMoreButtonLabel": "Laadi rohkem", + "app.chat.inputPlaceholder": "Sõnum: {0}", "app.chat.titlePublic": "Avalik vestlus", - "app.chat.titlePrivate": "Privaatne vestlus kasutajaga {0}", + "app.chat.titlePrivate": "Privaatne vestlus - {0}", "app.chat.partnerDisconnected": "{0} lahkus koosolekult", "app.chat.closeChatLabel": "Sulge {0}", "app.chat.hideChatLabel": "Peida {0}", @@ -24,8 +25,11 @@ "app.chat.breakoutDurationUpdated": "Eraldatud ruumi aeg on nüüd {0} minutit", "app.chat.breakoutDurationUpdatedModerator": "Eraldatud ruumi aeg on nüüd {0} minutit, teavitus saadetud.", "app.chat.emptyLogLabel": "Vestluse logi on tühi", - "app.chat.clearPublicChatMessage": "Avaliku vestluse ajalugu tühjendati moderaatori poolt", + "app.chat.away": "On eemal", + "app.chat.notAway": "Pole enam eemal", + "app.chat.clearPublicChatMessage": "Moderaator tühjendas avaliku vestluse ajaloo", "app.chat.multi.typing": "Mitu kasutajat kirjutavad", + "app.chat.someone.typing": "Keegi kirjutab", "app.chat.one.typing": "{0} kirjutab", "app.chat.two.typing": "{0} ja {1} kirjutavad", "app.chat.copySuccess": "Vestluse logi kopeeritud", @@ -36,6 +40,7 @@ "app.emojiPicker.clear": "Tühjenda", "app.emojiPicker.categories.label": "Emojide kategooriad", "app.emojiPicker.categories.people": "Inimesed & Kehaosad", + "app.emojiPicker.categories.reactions": "Reaktsioonid", "app.emojiPicker.categories.nature": "Loomad & Loodus", "app.emojiPicker.categories.foods": "Söök & Jook", "app.emojiPicker.categories.places": "Reisimine & Kohad", @@ -43,7 +48,7 @@ "app.emojiPicker.categories.objects": "Objektid", "app.emojiPicker.categories.symbols": "Sümbolid", "app.emojiPicker.categories.flags": "Lipud", - "app.emojiPicker.categories.recent": "Sagedasti kasutatud", + "app.emojiPicker.categories.recent": "Sageli kasutatud", "app.emojiPicker.categories.search": "Otsingutulemused", "app.emojiPicker.skintones.1": "Tavaline nahatoon", "app.emojiPicker.skintones.2": "Hele nahatoon", @@ -51,24 +56,41 @@ "app.emojiPicker.skintones.4": "Keskmine nahatoon", "app.emojiPicker.skintones.5": "Keskmiselt tume nahatoon", "app.emojiPicker.skintones.6": "Tume nahatoon", - "app.captions.label": "Tiitrid", + "app.timer.title": "Aeg", + "app.timer.stopwatch.title": "Stopper", + "app.timer.timer.title": "Taimer", + "app.timer.hideTimerLabel": "Peida aeg", + "app.timer.button.stopwatch": "Stopper", + "app.timer.button.timer": "Taimer", + "app.timer.button.start": "Käivita", + "app.timer.button.stop": "Peata", + "app.timer.button.reset": "Lähtesta", + "app.timer.hours": "tunnid", + "app.timer.minutes": "minutid", + "app.timer.seconds": "sekundid", + "app.timer.songs": "Laulud", + "app.timer.noTrack": "Pole", + "app.timer.track1": "Lõdvestav", + "app.timer.track2": "Rahulik", + "app.timer.track3": "Rõõmus", + "app.captions.label": "Subtiitrid", "app.captions.menu.close": "Sulge", "app.captions.menu.start": "Alusta", - "app.captions.menu.ariaStart": "Alusta tiitrite kirjutamist", - "app.captions.menu.ariaStartDesc": "Avab tiitrite redaktori ning sulgeb selle akna", - "app.captions.menu.select": "Vali saadaval olev keel", - "app.captions.menu.ariaSelect": "Tiitrite keel", - "app.captions.menu.subtitle": "Vali selle ruumi tiitrite jaoks keel ja kirjastiil", - "app.captions.menu.title": "Tiitrid", + "app.captions.menu.ariaStart": "Alusta subtiitrite kirjutamist", + "app.captions.menu.ariaStartDesc": "Avab subtiitrite redaktori ning sulgeb selle akna", + "app.captions.menu.select": "Vali olemasolev keel", + "app.captions.menu.ariaSelect": "Subtiitrite keel", + "app.captions.menu.subtitle": "Vali oma sessiooni subtiitrite jaoks keel ja kirjastiil", + "app.captions.menu.title": "Subtiitrid", "app.captions.menu.fontSize": "Suurus", "app.captions.menu.fontColor": "Teksti värv", "app.captions.menu.fontFamily": "Kirjatüüp", "app.captions.menu.backgroundColor": "Taustavärv", "app.captions.menu.previewLabel": "Eelvaade", "app.captions.menu.cancelLabel": "Tühista", - "app.captions.hide": "Peida tiitrid", + "app.captions.hide": "Peida subtiitrid", "app.captions.ownership": "Võta üle", - "app.captions.ownershipTooltip": "Sind määratakse {0} tiitri omanikuks", + "app.captions.ownershipTooltip": "Sind määratakse {0} subtiitri omanikuks", "app.captions.dictationStart": "Alusta dikteerimist", "app.captions.dictationStop": "Lõpeta dikteerimine", "app.captions.dictationOnDesc": "Lülitab kõnetuvastuse sisse", @@ -87,22 +109,23 @@ "app.notes.pinnedNotification": "Jagatud märkmed on nüüd tahvlile kinnitatud.", "app.notes.label": "Märkmed", "app.notes.hide": "Peida märkmed", - "app.notes.locked": "Lukus", + "app.notes.locked": "Lukustatud", "app.notes.disabled": "Kinnitatud meediaalale", "app.notes.notesDropdown.covertAndUpload": "Teisenda märkmed esitluseks", "app.notes.notesDropdown.pinNotes": "Kinnita märkmed tahvlile", "app.notes.notesDropdown.unpinNotes": "Vabasta märkmed", "app.notes.notesDropdown.notesOptions": "Märkmete valikud", "app.pads.hint": "Kirjutusala tööriistariba aktiveerimiseks vajuta Esc-klahvi", - "app.user.activityCheck": "Kasutaja tegevuse kontroll", - "app.user.activityCheck.label": "Kontrolli kas kasutaja ({0}) on veel koosolekul", + "app.user.activityCheck": "Kasutaja aktiivsuse kontroll", + "app.user.activityCheck.label": "Kontrolli, kas kasutaja on veel koosolekul ({0})", "app.user.activityCheck.check": "Kontrolli", "app.userList.usersTitle": "Kasutajad", "app.userList.participantsTitle": "Osalejad", "app.userList.messagesTitle": "Sõnumid", "app.userList.notesTitle": "Märkmed", - "app.userList.notesListItem.unreadContent": "Jagatud märkmetes on loodud uut sisu", - "app.userList.captionsTitle": "Tiitrid", + "app.userList.notesListItem.unreadContent": "Jagatud märkmetes on uut sisu", + "app.userList.timerTitle": "Aeg", + "app.userList.captionsTitle": "Subtiitrid", "app.userList.presenter": "Esitleja", "app.userList.you": "Sina", "app.userList.locked": "Lukustatud", @@ -113,36 +136,40 @@ "app.userList.mobile": "Mobiil", "app.userList.guest": "Külaline", "app.userList.sharingWebcam": "Veebikaamera", - "app.userList.menuTitleContext": "Saadaval olevad valikud", + "app.userList.menuTitleContext": "Saadaolevad valikud", "app.userList.chatListItem.unreadSingular": "Üks uus sõnum", "app.userList.chatListItem.unreadPlural": "{0} uut sõnumit", + "app.userList.menu.away": "Määra ennast eemalolijaks", + "app.userList.menu.notAway": "Määra ennast aktiivseks", "app.userList.menu.chat.label": "Alusta privaatset vestlust", - "app.userList.menu.clearStatus.label": "Tühista staatus", + "app.userList.menu.clearStatus.label": "Kustuta olek", "app.userList.menu.removeUser.label": "Eemalda kasutaja", "app.userList.menu.removeConfirmation.label": "Eemalda kasutaja ({0})", - "app.userlist.menu.removeConfirmation.desc": "Takista kasutajal sessiooniga taasliituda.", + "app.userlist.menu.removeConfirmation.desc": "Tõkesta sellel kasutajal sessiooniga taasliitumine.", "app.userList.menu.muteUserAudio.label": "Vaigista kasutaja", "app.userList.menu.unmuteUserAudio.label": "Eemalda kasutaja vaigistus", "app.userList.menu.webcamPin.label": "Kinnita kasutaja veebikaamera", "app.userList.menu.webcamUnpin.label": "Vabasta kasutaja veebikaamera", - "app.userList.menu.giveWhiteboardAccess.label" : "Anna tahvli kasutusluba", - "app.userList.menu.removeWhiteboardAccess.label": "Eemalda tahvli kasutusluba", + "app.userList.menu.giveWhiteboardAccess.label" : "Anna juurdepääs tahvlile", + "app.userList.menu.removeWhiteboardAccess.label": "Sulge juurdepääs tahvlile", "app.userList.menu.ejectUserCameras.label": "Sulge kaamerad", - "app.userList.userAriaLabel": "{0} {1} {2} staatus {3}", + "app.userList.userAriaLabel": "{0} {1} {2} olek {3}", "app.userList.menu.promoteUser.label": "Ülenda moderaatoriks", "app.userList.menu.demoteUser.label": "Alanda vaatajaks", - "app.userList.menu.unlockUser.label": "Ava {0}", + "app.userList.menu.unlockUser.label": "Vabasta {0}", "app.userList.menu.lockUser.label": "Lukusta {0}", - "app.userList.menu.directoryLookup.label": "Otsi kataloogist", + "app.userList.menu.directoryLookup.label": "Otsing kataloogist", "app.userList.menu.makePresenter.label": "Muuda esitlejaks", "app.userList.userOptions.manageUsersLabel": "Halda kasutajaid", "app.userList.userOptions.muteAllLabel": "Vaigista kõik kasutajad", "app.userList.userOptions.muteAllDesc": "Vaigistab kõik kasutajad koosolekul", - "app.userList.userOptions.clearAllLabel": "Tühista kõik staatuse ikoonid", - "app.userList.userOptions.clearAllDesc": "Tühistab kõikide kasutajate staatuse ikoonid", + "app.userList.userOptions.clearAllLabel": "Kustuta kõik olekuikoonid", + "app.userList.userOptions.clearAllDesc": "Kustutab kõigi kasutajate olekuikoonid", + "app.userList.userOptions.clearAllReactionsLabel": "Kustuta kõik reaktsioonid", + "app.userList.userOptions.clearAllReactionsDesc": "Kustutab kõigi kasutajate reaktsiooni-emojid", "app.userList.userOptions.muteAllExceptPresenterLabel": "Vaigista kõik kasutajad peale esitleja", "app.userList.userOptions.muteAllExceptPresenterDesc": "Vaigistab kõik kasutajad koosolekul peale esitleja", - "app.userList.userOptions.unmuteAllLabel": "Lülita koosoleku vaigistamine välja", + "app.userList.userOptions.unmuteAllLabel": "Eemalda koosoleku vaigistus", "app.userList.userOptions.unmuteAllDesc": "Taastab koosoleku heli", "app.userList.userOptions.lockViewersLabel": "Lukusta vaatajad", "app.userList.userOptions.lockViewersDesc": "Lukustab koosolekul osalejate teatud funktsionaalsused", @@ -152,45 +179,54 @@ "app.userList.userOptions.disableMic": "Vaatajate mikrofonid on keelatud", "app.userList.userOptions.disablePrivChat": "Privaatne vestlus on keelatud", "app.userList.userOptions.disablePubChat": "Avalik vestlus on keelatud", - "app.userList.userOptions.disableNotes": "Jagatud märkmed on nüüd lukus", - "app.userList.userOptions.hideUserList": "Kasutajate nimekiri on vaatajate eest peidetud", - "app.userList.userOptions.webcamsOnlyForModerator": "Ainult moderaatorid näevad kasutajate veebikaameraid (lukustamisseadete tõttu)", - "app.userList.content.participants.options.clearedStatus": "Kõikide kasutajate staatused tühistati", + "app.userList.userOptions.disableNotes": "Jagatud märkmed on nüüd lukustatud", + "app.userList.userOptions.hideUserList": "Kasutajate nimekiri on nüüd vaatajate eest peidetud", + "app.userList.userOptions.webcamsOnlyForModerator": "Ainult moderaatorid näevad vaatajate veebikaameraid (lukustussätete tõttu)", + "app.userList.content.participants.options.clearedStatus": "Kõigi kasutajate olekud kustutatud", + "app.userList.content.participants.options.clearedReactions": "Kõigi kasutajate reaktsioonid kustutatud", "app.userList.userOptions.enableCam": "Vaatajate veebikaamerad on lubatud", "app.userList.userOptions.enableMic": "Vaatajate mikrofonid on lubatud", "app.userList.userOptions.enablePrivChat": "Privaatne vestlus on lubatud", "app.userList.userOptions.enablePubChat": "Avalik vestlus on lubatud", - "app.userList.userOptions.enableNotes": "Jagatud märkmed on nüüd lubatud", + "app.userList.userOptions.enableNotes": "Jagatud märkmed on nüüd avatud", "app.userList.userOptions.showUserList": "Kasutajate nimekiri on nüüd vaatajatele nähtav", - "app.userList.userOptions.enableOnlyModeratorWebcam": "Saad nüüd veebikaamera lubada ning kõik näevad seda", - "app.userList.userOptions.savedNames.title": "Kasutajate nimekiri koosolekul {0} kell {1}", + "app.userList.userOptions.enableOnlyModeratorWebcam": "Saad nüüd oma veebikaamera sisse lülitada, kõik näevad sind", + "app.userList.userOptions.savedNames.title": "Kasutajate nimekiri koosolekul {0} ajahetkel {1}", "app.userList.userOptions.sortedFirstName.heading": "Sorteeritult eesnime järgi:", "app.userList.userOptions.sortedLastName.heading": "Sorteeritult perekonnanime järgi:", "app.userList.userOptions.hideViewersCursor": "Vaatajate kursorid on lukustatud", - "app.userList.userOptions.showViewersCursor": "Vaatajate kursorid on vabastatud", + "app.userList.userOptions.showViewersCursor": "Vaatajate kursorid on vabad", "app.media.label": "Meedia", "app.media.autoplayAlertDesc": "Luba juurdepääs", "app.media.screenshare.start": "Ekraanijagamine algas", "app.media.screenshare.end": "Ekraanijagamine lõppes", - "app.media.screenshare.endDueToDataSaving": "Ekraanijagamine peatatud andmemahu kokkuhoiu tõttu", + "app.media.screenshare.endDueToDataSaving": "Ekraanijagamine peatatud andmesäästu tõttu", "app.media.screenshare.unavailable": "Ekraanijagamine pole saadaval", - "app.media.screenshare.notSupported": "Ekraanijagamine pole selles brauseris toetatud.", + "app.media.screenshare.notSupported": "See brauser ei toeta ekraanijagamist.", "app.media.screenshare.autoplayBlockedDesc": "Vajame sinu luba, et näidata sulle esitleja ekraani.", "app.media.screenshare.autoplayAllowLabel": "Vaata jagatud ekraani", + "app.media.cameraAsContent.start": "Esitluskaamera algas", + "app.media.cameraAsContent.end": "Esitluskaamera lõppes", + "app.media.cameraAsContent.endDueToDataSaving": "Esitluskaamera peatatud andmesäästu tõttu", + "app.media.cameraAsContent.autoplayBlockedDesc": "Vajame sinu luba, et näidata sulle esitleja kaamerat.", + "app.media.cameraAsContent.autoplayAllowLabel": "Vaata esitluskaamerat", "app.screenshare.presenterLoadingLabel": "Sinu ekraanijagamist laaditakse", - "app.screenshare.viewerLoadingLabel": "Esitleja ekraanijagamist laaditakse", + "app.screenshare.viewerLoadingLabel": "Esitleja ekraani laaditakse", "app.screenshare.presenterSharingLabel": "Sa jagad nüüd oma ekraani", "app.screenshare.screenshareFinalError": "Kood {0}. Ei saa ekraani jagada.", "app.screenshare.screenshareRetryError": "Kood {0}. Proovi uuesti ekraani jagada.", - "app.screenshare.screenshareRetryOtherEnvError": "Kood {0}. Ei saa ekraani jagada. Proovi mõne muu brauseri või seadmega.", - "app.screenshare.screenshareUnsupportedEnv": "Kood {0}. Brauserit ei toetata. Proovi mõne muu brauseri või seadmega.", - "app.screenshare.screensharePermissionError": "Kood {0}. Ekraani salvestamiseks on vaja luba.", + "app.screenshare.screenshareRetryOtherEnvError": "Kood {0}. Ei saa ekraani jagada. Proovi uuesti muu brauseri või seadmega.", + "app.screenshare.screenshareUnsupportedEnv": "Kood {0}. Brauserit ei toetata. Proovi uuesti muu brauseri või seadmega.", + "app.screenshare.screensharePermissionError": "Kood {0}. Ekraani hõivamiseks on vaja luba.", + "app.cameraAsContent.presenterLoadingLabel": "Sinu kaamerat laaditakse", + "app.cameraAsContent.viewerLoadingLabel": "Esitleja kaamerat laaditakse", + "app.cameraAsContent.presenterSharingLabel": "Sa näitad nüüd oma kaamerat", "app.meeting.ended": "Sessioon on lõppenud", - "app.meeting.meetingTimeRemaining": "Järelejäänud aeg: {0}", - "app.meeting.meetingTimeHasEnded": "Aeg sai läbi. Koosolek suletakse kohe", + "app.meeting.meetingTimeRemaining": "Koosoleku järelejäänud aeg: {0}", + "app.meeting.meetingTimeHasEnded": "Aeg sai läbi. Koosolek sulgub kohe", "app.meeting.endedByUserMessage": "{0} lõpetas sessiooni", - "app.meeting.endedByNoModeratorMessageSingular": "Koosolek lõppes, sest ükski moderaator pole ühe minuti jooksul kohal olnud", - "app.meeting.endedByNoModeratorMessagePlural": "Koosolek lõppes, sest ükski moderaator pole {0} minuti jooksul kohal olnud", + "app.meeting.endedByNoModeratorMessageSingular": "Koosolek lõppes, sest ühe minuti jooksul ei olnud ükski moderaator kohal", + "app.meeting.endedByNoModeratorMessagePlural": "Koosolek lõppes, sest {0} minuti jooksul ei olnud ükski moderaator kohal", "app.meeting.endedMessage": "Sind suunatakse tagasi avalehele", "app.meeting.alertMeetingEndsUnderMinutesSingular": "Koosolek sulgub ühe minuti pärast.", "app.meeting.alertMeetingEndsUnderMinutesPlural": "Koosolek sulgub {0} minuti pärast.", @@ -199,18 +235,19 @@ "app.presentation.hide": "Peida esitlus", "app.presentation.notificationLabel": "Aktiivne esitlus", "app.presentation.downloadLabel": "Laadi alla", - "app.presentation.slideContent": "Slaidi sisu", - "app.presentation.startSlideContent": "Slaidi sisu algus", - "app.presentation.endSlideContent": "Slaidi sisu lõpp", - "app.presentation.changedSlideContent": "Esitluses pööratud ette slaid {0}", + "app.presentation.actionsLabel": "Tegevused", + "app.presentation.slideContent": "Slaidisisu", + "app.presentation.startSlideContent": "Slaidisisu algus", + "app.presentation.endSlideContent": "Slaidisisu lõpp", + "app.presentation.changedSlideContent": "Esitluse slaid on nüüd {0}", "app.presentation.emptySlideContent": "Aktiivsel slaidil puudub sisu", "app.presentation.options.fullscreen": "Esitlus täisekraanil", "app.presentation.options.exitFullscreen": "Välju täisekraanilt", "app.presentation.options.minimize": "Minimeeri", "app.presentation.options.snapshot": "Praeguse slaidi hetktõmmis", "app.presentation.options.downloading": "Allalaadimine...", - "app.presentation.options.downloaded": "Aktiivne esitlus laaditi alla", - "app.presentation.options.downloadFailed": "Ei saa aktiivset esitlust alla laadida", + "app.presentation.options.downloaded": "Praegune slaid on alla laaditud", + "app.presentation.options.downloadFailed": "Ei saa praegust slaidi alla laadida", "app.presentation.presentationToolbar.noNextSlideDesc": "Esitluse lõpp", "app.presentation.presentationToolbar.noPrevSlideDesc": "Esitluse algus", "app.presentation.presentationToolbar.selectLabel": "Vali slaid", @@ -222,8 +259,8 @@ "app.presentation.presentationToolbar.skipSlideDesc": "Mine esitluses kindlale slaidile", "app.presentation.presentationToolbar.fitWidthLabel": "Kohanda laiusele", "app.presentation.presentationToolbar.fitWidthDesc": "Kuva slaid kogu laiuses", - "app.presentation.presentationToolbar.fitScreenLabel": "Täida ekraani ulatuses", - "app.presentation.presentationToolbar.fitScreenDesc": "Näita kogu slaidi", + "app.presentation.presentationToolbar.fitScreenLabel": "Kohanda ekraanile", + "app.presentation.presentationToolbar.fitScreenDesc": "Kuva kogu slaid", "app.presentation.presentationToolbar.zoomLabel": "Suurendus", "app.presentation.presentationToolbar.zoomDesc": "Muuda esitluse suurendust", "app.presentation.presentationToolbar.zoomInLabel": "Suurenda", @@ -233,95 +270,102 @@ "app.presentation.presentationToolbar.zoomReset": "Lähtesta suurendus", "app.presentation.presentationToolbar.zoomIndicator": "Praegune suurendusaste", "app.presentation.presentationToolbar.fitToWidth": "Kohanda laiusele", - "app.presentation.presentationToolbar.fitToPage": "Kohanda lehe laiusele", + "app.presentation.presentationToolbar.fitToPage": "Kohanda lehele", "app.presentation.presentationToolbar.goToSlide": "Slaid {0}", "app.presentation.presentationToolbar.hideToolsDesc": "Peida tööriistaribad", - "app.presentation.presentationToolbar.showToolsDesc": " Näita tööriistaribasid", + "app.presentation.presentationToolbar.showToolsDesc": "Näita tööriistaribasid", "app.presentation.placeholder": "Aktiivne esitlus puudub", "app.presentationUploder.title": "Esitlus", - "app.presentationUploder.message": "Esitlejana saad üles laadida igasuguseid Office'i dokumente ja PDF-faile. Parima tulemuse saamiseks soovitame PDFi. Veendu, et esitlus oleks valitud vasakul asuva märkeringi abil.", - "app.presentationUploader.exportHint": "Valides \"Saada vestlusesse\", saavad kasutajad avalikus vestluses allalaaditava lingi koos märkmetega.", + "app.presentationUploder.message": "Esitlejana saad üles laadida ükskõik millise Office'i dokumendi või PDF-faili. Parima tulemuse saamiseks soovitame PDF-faili. Veendu, et esitlus oleks valitud vasakul asuva märkeringiga.", + "app.presentationUploader.exportHint": "Valides \"Saada vestlusesse\", saavad kasutajad avalikus vestluses allalaaditava lingi koos märgetega.", "app.presentationUploader.exportToastHeader": "Vestlusesse saatmine ({0} fail)", "app.presentationUploader.exportToastHeaderPlural": "Vestlusesse saatmine ({0} faili)", "app.presentationUploader.exporting": "Vestlusesse saatmine", "app.presentationUploader.sending": "Saatmine...", "app.presentationUploader.collecting": "Slaidide väljaeraldamine: {0}/{1}...", - "app.presentationUploader.processing": "Slaidide märkmete lisamine: {0}/{1}...", + "app.presentationUploader.processing": "Slaididele märgete lisamine: {0}/{1}...", "app.presentationUploader.sent": "Saadetud", "app.presentationUploader.exportingTimeout": "Eksportimine võtab liiga kaua aega...", "app.presentationUploader.export": "Saada vestlusesse", + "app.presentationUploader.exportCurrentStatePresentation": "Saada välja link esitluse allalaadimiseks selle praeguses olekus", + "app.presentationUploader.enableOriginalPresentationDownload": "Luba originaalesitluse allalaadimine", + "app.presentationUploader.disableOriginalPresentationDownload": "Keela originaalesitluse allalaadimine", + "app.presentationUploader.dropdownExportOptions": "Ekspordi valikud", "app.presentationUploader.export.linkAvailable": "Faili {0} allalaadimise link on avalikus vestluses.", - "app.presentationUploader.export.notAccessibleWarning": "ei tarvitse vastata hõlbustusnõuetele", + "app.presentationUploader.export.downloadButtonAvailable": "Esitluse {0} allalaadimisnupp on saadaval.", + "app.presentationUploader.export.notAccessibleWarning": "ei tarvitse vastata ligipääsetavuse nõuetele", + "app.presentationUploader.export.originalLabel": "Originaal", + "app.presentationUploader.export.inCurrentStateLabel": "Praeguses olekus", "app.presentationUploader.currentPresentationLabel": "Aktiivne esitlus", - "app.presentationUploder.extraHint": "TÄHTIS: ükski fail ei tohi suuruselt ületada {0} MB ja {1} lehekülge.", + "app.presentationUploder.extraHint": "TÄHELEPANU: ükski fail ei tohi suuruselt ületada {0} MB ja {1} lehte.", "app.presentationUploder.uploadLabel": "Laadi üles", "app.presentationUploder.confirmLabel": "Kinnita", - "app.presentationUploder.confirmDesc": "Salvesta muudatused ning alusta esitlust", + "app.presentationUploder.confirmDesc": "Salvesta muudatused ja käivita esitlus", "app.presentationUploder.dismissLabel": "Tühista", - "app.presentationUploder.dismissDesc": "Sulge aken ning tühista muudatused", + "app.presentationUploder.dismissDesc": "Sulge aken ja hülga muudatused", "app.presentationUploder.dropzoneLabel": "Aseta üleslaaditavad failid siia", "app.presentationUploder.dropzoneImagesLabel": "Aseta üleslaaditavad pildid siia", - "app.presentationUploder.browseFilesLabel": "või otsi faile arvutist", + "app.presentationUploder.browseFilesLabel": "või sirvi faile", "app.presentationUploder.browseImagesLabel": "või vali/tee pilte", "app.presentationUploder.externalUploadTitle": "Sisu lisamine kolmanda osapoole rakendusest", "app.presentationUploder.externalUploadLabel": "Sirvi faile", - "app.presentationUploder.fileToUpload": "Ootab üleslaadimist...", + "app.presentationUploder.fileToUpload": "Valmis üleslaadimiseks...", "app.presentationUploder.currentBadge": "Aktiivne", - "app.presentationUploder.rejectedError": "Valitud fail(id) lükati tagasi. Palun kontrolli failitüüpi.", + "app.presentationUploder.rejectedError": "Valitud failid lükati tagasi. Palun kontrolli failitüüpi.", "app.presentationUploder.connectionClosedError": "Katkestatud halva ühenduvuse tõttu. Palun proovi uuesti.", "app.presentationUploder.upload.progress": "Üleslaadimine ({0}%)", - "app.presentationUploder.conversion.204": "Puudub hõivatav sisu", + "app.presentationUploder.conversion.204": "Pole sisu, mida jäädvustada", "app.presentationUploder.upload.413": "Fail on liiga suur, maksimaalne suurus on {0} MB.", - "app.presentationUploder.genericError": "Oih, miskit läks valesti...", + "app.presentationUploder.genericError": "Oih, midagi läks valesti...", "app.presentationUploder.upload.408": "Üleslaadimistõendi taotlemine aegus.", "app.presentationUploder.upload.404": "404: Kehtetu üleslaadimistõend.", "app.presentationUploder.upload.401": "Esitluse üleslaadimistõendi taotlemine ebaõnnestus.", - "app.presentationUploder.conversion.conversionProcessingSlides": "Töötlen lehte {0} / {1}", - "app.presentationUploder.conversion.genericConversionStatus": "Teisendan faili...", - "app.presentationUploder.conversion.generatingThumbnail": "Genereerin pisipilte...", - "app.presentationUploder.conversion.generatedSlides": "Slaidid genereeritud...", - "app.presentationUploder.conversion.generatingSvg": "Genereerin SVG-pilte ...", - "app.presentationUploder.conversion.pageCountExceeded": "Lehekülgede arv on liiga suur, maksimum on {0}", - "app.presentationUploder.conversion.invalidMimeType": "Tuvastati lubamatu vorming (laiend = {0}, sisutüüp={1})", + "app.presentationUploder.conversion.conversionProcessingSlides": "Lehe töötlemine {0} / {1}", + "app.presentationUploder.conversion.genericConversionStatus": "Faili teisendamine...", + "app.presentationUploder.conversion.generatingThumbnail": "Pisipiltide genereerimine...", + "app.presentationUploder.conversion.generatedSlides": "Slaidid loodud...", + "app.presentationUploder.conversion.generatingSvg": "SVG-piltide genereerimine...", + "app.presentationUploder.conversion.pageCountExceeded": "Lehtede arv on liiga suur, maksimum on {0} lehte", + "app.presentationUploder.conversion.invalidMimeType": "Tuvastati sobimatu vorming (laiend = {0}, sisu tüüp = {1})", "app.presentationUploder.conversion.conversionTimeout": "Slaidi {0} ei õnnestunud {1} katsega töödelda.", "app.presentationUploder.conversion.officeDocConversionInvalid": "Office'i dokumendi töötlemine ebaõnnestus. Palun laadi selle asemel üles PDF.", "app.presentationUploder.conversion.officeDocConversionFailed": "Office'i dokumendi töötlemine ebaõnnestus. Palun laadi selle asemel üles PDF.", - "app.presentationUploder.conversion.pdfHasBigPage": "Ei suutnud PDF-faili teisendada, püüa faili optimeerida. Maksimaalne leheküljesuurus on {0}.", + "app.presentationUploder.conversion.pdfHasBigPage": "Ei suutnud PDF-faili teisendada, palun proovi faili optimeerida. Maksimaalne lehesuurus on {0}.", "app.presentationUploder.conversion.timeout": "Oih, teisendamine võttis liiga kaua aega", - "app.presentationUploder.conversion.pageCountFailed": "Lehekülgede arvu määramine ebaõnnestus", - "app.presentationUploder.conversion.unsupportedDocument": "Faililaiendit ei toetata.", + "app.presentationUploder.conversion.pageCountFailed": "Lehtede arvu määramine ebaõnnestus", + "app.presentationUploder.conversion.unsupportedDocument": "Faililaiendit ei toetata", "app.presentationUploder.removePresentationLabel": "Eemalda esitlus", "app.presentationUploder.setAsCurrentPresentation": "Määra esitlus aktiivseks", "app.presentationUploder.tableHeading.filename": "Faili nimi", "app.presentationUploder.tableHeading.options": "Valikud", - "app.presentationUploder.tableHeading.status": "Staatus", + "app.presentationUploder.tableHeading.status": "Olek", "app.presentationUploder.uploading": "Üleslaadimine {0} {1}", - "app.presentationUploder.uploadStatus": "{0} ülelaadimist {1}-st lõpetatud", + "app.presentationUploder.uploadStatus": "{0} üleslaadimist {1}-st lõpetatud", "app.presentationUploder.completed": "{0} üleslaadimist lõpetatud", - "app.presentationUploder.item" : "element", - "app.presentationUploder.itemPlural" : "elementi", - "app.presentationUploder.clearErrors": "Kustuta vead", - "app.presentationUploder.clearErrorsDesc": "Kustutab esitluste ebaõnnestunud üleslaadimised", + "app.presentationUploder.item" : "objekt", + "app.presentationUploder.itemPlural" : "objekti", + "app.presentationUploder.clearErrors": "Tühjenda vead", + "app.presentationUploder.clearErrorsDesc": "Tühjendab esitluste ebaõnnestunud üleslaadimised", "app.presentationUploder.uploadViewTitle": "Laadi esitlus üles", - "app.poll.questionAndoptions.label" : "Näidatav küsimuse tekst.\nA. Küsitluse vastusevariant *\nB. Küsitluse vastusevariant (valikuline)\nC. Küsitluse vastusevariant (valikuline)\nD. Küsitluse vastusevariant (valikuline)\nE. Küsitluse vastusevariant (valikuline)", + "app.poll.questionAndoptions.label" : "Kuvatav küsimuse tekst.\nA. Vastus *\nB. Vastus (valikuline)\nC. Vastus (valikuline)\nD. Vastus (valikuline)\nE. Vastus (valikuline)", "app.poll.customInput.label": "Kohandatud sisend", - "app.poll.customInputInstructions.label": "Kohandatud sisend on lubatud – kirjuta küsimus ja valikuvariant(-did) antud vormingus või vali tekstifail samas vormingus.", - "app.poll.maxOptionsWarning.label": "Kasutada saab ainult esimest 5 varianti!", + "app.poll.customInputInstructions.label": "Kohandatud sisend on lubatud – kirjuta küsimus ja vastus(-ed) antud vormingus või vali tekstifail samas vormingus.", + "app.poll.maxOptionsWarning.label": "Kasutada saab ainult esimest 5 vastust!", "app.poll.pollPaneTitle": "Küsitlus", - "app.poll.enableMultipleResponseLabel": "Kas lubada mitu vastust vastaja kohta?", + "app.poll.enableMultipleResponseLabel": "Kas lubada mitut vastust vastaja kohta?", "app.poll.quickPollTitle": "Kiirküsitlus", "app.poll.hidePollDesc": "Peidab küsitluse paneeli", - "app.poll.quickPollInstruction": "Vali sobiv variant ja alusta küsitlust.", - "app.poll.activePollInstruction": "Jäta see paneel lahti, et jooksvalt näha osalejate vastuseid. Kui oled valmis, vajuta 'Avalda küsitluse tulemused', et näidata tulemusi osalejatele ning küsitlus lõpetada.", - "app.poll.dragDropPollInstruction": "Küsitluse vastusevariantide määramiseks lohista vastusevariante sisaldav tekstifail märgistatud väljale", - "app.poll.customPollTextArea": "Täida küsitluse vastusevariandid", + "app.poll.quickPollInstruction": "Küsitluse alustamiseks vali all sobiv variant.", + "app.poll.activePollInstruction": "Jäta see paneel lahti, et jooksvalt näha osalejate vastuseid. Kui oled valmis, siis vajuta tulemuste avaldamiseks ja küsitluse lõpetamiseks nuppu 'Avalda küsitluse tulemused'.", + "app.poll.dragDropPollInstruction": "Küsitluse vastuste määramiseks lohista vastuseid sisaldav tekstifail märgistatud väljale", + "app.poll.customPollTextArea": "Lisa küsitlusele vastused", "app.poll.publishLabel": "Avalda küsitlus", "app.poll.cancelPollLabel": "Tühista", "app.poll.backLabel": "Alusta küsitlust", "app.poll.closeLabel": "Sulge", "app.poll.waitingLabel": "Vastuste ootamine ({0}/{1})", - "app.poll.ariaInputCount": "Kohandatud küsitluse vastusevariant {0} / {1}", - "app.poll.customPlaceholder": "Lisa küsitluse vastusevariant", + "app.poll.ariaInputCount": "Kohandatud vastus {0} / {1}", + "app.poll.customPlaceholder": "Lisa vastus", "app.poll.noPresentationSelected": "Esitlust ei ole valitud! Palun vali esitlus.", "app.poll.clickHereToSelect": "Valimiseks klõpsa siin", "app.poll.question.label" : "Kirjuta küsimus...", @@ -330,14 +374,14 @@ "app.poll.responseTypes.label" : "Vastusetüübid", "app.poll.optionDelete.label" : "Kustuta", "app.poll.responseChoices.label" : "Vastusevariandid", - "app.poll.typedResponse.desc" : "Kasutajale esitatakse tekstiväli vastuse kirjutamiseks", - "app.poll.addItem.label" : "Lisa element", + "app.poll.typedResponse.desc" : "Kasutajale esitatakse tekstiväli vastuse kirjutamiseks.", + "app.poll.addItem.label" : "Lisa vastus", "app.poll.start.label" : "Alusta küsitlust", "app.poll.secretPoll.label" : "Anonüümne küsitlus", - "app.poll.secretPoll.isSecretLabel": "Küsitlus on anonüümne - üksikvastused ei ole nähtavad", - "app.poll.questionErr": "Küsimuse määramine on nõutav.", + "app.poll.secretPoll.isSecretLabel": "Küsitlus on anonüümne – üksikvastused ei ole nähtavad.", + "app.poll.questionErr": "Küsimuse sisestamine on nõutav.", "app.poll.optionErr": "Sisesta vastusevariant", - "app.poll.startPollDesc": "Alustab küsitlust.", + "app.poll.startPollDesc": "Alustab küsitlust", "app.poll.showRespDesc": "Kuvab vastuste seadistuse", "app.poll.addRespDesc": "Lisab vastuse sisestamise välja", "app.poll.deleteRespDesc": "Eemaldab vastusevariandi {0}", @@ -364,10 +408,10 @@ "app.poll.answer.e": "E", "app.poll.liveResult.usersTitle": "Kasutaja", "app.poll.liveResult.responsesTitle": "Vastus", - "app.poll.liveResult.secretLabel": "See on anonüümne küsitlus. Üksikvastused ei ole nähtavad", - "app.poll.removePollOpt": "Eemaldatud vastusevariant {0}", + "app.poll.liveResult.secretLabel": "See on anonüümne küsitlus. Üksikvastuseid ei näidata.", + "app.poll.removePollOpt": "Eemaldatud vastus {0}", "app.poll.emptyPollOpt": "Tühi", - "app.polling.pollingTitle": "Küsitluse valikud", + "app.polling.pollingTitle": "Küsitluse vastused", "app.polling.pollQuestionTitle": "Küsitluse küsimus", "app.polling.submitLabel": "Saada", "app.polling.submitAriaLabel": "Saada küsitluse vastus", @@ -376,35 +420,35 @@ "app.polling.responseNotSecret": "Tavaline küsitlus - esitleja näeb sinu vastust.", "app.polling.pollAnswerLabel": "Küsitluse vastus {0}", "app.polling.pollAnswerDesc": "Vali see variant, et hääletada {0} poolt", - "app.failedMessage": "Vabandame! Serveriga ühendumisel esineb tõrkeid.", + "app.failedMessage": "Vabandust! Serveriga ühenduse loomisel esineb probleeme.", "app.downloadPresentationButton.label": "Laadi alla esitluse originaal", "app.connectingMessage": "Ühendumine...", - "app.waitingMessage": "Ühendus katkes. Uus ühendumiskatse {0} sekundi pärast ...", - "app.retryNow": "Proovi uuesti kohe", - "app.muteWarning.label": "Klõpsa {0}, et vaigistus eemaldada", - "app.muteWarning.disableMessage": "Vaigistuse alarmid keelatud kuni vaigistuse eemaldamiseni", - "app.muteWarning.tooltip": "Klõpsa, et sulgeda ja keelata hoiatus kuni vaigistuse eemaldamiseni", + "app.waitingMessage": "Ühendus katkes. Proovin uuesti ühendust luua {0} sekundi pärast ...", + "app.retryNow": "Proovi kohe", + "app.muteWarning.label": "Klõpsa {0} vaigistuse eemaldamiseks", + "app.muteWarning.disableMessage": "Vaigistuse märguanded keelatud kuni vaigistuse eemaldamiseni", + "app.muteWarning.tooltip": "Klõpsa, et sulgeda ja keelata märguanded kuni järgmise vaigistuse eemaldamiseni", "app.navBar.settingsDropdown.optionsLabel": "Valikud", - "app.navBar.settingsDropdown.fullscreenLabel": "Täisekraanrakendus", - "app.navBar.settingsDropdown.settingsLabel": "Seaded", - "app.navBar.settingsDropdown.aboutLabel": "Meist", + "app.navBar.settingsDropdown.fullscreenLabel": "Rakendus täisekraanil", + "app.navBar.settingsDropdown.settingsLabel": "Sätted", + "app.navBar.settingsDropdown.aboutLabel": "Info", "app.navBar.settingsDropdown.leaveSessionLabel": "Lahku koosolekult", "app.navBar.settingsDropdown.exitFullscreenLabel": "Välju täisekraanilt", - "app.navBar.settingsDropdown.fullscreenDesc": "Esita seadete menüü täisekraanil", - "app.navBar.settingsDropdown.settingsDesc": "Muuda üldseadeid", + "app.navBar.settingsDropdown.fullscreenDesc": "Kuva sätete menüü täisekraanil", + "app.navBar.settingsDropdown.settingsDesc": "Muuda üldisi sätteid", "app.navBar.settingsDropdown.aboutDesc": "Näita informatsiooni kliendi kohta", "app.navBar.settingsDropdown.leaveSessionDesc": "Lahku koosolekult", - "app.navBar.settingsDropdown.exitFullscreenDesc": "Välju täisekraani vaatest", + "app.navBar.settingsDropdown.exitFullscreenDesc": "Välju täisekraani režiimist", "app.navBar.settingsDropdown.hotkeysLabel": "Kiirklahvid", - "app.navBar.settingsDropdown.hotkeysDesc": "Olemasolevad klahvikombinatsioonid", + "app.navBar.settingsDropdown.hotkeysDesc": "Olemasolevate kiirklahvide loetelu", "app.navBar.settingsDropdown.helpLabel": "Abi", "app.navBar.settingsDropdown.openAppLabel": "Ava BigBlueButtoni mobiilirakenduses", - "app.navBar.settingsDropdown.helpDesc": "Suunab kasutaja videojuhiste juurde (avaneb uuel sakil)", + "app.navBar.settingsDropdown.helpDesc": "Suunab kasutaja videojuhiste juurde (avab uue vahekaardi)", "app.navBar.settingsDropdown.endMeetingDesc": "Lõpetab käimasoleva koosoleku", "app.navBar.settingsDropdown.endMeetingLabel": "Lõpeta koosolek", "app.navBar.userListToggleBtnLabel": "Kasutajate nimekiri sisse/välja", "app.navBar.toggleUserList.ariaLabel": "Kasutajad ja sõnumid sisse/välja", - "app.navBar.toggleUserList.newMessages": "koos uue sõnumi teavitusega", + "app.navBar.toggleUserList.newMessages": "koos uute sõnumite teavitustega", "app.navBar.toggleUserList.newMsgAria": "Uus sõnum kasutajalt {0}", "app.navBar.recording": "Sessiooni salvestatakse", "app.navBar.recording.on": "Salvestatakse", @@ -418,48 +462,52 @@ "app.endMeeting.contentWarning": "Selle sessiooni vestluse sõnumid, jagatud märkmed, tahvli sisu ja jagatud dokumendid pole seejärel enam vahetult kättesaadavad.", "app.endMeeting.yesLabel": "Lõpeta sessioon kõigi kasutajate jaoks", "app.endMeeting.noLabel": "Ei", - "app.about.title": "Meist", - "app.about.version": "Kliendi versioon:", - "app.about.version_label": "BigBlueButtoni versioon:", - "app.about.copyright": "Autoriõigused:", - "app.about.confirmLabel": "Ok", - "app.about.confirmDesc": "Ok", + "app.about.title": "Info", + "app.about.version": "Kliendi versioon", + "app.about.version_label": "BigBlueButtoni versioon", + "app.about.copyright": "Autoriõigus", + "app.about.confirmLabel": "OK", + "app.about.confirmDesc": "OK", "app.about.dismissLabel": "Tühista", - "app.about.dismissDesc": "Sulge informatsioon kliendi kohta", + "app.about.dismissDesc": "Sulge info kliendi kohta", "app.mobileAppModal.title": "Ava BigBlueButtoni mobiilirakendus", - "app.mobileAppModal.description": "Kas su seadmesse on installitud BigBlueButtoni rakendus?", + "app.mobileAppModal.description": "Kas BigBlueButtoni rakendus on seadmesse paigaldatud?", "app.mobileAppModal.openApp": "Jah, ava rakendus", "app.mobileAppModal.obtainUrlMsg": "Koosoleku URLi hankimine", "app.mobileAppModal.obtainUrlErrorMsg": "Viga koosoleku URLi hankimisel", - "app.mobileAppModal.openStore": "Ei, ava rakendusepood ja laadi alla", + "app.mobileAppModal.openStore": "Ei, laadi alla rakendustepoest", "app.mobileAppModal.dismissLabel": "Tühista", "app.mobileAppModal.dismissDesc": "Sulge", - "app.mobileAppModal.userConnectedWithSameId": "Kasutaja {0} ühendus just sama IDga nagu sinul.", - "app.actionsBar.changeStatusLabel": "Muuda staatust", + "app.mobileAppModal.userConnectedWithSameId": "Kasutaja {0} ühendus just sama IDga nagu sina.", + "app.actionsBar.changeStatusLabel": "Muuda olekut", "app.actionsBar.muteLabel": "Vaigista", "app.actionsBar.unmuteLabel": "Eemalda vaigistus", "app.actionsBar.camOffLabel": "Lülita kaamera välja", "app.actionsBar.raiseLabel": "Tõsta käsi", - "app.actionsBar.label": "Tegevusteriba", + "app.actionsBar.label": "Tegevusriba", "app.actionsBar.actionsDropdown.restorePresentationLabel": "Taasta esitlus", - "app.actionsBar.actionsDropdown.restorePresentationDesc": "Nupp esitluse taastamiseks, kui see on minimeeritud.", + "app.actionsBar.actionsDropdown.restorePresentationDesc": "Nupp esitluse taasavamiseks pärast selle minimeerimist", "app.actionsBar.actionsDropdown.minimizePresentationLabel": "Minimeeri esitlus", - "app.actionsBar.actionsDropdown.minimizePresentationDesc": "Esitluse minimeerimise nupp", - "app.actionsBar.actionsDropdown.layoutModal": "Paigutusseadete aken", - "app.screenshare.screenShareLabel" : "Ekraanijagamine", + "app.actionsBar.actionsDropdown.minimizePresentationDesc": "Nupp esitluse minimeerimiseks", + "app.actionsBar.actionsDropdown.layoutModal": "Paigutuse sätted", + "app.actionsBar.actionsDropdown.shareCameraAsContent": "Jaga kaamerat sisuna", + "app.actionsBar.actionsDropdown.unshareCameraAsContent": "Peata kaamera jagamine sisuna", + "app.screenshare.screenShareLabel" : "Ekraani jagamine", + "app.cameraAsContent.cameraAsContentLabel" : "Esitluskaamera", "app.submenu.application.applicationSectionTitle": "Rakendus", "app.submenu.application.animationsLabel": "Animatsioonid", "app.submenu.application.audioFilterLabel": "Mikrofoni audiofiltrid", "app.submenu.application.wbToolbarsAutoHideLabel": "Peida tahvli tööriistaribad automaatselt", "app.submenu.application.darkThemeLabel": "Tume režiim", - "app.submenu.application.fontSizeControlLabel": "Teksti suurus", - "app.submenu.application.increaseFontBtnLabel": "Suurenda rakenduse teksti suurust", - "app.submenu.application.decreaseFontBtnLabel": "Vähenda rakenduste teksti suurust", + "app.submenu.application.fontSizeControlLabel": "Kirjasuurus", + "app.submenu.application.increaseFontBtnLabel": "Suurenda rakenduse kirjasuurust", + "app.submenu.application.decreaseFontBtnLabel": "Vähenda rakenduse kirjasuurust", "app.submenu.application.currentSize": "praegu {0}", "app.submenu.application.languageLabel": "Rakenduse keel", "app.submenu.application.languageOptionLabel": "Vali keel", - "app.submenu.application.noLocaleOptionLabel": "Aktiivseid tõlkeid ei ole", - "app.submenu.application.paginationEnabledLabel": "Videod lehtedena", + "app.submenu.application.noLocaleOptionLabel": "Tõlked puuduvad", + "app.submenu.application.paginationEnabledLabel": "Videod lehtedel", + "app.submenu.application.wakeLockEnabledLabel": "Unelukk", "app.submenu.application.layoutOptionLabel": "Paigutuse tüüp", "app.submenu.application.pushLayoutLabel": "Rakenda paigutus", "app.submenu.application.localeDropdown.af": "Afrikaani", @@ -525,13 +573,13 @@ "app.submenu.application.localeDropdown.zh-CN": "Lihtsustatud hiina (Hiina)", "app.submenu.application.localeDropdown.zh-TW": "Traditsiooniline hiina (Taiwan)", "app.submenu.notification.SectionTitle": "Teavitused", - "app.submenu.notification.Desc": "Määra, kuidas ja mille kohta teavitusi antakse.", - "app.submenu.notification.audioAlertLabel": "Audioteatised", - "app.submenu.notification.pushAlertLabel": "Hüpikteatised", + "app.submenu.notification.Desc": "Määra, kuidas ja millest teavitatakse.", + "app.submenu.notification.audioAlertLabel": "Helimärguanded", + "app.submenu.notification.pushAlertLabel": "Hüpikmärguanded", "app.submenu.notification.messagesLabel": "Sõnum vestluses", "app.submenu.notification.userJoinLabel": "Kasutaja liitus", "app.submenu.notification.userLeaveLabel": "Kasutaja lahkus", - "app.submenu.notification.guestWaitingLabel": "Külaline ootab nõusolekut", + "app.submenu.notification.guestWaitingLabel": "Külaline ootab heakskiitu", "app.submenu.audio.micSourceLabel": "Mikrofoni sisend", "app.submenu.audio.speakerSourceLabel": "Kõlarite sisend", "app.submenu.audio.streamVolumeLabel": "Audiovoo helitugevus", @@ -540,21 +588,21 @@ "app.submenu.video.videoOptionLabel": "Vali video sisend", "app.submenu.video.videoQualityLabel": "Video kvaliteet", "app.submenu.video.qualityOptionLabel": "Vali video kvaliteet", - "app.submenu.video.participantsCamLabel": "Näita osalejate veebikaameraid", + "app.submenu.video.participantsCamLabel": "Osalejate veebikaamerate vaatamine", "app.settings.applicationTab.label": "Rakendus", "app.settings.audioTab.label": "Audio", "app.settings.videoTab.label": "Video", "app.settings.usersTab.label": "Osalejad", - "app.settings.main.label": "Seaded", + "app.settings.main.label": "Sätted", "app.settings.main.cancel.label": "Tühista", - "app.settings.main.cancel.label.description": "Tühistab muudatused ning sulgeb seadete menüü", + "app.settings.main.cancel.label.description": "Tühistab muudatused ja sulgeb sätete menüü", "app.settings.main.save.label": "Salvesta", - "app.settings.main.save.label.description": "Salvestab muudatused ning sulgeb seadete menüü", - "app.settings.dataSavingTab.label": "Andmemahu säästmine", + "app.settings.main.save.label.description": "Salvestab muudatused ja sulgeb sätete menüü", + "app.settings.dataSavingTab.label": "Andmesääst", "app.settings.dataSavingTab.webcam": "Luba teiste osalejate veebikaamerad", "app.settings.dataSavingTab.screenShare": "Luba teiste osalejate töölaua jagamine", "app.settings.dataSavingTab.description": "Andmemahu säästmiseks võid muuta, mida ekraanil näidatakse.", - "app.settings.save-notification.label": "Seaded on salvestatud", + "app.settings.save-notification.label": "Sätted on salvestatud", "app.statusNotifier.lowerHands": "Langeta käed", "app.statusNotifier.lowerHandDescOneUser": "Langeta kasutaja {0} käsi", "app.statusNotifier.raisedHandsTitle": "Tõstetud käed", @@ -563,127 +611,131 @@ "app.statusNotifier.and": "ja", "app.switch.onLabel": "SEES", "app.switch.offLabel": "VÄLJAS", - "app.talkingIndicator.ariaMuteDesc" : "Vali vaigistamiseks kasutaja", + "app.talkingIndicator.ariaMuteDesc" : "Vali, et kasutaja vaigistada", "app.talkingIndicator.isTalking" : "{0} räägib", "app.talkingIndicator.moreThanMaxIndicatorsTalking" : "{0}+ räägivad", "app.talkingIndicator.moreThanMaxIndicatorsWereTalking" : "{0}+ rääkisid", "app.talkingIndicator.wasTalking" : "{0} lõpetas rääkimise", "app.actionsBar.actionsDropdown.actionsLabel": "Tegevused", - "app.actionsBar.actionsDropdown.presentationLabel": "Laadi üles/halda esitlusi", - "app.actionsBar.actionsDropdown.initPollLabel": "Alusta küsitlust", + "app.actionsBar.actionsDropdown.activateTimerStopwatchLabel": "Aktiveeri taimer/stopper", + "app.actionsBar.actionsDropdown.deactivateTimerStopwatchLabel": "Desaktiveeri taimer/stopper", + "app.actionsBar.actionsDropdown.presentationLabel": "Laadi üles / halda esitlusi", + "app.actionsBar.actionsDropdown.initPollLabel": "Loo küsitlus", "app.actionsBar.actionsDropdown.desktopShareLabel": "Jaga ekraani", - "app.actionsBar.actionsDropdown.stopDesktopShareLabel": "Lõpeta ekraanijagamine", + "app.actionsBar.actionsDropdown.stopDesktopShareLabel": "Lõpeta ekraani jagamine", "app.actionsBar.actionsDropdown.presentationDesc": "Laadi esitlus üles", - "app.actionsBar.actionsDropdown.initPollDesc": "Alusta küsitlust", - "app.actionsBar.actionsDropdown.desktopShareDesc": "Jaga ekraani teistega", - "app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Lõpeta ekraanijagamine", + "app.actionsBar.actionsDropdown.initPollDesc": "Loo küsitlus", + "app.actionsBar.actionsDropdown.desktopShareDesc": "Jaga oma ekraani teistega", + "app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Lõpeta oma ekraani jagamine", "app.actionsBar.actionsDropdown.pollBtnLabel": "Alusta küsitlust", - "app.actionsBar.actionsDropdown.pollBtnDesc": "Vaheta küsitluse vaadet", + "app.actionsBar.actionsDropdown.pollBtnDesc": "Lülitab küsitluse paneeli", "app.actionsBar.actionsDropdown.saveUserNames": "Salvesta kasutajate nimed", "app.actionsBar.actionsDropdown.createBreakoutRoom": "Loo eraldatud ruumid", "app.actionsBar.actionsDropdown.createBreakoutRoomDesc": "Loob koosolekul osalejate jaoks eraldatud ruumid", - "app.actionsBar.actionsDropdown.captionsLabel": "Tiitrite kirjutamine", - "app.actionsBar.actionsDropdown.captionsDesc": "Tiitrite paneel sisse/välja", + "app.actionsBar.actionsDropdown.captionsLabel": "Subtiitrite kirjutamine", + "app.actionsBar.actionsDropdown.captionsDesc": "Lülitab subtiitrite paneeli", "app.actionsBar.actionsDropdown.takePresenter": "Võta esitleja roll", "app.actionsBar.actionsDropdown.takePresenterDesc": "Määra ennast uueks esitlejaks", "app.actionsBar.actionsDropdown.selectRandUserLabel": "Vali juhuslik kasutaja", - "app.actionsBar.actionsDropdown.selectRandUserDesc": "Valib kasutaja juhuslikult kõigi vaatajate seast", - "app.actionsBar.actionsDropdown.propagateLayoutLabel": "Levita paigutus", - "app.actionsBar.emojiMenu.statusTriggerLabel": "Määra staatus", + "app.actionsBar.actionsDropdown.selectRandUserDesc": "Valib kõigi vaatajate seast juhuslikult ühe", + "app.actionsBar.reactions.reactionsButtonLabel": "Reaktsioonide riba", + "app.actionsBar.reactions.raiseHand": "Tõsta käsi", + "app.actionsBar.reactions.lowHand": "Langeta käsi", + "app.actionsBar.emojiMenu.statusTriggerLabel": "Määra olek", "app.actionsBar.emojiMenu.awayLabel": "Eemal", - "app.actionsBar.emojiMenu.awayDesc": "Määra oma staatuseks Eemal", + "app.actionsBar.emojiMenu.awayDesc": "Määra oma olekuks Eemal", "app.actionsBar.emojiMenu.raiseHandLabel": "Tõsta käsi", "app.actionsBar.emojiMenu.lowerHandLabel": "Langeta käsi", "app.actionsBar.emojiMenu.raiseHandDesc": "Tõsta käsi küsimuse esitamiseks", "app.actionsBar.emojiMenu.neutralLabel": "Kõhklev", - "app.actionsBar.emojiMenu.neutralDesc": "Määra oma saatuseks Kõhklev", + "app.actionsBar.emojiMenu.neutralDesc": "Määra oma olekuks Kõhklev", "app.actionsBar.emojiMenu.confusedLabel": "Segaduses", - "app.actionsBar.emojiMenu.confusedDesc": "Määra oma staatuseks Segaduses", - "app.actionsBar.emojiMenu.sadLabel": "Nukker", - "app.actionsBar.emojiMenu.sadDesc": "Määra oma staatuseks Nukker", + "app.actionsBar.emojiMenu.confusedDesc": "Määra oma olekuks Segaduses", + "app.actionsBar.emojiMenu.sadLabel": "Kurb", + "app.actionsBar.emojiMenu.sadDesc": "Määra oma olekuks Kurb", "app.actionsBar.emojiMenu.happyLabel": "Rõõmus", - "app.actionsBar.emojiMenu.happyDesc": "Määra oma staatuseks Rõõmus", - "app.actionsBar.emojiMenu.noneLabel": "Tühista staatus", - "app.actionsBar.emojiMenu.noneDesc": "Tühista oma staatus", + "app.actionsBar.emojiMenu.happyDesc": "Määra oma olekuks Rõõmus", + "app.actionsBar.emojiMenu.noneLabel": "Kustuta olek", + "app.actionsBar.emojiMenu.noneDesc": "Kustuta oma olek", "app.actionsBar.emojiMenu.applauseLabel": "Aplodeeri", - "app.actionsBar.emojiMenu.applauseDesc": "Määra oma staatuseks Aplodeeri", + "app.actionsBar.emojiMenu.applauseDesc": "Määra oma olekuks Aplodeeri", "app.actionsBar.emojiMenu.thumbsUpLabel": "Pöial püsti", - "app.actionsBar.emojiMenu.thumbsUpDesc": "Määra oma staatuseks Pöial püsti", + "app.actionsBar.emojiMenu.thumbsUpDesc": "Määra oma olekuks Pöial püsti", "app.actionsBar.emojiMenu.thumbsDownLabel": "Pöial alla", - "app.actionsBar.emojiMenu.thumbsDownDesc": "Määra oma staatuseks Pöial alla", - "app.actionsBar.currentStatusDesc": "praegune staatus {0}", - "app.actionsBar.captions.start": "Alusta tiitrite vaatamist", - "app.actionsBar.captions.stop": "Lõpeta tiitrite vaatamine", - "app.audioNotification.audioFailedError1001": "WebSocket kaotas ühenduse (viga 1001)", + "app.actionsBar.emojiMenu.thumbsDownDesc": "Määra oma olekuks Pöial alla", + "app.actionsBar.currentStatusDesc": "praegune olek {0}", + "app.actionsBar.captions.start": "Kuva subtiitrid", + "app.actionsBar.captions.stop": "Peida subtiitrid", + "app.audioNotification.audioFailedError1001": "WebSocketi ühendus katkenud (viga 1001)", "app.audioNotification.audioFailedError1002": "WebSocketi ühendust ei õnnestunud luua (viga 1002)", - "app.audioNotification.audioFailedError1003": "Veebilehitseja versiooni ei toetata (viga 1003)", - "app.audioNotification.audioFailedError1004": "Ühendumise viga (põhjus={0}) (viga 1004)", - "app.audioNotification.audioFailedError1005": "Ühendus lõppes ootamatult (viga 1005)", - "app.audioNotification.audioFailedError1006": "Ühendus aegus (viga 1006)", - "app.audioNotification.audioFailedError1007": "Ühendamise viga (ICE viga 1007)", - "app.audioNotification.audioFailedError1008": "Ülekanne ebaõnnestus (viga 1008)", - "app.audioNotification.audioFailedError1009": "Ei õnnestunud hankida STUN/TURN serveri informatsiooni (viga 1009)", + "app.audioNotification.audioFailedError1003": "Brauseri versiooni ei toetata (viga 1003)", + "app.audioNotification.audioFailedError1004": "Viga ühendumisel (põhjus={0}) (viga 1004)", + "app.audioNotification.audioFailedError1005": "Ühendumine lõppes ootamatult (viga 1005)", + "app.audioNotification.audioFailedError1006": "Ühendumine aegus (viga 1006)", + "app.audioNotification.audioFailedError1007": "Ühendumine ebaõnnestus (ICE viga 1007)", + "app.audioNotification.audioFailedError1008": "Edasiandmine ebaõnnestus (viga 1008)", + "app.audioNotification.audioFailedError1009": "Ei õnnestunud hankida STUN/TURN-serveri informatsiooni (viga 1009)", "app.audioNotification.audioFailedError1010": "Ühenduse kooskõlastamine aegus (ICE viga 1010)", "app.audioNotification.audioFailedError1011": "Ühendus aegus (ICE viga 1011)", "app.audioNotification.audioFailedError1012": "Ühendus suletud (ICE viga 1012)", - "app.audioNotification.audioFailedMessage": "Audio ühendamine ebaõnnestus", - "app.audioNotification.mediaFailedMessage": "getUserMicMedia ebaõnnestus, sest lubatud on ainult turvalised allikad", - "app.audioNotification.deviceChangeFailed": "Audioseadme muutmine ebaõnnestus. Kontrolli, kas valitud seade on õigesti seadistatud ja saadaval", + "app.audioNotification.audioFailedMessage": "Audioühenduse loomine ebaõnnestus", + "app.audioNotification.mediaFailedMessage": "getUserMicMedia ebaõnnestus, sest lubatud on ainult turvalised allikad.", + "app.audioNotification.deviceChangeFailed": "Audioseadme muutmine ebaõnnestus. Kontrolli, kas valitud seade on õigesti seadistatud ja saadaval.", "app.audioNotification.closeLabel": "Sulge", "app.audioNotificaion.reconnectingAsListenOnly": "Vaatajate mikrofonid on lukustatud, sind ühendatakse ainult kuulajana", "app.breakoutJoinConfirmation.title": "Liitu eraldatud ruumiga", - "app.breakoutJoinConfirmation.message": "Kas soovid liituda?", - "app.breakoutJoinConfirmation.confirmDesc": "Liitu eraldatud ruumiga", + "app.breakoutJoinConfirmation.message": "Kas soovid liituda ruumiga", + "app.breakoutJoinConfirmation.confirmDesc": "Ühendab sind eraldatud ruumiga", "app.breakoutJoinConfirmation.dismissLabel": "Tühista", - "app.breakoutJoinConfirmation.dismissDesc": "Sulgeb ja keelab eraldatud ruumiga liitumise", + "app.breakoutJoinConfirmation.dismissDesc": "Sulgeb ja lükkab tagasi eraldatud ruumiga liitumise", "app.breakoutJoinConfirmation.freeJoinMessage": "Vali eraldatud ruum, millega liituda", "app.breakoutTimeRemainingMessage": "Eraldatud ruumi järelejäänud aeg: {0}", - "app.breakoutWillCloseMessage": "Aeg sai läbi. Eraldatud ruum suletakse kohe", + "app.breakoutWillCloseMessage": "Aeg sai läbi. Eraldatud ruum sulgub kohe.", "app.breakout.dropdown.manageDuration": "Muuda kestust", "app.breakout.dropdown.destroyAll": "Lõpeta eraldatud ruumid", "app.breakout.dropdown.options": "Eraldatud ruumide valikud", "app.breakout.dropdown.manageUsers": "Halda kasutajaid", - "app.calculatingBreakoutTimeRemaining": "Arvutan järelejäänud aega...", + "app.calculatingBreakoutTimeRemaining": "Järelejäänud aja arvutamine...", "app.audioModal.ariaTitle": "Audioga liitumise aken", "app.audioModal.microphoneLabel": "Mikrofoniga", "app.audioModal.listenOnlyLabel": "Ainult kuulata", - "app.audioModal.microphoneDesc": "Audiosessiooniga ühinemine mikrofoniga", - "app.audioModal.listenOnlyDesc": "Audiosessiooniga ühinemine ainult kuulajana", + "app.audioModal.microphoneDesc": "Audiosessioonil osalemine mikrofoniga", + "app.audioModal.listenOnlyDesc": "Audiosessioonil osalemine ainult kuulajana", "app.audioModal.audioChoiceLabel": "Kuidas soovid audioga liituda?", "app.audioModal.iOSBrowser": "Audio/video ei ole toetatud", - "app.audioModal.iOSErrorDescription": "Praegu Chrome iOSis audiot ja videot ei toeta.", + "app.audioModal.iOSErrorDescription": "Chrome praegu iOSis audiot ja videot ei toeta.", "app.audioModal.iOSErrorRecommendation": "Soovitame iOSis kasutada Safarit.", "app.audioModal.audioChoiceDesc": "Vali, kuidas selle koosoleku audioga liituda", - "app.audioModal.unsupportedBrowserLabel": "Paistab, et kasutad veebilehitsejat, mida täielikult ei toetata. Täielikuks toetuseks kasuta palun brausereid {0} või {1}.", + "app.audioModal.unsupportedBrowserLabel": "Paistab, et kasutad brauserit, mida täielikult ei toetata. Täielikult on toetatud näiteks {0} või {1}.", "app.audioModal.closeLabel": "Sulge", "app.audioModal.yes": "Jah", "app.audioModal.no": "Ei", "app.audioModal.yes.arialabel" : "Heli on kuulda", "app.audioModal.no.arialabel" : "Heli ei ole kuulda", "app.audioModal.echoTestTitle": "See on privaatne helitest. Ütle mõned sõnad. Kas kuuled ennast rääkimas?", - "app.audioModal.settingsTitle": "Muuda audioseadeid", + "app.audioModal.settingsTitle": "Muuda audiosätteid", "app.audioModal.helpTitle": "Sinu meediaseadmetega tekkis probleem", - "app.audioModal.helpText": "Kas sa andsid mikrofonile juurdepääsuks loa? Kui püüad audioga liituda, siis peaks ilmuma dialoogiaken, kus küsitakse luba meediaseadme kasutamiseks. Palun anna luba, et audiosessiooniga ühineda. Kui on midagi muud, siis püüa muuta veebilehitseja seadetes mikrofoniga seotud õigusi.", - "app.audioModal.help.noSSL": "See leht pole turvaline. Mikrofoni lubamiseks peab leht olema turvalisel HTTPS-ühendusel. Võta palun ühendust serveri administraatoriga.", - "app.audioModal.help.macNotAllowed": "Paistab, et Maci süsteemiseaded blokeerivad mikrofonile juurdepääsu. Ava System Preferences > Security & Privacy > Privacy > Microphone ning veendu, et veebilehitseja, mida kasutad, on märgitud.", + "app.audioModal.helpText": "Kas andsid loa juurdepääsuks mikrofonile? Kui püüad liituda audioga, siis peaks ilmuma dialoogiaken, kus küsitakse luba meediaseadme kasutamiseks; audiosessiooniga liitumiseks palun nõustu sellega. Muul juhul proovi muuta brauseri sätetes mikrofoniga seotud õigusi.", + "app.audioModal.help.noSSL": "See leht pole turvaline. Mikrofonile juurdepääsu lubamiseks peab leht kasutama HTTPSi. Palun võta ühendust serveri administraatoriga.", + "app.audioModal.help.macNotAllowed": "Paistab, et Maci süsteemieelistused blokeerivad juurdepääsu mikrofonile. Ava System Preferences > Security & Privacy > Privacy > Microphone ja kontrolli, et brauser, mida kasutad, oleks märgitud.", "app.audioModal.audioDialTitle": "Liitu telefoni abil", "app.audioDial.audioDialDescription": "Vali", - "app.audioDial.audioDialConfrenceText": "ja sisesta sessiooni PIN-number:", - "app.audioModal.autoplayBlockedDesc": "Vajame audio mängimiseks sinu luba", - "app.audioModal.playAudio": "Mängi audiot", - "app.audioModal.playAudio.arialabel" : "Mängi audiot", + "app.audioDial.audioDialConfrenceText": "ja sisesta koosoleku PIN-kood:", + "app.audioModal.autoplayBlockedDesc": "Vajame heli esitamiseks sinu luba", + "app.audioModal.playAudio": "Mängi heli", + "app.audioModal.playAudio.arialabel" : "Mängi heli", "app.audioDial.tipIndicator": "Soovitus", "app.audioDial.tipMessage": "Vajuta oma telefonil klahvi '0', et end vaigistada või vaigistus eemaldada.", "app.audioModal.connecting": "Audioühenduse loomine", - "app.audioManager.joinedAudio": "Oled audiosessiooniga ühinenud", - "app.audioManager.joinedEcho": "Helitest käivitus", + "app.audioManager.joinedAudio": "Oled audiosessiooniga liitunud", + "app.audioManager.joinedEcho": "Oled helitestiga liitunud", "app.audioManager.leftAudio": "Oled audiosessioonist lahkunud", "app.audioManager.reconnectingAudio": "Üritame uuesti audioga ühenduda", - "app.audioManager.genericError": "Viga: Esines viga, palun proovi uuesti", + "app.audioManager.genericError": "Viga: Tekkis viga, palun proovi uuesti", "app.audioManager.connectionError": "Viga: Ühenduse viga", "app.audioManager.requestTimeout": "Viga: Päring aegus", "app.audioManager.invalidTarget": "Viga: Päring teostati valele objektile", - "app.audioManager.mediaError": "Viga: Ei saanud ühendust sinu meediaseadmetega", + "app.audioManager.mediaError": "Viga: Tekkis probleem sinu meediaseadmete kättesaamisega", "app.audio.joinAudio": "Liitu audioga", "app.audio.leaveAudio": "Lahku audiost", "app.audio.changeAudioDevice": "Muuda audioseadet", @@ -695,8 +747,8 @@ "app.audio.microphones": "Mikrofonid", "app.audio.speakers": "Kõlarid", "app.audio.noDeviceFound": "Seadmeid ei leitud", - "app.audio.audioSettings.titleLabel": "Vali oma audio seaded", - "app.audio.audioSettings.descriptionLabel": "Pane tähele, et veebilehitsejas avaneb dialoogiaken, kus palutakse luba sinu mikrofoni jagamiseks.", + "app.audio.audioSettings.titleLabel": "Vali oma audio sätted", + "app.audio.audioSettings.descriptionLabel": "Pane tähele, et brauseris avaneb dialoogiaken, kus palutakse nõustuda mikrofoni jagamisega.", "app.audio.audioSettings.microphoneSourceLabel": "Mikrofoni sisend", "app.audio.audioSettings.speakerSourceLabel": "Kõlarite sisend", "app.audio.audioSettings.testSpeakerLabel": "Testi kõlarit", @@ -709,15 +761,15 @@ "app.audio.listenOnly.backLabel": "Tagasi", "app.audio.listenOnly.closeLabel": "Sulge", "app.audio.permissionsOverlay.title": "Luba juurdepääs oma mikrofonile", - "app.audio.permissionsOverlay.hint": "Vajame sinu meediaseadmete kasutamiseks luba, et saaksime sind audiokonverentsiga ühendada :)", - "app.audio.captions.button.start": "Kuva tiitrid", - "app.audio.captions.button.stop": "Peata tiitrid", + "app.audio.permissionsOverlay.hint": "Vajame luba sinu meediaseadmete kasutamiseks, et saaksime sind audiosessiooniga ühendada :)", + "app.audio.captions.button.start": "Kuva subtiitrid", + "app.audio.captions.button.stop": "Peida subtiitrid", "app.audio.captions.button.language": "Keel", "app.audio.captions.button.transcription": "Transkriptsioon", - "app.audio.captions.button.transcriptionSettings": "Transkriptsiooni seaded", - "app.audio.captions.speech.title": "Automaatne transkribeerimine", + "app.audio.captions.button.transcriptionSettings": "Transkriptsiooni sätted", + "app.audio.captions.speech.title": "Automaattranskriptsioon", "app.audio.captions.speech.disabled": "Keelatud", - "app.audio.captions.speech.unsupported": "See brauser ei toeta kõnetuvastust. Audiot ei transkribeerita.", + "app.audio.captions.speech.unsupported": "See brauser ei toeta kõnetuvastust. Sinu audiot ei transkribeerita.", "app.audio.captions.select.de-DE": "Saksa", "app.audio.captions.select.en-US": "Inglise", "app.audio.captions.select.es-ES": "Hispaania", @@ -728,28 +780,28 @@ "app.audio.captions.select.pt-BR": "Portugali", "app.audio.captions.select.ru-RU": "Vene", "app.audio.captions.select.zh-CN": "Hiina", - "app.error.removed": "Oled sessioonilt eemaldatud", - "app.error.meeting.ended": "Oled sessioonist välja logitud", - "app.meeting.logout.duplicateUserEjectReason": "Olemasolev kasutaja püüab uuesti koosolekuga liituda", - "app.meeting.logout.permissionEjectReason": "Tagasi lükatud õiguste rikkumise pärast", + "app.error.removed": "Oled koosolekult eemaldatud", + "app.error.meeting.ended": "Oled koosolekult välja logitud", + "app.meeting.logout.duplicateUserEjectReason": "Juba kohal olev kasutaja püüab koosolekuga liituda", + "app.meeting.logout.permissionEjectReason": "Välja heidetud õiguste rikkumise pärast", "app.meeting.logout.ejectedFromMeeting": "Oled koosolekult eemaldatud", "app.meeting.logout.validateTokenFailedEjectReason": "Autoriseerimistõendi kehtivuse kinnitamine ebaõnnestus", "app.meeting.logout.userInactivityEjectReason": "Kasutaja on olnud liiga kaua mitteaktiivne", "app.meeting.logout.maxParticipantsReached": "Sellel koosolekul lubatud osalejate maksimaalarv on täis", - "app.meeting-ended.rating.legendLabel": "Tagasiside hinnang", + "app.meeting-ended.rating.legendLabel": "Tagasisidehinnang", "app.meeting-ended.rating.starLabel": "Täht", "app.modal.close": "Sulge", - "app.modal.close.description": "Tühista muudatused ning sulge aken", + "app.modal.close.description": "Tühistab muudatused ja sulgeb akna", "app.modal.confirm": "Valmis", - "app.modal.newTab": "(avaneb uuel sakil)", - "app.modal.confirm.description": "Salvestab muudatused ning sulgeb akna", - "app.modal.randomUser.noViewers.description": "Pole osalejaid, kelle hulgast juhuslikult valida", + "app.modal.newTab": "(avab uue vahekaardi)", + "app.modal.confirm.description": "Salvestab muudatused ja sulgeb akna", + "app.modal.randomUser.noViewers.description": "Pole vaatajaid, kelle hulgast juhuslikult valida", "app.modal.randomUser.selected.description": "Sind valiti juhuslikult", - "app.modal.randomUser.title": "Juhuslikult valitud kasutaja", + "app.modal.randomUser.title": "Juhuslikult valitud osaleja", "app.modal.randomUser.who": "Kes valitakse...?", - "app.modal.randomUser.alone": "On ainult üks osaleja", + "app.modal.randomUser.alone": "On ainult üks vaataja", "app.modal.randomUser.reselect.label": "Vali uuesti", - "app.modal.randomUser.ariaLabel.title": "Juhuslikult valitud kasutaja aken", + "app.modal.randomUser.ariaLabel.title": "Juhusliku osaleja valimise aken", "app.dropdown.close": "Sulge", "app.dropdown.list.item.activeLabel": "Aktiivne", "app.error.400": "Vigane päring", @@ -764,39 +816,39 @@ "app.error.disconnected.rejoin": "Uuesti liitumiseks võid lehte värskendada.", "app.error.userLoggedOut": "Kasutaja sessioonitõend on kehtetu väljalogimise tõttu", "app.error.ejectedUser": "Kasutaja sessioonitõend on kehtetu väljaheitmise tõttu", - "app.error.joinedAnotherWindow": "See sessioon paistab olevat avatud teises brauseriaknas.", + "app.error.joinedAnotherWindow": "See sessioon paistab olevat juba avatud teises brauseriaknas.", "app.error.userBanned": "Kasutaja on blokeeritud", - "app.error.leaveLabel": "Sisene uuesti", - "app.error.fallback.presentation.title": "Esines viga", + "app.error.leaveLabel": "Logi uuesti sisse", + "app.error.fallback.presentation.title": "Tekkis viga", "app.error.fallback.presentation.description": "Viga on registreeritud. Palun proovi leht uuesti laadida.", "app.error.fallback.presentation.reloadButton": "Laadi uuesti", - "app.guest.errorSeeConsole": "Viga: täpsemad üksikasjad konsoolil.", + "app.guest.errorSeeConsole": "Viga: rohkem üksikasju konsoolil.", "app.guest.noModeratorResponse": "Pole vastust moderaatorilt.", - "app.guest.noSessionToken": "Pole kätte saanud sessioonitõendit.", + "app.guest.noSessionToken": "Pole saadud sessioonitõendit.", "app.guest.windowTitle": "BigBlueButton - Külaliste ala", - "app.guest.missingToken": "Külalisel pole sessioonitõendit.", - "app.guest.missingSession": "Külalisel pole sessiooni.", + "app.guest.missingToken": "Külalisel puudub sessioonitõend.", + "app.guest.missingSession": "Külalisel puudub sessioon.", "app.guest.missingMeeting": "Koosolekut pole olemas.", "app.guest.meetingEnded": "Koosolek on lõppenud.", - "app.guest.guestWait": "Palun oota, kuni moderaator annab nõusoleku koosolekuga ühinemiseks.", - "app.guest.guestDeny": "Külalisel ei lubatud koosolekuga ühineda.", + "app.guest.guestWait": "Palun oota, kuni moderaator kiidab heaks sinu liitumise koosolekuga.", + "app.guest.guestDeny": "Külalisel ei lubatud koosolekuga liituda.", "app.guest.seatWait": "Külaline ootab kohta koosolekul.", - "app.guest.allow": "Külalisele nõusolek antud, suunamine koosolekule.", + "app.guest.allow": "Külalisele heakskiit antud, suunamine koosolekule.", "app.guest.firstPositionInWaitingQueue": "Oled järjekorras esimene!", "app.guest.positionInWaitingQueue": "Sinu koht ootejärjekorras:", "app.guest.guestInvalid": "Külaline on sobimatu", "app.guest.meetingForciblyEnded": "Ei saa liituda koosolekuga, mis on lõpetatud", - "app.userList.guest.waitingUsers": "Kasutajate ootamine", - "app.userList.guest.waitingUsersTitle": "Kasutajate haldus", - "app.userList.guest.optionTitle": "Vaata üle ootel olevad kasutajad", - "app.userList.guest.allowAllAuthenticated": "Luba kõik autenditud kasutajad", + "app.userList.guest.waitingUsers": "Ootavad kasutajad", + "app.userList.guest.waitingUsersTitle": "Kasutajate haldamine", + "app.userList.guest.optionTitle": "Vaata ootel olevaid kasutajaid", + "app.userList.guest.allowAllAuthenticated": "Luba kõik autenditud", "app.userList.guest.allowAllGuests": "Luba kõik külalised", - "app.userList.guest.allowEveryone": "Luba kõik kasutajad", - "app.userList.guest.denyEveryone": "Keela kõik kasutajad", + "app.userList.guest.allowEveryone": "Luba kõik", + "app.userList.guest.denyEveryone": "Keela kõik", "app.userList.guest.pendingUsers": "{0} ootel olevat kasutajat", "app.userList.guest.noPendingUsers": "Ootel kasutajaid ei ole...", - "app.userList.guest.pendingGuestUsers": "{0} ootel olevat külalist", - "app.userList.guest.pendingGuestAlert": "On liitunud sessiooniga ning ootab sinu nõusolekut.", + "app.userList.guest.pendingGuestUsers": "{0} külalist ootel", + "app.userList.guest.pendingGuestAlert": "On sessiooniga liitunud ja ootab sinu heakskiitu.", "app.userList.guest.rememberChoice": "Jäta valik meelde", "app.userList.guest.emptyMessage": "Sõnum praegu puudub", "app.userList.guest.inputPlaceholder": "Sõnum külaliste alale", @@ -806,45 +858,52 @@ "app.userList.guest.denyLabel": "Keela", "app.userList.guest.feedbackMessage": "Rakendatud tegevus:", "app.user-info.title": "Otsi kataloogist", - "app.toast.breakoutRoomEnded": "Eraldatud ruum aegus. Palun ühendu uuesti audioga.", + "app.toast.breakoutRoomEnded": "Eraldatud ruum on lõppenud. Palun liitu uuesti audioga.", "app.toast.chat.public": "Uus avalik sõnum", "app.toast.chat.private": "Uus privaatne sõnum", "app.toast.chat.system": "Süsteem", "app.toast.chat.poll": "Küsitluse tulemused", "app.toast.chat.pollClick": "Küsitluse tulemused avaldati. Vaatamiseks klõpsa siin.", - "app.toast.clearedEmoji.label": "Emoji-staatus tühistatud", - "app.toast.setEmoji.label": "Emoji-staatus määratud: {0}", + "app.toast.clearedEmoji.label": "Emoji-olek kustutatud", + "app.toast.setEmoji.label": "Emoji-olek seatud: {0}", "app.toast.meetingMuteOn.label": "Kõik kasutajad on vaigistatud", "app.toast.meetingMuteOnViewers.label": "Kõik vaatajad on vaigistatud", "app.toast.meetingMuteOff.label": "Koosoleku vaigistamine on välja lülitatud", + "app.toast.wakeLock.offerTitle": "Kas soovid, et su seadme ekraan jääks koosoleku ajal aktiivseks?", + "app.toast.wakeLock.offerAccept": "Jah!", + "app.toast.wakeLock.offerDecline": "Mitte praegu", + "app.toast.wakeLock.acquireSuccess": "Unelukk aktiivne! Saad selle sätete menüüs välja lülitada.", + "app.toast.wakeLock.acquireFailed": "Viga uneluku hankimisel.", "app.toast.setEmoji.raiseHand": "Sa tõstsid käe", "app.toast.setEmoji.lowerHand": "Sinu käsi langetati", - "app.toast.promotedLabel": "Sa oled ülendatud moderaatoriks", - "app.toast.demotedLabel": "Sa oled alandatud vaatajaks", - "app.notification.recordingStart": "Sessiooni salvestatakse", - "app.notification.recordingStop": "Sessiooni ei salvestata", - "app.notification.recordingPaused": "Sessiooni enam ei salvestata", + "app.toast.setEmoji.away": "Sa määrasid oma olekuks Eemal", + "app.toast.setEmoji.notAway": "Sa kustutasid endalt oleku Eemal", + "app.toast.promotedLabel": "Sind ülendati moderaatoriks", + "app.toast.demotedLabel": "Sind alandati vaatajaks", + "app.notification.recordingStart": "Sessiooni salvestamine algas", + "app.notification.recordingStop": "Sessiooni salvestamine lõppes", + "app.notification.recordingPaused": "Sessiooni salvestamine peatatud", "app.notification.recordingAriaLabel": "Salvestuse kestus", "app.notification.userJoinPushAlert": "{0} liitus sessiooniga", "app.notification.userLeavePushAlert": "{0} lahkus sessioonist", "app.submenu.notification.raiseHandLabel": "Tõsta käsi", "app.shortcut-help.title": "Kiirklahvid", - "app.shortcut-help.accessKeyNotAvailable": "Juurdepääsuklahvid pole saadaval", + "app.shortcut-help.accessKeyNotAvailable": "Juurdepääsuklahvid ei ole saadaval", "app.shortcut-help.comboLabel": "Klahv", "app.shortcut-help.alternativeLabel": "Alternatiiv", "app.shortcut-help.functionLabel": "Funktsioon", "app.shortcut-help.closeLabel": "Sulge", "app.shortcut-help.closeDesc": "Sulgeb kiirklahvide akna", "app.shortcut-help.openOptions": "Ava valikud", - "app.shortcut-help.toggleUserList": "Ava/peida kasutajate nimekiri", - "app.shortcut-help.toggleMute": "Vaigista / Eemalda vaigistus", - "app.shortcut-help.togglePublicChat": "Ava/peida avalik sõnumivahetus (kasutajate nimekiri peab olema avatud)", + "app.shortcut-help.toggleUserList": "Ava / Peida kasutajate nimekiri", + "app.shortcut-help.toggleMute": "Vaigistus sisse / välja", + "app.shortcut-help.togglePublicChat": "Ava / Peida avalik vestus (kasutajate nimekiri peab olema avatud)", "app.shortcut-help.hidePrivateChat": "Peida privaatne vestlus", "app.shortcut-help.closePrivateChat": "Sulge privaatne vestlus", - "app.shortcut-help.openActions": "Ava tegevustemenüü", - "app.shortcut-help.raiseHand": "Lülita käetõstmist", + "app.shortcut-help.openActions": "Ava tegevuste menüü", + "app.shortcut-help.raiseHand": "Tõsta / Langeta käsi", "app.shortcut-help.openDebugWindow": "Ava silumisaken", - "app.shortcut-help.openStatus": "Ava staatusemenüü", + "app.shortcut-help.openStatus": "Ava olekumenüü", "app.shortcut-help.togglePan": "Aktiveeri liigutamistööriist (Esitleja)", "app.shortcut-help.toggleFullscreen": "Lülita täisekraani (Esitleja)", "app.shortcut-help.nextSlideDesc": "Järgmine slaid (Esitleja)", @@ -855,13 +914,13 @@ "app.shortcut-help.previousSlideKey": "Nool vasakule", "app.shortcut-help.select": "Vali tööriist", "app.shortcut-help.pencil": "Pliiats", - "app.shortcut-help.eraser": "Kustutuskumm", + "app.shortcut-help.eraser": "Kustutaja", "app.shortcut-help.rectangle": "Ristkülik", "app.shortcut-help.elipse": "Ellips", "app.shortcut-help.triangle": "Kolmnurk", "app.shortcut-help.line": "Joon", "app.shortcut-help.arrow": "Nool", - "app.shortcut-help.text": "Tekstitööriist", + "app.shortcut-help.text": "Tekst", "app.shortcut-help.note": "Kleepmärge", "app.shortcut-help.general": "Üldine", "app.shortcut-help.presentation": "Esitlus", @@ -886,47 +945,49 @@ "app.shortcut-help.delete": "Kustuta", "app.shortcut-help.duplicate": "Dubleeri", "app.lock-viewers.title": "Lukusta vaatajad", - "app.lock-viewers.description": "Need valikud võimaldavad keelata vaatajatel teatud võimalusi kasutada.", - "app.lock-viewers.featuresLable": "Võimalus", - "app.lock-viewers.lockStatusLabel": "Staatus", - "app.lock-viewers.webcamLabel": "Jaga veebikaamerat", - "app.lock-viewers.otherViewersWebcamLabel": "Näita teiste vaatajate veebikaameraid", - "app.lock-viewers.microphoneLable": "Jaga mikrofoni", - "app.lock-viewers.PublicChatLabel": "Saada avalikke sõnumeid", - "app.lock-viewers.PrivateChatLable": "Saada privaatseid sõnumeid", - "app.lock-viewers.notesLabel": "Muuda jagatud märkmeid", - "app.lock-viewers.userListLabel": "Näita teisi vaatajaid kasutajate nimekirjas", - "app.lock-viewers.ariaTitle": "Vaatajate seadete lukustamise aken", - "app.lock-viewers.button.apply": "Kinnita", + "app.lock-viewers.description": "Need valikud võimaldavad keelata vaatajatel teatud funktsioone kasutada.", + "app.lock-viewers.featuresLable": "Funktsioon", + "app.lock-viewers.lockStatusLabel": "Olek", + "app.lock-viewers.webcamLabel": "Veebikaamera jagamine", + "app.lock-viewers.otherViewersWebcamLabel": "Teiste vaatajate veebikaamerate nägemine", + "app.lock-viewers.microphoneLable": "Mikrofoni jagamine", + "app.lock-viewers.PublicChatLabel": "Avalike sõnumite saatmine", + "app.lock-viewers.PrivateChatLable": "Privaatsete sõnumite saatmine", + "app.lock-viewers.notesLabel": "Jagatud märkmete muutmine", + "app.lock-viewers.userListLabel": "Kasutajate nimekirjas teiste vaatajate nägemine", + "app.lock-viewers.ariaTitle": "Vaatajate lukustamise aken", + "app.lock-viewers.button.apply": "Rakenda", "app.lock-viewers.button.cancel": "Tühista", "app.lock-viewers.locked": "Lukustatud", - "app.lock-viewers.hideViewersCursor": "Näita teiste vaatajate kursoreid", - "app.guest-policy.ariaTitle": "Külaliste reeglite seadistamise aken", + "app.lock-viewers.hideViewersCursor": "Teiste vaatajate kursorite nägemine", + "app.lock-viewers.hideAnnotationsLabel": "Teiste vaatajate märgete nägemine", + "app.guest-policy.ariaTitle": "Külaliste reeglite määramise aken", "app.guest-policy.title": "Külaliste reeglid", "app.guest-policy.description": "Muuda koosoleku külaliste reegleid", "app.guest-policy.button.askModerator": "Küsi moderaatorilt", "app.guest-policy.button.alwaysAccept": "Luba alati", "app.guest-policy.button.alwaysDeny": "Keela alati", "app.guest-policy.policyBtnDesc": "Määrab koosoleku külaliste reeglid", - "app.connection-status.ariaTitle": "Ühenduse staatuse aken", - "app.connection-status.title": "Ühenduse staatus", - "app.connection-status.description": "Vaata kasutajate ühenduse staatust", - "app.connection-status.empty": "Ühendusprobleemide teavitused praegu puuduvad", + "app.guest-policy.feedbackMessage": "Külaliste reeglid on nüüd:", + "app.connection-status.ariaTitle": "Ühenduse oleku aken", + "app.connection-status.title": "Ühenduse olek", + "app.connection-status.description": "Vaata kasutajate ühenduse olekut", + "app.connection-status.empty": "Ühendusprobleemidest praegu teatatud ei ole", "app.connection-status.more": "veel", "app.connection-status.copy": "Kopeeri statistika", "app.connection-status.copied": "Kopeeritud!", "app.connection-status.jitter": "Värin", - "app.connection-status.label": "Ühenduse staatus", - "app.connection-status.settings": "Seadete kohandamine", + "app.connection-status.label": "Ühenduse olek", + "app.connection-status.settings": "Sätete kohandamine", "app.connection-status.no": "Ei", "app.connection-status.notification": "Avastati sinu ühenduse kadumine", - "app.connection-status.offline": "väljas", + "app.connection-status.offline": "ühenduseta", "app.connection-status.clientNotRespondingWarning": "Klient ei vasta", "app.connection-status.audioUploadRate": "Audio üleslaadimise kiirus", "app.connection-status.audioDownloadRate": "Audio allalaadimise kiirus", "app.connection-status.videoUploadRate": "Video üleslaadimise kiirus", "app.connection-status.videoDownloadRate": "Video allalaadimise kiirus", - "app.connection-status.lostPackets": "Kadunud paketid", + "app.connection-status.lostPackets": "Kadunud pakette", "app.connection-status.usingTurn": "Kasutatakse TURNi", "app.connection-status.yes": "Jah", "app.connection-status.connectionStats": "Ühenduse statistika", @@ -940,8 +1001,8 @@ "app.recording.startTitle": "Alusta salvestamist", "app.recording.stopTitle": "Peata salvestamine", "app.recording.resumeTitle": "Jätka salvestamist", - "app.recording.startDescription": "Salvestamise nuppu uuesti vajutades saad salvestamise peatada.", - "app.recording.stopDescription": "Kas oled kindel, et soovid salvestamise peatada? Salvestamise nuppu uuesti vajutades saad salvestamist jätkata.", + "app.recording.startDescription": "Salvestamise saad peatada salvestamisnuppu uuesti vajutades.", + "app.recording.stopDescription": "Kas oled kindel, et soovid salvestamise peatada? Salvestamist saad jätkata salvestamisnuppu uuesti vajutades.", "app.recording.notify.title": "Salvestamine algas", "app.recording.notify.description": "Sessiooni ülejäänud osa kohta tekib salvestis", "app.recording.notify.continue": "Jätka", @@ -956,46 +1017,47 @@ "app.videoPreview.quality.hd": "Kõrglahutusega", "app.videoPreview.cancelLabel": "Tühista", "app.videoPreview.closeLabel": "Sulge", - "app.videoPreview.findingWebcamsLabel": "Otsime veebikaameraid", + "app.videoPreview.findingWebcamsLabel": "Veebikaamerate otsimine", "app.videoPreview.startSharingLabel": "Alusta jagamist", "app.videoPreview.stopSharingLabel": "Lõpeta jagamine", "app.videoPreview.stopSharingAllLabel": "Lõpeta kõik", "app.videoPreview.sharedCameraLabel": "Seda kaamerat juba jagatakse", "app.videoPreview.webcamOptionLabel": "Vali veebikaamera", "app.videoPreview.webcamPreviewLabel": "Veebikaamera eelvaade", - "app.videoPreview.webcamSettingsTitle": "Veebikaamera seaded", + "app.videoPreview.webcamSettingsTitle": "Veebikaamera sätted", "app.videoPreview.webcamEffectsTitle": "Veebikaamera visuaalefektid", - "app.videoPreview.webcamVirtualBackgroundLabel": "Virtuaalse tausta seaded", - "app.videoPreview.webcamVirtualBackgroundDisabledLabel": "See seade ei toeta virtuaalseid taustu", + "app.videoPreview.cameraAsContentSettingsTitle": "Esitluskaamera", + "app.videoPreview.webcamVirtualBackgroundLabel": "Virtuaalse tausta sätted", + "app.videoPreview.webcamVirtualBackgroundDisabledLabel": "See seade ei toeta virtuaalset tausta", "app.videoPreview.webcamNotFoundLabel": "Veebikaamerat ei leitud", - "app.videoPreview.profileNotFoundLabel": "Puudub toetatud kaameraprofiil", + "app.videoPreview.profileNotFoundLabel": "Toetatud kaameraprofiil puudub", "app.videoPreview.brightness": "Heledus", - "app.videoPreview.wholeImageBrightnessLabel": "Terve pilt", - "app.videoPreview.wholeImageBrightnessDesc": "Rakendab heledust kaamerapildile ja taustapildile", - "app.videoPreview.sliderDesc": "Suurenda või vähenda heldust", + "app.videoPreview.wholeImageBrightnessLabel": "Kogu pilt", + "app.videoPreview.wholeImageBrightnessDesc": "Rakendab heledust kaamera- ja taustapildile", + "app.videoPreview.sliderDesc": "Suurenda või vähenda heledust", "app.video.joinVideo": "Jaga veebikaamerat", "app.video.connecting": "Veebikaamera jagamine käivitub...", "app.video.leaveVideo": "Lõpeta veebikaamera jagamine", - "app.video.videoSettings": "Videoseaded", + "app.video.videoSettings": "Videosätted", "app.video.visualEffects": "Visuaalefektid", - "app.video.advancedVideo": "Ava täpsemad seaded", + "app.video.advancedVideo": "Ava täpsemad sätted", "app.video.iceCandidateError": "Viga ICE kandidaadi lisamisel", - "app.video.iceConnectionStateError": "Ühendus ebaõnnestus (ICE viga 1107)", + "app.video.iceConnectionStateError": "Ühenduse tõrge (ICE viga 1107)", "app.video.permissionError": "Viga veebikaamera jagamisel. Palun kontrolli õigusi", "app.video.sharingError": "Viga veebikaamera jagamisel", - "app.video.abortError": "Tekkis tundmatu viga, mis tõkestas veebikaamera kasutamise", - "app.video.overconstrainedError": "Kaamera ei toeta seda kvaliteediprofiili.", + "app.video.abortError": "Tekkis tundmatu viga, mis takistas veebikaamera kasutamist", + "app.video.overconstrainedError": "Kaamera ei toeta seda kvaliteediprofiili", "app.video.securityError": "Brauser keelas kaamera kasutamise. Proovi mõnda teist brauserit", "app.video.typeError": "Kaamera kvaliteediprofiil on sobimatu. Võta ühendust administraatoriga", - "app.video.notFoundError": "Veebikaamerat ei leitud. Palun kontrolli, kas see on ühendatud", - "app.video.notAllowed": "Veebikaamera jagamiseks puuduvad õigused. Palun kontrolli veebilehitsejas jagamisõigusi", - "app.video.notSupportedError": "Veebikaamera videot saab jagada vaid üle turvalise ühenduse. Veendu, et su SSL sertifikaat on kehtiv.", - "app.video.notReadableError": "Veebikaamera videot ei õnnestunud kätte saada. Palun kontrolli, et ükski teine programm veebikaamerat samal ajal ei kasuta", + "app.video.notFoundError": "Veebikaamerat ei leitud. Palun veendu, et see on ühendatud", + "app.video.notAllowed": "Veebikaamerale juurdepääsuks peab olema antud luba.", + "app.video.notSupportedError": "Brauserit ei toetata. Proovi uuesti muu brauseri või seadmega.", + "app.video.notReadableError": "Veebikaamera videot ei õnnestunud kätte saada. Palun veendu, et veebikaamerat ei kasuta mõni muu programm", "app.video.timeoutError": "Brauser ei vastanud õigeaegselt.", "app.video.genericError": "Seadmega tekkis tundmatu viga ({0})", - "app.video.inactiveError": "Su veebikaamera peatus ootamatult. Palun vaata üle brauseri õigused", + "app.video.inactiveError": "Su veebikaamera peatus ootamatult. Palun kontrolli brauseri õigusi", "app.video.mediaTimedOutError": "Veebikaamera edastusvoog on katkenud. Proovi seda uuesti jagada", - "app.video.mediaFlowTimeout1020": "Meedia ei saanud serveriga ühendust (viga 1020)", + "app.video.mediaFlowTimeout1020": "Meedia ei saanud ühendust serveriga (viga 1020)", "app.video.suggestWebcamLock": "Kas jõustada vaatajate veebikaamerate lukustamine?", "app.video.suggestWebcamLockReason": "(see parandab koosoleku stabiilsust)", "app.video.enable": "Luba", @@ -1005,7 +1067,7 @@ "app.video.videoLocked": "Veebikaamerate jagamine on lukustatud", "app.video.videoButtonDesc": "Jaga veebikaamerat", "app.video.videoMenu": "Videomenüü", - "app.video.videoMenuDisabled": "Veebikaamera videomenüü on seadetes välja lülitatud", + "app.video.videoMenuDisabled": "Veebikaamera videomenüü on sätetes välja lülitatud", "app.video.videoMenuDesc": "Ava videomenüü", "app.video.pagination.prevPage": "Vaata eelmisi videoid", "app.video.pagination.nextPage": "Vaata järgmisi videoid", @@ -1020,7 +1082,7 @@ "app.video.virtualBackground.custom": "Laadi üles arvutist", "app.video.virtualBackground.remove": "Eemalda lisatud pilt", "app.video.virtualBackground.genericError": "Kaameraefekti rakendamine ebaõnnestus. Proovi uuesti.", - "app.video.virtualBackground.camBgAriaDesc": "Määrab {0} veebikaamera virtuaalseks taustaks", + "app.video.virtualBackground.camBgAriaDesc": "Veebikaamera virtuaalseks taustaks määratakse {0}", "app.video.virtualBackground.maximumFileSizeExceeded": "Maksimaalne failisuurus ületatud. ({0}MB)", "app.video.virtualBackground.typeNotAllowed": "Failitüüp pole lubatud.", "app.video.virtualBackground.errorOnRead": "Faili lugemisel läks midagi valesti.", @@ -1031,24 +1093,24 @@ "app.video.meetingCamCapReached": "Koosoleku samaaegsete kaamerate piir on saavutatud", "app.video.dropZoneLabel": "Aseta siia", "app.fullscreenButton.label": "Laienda {0} täisekraanile", - "app.fullscreenUndoButton.label": "Loobu {0} täisekraanist", + "app.fullscreenUndoButton.label": "Lõpeta {0} täisekraan", "app.switchButton.expandLabel": "Laienda ekraanijagamise videot", - "app.switchButton.shrinkLabel": "Ahenda ekraanijagamise videot", + "app.switchButton.shrinkLabel": "Kahanda ekraanijagamise videot", "app.sfu.mediaServerConnectionError2000": "Meediaserveriga ei saa ühendust (viga 2000)", - "app.sfu.mediaServerOffline2001": "Meediaserver ei vasta. Palun proovi hiljem uuesti (viga 2001)", + "app.sfu.mediaServerOffline2001": "Meediaserver on võrgust väljas. Palun proovi hiljem uuesti (viga 2001)", "app.sfu.mediaServerNoResources2002": "Meediaserveril ei ole vabu ressursse (viga 2002)", "app.sfu.mediaServerRequestTimeout2003": "Meediaserveri päringud aeguvad (viga 2003)", "app.sfu.serverIceGatheringFailed2021": "Meediaserver ei saa koguda ühendusekandidaate (ICE viga 2021)", "app.sfu.serverIceGatheringFailed2022": "Meediaserveriga ühendumine ebaõnnestus (ICE viga 2022)", "app.sfu.mediaGenericError2200": "Meediaserver ei suutnud päringut töödelda (viga 2200)", "app.sfu.invalidSdp2202":"Klient genereeris vigase meediapäringu (SDP viga 2202)", - "app.sfu.noAvailableCodec2203": "Server ei leidnud sobivat koodekit (viga 2203)", - "app.meeting.endNotification.ok.label": "Ok", + "app.sfu.noAvailableCodec2203": "Server ei suutnud leida sobivat koodekit (viga 2203)", + "app.meeting.endNotification.ok.label": "OK", "app.whiteboard.annotations.poll": "Küsitluse tulemused avaldati", "app.whiteboard.annotations.pollResult": "Küsitluse tulemus", "app.whiteboard.annotations.noResponses": "Vastuseid pole", "app.whiteboard.annotations.notAllowed": "Sul ei ole lubatud seda muudatust teha", - "app.whiteboard.annotations.numberExceeded": "Märkmete arv ületas piirväärtuse ({0})", + "app.whiteboard.annotations.numberExceeded": "Märgete arv ületas lubatud piiri ({0})", "app.whiteboard.toolbar.tools": "Tööriistad", "app.whiteboard.toolbar.tools.hand": "Liiguta", "app.whiteboard.toolbar.tools.pencil": "Pliiats", @@ -1066,51 +1128,55 @@ "app.whiteboard.toolbar.color.red": "Punane", "app.whiteboard.toolbar.color.orange": "Oranž", "app.whiteboard.toolbar.color.eletricLime": "Kollakasroheline", - "app.whiteboard.toolbar.color.lime": "Heleroheline", + "app.whiteboard.toolbar.color.lime": "Roheline", "app.whiteboard.toolbar.color.cyan": "Tsüaansinine", "app.whiteboard.toolbar.color.dodgerBlue": "Dodgeri sinine", "app.whiteboard.toolbar.color.blue": "Sinine", "app.whiteboard.toolbar.color.violet": "Violetne", "app.whiteboard.toolbar.color.magenta": "Magenta", "app.whiteboard.toolbar.color.silver": "Hõbe", - "app.whiteboard.toolbar.undo": "Võta märge tagasi", - "app.whiteboard.toolbar.clear": "Kustuta kõik märkmed", - "app.whiteboard.toolbar.clearConfirmation": "Kas soovid kõik märkmed kustutada?", + "app.whiteboard.toolbar.undo": "Tühista märge", + "app.whiteboard.toolbar.clear": "Kustuta kõik märked", + "app.whiteboard.toolbar.clearConfirmation": "Kas soovid kõik märked kustutada?", "app.whiteboard.toolbar.multiUserOn": "Lülita mitme kasutaja tahvel sisse", "app.whiteboard.toolbar.multiUserOff": "Lülita mitme kasutaja tahvel välja", "app.whiteboard.toolbar.palmRejectionOn": "Lülita käelaba hülgamine sisse", "app.whiteboard.toolbar.palmRejectionOff": "Lülita käelaba hülgamine välja", - "app.whiteboard.toolbar.fontSize": "Tekstisuuruste nimikiri", + "app.whiteboard.toolbar.fontSize": "Kirjasuuruste loetelu", "app.whiteboard.toolbarAriaLabel": "Esitluse tööriistad", - "app.feedback.title": "Oled konverentsist välja logitud", - "app.feedback.subtitle": "Tahaksime teada, mida arvad BigBlueButtonist (vabatahtlik)", + "app.feedback.title": "Oled koosolekult välja logitud", + "app.feedback.subtitle": "Sooviksime teada, mida BigBlueButtonist arvad (vabatahtlik)", "app.feedback.textarea": "Kuidas saaksime BigBlueButtonit paremaks muuta?", - "app.feedback.sendFeedback": "Saada tagasisidet", + "app.feedback.sendFeedback": "Saada tagasiside", "app.feedback.sendFeedbackDesc": "Saada tagasiside ning lahku koosolekult", "app.videoDock.webcamMirrorLabel": "Peegelda", "app.videoDock.webcamMirrorDesc": "Peegelda valitud veebikaamerat", - "app.videoDock.webcamFocusLabel": "Anna fookus", - "app.videoDock.webcamFocusDesc": "Anna valitud veebikaamerale fookus", + "app.videoDock.webcamFocusLabel": "Sea fookusesse", + "app.videoDock.webcamFocusDesc": "Sea valitud veebikaamera fookusesse", "app.videoDock.webcamUnfocusLabel": "Lõpeta fookus", "app.videoDock.webcamUnfocusDesc": "Lõpeta valitud veebikaamera fookus", + "app.videoDock.webcamDisableLabel": "Keela enesevaade", + "app.videoDock.webcamDisableLabelAllCams": "Keela enesevaade (kõik kaamerad)", + "app.videoDock.webcamEnableLabel": "Luba enesevaade", + "app.videoDock.webcamDisableDesc": "Enesevaade keelatud", "app.videoDock.webcamPinLabel": "Kinnita", "app.videoDock.webcamPinDesc": "Kinnita valitud veebikaamera", "app.videoDock.webcamFullscreenLabel": "Veebikaamera täisekraanil", "app.videoDock.webcamSqueezedButtonLabel": "Veebikaamera valikud", "app.videoDock.webcamUnpinLabel": "Vabasta", - "app.videoDock.webcamUnpinLabelDisabled": "Kasutajat saab vabastada ainult moderaator", + "app.videoDock.webcamUnpinLabelDisabled": "Veebikaameraid saab vabastada ainult moderaator", "app.videoDock.webcamUnpinDesc": "Vabasta valitud veebikaamera", "app.videoDock.autoplayBlockedDesc": "Vajame sinu luba, et näidata sulle teiste kasutajate veebikaameraid.", "app.videoDock.autoplayAllowLabel": "Vaata veebikaameraid", "app.createBreakoutRoom.title": "Eraldatud ruumid", "app.createBreakoutRoom.ariaTitle": "Peida eraldatud ruumid", "app.createBreakoutRoom.breakoutRoomLabel": "Eraldatud ruumid {0}", - "app.createBreakoutRoom.askToJoin": "Palu liituda", - "app.createBreakoutRoom.generatingURL": "Lingi loomine", - "app.createBreakoutRoom.generatingURLMessage": "Ühinemise lingi loomine valitud eraldatud ruumi jaoks. See võib võtta paar sekundit...", + "app.createBreakoutRoom.askToJoin": "Liitu ruumiga", + "app.createBreakoutRoom.generatingURL": "URLi loomine", + "app.createBreakoutRoom.generatingURLMessage": "Liitumise URLi loomine valitud eraldatud ruumi jaoks. See võib võtta paar sekundit...", "app.createBreakoutRoom.duration": "Kestus {0}", "app.createBreakoutRoom.room": "Ruum {0}", - "app.createBreakoutRoom.notAssigned": "Pole määratud ({0})", + "app.createBreakoutRoom.notAssigned": "Jaotamata ({0})", "app.createBreakoutRoom.join": "Liitu ruumiga", "app.createBreakoutRoom.joinAudio": "Liitu audioga", "app.createBreakoutRoom.returnAudio": "Taasta audio", @@ -1123,37 +1189,39 @@ "app.createBreakoutRoom.randomlyAssignDesc": "Jaotab osalejad eraldatud ruumidesse juhuslikult", "app.createBreakoutRoom.resetAssignments": "Lähtesta jaotamine", "app.createBreakoutRoom.resetAssignmentsDesc": "Lähtesta kõik kasutajate ruumidesse jaotamised", - "app.createBreakoutRoom.endAllBreakouts": "Sulge kõik eraldatud ruumid", + "app.createBreakoutRoom.endAllBreakouts": "Lõpeta kõik eraldatud ruumid", "app.createBreakoutRoom.chatTitleMsgAllRooms": "kõik ruumid", "app.createBreakoutRoom.msgToBreakoutsSent": "Sõnum saadeti {0} eraldatud ruumi", "app.createBreakoutRoom.roomName": "{0} (Ruum - {1})", "app.createBreakoutRoom.doneLabel": "Valmis", "app.createBreakoutRoom.nextLabel": "Järgmine", - "app.createBreakoutRoom.minusRoomTime": "Vähenda eraldatud ruumi aega:", - "app.createBreakoutRoom.addRoomTime": "Suurenda eraldatud ruumi aega:", + "app.createBreakoutRoom.minusRoomTime": "Vähenda eraldatud ruumi aega kuni:", + "app.createBreakoutRoom.addRoomTime": "Suurenda eraldatud ruumi aega kuni:", "app.createBreakoutRoom.addParticipantLabel": "+ Lisa osaleja", "app.createBreakoutRoom.freeJoin": "Luba kasutajatel ise valida eraldatud ruum, millega liituda", - "app.createBreakoutRoom.captureNotes": "Jäädvusta jagatud märkmed pärast eraldatud ruumide lõppemist", - "app.createBreakoutRoom.captureSlides": "Jäädvusta tahvel pärast eraldatud ruumide lõppemist", - "app.createBreakoutRoom.leastOneWarnBreakout": "Eraldatud ruumi peab paigutama vähemalt ühe kasutaja.", + "app.createBreakoutRoom.manageRoomsLabel": "Ruumide haldamine", + "app.createBreakoutRoom.captureNotes": "Salvesta jagatud märkmed, kui eraldatud ruumid lõpevad", + "app.createBreakoutRoom.captureSlides": "Salvesta tahvel, kui eraldatud ruumid lõpevad", + "app.createBreakoutRoom.sendInvitationToMods": "Saada kutse määratud moderaatoritele", + "app.createBreakoutRoom.leastOneWarnBreakout": "Tuleb paigutada vähemalt üks kasutaja eraldatud ruumi.", "app.createBreakoutRoom.minimumDurationWarnBreakout": "Eraldatud ruumi vähim kestus on {0} minutit.", - "app.createBreakoutRoom.modalDesc": "Soovitus: kasutajate nimesid saab hiirega tirida soovitud eraldatud ruumidesse.", + "app.createBreakoutRoom.modalDesc": "Vihje: kasutajate nimesid saab hiirega lohistada soovitud eraldatud ruumidesse.", "app.createBreakoutRoom.roomTime": "{0} minutit", "app.createBreakoutRoom.numberOfRoomsError": "Ruumide arv ei sobi.", - "app.createBreakoutRoom.duplicatedRoomNameError": "Ruumi nimi ei tohi korduda.", + "app.createBreakoutRoom.duplicatedRoomNameError": "Ruumi nimi ei või korduda.", "app.createBreakoutRoom.emptyRoomNameError": "Ruumi nimi ei või olla tühi.", "app.createBreakoutRoom.setTimeInMinutes": "Määra kestuseks (minutit)", "app.createBreakoutRoom.setTimeLabel": "Rakenda", "app.createBreakoutRoom.setTimeCancel": "Tühista", "app.createBreakoutRoom.setTimeHigherThanMeetingTimeError": "Eraldatud ruumide kestus ei või ületada koosoleku järelejäänud aega.", - "app.createBreakoutRoom.roomNameInputDesc": "Värskendab eraldatud ruumi nime", + "app.createBreakoutRoom.roomNameInputDesc": "Uuendab eraldatud ruumi nime", "app.createBreakoutRoom.movedUserLabel": "{0} üle viidud ruumi {1}", - "app.updateBreakoutRoom.modalDesc": "Kasutaja värskendamiseks või kutsumiseks lohista ta lihtsalt soovitud ruumi.", + "app.updateBreakoutRoom.modalDesc": "Kasutaja muutmiseks või kutsumiseks lohista ta lihtsalt soovitud ruumi.", "app.updateBreakoutRoom.cancelLabel": "Tühista", - "app.updateBreakoutRoom.title": "Värskenda eraldatud ruume", + "app.updateBreakoutRoom.title": "Uuenda eraldatud ruume", "app.updateBreakoutRoom.confirm": "Rakenda", "app.updateBreakoutRoom.userChangeRoomNotification": "Sind viidi üle ruumi {0}.", - "app.smartMediaShare.externalVideo": "Väline video", + "app.smartMediaShare.externalVideo": "Välised videod", "app.update.resetRoom": "Lähtesta kasutaja ruum", "app.externalVideo.start": "Jaga uut videot", "app.externalVideo.title": "Jaga välist videot", @@ -1164,28 +1232,27 @@ "app.externalVideo.autoPlayWarning": "Käivita video, et meedia sünkroniseerimine aktiveerida", "app.externalVideo.refreshLabel": "Värskenda videomängijat", "app.externalVideo.fullscreenLabel": "Videomängija", - "app.externalVideo.noteLabel": "Märkus: jagatud väliseid videoid sessiooni salvestisse ei kaasata. Toetatatakse keskkondi YouTube, Vimeo, Instructure Media, Twitch ja Daily Motion ning linke meediafailidele (nt https://example.com/xy.mp4).", + "app.externalVideo.noteLabel": "Märkus: jagatud väliseid videoid sessiooni salvestisse ei kaasata. Toetatakse keskkondi YouTube, Vimeo, Instructure Media, Twitch ja Dailymotion ning meediafailide URL-e (nt https://example.com/xy.mp4).", "app.externalVideo.subtitlesOn": "Lülita välja", "app.externalVideo.subtitlesOff": "Lülita sisse (kui saadaval)", "app.actionsBar.actionsDropdown.shareExternalVideo": "Jaga välist videot", "app.actionsBar.actionsDropdown.stopShareExternalVideo": "Lõpeta välise video jagamine", - "app.legacy.unsupportedBrowser": "Paistab, et kasutad veebilehitsejat, mida ei toetata. Täielikuks toetuseks kasuta palun brauserit {0} või {1}.", - "app.legacy.upgradeBrowser": "Paistab, et kasutad toetatud veebilehitseja vanemat versiooni. Täielikuks toetuseks uuenda palun oma veebilehitsejat.", - "app.legacy.criosBrowser": "Täielikuks toetuseks kasuta palun iOSis Safarit.", + "app.legacy.unsupportedBrowser": "Paistab, et kasutad brauserit, mida ei toetata. Täielikult on toetatud näiteks {0} või {1}.", + "app.legacy.upgradeBrowser": "Paistab, et kasutad toetatud brauseri vanemat versiooni. Täielikuks toetuseks palun uuenda oma brauserit.", + "app.legacy.criosBrowser": "Täielikuks toetuseks kasuta iOSis palun Safarit.", "app.debugWindow.windowTitle": "Otsi vigu", - "app.debugWindow.form.userAgentLabel": "Kasutajaagent", + "app.debugWindow.form.userAgentLabel": "Kasutaja identifikaator", "app.debugWindow.form.button.copy": "Kopeeri", - "app.debugWindow.form.enableAutoarrangeLayoutLabel": "Lülita automaatpaigutamine sisse", + "app.debugWindow.form.enableAutoarrangeLayoutLabel": "Lülita sisse automaatne paigutamine", "app.debugWindow.form.enableAutoarrangeLayoutDescription": "(lülitub välja, kui veebikaamerate ala nihutada või selle suurust muuta)", - "app.debugWindow.form.chatLoggerLabel": "Vestluse logimistasemete test", + "app.debugWindow.form.chatLoggerLabel": "Testi vestluse logimistasemeid", "app.debugWindow.form.button.apply": "Rakenda", "app.layout.modal.title": "Paigutused", - "app.layout.modal.confirm": "Kinnita", - "app.layout.modal.cancel": "Tühista", + "app.layout.modal.update": "Uuenda", + "app.layout.modal.updateAll": "Uuenda kõik", "app.layout.modal.layoutLabel": "Vali paigutus", - "app.layout.modal.keepPushingLayoutLabel": "Rakenda paigutus kõigile", - "app.layout.modal.pushLayoutLabel": "Rakenda kõigile", - "app.layout.modal.layoutToastLabel": "Paigutuse seaded muudetud", + "app.layout.modal.pushLayoutLabel": "Rakenda kõigile kasutajatele", + "app.layout.modal.layoutToastLabel": "Paigutuse sätted muudetud", "app.layout.modal.layoutSingular": "Paigutus", "app.layout.modal.layoutBtnDesc": "Määrab paigutuse valitud valikuks", "app.layout.style.custom": "Kohandatud", @@ -1202,20 +1269,20 @@ "playback.button.fullscreen.aria": "Sisu täisekraanil", "playback.button.restore.aria": "Taasta sisu", "playback.button.search.aria": "Otsi", - "playback.button.section.aria": "Külgsektsioon", + "playback.button.section.aria": "Külgpaneel", "playback.button.swap.aria": "Vaheta sisu", - "playback.button.theme.aria": "Lülita teemat", + "playback.button.theme.aria": "Vaheta teemat", "playback.error.wrapper.aria": "Vigade ala", "playback.loader.wrapper.aria": "Laadija ala", "playback.player.wrapper.aria": "Mängija ala", "playback.player.about.modal.shortcuts.title": "Kiirklahvid", "playback.player.about.modal.shortcuts.alt": "Alt", "playback.player.about.modal.shortcuts.shift": "Shift", - "playback.player.about.modal.shortcuts.fullscreen": "Lülita täisekraaniks", + "playback.player.about.modal.shortcuts.fullscreen": "Lülita täisekraani", "playback.player.about.modal.shortcuts.play": "Esita/Peata", - "playback.player.about.modal.shortcuts.section": "Lülita külgsektsiooniks", - "playback.player.about.modal.shortcuts.seek.backward": "Otsi tagasisuunas", - "playback.player.about.modal.shortcuts.seek.forward": "Otsi edasisuunas", + "playback.player.about.modal.shortcuts.section": "Lülita külgpaneeli", + "playback.player.about.modal.shortcuts.seek.backward": "Keri tagasi", + "playback.player.about.modal.shortcuts.seek.forward": "Keri edasi", "playback.player.about.modal.shortcuts.skip.next": "Järgmine slaid", "playback.player.about.modal.shortcuts.skip.previous": "Eelmine slaid", "playback.player.about.modal.shortcuts.swap": "Vaheta sisu", @@ -1253,8 +1320,8 @@ "app.learningDashboard.indicators.activityScore": "Aktiivsusskoor", "app.learningDashboard.indicators.duration": "Kestus", "app.learningDashboard.userDetails.startTime": "Algusaeg", - "app.learningDashboard.userDetails.endTime": "Lõppaeg", - "app.learningDashboard.userDetails.joined": "Liitunud", + "app.learningDashboard.userDetails.endTime": "Lõpuaeg", + "app.learningDashboard.userDetails.joined": "Liitus", "app.learningDashboard.userDetails.category": "Kategooria", "app.learningDashboard.userDetails.average": "Keskmine", "app.learningDashboard.userDetails.activityPoints": "Aktiivsuspunktid", @@ -1262,7 +1329,7 @@ "app.learningDashboard.userDetails.response": "Vastus", "app.learningDashboard.userDetails.mostCommonAnswer": "Kõige sagedasem vastus", "app.learningDashboard.userDetails.anonymousAnswer": "Anonüümne küsitlus", - "app.learningDashboard.userDetails.talkTime": "Rääkimisaeg", + "app.learningDashboard.userDetails.talkTime": "Rääkimise aeg", "app.learningDashboard.userDetails.messages": "Sõnumid", "app.learningDashboard.userDetails.emojis": "Emojid", "app.learningDashboard.userDetails.raiseHands": "Tõstetud käed", @@ -1270,13 +1337,13 @@ "app.learningDashboard.userDetails.onlineIndicator": "{0} kohaloldud aeg", "app.learningDashboard.usersTable.title": "Ülevaade", "app.learningDashboard.usersTable.colOnline": "Kohaloldud aeg", - "app.learningDashboard.usersTable.colTalk": "Rääkimisaeg", + "app.learningDashboard.usersTable.colTalk": "Rääkimise aeg", "app.learningDashboard.usersTable.colWebcam": "Veebikaamera aeg", "app.learningDashboard.usersTable.colMessages": "Sõnumid", "app.learningDashboard.usersTable.colEmojis": "Emojid", "app.learningDashboard.usersTable.colRaiseHands": "Tõstetud käed", "app.learningDashboard.usersTable.colActivityScore": "Aktiivsusskoor", - "app.learningDashboard.usersTable.colStatus": "Staatus", + "app.learningDashboard.usersTable.colStatus": "Olek", "app.learningDashboard.usersTable.userStatusOnline": "Kohal", "app.learningDashboard.usersTable.userStatusOffline": "Väljas", "app.learningDashboard.usersTable.noUsers": "Pole veel kasutajaid", @@ -1290,7 +1357,7 @@ "app.learningDashboard.pollsTable.anonymousAnswer": "Anonüümne küsitlus (vastused viimases reas)", "app.learningDashboard.pollsTable.anonymousRowName": "Anonüümne", "app.learningDashboard.pollsTable.noPollsCreatedHeading": "Küsitlusi ei ole loodud", - "app.learningDashboard.pollsTable.noPollsCreatedMessage": "Tulemused ilmuvad sellesse loendisse niipea, kui küsitlus kasutajatele saadetakse.", + "app.learningDashboard.pollsTable.noPollsCreatedMessage": "Tulemused ilmuvad siia niipea, kui küsitlus kasutajatele saadetakse.", "app.learningDashboard.pollsTable.answerTotal": "Kokku", "app.learningDashboard.pollsTable.userLabel": "Kasutaja", "app.learningDashboard.statusTimelineTable.title": "Ajaskaala", @@ -1310,5 +1377,5 @@ "mobileApp.portals.drawerNavigation.button.label": "Portaalid", "mobileApp.portals.addPortalPopup.validation.emptyFields": "Nõutavad väljad", "mobileApp.portals.addPortalPopup.validation.portalNameAlreadyExists": "Nimi on juba kasutusel", - "mobileApp.portals.addPortalPopup.validation.urlInvalid": "Viga lehekülje laadimisel - kontrolli aadressi ja võrguühendust" + "mobileApp.portals.addPortalPopup.validation.urlInvalid": "Viga lehe laadimisel - kontrolli URLi ja võrguühendust" } From 6fc8dde90ccf93f22e739593f08c8a49cc4d184a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Wed, 16 Aug 2023 10:15:11 -0300 Subject: [PATCH 209/252] fix online styles in chat --- .../page/chat-message/message-header/component.tsx | 2 +- .../page/chat-message/message-header/styles.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/component.tsx index c7e4cbeddb..de17dc54a6 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/component.tsx +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/component.tsx @@ -43,7 +43,7 @@ const ChatMessageHeader: React.FC = ({ {name.toLowerCase().slice(0, 2) || " "} - + {name} { diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/styles.ts b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/styles.ts index 3a86e12e65..37621c6955 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/styles.ts +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/styles.ts @@ -13,13 +13,17 @@ import { } from '/imports/ui/stylesheets/styled-components/palette'; import { lineHeightComputed } from '/imports/ui/stylesheets/styled-components/typography'; +interface ChatUserNameProps { + isOnline: boolean; +} + export const HeaderContent = styled.div` display: flex; flex-flow: row; width: 100%; `; -export const ChatUserName = styled.div` +export const ChatUserName = styled.div` display: flex; min-width: 0; font-weight: 600; From e7be097e01df8589db91f1b0ba7be70d779b5fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Wed, 16 Aug 2023 11:35:17 -0300 Subject: [PATCH 210/252] fix chat position --- .../page/chat-message/componet.tsx | 17 ++- .../message-content/text-content/styles.ts | 1 - .../chat-message/message-header/component.tsx | 13 -- .../chat-message/message-header/styles.ts | 94 +------------- .../page/chat-message/styles.ts | 116 +++++++++++++++++- 5 files changed, 126 insertions(+), 115 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/componet.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/componet.tsx index dc6b2e6e84..20b8b73cdf 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/componet.tsx +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/componet.tsx @@ -3,6 +3,7 @@ import { Message } from '/imports/ui/Types/message'; import { ChatWrapper, ChatContent, + ChatAvatar, } from "./styles"; import ChatMessageHeader from "./message-header/component"; import ChatMessageTextContent from "./message-content/text-content/component"; @@ -148,16 +149,24 @@ const ChatMesssage: React.FC = ({ sameSender={sameSender} ref={messageRef} > + {(!message?.user || !sameSender) + && ( + + {messageContent.name.toLowerCase().slice(0, 2) || " "} + + ) + } + - { messageContent.component } diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/styles.ts b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/styles.ts index 5ac698cade..671f677788 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/styles.ts +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/styles.ts @@ -7,7 +7,6 @@ export const ChatMessage = styled.div` flex-flow: row; color: ${colorText}; word-break: break-word; - margin-left: 2.75rem; ${({ emphasizedMessage }) => emphasizedMessage && ` diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/component.tsx index de17dc54a6..9d36800736 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/component.tsx +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/component.tsx @@ -13,9 +13,6 @@ const intlMessages = defineMessages({ interface ChatMessageHeaderProps { name: string; - avatar: string; - color: string; - isModerator: boolean; isOnline: boolean; dateTime: Date; sameSender: boolean; @@ -24,9 +21,6 @@ interface ChatMessageHeaderProps { const ChatMessageHeader: React.FC = ({ sameSender, name, - color, - isModerator, - avatar, isOnline, dateTime, }) => { @@ -35,13 +29,6 @@ const ChatMessageHeader: React.FC = ({ return ( - - {name.toLowerCase().slice(0, 2) || " "} - {name} diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/styles.ts b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/styles.ts index 37621c6955..a1ae57038a 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/styles.ts +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-header/styles.ts @@ -1,12 +1,6 @@ -import styled, { css } from 'styled-components'; +import styled from 'styled-components'; import { - userIndicatorsOffset, -} from '/imports/ui/stylesheets/styled-components/general'; -import { - colorWhite, - userListBg, - colorSuccess, colorHeading, palettePlaceholderText, colorGrayLight, @@ -87,91 +81,6 @@ export const ChatTime = styled.time` } `; -export const ChatAvatar = styled.div` - flex: 0 0 2.25rem; - margin: 0px calc(0.5rem) 0px 0px; - box-flex: 0; - position: relative; - height: 2.25rem; - width: 2.25rem; - border-radius: 50%; - text-align: center; - font-size: .85rem; - border: 2px solid transparent; - user-select: none; - ${({ color }) => css` - background-color: ${color}; - `} - } - - &:after, - &:before { - content: ""; - position: absolute; - width: 0; - height: 0; - padding-top: .5rem; - padding-right: 0; - padding-left: 0; - padding-bottom: 0; - color: inherit; - top: auto; - left: auto; - bottom: ${userIndicatorsOffset}; - right: ${userIndicatorsOffset}; - border: 1.5px solid ${userListBg}; - border-radius: 50%; - background-color: ${colorSuccess}; - color: ${colorWhite}; - opacity: 0; - font-family: 'bbb-icons'; - font-size: .65rem; - line-height: 0; - text-align: center; - vertical-align: middle; - letter-spacing: -.65rem; - z-index: 1; - - [dir="rtl"] & { - left: ${userIndicatorsOffset}; - right: auto; - padding-right: .65rem; - padding-left: 0; - } - } - - ${({ moderator }) => - moderator && - ` - border-radius: 5px; - `} - - // ================ image ================ - ${({ avatar, emoji }) => - avatar?.length !== 0 && - !emoji && - css` - background-image: url(${avatar}); - background-repeat: no-repeat; - background-size: contain; - `} - // ================ image ================ - - // ================ content ================ - color: ${colorWhite}; - font-size: 110%; - text-transform: capitalize; - display: flex; - justify-content: center; - align-items:center; - // ================ content ================ - - & .react-loading-skeleton { - height: 2.25rem; - width: 2.25rem; - } -`; - export const ChatHeaderText = styled.div` display: flex; align-items: baseline; @@ -180,7 +89,6 @@ export const ChatHeaderText = styled.div` export default { HeaderContent, - ChatAvatar, ChatTime, ChatUserOffline, ChatUserName, diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/styles.ts b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/styles.ts index a1721934cd..1218cc07b1 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/styles.ts +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/styles.ts @@ -1,20 +1,36 @@ -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; import { borderSize, + userIndicatorsOffset, } from '/imports/ui/stylesheets/styled-components/general'; import { lineHeightComputed, fontSizeBase, } from '/imports/ui/stylesheets/styled-components/typography'; -export const ChatWrapper = styled.div` +import { + colorWhite, + userListBg, + colorSuccess, +} from '/imports/ui/stylesheets/styled-components/palette'; + + +interface ChatWrapperProps { + sameSender: boolean; +} + +interface ChatContentProps { + sameSender: boolean; +} + +export const ChatWrapper = styled.div` pointer-events: auto; [dir='rtl'] & { direction: rtl; } display: flex; - flex-flow: column; + flex-flow: row; position: relative; ${({ sameSender }) => sameSender && @@ -34,8 +50,100 @@ export const ChatWrapper = styled.div` font-size: ${fontSizeBase}; `; -export const ChatContent = styled.div` +export const ChatContent = styled.div` display: flex; flex-flow: column; width: 100%; + + ${({ sameSender }) => + sameSender && + ` + margin-left: 2.6rem; + `} `; + + +export const ChatAvatar = styled.div` + flex: 0 0 2.25rem; + margin: 0px calc(0.5rem) 0px 0px; + box-flex: 0; + position: relative; + height: 2.25rem; + width: 2.25rem; + border-radius: 50%; + text-align: center; + font-size: .85rem; + border: 2px solid transparent; + user-select: none; + ${({ color }) => css` + background-color: ${color}; + `} + } + + &:after, + &:before { + content: ""; + position: absolute; + width: 0; + height: 0; + padding-top: .5rem; + padding-right: 0; + padding-left: 0; + padding-bottom: 0; + color: inherit; + top: auto; + left: auto; + bottom: ${userIndicatorsOffset}; + right: ${userIndicatorsOffset}; + border: 1.5px solid ${userListBg}; + border-radius: 50%; + background-color: ${colorSuccess}; + color: ${colorWhite}; + opacity: 0; + font-family: 'bbb-icons'; + font-size: .65rem; + line-height: 0; + text-align: center; + vertical-align: middle; + letter-spacing: -.65rem; + z-index: 1; + + [dir="rtl"] & { + left: ${userIndicatorsOffset}; + right: auto; + padding-right: .65rem; + padding-left: 0; + } + } + + ${({ moderator }) => + moderator && + ` + border-radius: 5px; + `} + + // ================ image ================ + ${({ avatar, emoji }) => + avatar?.length !== 0 && + !emoji && + css` + background-image: url(${avatar}); + background-repeat: no-repeat; + background-size: contain; + `} + // ================ image ================ + + // ================ content ================ + color: ${colorWhite}; + font-size: 110%; + text-transform: capitalize; + display: flex; + justify-content: center; + align-items:center; + // ================ content ================ + + & .react-loading-skeleton { + height: 2.25rem; + width: 2.25rem; + } +`; \ No newline at end of file From eac3cc84ab05d0c64454e90c85cc79618a755748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Wed, 16 Aug 2023 16:54:43 -0300 Subject: [PATCH 211/252] display username in grid mode --- .../video-list/video-list-item/styles.js | 1 - .../video-list-item/user-actions/component.jsx | 16 +++++++++------- .../video-list-item/user-actions/styles.js | 7 +++---- .../video-list-item/user-avatar/styles.js | 1 + 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/styles.js b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/styles.js index 6810e60691..22a845e703 100644 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/styles.js +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/styles.js @@ -91,7 +91,6 @@ const WebcamConnecting = styled.div` min-width: 100%; border-radius: 10px; background-color: ${webcamBackgroundColor}; - scale: 1.5; z-index: 0; &::after { diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/user-actions/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/user-actions/component.jsx index 45ec5b207b..4712eafa73 100644 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/user-actions/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/user-actions/component.jsx @@ -117,13 +117,15 @@ const UserActions = (props) => { }); } - menuItems.push({ - key: `${cameraId}-mirror`, - label: intl.formatMessage(intlMessages.mirrorLabel), - description: intl.formatMessage(intlMessages.mirrorDesc), - onClick: () => onHandleMirror(cameraId), - dataTest: 'mirrorWebcamBtn', - }); + if (isStream) { + menuItems.push({ + key: `${cameraId}-mirror`, + label: intl.formatMessage(intlMessages.mirrorLabel), + description: intl.formatMessage(intlMessages.mirrorDesc), + onClick: () => onHandleMirror(cameraId), + dataTest: 'mirrorWebcamBtn', + }); + } if (numOfStreams > 2 && isStream) { menuItems.push({ diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/user-actions/styles.js b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/user-actions/styles.js index 9def35b4f2..0cfdf9b438 100644 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/user-actions/styles.js +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/user-actions/styles.js @@ -32,10 +32,7 @@ const DropdownTrigger = styled(DivElipsis)` const UserName = styled(TextElipsis)` position: relative; - max-width: 75%; // Keep the background with 0.5 opacity, but leave the text with 1 - background-color: rgba(0, 0, 0, 0.5); - border-radius: 1px; color: ${colorOffWhite}; padding: 0 1rem 0 .5rem !important; font-size: 80%; @@ -48,7 +45,9 @@ const UserName = styled(TextElipsis)` const Dropdown = styled.div` display: flex; outline: none !important; - width: 70%; + background-color: rgba(0, 0, 0, 0.5); + border-radius: 10px; + display: inline-block; @media ${mediumUp} { >[aria-expanded] { diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/user-avatar/styles.js b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/user-avatar/styles.js index 58c128901b..9386fe05ae 100644 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/user-avatar/styles.js +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/user-avatar/styles.js @@ -13,6 +13,7 @@ const UserAvatarStyled = styled(UserAvatar)` width: 45%; max-width: 66px; max-height: 66px; + scale: 1.5; ${({ unhealthyStream }) => unhealthyStream && ` filter: grayscale(50%) opacity(50%); From 50880961e80d508a2b9c83b0caf2a64e9f60b663 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 16 Aug 2023 17:21:42 -0300 Subject: [PATCH 212/252] Merge tests improvements to Develop --- .../automated-tests-build-package-job.yml | 2 +- .github/workflows/automated-tests.yml | 379 ++++-------------- 2 files changed, 82 insertions(+), 299 deletions(-) diff --git a/.github/workflows/automated-tests-build-package-job.yml b/.github/workflows/automated-tests-build-package-job.yml index 75cdc823e8..e678aee6c7 100644 --- a/.github/workflows/automated-tests-build-package-job.yml +++ b/.github/workflows/automated-tests-build-package-job.yml @@ -13,7 +13,7 @@ on: type: string jobs: b: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout ${{ github.event.pull_request.base.ref || 'master' }} uses: actions/checkout@v3 diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index 7d77a89ced..a7df83feea 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -15,318 +15,96 @@ on: - '**/*.md' permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: build-bbb-apps-akka: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 # Fetch all history - - run: echo "CACHE_AKKA_APPS_KEY=$(git log -1 --format=%H -- akka-bbb-apps)" >> $GITHUB_ENV - - run: echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV - - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - name: Handle cache - id: cache-action - uses: actions/cache@v3 - with: - path: artifacts.tar - key: ${{ runner.os }}-bbb-apps-akka-${{ env.CACHE_AKKA_APPS_KEY }}-${{ env.CACHE_COMMON_MSG_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} - name: Generate artifacts - run: | - ./build/get_external_dependencies.sh - ./build/setup.sh bbb-apps-akka - tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-apps-akka.tar - path: | - artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-apps-akka + cache-files-list: akka-bbb-apps bbb-common-message build-bbb-config: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - run: | - ./build/get_external_dependencies.sh - ./build/setup.sh bbb-config - tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-config.tar - path: artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-config + cache-files-list: bigbluebutton-config build-bbb-export-annotations: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - run: | - ./build/get_external_dependencies.sh - ./build/setup.sh bbb-export-annotations - tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-export-annotations.tar - path: artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-export-annotations + cache-files-list: bbb-export-annotations build-bbb-learning-dashboard: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 # Fetch all history - - run: echo "CACHE_LEARNING_DASHBOARD_KEY=$(git log -1 --format=%H -- bbb-learning-dashboard)" >> $GITHUB_ENV - - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - name: Handle cache - id: cache-action - uses: actions/cache@v3 - with: - path: artifacts.tar - key: ${{ runner.os }}-bbb-learning-dashboard-${{ env.CACHE_LEARNING_DASHBOARD_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} - name: Generate artifacts - run: | - ./build/get_external_dependencies.sh - ./build/setup.sh bbb-learning-dashboard - tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-learning-dashboard.tar - path: artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-learning-dashboard + cache-files-list: bbb-learning-dashboard build-bbb-playback-record: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - - run: ./build/get_external_dependencies.sh - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - run: ./build/setup.sh bbb-playback - - run: ./build/setup.sh bbb-playback-notes - - run: ./build/setup.sh bbb-playback-podcast - - run: ./build/setup.sh bbb-playback-presentation - - run: ./build/setup.sh bbb-playback-screenshare - - run: ./build/setup.sh bbb-playback-video - - run: ./build/setup.sh bbb-record-core - - run: tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-playback-record.tar - path: | - artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-playback-record + build-list: bbb-playback bbb-playback-notes bbb-playback-podcast bbb-playback-presentation bbb-playback-screenshare bbb-playback-video bbb-record-core build-bbb-graphql-server: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - - run: ./build/get_external_dependencies.sh - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - run: ./build/setup.sh bbb-graphql-middleware - - run: ./build/setup.sh bbb-graphql-server - - run: tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-graphql-server.tar - path: | - artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-graphql-server + build-list: bbb-graphql-server bbb-graphql-middleware build-bbb-etherpad: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 # Fetch all history - - run: echo "CACHE_ETHERPAD_VERSION_KEY=$(git log -1 --format=%H -- bbb-etherpad.placeholder.sh)" >> $GITHUB_ENV - - run: echo "CACHE_ETHERPAD_BUILD_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-etherpad)" >> $GITHUB_ENV - - run: echo "CACHE_URL1_KEY=$(curl -s https://api.github.com/repos/mconf/ep_pad_ttl/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - - run: echo "CACHE_URL2_KEY=$(curl -s https://api.github.com/repos/alangecker/bbb-etherpad-plugin/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - - run: echo "CACHE_URL3_KEY=$(curl -s https://api.github.com/repos/mconf/ep_redis_publisher/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - - run: echo "CACHE_URL4_KEY=$(curl -s https://api.github.com/repos/alangecker/bbb-etherpad-skin/commits | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - name: Handle cache - id: cache-action - uses: actions/cache@v3 - with: - path: artifacts.tar - key: ${{ runner.os }}-bbb-etherpad-${{ env.CACHE_ETHERPAD_VERSION_KEY }}-${{ env.CACHE_ETHERPAD_BUILD_KEY }}-${{ env.CACHE_URL1_KEY }}-${{ env.CACHE_URL2_KEY }}-${{ env.CACHE_URL3_KEY }}-${{ env.CACHE_URL4_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} - name: Generate artifacts - run: | - ./build/get_external_dependencies.sh - ./build/setup.sh bbb-etherpad - tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-etherpad.tar - path: | - artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-etherpad + cache-files-list: bbb-etherpad.placeholder.sh build/packages-template/bbb-etherpad + cache-urls-list: https://api.github.com/repos/mconf/ep_pad_ttl/commits https://api.github.com/repos/alangecker/bbb-etherpad-plugin/commits https://api.github.com/repos/mconf/ep_redis_publisher/commits https://api.github.com/repos/alangecker/bbb-etherpad-skin/commits build-bbb-bbb-web: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 # Fetch all history - - run: echo "CACHE_BBB_WEB_KEY=$(git log -1 --format=%H -- bigbluebutton-web)" >> $GITHUB_ENV - - run: echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV - - run: echo "CACHE_COMMON_WEB_KEY=$(git log -1 --format=%H -- bbb-common-web)" >> $GITHUB_ENV - - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - name: Handle cache - id: cache-action - uses: actions/cache@v3 - with: - path: artifacts.tar - key: ${{ runner.os }}-bbb-web-${{ env.CACHE_BBB_WEB_KEY }}-${{ env.CACHE_COMMON_MSG_KEY }}-${{ env.CACHE_COMMON_WEB_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} - name: Generate artifacts - run: | - ./build/get_external_dependencies.sh - ./build/setup.sh bbb-web - tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-web.tar - path: | - artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-web + cache-files-list: bigbluebutton-web bbb-common-message bbb-common-web build-bbb-fsesl-akka: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 # Fetch all history - - run: echo "CACHE_AKKA_FSESL_KEY=$(git log -1 --format=%H -- akka-bbb-fsesl)" >> $GITHUB_ENV - - run: echo "CACHE_COMMON_MSG_KEY=$(git log -1 --format=%H -- bbb-common-message)" >> $GITHUB_ENV - - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - name: Handle cache - id: cache-action - uses: actions/cache@v3 - with: - path: artifacts.tar - key: ${{ runner.os }}-bbb-fsesl-akka-${{ env.CACHE_AKKA_FSESL_KEY }}-${{ env.CACHE_COMMON_MSG_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} - name: Generate artifacts - run: | - ./build/get_external_dependencies.sh - ./build/setup.sh bbb-fsesl-akka - tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-fsesl-akka.tar - path: | - artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-fsesl-akka + cache-files-list: akka-bbb-fsesl bbb-common-message build-bbb-html5: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 # Fetch all history - - run: echo "CACHE_KEY=$(git log -1 --format=%H -- bigbluebutton-html5)" >> $GITHUB_ENV - - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - name: Handle cache - id: cache-action - uses: actions/cache@v3 - with: - path: artifacts.tar - key: ${{ runner.os }}-bbb-html5-${{ env.CACHE_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} - name: Generate artifacts - run: | - ./build/get_external_dependencies.sh - ./build/setup.sh bbb-html5-nodejs - ./build/setup.sh bbb-html5 - tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-html5.tar - path: | - artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-html5 + build-list: bbb-html5-nodejs bbb-html5 + cache-files-list: bigbluebutton-html5 build-bbb-freeswitch: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 # Fetch all history - - run: echo "CACHE_FREESWITCH_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-core)" >> $GITHUB_ENV - - run: echo "CACHE_FREESWITCH_SOUNDS_KEY=$(git log -1 --format=%H -- build/packages-template/bbb-freeswitch-sounds)" >> $GITHUB_ENV - - run: echo "CACHE_SOUNDS_KEY=$(curl -Is http://bigbluebutton.org/downloads/sounds.tar.gz | grep "Last-Modified" | md5sum | awk '{ print $1 }')" >> $GITHUB_ENV - - run: echo "CACHE_BBB_RELEASE_KEY=$(git log -1 --format=%H -- bigbluebutton-config/bigbluebutton-release)" >> $GITHUB_ENV - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - name: Handle cache - id: cache-action - uses: actions/cache@v3 - with: - path: artifacts.tar - key: ${{ runner.os }}-bbb-freeswitch-${{ env.CACHE_FREESWITCH_KEY }}-${{ env.CACHE_FREESWITCH_SOUNDS_KEY }}-${{ env.CACHE_SOUNDS_KEY }}-${{ env.CACHE_BBB_RELEASE_KEY }} - - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} - name: Generate artifacts - run: | - ./build/get_external_dependencies.sh - ./build/setup.sh bbb-freeswitch-core - ./build/setup.sh bbb-freeswitch-sounds - tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts_bbb-freeswitch.tar - path: | - artifacts.tar + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-freeswitch + build-list: bbb-freeswitch-core bbb-freeswitch-sounds + cache-files-list: freeswitch.placeholder.sh build/packages-template/bbb-freeswitch-core build/packages-template/bbb-freeswitch-sounds + cache-urls-list: http://bigbluebutton.org/downloads/sounds.tar.gz + build-bbb-webrtc: + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + with: + build-name: bbb-webrtc + build-list: bbb-webrtc-sfu bbb-webrtc-recorder + cache-files-list: bbb-webrtc-sfu.placeholder.sh bbb-webrtc-recorder.placeholder.sh build/packages-template/bbb-webrtc-sfu build/packages-template/bbb-webrtc-recorder build-others: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - - run: ./build/get_external_dependencies.sh - - run: echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh - - run: echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - - run: ./build/setup.sh bbb-mkclean - - run: ./build/setup.sh bbb-pads - - run: ./build/setup.sh bbb-libreoffice-docker - - run: ./build/setup.sh bbb-webrtc-sfu - - run: ./build/setup.sh bbb-webrtc-recorder - - run: ./build/setup.sh bbb-transcription-controller - - run: ./build/setup.sh bigbluebutton - - run: tar cvf artifacts.tar artifacts/ - - name: Archive packages - uses: actions/upload-artifact@v3 - with: - name: artifacts.tar - path: | - artifacts.tar - # - name: Fake package build - # run: | - # sudo -i < /etc/apt/sources.list.d/bigbluebutton.list||g" | bash -s -- -v jammy-28-develop -s bbb-ci.test -j -d /certs/ bbb-conf --salt bbbci echo "NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt" >> /usr/share/meteor/bundle/bbb-html5-with-roles.conf From 1a058d9bb1bb6631847aabbb7b02568a3db26468 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Thu, 17 Aug 2023 08:13:07 -0300 Subject: [PATCH 213/252] Use reusable workflow from Develop instead of v2.7-release --- .github/workflows/automated-tests.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index a7df83feea..55941642a0 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -20,72 +20,72 @@ concurrency: cancel-in-progress: true jobs: build-bbb-apps-akka: - uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@develop with: build-name: bbb-apps-akka cache-files-list: akka-bbb-apps bbb-common-message build-bbb-config: - uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@develop with: build-name: bbb-config cache-files-list: bigbluebutton-config build-bbb-export-annotations: - uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@develop with: build-name: bbb-export-annotations cache-files-list: bbb-export-annotations build-bbb-learning-dashboard: - uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@develop with: build-name: bbb-learning-dashboard cache-files-list: bbb-learning-dashboard build-bbb-playback-record: - uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@develop with: build-name: bbb-playback-record build-list: bbb-playback bbb-playback-notes bbb-playback-podcast bbb-playback-presentation bbb-playback-screenshare bbb-playback-video bbb-record-core build-bbb-graphql-server: - uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@develop with: build-name: bbb-graphql-server build-list: bbb-graphql-server bbb-graphql-middleware build-bbb-etherpad: - uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@develop with: build-name: bbb-etherpad cache-files-list: bbb-etherpad.placeholder.sh build/packages-template/bbb-etherpad cache-urls-list: https://api.github.com/repos/mconf/ep_pad_ttl/commits https://api.github.com/repos/alangecker/bbb-etherpad-plugin/commits https://api.github.com/repos/mconf/ep_redis_publisher/commits https://api.github.com/repos/alangecker/bbb-etherpad-skin/commits build-bbb-bbb-web: - uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@develop with: build-name: bbb-web cache-files-list: bigbluebutton-web bbb-common-message bbb-common-web build-bbb-fsesl-akka: - uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@develop with: build-name: bbb-fsesl-akka cache-files-list: akka-bbb-fsesl bbb-common-message build-bbb-html5: - uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@develop with: build-name: bbb-html5 build-list: bbb-html5-nodejs bbb-html5 cache-files-list: bigbluebutton-html5 build-bbb-freeswitch: - uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@develop with: build-name: bbb-freeswitch build-list: bbb-freeswitch-core bbb-freeswitch-sounds cache-files-list: freeswitch.placeholder.sh build/packages-template/bbb-freeswitch-core build/packages-template/bbb-freeswitch-sounds cache-urls-list: http://bigbluebutton.org/downloads/sounds.tar.gz build-bbb-webrtc: - uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@develop with: build-name: bbb-webrtc build-list: bbb-webrtc-sfu bbb-webrtc-recorder cache-files-list: bbb-webrtc-sfu.placeholder.sh bbb-webrtc-recorder.placeholder.sh build/packages-template/bbb-webrtc-sfu build/packages-template/bbb-webrtc-recorder build-others: - uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@v2.7.x-release + uses: bigbluebutton/bigbluebutton/.github/workflows/automated-tests-build-package-job.yml@develop with: build-name: others build-list: bbb-mkclean bbb-pads bbb-libreoffice-docker bbb-transcription-controller bigbluebutton From a6efce7f11e53881e2ba106b1fb50cd1f1cb7026 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Thu, 17 Aug 2023 08:25:02 -0300 Subject: [PATCH 214/252] Include OS version to cache key --- .github/workflows/automated-tests-build-package-job.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/automated-tests-build-package-job.yml b/.github/workflows/automated-tests-build-package-job.yml index e678aee6c7..2c0cd20dab 100644 --- a/.github/workflows/automated-tests-build-package-job.yml +++ b/.github/workflows/automated-tests-build-package-job.yml @@ -32,6 +32,7 @@ jobs: echo "CACHE_KEY_FILES=$(echo '${{ inputs.cache-files-list }} .gitlab-ci.yml' | xargs -n1 git log -1 --format=%h -- | tr '\n' '-' | sed 's/-$//')" >> $GITHUB_ENV echo "CACHE_KEY_URLS=$(echo '${{ inputs.cache-urls-list }}' | xargs -r -n 1 curl -Is | grep -i 'Last-Modified' | md5sum | cut -c1-10)" >> $GITHUB_ENV cat bigbluebutton-config/bigbluebutton-release >> $GITHUB_ENV + echo "RUNNER_OS=$(lsb_release -d | cut -f2 | tr -d ' ')" >> $GITHUB_ENV echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh echo "FORCE_COMMIT_DATE=0" >> $GITHUB_ENV #used by setup.sh - name: Handle cache @@ -40,7 +41,7 @@ jobs: uses: actions/cache@v3 with: path: artifacts.tar - key: ${{ runner.os }}-${{ inputs.build-name }}-${{ env.BIGBLUEBUTTON_RELEASE }}-commits-${{ env.CACHE_KEY_FILES }}-urls-${{ env.CACHE_KEY_URLS }} + key: ${{ env.RUNNER_OS }}-${{ inputs.build-name }}-${{ env.BIGBLUEBUTTON_RELEASE }}-commits-${{ env.CACHE_KEY_FILES }}-urls-${{ env.CACHE_KEY_URLS }} - if: ${{ steps.cache-action.outputs.cache-hit != 'true' }} name: Generate artifacts run: | From 59b6a2d3fd354a13e9be194ec45e1de3a995b572 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Thu, 17 Aug 2023 09:47:00 -0400 Subject: [PATCH 215/252] Updates for project BigBlueButton v2.7 HTML5 client and lanuage et on branch v2.7.x-release (#18578) * Translate en.json in et 100% translated source file: 'en.json' on 'et'. --------- Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- bigbluebutton-html5/public/locales/et.json | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/bigbluebutton-html5/public/locales/et.json b/bigbluebutton-html5/public/locales/et.json index d6b84a2ac1..b3620cdec5 100644 --- a/bigbluebutton-html5/public/locales/et.json +++ b/bigbluebutton-html5/public/locales/et.json @@ -68,7 +68,7 @@ "app.timer.hours": "tunnid", "app.timer.minutes": "minutid", "app.timer.seconds": "sekundid", - "app.timer.songs": "Laulud", + "app.timer.songs": "Muusika", "app.timer.noTrack": "Pole", "app.timer.track1": "Lõdvestav", "app.timer.track2": "Rahulik", @@ -139,8 +139,8 @@ "app.userList.menuTitleContext": "Saadaolevad valikud", "app.userList.chatListItem.unreadSingular": "Üks uus sõnum", "app.userList.chatListItem.unreadPlural": "{0} uut sõnumit", - "app.userList.menu.away": "Määra ennast eemalolijaks", - "app.userList.menu.notAway": "Määra ennast aktiivseks", + "app.userList.menu.away": "Märgi ennast eemalolijaks", + "app.userList.menu.notAway": "Märgi ennast aktiivseks", "app.userList.menu.chat.label": "Alusta privaatset vestlust", "app.userList.menu.clearStatus.label": "Kustuta olek", "app.userList.menu.removeUser.label": "Eemalda kasutaja", @@ -247,7 +247,7 @@ "app.presentation.options.snapshot": "Praeguse slaidi hetktõmmis", "app.presentation.options.downloading": "Allalaadimine...", "app.presentation.options.downloaded": "Praegune slaid on alla laaditud", - "app.presentation.options.downloadFailed": "Ei saa praegust slaidi alla laadida", + "app.presentation.options.downloadFailed": "Ei õnnestunud praegust slaidi alla laadida", "app.presentation.presentationToolbar.noNextSlideDesc": "Esitluse lõpp", "app.presentation.presentationToolbar.noPrevSlideDesc": "Esitluse algus", "app.presentation.presentationToolbar.selectLabel": "Vali slaid", @@ -256,7 +256,7 @@ "app.presentation.presentationToolbar.nextSlideLabel": "Järgmine slaid", "app.presentation.presentationToolbar.nextSlideDesc": "Mine esitluses järgmisele slaidile", "app.presentation.presentationToolbar.skipSlideLabel": "Jäta slaid vahele", - "app.presentation.presentationToolbar.skipSlideDesc": "Mine esitluses kindlale slaidile", + "app.presentation.presentationToolbar.skipSlideDesc": "Mine esitluses konkreetsele slaidile", "app.presentation.presentationToolbar.fitWidthLabel": "Kohanda laiusele", "app.presentation.presentationToolbar.fitWidthDesc": "Kuva slaid kogu laiuses", "app.presentation.presentationToolbar.fitScreenLabel": "Kohanda ekraanile", @@ -278,8 +278,8 @@ "app.presentationUploder.title": "Esitlus", "app.presentationUploder.message": "Esitlejana saad üles laadida ükskõik millise Office'i dokumendi või PDF-faili. Parima tulemuse saamiseks soovitame PDF-faili. Veendu, et esitlus oleks valitud vasakul asuva märkeringiga.", "app.presentationUploader.exportHint": "Valides \"Saada vestlusesse\", saavad kasutajad avalikus vestluses allalaaditava lingi koos märgetega.", - "app.presentationUploader.exportToastHeader": "Vestlusesse saatmine ({0} fail)", - "app.presentationUploader.exportToastHeaderPlural": "Vestlusesse saatmine ({0} faili)", + "app.presentationUploader.exportToastHeader": "Vestlusesse saatmine ({0} element)", + "app.presentationUploader.exportToastHeaderPlural": "Vestlusesse saatmine ({0} elementi)", "app.presentationUploader.exporting": "Vestlusesse saatmine", "app.presentationUploader.sending": "Saatmine...", "app.presentationUploader.collecting": "Slaidide väljaeraldamine: {0}/{1}...", @@ -307,7 +307,7 @@ "app.presentationUploder.dropzoneImagesLabel": "Aseta üleslaaditavad pildid siia", "app.presentationUploder.browseFilesLabel": "või sirvi faile", "app.presentationUploder.browseImagesLabel": "või vali/tee pilte", - "app.presentationUploder.externalUploadTitle": "Sisu lisamine kolmanda osapoole rakendusest", + "app.presentationUploder.externalUploadTitle": "Sisu lisamine välisest rakendusest", "app.presentationUploder.externalUploadLabel": "Sirvi faile", "app.presentationUploder.fileToUpload": "Valmis üleslaadimiseks...", "app.presentationUploder.currentBadge": "Aktiivne", @@ -374,7 +374,7 @@ "app.poll.responseTypes.label" : "Vastusetüübid", "app.poll.optionDelete.label" : "Kustuta", "app.poll.responseChoices.label" : "Vastusevariandid", - "app.poll.typedResponse.desc" : "Kasutajale esitatakse tekstiväli vastuse kirjutamiseks.", + "app.poll.typedResponse.desc" : "Kasutajale esitatakse tekstikast vastuse kirjutamiseks.", "app.poll.addItem.label" : "Lisa vastus", "app.poll.start.label" : "Alusta küsitlust", "app.poll.secretPoll.label" : "Anonüümne küsitlus", @@ -421,7 +421,7 @@ "app.polling.pollAnswerLabel": "Küsitluse vastus {0}", "app.polling.pollAnswerDesc": "Vali see variant, et hääletada {0} poolt", "app.failedMessage": "Vabandust! Serveriga ühenduse loomisel esineb probleeme.", - "app.downloadPresentationButton.label": "Laadi alla esitluse originaal", + "app.downloadPresentationButton.label": "Laadi alla orginaalesitlus", "app.connectingMessage": "Ühendumine...", "app.waitingMessage": "Ühendus katkes. Proovin uuesti ühendust luua {0} sekundi pärast ...", "app.retryNow": "Proovi kohe", @@ -638,7 +638,7 @@ "app.actionsBar.actionsDropdown.takePresenterDesc": "Määra ennast uueks esitlejaks", "app.actionsBar.actionsDropdown.selectRandUserLabel": "Vali juhuslik kasutaja", "app.actionsBar.actionsDropdown.selectRandUserDesc": "Valib kõigi vaatajate seast juhuslikult ühe", - "app.actionsBar.reactions.reactionsButtonLabel": "Reaktsioonide riba", + "app.actionsBar.reactions.reactionsButtonLabel": "Reaktsiooniriba", "app.actionsBar.reactions.raiseHand": "Tõsta käsi", "app.actionsBar.reactions.lowHand": "Langeta käsi", "app.actionsBar.emojiMenu.statusTriggerLabel": "Määra olek", @@ -717,7 +717,7 @@ "app.audioModal.helpTitle": "Sinu meediaseadmetega tekkis probleem", "app.audioModal.helpText": "Kas andsid loa juurdepääsuks mikrofonile? Kui püüad liituda audioga, siis peaks ilmuma dialoogiaken, kus küsitakse luba meediaseadme kasutamiseks; audiosessiooniga liitumiseks palun nõustu sellega. Muul juhul proovi muuta brauseri sätetes mikrofoniga seotud õigusi.", "app.audioModal.help.noSSL": "See leht pole turvaline. Mikrofonile juurdepääsu lubamiseks peab leht kasutama HTTPSi. Palun võta ühendust serveri administraatoriga.", - "app.audioModal.help.macNotAllowed": "Paistab, et Maci süsteemieelistused blokeerivad juurdepääsu mikrofonile. Ava System Preferences > Security & Privacy > Privacy > Microphone ja kontrolli, et brauser, mida kasutad, oleks märgitud.", + "app.audioModal.help.macNotAllowed": "Paistab, et Maci süsteemieelistustes on juurdepääs mikrofonile blokeeritud. Ava System Preferences > Security & Privacy > Privacy > Microphone ja kontrolli, et brauser, mida kasutad, oleks märgitud.", "app.audioModal.audioDialTitle": "Liitu telefoni abil", "app.audioDial.audioDialDescription": "Vali", "app.audioDial.audioDialConfrenceText": "ja sisesta koosoleku PIN-kood:", @@ -785,7 +785,7 @@ "app.meeting.logout.duplicateUserEjectReason": "Juba kohal olev kasutaja püüab koosolekuga liituda", "app.meeting.logout.permissionEjectReason": "Välja heidetud õiguste rikkumise pärast", "app.meeting.logout.ejectedFromMeeting": "Oled koosolekult eemaldatud", - "app.meeting.logout.validateTokenFailedEjectReason": "Autoriseerimistõendi kehtivuse kinnitamine ebaõnnestus", + "app.meeting.logout.validateTokenFailedEjectReason": "Volitustõendi kehtivuse kinnitamine ebaõnnestus", "app.meeting.logout.userInactivityEjectReason": "Kasutaja on olnud liiga kaua mitteaktiivne", "app.meeting.logout.maxParticipantsReached": "Sellel koosolekul lubatud osalejate maksimaalarv on täis", "app.meeting-ended.rating.legendLabel": "Tagasisidehinnang", @@ -805,7 +805,7 @@ "app.dropdown.close": "Sulge", "app.dropdown.list.item.activeLabel": "Aktiivne", "app.error.400": "Vigane päring", - "app.error.401": "Autoriseerimata", + "app.error.401": "Volitamata", "app.error.403": "Oled koosolekult eemaldatud", "app.error.404": "Ei leitud", "app.error.408": "Autentimine ebaõnnestus", @@ -1041,7 +1041,7 @@ "app.video.videoSettings": "Videosätted", "app.video.visualEffects": "Visuaalefektid", "app.video.advancedVideo": "Ava täpsemad sätted", - "app.video.iceCandidateError": "Viga ICE kandidaadi lisamisel", + "app.video.iceCandidateError": "Viga ICE-kandidaadi lisamisel", "app.video.iceConnectionStateError": "Ühenduse tõrge (ICE viga 1107)", "app.video.permissionError": "Viga veebikaamera jagamisel. Palun kontrolli õigusi", "app.video.sharingError": "Viga veebikaamera jagamisel", @@ -1140,8 +1140,8 @@ "app.whiteboard.toolbar.clearConfirmation": "Kas soovid kõik märked kustutada?", "app.whiteboard.toolbar.multiUserOn": "Lülita mitme kasutaja tahvel sisse", "app.whiteboard.toolbar.multiUserOff": "Lülita mitme kasutaja tahvel välja", - "app.whiteboard.toolbar.palmRejectionOn": "Lülita käelaba hülgamine sisse", - "app.whiteboard.toolbar.palmRejectionOff": "Lülita käelaba hülgamine välja", + "app.whiteboard.toolbar.palmRejectionOn": "Lülita käelabakontakti hülgamine sisse", + "app.whiteboard.toolbar.palmRejectionOff": "Lülita käelabakontakti hülgamine välja", "app.whiteboard.toolbar.fontSize": "Kirjasuuruste loetelu", "app.whiteboard.toolbarAriaLabel": "Esitluse tööriistad", "app.feedback.title": "Oled koosolekult välja logitud", @@ -1232,7 +1232,7 @@ "app.externalVideo.autoPlayWarning": "Käivita video, et meedia sünkroniseerimine aktiveerida", "app.externalVideo.refreshLabel": "Värskenda videomängijat", "app.externalVideo.fullscreenLabel": "Videomängija", - "app.externalVideo.noteLabel": "Märkus: jagatud väliseid videoid sessiooni salvestisse ei kaasata. Toetatakse keskkondi YouTube, Vimeo, Instructure Media, Twitch ja Dailymotion ning meediafailide URL-e (nt https://example.com/xy.mp4).", + "app.externalVideo.noteLabel": "Märkus: jagatud väliseid videoid sessiooni salvestisse ei kaasata. Toetatakse keskkondi YouTube, Vimeo, Instructure Media, Twitch ja Dailymotion ning meediafailide URLe (nt https://example.com/xy.mp4).", "app.externalVideo.subtitlesOn": "Lülita välja", "app.externalVideo.subtitlesOff": "Lülita sisse (kui saadaval)", "app.actionsBar.actionsDropdown.shareExternalVideo": "Jaga välist videot", From 24ac10a065940cee1fdd3e8944223d8f4361b1fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Thu, 17 Aug 2023 10:54:35 -0300 Subject: [PATCH 216/252] fix incorrect chat on join --- bigbluebutton-html5/imports/startup/client/base.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/startup/client/base.jsx b/bigbluebutton-html5/imports/startup/client/base.jsx index 51c55eeaa8..1686a94f7e 100755 --- a/bigbluebutton-html5/imports/startup/client/base.jsx +++ b/bigbluebutton-html5/imports/startup/client/base.jsx @@ -24,7 +24,7 @@ import { makeCall } from '/imports/ui/services/api'; import BBBStorage from '/imports/ui/services/storage'; const CHAT_CONFIG = Meteor.settings.public.chat; -const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id; +const PUBLIC_CHAT_ID = CHAT_CONFIG.public_group_id; const USER_WAS_EJECTED = 'userWasEjected'; const HTML = document.getElementsByTagName('html')[0]; From aab1a8c1f88f07f8972b72afbb9013065c21acf7 Mon Sep 17 00:00:00 2001 From: Anton Georgiev Date: Thu, 17 Aug 2023 11:00:22 -0400 Subject: [PATCH 217/252] build: Recover SERVLET_DIR definition deb-helper.sh --- build/deb-helper.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/deb-helper.sh b/build/deb-helper.sh index 72754e48ae..7133ac6901 100755 --- a/build/deb-helper.sh +++ b/build/deb-helper.sh @@ -211,6 +211,8 @@ if [ -z "$IP" ]; then read -r IP _ <<< "$(hostname -I)" fi +SERVLET_DIR=/usr/share/bbb-web # reused in various package scripts + ########################## ### END DEB-HELPERS.SH ### ########################## From bba4658dd34114fcc0e997dd35e8a5b53a014781 Mon Sep 17 00:00:00 2001 From: prlanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Tue, 15 Aug 2023 22:03:09 -0300 Subject: [PATCH 218/252] fix(recording): remux bbb-webrtc-recorder and KMS files during archive Kurento may *rarely* generate WebM/MKV files with corrupt or absent SeekHead sectors. bbb-webrtc-recorder also doesn't generate SeekHead or even the Cues sectors by default. While those are are *optional* fields by spec, files need to be seekable for our recording processing scripts to work. This commit adds a remuxing step for Kurento and bbb-webrtc-recorder raw files that is executed during the archive phase. It should re-include any of the missing fields that make files seekable and restore the Cues sector in WebM files. --- .../core/scripts/archive/archive.rb | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/record-and-playback/core/scripts/archive/archive.rb b/record-and-playback/core/scripts/archive/archive.rb index 6f0283c38e..0074f1c64c 100755 --- a/record-and-playback/core/scripts/archive/archive.rb +++ b/record-and-playback/core/scripts/archive/archive.rb @@ -111,6 +111,37 @@ def delete_audio(meeting_id, audio_dir) end end +def remux_and_archive(source_dir, dest_dir) + files = Dir.glob("#{source_dir}/*") + + if files.empty? + BigBlueButton.logger.warn("No media files found in #{source_dir}") + return + end + + BigBlueButton.logger.info("Remuxing and archiving files at #{source_dir}") + FileUtils.mkdir_p(dest_dir) + + files .each do |file| + ext = File.extname(file) + next if ext.empty? + # These can potentially be webm (VP8/VP9), mp4 (H.264), or mkv (VP8/VP9/H.264) + output_basename = File.join(dest_dir, File.basename(file, ext)) + format = { + extension: ext.delete_prefix('.'), + parameters: [ + %w[-c copy], + ] + } + BigBlueButton::EDL.encode( + nil, + file, + format, + output_basename + ) + end +end + def archive_directory(source, dest) BigBlueButton.logger.info("Archiving contents of #{source}") FileUtils.mkdir_p(dest) @@ -207,14 +238,14 @@ archive_notes(meeting_id, notes_endpoint, notes_formats, raw_archive_dir) # Presentation files archive_directory("#{presentation_dir}/#{meeting_id}/#{meeting_id}", "#{target_dir}/presentation") # Kurento media -archive_directory("#{kurento_screenshare_dir}/#{meeting_id}", "#{target_dir}/deskshare") -archive_directory("#{kurento_video_dir}/#{meeting_id}", "#{target_dir}/video/#{meeting_id}") +remux_and_archive("#{kurento_screenshare_dir}/#{meeting_id}", "#{target_dir}/deskshare") +remux_and_archive("#{kurento_video_dir}/#{meeting_id}", "#{target_dir}/video/#{meeting_id}") # mediasoup media archive_directory("#{mediasoup_screenshare_dir}/#{meeting_id}", "#{target_dir}/deskshare") archive_directory("#{mediasoup_video_dir}/#{meeting_id}", "#{target_dir}/video/#{meeting_id}") # bbb-webrtc-recorder media -archive_directory("#{webrtc_recorder_screenshare_dir}/#{meeting_id}", "#{target_dir}/deskshare") -archive_directory("#{webrtc_recorder_video_dir}/#{meeting_id}", "#{target_dir}/video/#{meeting_id}") +remux_and_archive("#{webrtc_recorder_screenshare_dir}/#{meeting_id}", "#{target_dir}/deskshare") +remux_and_archive("#{webrtc_recorder_video_dir}/#{meeting_id}", "#{target_dir}/video/#{meeting_id}") # If this was the last (or only) segment in a recording, delete the original media files if break_timestamp.nil? From b6b9dd55576251a7375bea096b42a86ecffc5af0 Mon Sep 17 00:00:00 2001 From: prlanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Tue, 15 Aug 2023 22:18:10 -0300 Subject: [PATCH 219/252] build(bbb-webrtc-recorder): v0.4.1 - fix: only generate audio tracks if peer has audio - fix: create media files with appropriate permissions (0700) - fix: change to working env prefix BBBRECORDER_, add docs on env vars - feat: add recorder.fileMode, recorder.dirFileMode configs * dirFileMode=0700 (default) * fileMode=0600 (default) --- bbb-webrtc-recorder.placeholder.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbb-webrtc-recorder.placeholder.sh b/bbb-webrtc-recorder.placeholder.sh index d1cfc234da..fde8e31606 100755 --- a/bbb-webrtc-recorder.placeholder.sh +++ b/bbb-webrtc-recorder.placeholder.sh @@ -1 +1 @@ -git clone --branch v0.3.0 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-recorder bbb-webrtc-recorder +git clone --branch v0.4.1 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-recorder bbb-webrtc-recorder From cac83a9c92eaa534509051a59889c29fd7e50aae Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Thu, 17 Aug 2023 13:17:33 -0300 Subject: [PATCH 220/252] Add build/deb-helper.sh to cache key --- .github/workflows/automated-tests-build-package-job.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/automated-tests-build-package-job.yml b/.github/workflows/automated-tests-build-package-job.yml index 2c0cd20dab..a0e1e73b51 100644 --- a/.github/workflows/automated-tests-build-package-job.yml +++ b/.github/workflows/automated-tests-build-package-job.yml @@ -29,7 +29,7 @@ jobs: git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} - name: Set cache-key vars run: | - echo "CACHE_KEY_FILES=$(echo '${{ inputs.cache-files-list }} .gitlab-ci.yml' | xargs -n1 git log -1 --format=%h -- | tr '\n' '-' | sed 's/-$//')" >> $GITHUB_ENV + echo "CACHE_KEY_FILES=$(echo '${{ inputs.cache-files-list }} .gitlab-ci.yml build/deb-helper.sh' | xargs -n1 git log -1 --format=%h -- | tr '\n' '-' | sed 's/-$//')" >> $GITHUB_ENV echo "CACHE_KEY_URLS=$(echo '${{ inputs.cache-urls-list }}' | xargs -r -n 1 curl -Is | grep -i 'Last-Modified' | md5sum | cut -c1-10)" >> $GITHUB_ENV cat bigbluebutton-config/bigbluebutton-release >> $GITHUB_ENV echo "RUNNER_OS=$(lsb_release -d | cut -f2 | tr -d ' ')" >> $GITHUB_ENV From 0bf646b717d9177a5cc79596448e6d0864ab503d Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Thu, 17 Aug 2023 13:20:49 -0300 Subject: [PATCH 221/252] Add build/deb-helper.sh to cache key --- .github/workflows/automated-tests-build-package-job.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/automated-tests-build-package-job.yml b/.github/workflows/automated-tests-build-package-job.yml index 75cdc823e8..7c99b9fb8d 100644 --- a/.github/workflows/automated-tests-build-package-job.yml +++ b/.github/workflows/automated-tests-build-package-job.yml @@ -29,7 +29,7 @@ jobs: git pull origin pull/${{ github.event.number }}/head:${{ github.head_ref }} - name: Set cache-key vars run: | - echo "CACHE_KEY_FILES=$(echo '${{ inputs.cache-files-list }} .gitlab-ci.yml' | xargs -n1 git log -1 --format=%h -- | tr '\n' '-' | sed 's/-$//')" >> $GITHUB_ENV + echo "CACHE_KEY_FILES=$(echo '${{ inputs.cache-files-list }} .gitlab-ci.yml build/deb-helper.sh' | xargs -n1 git log -1 --format=%h -- | tr '\n' '-' | sed 's/-$//')" >> $GITHUB_ENV echo "CACHE_KEY_URLS=$(echo '${{ inputs.cache-urls-list }}' | xargs -r -n 1 curl -Is | grep -i 'Last-Modified' | md5sum | cut -c1-10)" >> $GITHUB_ENV cat bigbluebutton-config/bigbluebutton-release >> $GITHUB_ENV echo "FORCE_GIT_REV=0" >> $GITHUB_ENV #used by setup.sh From ee56979fa4ea932bd384479af486f55db1b55b0f Mon Sep 17 00:00:00 2001 From: Paul Trudel Date: Thu, 17 Aug 2023 22:27:52 +0000 Subject: [PATCH 222/252] Use presentation svg to compute dimensions --- .../PresentationPageConvertedSysMsgHdlr.scala | 4 +- .../common2/domain/Presentation.scala | 4 +- bbb-common-web/build.sbt | 3 +- .../org/bigbluebutton/api2/MsgBuilder.scala | 53 ++++++++++++++++--- 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPageConvertedSysMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPageConvertedSysMsgHdlr.scala index ccffb183ee..b7bb23fe71 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPageConvertedSysMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/presentationpod/PresentationPageConvertedSysMsgHdlr.scala @@ -54,7 +54,9 @@ trait PresentationPageConvertedSysMsgHdlr { msg.body.page.id, msg.body.page.num, msg.body.page.urls, - msg.body.page.current + msg.body.page.current, + width = msg.body.page.width, + height = msg.body.page.height ) val newState = for { diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Presentation.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Presentation.scala index 11590cb337..7448d31904 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Presentation.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Presentation.scala @@ -15,7 +15,9 @@ case class PresentationPageConvertedVO( id: String, num: Int, urls: Map[String, String], - current: Boolean = false + current: Boolean = false, + width: Double = 1440D, + height: Double = 1080D ) case class PresentationPageVO( diff --git a/bbb-common-web/build.sbt b/bbb-common-web/build.sbt index 1395104667..e7d24da7b7 100755 --- a/bbb-common-web/build.sbt +++ b/bbb-common-web/build.sbt @@ -112,5 +112,6 @@ libraryDependencies ++= Seq( "com.zaxxer" % "HikariCP" % "4.0.3", "commons-validator" % "commons-validator" % "1.7", "org.apache.tika" % "tika-core" % "2.8.0", - "org.apache.tika" % "tika-parsers-standard-package" % "2.8.0" + "org.apache.tika" % "tika-parsers-standard-package" % "2.8.0", + "org.scala-lang.modules" %% "scala-xml" % "2.2.0" ) diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/MsgBuilder.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/MsgBuilder.scala index 815e5d6e5d..96cff2fe2f 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/MsgBuilder.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/MsgBuilder.scala @@ -6,6 +6,11 @@ import org.bigbluebutton.common2.domain.{ DefaultProps, PageVO, PresentationPage import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.presentation.messages._ +import java.io.IOException +import java.net.URL +import javax.imageio.ImageIO +import scala.xml.XML + object MsgBuilder { def buildDestroyMeetingSysCmdMsg(msg: DestroyMeetingMessage): BbbCommonEnvCoreMsg = { val routing = collection.immutable.HashMap("sender" -> "bbb-web") @@ -65,6 +70,19 @@ object MsgBuilder { } def generatePresentationPage(presId: String, numPages: Int, presBaseUrl: String, page: Int): PresentationPageConvertedVO = { + def parseDimension(value: String): Option[Int] = { + val pattern = """^(\d+)\s*(\w)$""".r + value match { + case pattern(number, unit) if number.matches("\\d+") => + val numericValue = number.toInt + unit match { + case "" | "px" | "pt" => Some(numericValue) + case _ => None + } + case _ => None + } + } + val id = presId + "/" + page val current = if (page == 1) true else false val thumbUrl = presBaseUrl + "/thumbnail/" + page @@ -75,12 +93,35 @@ object MsgBuilder { val urls = Map("thumb" -> thumbUrl, "text" -> txtUrl, "svg" -> svgUrl, "png" -> pngUrl) - PresentationPageConvertedVO( - id = id, - num = page, - urls = urls, - current = current - ) + println("***** ATTEMPTING TO DETERMINE PAGE DIMENSIONS FOR " + svgUrl + "*****") + try { + val imgUrl = new URL(svgUrl) + val imgContent = XML.load(imgUrl) + + val w = (imgContent \ "@width").text.replaceAll("[^\\d]", "") + val h = (imgContent \ "@height").text.replaceAll("[^\\d]", "") + + val width = w.toInt + val height = h.toInt + + PresentationPageConvertedVO( + id = id, + num = page, + urls = urls, + current = current, + width = width, + height = height + ) + } catch { + case e: Exception => + e.printStackTrace() + PresentationPageConvertedVO( + id = id, + num = page, + urls = urls, + current = current + ) + } } def buildPresentationPageConvertedSysMsg(msg: DocPageGeneratedProgress): BbbCommonEnvCoreMsg = { From 4f4eced7bd86818ac7fc66e4516517bbf96c8497 Mon Sep 17 00:00:00 2001 From: Paulo Lanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Thu, 17 Aug 2023 19:48:33 -0300 Subject: [PATCH 223/252] build(bbb-webrtc-sfu): v2.11.0 See https://github.com/bigbluebutton/bbb-webrtc-sfu/releases/tag/v2.11.0 --- bbb-webrtc-sfu.placeholder.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbb-webrtc-sfu.placeholder.sh b/bbb-webrtc-sfu.placeholder.sh index 46bc6d83ec..85b50f0065 100755 --- a/bbb-webrtc-sfu.placeholder.sh +++ b/bbb-webrtc-sfu.placeholder.sh @@ -1 +1 @@ -git clone --branch v2.11.0-beta.4 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu +git clone --branch v2.11.0 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu From c852360ebe418cae6c649a441a83e87c51420c71 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Fri, 18 Aug 2023 11:58:52 +0000 Subject: [PATCH 224/252] Translate en.json in et 100% translated source file: 'en.json' on 'et'. --- bigbluebutton-html5/public/locales/et.json | 1 + 1 file changed, 1 insertion(+) diff --git a/bigbluebutton-html5/public/locales/et.json b/bigbluebutton-html5/public/locales/et.json index b3620cdec5..90973f0f86 100644 --- a/bigbluebutton-html5/public/locales/et.json +++ b/bigbluebutton-html5/public/locales/et.json @@ -641,6 +641,7 @@ "app.actionsBar.reactions.reactionsButtonLabel": "Reaktsiooniriba", "app.actionsBar.reactions.raiseHand": "Tõsta käsi", "app.actionsBar.reactions.lowHand": "Langeta käsi", + "app.actionsBar.reactions.autoCloseReactionsBarLabel": "Sulge reaktsiooniriba automaatselt", "app.actionsBar.emojiMenu.statusTriggerLabel": "Määra olek", "app.actionsBar.emojiMenu.awayLabel": "Eemal", "app.actionsBar.emojiMenu.awayDesc": "Määra oma olekuks Eemal", From 1b64bc0220122b96a5ad3308bf470f0e4b8af4ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Fri, 18 Aug 2023 10:29:19 -0300 Subject: [PATCH 225/252] fix chat parsing --- bigbluebutton-html5/imports/api/common/server/helpers.js | 8 ++++++++ .../server/modifiers/addBulkGroupChatMsgs.js | 2 +- .../message-content/text-content/component.tsx | 7 ++++--- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/bigbluebutton-html5/imports/api/common/server/helpers.js b/bigbluebutton-html5/imports/api/common/server/helpers.js index 56a4142314..b365b0d5d7 100755 --- a/bigbluebutton-html5/imports/api/common/server/helpers.js +++ b/bigbluebutton-html5/imports/api/common/server/helpers.js @@ -1,6 +1,7 @@ import Users from '/imports/api/users'; import Logger from '/imports/startup/server/logger'; import RegexWebUrl from '/imports/utils/regex-weburl'; +import { BREAK_LINE } from '/imports/utils/lineEndings'; const MSG_DIRECT_TYPE = 'DIRECT'; const NODE_USER = 'nodeJSapp'; @@ -25,6 +26,13 @@ export const parseMessage = (message) => { // Replace flash links to flash valid ones parsedMessage = parsedMessage.replace(RegexWebUrl, "$&"); + // Replace flash links to html valid ones + parsedMessage = parsedMessage.split(' - val numericValue = number.toInt - unit match { - case "" | "px" | "pt" => Some(numericValue) - case _ => None - } - case _ => None - } - } - val id = presId + "/" + page val current = if (page == 1) true else false val thumbUrl = presBaseUrl + "/thumbnail/" + page From 4c863cf2b4798388d0d5c3ae344845cbee586c75 Mon Sep 17 00:00:00 2001 From: Paul Trudel Date: Fri, 18 Aug 2023 14:57:23 +0000 Subject: [PATCH 227/252] Remove comments from testing --- .../src/main/scala/org/bigbluebutton/api2/MsgBuilder.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/MsgBuilder.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/MsgBuilder.scala index 7dc7d89179..af5efd4afc 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/MsgBuilder.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/MsgBuilder.scala @@ -79,8 +79,7 @@ object MsgBuilder { val pngUrl = presBaseUrl + "/png/" + page val urls = Map("thumb" -> thumbUrl, "text" -> txtUrl, "svg" -> svgUrl, "png" -> pngUrl) - - println("***** ATTEMPTING TO DETERMINE PAGE DIMENSIONS FOR " + svgUrl + "*****") + try { val imgUrl = new URL(svgUrl) val imgContent = XML.load(imgUrl) From 0463531aba074a27ef237ba7eadb5357bd6f8f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Fri, 18 Aug 2023 15:30:23 -0300 Subject: [PATCH 228/252] remove chat list item border --- .../user-messages/chat-list/chat-list-item/styles.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/chat-list/chat-list-item/styles.ts b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/chat-list/chat-list-item/styles.ts index e20b723fda..b32907fe72 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/chat-list/chat-list-item/styles.ts +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-messages/chat-list/chat-list-item/styles.ts @@ -158,6 +158,7 @@ const ChatListItem = styled.button` border-top-right-radius: 0; border-bottom-right-radius: 0; cursor: pointer; + border-color: transparent; [dir="rtl"] & { border-top-left-radius: 0; From 30b911b777795e734a85f512f13a0e4a3012f479 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sun, 20 Aug 2023 05:28:08 +0000 Subject: [PATCH 229/252] Translate en.json in ja 100% translated source file: 'en.json' on 'ja'. --- bigbluebutton-html5/public/locales/ja.json | 1 + 1 file changed, 1 insertion(+) diff --git a/bigbluebutton-html5/public/locales/ja.json b/bigbluebutton-html5/public/locales/ja.json index 935846c81f..9a69743d04 100644 --- a/bigbluebutton-html5/public/locales/ja.json +++ b/bigbluebutton-html5/public/locales/ja.json @@ -641,6 +641,7 @@ "app.actionsBar.reactions.reactionsButtonLabel": "リアクションバー", "app.actionsBar.reactions.raiseHand": "手をあげる", "app.actionsBar.reactions.lowHand": "手をおろす", + "app.actionsBar.reactions.autoCloseReactionsBarLabel": "リアクションバーを自動で隠す", "app.actionsBar.emojiMenu.statusTriggerLabel": "ステータス設定", "app.actionsBar.emojiMenu.awayLabel": "不在", "app.actionsBar.emojiMenu.awayDesc": "スタータスを「不在」にする", From b0214933974c3d7ab5e5edebead92a29a015246f Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Mon, 21 Aug 2023 09:01:55 -0300 Subject: [PATCH 230/252] Update path of hasura --- bbb-graphql-server/update_graphql_data.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbb-graphql-server/update_graphql_data.sh b/bbb-graphql-server/update_graphql_data.sh index d7b8795006..6792105ca7 100755 --- a/bbb-graphql-server/update_graphql_data.sh +++ b/bbb-graphql-server/update_graphql_data.sh @@ -34,4 +34,4 @@ if [ "$akka_apps_status" = "active" ]; then fi echo "Applying new metadata to Hasura" -/usr/local/bin/hasura metadata apply \ No newline at end of file +/usr/local/bin/hasura/hasura metadata apply From 39c5954614d003c6152ed2a934ab43430fc2ac77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Mon, 21 Aug 2023 10:06:07 -0300 Subject: [PATCH 231/252] markdown chat messages --- .../server/methods/sendGroupChatMsg.js | 4 +- .../chat-message-form/component.tsx | 3 +- .../text-content/component.tsx | 5 +- bigbluebutton-html5/package-lock.json | 758 +++++++++++++++--- bigbluebutton-html5/package.json | 3 +- 5 files changed, 661 insertions(+), 112 deletions(-) diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/sendGroupChatMsg.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/sendGroupChatMsg.js index f4caf78df6..db6e19ca1d 100644 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/sendGroupChatMsg.js +++ b/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/sendGroupChatMsg.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import RedisPubSub from '/imports/startup/server/redis'; -import { extractCredentials, parseMessage } from '/imports/api/common/server/helpers'; +import { extractCredentials } from '/imports/api/common/server/helpers'; import Logger from '/imports/startup/server/logger'; export default function sendGroupChatMsg(chatId, message) { @@ -17,8 +17,6 @@ export default function sendGroupChatMsg(chatId, message) { check(requesterUserId, String); check(chatId, String); check(message, Object); - const parsedMessage = parseMessage(message.message); - message.message = parsedMessage; const payload = { msg: message, diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-form/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-form/component.tsx index 66bdc821fa..229e036585 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-form/component.tsx +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-form/component.tsx @@ -4,7 +4,6 @@ import { defineMessages, useIntl } from 'react-intl'; import { isChatEnabled } from '/imports/ui/services/features'; import ClickOutside from '/imports/ui/components/click-outside/component'; import Styled from './styles'; -import { escapeHtml } from '/imports/utils/string-utils'; import { checkText } from 'smile2emoji'; import deviceInfo from '/imports/utils/deviceInfo'; import { usePreviousValue } from '/imports/ui/components/utils/hooks'; @@ -187,7 +186,7 @@ const ChatMessageForm: React.FC = ({ return; } - handleSendMessage(escapeHtml(msg), chatId); + handleSendMessage(msg, chatId); setMessage(''); updateUnreadMessages(chatId, ''); setHasErrors(false); diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx index 234e956f54..a2b4767dd2 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx @@ -1,4 +1,5 @@ import React from "react"; +import ReactMarkdown from 'react-markdown'; import Styled from './styles'; interface ChatMessageTextContentProps { text: string; @@ -11,7 +12,9 @@ const ChatMessageTextContent: React.FC = ({ }) => { return ( - {text} + + {text} + ); }; diff --git a/bigbluebutton-html5/package-lock.json b/bigbluebutton-html5/package-lock.json index 1ed31f7bdf..8e023de8d5 100644 --- a/bigbluebutton-html5/package-lock.json +++ b/bigbluebutton-html5/package-lock.json @@ -1928,6 +1928,22 @@ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==" }, + "@types/debug": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", + "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", + "requires": { + "@types/ms": "*" + } + }, + "@types/hast": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.5.tgz", + "integrity": "sha512-SvQi0L/lNpThgPoleH53cdjB3y9zpLlVjRbqB3rH8hx1jiRSBGAhyjV3H+URFjNVRqt2EdYNrbZE5IsGlNfpRg==", + "requires": { + "@types/unist": "^2" + } + }, "@types/hoist-non-react-statics": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", @@ -1949,6 +1965,19 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "@types/mdast": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.12.tgz", + "integrity": "sha512-DT+iNIRNX884cx0/Q1ja7NyUPpZuv0KPyL5rGNxm1WC1OtHstl7n4Jb7nk+xacNShQMbczJjt8uFzznpp6kYBg==", + "requires": { + "@types/unist": "^2" + } + }, + "@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -2020,6 +2049,11 @@ "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz", "integrity": "sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g==" }, + "@types/unist": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.7.tgz", + "integrity": "sha512-cputDpIbFgLUaGQn6Vqg3/YsJwxUwHLO13v3i5ouxT4lat0khip9AEWxtERujXV9wxIB1EyF97BSJFt6vpdI8g==" + }, "@types/uuid": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.2.tgz", @@ -2542,6 +2576,11 @@ "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==" }, + "bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==" + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2691,6 +2730,11 @@ } } }, + "character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==" + }, "charenc": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", @@ -2827,6 +2871,11 @@ "text-hex": "1.0.x" } }, + "comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" + }, "commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", @@ -3032,6 +3081,14 @@ "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" }, + "decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "requires": { + "character-entities": "^2.0.0" + } + }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -3070,8 +3127,7 @@ "dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" }, "detect-libc": { "version": "1.0.3", @@ -3814,6 +3870,11 @@ "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==" }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4199,6 +4260,11 @@ "has-symbols": "^1.0.2" } }, + "hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==" + }, "hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -4367,6 +4433,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + }, "internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", @@ -4437,6 +4508,11 @@ "has-tostringtag": "^1.0.0" } }, + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" + }, "is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -4523,6 +4599,11 @@ "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, + "is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==" + }, "is-plain-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", @@ -4675,6 +4756,11 @@ "object.values": "^1.1.6" } }, + "kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==" + }, "kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -4951,6 +5037,58 @@ "custom-event-polyfill": "~0.3" } }, + "mdast-util-definitions": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", + "requires": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + } + }, + "mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "requires": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + } + }, + "mdast-util-to-hast": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", + "requires": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-definitions": "^5.0.0", + "micromark-util-sanitize-uri": "^1.1.0", + "trim-lines": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + } + }, + "mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "requires": { + "@types/mdast": "^3.0.0" + } + }, "memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", @@ -5001,7 +5139,7 @@ "dependencies": { "asn1.js": { "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "resolved": false, "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", "requires": { "bn.js": "^4.0.0", @@ -5012,14 +5150,14 @@ "dependencies": { "bn.js": { "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "resolved": false, "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" } } }, "assert": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", + "resolved": false, "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", "requires": { "es6-object-assign": "^1.1.0", @@ -5030,27 +5168,27 @@ }, "available-typed-arrays": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz", + "resolved": false, "integrity": "sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA==" }, "base64-js": { "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "resolved": false, "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "bn.js": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", + "resolved": false, "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" }, "brorand": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "resolved": false, "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" }, "browserify-aes": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": false, "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "requires": { "buffer-xor": "^1.0.3", @@ -5063,7 +5201,7 @@ }, "browserify-cipher": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "resolved": false, "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", "requires": { "browserify-aes": "^1.0.4", @@ -5073,7 +5211,7 @@ }, "browserify-des": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "resolved": false, "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", "requires": { "cipher-base": "^1.0.1", @@ -5084,7 +5222,7 @@ }, "browserify-rsa": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "resolved": false, "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", "requires": { "bn.js": "^5.0.0", @@ -5093,7 +5231,7 @@ }, "browserify-sign": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "resolved": false, "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", "requires": { "bn.js": "^5.1.1", @@ -5109,7 +5247,7 @@ }, "browserify-zlib": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "resolved": false, "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "requires": { "pako": "~1.0.5" @@ -5126,17 +5264,17 @@ }, "buffer-xor": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "resolved": false, "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" }, "builtin-status-codes": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "resolved": false, "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" }, "call-bind": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "resolved": false, "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "requires": { "function-bind": "^1.1.1", @@ -5145,7 +5283,7 @@ }, "cipher-base": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "resolved": false, "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", "requires": { "inherits": "^2.0.1", @@ -5154,17 +5292,17 @@ }, "console-browserify": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "resolved": false, "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==" }, "constants-browserify": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "resolved": false, "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" }, "create-ecdh": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "resolved": false, "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", "requires": { "bn.js": "^4.1.0", @@ -5173,14 +5311,14 @@ "dependencies": { "bn.js": { "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "resolved": false, "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" } } }, "create-hash": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": false, "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "requires": { "cipher-base": "^1.0.1", @@ -5192,7 +5330,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": false, "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "requires": { "cipher-base": "^1.0.3", @@ -5205,7 +5343,7 @@ }, "crypto-browserify": { "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "resolved": false, "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", "requires": { "browserify-cipher": "^1.0.0", @@ -5223,7 +5361,7 @@ }, "define-properties": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "resolved": false, "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "requires": { "object-keys": "^1.0.12" @@ -5231,7 +5369,7 @@ }, "des.js": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "resolved": false, "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", "requires": { "inherits": "^2.0.1", @@ -5240,7 +5378,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": false, "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "requires": { "bn.js": "^4.1.0", @@ -5250,7 +5388,7 @@ "dependencies": { "bn.js": { "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "resolved": false, "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" } } @@ -5262,7 +5400,7 @@ }, "elliptic": { "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "resolved": false, "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "requires": { "bn.js": "^4.11.9", @@ -5276,14 +5414,14 @@ "dependencies": { "bn.js": { "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "resolved": false, "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" } } }, "es-abstract": { "version": "1.18.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", + "resolved": false, "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", "requires": { "call-bind": "^1.0.2", @@ -5306,7 +5444,7 @@ }, "es-to-primitive": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "resolved": false, "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "requires": { "is-callable": "^1.1.4", @@ -5316,17 +5454,17 @@ }, "es6-object-assign": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "resolved": false, "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=" }, "events": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "resolved": false, "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, "evp_bytestokey": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "resolved": false, "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "requires": { "md5.js": "^1.3.4", @@ -5335,17 +5473,17 @@ }, "foreach": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "resolved": false, "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" }, "function-bind": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "resolved": false, "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "get-intrinsic": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "resolved": false, "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", "requires": { "function-bind": "^1.1.1", @@ -5355,7 +5493,7 @@ }, "has": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "resolved": false, "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "requires": { "function-bind": "^1.1.1" @@ -5363,17 +5501,17 @@ }, "has-bigints": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "resolved": false, "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" }, "has-symbols": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "resolved": false, "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" }, "hash-base": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "resolved": false, "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", "requires": { "inherits": "^2.0.4", @@ -5383,7 +5521,7 @@ }, "hash.js": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "resolved": false, "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "requires": { "inherits": "^2.0.3", @@ -5392,7 +5530,7 @@ }, "hmac-drbg": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "resolved": false, "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "requires": { "hash.js": "^1.0.3", @@ -5402,22 +5540,22 @@ }, "https-browserify": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "resolved": false, "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" }, "ieee754": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "resolved": false, "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "resolved": false, "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "is-arguments": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", + "resolved": false, "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", "requires": { "call-bind": "^1.0.0" @@ -5425,12 +5563,12 @@ }, "is-bigint": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", + "resolved": false, "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==" }, "is-boolean-object": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", + "resolved": false, "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", "requires": { "call-bind": "^1.0.2" @@ -5438,22 +5576,22 @@ }, "is-callable": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "resolved": false, "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==" }, "is-date-object": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", + "resolved": false, "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==" }, "is-generator-function": { "version": "1.0.9", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.9.tgz", + "resolved": false, "integrity": "sha512-ZJ34p1uvIfptHCN7sFTjGibB9/oBg17sHqzDLfuwhvmN/qLVvIQXRQ8licZQ35WJ8KuEQt/etnnzQFI9C9Ue/A==" }, "is-nan": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "resolved": false, "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", "requires": { "call-bind": "^1.0.0", @@ -5462,17 +5600,17 @@ }, "is-negative-zero": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "resolved": false, "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==" }, "is-number-object": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", + "resolved": false, "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==" }, "is-regex": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", + "resolved": false, "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", "requires": { "call-bind": "^1.0.2", @@ -5481,12 +5619,12 @@ }, "is-string": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", + "resolved": false, "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==" }, "is-symbol": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "resolved": false, "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "requires": { "has-symbols": "^1.0.2" @@ -5494,7 +5632,7 @@ }, "is-typed-array": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.5.tgz", + "resolved": false, "integrity": "sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug==", "requires": { "available-typed-arrays": "^1.0.2", @@ -5506,7 +5644,7 @@ }, "md5.js": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "resolved": false, "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "requires": { "hash-base": "^3.0.0", @@ -5516,7 +5654,7 @@ }, "miller-rabin": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "resolved": false, "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "requires": { "bn.js": "^4.0.0", @@ -5525,29 +5663,29 @@ "dependencies": { "bn.js": { "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "resolved": false, "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" } } }, "minimalistic-assert": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "resolved": false, "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "minimalistic-crypto-utils": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "resolved": false, "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" }, "object-inspect": { "version": "1.10.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", + "resolved": false, "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==" }, "object-is": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "resolved": false, "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", "requires": { "call-bind": "^1.0.2", @@ -5556,12 +5694,12 @@ }, "object-keys": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "resolved": false, "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object.assign": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "resolved": false, "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", "requires": { "call-bind": "^1.0.0", @@ -5572,17 +5710,17 @@ }, "os-browserify": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "resolved": false, "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" }, "pako": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "resolved": false, "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "parse-asn1": { "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "resolved": false, "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", "requires": { "asn1.js": "^5.2.0", @@ -5594,12 +5732,12 @@ }, "path-browserify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "resolved": false, "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" }, "pbkdf2": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "resolved": false, "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", "requires": { "create-hash": "^1.1.2", @@ -5611,12 +5749,12 @@ }, "process": { "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "resolved": false, "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" }, "public-encrypt": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "resolved": false, "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", "requires": { "bn.js": "^4.1.0", @@ -5629,7 +5767,7 @@ "dependencies": { "bn.js": { "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "resolved": false, "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" } } @@ -5641,17 +5779,17 @@ }, "querystring": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "resolved": false, "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, "querystring-es3": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "resolved": false, "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" }, "randombytes": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "resolved": false, "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "requires": { "safe-buffer": "^5.1.0" @@ -5659,7 +5797,7 @@ }, "randomfill": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "resolved": false, "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "requires": { "randombytes": "^2.0.5", @@ -5668,7 +5806,7 @@ }, "readable-stream": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "resolved": false, "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { "inherits": "^2.0.3", @@ -5678,7 +5816,7 @@ }, "ripemd160": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "resolved": false, "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", "requires": { "hash-base": "^3.0.0", @@ -5687,22 +5825,22 @@ }, "safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "resolved": false, "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "resolved": false, "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "setimmediate": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "resolved": false, "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "sha.js": { "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": false, "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "requires": { "inherits": "^2.0.1", @@ -5711,7 +5849,7 @@ }, "stream-browserify": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "resolved": false, "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", "requires": { "inherits": "~2.0.4", @@ -5720,7 +5858,7 @@ }, "stream-http": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", + "resolved": false, "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", "requires": { "builtin-status-codes": "^3.0.0", @@ -5731,7 +5869,7 @@ }, "string.prototype.trimend": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "resolved": false, "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", "requires": { "call-bind": "^1.0.2", @@ -5740,7 +5878,7 @@ }, "string.prototype.trimstart": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "resolved": false, "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", "requires": { "call-bind": "^1.0.2", @@ -5749,7 +5887,7 @@ }, "string_decoder": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "resolved": false, "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "requires": { "safe-buffer": "~5.2.0" @@ -5757,7 +5895,7 @@ }, "timers-browserify": { "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "resolved": false, "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", "requires": { "setimmediate": "^1.0.4" @@ -5765,12 +5903,12 @@ }, "tty-browserify": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "resolved": false, "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==" }, "unbox-primitive": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "resolved": false, "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", "requires": { "function-bind": "^1.1.1", @@ -5781,7 +5919,7 @@ }, "url": { "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "resolved": false, "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", "requires": { "punycode": "1.3.2", @@ -5790,14 +5928,14 @@ "dependencies": { "punycode": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "resolved": false, "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" } } }, "util": { "version": "0.12.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", + "resolved": false, "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", "requires": { "inherits": "^2.0.3", @@ -5810,17 +5948,17 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "resolved": false, "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "vm-browserify": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "resolved": false, "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" }, "which-boxed-primitive": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "resolved": false, "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", "requires": { "is-bigint": "^1.0.1", @@ -5832,7 +5970,7 @@ }, "which-typed-array": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz", + "resolved": false, "integrity": "sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA==", "requires": { "available-typed-arrays": "^1.0.2", @@ -5846,11 +5984,237 @@ }, "xtend": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "resolved": false, "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" } } }, + "micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "requires": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "requires": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "requires": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "requires": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-decode-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "requires": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==" + }, + "micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==" + }, + "micromark-util-normalize-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "requires": { + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-subtokenize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "requires": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==" + }, + "micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==" + }, "micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -5887,6 +6251,11 @@ "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.4.2.tgz", "integrity": "sha512-b4xQJYiH8sb0sEbfq/Ws3N77DEJtSihUFD1moeiz2jNoJ5B+mqJutt54ouO9iEfkp7Wk4jQDsVUOh7DPEW3wEw==" }, + "mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==" + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -6319,6 +6688,11 @@ "react-is": "^16.13.1" } }, + "property-information": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz", + "integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==" + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -6484,6 +6858,35 @@ "resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.3.1.tgz", "integrity": "sha512-NilqqwMh2v9omN7LteiDloEVpFyMIa0VGqF+ukqp0ncVlYu1sKYbYGX9JEl+GtOT9TKsh04zCHAbavnQ2USldA==" }, + "react-markdown": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz", + "integrity": "sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==", + "requires": { + "@types/hast": "^2.0.0", + "@types/prop-types": "^15.0.0", + "@types/unist": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^2.0.0", + "prop-types": "^15.0.0", + "property-information": "^6.0.0", + "react-is": "^18.0.0", + "remark-parse": "^10.0.0", + "remark-rehype": "^10.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.4.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0" + }, + "dependencies": { + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + } + } + }, "react-modal": { "version": "3.16.1", "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.1.tgz", @@ -6768,6 +7171,27 @@ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, + "remark-parse": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", + "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" + } + }, + "remark-rehype": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz", + "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", + "requires": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-to-hast": "^12.1.0", + "unified": "^10.0.0" + } + }, "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -6849,6 +7273,14 @@ "tslib": "^2.1.0" } }, + "sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "requires": { + "mri": "^1.1.0" + } + }, "safe-array-concat": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", @@ -7059,6 +7491,11 @@ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" }, + "space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" + }, "spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -7253,6 +7690,14 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "style-to-object": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.2.tgz", + "integrity": "sha512-1JGpfPB3lo42ZX8cuPrheZbfQ6kqPPnPHlKMyeRYtfKD+0jG+QsXgXN57O/dvJlzlB2elI6dGmrPnl5VPQFPaA==", + "requires": { + "inline-style-parser": "0.1.1" + } + }, "styled-components": { "version": "5.3.11", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz", @@ -7414,11 +7859,21 @@ "is-number": "^7.0.0" } }, + "trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==" + }, "triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==" }, + "trough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==" + }, "ts-api-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", @@ -7549,6 +8004,68 @@ "which-boxed-primitive": "^1.0.2" } }, + "unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "requires": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + } + }, + "unist-util-generated": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", + "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==" + }, + "unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "requires": { + "@types/unist": "^2.0.0" + } + }, + "unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "requires": { + "@types/unist": "^2.0.0" + } + }, + "unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "requires": { + "@types/unist": "^2.0.0" + } + }, + "unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + } + }, + "unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + } + }, "update-browserslist-db": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", @@ -7609,6 +8126,17 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" }, + "uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "requires": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + } + }, "v8-compile-cache": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", @@ -7625,6 +8153,26 @@ "spdx-expression-parse": "^3.0.0" } }, + "vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "requires": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + } + }, + "vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + } + }, "victory-vendor": { "version": "36.6.11", "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.6.11.tgz", diff --git a/bigbluebutton-html5/package.json b/bigbluebutton-html5/package.json index bfc7edd6a3..dfeb78909f 100644 --- a/bigbluebutton-html5/package.json +++ b/bigbluebutton-html5/package.json @@ -79,6 +79,7 @@ "react-dropzone": "^7.0.1", "react-intl": "^6.1.0", "react-loading-skeleton": "^3.0.3", + "react-markdown": "^8.0.7", "react-modal": "^3.15.1", "react-player": "^2.10.0", "react-tabs": "^4.2.1", @@ -99,8 +100,8 @@ "subscriptions-transport-ws": "^0.11.0", "tippy.js": "^5.1.3", "use-context-selector": "^1.3.7", - "wasm-feature-detect": "^1.5.1", "uuid": "^9.0.0", + "wasm-feature-detect": "^1.5.1", "webrtc-adapter": "^8.1.1", "winston": "^3.7.2", "yaml": "^2.2.2" From 2d81936a57e837b8d2284ae758afd57a4c1e9b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Mon, 21 Aug 2023 13:39:23 -0300 Subject: [PATCH 232/252] fix new lines and links --- .../imports/api/common/server/helpers.js | 13 +++++++++++++ .../server/methods/sendGroupChatMsg.js | 3 ++- .../message-content/text-content/component.tsx | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-html5/imports/api/common/server/helpers.js b/bigbluebutton-html5/imports/api/common/server/helpers.js index 56a4142314..d31bf287d8 100755 --- a/bigbluebutton-html5/imports/api/common/server/helpers.js +++ b/bigbluebutton-html5/imports/api/common/server/helpers.js @@ -28,6 +28,19 @@ export const parseMessage = (message) => { return parsedMessage; }; +export const textToMarkdown = (message) => { + let parsedMessage = message || ''; + parsedMessage = parsedMessage.trim(); + + // replace url with markdown links + const urlRegex = /(? { diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/sendGroupChatMsg.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/sendGroupChatMsg.js index db6e19ca1d..179b8444e4 100644 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/sendGroupChatMsg.js +++ b/bigbluebutton-html5/imports/api/group-chat-msg/server/methods/sendGroupChatMsg.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import RedisPubSub from '/imports/startup/server/redis'; -import { extractCredentials } from '/imports/api/common/server/helpers'; +import { extractCredentials, textToMarkdown } from '/imports/api/common/server/helpers'; import Logger from '/imports/startup/server/logger'; export default function sendGroupChatMsg(chatId, message) { @@ -17,6 +17,7 @@ export default function sendGroupChatMsg(chatId, message) { check(requesterUserId, String); check(chatId, String); check(message, Object); + message.message = textToMarkdown(message.message); const payload = { msg: message, diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx index a2b4767dd2..a9690ff05d 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx @@ -12,7 +12,7 @@ const ChatMessageTextContent: React.FC = ({ }) => { return ( - + {text} From aae22e06bc9295246f792f77a208bdd74d9101fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Mon, 21 Aug 2023 16:38:00 -0300 Subject: [PATCH 233/252] adjust text and image position --- .../chat-message/message-content/text-content/styles.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/styles.ts b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/styles.ts index 5ac698cade..1b153d61be 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/styles.ts +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/styles.ts @@ -5,6 +5,7 @@ export const ChatMessage = styled.div` flex: 1; display: flex; flex-flow: row; + flex-direction: column; color: ${colorText}; word-break: break-word; margin-left: 2.75rem; @@ -13,6 +14,11 @@ export const ChatMessage = styled.div` ` font-weight: bold; `} + + & img { + max-width: 100%; + max-height: 100%; + } `; export default { From bbb6f54a2f3a17c19b3ff489fa1f5b5996cda39f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Mon, 21 Aug 2023 17:08:33 -0300 Subject: [PATCH 234/252] add settings for allowed elements --- .../message-content/text-content/component.tsx | 8 +++++++- bigbluebutton-html5/private/config/settings.yml | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx index a9690ff05d..d150607532 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx @@ -10,9 +10,15 @@ const ChatMessageTextContent: React.FC = ({ text, emphasizedMessage, }) => { + // @ts-ignore - temporary, while meteor exists in the project + const { allowedElements } = Meteor.settings.public.chat.allowedElements; + return ( - + {text} diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index cfd43a493b..d6f06f56ee 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -594,6 +594,7 @@ public: frequentEmojiSortOnClick: false # e.g.: disableEmojis: ['1F595','1F922'] disableEmojis: [] + allowedElements: ['a', 'code', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img', 'li', 'ol', 'ul', 'p', 'strong'] userReaction: enabled: true expire: 60 From 116b00f56d1fbab347555a598a7520602ce3ed63 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 21:38:59 +0000 Subject: [PATCH 235/252] Translate en.json in eu 100% translated source file: 'en.json' on 'eu'. --- bigbluebutton-html5/public/locales/eu.json | 1 + 1 file changed, 1 insertion(+) diff --git a/bigbluebutton-html5/public/locales/eu.json b/bigbluebutton-html5/public/locales/eu.json index 2404fe1ac0..6bc43ccaff 100644 --- a/bigbluebutton-html5/public/locales/eu.json +++ b/bigbluebutton-html5/public/locales/eu.json @@ -641,6 +641,7 @@ "app.actionsBar.reactions.reactionsButtonLabel": "Erreakzioen barra", "app.actionsBar.reactions.raiseHand": "Altxatu eskua", "app.actionsBar.reactions.lowHand": "Jaitsi eskua", + "app.actionsBar.reactions.autoCloseReactionsBarLabel": "Itxi automatikoki erreakzio-barra", "app.actionsBar.emojiMenu.statusTriggerLabel": "Ezarri egoera", "app.actionsBar.emojiMenu.awayLabel": "Kanpoan", "app.actionsBar.emojiMenu.awayDesc": "Aldatu zure egoera: kanpoan", From d55d2e0957fdb60ace7ef54b0a7f81e753146a16 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Tue, 22 Aug 2023 14:41:58 +0000 Subject: [PATCH 236/252] Translate en.json in eu 100% translated source file: 'en.json' on 'eu'. --- bigbluebutton-html5/public/locales/eu.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bigbluebutton-html5/public/locales/eu.json b/bigbluebutton-html5/public/locales/eu.json index 6bc43ccaff..dc50053c63 100644 --- a/bigbluebutton-html5/public/locales/eu.json +++ b/bigbluebutton-html5/public/locales/eu.json @@ -870,11 +870,10 @@ "app.toast.meetingMuteOn.label": "Erabiltzaile guztiak isilarazi dira", "app.toast.meetingMuteOnViewers.label": "Ikusle guztiak isilarazi dira", "app.toast.meetingMuteOff.label": "Bileraren isilaraztea itzali da", - "app.toast.wakeLock.offerTitle": "Bileran zehar zure gailuaren pantaila aktibo mantendu nahi duzu?", - "app.toast.wakeLock.offerAccept": "Bai!", - "app.toast.wakeLock.offerDecline": "Orain ez", "app.toast.wakeLock.acquireSuccess": "Pantaila beti aktiboa martxan! Ezarpenen menuan desaktibatu dezakezu.", - "app.toast.wakeLock.acquireFailed": "Errore bat gertatu da pantaila beti aktiboa eskuratzean.", + "app.toast.wakeLock.acquireFailed": "Errore bat gertatu da aktibazio blokeoa eskuratzean", + "app.toast.wakeLock.notSupported": "Aktibazio blokeoa ez da onartzen", + "app.toast.wakeLock.disclaimer": "{0}. Pantaila itzaltzen bada deitik aterako zara.", "app.toast.setEmoji.raiseHand": "Eskua altxatu duzu", "app.toast.setEmoji.lowerHand": "Zure eskua jaitsi dute", "app.toast.setEmoji.away": "Adierazi duzu kanpoan zaudela", From 23ecdc2c7be70c1702c8261c582c66c9d74e4260 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Tue, 22 Aug 2023 13:45:33 -0300 Subject: [PATCH 237/252] Fix talking indicator disappearing --- bbb-graphql-server/bbb_schema.sql | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/bbb-graphql-server/bbb_schema.sql b/bbb-graphql-server/bbb_schema.sql index f8663c7399..82b4af01a4 100644 --- a/bbb-graphql-server/bbb_schema.sql +++ b/bbb-graphql-server/bbb_schema.sql @@ -468,17 +468,22 @@ CREATE TABLE "user_voice" ( "startTime" bigint ); --CREATE INDEX "idx_user_voice_userId" ON "user_voice"("userId"); -ALTER TABLE "user_voice" ADD COLUMN "hideTalkingIndicatorAt" timestamp with time zone GENERATED ALWAYS AS (to_timestamp((COALESCE("endTime","startTime") + 6000) / 1000)) STORED; -CREATE INDEX "idx_user_voice_userId_talking" ON "user_voice"("userId","hideTalkingIndicatorAt","startTime"); +ALTER TABLE "user_voice" ADD COLUMN "hideTalkingIndicatorAt" timestamp with time zone +GENERATED ALWAYS AS (to_timestamp((COALESCE("endTime","startTime") + 6000) / 1000)) STORED; + +CREATE INDEX "idx_user_voice_userId_talking" ON "user_voice"("userId","talking"); +CREATE INDEX "idx_user_voice_userId_hideTalkingIndicatorAt" ON "user_voice"("userId","hideTalkingIndicatorAt"); CREATE OR REPLACE VIEW "v_user_voice" AS SELECT u."meetingId", "user_voice" .*, greatest(coalesce(user_voice."startTime", 0), coalesce(user_voice."endTime", 0)) AS "lastSpeakChangedAt", - case when "hideTalkingIndicatorAt" > current_timestamp then true else false end "showTalkingIndicator" + user_talking."userId" IS NOT NULL "showTalkingIndicator" FROM "user" u -JOIN "user_voice" ON u."userId" = "user_voice"."userId"; +JOIN "user_voice" ON "user_voice"."userId" = u."userId" +LEFT JOIN "user_voice" user_talking ON (user_talking."userId" = u."userId" and user_talking."talking" IS TRUE) + OR (user_talking."userId" = u."userId" and user_talking."hideTalkingIndicatorAt" > now()); CREATE TABLE "user_camera" ( "streamId" varchar(100) PRIMARY KEY, From 8cbe878e4ca5103640df1743a868c8310c41025f Mon Sep 17 00:00:00 2001 From: imdt Date: Tue, 22 Aug 2023 16:39:16 -0300 Subject: [PATCH 238/252] Fix: typescript errors --- .../app/app-graphql/time-sync/component.tsx | 2 +- .../chat-message-form/component.tsx | 57 ++++++++++--------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/app/app-graphql/time-sync/component.tsx b/bigbluebutton-html5/imports/ui/components/app/app-graphql/time-sync/component.tsx index 492ce782ea..c5be9da268 100644 --- a/bigbluebutton-html5/imports/ui/components/app/app-graphql/time-sync/component.tsx +++ b/bigbluebutton-html5/imports/ui/components/app/app-graphql/time-sync/component.tsx @@ -23,4 +23,4 @@ const TimeSync: React.FC = () => { return null; }; -export default TimeSync; \ No newline at end of file +export default TimeSync; diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-form/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-form/component.tsx index 66bdc821fa..97ba118432 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-form/component.tsx +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-form/component.tsx @@ -3,6 +3,7 @@ import { layoutSelect } from '/imports/ui/components/layout/context'; import { defineMessages, useIntl } from 'react-intl'; import { isChatEnabled } from '/imports/ui/services/features'; import ClickOutside from '/imports/ui/components/click-outside/component'; +import TextareaAutosize from 'react-autosize-textarea'; import Styled from './styles'; import { escapeHtml } from '/imports/utils/string-utils'; import { checkText } from 'smile2emoji'; @@ -19,7 +20,6 @@ import { Layout } from '../../../layout/layoutTypes'; import { useMeeting } from '/imports/ui/core/hooks/useMeeting'; import Events from '/imports/ui/core/events/events'; import ChatOfflineIndicator from './chat-offline-indicator/component'; -import TextareaAutosize from 'react-autosize-textarea'; interface ChatMessageFormProps { minMessageLength: number, @@ -31,7 +31,9 @@ interface ChatMessageFormProps { locked: boolean, partnerIsLoggedOut: boolean, title: string, - handleClickOutside: Function, + handleEmojiSelect: (emojiObject: () => void, + { native: string }) => void; + handleClickOutside: () => void, } const messages = defineMessages({ @@ -195,11 +197,11 @@ const ChatMessageForm: React.FC = ({ if (ENABLE_TYPING_INDICATOR) stopUserTyping(); const sentMessageEvent = new CustomEvent(Events.SENT_MESSAGE); window.dispatchEvent(sentMessageEvent); - } + }; - const handleEmojiSelect = (emojiObject: { native: string }) => { + const handleEmojiSelect = (emojiObject: { native: string }): void => { const txtArea = textAreaRef?.current?.textarea; - if(!txtArea) return; + if (!txtArea) return; const cursor = txtArea.selectionStart; setMessage( @@ -212,7 +214,7 @@ const ChatMessageForm: React.FC = ({ setTimeout(() => txtArea.setSelectionRange(newCursor, newCursor), 10); } - const handleMessageChange = (e: ChangeEvent) => { + const handleMessageChange = (e: ChangeEvent) => { let newMessage = null; let newError = null; if (AUTO_CONVERT_EMOJI) { @@ -229,17 +231,17 @@ const ChatMessageForm: React.FC = ({ newMessage = newMessage.substring(0, maxMessageLength); } + const handleUserTyping = (hasError?: boolean) => { + if (hasError || !ENABLE_TYPING_INDICATOR) return; + startUserTyping(chatId); + }; + setMessage(newMessage); setError(newError); - handleUserTyping(newError!=null) - } + handleUserTyping(newError != null); + }; - const handleUserTyping = (hasError?: boolean) => { - if (hasError || !ENABLE_TYPING_INDICATOR) return; - startUserTyping(chatId); - } - - const handleMessageKeyDown = (e: React.KeyboardEvent) => { + const handleMessageKeyDown = (e: React.KeyboardEvent) => { // TODO Prevent send message pressing enter on mobile and/or virtual keyboard if (e.keyCode === 13 && !e.shiftKey) { e.preventDefault(); @@ -251,10 +253,10 @@ const ChatMessageForm: React.FC = ({ handleSubmit(event); } - } + }; const renderForm = () => { - const formRef = useRef(); + const formRef = useRef(null); return ( { - return { - lockSettings: { - hasActiveLockSetting: m?.lockSettings?.hasActiveLockSetting, - disablePublicChat: m?.lockSettings?.disablePublicChat, - disablePrivateChat: m?.lockSettings?.disablePrivateChat, - } - }; - }); + const meeting = useMeeting((m) => ({ + lockSettings: { + hasActiveLockSetting: m?.lockSettings?.hasActiveLockSetting, + disablePublicChat: m?.lockSettings?.disablePublicChat, + disablePrivateChat: m?.lockSettings?.disablePrivateChat, + }, + })); const locked = chat?.public ? meeting?.lockSettings?.disablePublicChat @@ -387,7 +386,8 @@ const ChatMessageFormContainer: React.FC = ({ return ; } - return ; + /> +); }; export default ChatMessageFormContainer; From a3759f681f1648347b656e65a03ba1310677123b Mon Sep 17 00:00:00 2001 From: Paulo Lanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Tue, 22 Aug 2023 17:55:34 -0300 Subject: [PATCH 239/252] build(bbb-webrtc-sfu): use bbb-webrtc-recorder, drop kurento from config - Set bbb-webrtc-recorder as the default `recordingAdapter` in bbb-webrtc-sfu by default - Set `kurento` in SFU's config to an empty array; Kurento isn't provided by 2.8+ by default --- build/packages-template/bbb-webrtc-sfu/after-install.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build/packages-template/bbb-webrtc-sfu/after-install.sh b/build/packages-template/bbb-webrtc-sfu/after-install.sh index 4809464cf2..7383dbb447 100755 --- a/build/packages-template/bbb-webrtc-sfu/after-install.sh +++ b/build/packages-template/bbb-webrtc-sfu/after-install.sh @@ -43,7 +43,10 @@ case "$1" in touch /var/log/bbb-webrtc-sfu/bbb-webrtc-sfu.log yq e -i '.recordWebcams = true' $TARGET - + # Set bbb-webrtc-recorder as the default recordingAdapter + yq e -i '.recordingAdapter = "bbb-webrtc-recorder"' $TARGET + # Do not configure any Kurento instances - BBB >= 2.8 doesn't provide Kurento by default + yq e -i '.kurento = []' $TARGET echo "Resetting mcs-address from localhost to 127.0.0.1" yq e -i '.mcs-address = "127.0.0.1"' $TARGET From 8d3f8be6ff31bbfa758361dc7111dd906d26d386 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 23 Aug 2023 09:26:32 -0300 Subject: [PATCH 240/252] Fix creating database hasura_app twice --- build/packages-template/bbb-graphql-server/after-install.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/build/packages-template/bbb-graphql-server/after-install.sh b/build/packages-template/bbb-graphql-server/after-install.sh index cb13e04bea..feee2a633f 100755 --- a/build/packages-template/bbb-graphql-server/after-install.sh +++ b/build/packages-template/bbb-graphql-server/after-install.sh @@ -9,6 +9,7 @@ case "$1" in sudo -u postgres psql -c "drop database if exists bbb_graphql" sudo -u postgres psql -c "create database bbb_graphql" sudo -u postgres psql -c "alter database bbb_graphql set timezone to 'UTC'" + sudo -u postgres psql -U postgres -d bbb_graphql -a -f /usr/share/bbb-graphql-server/bbb_schema.sql --set ON_ERROR_STOP=on DATABASE_NAME="hasura_app" DB_EXISTS=$(sudo -u postgres psql -U postgres -tAc "SELECT 1 FROM pg_database WHERE datname='$DATABASE_NAME'") @@ -20,10 +21,6 @@ case "$1" in echo "Database $DATABASE_NAME created" fi - sudo -u postgres psql -U postgres -d bbb_graphql -a -f /usr/share/bbb-graphql-server/bbb_schema.sql --set ON_ERROR_STOP=on - sudo -u postgres psql -c "drop database if exists hasura_app" - - sudo -u postgres psql -c "create database hasura_app" echo "Postgresql configured" if [ ! -f /.dockerenv ]; then From ae9baeebb8b9fd440b3d7ac39128ae1cda5bdb9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Wed, 23 Aug 2023 09:32:04 -0300 Subject: [PATCH 241/252] update regex --- bigbluebutton-html5/imports/api/common/server/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/api/common/server/helpers.js b/bigbluebutton-html5/imports/api/common/server/helpers.js index 1c010da92f..5b3c7d91fb 100755 --- a/bigbluebutton-html5/imports/api/common/server/helpers.js +++ b/bigbluebutton-html5/imports/api/common/server/helpers.js @@ -41,7 +41,7 @@ export const textToMarkdown = (message) => { parsedMessage = parsedMessage.trim(); // replace url with markdown links - const urlRegex = /(? Date: Wed, 23 Aug 2023 11:09:43 -0300 Subject: [PATCH 242/252] CI: add comment PR workflow --- .github/workflows/publish-test-report.yml | 88 +++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 .github/workflows/publish-test-report.yml diff --git a/.github/workflows/publish-test-report.yml b/.github/workflows/publish-test-report.yml new file mode 100644 index 0000000000..6991c20d8a --- /dev/null +++ b/.github/workflows/publish-test-report.yml @@ -0,0 +1,88 @@ +name: Publish Test Results +on: + workflow_run: + workflows: + - Automated tests + types: + - completed + +jobs: + get-pr-data: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.event == 'pull_request' }} + outputs: + pr-number: ${{ steps.set-env.outputs.pr-number }} + workflow-id: ${{ steps.set-env.outputs.workflow-id }} + steps: + # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#using-data-from-the-triggering-workflow + - name: Download artifact + uses: actions/github-script@v6 + with: + script: | + let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.payload.workflow_run.id, + }); + + let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => { + return artifact.name == "pr-comment-data" + })[0]; + let download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip', + }); + let fs = require('fs'); + fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/pr-comment-data.zip`, Buffer.from(download.data)); + - name: Unzip artifact + run: unzip pr-comment-data.zip + - name: Set env variables + id: set-env + run: | + echo "pr-number=$(cat ./pr_number)" >> $GITHUB_OUTPUT + echo "workflow-id=$(cat ./workflow_id)" >> $GITHUB_OUTPUT + comment-pr: + runs-on: ubuntu-latest + permissions: + pull-requests: write + needs: get-pr-data + steps: + - name: Find Comment + uses: peter-evans/find-comment@v2 + id: fc + with: + issue-number: ${{ needs.get-pr-data.outputs.pr-number }} + comment-author: "github-actions[bot]" + body-includes: Automated tests Summary + - name: Remove previous comment + if: steps.fc.outputs.comment-id != '' + uses: actions/github-script@v6 + with: + script: | + github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: ${{ steps.fc.outputs.comment-id }} + }) + - name: Passing tests comment + if: ${{ github.event.workflow_run.conclusion == 'success' }} + uses: peter-evans/create-or-update-comment@v2 + with: + issue-number: ${{ needs.get-pr-data.outputs.pr-number }} + body: | +

Automated tests Summary

+

:white_check_mark: All the CI tests have passed!

+ - name: Failing tests comment + if: ${{ github.event.workflow_run.conclusion == 'failure' }} + uses: peter-evans/create-or-update-comment@v2 + with: + issue-number: ${{ needs.get-pr-data.outputs.pr-number }} + body: | +

Automated tests Summary

+

:rotating_light: Test workflow has failed

+ + ___ + + [Click here](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ needs.get-pr-data.outputs.workflow-id }}) to check the action test reports From 4a6d069e3e7989cd8df839ad0336f7abee8e7b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Wed, 23 Aug 2023 11:38:28 -0300 Subject: [PATCH 243/252] adjust paragraph margin --- .../page/chat-message/message-content/text-content/styles.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/styles.ts b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/styles.ts index 911b4e1189..7972d3e923 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/styles.ts +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/styles.ts @@ -18,6 +18,10 @@ export const ChatMessage = styled.div` max-width: 100%; max-height: 100%; } + + & p { + margin: 0; + } `; export default { From 3bbe40a38b163326e31ab166d2d6866715264cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Wed, 23 Aug 2023 11:46:18 -0300 Subject: [PATCH 244/252] fix disable element --- .../chat-message/message-content/text-content/component.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx index d150607532..bfa6dc4c1f 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/page/chat-message/message-content/text-content/component.tsx @@ -11,13 +11,14 @@ const ChatMessageTextContent: React.FC = ({ emphasizedMessage, }) => { // @ts-ignore - temporary, while meteor exists in the project - const { allowedElements } = Meteor.settings.public.chat.allowedElements; + const { allowedElements } = Meteor.settings.public.chat; return ( {text} From c49a59f92da489ee3a9cc7e0a5da8a8591d563bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Wed, 23 Aug 2023 13:19:54 -0300 Subject: [PATCH 245/252] auto replace image links with images --- bigbluebutton-html5/imports/api/common/server/helpers.js | 8 ++++++++ bigbluebutton-html5/private/config/settings.yml | 1 + 2 files changed, 9 insertions(+) diff --git a/bigbluebutton-html5/imports/api/common/server/helpers.js b/bigbluebutton-html5/imports/api/common/server/helpers.js index 5b3c7d91fb..b402e26ca9 100755 --- a/bigbluebutton-html5/imports/api/common/server/helpers.js +++ b/bigbluebutton-html5/imports/api/common/server/helpers.js @@ -37,9 +37,17 @@ export const parseMessage = (message) => { }; export const textToMarkdown = (message) => { + const { replaceImageLinks } = Meteor.settings.public.chat; + let parsedMessage = message || ''; parsedMessage = parsedMessage.trim(); + // replace image links with markdown image links + if (replaceImageLinks) { + const imageRegex = /(? Date: Wed, 23 Aug 2023 14:33:03 -0300 Subject: [PATCH 246/252] disable images --- bigbluebutton-html5/imports/api/common/server/helpers.js | 8 -------- bigbluebutton-html5/private/config/settings.yml | 3 +-- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/bigbluebutton-html5/imports/api/common/server/helpers.js b/bigbluebutton-html5/imports/api/common/server/helpers.js index b402e26ca9..5b3c7d91fb 100755 --- a/bigbluebutton-html5/imports/api/common/server/helpers.js +++ b/bigbluebutton-html5/imports/api/common/server/helpers.js @@ -37,17 +37,9 @@ export const parseMessage = (message) => { }; export const textToMarkdown = (message) => { - const { replaceImageLinks } = Meteor.settings.public.chat; - let parsedMessage = message || ''; parsedMessage = parsedMessage.trim(); - // replace image links with markdown image links - if (replaceImageLinks) { - const imageRegex = /(? Date: Wed, 23 Aug 2023 17:47:11 -0300 Subject: [PATCH 247/252] adding flaky tag to failing tests on ci --- .../playwright/breakout/breakout.spec.js | 2 +- .../playwright/chat/chat.spec.js | 14 ++++++------ .../notifications/notifications.spec.js | 4 ++-- .../playwright/options/options.spec.js | 4 ++-- .../playwright/parameters/parameters.spec.js | 6 ++--- .../playwright/polling/polling.spec.js | 4 ++-- .../presentation/presentation.spec.js | 4 ++-- .../playwright/user/user.spec.js | 22 +++++++++---------- .../playwright/whiteboard/whiteboard.spec.js | 2 +- 9 files changed, 31 insertions(+), 31 deletions(-) diff --git a/bigbluebutton-tests/playwright/breakout/breakout.spec.js b/bigbluebutton-tests/playwright/breakout/breakout.spec.js index 27b80bfc17..c0116662cd 100644 --- a/bigbluebutton-tests/playwright/breakout/breakout.spec.js +++ b/bigbluebutton-tests/playwright/breakout/breakout.spec.js @@ -71,7 +71,7 @@ test.describe.parallel('Breakout', () => { await join.joinWithAudio(); }); - test('Message to all rooms @ci', async ({ browser, context, page }) => { + test('Message to all rooms @ci @flaky', async ({ browser, context, page }) => { const join = new Join(browser, context); await join.initPages(page); await join.create(); diff --git a/bigbluebutton-tests/playwright/chat/chat.spec.js b/bigbluebutton-tests/playwright/chat/chat.spec.js index 00f4791bec..cb7426ce4f 100644 --- a/bigbluebutton-tests/playwright/chat/chat.spec.js +++ b/bigbluebutton-tests/playwright/chat/chat.spec.js @@ -13,16 +13,16 @@ test.describe.serial('Chat', () => { }); // https://docs.bigbluebutton.org/2.6/release-tests.html#public-message-automated - test('Send public message @ci', async () => { + test('Send public message @ci @flaky', async () => { await chat.sendPublicMessage(); }); // https://docs.bigbluebutton.org/2.6/release-tests.html#private-message-automated - test('Send private message @ci', async () => { + test('Send private message @ci @flaky', async () => { await chat.sendPrivateMessage(); }); - test('Clear chat @ci', async () => { + test('Clear chat @ci @flaky', async () => { await chat.clearChat(); }); @@ -30,7 +30,7 @@ test.describe.serial('Chat', () => { await chat.copyChat(context); }); - test('Save chat @ci', async ({}, testInfo) => { + test('Save chat @ci @flaky', async ({}, testInfo) => { await chat.saveChat(testInfo); }); @@ -39,7 +39,7 @@ test.describe.serial('Chat', () => { }); // https://docs.bigbluebutton.org/2.6/release-tests.html#sending-empty-chat-message-automated - test('Not able to send an empty message @ci', async () => { + test('Not able to send an empty message @ci @flaky', async () => { await chat.emptyMessage(); }); @@ -57,7 +57,7 @@ test.describe.serial('Chat', () => { await chat.emojiCopyChat(); }); - test('Close private chat @ci', async () => { + test('Close private chat @ci @flaky', async () => { await chat.closePrivateChat(); }); @@ -85,7 +85,7 @@ test.describe.serial('Chat', () => { await chat.autoConvertEmojiSendPrivateChat(); }); - test('Private chat disabled when user leaves meeting @ci', async () => { + test('Private chat disabled when user leaves meeting @ci @flaky', async () => { await chat.chatDisabledUserLeaves(); }); }); diff --git a/bigbluebutton-tests/playwright/notifications/notifications.spec.js b/bigbluebutton-tests/playwright/notifications/notifications.spec.js index 619a038563..f93efeed6e 100644 --- a/bigbluebutton-tests/playwright/notifications/notifications.spec.js +++ b/bigbluebutton-tests/playwright/notifications/notifications.spec.js @@ -24,14 +24,14 @@ test.describe.parallel('Notifications', () => { await notifications.getUserJoinPopupResponse(); }); - test('Raise and lower hand notification @ci', async ({ browser, context, page }) => { + test('Raise and lower hand notification @ci @flaky', async ({ browser, context, page }) => { const notifications = new Notifications(browser, context); await notifications.initModPage(page); await notifications.raiseAndLowerHandNotification(); }); test.describe.parallel('Chat', () => { - test('Public Chat notification @ci', async ({ browser, context, page }) => { + test('Public Chat notification @ci @flaky', async ({ browser, context, page }) => { const chatNotifications = new ChatNotifications(browser, context); await chatNotifications.initPages(page, true); await chatNotifications.publicChatNotification(); diff --git a/bigbluebutton-tests/playwright/options/options.spec.js b/bigbluebutton-tests/playwright/options/options.spec.js index 08676a6502..80d4721d83 100644 --- a/bigbluebutton-tests/playwright/options/options.spec.js +++ b/bigbluebutton-tests/playwright/options/options.spec.js @@ -22,11 +22,11 @@ test.describe.serial('Options', () => { await options.localesTest(); }); - test('Dark mode @ci', async () => { + test('Dark mode @ci @flaky', async () => { await options.darkMode(); }); - test('Font size @ci', async () => { + test('Font size @ci @flaky', async () => { await options.fontSizeTest(); }); }); diff --git a/bigbluebutton-tests/playwright/parameters/parameters.spec.js b/bigbluebutton-tests/playwright/parameters/parameters.spec.js index 12dab266f9..6c3fdfc1c0 100644 --- a/bigbluebutton-tests/playwright/parameters/parameters.spec.js +++ b/bigbluebutton-tests/playwright/parameters/parameters.spec.js @@ -148,7 +148,7 @@ test.describe.parallel('Create Parameters', () => { await disabledFeatures.initModPage(page, true, { createParameter: c.chatDisabled }); await disabledFeatures.chat(); }); - test('Chat (exclude)', async ({ browser, context, page }) => { + test('Chat (exclude) @flaky', async ({ browser, context, page }) => { const disabledFeatures = new DisabledFeatures(browser, context); await disabledFeatures.initModPage(page, true, { createParameter: c.chatExclude }); await disabledFeatures.chatExclude(); @@ -378,7 +378,7 @@ test.describe.parallel('Custom Parameters', () => { await customParam.shortcuts(); }); - test('Custom Styles: CSS code @ci', async ({ browser, context, page }) => { + test('Custom Styles: CSS code @ci @flaky', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); await customParam.initModPage(page, true, { joinParameter: encodeCustomParams(c.customStyle) }); await customParam.customStyle(); @@ -422,7 +422,7 @@ test.describe.parallel('Custom Parameters', () => { }); test.describe.parallel('Audio', () => { - test('Auto join @ci', async ({ browser, context, page }) => { + test('Auto join @ci @flaky', async ({ browser, context, page }) => { const customParam = new CustomParameters(browser, context); await customParam.initModPage(page, false, { joinParameter: c.autoJoin }); await customParam.autoJoin(); diff --git a/bigbluebutton-tests/playwright/polling/polling.spec.js b/bigbluebutton-tests/playwright/polling/polling.spec.js index 048ea0a5a9..611bab8f4d 100644 --- a/bigbluebutton-tests/playwright/polling/polling.spec.js +++ b/bigbluebutton-tests/playwright/polling/polling.spec.js @@ -53,11 +53,11 @@ test.describe.serial('Polling', () => { }); // Results - test('Poll results in chat message @ci', async () => { + test('Poll results in chat message @ci @flaky', async () => { await polling.pollResultsOnChat(); }); - test('Poll results on whiteboard @ci', async () => { + test('Poll results on whiteboard @ci @flaky', async () => { await polling.pollResultsOnWhiteboard(); }); diff --git a/bigbluebutton-tests/playwright/presentation/presentation.spec.js b/bigbluebutton-tests/playwright/presentation/presentation.spec.js index 80753b36c7..0b353ee070 100644 --- a/bigbluebutton-tests/playwright/presentation/presentation.spec.js +++ b/bigbluebutton-tests/playwright/presentation/presentation.spec.js @@ -27,7 +27,7 @@ test.describe.parallel('Presentation', () => { }); // https://docs.bigbluebutton.org/2.6/release-tests.html#fit-to-width-option - test('Presentation fit to width @ci', async ({ browser, context, page }) => { + test('Presentation fit to width @ci @flaky', async ({ browser, context, page }) => { const presentation = new Presentation(browser, context); await presentation.initModPage(page, true, { customParameter: customStyleAvoidUploadingNotifications }); await presentation.initUserPage(true, context); @@ -90,7 +90,7 @@ test.describe.parallel('Presentation', () => { await presentation.enableAndDisablePresentationDownload(testInfo); }); - test('Send presentation in the current state (with annotations) to chat for downloading @ci', async ({ browser, context, page }, testInfo) => { + test('Send presentation in the current state (with annotations) to chat for downloading @ci @flaky', async ({ browser, context, page }, testInfo) => { const presentation = new Presentation(browser, context); await presentation.initPages(page); await presentation.sendPresentationToDownload(testInfo); diff --git a/bigbluebutton-tests/playwright/user/user.spec.js b/bigbluebutton-tests/playwright/user/user.spec.js index ce309f26aa..6f540d983d 100644 --- a/bigbluebutton-tests/playwright/user/user.spec.js +++ b/bigbluebutton-tests/playwright/user/user.spec.js @@ -22,7 +22,7 @@ test.describe.parallel('User', () => { await multiusers.raiseHandRejected(); }); - test('Toggle user list @ci', async ({ browser, context, page }) => { + test('Toggle user list @ci @flaky', async ({ browser, context, page }) => { const multiusers = new MultiUsers(browser, context); await multiusers.initModPage(page); await multiusers.toggleUserList(); @@ -44,41 +44,41 @@ test.describe.parallel('User', () => { }); // https://docs.bigbluebutton.org/2.6/release-tests.html#make-viewer-a-presenter-automated - test('Make presenter @ci', async ({ browser, context, page }) => { + test('Make presenter @ci @flaky', async ({ browser, context, page }) => { const multiusers = new MultiUsers(browser, context); await multiusers.initPages(page); await multiusers.makePresenter(); }); // https://docs.bigbluebutton.org/2.6/release-tests.html#taking-presenter-status-back-automated - test('Take presenter @ci', async ({ browser, context, page }) => { + test('Take presenter @ci @flaky', async ({ browser, context, page }) => { const multiusers = new MultiUsers(browser, context); await multiusers.initModPage(page); await multiusers.initModPage2(); await multiusers.takePresenter(); }); - test('Promote to moderator @ci', async ({ browser, context, page }) => { + test('Promote to moderator @ci @flaky', async ({ browser, context, page }) => { const multiusers = new MultiUsers(browser, context); await multiusers.initPages(page); await multiusers.promoteToModerator(); }); - test('Demote to viewer @ci', async ({ browser, context, page }) => { + test('Demote to viewer @ci @flaky', async ({ browser, context, page }) => { const multiusers = new MultiUsers(browser, context); await multiusers.initModPage(page); await multiusers.initModPage2(); await multiusers.demoteToViewer(); }); - test('Give and remove whiteboard access @ci', async ({ browser, context, page }) => { + test('Give and remove whiteboard access @ci @flaky', async ({ browser, context, page }) => { const multiusers = new MultiUsers(browser, context); await multiusers.initModPage(page); await multiusers.initModPage2(); await multiusers.giveAndRemoveWhiteboardAccess(); }); - test('Remove user @ci', async ({ browser, context, page }) => { + test('Remove user @ci @flaky', async ({ browser, context, page }) => { const multiusers = new MultiUsers(browser, context); await multiusers.initModPage(page, true); await multiusers.initModPage2(true); @@ -102,7 +102,7 @@ test.describe.parallel('User', () => { await guestPolicy.initModPage(page); await guestPolicy.messageToGuestLobby(); }); - test('Allow Everyone', async ({ browser, context, page }) => { + test('Allow Everyone @flaky', async ({ browser, context, page }) => { const guestPolicy = new GuestPolicy(browser, context); await guestPolicy.initModPage(page); await guestPolicy.allowEveryone(); @@ -126,7 +126,7 @@ test.describe.parallel('User', () => { await guestPolicy.messageToSpecificUser(); }); - test('Accept', async ({ browser, context, page }) => { + test('Accept @flaky', async ({ browser, context, page }) => { const guestPolicy = new GuestPolicy(browser, context); await guestPolicy.initModPage(page); await guestPolicy.acceptSpecificUser(); @@ -153,7 +153,7 @@ test.describe.parallel('User', () => { }); }); - test.describe.parallel('Lock viewers @ci', () => { + test.describe.parallel('Lock viewers @ci @flaky', () => { // https://docs.bigbluebutton.org/2.6/release-tests.html#webcam test('Lock Share webcam', async ({ browser, context, page }) => { const lockViewers = new LockViewers(browser, context); @@ -258,7 +258,7 @@ test.describe.parallel('User', () => { test.skip(browserName === 'firefox', 'Mobile tests are not able in Firefox browser'); }); - test('Mobile Tag Name For Mobile User @ci', async ({ browser }) => { + test('Mobile Tag Name For Mobile User @ci @flaky', async ({ browser }) => { const context = await browser.newContext({ ...iPhone11 }); const mobilePage = await context.newPage(); const mobileDevices = new MobileDevices(browser, context); diff --git a/bigbluebutton-tests/playwright/whiteboard/whiteboard.spec.js b/bigbluebutton-tests/playwright/whiteboard/whiteboard.spec.js index 3e5d591e22..a9e573d0b4 100644 --- a/bigbluebutton-tests/playwright/whiteboard/whiteboard.spec.js +++ b/bigbluebutton-tests/playwright/whiteboard/whiteboard.spec.js @@ -29,7 +29,7 @@ test.describe.parallel('Whiteboard @ci', () => { await draw.test(); }); - test('Give Additional Whiteboard Access', async ({ browser, context, page }) => { + test('Give Additional Whiteboard Access @flaky', async ({ browser, context, page }) => { const multiusers = new MultiUsers(browser, context); await multiusers.initPages(page); await multiusers.whiteboardAccess(); From e89dcd356c014b2d66ce8d40b52dfc8c50458713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Souza?= Date: Wed, 23 Aug 2023 11:35:26 -0300 Subject: [PATCH 248/252] restore unread messages button --- .../chat-message-list/component.tsx | 153 +++++++++++------- .../chat-graphql/chat-message-list/styles.ts | 10 ++ 2 files changed, 106 insertions(+), 57 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/component.tsx b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/component.tsx index 069f58191f..4ba2be45f2 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/component.tsx +++ b/bigbluebutton-html5/imports/ui/components/chat/chat-graphql/chat-message-list/component.tsx @@ -6,6 +6,7 @@ import { ButtonLoadMore, MessageList, MessageListWrapper, + UnreadButton, } from "./styles"; import { layoutSelect } from "../../../layout/context"; import ChatListPage from "./page/component"; @@ -30,6 +31,10 @@ const intlMessages = defineMessages({ id: 'app.chat.loadMoreButtonLabel', description: 'Label for load more button', }, + moreMessages: { + id: 'app.chat.moreMessages', + description: 'Chat message when the user has unread messages below the scroll', + }, }); interface ChatListProps { @@ -97,15 +102,18 @@ const ChatMessageList: React.FC = ({ chatId, setMessageAsSeenMutation, lastSeenAt, + totalUnread, }) => { const intl = useIntl(); const messageListRef = React.useRef(); const contentRef = React.useRef(); // I used a ref here because I don't want to re-render the component when the last sender changes const lastSenderPerPage = React.useRef>(new Map()); + const messagesEndRef = React.useRef(); const [userLoadedBackUntilPage, setUserLoadedBackUntilPage] = useState(null); const [lastMessageCreatedTime, setLastMessageCreatedTime] = useState(0); const [followingTail, setFollowingTail] = React.useState(true); + const [userScrolledBack, setUserScrolledBack] = React.useState(false); useEffect(() => { setter({ ...setter(), @@ -167,6 +175,31 @@ const ChatMessageList: React.FC = ({ } }; + const renderUnreadNotification = () => { + if (totalUnread && userScrolledBack) { + return ( +