From 6f35d4d366a4223b294234083154dfc02147c7e3 Mon Sep 17 00:00:00 2001 From: Guilherme Leme Date: Wed, 31 Jul 2024 14:47:54 -0300 Subject: [PATCH] [video-control-plugin-sdk] created extensible-are screenshare helper and refactored buttons rendering for screenshare --- .../components/screenshare-helper/manager.tsx | 53 ++++++ .../extensible-areas/manager.tsx | 2 + .../plugins-engine/extensible-areas/types.ts | 1 + .../ui/components/screenshare/component.jsx | 173 +++++++++++++++--- .../ui/components/screenshare/container.jsx | 11 +- .../screenshare/plugin-button/component.tsx | 40 ++++ .../screenshare/plugin-button/container.tsx | 34 ++++ .../screenshare/plugin-button/styles.js | 64 +++++++ .../ui/components/screenshare/styles.js | 30 +++ .../screenshare/switch-button/styles.js | 2 +- 10 files changed, 378 insertions(+), 32 deletions(-) create mode 100644 bigbluebutton-html5/imports/ui/components/plugins-engine/extensible-areas/components/screenshare-helper/manager.tsx create mode 100644 bigbluebutton-html5/imports/ui/components/screenshare/plugin-button/component.tsx create mode 100644 bigbluebutton-html5/imports/ui/components/screenshare/plugin-button/container.tsx create mode 100644 bigbluebutton-html5/imports/ui/components/screenshare/plugin-button/styles.js diff --git a/bigbluebutton-html5/imports/ui/components/plugins-engine/extensible-areas/components/screenshare-helper/manager.tsx b/bigbluebutton-html5/imports/ui/components/plugins-engine/extensible-areas/components/screenshare-helper/manager.tsx new file mode 100644 index 0000000000..c67919c7b3 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/plugins-engine/extensible-areas/components/screenshare-helper/manager.tsx @@ -0,0 +1,53 @@ +import { useEffect, useState, useContext } from 'react'; +import * as PluginSdk from 'bigbluebutton-html-plugin-sdk'; + +import { + ExtensibleAreaComponentManagerProps, ExtensibleArea, + ExtensibleAreaComponentManager, +} from '../../types'; +import { PluginsContext } from '../../../../components-data/plugin-context/context'; + +const ScreenshareHelperPluginStateContainer = (( + props: ExtensibleAreaComponentManagerProps, +) => { + const { + uuid, + generateItemWithId, + extensibleAreaMap, + pluginApi, + } = props; + const [ + screenshareHelperItems, + setScreenshareHelperItems, + ] = useState([]); + + const { + pluginsExtensibleAreasAggregatedState, + setPluginsExtensibleAreasAggregatedState, + } = useContext(PluginsContext); + + useEffect(() => { + extensibleAreaMap[uuid].screenshareHelperItems = screenshareHelperItems; + + const aggregatedScreenshareHelperItems = ([] as PluginSdk.ScreenshareHelperInterface[]).concat( + ...Object.values(extensibleAreaMap) + .map((extensibleArea: ExtensibleArea) => extensibleArea.screenshareHelperItems), + ); + + setPluginsExtensibleAreasAggregatedState( + { + ...pluginsExtensibleAreasAggregatedState, + screenshareHelperItems: aggregatedScreenshareHelperItems, + }, + ); + }, [screenshareHelperItems]); + + pluginApi.setScreenshareHelperItems = (items: PluginSdk.ScreenshareHelperInterface[]) => { + const itemsWithId = items.map(generateItemWithId) as PluginSdk.ScreenshareHelperInterface[]; + setScreenshareHelperItems(itemsWithId); + return itemsWithId.map((i) => i.id); + }; + return null; +}) as ExtensibleAreaComponentManager; + +export default ScreenshareHelperPluginStateContainer; diff --git a/bigbluebutton-html5/imports/ui/components/plugins-engine/extensible-areas/manager.tsx b/bigbluebutton-html5/imports/ui/components/plugins-engine/extensible-areas/manager.tsx index c544c3c820..ae2cba6171 100644 --- a/bigbluebutton-html5/imports/ui/components/plugins-engine/extensible-areas/manager.tsx +++ b/bigbluebutton-html5/imports/ui/components/plugins-engine/extensible-areas/manager.tsx @@ -19,6 +19,7 @@ import { } from './types'; import FloatingWindowPluginStateContainer from './components/floating-window/manager'; import GenericContentPluginStateContainer from './components/generic-content/manager'; +import ScreenshareHelperPluginStateContainer from './components/screenshare-helper/manager'; const extensibleAreaMap: ExtensibleAreaMap = {}; @@ -36,6 +37,7 @@ const extensibleAreaComponentManagers: ExtensibleAreaComponentManager[] = [ UserListItemAdditionalInformationPluginStateContainer, FloatingWindowPluginStateContainer, GenericContentPluginStateContainer, + ScreenshareHelperPluginStateContainer, ]; function generateItemWithId( diff --git a/bigbluebutton-html5/imports/ui/components/plugins-engine/extensible-areas/types.ts b/bigbluebutton-html5/imports/ui/components/plugins-engine/extensible-areas/types.ts index 47a2803e70..3e0bd383f7 100644 --- a/bigbluebutton-html5/imports/ui/components/plugins-engine/extensible-areas/types.ts +++ b/bigbluebutton-html5/imports/ui/components/plugins-engine/extensible-areas/types.ts @@ -15,6 +15,7 @@ export interface ExtensibleArea { actionsBarItems: PluginSdk.ActionsBarInterface[]; presentationDropdownItems: PluginSdk.PresentationDropdownInterface[]; navBarItems: PluginSdk.NavBarInterface[]; + screenshareHelperItems: PluginSdk.ScreenshareHelperInterface[]; optionsDropdownItems: PluginSdk.OptionsDropdownInterface[]; cameraSettingsDropdownItems: PluginSdk.CameraSettingsDropdownInterface[]; userCameraDropdownItems: PluginSdk.UserCameraDropdownInterface[]; diff --git a/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx b/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx index fee951842f..0c7cacbb77 100755 --- a/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/screenshare/component.jsx @@ -5,7 +5,9 @@ import { debounce } from '/imports/utils/debounce'; import FullscreenButtonContainer from '/imports/ui/components/common/fullscreen-button/container'; import SwitchButtonContainer from './switch-button/container'; import Styled from './styles'; +import * as PluginSdk from 'bigbluebutton-html-plugin-sdk'; import VolumeSlider from '../external-video-player/volume-slider/component'; +import PluginButtonContainer from './plugin-button/container'; import AutoplayOverlay from '../media/autoplay-overlay/component'; import logger from '/imports/startup/client/logger'; import playAndRetry from '/imports/utils/mediaElementPlayRetry'; @@ -38,6 +40,32 @@ const MOBILE_HOVER_TIMEOUT = 5000; const MEDIA_FLOW_PROBE_INTERVAL = 500; const SCREEN_SIZE_DISPATCH_INTERVAL = 500; +const renderPluginItems = (pluginItems, bottom, right) => { + if (pluginItems !== undefined) { + return ( + <> + { + pluginItems.map((pluginItem) => { + const returnComponent = ( + + ); + return returnComponent; + }) + } + + ); + } + return (<>); +}; + class ScreenshareComponent extends React.Component { static renderScreenshareContainerInside(mainText) { return ( @@ -70,6 +98,8 @@ class ScreenshareComponent extends React.Component { this.dispatchScreenShareSize = this.dispatchScreenShareSize.bind(this); this.handleOnMuted = this.handleOnMuted.bind(this); this.dispatchScreenShareSize = this.dispatchScreenShareSize.bind(this); + this.renderScreenshareButtons = this.renderScreenshareButtons.bind(this); + this.splitPluginItems = this.splitPluginItems.bind(this); this.debouncedDispatchScreenShareSize = debounce( this.dispatchScreenShareSize, SCREEN_SIZE_DISPATCH_INTERVAL, @@ -336,14 +366,16 @@ class ScreenshareComponent extends React.Component { if (!ALLOW_FULLSCREEN) return null; return ( - + + + ); } @@ -410,7 +442,8 @@ class ScreenshareComponent extends React.Component { return [( + key="hover-toolbar-screenshare" + > - ), - (deviceInfo.isMobile) && this.renderMobileVolumeControlOverlay(), + ), + (deviceInfo.isMobile) && this.renderMobileVolumeControlOverlay(), ]; } @@ -446,17 +479,41 @@ class ScreenshareComponent extends React.Component { ); } + splitPluginItems() { + const { pluginScreenshareHelperItems } = this.props; + + return pluginScreenshareHelperItems.reduce((result, item) => { + switch (item.position) { + case PluginSdk.ScreenshareHelperItemPosition.TOP_RIGHT: + result.topRightPluginItems.push(item); + break; + case PluginSdk.ScreenshareHelperItemPosition.TOP_LEFT: + result.topLeftPluginItems.push(item); + break; + case PluginSdk.ScreenshareHelperItemPosition.BOTTOM_RIGHT: + result.bottomRightPluginItems.push(item); + break; + case PluginSdk.ScreenshareHelperItemPosition.BOTTOM_LEFT: + result.bottomLeftPluginItems.push(item); + break; + default: + break; + } + return result; + }, { + topRightPluginItems: [], + topLeftPluginItems: [], + bottomRightPluginItems: [], + bottomLeftPluginItems: [], + }); + } + renderScreensharePresenter() { const { switched } = this.state; const { isGloballyBroadcasting, intl } = this.props; return ( - { this.screenshareContainer = ref; }} - > - {isGloballyBroadcasting && this.renderSwitchButton()} + <> {this.renderVideo(switched)} { @@ -473,7 +530,7 @@ class ScreenshareComponent extends React.Component { intl.formatMessage(this.locales.presenterLoadingLabel), ) } - + ); } @@ -482,15 +539,7 @@ class ScreenshareComponent extends React.Component { const { loaded } = this.state; return ( - { - this.screenshareContainer = ref; - }} - id="screenshareContainer" - > - {loaded && this.renderFullscreenButton()} + <> {this.renderVideo(true)} {loaded && enableVolumeControl && this.renderVolumeSlider() } @@ -503,12 +552,61 @@ class ScreenshareComponent extends React.Component { : null } - + + ); + } + + renderScreenshareButtons() { + const { isPresenter, isGloballyBroadcasting } = this.props; + const { loaded } = this.state; + const { + topRightPluginItems, + topLeftPluginItems, + bottomRightPluginItems, + bottomLeftPluginItems, + } = this.splitPluginItems(); + return ( + <> + + {renderPluginItems(topLeftPluginItems, false, false)} + + + {isPresenter + // Presenter button: + ? isGloballyBroadcasting && this.renderSwitchButton() + // Non-presenter button: + : loaded && this.renderFullscreenButton()} + {renderPluginItems(topRightPluginItems, false, true)} + + + {renderPluginItems(bottomLeftPluginItems, true, false)} + + + {renderPluginItems(bottomRightPluginItems, true, true)} + + ); } render() { - const { loaded, autoplayBlocked, mediaFlowing} = this.state; + const { + loaded, + autoplayBlocked, + mediaFlowing, + switched, + } = this.state; const { isPresenter, isGloballyBroadcasting, @@ -565,7 +663,19 @@ class ScreenshareComponent extends React.Component { )} {autoplayBlocked ? this.renderAutoplayOverlay() : null} - {isPresenter ? this.renderScreensharePresenter() : this.renderScreenshareDefault()} + { + this.screenshareContainer = ref; + }} + id="screenshareContainer" + > + {this.renderScreenshareButtons()} + {isPresenter + ? this.renderScreensharePresenter() + : this.renderScreenshareDefault()} + ); } @@ -577,6 +687,9 @@ ScreenshareComponent.propTypes = { intl: PropTypes.shape({ formatMessage: PropTypes.func.isRequired, }).isRequired, + pluginScreenshareHelperItems: PropTypes.arrayOf(PropTypes.objectOf({ + position: PropTypes.string, + })).isRequired, isPresenter: PropTypes.bool.isRequired, layoutContextDispatch: PropTypes.func.isRequired, enableVolumeControl: PropTypes.bool.isRequired, diff --git a/bigbluebutton-html5/imports/ui/components/screenshare/container.jsx b/bigbluebutton-html5/imports/ui/components/screenshare/container.jsx index 367ecf3dc6..746bc080b3 100755 --- a/bigbluebutton-html5/imports/ui/components/screenshare/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/screenshare/container.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { useMutation, useReactiveVar } from '@apollo/client'; import { defineMessages } from 'react-intl'; import { @@ -11,6 +11,7 @@ import { useScreenshareHasAudio, useBroadcastContentType, } from './service'; +import { PluginsContext } from '/imports/ui/components/components-data/plugin-context/context'; import ScreenshareComponent from './component'; import { layoutSelect, layoutSelectOutput, layoutDispatch } from '../layout/context'; import getFromUserSettings from '/imports/ui/services/users-settings'; @@ -93,6 +94,7 @@ const ScreenshareContainer = (props) => { const screenShare = layoutSelectOutput((i) => i.screenShare); const fullscreen = layoutSelect((i) => i.fullscreen); const layoutContextDispatch = layoutDispatch(); + const { pluginsExtensibleAreasAggregatedState } = useContext(PluginsContext); const { element } = fullscreen; const fullscreenElementId = 'Screenshare'; @@ -137,11 +139,18 @@ const ScreenshareContainer = (props) => { const isCameraAsContentBroadcasting = useIsCameraAsContentBroadcasting(); const hasAudio = useScreenshareHasAudio(); + let pluginScreenshareHelperItems = []; + if (pluginsExtensibleAreasAggregatedState.screenshareHelperItems) { + pluginScreenshareHelperItems = [ + ...pluginsExtensibleAreasAggregatedState.screenshareHelperItems, + ]; + } if (isScreenBroadcasting || isCameraAsContentBroadcasting) { return ( void; +} + +const PluginButtonComponent = ({ + dark = false, + bottom = false, + onClick = () => {}, + right, + icon, + label, +}: PluginButtonComponentProps) => ( + + + +); + +export default PluginButtonComponent; diff --git a/bigbluebutton-html5/imports/ui/components/screenshare/plugin-button/container.tsx b/bigbluebutton-html5/imports/ui/components/screenshare/plugin-button/container.tsx new file mode 100644 index 0000000000..b2347a167b --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/screenshare/plugin-button/container.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import PluginButtonComponent from './component'; + +interface PluginButtonContainerProps { + dark: boolean; + bottom: boolean; + right: boolean; + icon: string; + label: string; + onClick: () => void; +} + +const PluginButtonContainer = (props: PluginButtonContainerProps) => { + const { + dark, + bottom, + right, + icon, + label, + onClick, + } = props; + return ( + + ); +}; + +export default PluginButtonContainer; diff --git a/bigbluebutton-html5/imports/ui/components/screenshare/plugin-button/styles.js b/bigbluebutton-html5/imports/ui/components/screenshare/plugin-button/styles.js new file mode 100644 index 0000000000..0532a8b329 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/screenshare/plugin-button/styles.js @@ -0,0 +1,64 @@ +import styled from 'styled-components'; +import { + colorWhite, + colorTransparent, + colorBlack, +} from '/imports/ui/stylesheets/styled-components/palette'; +import Button from '/imports/ui/components/common/button/component'; + +const PluginButtonWrapper = styled.div` + background-color: ${colorTransparent}; + cursor: pointer; + border: 0; + z-index: 2; + margin: 2px; + [dir="rtl"] & { + right: auto; + left: 0; + ${({ fullScreenEnabled }) => fullScreenEnabled && ` + left: 1.75rem; + `} + } + [class*="presentationZoomControls"] & { + position: relative !important; + } + + ${({ dark }) => dark && ` + background-color: rgba(0,0,0,.3); + + & button i { + color: ${colorWhite}; + } + `} + + ${({ dark }) => !dark && ` + background-color: ${colorTransparent}; + + & button i { + color: ${colorBlack}; + } + `} +`; + +const PluginButton = styled(Button)` + padding: 5px; + + &, + &:active, + &:hover, + &:focus { + background-color: ${colorTransparent} !important; + border: none !important; + + i { + border: none !important; + background-color: ${colorTransparent} !important; + font-size: 1rem; + } + } +`; + +export default { + PluginButtonWrapper, + PluginButton, +}; diff --git a/bigbluebutton-html5/imports/ui/components/screenshare/styles.js b/bigbluebutton-html5/imports/ui/components/screenshare/styles.js index b5f948aa68..949d3e1ecb 100644 --- a/bigbluebutton-html5/imports/ui/components/screenshare/styles.js +++ b/bigbluebutton-html5/imports/ui/components/screenshare/styles.js @@ -90,7 +90,37 @@ const HoverToolbar = styled.div` `} `; +const ScreenshareButtonsContainterWrapper = styled.div` + position: absolute; + z-index: 2; + display: flex; + width: 50%; + + ${({ positionXAxis }) => positionXAxis === 'right' && ` + right: 0; + flex-direction: row-reverse; + `} + ${({ positionXAxis }) => positionXAxis === 'left' && ` + left: 0; + `} + + ${({ positionYAxis }) => positionYAxis === 'top' && ` + top: 0; + `} + ${({ positionYAxis }) => positionYAxis === 'bottom' && ` + bottom: 0; + `} +`; + +const FullscreenButtonWrapperForScreenshare = styled.div` + & > * { + position: relative !important; + } +`; + export default { + FullscreenButtonWrapperForScreenshare, + ScreenshareButtonsContainterWrapper, ScreenshareContainerInside, MainText, ScreenshareVideo, diff --git a/bigbluebutton-html5/imports/ui/components/screenshare/switch-button/styles.js b/bigbluebutton-html5/imports/ui/components/screenshare/switch-button/styles.js index 94f1c68fb1..42821cfb27 100644 --- a/bigbluebutton-html5/imports/ui/components/screenshare/switch-button/styles.js +++ b/bigbluebutton-html5/imports/ui/components/screenshare/switch-button/styles.js @@ -7,7 +7,7 @@ import { import Button from '/imports/ui/components/common/button/component'; const SwitchButtonWrapper = styled.div` - position: absolute; + position: relative; right: 0; left: auto; background-color: ${colorTransparent};