Merge remote-tracking branch 'upstream/v3.0.x-release' into refactor-captions

This commit is contained in:
Tainan Felipe 2024-04-22 12:32:41 -03:00
commit 47540c7711
21 changed files with 241 additions and 205 deletions

View File

@ -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)) {

View File

@ -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

View 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")
}
}
}

View 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 };
}

View File

@ -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 "user_connectionStatusMetrics"("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 *
@ -1773,7 +1803,7 @@ CREATE OR REPLACE VIEW "v_caption_activeLocales" AS
select distinct "meetingId", "locale", "ownerUserId", "captionType"
from "caption_locale";
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';
------------------------------------
----
@ -1805,7 +1835,7 @@ CREATE TABLE "notification" (
"messageDescription" varchar(100),
"messageValues" jsonb,
"role" varchar(100), --MODERATOR, PRESENTER, VIEWER
"userMeetingId" varchar(50),
"userMeetingId" varchar(100),
"userId" varchar(50),
"createdAt" timestamp with time zone DEFAULT current_timestamp,
FOREIGN KEY ("userMeetingId", "userId") REFERENCES "user"("meetingId","userId") ON DELETE CASCADE

View File

@ -559,3 +559,11 @@ input GuestUserApprovalStatus {
status: String!
}
type Mutation {
captionSetOwner(
locale: String!
ownerUserId: String!
): Boolean
}

View File

@ -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:

View File

@ -26,7 +26,7 @@ select_permissions:
- captionId
- userId
- captionType
- lang
- locale
filter:
meetingId:
_eq: X-Hasura-MeetingId

View File

@ -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"

View File

@ -584,7 +584,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()}

View File

@ -64,7 +64,6 @@ const AppContainer = (props) => {
meetingLayoutCameraPosition,
meetingLayoutFocusedCamera,
meetingLayoutVideoRate,
isSharedNotesPinned,
viewScreenshare,
...otherProps
} = props;
@ -77,6 +76,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();
@ -86,8 +86,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({
@ -182,7 +183,7 @@ const AppContainer = (props) => {
const shouldShowScreenshare = propsShouldShowScreenshare
&& (viewScreenshare || isPresenter);
const shouldShowPresentation = (!shouldShowScreenshare && !shouldShowSharedNotes
const shouldShowPresentation = (!shouldShowScreenshare && !isSharedNotesPinned
&& !shouldShowExternalVideo && !shouldShowGenericComponent
&& (presentationIsOpen || presentationRestoreOnUpdate)) && isPresentationEnabled();
@ -228,7 +229,7 @@ const AppContainer = (props) => {
speechLocale: currentUserData?.speechLocale,
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);

View File

@ -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}
/>
);
};

View File

@ -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,
}
};

View File

@ -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 ? (

View File

@ -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}
/>

View File

@ -23,9 +23,6 @@ const SmartMediaShareContainer = (props) => {
externalVideoUrl = Panopto.getSocialUrl(url);
}
// Close Shared Notes if open.
NotesService.pinSharedNotes(false);
startExternalVideo({ variables: { externalVideoUrl } });
};

View File

@ -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() {

View File

@ -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);

View File

@ -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();

View File

@ -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) => {

View File

@ -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,
};