Compare commits

...

16 Commits

Author SHA1 Message Date
dependabot[bot]
fe9176ee4c
build(deps): bump the bundler group across 1 directory with 4 updates
Bumps the bundler group with 4 updates in the /record-and-playback/core directory: [nokogiri](https://github.com/sparklemotion/nokogiri), [activesupport](https://github.com/rails/rails), [rack](https://github.com/rack/rack) and [rexml](https://github.com/ruby/rexml).


Updates `nokogiri` from 1.13.10 to 1.16.5
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.13.10...v1.16.5)

Updates `activesupport` from 7.0.5 to 7.0.7.1
- [Release notes](https://github.com/rails/rails/releases)
- [Changelog](https://github.com/rails/rails/blob/v7.2.1.2/activesupport/CHANGELOG.md)
- [Commits](https://github.com/rails/rails/compare/v7.0.5...v7.0.7.1)

Updates `rack` from 2.2.8 to 2.2.8.1
- [Release notes](https://github.com/rack/rack/releases)
- [Changelog](https://github.com/rack/rack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rack/rack/compare/v2.2.8...v2.2.8.1)

Updates `rexml` from 3.3.6 to 3.3.9
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.3.6...v3.3.9)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-type: direct:production
  dependency-group: bundler
- dependency-name: activesupport
  dependency-type: indirect
  dependency-group: bundler
- dependency-name: rack
  dependency-type: indirect
  dependency-group: bundler
- dependency-name: rexml
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 18:50:11 +00:00
Tainan Felipe
988ae28d42
Fix: hook being used inside useEffect (#21534) 2024-10-28 13:14:43 -03:00
Gustavo Trott
bf3cd7a5d5
Merge pull request #21535 from gustavotrott/ci-improve-retry
ci: Improve retry for `Install BBB` step in Automated Tests
2024-10-26 16:14:41 -03:00
Gustavo Trott
916935484e remove short timeout 2024-10-26 11:46:01 -03:00
Gustavo Trott
b6a75d1fef check if there is apt and dpkg running 2024-10-26 09:56:07 -03:00
Gustavo Trott
5e4df03283 set 7min timeout for testing 2024-10-25 17:29:14 -03:00
Gustavo Trott
6ba4ad5560 print running processes in apt and dpkg 2024-10-25 17:29:00 -03:00
KDSBrowne
7a1ae9e350
update default text alignment values (#21443) 2024-10-25 15:51:26 -03:00
Gustavo Trott
c0ed6bc8cd
Merge pull request #21531 from gustavotrott/ci-unlock-debconf
ci: Improve retry for `Install BBB` step in Automated Tests
2024-10-25 14:45:58 -03:00
germanocaumo
e992862e40
update(chat): add option to disable private chat (#21459)
* update(chat): add option to disable private chat

- Adds `public.chat.private` to settings.yml, defaults to true
- Adds `privateChat` to disabledFeatures

* backend validation for disabledFeatures=privateChat

* refactor(settings): remove chat.private from settings.yml

Will be kept only in disabledFeatures

---------

Co-authored-by: Gustavo Trott <gustavo@trott.com.br>
2024-10-25 13:40:10 -04:00
Gustavo Trott
df89f8aea6 hold Firefox once bbb-install frequently stuck at 2024-10-25 14:16:21 -03:00
Gustavo Trott
e1e9d645c4 Avoid error '/var/cache/debconf/config.dat is locked by another process' when retrying 2024-10-25 14:12:26 -03:00
Gustavo Trott
82774b9a08
Merge pull request #21509 from gustavotrott/gql-annotations-diff
refactor (graphql-server): Preserves the entire history of annotation changes (type `pres_annotation_history_curr`)
2024-10-25 12:01:31 -03:00
Gustavo Trott
7d5e5eb75b fix insertOrUpdate causing conflict when sending in batch 2024-10-23 21:57:03 -03:00
Gustavo Trott
392953f08e Improve insert of annotations to send it in batch 2024-10-23 17:32:38 -03:00
Gustavo Trott
60c15006f2 Re-enable the graphql type pres_annotation_history_curr to receive all annotations as incremental diff.
It will make the whiteboard more consistent and easier to merge the data.
2024-10-23 16:15:43 -03:00
21 changed files with 200 additions and 216 deletions

View File

@ -255,6 +255,7 @@ jobs:
run: |
sudo sh -c '
apt --purge -y remove apache2-bin
apt-mark hold firefox #hold ff once bbb-install frequently stuck at `Installing the firefox snap`
'
- name: Install BBB
env:
@ -293,16 +294,22 @@ jobs:
echo "Retrying installation within $RETRY_INTERVAL seconds..."
sleep $RETRY_INTERVAL
fi
echo "Check if there is some process still locking:1"
ps aux | grep -E 'dpkg|apt'
echo "Stop any ongoing processes related to apt-get or dpkg that might be stuck"
# Use -q to suppress "no process found" messages
killall -q apt-get || true
killall -q dpkg || true
# Kill any apt-get or dpkg processes that might be hanging
killall -9 -q apt-get || true
killall -9 -q dpkg || true
echo "Remove the lock files that may have been left behind"
# Group lock file removal for better readability
rm -f /var/lib/dpkg/lock-frontend
rm -f /var/lib/dpkg/lock
rm -f /var/cache/apt/archives/lock
rm -f /var/cache/debconf/config.dat
echo "Reconfigure the package manager"
dpkg --configure -a
@ -311,6 +318,12 @@ jobs:
apt-get clean
apt-get autoremove
echo "Check if there is some process still locking:2"
ps aux | grep -E 'dpkg|apt'
#remove all apt-update to make the install faster
sed -i 's/apt-get update/#apt-get update/g' bbb-install.sh
RETRY_COUNT=$((RETRY_COUNT + 1))
done

View File

@ -4,7 +4,7 @@ import scala.collection.immutable.HashMap
import org.bigbluebutton.common2.msgs.AnnotationVO
import org.bigbluebutton.core.apps.whiteboard.Whiteboard
import org.bigbluebutton.SystemConfiguration
import org.bigbluebutton.core.db.{ PresAnnotationDAO, PresPageWritersDAO }
import org.bigbluebutton.core.db.{ PresAnnotationDAO, PresAnnotationHistoryDAO, PresPageWritersDAO }
class WhiteboardModel extends SystemConfiguration {
private var _whiteboards = new HashMap[String, Whiteboard]()
@ -85,7 +85,9 @@ class WhiteboardModel extends SystemConfiguration {
}
}
PresAnnotationDAO.insertOrUpdateMap(meetingId, annotationsAdded)
val annotationUpdatedAt = System.currentTimeMillis()
PresAnnotationHistoryDAO.insertOrUpdateMap(meetingId, annotationsDiffAdded, annotationUpdatedAt)
PresAnnotationDAO.insertOrUpdateMap(meetingId, annotationsAdded, annotationUpdatedAt)
val newWb = wb.copy(annotationsMap = newAnnotationsMap)
saveWhiteboard(newWb)
@ -154,7 +156,9 @@ class WhiteboardModel extends SystemConfiguration {
val updatedWb = wb.copy(annotationsMap = newAnnotationsMap)
saveWhiteboard(updatedWb)
PresAnnotationDAO.delete(meetingId, userId, annotationsIdsRemoved)
val annotationUpdatedAt = System.currentTimeMillis()
PresAnnotationHistoryDAO.deleteAnnotations(meetingId, wb.id, userId, annotationsIdsRemoved, annotationUpdatedAt)
PresAnnotationDAO.deleteAnnotations(meetingId, userId, annotationsIdsRemoved, annotationUpdatedAt)
annotationsIdsRemoved
}

View File

@ -20,8 +20,13 @@ trait CreateGroupChatReqMsgHdlr extends SystemConfiguration {
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
log.debug("RECEIVED CREATE CHAT REQ MESSAGE")
var privateChatDisabled: Boolean = false
var chatLocked: Boolean = false
if (msg.body.access == GroupChatAccess.PRIVATE) {
privateChatDisabled = liveMeeting.props.meetingProp.disabledFeatures.contains("privateChat")
}
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
} yield {
@ -45,7 +50,12 @@ trait CreateGroupChatReqMsgHdlr extends SystemConfiguration {
// Check if this message was sent while the lock settings was being changed.
val isDelayedMessage = System.currentTimeMillis() - MeetingStatus2x.getPermissionsChangedOn(liveMeeting.status) < 5000
if (applyPermissionCheck && chatLocked && !isDelayedMessage) {
if (privateChatDisabled ||
(
applyPermissionCheck &&
chatLocked &&
!isDelayedMessage
)) {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "No permission to create a new group chat."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)

View File

@ -25,6 +25,7 @@ trait SendGroupChatMessageMsgHdlr extends HandlerHelpers {
}
val chatDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("chat")
var privateChatDisabled: Boolean = false
val replyChatMessageDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("replyChatMessage")
var chatLocked: Boolean = false
var chatLockedForUser: Boolean = false
@ -33,6 +34,10 @@ trait SendGroupChatMessageMsgHdlr extends HandlerHelpers {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
groupChat <- state.groupChats.find(msg.body.chatId)
} yield {
if (groupChat.access == GroupChatAccess.PRIVATE) {
privateChatDisabled = liveMeeting.props.meetingProp.disabledFeatures.contains("privateChat")
}
if (groupChat.access == GroupChatAccess.PUBLIC && user.userLockSettings.disablePublicChat && user.role != Roles.MODERATOR_ROLE) {
chatLockedForUser = true
}
@ -54,7 +59,10 @@ trait SendGroupChatMessageMsgHdlr extends HandlerHelpers {
}
}
if (!chatDisabled && !(applyPermissionCheck && chatLocked) && !chatLockedForUser) {
if (!chatDisabled &&
!privateChatDisabled &&
!(applyPermissionCheck && chatLocked) &&
!chatLockedForUser) {
val newState = for {
sender <- GroupChatApp.findGroupChatUser(msg.header.userId, liveMeeting.users2x)
chat <- state.groupChats.find(msg.body.chatId)

View File

@ -4,13 +4,12 @@ import org.bigbluebutton.common2.msgs.AnnotationVO
import slick.jdbc.PostgresProfile.api._
case class PresAnnotationDbModel(
annotationId: String,
pageId: String,
meetingId: String,
userId: String,
annotationInfo: String,
lastHistorySequence: Int,
lastUpdatedAt: java.sql.Timestamp = new java.sql.Timestamp(System.currentTimeMillis())
annotationId: String,
pageId: String,
meetingId: String,
userId: String,
annotationInfo: String,
lastUpdatedAt: java.sql.Timestamp = new java.sql.Timestamp(System.currentTimeMillis())
)
class PresAnnotationDbTableDef(tag: Tag) extends Table[PresAnnotationDbModel](tag, None, "pres_annotation") {
@ -19,77 +18,37 @@ class PresAnnotationDbTableDef(tag: Tag) extends Table[PresAnnotationDbModel](ta
val meetingId = column[String]("meetingId")
val userId = column[String]("userId")
val annotationInfo = column[String]("annotationInfo")
val lastHistorySequence = column[Int]("lastHistorySequence")
val lastUpdatedAt = column[java.sql.Timestamp]("lastUpdatedAt")
// def whiteboard = foreignKey("whiteboard_fk", whiteboardId, Whiteboards)(_.whiteboardId, onDelete = ForeignKeyAction.Cascade)
def * = (annotationId, pageId, meetingId, userId, annotationInfo, lastHistorySequence, lastUpdatedAt) <> (PresAnnotationDbModel.tupled, PresAnnotationDbModel.unapply)
def * = (annotationId, pageId, meetingId, userId, annotationInfo, lastUpdatedAt) <> (PresAnnotationDbModel.tupled, PresAnnotationDbModel.unapply)
}
object PresAnnotationDAO {
def insertOrUpdate(meetingId: String, annotation: AnnotationVO, annotationDiff: AnnotationVO) = {
// //TODO do it via trigger?
// PresAnnotationHistoryDAO.insert(meetingId, annotationDiff).onComplete {
// case Success(sequence) => {
// DatabaseConnection.logger.debug(s"Sequence generated to PresAnnotationHistory record: $sequence")
//
DatabaseConnection.enqueue(
TableQuery[PresAnnotationDbTableDef].insertOrUpdate(
PresAnnotationDbModel(
annotationId = annotation.id,
pageId = annotation.wbId,
meetingId = meetingId,
userId = annotation.userId,
annotationInfo = JsonUtils.mapToJson(annotation.annotationInfo).compactPrint,
lastHistorySequence = 0,
lastUpdatedAt = new java.sql.Timestamp(System.currentTimeMillis())
)
def insertOrUpdateMap(meetingId: String, annotations: Array[AnnotationVO], annotationUpdatedAt: Long) = {
for {
annotation <- annotations
} yield {
DatabaseConnection.enqueue(
sqlu"""
WITH upsert AS (
UPDATE pres_annotation
SET "annotationInfo"=${JsonUtils.mapToJson(annotation.annotationInfo).compactPrint},
"lastUpdatedAt" = ${new java.sql.Timestamp(annotationUpdatedAt)}
WHERE "annotationId" = ${annotation.id}
RETURNING *)
INSERT INTO pres_annotation ("annotationId", "pageId", "meetingId", "userId", "annotationInfo", "lastUpdatedAt")
SELECT ${annotation.id}, ${annotation.wbId}, ${meetingId}, ${annotation.userId},
${JsonUtils.mapToJson(annotation.annotationInfo).compactPrint}, ${new java.sql.Timestamp(annotationUpdatedAt)}
WHERE NOT EXISTS (SELECT * FROM upsert)"""
)
)
// }
// case Failure(e) => DatabaseConnection.logger.error(s"Error inserting PresAnnotationHistory: $e")
}
}
def prepareInsertOrUpdate(meetingId: String, annotation: AnnotationVO) = {
TableQuery[PresAnnotationDbTableDef].insertOrUpdate(
PresAnnotationDbModel(
annotationId = annotation.id,
pageId = annotation.wbId,
meetingId = meetingId,
userId = annotation.userId,
annotationInfo = JsonUtils.mapToJson(annotation.annotationInfo).compactPrint,
lastHistorySequence = 0,
lastUpdatedAt = new java.sql.Timestamp(System.currentTimeMillis())
)
)
}
def insertOrUpdateMap(meetingId: String, annotations: Array[AnnotationVO]) = {
DatabaseConnection.enqueue(
DBIO.sequence(
annotations.map { annotation =>
prepareInsertOrUpdate(meetingId, annotation)
}.toVector
).transactionally
)
}
def delete(wbId: String, meetingId: String, userId: String, annotationId: String) = {
// PresAnnotationHistoryDAO.delete(wbId, meetingId, userId, annotationId)
DatabaseConnection.enqueue(
TableQuery[PresAnnotationDbTableDef]
.filter(_.annotationId === annotationId)
.map(a => (a.annotationInfo, a.lastHistorySequence, a.meetingId, a.userId, a.lastUpdatedAt))
.update("", 0, meetingId, userId, new java.sql.Timestamp(System.currentTimeMillis()))
)
}
def delete(meetingId: String, userId: String, annotationIds: Array[String]) = {
def deleteAnnotations(meetingId: String, userId: String, annotationIds: Array[String], annotationUpdatedAt: Long) = {
DatabaseConnection.enqueue(
TableQuery[PresAnnotationDbTableDef]
.filter(_.annotationId inSet annotationIds)
.map(a => (a.annotationInfo, a.lastHistorySequence, a.meetingId, a.userId, a.lastUpdatedAt))
.update("", 0, meetingId, userId, new java.sql.Timestamp(System.currentTimeMillis()))
.map(a => (a.annotationInfo, a.meetingId, a.userId, a.lastUpdatedAt))
.update("", meetingId, userId, new java.sql.Timestamp(annotationUpdatedAt))
)
}

View File

@ -4,58 +4,57 @@ import org.bigbluebutton.common2.msgs.AnnotationVO
import PostgresProfile.api._
case class PresAnnotationHistoryDbModel(
sequence: Option[Int] = None,
annotationId: String,
pageId: String,
meetingId: String,
userId: String,
annotationInfo: String
// lastUpdatedAt: java.sql.Timestamp = new java.sql.Timestamp(System.currentTimeMillis())
annotationInfo: String,
updatedAt: java.sql.Timestamp
)
class PresAnnotationHistoryDbTableDef(tag: Tag) extends Table[PresAnnotationHistoryDbModel](tag, None, "pres_annotation_history") {
val sequence = column[Option[Int]]("sequence", O.PrimaryKey, O.AutoInc)
val annotationId = column[String]("annotationId")
val pageId = column[String]("pageId")
val meetingId = column[String]("meetingId")
val userId = column[String]("userId")
val annotationInfo = column[String]("annotationInfo")
// val lastUpdatedAt = column[java.sql.Timestamp]("lastUpdatedAt")
// def whiteboard = foreignKey("whiteboard_fk", whiteboardId, Whiteboards)(_.whiteboardId, onDelete = ForeignKeyAction.Cascade)
def * = (sequence, annotationId, pageId, meetingId, userId, annotationInfo) <> (PresAnnotationHistoryDbModel.tupled, PresAnnotationHistoryDbModel.unapply)
val updatedAt = column[java.sql.Timestamp]("updatedAt")
def * = (annotationId, pageId, meetingId, userId, annotationInfo, updatedAt) <> (PresAnnotationHistoryDbModel.tupled, PresAnnotationHistoryDbModel.unapply)
}
object PresAnnotationHistoryDAO {
def insert(meetingId: String, annotationDiff: AnnotationVO) = {
DatabaseConnection.db.run(
//TODO not being used for now
TableQuery[PresAnnotationHistoryDbTableDef].returning(
TableQuery[PresAnnotationHistoryDbTableDef].map(_.sequence)
) += PresAnnotationHistoryDbModel(
None,
annotationId = annotationDiff.id,
pageId = annotationDiff.wbId,
meetingId = meetingId,
userId = annotationDiff.userId,
annotationInfo = JsonUtils.mapToJson(annotationDiff.annotationInfo).compactPrint
)
def insertOrUpdateMap(meetingId: String, annotations: Array[AnnotationVO], annotationUpdatedAt: Long) = {
val dbModels = annotations.map { annotation =>
PresAnnotationHistoryDbModel(
annotationId = annotation.id,
pageId = annotation.wbId,
meetingId = meetingId,
userId = annotation.userId,
annotationInfo = JsonUtils.mapToJson(annotation.annotationInfo).compactPrint,
updatedAt = new java.sql.Timestamp(annotationUpdatedAt)
)
}
DatabaseConnection.enqueue(
TableQuery[PresAnnotationHistoryDbTableDef] ++= dbModels
)
}
def delete(wbId: String, meetingId: String, userId: String, annotationId: String) = {
DatabaseConnection.db.run(
//TODO not being used for now
TableQuery[PresAnnotationHistoryDbTableDef].returning(
TableQuery[PresAnnotationHistoryDbTableDef].map(_.sequence)
) += PresAnnotationHistoryDbModel(
None,
annotationId = annotationId,
pageId = wbId,
meetingId = meetingId,
userId = userId,
annotationInfo = ""
)
def deleteAnnotations(meetingId: String, pageId: String, userId: String, annotations: Array[String], annotationUpdatedAt: Long) = {
val dbModels = annotations.map { annotationId =>
PresAnnotationHistoryDbModel(
annotationId = annotationId,
pageId = pageId,
meetingId = meetingId,
userId = userId,
annotationInfo = "",
updatedAt = new java.sql.Timestamp(annotationUpdatedAt)
)
}
DatabaseConnection.enqueue(
TableQuery[PresAnnotationHistoryDbTableDef] ++= dbModels
)
}
}

View File

@ -1366,8 +1366,7 @@ CREATE TABLE "pres_annotation" (
"meetingId" varchar(100),
"userId" varchar(50),
"annotationInfo" TEXT,
"lastHistorySequence" integer,
"lastUpdatedAt" timestamp with time zone DEFAULT now()
"lastUpdatedAt" timestamp with time zone
);
CREATE INDEX "idx_pres_annotation_pageId" ON "pres_annotation"("pageId");
CREATE INDEX "idx_pres_annotation_updatedAt" ON "pres_annotation"("pageId","lastUpdatedAt");
@ -1379,25 +1378,30 @@ CREATE TABLE "pres_annotation_history" (
"pageId" varchar(100) REFERENCES "pres_page"("pageId") ON DELETE CASCADE,
"meetingId" varchar(100),
"userId" varchar(50),
"annotationInfo" TEXT
-- "lastUpdatedAt" timestamp with time zone DEFAULT now()
"annotationInfo" TEXT,
"updatedAt" timestamp with time zone
);
CREATE INDEX "idx_pres_annotation_history_pageId" ON "pres_annotation"("pageId");
create index "idx_pres_annotation_history_user_meeting" on "pres_annotation_history" ("userId", "meetingId");
CREATE INDEX "idx_pres_annotation_history_updatedAt" ON "pres_annotation_history"("pageId", "updatedAt");
CREATE VIEW "v_pres_annotation_curr" AS
SELECT p."meetingId", pp."presentationId", pa."annotationId", pa."pageId", pa."userId", pa."annotationInfo", pa."lastHistorySequence", pa."lastUpdatedAt"
SELECT p."meetingId", pp."presentationId", pa."annotationId", pa."pageId", pa."userId", pa."annotationInfo",
pa."lastUpdatedAt", "user"."isModerator" as "userIsModerator"
FROM pres_presentation p
JOIN pres_page pp ON pp."presentationId" = p."presentationId"
JOIN pres_annotation pa ON pa."pageId" = pp."pageId"
JOIN "user" on "user"."meetingId" = pa."meetingId" and "user"."userId" = pa."userId"
WHERE p."current" IS true
AND pp."current" IS true;
CREATE VIEW "v_pres_annotation_history_curr" AS
SELECT p."meetingId", pp."presentationId", pah."pageId", pah."userId", pah."annotationId", pah."annotationInfo", pah."sequence"
SELECT p."meetingId", pp."presentationId", pah."pageId", pah."userId", pah."annotationId", pah."annotationInfo",
pah."updatedAt", "user"."isModerator" as "userIsModerator"
FROM pres_presentation p
JOIN pres_page pp ON pp."presentationId" = p."presentationId"
JOIN pres_annotation_history pah ON pah."pageId" = pp."pageId"
JOIN "user" on "user"."meetingId" = pah."meetingId" and "user"."userId" = pah."userId"
WHERE p."current" IS true
AND pp."current" IS true;

View File

@ -24,7 +24,6 @@ select_permissions:
- pageId
- presentationId
- userId
- lastHistorySequence
- annotationInfo
- lastUpdatedAt
filter:
@ -32,9 +31,8 @@ select_permissions:
- meetingId:
_eq: X-Hasura-MeetingId
- _or:
- user:
isModerator:
_eq: true
- userIsModerator:
_eq: true
- meetingId:
_eq: X-Hasura-AnnotationsNotLockedInMeeting
- userId:

View File

@ -24,16 +24,15 @@ select_permissions:
- pageId
- presentationId
- userId
- sequence
- updatedAt
- annotationInfo
filter:
_and:
- meetingId:
_eq: X-Hasura-MeetingId
- _or:
- user:
isModerator:
_eq: true
- userIsModerator:
_eq: true
- meetingId:
_eq: X-Hasura-AnnotationsNotLockedInMeeting
- userId:

View File

@ -93,22 +93,10 @@ const ActionsBarContainer = (props) => {
const amIPresenter = currentUserData?.presenter;
const amIModerator = currentUserData?.isModerator;
const [pinnedPadDataState, setPinnedPadDataState] = useState(null);
const { data: pinnedPadData } = useDeduplicatedSubscription(
PINNED_PAD_SUBSCRIPTION,
);
useEffect(() => {
const fetchData = async () => {
const { data: pinnedPadData } = await useDeduplicatedSubscription(
PINNED_PAD_SUBSCRIPTION,
);
setPinnedPadDataState(pinnedPadData || []);
};
fetchData();
}, []);
const isSharedNotesPinnedFromGraphql = !!pinnedPadDataState
&& pinnedPadDataState.sharedNotes[0]?.sharedNotesExtId === NOTES_CONFIG.id;
const isSharedNotesPinned = isSharedNotesPinnedFromGraphql;
const allowExternalVideo = useIsExternalVideoEnabled();
const connected = useReactiveVar(connectionStatus.getConnectedStatusVar());
const intl = useIntl();
@ -131,7 +119,12 @@ const ActionsBarContainer = (props) => {
&& (deviceInfo.isPhone || isLayeredView.matches);
if (actionsBarStyle.display === false) return null;
if (!currentMeeting) return null;
if (!pinnedPadData) return null;
const isSharedNotesPinnedFromGraphql = !!pinnedPadData
&& pinnedPadData.sharedNotes[0]?.sharedNotesExtId === NOTES_CONFIG.id;
const isSharedNotesPinned = isSharedNotesPinnedFromGraphql;
return (
<ActionsBar {
...{

View File

@ -74,13 +74,13 @@ const intlMessages = defineMessages({
description: 'locked element label',
},
hideCursorsLabel: {
id: "app.lock-viewers.hideViewersCursor",
id: 'app.lock-viewers.hideViewersCursor',
description: 'label for other viewers cursor',
},
hideAnnotationsLabel: {
id: "app.lock-viewers.hideAnnotationsLabel",
id: 'app.lock-viewers.hideAnnotationsLabel',
description: 'label for other viewers annotation',
}
},
});
const propTypes = {
@ -88,7 +88,7 @@ const propTypes = {
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}).isRequired,
meeting: PropTypes.object.isRequired,
meeting: PropTypes.shape({}).isRequired,
showToggleLabel: PropTypes.bool.isRequired,
updateLockSettings: PropTypes.func.isRequired,
updateWebcamsOnlyForModerator: PropTypes.func.isRequired,
@ -106,6 +106,12 @@ class LockViewersComponent extends Component {
};
}
componentWillUnmount() {
const { closeModal } = this.props;
closeModal();
}
toggleLockSettings(property) {
const { lockSettingsProps } = this.state;
@ -129,18 +135,14 @@ class LockViewersComponent extends Component {
displayLockStatus(status) {
const { intl } = this.props;
return (
status && <Styled.ToggleLabel>
status && (
<Styled.ToggleLabel>
{intl.formatMessage(intlMessages.lockedLabel)}
</Styled.ToggleLabel>
)
);
}
componentWillUnmount() {
const { closeModal } = this.props;
closeModal();
}
render() {
const {
closeModal,
@ -152,6 +154,7 @@ class LockViewersComponent extends Component {
onRequestClose,
priority,
isChatEnabled,
isPrivateChatEnabled,
isSharedNotesEnabled,
} = this.props;
@ -257,7 +260,7 @@ class LockViewersComponent extends Component {
</Styled.Row>
{isChatEnabled ? (
<Fragment>
<>
<Styled.Row data-test="lockPublicChatItem">
<Styled.Col aria-hidden="true">
<Styled.FormElement>
@ -283,34 +286,35 @@ class LockViewersComponent extends Component {
</Styled.FormElementRight>
</Styled.Col>
</Styled.Row>
<Styled.Row data-test="lockPrivateChatItem">
<Styled.Col aria-hidden="true">
<Styled.FormElement>
<Styled.Label>
{intl.formatMessage(intlMessages.privateChatLable)}
</Styled.Label>
</Styled.FormElement>
</Styled.Col>
<Styled.Col>
<Styled.FormElementRight>
{this.displayLockStatus(lockSettingsProps.disablePrivateChat)}
<Toggle
icons={false}
defaultChecked={lockSettingsProps.disablePrivateChat}
onChange={() => {
this.toggleLockSettings('disablePrivateChat');
}}
ariaLabel={intl.formatMessage(intlMessages.privateChatLable)}
showToggleLabel={showToggleLabel}
invertColors={invertColors}
data-test="lockPrivateChat"
/>
</Styled.FormElementRight>
</Styled.Col>
</Styled.Row>
</Fragment>
) : null
}
{isPrivateChatEnabled ? (
<Styled.Row data-test="lockPrivateChatItem">
<Styled.Col aria-hidden="true">
<Styled.FormElement>
<Styled.Label>
{intl.formatMessage(intlMessages.privateChatLable)}
</Styled.Label>
</Styled.FormElement>
</Styled.Col>
<Styled.Col>
<Styled.FormElementRight>
{this.displayLockStatus(lockSettingsProps.disablePrivateChat)}
<Toggle
icons={false}
defaultChecked={lockSettingsProps.disablePrivateChat}
onChange={() => {
this.toggleLockSettings('disablePrivateChat');
}}
ariaLabel={intl.formatMessage(intlMessages.privateChatLable)}
showToggleLabel={showToggleLabel}
invertColors={invertColors}
data-test="lockPrivateChat"
/>
</Styled.FormElementRight>
</Styled.Col>
</Styled.Row>
) : null}
</>
) : null}
{isSharedNotesEnabled
? (
<Styled.Row data-test="lockEditSharedNotesItem">
@ -339,8 +343,7 @@ class LockViewersComponent extends Component {
</Styled.Col>
</Styled.Row>
)
: null
}
: null}
<Styled.Row data-test="lockUserListItem">
<Styled.Col aria-hidden="true">
<Styled.FormElement>

View File

@ -4,7 +4,7 @@ import LockViewersComponent from './component';
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
import { SET_LOCK_SETTINGS_PROPS, SET_WEBCAM_ONLY_FOR_MODERATOR } from './mutations';
import useMeeting from '../../core/hooks/useMeeting';
import { useIsChatEnabled, useIsSharedNotesEnabled } from '../../services/features';
import { useIsChatEnabled, useIsPrivateChatEnabled, useIsSharedNotesEnabled } from '../../services/features';
const LockViewersContainer = (props) => {
const { data: currentUserData } = useCurrentUser((user) => ({
@ -47,6 +47,7 @@ const LockViewersContainer = (props) => {
usersPolicies: m.usersPolicies,
}));
const isChatEnabled = useIsChatEnabled();
const isPrivateChatEnabled = useIsPrivateChatEnabled();
const isSharedNotesEnabled = useIsSharedNotesEnabled();
return amIModerator && meeting && (
@ -57,6 +58,7 @@ const LockViewersContainer = (props) => {
showToggleLabel={false}
meeting={meeting}
isChatEnabled={isChatEnabled}
isPrivateChatEnabled={isPrivateChatEnabled}
isSharedNotesEnabled={isSharedNotesEnabled}
{...props}
/>

View File

@ -6,6 +6,7 @@ import {
import Auth from '/imports/ui/services/auth';
import logger from '/imports/startup/client/logger';
import { toggleMuteMicrophone } from '/imports/ui/components/audio/audio-graphql/audio-controls/input-stream-live-selector/service';
import { useIsPrivateChatEnabled } from '/imports/ui/services/features';
import getFromUserSettings from '/imports/ui/services/users-settings';
export const isVoiceOnlyUser = (userId: string) => userId.toString().startsWith('v_');
@ -31,7 +32,7 @@ export const generateActionsPermissions = (
const parentRoomModerator = getFromUserSettings('bbb_parent_room_moderator', false);
const isSubjectUserGuest = subjectUser.guest;
const hasAuthority = currentUser.isModerator || amISubjectUser;
const allowedToChatPrivately = !amISubjectUser && !isDialInUser;
const allowedToChatPrivately = !amISubjectUser && !isDialInUser && useIsPrivateChatEnabled();
const allowedToMuteAudio = hasAuthority
&& subjectUserVoice?.joined
&& !isMuted

View File

@ -191,30 +191,13 @@ const UserNotesGraphql: React.FC<UserNotesGraphqlProps> = (props) => {
};
const UserNotesContainerGraphql: React.FC<UserNotesContainerGraphqlProps> = (props) => {
type PinnedPadData = {
sharedNotes: Array<{
sharedNotesExtId: string;
}>;
};
const { userLocks } = props;
const disableNotes = userLocks.userNotes;
const [pinnedPadDataState, setPinnedPadDataState] = useState<PinnedPadData | null>(null);
useEffect(() => {
const fetchData = async () => {
const { data: pinnedPadData } = await useDeduplicatedSubscription(
PINNED_PAD_SUBSCRIPTION,
);
setPinnedPadDataState(pinnedPadData || []);
};
fetchData();
}, []);
const { data: pinnedPadData } = useDeduplicatedSubscription(
PINNED_PAD_SUBSCRIPTION,
);
const NOTES_CONFIG = window.meetingClientSettings.public.notes;
const isPinned = !!pinnedPadDataState && pinnedPadDataState.sharedNotes[0]?.sharedNotesExtId === NOTES_CONFIG.id;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sidebarContent = layoutSelectInput((i: any) => i.sidebarContent);
const { sidebarContentPanel } = sidebarContent;
@ -226,7 +209,9 @@ const UserNotesContainerGraphql: React.FC<UserNotesContainerGraphqlProps> = (pro
const hasUnreadNotes = useHasUnreadNotes();
const markNotesAsRead = () => setNotesLastRev(rev);
const isEnabled = NotesService.useIsEnabled();
if (!pinnedPadData) return null;
const isPinned = !!pinnedPadData && pinnedPadData?.sharedNotes[0]?.sharedNotesExtId === NOTES_CONFIG.id;
return (
<UserNotesGraphql
disableNotes={disableNotes}

View File

@ -9,6 +9,8 @@ import {
DefaultFillStyle,
DefaultFontStyle,
DefaultSizeStyle,
DefaultHorizontalAlignStyle,
DefaultVerticalAlignStyle,
InstancePresenceRecordType,
setDefaultUiAssetUrls,
setDefaultEditorAssetUrls,
@ -552,6 +554,9 @@ const Whiteboard = React.memo((props) => {
setTlEditor(editor);
setTldrawAPI(editor);
DefaultHorizontalAlignStyle.defaultValue = isRTL ? 'end' : 'start';
DefaultVerticalAlignStyle.defaultValue = 'start';
editor?.user?.updateUserPreferences({ locale: language });
const colorStyles = [

View File

@ -111,7 +111,6 @@ export const CURRENT_PAGE_ANNOTATIONS_QUERY = gql`query CurrentPageAnnotationsQu
pres_annotation_curr(order_by: { lastUpdatedAt: desc }) {
annotationId
annotationInfo
lastHistorySequence
lastUpdatedAt
pageId
presentationId

View File

@ -131,3 +131,7 @@ export function useIsChatMessageReactionsEnabled() {
&& window.meetingClientSettings.public.chat.toolbar.includes('reactions')
);
}
export function useIsPrivateChatEnabled() {
return useDisabledFeatures().indexOf('privateChat') === -1;
}

View File

@ -479,7 +479,7 @@ endWhenNoModeratorDelayInMinutes=1
# List of features to disable (comma-separated)
# https://docs.bigbluebutton.org/3.0/development/api/#create
# Available options:
# chat, sharedNotes, polls, screenshare, externalVideos, layouts, captions, liveTranscription,
# chat, privateChat, sharedNotes, polls, screenshare, externalVideos, layouts, captions, liveTranscription,
# breakoutRooms, importSharedNotesFromBreakoutRooms, importPresentationWithAnnotationsFromBreakoutRooms,
# presentation, downloadPresentationWithAnnotations, downloadPresentationOriginalFile, downloadPresentationConvertedToPdf,
# learningDashboard, learningDashboardDownloadSessionData,

View File

@ -350,7 +350,7 @@ const createEndpointTableData = [
"name": "disabledFeatures",
"required": false,
"type": "String",
"description": (<>List (comma-separated) of features to disable in a particular meeting. (added 2.5)<br /><br />Available options to disable:<br /><ul><li><code className="language-plaintext highlighter-rouge">breakoutRooms</code>- <b>Breakout Rooms</b> </li><li><code className="language-plaintext highlighter-rouge">captions</code>- <b>Closed Caption</b> </li><li><code className="language-plaintext highlighter-rouge">chat</code>- <b>Chat</b></li><li><code className="language-plaintext highlighter-rouge">downloadPresentationWithAnnotations</code>- <b>Annotated presentation download</b></li><li><code className="language-plaintext highlighter-rouge">snapshotOfCurrentSlide</code>- <b>Allow snapshot of the current slide</b></li><li><code className="language-plaintext highlighter-rouge">externalVideos</code>- <b>Share an external video</b> </li><li><code className="language-plaintext highlighter-rouge">importPresentationWithAnnotationsFromBreakoutRooms</code>- <b>Capture breakout presentation</b></li><li><code className="language-plaintext highlighter-rouge">importSharedNotesFromBreakoutRooms</code>- <b>Capture breakout shared notes</b></li><li><code className="language-plaintext highlighter-rouge">layouts</code>- <b>Layouts</b> (allow only default layout)</li><li><code className="language-plaintext highlighter-rouge">learningDashboard</code>- <b>Learning Analytics Dashboard</b></li><li><code className="language-plaintext highlighter-rouge">learningDashboardDownloadSessionData</code>- <b>Learning Analytics Dashboard Download Session Data (prevents the option to download)</b></li><li><code className="language-plaintext highlighter-rouge">polls</code>- <b>Polls</b> </li><li><code className="language-plaintext highlighter-rouge">screenshare</code>- <b>Screen Sharing</b></li><li><code className="language-plaintext highlighter-rouge">sharedNotes</code>- <b>Shared Notes</b></li><li><code className="language-plaintext highlighter-rouge">virtualBackgrounds</code>- <b>Virtual Backgrounds</b></li><li><code className="language-plaintext highlighter-rouge">customVirtualBackgrounds</code>- <b>Virtual Backgrounds Upload</b></li><li><code className="language-plaintext highlighter-rouge">liveTranscription</code>- <b>Live Transcription</b></li><li><code className="language-plaintext highlighter-rouge">presentation</code>- <b>Presentation</b></li><li><code className="language-plaintext highlighter-rouge">cameraAsContent</code>-<b>Enables/Disables camera as a content</b></li><li><code className="language-plaintext highlighter-rouge">timer</code>- <b>disables timer</b></li><li><code className="language-plaintext highlighter-rouge">infiniteWhiteboard</code>- <b>Infinite Whiteboard (added in BigBlueButton 3.0)</b></li></ul></>)
"description": (<>List (comma-separated) of features to disable in a particular meeting. (added 2.5)<br /><br />Available options to disable:<br /><ul><li><code className="language-plaintext highlighter-rouge">breakoutRooms</code>- <b>Breakout Rooms</b> </li><li><code className="language-plaintext highlighter-rouge">captions</code>- <b>Closed Caption</b> </li><li><code className="language-plaintext highlighter-rouge">chat</code>- <b>Chat</b></li><li><code className="language-plaintext highlighter-rouge">privateChat</code>- <b>Private Chat</b></li><li><code className="language-plaintext highlighter-rouge">downloadPresentationWithAnnotations</code>- <b>Annotated presentation download</b></li><li><code className="language-plaintext highlighter-rouge">snapshotOfCurrentSlide</code>- <b>Allow snapshot of the current slide</b></li><li><code className="language-plaintext highlighter-rouge">externalVideos</code>- <b>Share an external video</b> </li><li><code className="language-plaintext highlighter-rouge">importPresentationWithAnnotationsFromBreakoutRooms</code>- <b>Capture breakout presentation</b></li><li><code className="language-plaintext highlighter-rouge">importSharedNotesFromBreakoutRooms</code>- <b>Capture breakout shared notes</b></li><li><code className="language-plaintext highlighter-rouge">layouts</code>- <b>Layouts</b> (allow only default layout)</li><li><code className="language-plaintext highlighter-rouge">learningDashboard</code>- <b>Learning Analytics Dashboard</b></li><li><code className="language-plaintext highlighter-rouge">learningDashboardDownloadSessionData</code>- <b>Learning Analytics Dashboard Download Session Data (prevents the option to download)</b></li><li><code className="language-plaintext highlighter-rouge">polls</code>- <b>Polls</b> </li><li><code className="language-plaintext highlighter-rouge">screenshare</code>- <b>Screen Sharing</b></li><li><code className="language-plaintext highlighter-rouge">sharedNotes</code>- <b>Shared Notes</b></li><li><code className="language-plaintext highlighter-rouge">virtualBackgrounds</code>- <b>Virtual Backgrounds</b></li><li><code className="language-plaintext highlighter-rouge">customVirtualBackgrounds</code>- <b>Virtual Backgrounds Upload</b></li><li><code className="language-plaintext highlighter-rouge">liveTranscription</code>- <b>Live Transcription</b></li><li><code className="language-plaintext highlighter-rouge">presentation</code>- <b>Presentation</b></li><li><code className="language-plaintext highlighter-rouge">cameraAsContent</code>-<b>Enables/Disables camera as a content</b></li><li><code className="language-plaintext highlighter-rouge">timer</code>- <b>disables timer</b></li><li><code className="language-plaintext highlighter-rouge">infiniteWhiteboard</code>- <b>Infinite Whiteboard (added in BigBlueButton 3.0)</b></li></ul></>)
},
{
"name": "disabledFeaturesExclude",

View File

@ -26,7 +26,7 @@ gem 'journald-logger', '~> 3.0'
gem 'jwt', '~> 2.2'
gem 'locale', '~> 2.1'
gem 'loofah', '~> 2.19.1'
gem 'nokogiri', '~> 1.13.10', '>= 1.13.10'
gem 'nokogiri', '~> 1.16.5'
gem 'open4', '~> 1.3'
gem 'rb-inotify', '~> 0.10'
gem 'redis', '~> 4.1'

View File

@ -2,7 +2,7 @@ GEM
remote: https://rubygems.org/
specs:
absolute_time (1.0.0)
activesupport (7.0.5)
activesupport (7.0.7.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@ -12,11 +12,11 @@ GEM
activesupport (>= 5.0.0.1, < 8)
rexml
builder (3.2.4)
concurrent-ruby (1.2.2)
concurrent-ruby (1.3.4)
crass (1.0.6)
fastimage (2.2.6)
ffi (1.15.5)
i18n (1.14.1)
i18n (1.14.6)
concurrent-ruby (~> 1.0)
java_properties (0.0.4)
journald-logger (3.1.0)
@ -28,14 +28,14 @@ GEM
loofah (2.19.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mini_portile2 (2.8.0)
mini_portile2 (2.8.7)
minitest (5.14.4)
mono_logger (1.1.2)
multi_json (1.15.0)
mustermann (3.0.0)
ruby2_keywords (~> 0.0.1)
nokogiri (1.13.10)
mini_portile2 (~> 2.8.0)
nokogiri (1.16.5)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
open4 (1.3.4)
optimist (3.0.1)
@ -44,7 +44,7 @@ GEM
ast (~> 2.4.1)
racc
racc (1.8.1)
rack (2.2.8)
rack (2.2.8.1)
rack-protection (3.1.0)
rack (~> 2.2, >= 2.2.4)
rainbow (3.1.1)
@ -60,8 +60,7 @@ GEM
multi_json (~> 1.0)
redis-namespace (~> 1.6)
sinatra (>= 0.9.2)
rexml (3.3.6)
strscan
rexml (3.3.9)
rubocop (1.34.1)
json (~> 2.3)
parallel (~> 1.10)
@ -82,7 +81,6 @@ GEM
rack (~> 2.2, >= 2.2.4)
rack-protection (= 3.1.0)
tilt (~> 2.0)
strscan (3.1.0)
tilt (2.2.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
@ -102,7 +100,7 @@ DEPENDENCIES
locale (~> 2.1)
loofah (~> 2.19.1)
minitest (~> 5.14.1)
nokogiri (~> 1.13.10, >= 1.13.10)
nokogiri (~> 1.16.5)
open4 (~> 1.3)
optimist
rake (>= 12.3, < 14)