Merge remote-tracking branch 'upstream/v3.0.x-release' into migrate-notification
This commit is contained in:
commit
f0b334450b
@ -4,8 +4,9 @@ import org.apache.pekko.actor.ActorContext
|
||||
import org.apache.pekko.event.Logging
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting }
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.db.{ CaptionLocaleDAO, CaptionTypes }
|
||||
|
||||
class CaptionApp2x(implicit val context: ActorContext) extends RightsManagementTrait {
|
||||
val log = Logging(context.system, getClass)
|
||||
@ -84,6 +85,7 @@ class CaptionApp2x(implicit val context: ActorContext) extends RightsManagementT
|
||||
val event = UpdateCaptionOwnerEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
CaptionLocaleDAO.insertOrUpdateCaptionLocale(liveMeeting.props.meetingProp.intId, locale, CaptionTypes.TYPED, newOwnerId)
|
||||
}
|
||||
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
|
@ -9,7 +9,7 @@ case class CaptionDbModel(
|
||||
meetingId: String,
|
||||
captionType: String,
|
||||
userId: String,
|
||||
lang: String,
|
||||
locale: String,
|
||||
captionText: String,
|
||||
createdAt: java.sql.Timestamp
|
||||
)
|
||||
@ -19,10 +19,10 @@ class CaptionTableDef(tag: Tag) extends Table[CaptionDbModel](tag, None, "captio
|
||||
val meetingId = column[String]("meetingId")
|
||||
val captionType = column[String]("captionType")
|
||||
val userId = column[String]("userId")
|
||||
val lang = column[String]("lang")
|
||||
val locale = column[String]("locale")
|
||||
val captionText = column[String]("captionText")
|
||||
val createdAt = column[java.sql.Timestamp]("createdAt")
|
||||
def * = (captionId, meetingId, captionType, userId, lang, captionText, createdAt) <> (CaptionDbModel.tupled, CaptionDbModel.unapply)
|
||||
def * = (captionId, meetingId, captionType, userId, locale, captionText, createdAt) <> (CaptionDbModel.tupled, CaptionDbModel.unapply)
|
||||
}
|
||||
|
||||
object CaptionTypes {
|
||||
@ -32,7 +32,7 @@ object CaptionTypes {
|
||||
|
||||
object CaptionDAO {
|
||||
|
||||
def insertOrUpdateAudioCaption(captionId: String, meetingId: String, userId: String, transcript: String, lang: String) = {
|
||||
def insertOrUpdateAudioCaption(captionId: String, meetingId: String, userId: String, transcript: String, locale: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[CaptionTableDef].insertOrUpdate(
|
||||
CaptionDbModel(
|
||||
@ -40,7 +40,7 @@ object CaptionDAO {
|
||||
meetingId = meetingId,
|
||||
captionType = CaptionTypes.AUDIO_TRANSCRIPTION,
|
||||
userId = userId,
|
||||
lang = lang,
|
||||
locale = locale,
|
||||
captionText = transcript,
|
||||
createdAt = new java.sql.Timestamp(System.currentTimeMillis())
|
||||
)
|
||||
@ -68,7 +68,7 @@ object CaptionDAO {
|
||||
SELECT "captionId", "captionText", "createdAt"
|
||||
FROM caption
|
||||
WHERE "meetingId" = ${meetingId}
|
||||
AND lang = ${locale}
|
||||
AND locale = ${locale}
|
||||
AND "captionType" = ${CaptionTypes.TYPED}
|
||||
order by "createdAt" desc
|
||||
limit 2
|
||||
@ -80,13 +80,13 @@ object CaptionDAO {
|
||||
LIMIT 1
|
||||
)
|
||||
RETURNING *)
|
||||
INSERT INTO caption ("captionId", "meetingId", "captionType", "userId", "lang", "captionText", "createdAt")
|
||||
INSERT INTO caption ("captionId", "meetingId", "captionType", "userId", "locale", "captionText", "createdAt")
|
||||
SELECT md5(random()::text || clock_timestamp()::text), ${meetingId}, 'TYPED', ${userId}, ${locale}, ${line}, current_timestamp
|
||||
WHERE NOT EXISTS (SELECT * FROM upsert)
|
||||
AND ${line} NOT IN (SELECT "captionText"
|
||||
FROM caption
|
||||
WHERE "meetingId" = ${meetingId}
|
||||
AND lang = ${locale}
|
||||
AND locale = ${locale}
|
||||
AND "captionType" = ${CaptionTypes.TYPED}
|
||||
order by "createdAt" desc
|
||||
limit 2
|
||||
|
41
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/CaptionLangDAO.scala
Executable file
41
akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/CaptionLangDAO.scala
Executable file
@ -0,0 +1,41 @@
|
||||
package org.bigbluebutton.core.db
|
||||
|
||||
import slick.jdbc.PostgresProfile.api._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.util.{ Failure, Success }
|
||||
|
||||
case class CaptionLocaleDbModel(
|
||||
meetingId: String,
|
||||
locale: String,
|
||||
captionType: String,
|
||||
ownerUserId: String,
|
||||
updatedAt: java.sql.Timestamp
|
||||
)
|
||||
|
||||
class CaptionLocaleTableDef(tag: Tag) extends Table[CaptionLocaleDbModel](tag, None, "caption_locale") {
|
||||
val meetingId = column[String]("meetingId", O.PrimaryKey)
|
||||
val locale = column[String]("locale", O.PrimaryKey)
|
||||
val captionType = column[String]("captionType", O.PrimaryKey)
|
||||
val ownerUserId = column[String]("ownerUserId")
|
||||
val updatedAt = column[java.sql.Timestamp]("updatedAt")
|
||||
def * = (meetingId, locale, captionType, ownerUserId, updatedAt) <> (CaptionLocaleDbModel.tupled, CaptionLocaleDbModel.unapply)
|
||||
}
|
||||
|
||||
object CaptionLocaleDAO {
|
||||
def insertOrUpdateCaptionLocale(meetingId: String, locale: String, captionType: String, ownerUserId: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[CaptionLocaleTableDef].insertOrUpdate(
|
||||
CaptionLocaleDbModel(
|
||||
meetingId = meetingId,
|
||||
locale = locale,
|
||||
captionType = captionType,
|
||||
ownerUserId = ownerUserId,
|
||||
updatedAt = new java.sql.Timestamp(System.currentTimeMillis())
|
||||
)
|
||||
)
|
||||
).onComplete {
|
||||
case Success(_) => DatabaseConnection.logger.debug(s"Upserted caption with ID $meetingId-$locale on CaptionLocale table")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error upserting caption on CaptionLocale: $e")
|
||||
}
|
||||
}
|
||||
}
|
@ -470,7 +470,7 @@ class ReceivedJsonMsgHandlerActor(
|
||||
route[CheckGraphqlMiddlewareAlivePongSysMsg](meetingManagerChannel, envelope, jsonNode)
|
||||
|
||||
case _ =>
|
||||
log.error("Cannot route envelope name " + envelope.name)
|
||||
log.debug("Cannot route envelope name " + envelope.name)
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
24
bbb-graphql-actions/src/actions/captionSetOwner.ts
Normal file
24
bbb-graphql-actions/src/actions/captionSetOwner.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { RedisMessage } from '../types';
|
||||
|
||||
export default function buildRedisMessage(sessionVariables: Record<string, unknown>, input: Record<string, unknown>): RedisMessage {
|
||||
const eventName = `UpdateCaptionOwnerPubMsg`;
|
||||
|
||||
const routing = {
|
||||
meetingId: sessionVariables['x-hasura-meetingid'] as String,
|
||||
userId: sessionVariables['x-hasura-userid'] as String
|
||||
};
|
||||
|
||||
const header = {
|
||||
name: eventName,
|
||||
meetingId: routing.meetingId,
|
||||
userId: routing.userId
|
||||
};
|
||||
|
||||
const body = {
|
||||
name: '',
|
||||
locale: input.locale,
|
||||
ownerId: input.ownerUserId,
|
||||
};
|
||||
|
||||
return { eventName, routing, header, body };
|
||||
}
|
@ -1748,21 +1748,51 @@ SELECT
|
||||
FLOOR(EXTRACT(EPOCH FROM current_timestamp) * 1000)::bigint AS "currentTimeMillis";
|
||||
|
||||
------------------------------------
|
||||
----audioCaption
|
||||
----audioCaption or typedCaption
|
||||
|
||||
CREATE TABLE "caption_locale" (
|
||||
"meetingId" varchar(100) NOT NULL REFERENCES "meeting"("meetingId") ON DELETE CASCADE,
|
||||
"locale" varchar(15) NOT NULL,
|
||||
"captionType" varchar(100) NOT NULL, --Audio Transcription or Typed Caption
|
||||
"ownerUserId" varchar(50),
|
||||
"createdAt" timestamp with time zone default current_timestamp,
|
||||
"updatedAt" timestamp with time zone,
|
||||
CONSTRAINT "caption_locale_pk" primary key ("meetingId","locale","captionType"),
|
||||
FOREIGN KEY ("meetingId", "ownerUserId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE "caption" (
|
||||
"captionId" varchar(100) NOT NULL PRIMARY KEY,
|
||||
"meetingId" varchar(100) NOT NULL REFERENCES "meeting"("meetingId") ON DELETE CASCADE,
|
||||
"captionType" varchar(100) NOT NULL, --Audio Transcription or Typed Caption
|
||||
"userId" varchar(50),
|
||||
"lang" varchar(15),
|
||||
"locale" varchar(15),
|
||||
"captionText" text,
|
||||
"createdAt" timestamp with time zone,
|
||||
FOREIGN KEY ("meetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE
|
||||
);
|
||||
|
||||
create index idx_caption on caption("meetingId","lang","createdAt");
|
||||
create index idx_caption_captionType on caption("meetingId","lang","captionType","createdAt");
|
||||
CREATE OR REPLACE FUNCTION "update_caption_locale_owner_func"() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
WITH upsert AS (
|
||||
UPDATE "caption_locale" SET
|
||||
"ownerUserId" = NEW."userId",
|
||||
"updatedAt" = current_timestamp
|
||||
WHERE "meetingId"=NEW."meetingId" AND "locale"=NEW."locale" AND "captionType"= NEW."captionType"
|
||||
RETURNING *)
|
||||
INSERT INTO "caption_locale"("meetingId","locale","captionType","ownerUserId")
|
||||
SELECT NEW."meetingId", NEW."locale", NEW."captionType", NEW."userId"
|
||||
WHERE NOT EXISTS (SELECT * FROM upsert);
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER "insert_caption_trigger" BEFORE INSERT ON "caption" FOR EACH ROW
|
||||
EXECUTE FUNCTION "update_caption_locale_owner_func"();
|
||||
|
||||
create index idx_caption on caption("meetingId","locale","createdAt");
|
||||
create index idx_caption_captionType on caption("meetingId","locale","captionType","createdAt");
|
||||
|
||||
CREATE OR REPLACE VIEW "v_caption" AS
|
||||
SELECT *
|
||||
@ -1770,11 +1800,11 @@ FROM "caption"
|
||||
WHERE "createdAt" > current_timestamp - INTERVAL '5 seconds';
|
||||
|
||||
CREATE OR REPLACE VIEW "v_caption_typed_activeLocales" AS
|
||||
select distinct "meetingId", "lang", "userId"
|
||||
from "caption"
|
||||
select distinct "meetingId", "locale", "ownerUserId"
|
||||
from "caption_locale"
|
||||
where "captionType" = 'TYPED';
|
||||
|
||||
create index "idx_caption_typed_activeLocales" on caption("meetingId","lang","userId") where "captionType" = 'TYPED';
|
||||
create index "idx_caption_typed_activeLocales" on caption("meetingId","locale","userId") where "captionType" = 'TYPED';
|
||||
|
||||
------------------------------------
|
||||
----
|
||||
|
@ -559,3 +559,11 @@ input GuestUserApprovalStatus {
|
||||
status: String!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
captionSetOwner(
|
||||
locale: String!
|
||||
ownerUserId: String!
|
||||
): Boolean
|
||||
}
|
||||
|
||||
|
||||
|
@ -493,6 +493,12 @@ actions:
|
||||
handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}'
|
||||
permissions:
|
||||
- role: bbb_client
|
||||
- name: captionSetOwner
|
||||
definition:
|
||||
kind: synchronous
|
||||
handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}'
|
||||
permissions:
|
||||
- role: bbb_client
|
||||
custom_types:
|
||||
enums: []
|
||||
input_objects:
|
||||
|
@ -26,7 +26,7 @@ select_permissions:
|
||||
- captionId
|
||||
- userId
|
||||
- captionType
|
||||
- lang
|
||||
- locale
|
||||
filter:
|
||||
meetingId:
|
||||
_eq: X-Hasura-MeetingId
|
||||
|
@ -12,7 +12,7 @@ object_relationships:
|
||||
manual_configuration:
|
||||
column_mapping:
|
||||
meetingId: meetingId
|
||||
userId: userId
|
||||
ownerUserId: userId
|
||||
insertion_order: null
|
||||
remote_table:
|
||||
name: v_user_ref
|
||||
@ -21,7 +21,7 @@ select_permissions:
|
||||
- role: bbb_client
|
||||
permission:
|
||||
columns:
|
||||
- lang
|
||||
- locale
|
||||
filter:
|
||||
meetingId:
|
||||
_eq: X-Hasura-MeetingId
|
||||
|
@ -1,4 +1,10 @@
|
||||
#!/bin/bash
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run this script as root ( or with sudo )" ;
|
||||
exit 1;
|
||||
fi;
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
export LANGUAGE="en_US.UTF-8"
|
||||
export LC_ALL="en_US.UTF-8"
|
||||
|
@ -12,7 +12,6 @@ import Captions from '/imports/api/captions';
|
||||
import Pads, { PadsSessions, PadsUpdates } from '/imports/api/pads';
|
||||
import AuthTokenValidation from '/imports/api/auth-token-validation';
|
||||
import Breakouts from '/imports/api/breakouts';
|
||||
import BreakoutsHistory from '/imports/api/breakouts-history';
|
||||
import Meetings, {
|
||||
RecordMeetings, MeetingTimeRemaining, Notifications,
|
||||
} from '/imports/api/meetings';
|
||||
@ -38,7 +37,6 @@ export const localCollectionRegistry = {
|
||||
localRecordMeetingsSync: new AbstractCollection(RecordMeetings, RecordMeetings),
|
||||
localMeetingTimeRemainingSync: new AbstractCollection(MeetingTimeRemaining, MeetingTimeRemaining),
|
||||
localBreakoutsSync: new AbstractCollection(Breakouts, Breakouts),
|
||||
localBreakoutsHistorySync: new AbstractCollection(BreakoutsHistory, BreakoutsHistory),
|
||||
localMeetingsSync: new AbstractCollection(Meetings, Meetings),
|
||||
localUsersSync: new AbstractCollection(Users, Users),
|
||||
localNotificationsSync: new AbstractCollection(Notifications, Notifications),
|
||||
|
@ -1,13 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
const collectionOptions = Meteor.isClient ? {
|
||||
connection: null,
|
||||
} : {};
|
||||
|
||||
const BreakoutsHistory = new Mongo.Collection('breakouts-history', collectionOptions);
|
||||
|
||||
if (Meteor.isServer) {
|
||||
BreakoutsHistory.createIndexAsync({ meetingId: 1 });
|
||||
}
|
||||
|
||||
export default BreakoutsHistory;
|
@ -1,6 +0,0 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import handleBreakoutRoomsList from './handlers/breakoutRoomsList';
|
||||
import messageToAllSent from '/imports/api/breakouts-history/server/handlers/messageToAllSent';
|
||||
|
||||
RedisPubSub.on('BreakoutRoomsListEvtMsg', handleBreakoutRoomsList);
|
||||
RedisPubSub.on('SendMessageToAllBreakoutRoomsEvtMsg', messageToAllSent);
|
@ -1,35 +0,0 @@
|
||||
import { check } from 'meteor/check';
|
||||
import BreakoutsHistory from '/imports/api/breakouts-history';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default async function handleBreakoutRoomsList({ body }) {
|
||||
const {
|
||||
meetingId,
|
||||
rooms,
|
||||
} = body;
|
||||
|
||||
check(meetingId, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
meetingId,
|
||||
rooms,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const { insertedId } = await BreakoutsHistory.upsertAsync(selector, modifier);
|
||||
|
||||
if (insertedId) {
|
||||
Logger.info(`Added rooms to breakout-history Data: meeting=${meetingId}`);
|
||||
} else {
|
||||
Logger.info(`Upserted rooms to breakout-history Data: meeting=${meetingId}`);
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(`Adding rooms to the collection breakout-history: ${err}`);
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import BreakoutsHistory from '/imports/api/breakouts-history';
|
||||
|
||||
export default async function handleSendMessageToAllBreakoutRoomsEvtMsg({ body }, meetingId) {
|
||||
const {
|
||||
senderId,
|
||||
msg,
|
||||
totalOfRooms,
|
||||
} = body;
|
||||
|
||||
check(meetingId, String);
|
||||
check(senderId, String);
|
||||
check(msg, String);
|
||||
check(totalOfRooms, Number);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$push: {
|
||||
broadcastMsgs: {
|
||||
senderId,
|
||||
msg,
|
||||
totalOfRooms,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const { insertedId } = await BreakoutsHistory.upsertAsync(selector, modifier);
|
||||
|
||||
if (insertedId) {
|
||||
Logger.info(`Added broadCastMsg to breakout-history Data: meeting=${meetingId}`);
|
||||
} else {
|
||||
Logger.info(`Upserted broadCastMsg to breakout-history Data: meeting=${meetingId}`);
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(`Adding broadCastMsg to the collection breakout-history: ${err}`);
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
import './eventHandlers';
|
||||
import './publishers';
|
@ -1,58 +0,0 @@
|
||||
import BreakoutsHistory from '/imports/api/breakouts-history';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import AuthTokenValidation, { ValidationStates } from '/imports/api/auth-token-validation';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Users from '/imports/api/users';
|
||||
import { publicationSafeGuard } from '/imports/api/common/server/helpers';
|
||||
|
||||
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
|
||||
|
||||
async function breakoutsHistory() {
|
||||
const tokenValidation = await AuthTokenValidation
|
||||
.findOneAsync({ connectionId: this.connection.id });
|
||||
|
||||
if (!tokenValidation || tokenValidation.validationStatus !== ValidationStates.VALIDATED) {
|
||||
Logger.warn(`Publishing Meetings-history was requested by unauth connection ${this.connection.id}`);
|
||||
return Meetings.find({ meetingId: '' });
|
||||
}
|
||||
|
||||
const { meetingId, userId } = tokenValidation;
|
||||
Logger.debug('Publishing Breakouts-History', { meetingId, userId });
|
||||
|
||||
const User = await Users.findOneAsync({ userId, meetingId }, { fields: { userId: 1, role: 1 } });
|
||||
if (!User || User.role !== ROLE_MODERATOR) {
|
||||
return BreakoutsHistory.find({ meetingId: '' });
|
||||
}
|
||||
|
||||
check(meetingId, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
};
|
||||
|
||||
// Monitor this publication and stop it when user is not a moderator anymore
|
||||
const comparisonFunc = async () => {
|
||||
const user = await Users
|
||||
.findOneAsync({ userId, meetingId }, { fields: { role: 1, userId: 1 } });
|
||||
const condition = user.role === ROLE_MODERATOR;
|
||||
|
||||
if (!condition) {
|
||||
Logger.info(`conditions aren't filled anymore in publication ${this._name}:
|
||||
user.role === ROLE_MODERATOR :${condition}, user.role: ${user.role} ROLE_MODERATOR: ${ROLE_MODERATOR}`);
|
||||
}
|
||||
|
||||
return condition;
|
||||
};
|
||||
publicationSafeGuard(comparisonFunc, this);
|
||||
|
||||
return BreakoutsHistory.find(selector);
|
||||
}
|
||||
|
||||
function publish(...args) {
|
||||
const boundUsers = breakoutsHistory.bind(this);
|
||||
return boundUsers(...args);
|
||||
}
|
||||
|
||||
Meteor.publish('breakouts-history', publish);
|
@ -2,7 +2,6 @@ import Breakouts from '/imports/api/breakouts';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import flat from 'flat';
|
||||
import handleBreakoutRoomsListHist from '/imports/api/breakouts-history/server/handlers/breakoutRoomsList';
|
||||
|
||||
export default async function handleBreakoutRoomsList({ body }, meetingId) {
|
||||
// 0 seconds default breakout time, forces use of real expiration time
|
||||
@ -52,6 +51,5 @@ export default async function handleBreakoutRoomsList({ body }, meetingId) {
|
||||
} else {
|
||||
Logger.error(`updating breakout: ${numberAffected}`);
|
||||
}
|
||||
handleBreakoutRoomsListHist({ body });
|
||||
});
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ export default async function handleWebcamSync({ body }, meetingId) {
|
||||
|
||||
const streamsIds = webcamListSync.map((webcam) => webcam.stream);
|
||||
|
||||
const webcamStreams = VideoStreams.find({
|
||||
const webcamStreams = await VideoStreams.find({
|
||||
meetingId,
|
||||
stream: { $in: streamsIds },
|
||||
}, {
|
||||
@ -42,7 +42,7 @@ export default async function handleWebcamSync({ body }, meetingId) {
|
||||
stream: 1,
|
||||
userId: 1,
|
||||
},
|
||||
}).fetchAsynch();
|
||||
}).fetchAsync();
|
||||
|
||||
await Promise.all(videoStreamsToRemove
|
||||
.map(async (videoStream) => {
|
||||
|
@ -4,7 +4,6 @@ import { makeCall } from '/imports/ui/services/api';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Breakouts from '/imports/api/breakouts';
|
||||
import NotesService from '/imports/ui/components/notes/service';
|
||||
import BreakoutsHistory from '/imports/api/breakouts-history';
|
||||
|
||||
const DIAL_IN_USER = 'dial-in-user';
|
||||
|
||||
@ -12,16 +11,6 @@ const getBreakouts = () => Breakouts.find({ parentMeetingId: Auth.meetingID })
|
||||
.fetch()
|
||||
.sort((a, b) => a.sequence - b.sequence);
|
||||
|
||||
const getLastBreakouts = () => {
|
||||
const lastBreakouts = BreakoutsHistory.findOne({ meetingId: Auth.meetingID });
|
||||
if (lastBreakouts) {
|
||||
return lastBreakouts.rooms
|
||||
.sort((a, b) => a.sequence - b.sequence);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const currentBreakoutUsers = (user) => !Breakouts.findOne({
|
||||
'joinedUsers.userId': new RegExp(`^${user.userId}`),
|
||||
});
|
||||
@ -50,7 +39,6 @@ export default {
|
||||
joinedUsers: { $exists: true },
|
||||
}, { fields: { joinedUsers: 1, breakoutId: 1, sequence: 1 }, sort: { sequence: 1 } }).fetch(),
|
||||
getBreakouts,
|
||||
getLastBreakouts,
|
||||
getUsersNotJoined,
|
||||
isSharedNotesPinned: () => NotesService.isSharedNotesPinned(),
|
||||
};
|
||||
|
@ -585,7 +585,7 @@ setRandomUserSelectModalIsOpen(value) {
|
||||
pushAlertEnabled,
|
||||
shouldShowPresentation,
|
||||
shouldShowScreenshare,
|
||||
shouldShowSharedNotes,
|
||||
isSharedNotesPinned,
|
||||
isPresenter,
|
||||
selectedLayout,
|
||||
presentationIsOpen,
|
||||
@ -623,7 +623,7 @@ setRandomUserSelectModalIsOpen(value) {
|
||||
<BannerBarContainer />
|
||||
<NotificationsBarContainer />
|
||||
<SidebarNavigationContainer />
|
||||
<SidebarContentContainer isSharedNotesPinned={shouldShowSharedNotes} />
|
||||
<SidebarContentContainer isSharedNotesPinned={isSharedNotesPinned} />
|
||||
<NavBarContainer main="new" />
|
||||
<WebcamContainer isLayoutSwapped={!presentationIsOpen} layoutType={selectedLayout} />
|
||||
<ExternalVideoPlayerContainer />
|
||||
@ -653,11 +653,10 @@ setRandomUserSelectModalIsOpen(value) {
|
||||
)
|
||||
: null
|
||||
}
|
||||
{shouldShowSharedNotes
|
||||
{isSharedNotesPinned
|
||||
? (
|
||||
<NotesContainer
|
||||
area="media"
|
||||
layoutType={selectedLayout}
|
||||
/>
|
||||
) : null}
|
||||
{this.renderCaptions()}
|
||||
|
@ -68,7 +68,6 @@ const AppContainer = (props) => {
|
||||
meetingLayoutCameraPosition,
|
||||
meetingLayoutFocusedCamera,
|
||||
meetingLayoutVideoRate,
|
||||
isSharedNotesPinned,
|
||||
viewScreenshare,
|
||||
...otherProps
|
||||
} = props;
|
||||
@ -81,6 +80,7 @@ const AppContainer = (props) => {
|
||||
const cameraDock = layoutSelectOutput((i) => i.cameraDock);
|
||||
const cameraDockInput = layoutSelectInput((i) => i.cameraDock);
|
||||
const presentation = layoutSelectInput((i) => i.presentation);
|
||||
const sharedNotesInput = layoutSelectInput((i) => i.sharedNotes);
|
||||
const deviceType = layoutSelect((i) => i.deviceType);
|
||||
const layoutContextDispatch = layoutDispatch();
|
||||
|
||||
@ -90,8 +90,9 @@ const AppContainer = (props) => {
|
||||
const toggleVoice = useToggleVoice();
|
||||
const setLocalSettings = useUserChangedLocalSettings();
|
||||
const { data: pinnedPadData } = useSubscription(PINNED_PAD_SUBSCRIPTION);
|
||||
const shouldShowSharedNotes = !!pinnedPadData
|
||||
const isSharedNotesPinnedFromGraphql = !!pinnedPadData
|
||||
&& pinnedPadData.sharedNotes[0]?.sharedNotesExtId === NOTES_CONFIG.id;
|
||||
const isSharedNotesPinned = sharedNotesInput?.isPinned && isSharedNotesPinnedFromGraphql;
|
||||
|
||||
const setMobileUser = (mobile) => {
|
||||
setMobileFlag({
|
||||
@ -185,7 +186,7 @@ const AppContainer = (props) => {
|
||||
|
||||
const shouldShowScreenshare = propsShouldShowScreenshare
|
||||
&& (viewScreenshare || isPresenter);
|
||||
const shouldShowPresentation = (!shouldShowScreenshare && !shouldShowSharedNotes
|
||||
const shouldShowPresentation = (!shouldShowScreenshare && !isSharedNotesPinned
|
||||
&& !shouldShowExternalVideo && !shouldShowGenericComponent
|
||||
&& (presentationIsOpen || presentationRestoreOnUpdate)) && isPresentationEnabled();
|
||||
return currentUserId
|
||||
@ -227,7 +228,7 @@ const AppContainer = (props) => {
|
||||
enforceLayout: validateEnforceLayout(currentUserData),
|
||||
isModerator,
|
||||
shouldShowScreenshare,
|
||||
shouldShowSharedNotes,
|
||||
isSharedNotesPinned,
|
||||
shouldShowPresentation,
|
||||
setMobileUser,
|
||||
toggleVoice,
|
||||
@ -335,7 +336,6 @@ export default withTracker(() => {
|
||||
hideActionsBar: getFromUserSettings('bbb_hide_actions_bar', false),
|
||||
hideNavBar: getFromUserSettings('bbb_hide_nav_bar', false),
|
||||
ignorePollNotifications: Session.get('ignorePollNotifications'),
|
||||
isSharedNotesPinned: MediaService.shouldShowSharedNotes(),
|
||||
User: currentUser,
|
||||
};
|
||||
})(AppContainer);
|
||||
|
@ -174,13 +174,21 @@ const RoomManagmentState: React.FC<RoomManagmentStateProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (runningRooms && init) {
|
||||
const usersToMove: string[] = [];
|
||||
const toRooms: number[] = [];
|
||||
|
||||
runningRooms.forEach((r: breakoutRoom) => {
|
||||
r.participants.forEach((u) => {
|
||||
if (!rooms[r.sequence]?.users?.find((user) => user.userId === u.user.userId)) {
|
||||
moveUser(u.user.userId, 0, r.sequence);
|
||||
usersToMove.push(u.user.userId);
|
||||
toRooms.push(r.sequence);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (usersToMove.length > 0) {
|
||||
moveUser(usersToMove, 0, toRooms);
|
||||
}
|
||||
}
|
||||
}, [runningRooms, init]);
|
||||
|
||||
|
@ -12,8 +12,6 @@ import * as PluginSdk from 'bigbluebutton-html-plugin-sdk';
|
||||
import { UI_DATA_LISTENER_SUBSCRIBED } from 'bigbluebutton-html-plugin-sdk/dist/cjs/ui-data-hooks/consts';
|
||||
import { ExternalVideoVolumeUiDataNames } from 'bigbluebutton-html-plugin-sdk';
|
||||
import { ExternalVideoVolumeUiDataPayloads } from 'bigbluebutton-html-plugin-sdk/dist/cjs/ui-data-hooks/external-video/volume/types';
|
||||
import MediaService from '/imports/ui/components/media/service';
|
||||
import NotesService from '/imports/ui/components/notes/service';
|
||||
|
||||
import useMeeting from '/imports/ui/core/hooks/useMeeting';
|
||||
import {
|
||||
@ -74,8 +72,6 @@ interface ExternalVideoPlayerProps {
|
||||
currentTime: number;
|
||||
key: string;
|
||||
setKey: (key: string) => void;
|
||||
shouldShowSharedNotes(): boolean;
|
||||
pinSharedNotes(pinned: boolean): void;
|
||||
}
|
||||
|
||||
// @ts-ignore - PeerTubePlayer is not typed
|
||||
@ -97,8 +93,6 @@ const ExternalVideoPlayer: React.FC<ExternalVideoPlayerProps> = ({
|
||||
isEchoTest,
|
||||
key,
|
||||
setKey,
|
||||
shouldShowSharedNotes,
|
||||
pinSharedNotes,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
@ -225,7 +219,7 @@ const ExternalVideoPlayer: React.FC<ExternalVideoPlayerProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
const unsynchedPlayer = reactPlayerState !== playing;
|
||||
if (unsynchedPlayer) {
|
||||
if (unsynchedPlayer && !!videoUrl) {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
setShowUnsynchedMsg(true);
|
||||
}, AUTO_PLAY_BLOCK_DETECTION_TIMEOUT_SECONDS * 1000);
|
||||
@ -257,16 +251,6 @@ const ExternalVideoPlayer: React.FC<ExternalVideoPlayerProps> = ({
|
||||
}
|
||||
}, [playerRef.current]);
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldShowSharedNotes()) {
|
||||
pinSharedNotes(false);
|
||||
return () => {
|
||||
pinSharedNotes(true);
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}, []);
|
||||
|
||||
// --- Plugin related code ---;
|
||||
const internalPlayer = playerRef.current?.getInternalPlayer ? playerRef.current?.getInternalPlayer() : null;
|
||||
if (internalPlayer && internalPlayer?.isMuted
|
||||
@ -539,8 +523,6 @@ const ExternalVideoPlayerContainer: React.FC = () => {
|
||||
currentTime={isPresenter ? playerCurrentTime : currentTime}
|
||||
key={key}
|
||||
setKey={setKey}
|
||||
shouldShowSharedNotes={MediaService.shouldShowSharedNotes}
|
||||
pinSharedNotes={NotesService.pinSharedNotes}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -1,11 +1,16 @@
|
||||
import React, { useEffect, useReducer, useRef } from 'react';
|
||||
import { createContext, useContextSelector } from 'use-context-selector';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSubscription } from '@apollo/client';
|
||||
import { equals } from 'ramda';
|
||||
import { ACTIONS, PRESENTATION_AREA } from '/imports/ui/components/layout/enums';
|
||||
import { PINNED_PAD_SUBSCRIPTION } from '/imports/ui/components/notes/notes-graphql/queries';
|
||||
import {
|
||||
ACTIONS, PRESENTATION_AREA, PANELS, LAYOUT_TYPE,
|
||||
} from '/imports/ui/components/layout/enums';
|
||||
import DEFAULT_VALUES from '/imports/ui/components/layout/defaultValues';
|
||||
import { INITIAL_INPUT_STATE, INITIAL_OUTPUT_STATE } from './initState';
|
||||
import useUpdatePresentationAreaContentForPlugin from '/imports/ui/components/plugins-engine/ui-data-hooks/layout/presentation-area/utils';
|
||||
import { isPresentationEnabled } from '/imports/ui/services/features';
|
||||
|
||||
// variable to debug in console log
|
||||
const debug = false;
|
||||
@ -37,6 +42,9 @@ const initPresentationAreaContentActions = [{
|
||||
},
|
||||
}];
|
||||
|
||||
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
|
||||
const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id;
|
||||
|
||||
const initState = {
|
||||
presentationAreaContentActions: initPresentationAreaContentActions,
|
||||
deviceType: null,
|
||||
@ -55,7 +63,6 @@ const initState = {
|
||||
const reducer = (state, action) => {
|
||||
debugActions(action.type, action.value);
|
||||
switch (action.type) {
|
||||
|
||||
case ACTIONS.SET_FOCUSED_CAMERA_ID: {
|
||||
const { cameraDock } = state.input;
|
||||
const { focusedId } = cameraDock;
|
||||
@ -1331,6 +1338,8 @@ const updatePresentationAreaContent = (
|
||||
previousPresentationAreaContentActions,
|
||||
layoutContextDispatch,
|
||||
) => {
|
||||
const { layoutType } = layoutContextState;
|
||||
const { sidebarContent } = layoutContextState.input;
|
||||
const {
|
||||
presentationAreaContentActions: currentPresentationAreaContentActions,
|
||||
} = layoutContextState;
|
||||
@ -1355,6 +1364,32 @@ const updatePresentationAreaContent = (
|
||||
break;
|
||||
}
|
||||
case PRESENTATION_AREA.PINNED_NOTES: {
|
||||
if (
|
||||
(sidebarContent.isOpen || !isPresentationEnabled())
|
||||
&& (sidebarContent.sidebarContentPanel === PANELS.SHARED_NOTES
|
||||
|| !isPresentationEnabled())
|
||||
) {
|
||||
if (layoutType === LAYOUT_TYPE.VIDEO_FOCUS) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: PANELS.CHAT,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_ID_CHAT_OPEN,
|
||||
value: PUBLIC_CHAT_ID,
|
||||
});
|
||||
} else {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||
value: false,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: PANELS.NONE,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_HAS_GENERIC_COMPONENT,
|
||||
value: undefined,
|
||||
@ -1370,6 +1405,10 @@ const updatePresentationAreaContent = (
|
||||
type: ACTIONS.SET_HAS_GENERIC_COMPONENT,
|
||||
value: undefined,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_NOTES_IS_PINNED,
|
||||
value: !lastPresentationContentInPile.value.open,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_HAS_EXTERNAL_VIDEO,
|
||||
value: lastPresentationContentInPile.value.open,
|
||||
@ -1381,6 +1420,10 @@ const updatePresentationAreaContent = (
|
||||
type: ACTIONS.SET_HAS_GENERIC_COMPONENT,
|
||||
value: undefined,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_NOTES_IS_PINNED,
|
||||
value: !lastPresentationContentInPile.value.open,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_HAS_SCREEN_SHARE,
|
||||
value: lastPresentationContentInPile.value.open,
|
||||
@ -1400,6 +1443,10 @@ const updatePresentationAreaContent = (
|
||||
type: ACTIONS.SET_HAS_GENERIC_COMPONENT,
|
||||
value: undefined,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.PINNED_NOTES,
|
||||
value: !lastPresentationContentInPile.value.open,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -1422,6 +1469,8 @@ const LayoutContextProvider = (props) => {
|
||||
},
|
||||
}],
|
||||
);
|
||||
const { data: pinnedPadData } = useSubscription(PINNED_PAD_SUBSCRIPTION);
|
||||
|
||||
const [layoutContextState, layoutContextDispatch] = useReducer(reducer, initState);
|
||||
const { children } = props;
|
||||
useEffect(() => {
|
||||
@ -1431,6 +1480,27 @@ const LayoutContextProvider = (props) => {
|
||||
layoutContextDispatch,
|
||||
);
|
||||
}, [layoutContextState]);
|
||||
useEffect(() => {
|
||||
const isSharedNotesPinned = !!pinnedPadData
|
||||
&& pinnedPadData.sharedNotes[0]?.pinned;
|
||||
if (isSharedNotesPinned) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_PILE_CONTENT_FOR_PRESENTATION_AREA,
|
||||
value: {
|
||||
content: PRESENTATION_AREA.PINNED_NOTES,
|
||||
open: true,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_PILE_CONTENT_FOR_PRESENTATION_AREA,
|
||||
value: {
|
||||
content: PRESENTATION_AREA.PINNED_NOTES,
|
||||
open: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [pinnedPadData]);
|
||||
useUpdatePresentationAreaContentForPlugin(layoutContextState);
|
||||
return (
|
||||
<LayoutContextSelector.Provider value={
|
||||
@ -1446,18 +1516,16 @@ const LayoutContextProvider = (props) => {
|
||||
};
|
||||
LayoutContextProvider.propTypes = providerPropTypes;
|
||||
|
||||
const layoutSelect = (selector) => {
|
||||
return useContextSelector(LayoutContextSelector, layout => selector(layout[0]));
|
||||
};
|
||||
const layoutSelectInput = (selector) => {
|
||||
return useContextSelector(LayoutContextSelector, layout => selector(layout[0].input));
|
||||
};
|
||||
const layoutSelectOutput = (selector) => {
|
||||
return useContextSelector(LayoutContextSelector, layout => selector(layout[0].output));
|
||||
};
|
||||
const layoutDispatch = () => {
|
||||
return useContextSelector(LayoutContextSelector, layout => layout[1]);
|
||||
};
|
||||
const layoutSelect = (
|
||||
selector,
|
||||
) => useContextSelector(LayoutContextSelector, (layout) => selector(layout[0]));
|
||||
const layoutSelectInput = (
|
||||
selector,
|
||||
) => useContextSelector(LayoutContextSelector, (layout) => selector(layout[0].input));
|
||||
const layoutSelectOutput = (
|
||||
selector,
|
||||
) => useContextSelector(LayoutContextSelector, (layout) => selector(layout[0].output));
|
||||
const layoutDispatch = () => useContextSelector(LayoutContextSelector, (layout) => layout[1]);
|
||||
|
||||
export {
|
||||
LayoutContextProvider,
|
||||
@ -1465,4 +1533,4 @@ export {
|
||||
layoutSelectInput,
|
||||
layoutSelectOutput,
|
||||
layoutDispatch,
|
||||
}
|
||||
};
|
||||
|
@ -7,16 +7,11 @@ import PadContainer from '/imports/ui/components/pads/container';
|
||||
import Styled from './styles';
|
||||
import {
|
||||
PANELS, ACTIONS,
|
||||
LAYOUT_TYPE,
|
||||
PRESENTATION_AREA,
|
||||
} from '../layout/enums';
|
||||
import browserInfo from '/imports/utils/browserInfo';
|
||||
import Header from '/imports/ui/components/common/control-header/component';
|
||||
import NotesDropdown from '/imports/ui/components/notes/notes-dropdown/container';
|
||||
import { isPresentationEnabled } from '../../services/features';
|
||||
|
||||
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
|
||||
const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id;
|
||||
const DELAY_UNMOUNT_SHARED_NOTES = window.meetingClientSettings.public.app.delayForUnmountOfSharedNote;
|
||||
const intlMessages = defineMessages({
|
||||
hide: {
|
||||
@ -103,60 +98,6 @@ const Notes = ({
|
||||
}
|
||||
return () => clearTimeout(timoutRef);
|
||||
}, [isToSharedNotesBeShow, sidebarContent.sidebarContentPanel]);
|
||||
useEffect(() => {
|
||||
if (
|
||||
isOnMediaArea
|
||||
&& (sidebarContent.isOpen || !isPresentationEnabled())
|
||||
&& (sidebarContent.sidebarContentPanel === PANELS.SHARED_NOTES || !isPresentationEnabled())
|
||||
) {
|
||||
if (layoutType === LAYOUT_TYPE.VIDEO_FOCUS) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: PANELS.CHAT,
|
||||
});
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_ID_CHAT_OPEN,
|
||||
value: PUBLIC_CHAT_ID,
|
||||
});
|
||||
} else {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||
value: false,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: PANELS.NONE,
|
||||
});
|
||||
}
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_PILE_CONTENT_FOR_PRESENTATION_AREA,
|
||||
value: {
|
||||
content: PRESENTATION_AREA.PINNED_NOTES,
|
||||
open: true,
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_PILE_CONTENT_FOR_PRESENTATION_AREA,
|
||||
value: {
|
||||
content: PRESENTATION_AREA.PINNED_NOTES,
|
||||
open: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
} if (shouldShowSharedNotesOnPresentationArea) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_PILE_CONTENT_FOR_PRESENTATION_AREA,
|
||||
value: {
|
||||
content: PRESENTATION_AREA.PINNED_NOTES,
|
||||
open: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const renderHeaderOnMedia = () => {
|
||||
return amIPresenter ? (
|
||||
|
@ -1,15 +1,15 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useMutation, useSubscription } from '@apollo/client';
|
||||
import { Session } from 'meteor/session';
|
||||
import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
|
||||
import NotesService from '/imports/ui/components/notes/notes-graphql/service';
|
||||
import PadContainer from '/imports/ui/components/pads/container';
|
||||
import browserInfo from '/imports/utils/browserInfo';
|
||||
import Header from '/imports/ui/components/common/control-header/component';
|
||||
import NotesDropdown from './notes-dropdown/component';
|
||||
import { PANELS, ACTIONS, LAYOUT_TYPE } from '/imports/ui/components/layout/enums';
|
||||
import { isPresentationEnabled } from '/imports/ui/services/features';
|
||||
import {
|
||||
PANELS, ACTIONS,
|
||||
} from '/imports/ui/components/layout/enums';
|
||||
import { layoutSelectInput, layoutDispatch, layoutSelectOutput } from '/imports/ui/components/layout/context';
|
||||
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
|
||||
import useHasPermission from './hooks/useHasPermission';
|
||||
@ -22,9 +22,7 @@ import {
|
||||
isScreenBroadcasting,
|
||||
} from '/imports/ui/components/screenshare/service';
|
||||
|
||||
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
|
||||
const NOTES_CONFIG = window.meetingClientSettings.public.notes;
|
||||
const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id;
|
||||
const DELAY_UNMOUNT_SHARED_NOTES = window.meetingClientSettings.public.app.delayForUnmountOfSharedNote;
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
@ -44,7 +42,6 @@ const intlMessages = defineMessages({
|
||||
|
||||
interface NotesContainerGraphqlProps {
|
||||
area: 'media' | undefined;
|
||||
layoutType: string;
|
||||
isToSharedNotesBeShow: boolean;
|
||||
}
|
||||
|
||||
@ -73,7 +70,6 @@ const NotesGraphql: React.FC<NotesGraphqlProps> = (props) => {
|
||||
layoutContextDispatch,
|
||||
isResizing,
|
||||
area,
|
||||
layoutType,
|
||||
sidebarContent,
|
||||
sharedNotesOutput,
|
||||
amIPresenter,
|
||||
@ -113,64 +109,6 @@ const NotesGraphql: React.FC<NotesGraphqlProps> = (props) => {
|
||||
}
|
||||
return () => clearTimeout(timoutRef);
|
||||
}, [isToSharedNotesBeShow, sidebarContent.sidebarContentPanel]);
|
||||
// eslint-disable-next-line consistent-return
|
||||
useEffect(() => {
|
||||
if (
|
||||
isOnMediaArea
|
||||
&& (sidebarContent.isOpen || !isPresentationEnabled())
|
||||
&& (sidebarContent.sidebarContentPanel === PANELS.SHARED_NOTES || !isPresentationEnabled())
|
||||
) {
|
||||
if (layoutType === LAYOUT_TYPE.VIDEO_FOCUS) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: PANELS.CHAT,
|
||||
});
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_ID_CHAT_OPEN,
|
||||
value: PUBLIC_CHAT_ID,
|
||||
});
|
||||
} else {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||
value: false,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: PANELS.NONE,
|
||||
});
|
||||
}
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_NOTES_IS_PINNED,
|
||||
value: true,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_PRESENTATION_IS_OPEN,
|
||||
value: true,
|
||||
});
|
||||
|
||||
return () => {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_NOTES_IS_PINNED,
|
||||
value: false,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_PRESENTATION_IS_OPEN,
|
||||
value: Session.get('presentationLastState'),
|
||||
});
|
||||
};
|
||||
} if (shouldShowSharedNotesOnPresentationArea) {
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_NOTES_IS_PINNED,
|
||||
value: true,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_PRESENTATION_IS_OPEN,
|
||||
value: true,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const renderHeaderOnMedia = () => {
|
||||
return amIPresenter ? (
|
||||
@ -228,7 +166,7 @@ const NotesGraphql: React.FC<NotesGraphqlProps> = (props) => {
|
||||
};
|
||||
|
||||
const NotesContainerGraphql: React.FC<NotesContainerGraphqlProps> = (props) => {
|
||||
const { area, layoutType, isToSharedNotesBeShow } = props;
|
||||
const { area, isToSharedNotesBeShow } = props;
|
||||
|
||||
const hasPermission = useHasPermission();
|
||||
const { data: pinnedPadData } = useSubscription<PinnedPadSubscriptionResponse>(PINNED_PAD_SUBSCRIPTION);
|
||||
@ -272,7 +210,6 @@ const NotesContainerGraphql: React.FC<NotesContainerGraphqlProps> = (props) => {
|
||||
amIPresenter={amIPresenter}
|
||||
shouldShowSharedNotesOnPresentationArea={shouldShowSharedNotesOnPresentationArea}
|
||||
isRTL={isRTL}
|
||||
layoutType={layoutType}
|
||||
isToSharedNotesBeShow={isToSharedNotesBeShow}
|
||||
handlePinSharedNotes={handlePinSharedNotes}
|
||||
/>
|
||||
|
@ -23,9 +23,6 @@ const SmartMediaShareContainer = (props) => {
|
||||
externalVideoUrl = Panopto.getSocialUrl(url);
|
||||
}
|
||||
|
||||
// Close Shared Notes if open.
|
||||
NotesService.pinSharedNotes(false);
|
||||
|
||||
startExternalVideo({ variables: { externalVideoUrl } });
|
||||
};
|
||||
|
||||
|
@ -142,9 +142,6 @@ class ScreenshareComponent extends React.Component {
|
||||
intl,
|
||||
fullscreenContext,
|
||||
layoutContextDispatch,
|
||||
toggleSwapLayout,
|
||||
pinSharedNotes,
|
||||
stopExternalVideoShare,
|
||||
} = this.props;
|
||||
screenshareHasEnded();
|
||||
window.removeEventListener('screensharePlayFailed', this.handlePlayElementFailed);
|
||||
@ -179,8 +176,6 @@ class ScreenshareComponent extends React.Component {
|
||||
type: ACTIONS.SET_PRESENTATION_IS_OPEN,
|
||||
value: Session.get('presentationLastState'),
|
||||
});
|
||||
|
||||
pinSharedNotes(Session.get('pinnedNotesLastState'), stopExternalVideoShare);
|
||||
}
|
||||
|
||||
clearMediaFlowingMonitor() {
|
||||
|
@ -16,7 +16,6 @@ import getFromUserSettings from '/imports/ui/services/users-settings';
|
||||
import AudioService from '/imports/ui/components/audio/service';
|
||||
import MediaService from '/imports/ui/components/media/service';
|
||||
import { defineMessages } from 'react-intl';
|
||||
import NotesService from '/imports/ui/components/notes/service';
|
||||
import { EXTERNAL_VIDEO_STOP } from '../external-video-player/mutations';
|
||||
|
||||
const screenshareIntlMessages = defineMessages({
|
||||
@ -152,7 +151,5 @@ export default withTracker(() => {
|
||||
hidePresentationOnJoin: getFromUserSettings('bbb_hide_presentation_on_join', LAYOUT_CONFIG.hidePresentationOnJoin),
|
||||
enableVolumeControl: shouldEnableVolumeControl(),
|
||||
outputDeviceId: AudioService.outputDeviceId(),
|
||||
isSharedNotesPinned: MediaService.shouldShowSharedNotes(),
|
||||
pinSharedNotes: NotesService.pinSharedNotes,
|
||||
};
|
||||
})(ScreenshareContainer);
|
||||
|
@ -5,11 +5,9 @@ import Settings from '/imports/ui/services/settings';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import AudioService from '/imports/ui/components/audio/service';
|
||||
import { Meteor } from "meteor/meteor";
|
||||
import MediaStreamUtils from '/imports/utils/media-stream-utils';
|
||||
import ConnectionStatusService from '/imports/ui/components/connection-status/service';
|
||||
import browserInfo from '/imports/utils/browserInfo';
|
||||
import NotesService from '/imports/ui/components/notes/service';
|
||||
|
||||
const VOLUME_CONTROL_ENABLED = window.meetingClientSettings.public.kurento.screenshare.enableVolumeControl;
|
||||
const SCREENSHARE_MEDIA_ELEMENT_NAME = 'screenshareVideo';
|
||||
@ -309,8 +307,6 @@ const shareScreen = async (stopWatching, isPresenter, onFail, options = {}) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Close Shared Notes if open.
|
||||
NotesService.pinSharedNotes(false);
|
||||
// stop external video share if running
|
||||
stopWatching();
|
||||
|
||||
|
@ -24,7 +24,7 @@ const SUBSCRIPTIONS = [
|
||||
'video-streams',
|
||||
'voice-call-states',
|
||||
'breakouts',
|
||||
'breakouts-history',
|
||||
// 'breakouts-history',
|
||||
'pads',
|
||||
'pads-sessions',
|
||||
'pads-updates',
|
||||
@ -139,7 +139,7 @@ export default withTracker(() => {
|
||||
SubscriptionRegistry.getSubscription('meetings'),
|
||||
SubscriptionRegistry.getSubscription('users'),
|
||||
SubscriptionRegistry.getSubscription('breakouts'),
|
||||
SubscriptionRegistry.getSubscription('breakouts-history'),
|
||||
// SubscriptionRegistry.getSubscription('breakouts-history'),
|
||||
SubscriptionRegistry.getSubscription('guestUser'),
|
||||
].forEach((item) => {
|
||||
if (item) item.stop();
|
||||
|
@ -19,7 +19,8 @@ import Settings from "/imports/ui/services/settings";
|
||||
import KEY_CODES from "/imports/utils/keyCodes";
|
||||
import Styled from "./styles";
|
||||
import {
|
||||
mapLanguage
|
||||
mapLanguage,
|
||||
isValidShapeType,
|
||||
} from "./utils";
|
||||
import { useMouseEvents, useCursor } from "./hooks";
|
||||
import { notifyShapeNumberExceeded } from "./service";
|
||||
@ -95,6 +96,7 @@ const Whiteboard = React.memo(function Whiteboard(props) {
|
||||
skipToSlide,
|
||||
intl,
|
||||
maxNumberOfAnnotations,
|
||||
notifyNotAllowedChange,
|
||||
} = props;
|
||||
|
||||
clearTldrawCache();
|
||||
@ -833,10 +835,15 @@ const Whiteboard = React.memo(function Whiteboard(props) {
|
||||
|
||||
const addedCount = Object.keys(added).length;
|
||||
const shapeNumberExceeded = Object.keys(prevShapesRef.current).length + addedCount > maxNumberOfAnnotations;
|
||||
const invalidShapeType = Object.keys(added).find((id) => !isValidShapeType(added[id]));
|
||||
|
||||
if (shapeNumberExceeded) {
|
||||
if (shapeNumberExceeded || invalidShapeType) {
|
||||
// notify and undo last command without persisting to not generate the onUndo/onRedo callback
|
||||
notifyShapeNumberExceeded(intl, maxNumberOfAnnotations);
|
||||
if (shapeNumberExceeded) {
|
||||
notifyShapeNumberExceeded(intl, maxNumberOfAnnotations);
|
||||
} else {
|
||||
notifyNotAllowedChange(intl);
|
||||
}
|
||||
editor.history.undo({ persist: false });
|
||||
} else {
|
||||
Object.values(added).forEach((record) => {
|
||||
|
@ -79,7 +79,7 @@ const filterInvalidShapes = (shapes, curPageId, tldrawAPI) => {
|
||||
};
|
||||
|
||||
const isValidShapeType = (shape) => {
|
||||
const invalidTypes = ['image', 'video'];
|
||||
const invalidTypes = ['image', 'embed'];
|
||||
return !invalidTypes.includes(shape?.type);
|
||||
};
|
||||
|
||||
@ -106,5 +106,5 @@ const Utils = {
|
||||
|
||||
export default Utils;
|
||||
export {
|
||||
usePrevious, findRemoved, filterInvalidShapes, mapLanguage,
|
||||
usePrevious, findRemoved, filterInvalidShapes, mapLanguage, isValidShapeType,
|
||||
};
|
||||
|
@ -6,7 +6,6 @@ import '/imports/api/users/server';
|
||||
import '/imports/api/captions/server';
|
||||
import '/imports/api/presentation-upload-token/server';
|
||||
import '/imports/api/breakouts/server';
|
||||
import '/imports/api/breakouts-history/server';
|
||||
import '/imports/api/screenshare/server';
|
||||
import '/imports/api/users-settings/server';
|
||||
import '/imports/api/voice-users/server';
|
||||
|
Loading…
Reference in New Issue
Block a user