From 68d7f1be77ab2661273f17d67e148c4794e55f85 Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Wed, 27 Aug 2025 20:57:20 +0900
Subject: [PATCH 01/87] Update component.jsx
---
.../imports/ui/components/actions-bar/component.jsx | 2 ++
1 file changed, 2 insertions(+)
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx
index 3acf15e86137..eea05f3af191 100755
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx
@@ -154,6 +154,7 @@ class ActionsBar extends PureComponent {
showScreenshareQuickSwapButton,
isReactionsButtonEnabled,
isRaiseHandEnabled,
+ isPresentationDetached,
} = this.props;
const Settings = getSettingsSingletonInstance();
@@ -250,6 +251,7 @@ class ActionsBar extends PureComponent {
hasPinnedSharedNotes={isSharedNotesPinned}
hasGenericContent={hasGenericContent}
hasCameraAsContent={hasCameraAsContent}
+ isPresentationDetached={isPresentationDetached}
/>
)
: null}
From 6a718fde8166e5e7834604ccc080d80de370ce85 Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Wed, 27 Aug 2025 20:58:56 +0900
Subject: [PATCH 02/87] Update component.jsx
---
.../components/actions-bar/presentation-options/component.jsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/presentation-options/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/presentation-options/component.jsx
index ff05f294b778..ec92e13c81ec 100755
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/presentation-options/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/presentation-options/component.jsx
@@ -46,6 +46,7 @@ const PresentationOptionsContainer = ({
hasPinnedSharedNotes,
hasGenericContent,
hasCameraAsContent,
+ isPresentationDetached,
}) => {
let buttonType = 'presentation';
if (hasExternalVideo) {
@@ -100,7 +101,7 @@ const PresentationOptionsContainer = ({
}
}}
id="restore-presentation"
- disabled={!isThereCurrentPresentation}
+ disabled={!isThereCurrentPresentation || isPresentationDetached}
data-test={!presentationIsOpen ? 'restorePresentation' : 'minimizePresentation'}
/>
);
From aabe8a33e7c9a52f3fc70c1bad068c7cfd9e483c Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Wed, 27 Aug 2025 21:05:46 +0900
Subject: [PATCH 03/87] Update component.jsx
---
.../imports/ui/components/app/component.jsx | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/bigbluebutton-html5/imports/ui/components/app/component.jsx b/bigbluebutton-html5/imports/ui/components/app/component.jsx
index acfb827be6d7..eab9e1302f44 100644
--- a/bigbluebutton-html5/imports/ui/components/app/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/app/component.jsx
@@ -285,6 +285,7 @@ class App extends Component {
const {
hideActionsBar,
presentationIsOpen,
+ isPresentationDetached,
} = this.props;
if (hideActionsBar) return null;
@@ -293,6 +294,7 @@ class App extends Component {
);
}
@@ -337,6 +339,9 @@ class App extends Component {
isNotificationEnabled,
isNonMediaLayout,
isRaiseHandEnabled,
+ popupWindow,
+ isPresentationDetached,
+ toggleDetachPresentation,
} = this.props;
const {
@@ -388,6 +393,9 @@ class App extends Component {
fitToWidth={presentationFitToWidth}
darkTheme={darkTheme}
presentationIsOpen={presentationIsOpen}
+ popupWindow={popupWindow}
+ isPresentationDetached={isPresentationDetached}
+ toggleDetachPresentation={toggleDetachPresentation}
/>
)
: null
From 380ce89ae1942cc47153f787346b64ae75185e23 Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Wed, 27 Aug 2025 21:09:59 +0900
Subject: [PATCH 04/87] Update container.jsx
---
.../imports/ui/components/app/container.jsx | 26 ++++++++++++++++---
1 file changed, 22 insertions(+), 4 deletions(-)
diff --git a/bigbluebutton-html5/imports/ui/components/app/container.jsx b/bigbluebutton-html5/imports/ui/components/app/container.jsx
index f538a3d2439a..0950abc58e97 100755
--- a/bigbluebutton-html5/imports/ui/components/app/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/app/container.jsx
@@ -1,4 +1,4 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
import { useMutation, useReactiveVar } from '@apollo/client';
import AudioCaptionsLiveContainer from '/imports/ui/components/audio/audio-graphql/audio-captions/live/component';
import getFromUserSettings from '/imports/ui/services/users-settings';
@@ -106,9 +106,24 @@ const AppContainer = (props) => {
const shouldShowScreenshare = (viewScreenshare || isPresenter)
&& (currentMeeting?.componentsFlags?.hasScreenshare
|| currentMeeting?.componentsFlags?.hasCameraAsContent) && showScreenshare;
- const shouldShowPresentation = (!shouldShowScreenshare && !isSharedNotesPinned
- && !shouldShowExternalVideo && !shouldShowGenericMainContent
- && (presentationIsOpen || presentationRestoreOnUpdate)) && isPresentationEnabled;
+
+ const [popupWindow, setPopupWindow] = useState(null);
+ const [isPresentationDetached, setIsPresentationDetached] = useState(false);
+
+ const toggleDetachPresentation = (popup) => {
+ setPopupWindow(popup);
+ setIsPresentationDetached(Boolean(popup));
+ };
+
+ const hasPresentationContent =
+ (presentationIsOpen || presentationRestoreOnUpdate) && isPresentationEnabled;
+ const noOtherMainContent =
+ !shouldShowScreenshare && !isSharedNotesPinned &&
+ !shouldShowExternalVideo && !shouldShowGenericMainContent;
+ const shouldShowPresentation = isPresentationDetached
+ ? hasPresentationContent
+ : noOtherMainContent && hasPresentationContent;
+
const currentPageInfoData = currentPageInfo?.pres_page_curr[0] ?? {};
const fitToWidth = currentPageInfoData?.fitToWidth ?? false;
const pageId = currentPageInfoData?.pageId ?? '';
@@ -161,6 +176,9 @@ const AppContainer = (props) => {
isBreakout: currentMeeting?.isBreakout ?? false,
meetingName: currentMeeting?.name ?? '',
meetingId: currentMeeting?.meetingId ?? '',
+ isPresentationDetached,
+ popupWindow,
+ toggleDetachPresentation,
}}
{...props}
/>
From 4976e471b158f6410b2360a0b036e28b47148a8a Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Wed, 27 Aug 2025 21:15:21 +0900
Subject: [PATCH 05/87] Update service.js
---
.../common/fullscreen-button/service.js | 47 +++++++++++--------
1 file changed, 27 insertions(+), 20 deletions(-)
diff --git a/bigbluebutton-html5/imports/ui/components/common/fullscreen-button/service.js b/bigbluebutton-html5/imports/ui/components/common/fullscreen-button/service.js
index 550df065a10c..964139b8a894 100644
--- a/bigbluebutton-html5/imports/ui/components/common/fullscreen-button/service.js
+++ b/bigbluebutton-html5/imports/ui/components/common/fullscreen-button/service.js
@@ -1,25 +1,25 @@
-function getFullscreenElement() {
- if (document.fullscreenElement) return document.fullscreenElement;
- if (document.webkitFullscreenElement) return document.webkitFullscreenElement;
- if (document.mozFullScreenElement) return document.mozFullScreenElement;
- if (document.msFullscreenElement) return document.msFullscreenElement;
+function getFullscreenElement(d = document) {
+ if (d.fullscreenElement) return d.fullscreenElement;
+ if (d.webkitFullscreenElement) return d.webkitFullscreenElement;
+ if (d.mozFullScreenElement) return d.mozFullScreenElement;
+ if (d.msFullscreenElement) return d.msFullscreenElement;
return null;
}
-const isFullScreen = (element) => {
- if (getFullscreenElement() && getFullscreenElement() === element) {
+const isFullScreen = (element, doc = document) => {
+ if (getFullscreenElement(doc) && getFullscreenElement(doc) === element) {
return true;
}
return false;
};
-function cancelFullScreen() {
- if (document.exitFullscreen) {
- document.exitFullscreen();
- } else if (document.mozCancelFullScreen) {
- document.mozCancelFullScreen();
- } else if (document.webkitExitFullscreen) {
- document.webkitExitFullscreen();
+function cancelFullScreen(doc = document) {
+ if (doc.exitFullscreen) {
+ doc.exitFullscreen();
+ } else if (doc.mozCancelFullScreen) {
+ doc.mozCancelFullScreen();
+ } else if (doc.webkitExitFullscreen) {
+ doc.webkitExitFullscreen();
}
}
@@ -39,13 +39,20 @@ function fullscreenRequest(element) {
element.focus();
}
-const toggleFullScreen = (ref = null) => {
- const element = ref || document.documentElement;
-
- if (isFullScreen(element)) {
- cancelFullScreen();
+const toggleFullScreen = (ref = null, isDetached = false, p) => {
+ const element = isDetached ? p.document.documentElement : (ref || document.documentElement);
+ if (isDetached) {
+ if (isFullScreen(element, p.document)) {
+ cancelFullScreen(p.document);
+ } else {
+ fullscreenRequest(element);
+ }
} else {
- fullscreenRequest(element);
+ if (isFullScreen(element)) {
+ cancelFullScreen();
+ } else {
+ fullscreenRequest(element);
+ }
}
};
From c85ff13826975f5a55d4eb9b07043097e2bfe448 Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Wed, 27 Aug 2025 21:18:24 +0900
Subject: [PATCH 06/87] Update customLayout.jsx
---
.../layout/layout-manager/customLayout.jsx | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/bigbluebutton-html5/imports/ui/components/layout/layout-manager/customLayout.jsx b/bigbluebutton-html5/imports/ui/components/layout/layout-manager/customLayout.jsx
index ebc29fc616de..f3feb5bdbc94 100644
--- a/bigbluebutton-html5/imports/ui/components/layout/layout-manager/customLayout.jsx
+++ b/bigbluebutton-html5/imports/ui/components/layout/layout-manager/customLayout.jsx
@@ -461,10 +461,13 @@ const CustomLayout = (props) => {
}
if (
- fullscreenElement === 'Presentation' ||
- fullscreenElement === 'Screenshare' ||
- fullscreenElement === 'ExternalVideo' ||
- fullscreenElement === 'GenericContent'
+ (fullscreenElement === 'Presentation' ||
+ fullscreenElement === 'Screenshare' ||
+ fullscreenElement === 'ExternalVideo' ||
+ fullscreenElement === 'GenericContent') &&
+ // this is indispensable for showing a normal-sized operatable external video
+ // when popup is fullscreen within the sub-monitor
+ document.getElementById('presentationInnerWrapper')
) {
mediaBounds.width = windowWidth();
mediaBounds.height = windowHeight();
@@ -803,4 +806,4 @@ const CustomLayout = (props) => {
return null;
};
-export default CustomLayout;
\ No newline at end of file
+export default CustomLayout;
From 119c46111ff882378c6b3396e5b530b3f98db2ff Mon Sep 17 00:00:00 2001
From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com>
Date: Wed, 27 Aug 2025 22:05:49 +0900
Subject: [PATCH 07/87] Update component.jsx
---
.../ui/components/presentation/component.jsx | 282 ++++++++++++++++--
1 file changed, 264 insertions(+), 18 deletions(-)
diff --git a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx
index 1fbad7ddadc0..6e19a662f5a3 100755
--- a/bigbluebutton-html5/imports/ui/components/presentation/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/presentation/component.jsx
@@ -1,4 +1,5 @@
import React, { PureComponent } from 'react';
+import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import WhiteboardContainer from '/imports/ui/components/whiteboard/container';
import { HUNDRED_PERCENT, MAX_PERCENT, MIN_PERCENT } from '/imports/utils/slideCalcUtils';
@@ -6,6 +7,7 @@ import { SPACE } from '/imports/utils/keyCodes';
import { defineMessages, injectIntl } from 'react-intl';
import Session from '/imports/ui/services/storage/in-memory';
import PresentationToolbarContainer from './presentation-toolbar/container';
+import PresentationMenuContainer from './presentation-menu/container';
import PresentationMenu from './presentation-menu/container';
import DownloadPresentationButton from './download-presentation-button/component';
import Styled from './styles';
@@ -21,6 +23,7 @@ import { throttle } from '/imports/utils/throttle';
import LocatedErrorBoundary from '/imports/ui/components/common/error-boundary/located-error-boundary/component';
import FallbackView from '/imports/ui/components/common/fallback-errors/fallback-view/component';
import TooltipContainer from '/imports/ui/components/common/tooltip/container';
+import { StyleSheetManager } from 'styled-components';
const intlMessages = defineMessages({
presentationLabel: {
@@ -58,9 +61,9 @@ const FULLSCREEN_CHANGE_EVENT = isSafari
? 'webkitfullscreenchange'
: 'fullscreenchange';
-const getToolbarHeight = () => {
+const getToolbarHeight = (doc = document) => {
let height = 0;
- const toolbarEl = document.getElementById('presentationToolbarWrapper');
+ const toolbarEl = doc.getElementById('presentationToolbarWrapper');
if (toolbarEl) {
const { clientHeight } = toolbarEl;
height = clientHeight;
@@ -102,6 +105,8 @@ class Presentation extends PureComponent {
this.setIsToolbarVisible = this.setIsToolbarVisible.bind(this);
this.handlePanShortcut = this.handlePanShortcut.bind(this);
this.renderPresentationMenu = this.renderPresentationMenu.bind(this);
+ this.renderPresentationContents = this.renderPresentationContents.bind(this);
+ this.detachPresentation = this.detachPresentation.bind(this);
this.onResize = () => setTimeout(this.handleResize.bind(this), 0);
this.setPresentationRef = this.setPresentationRef.bind(this);
@@ -330,9 +335,19 @@ class Presentation extends PureComponent {
componentWillUnmount() {
Session.setItem('componentPresentationWillUnmount', true);
- const { fullscreenContext, layoutContextDispatch } = this.props;
+ const {
+ fullscreenContext,
+ layoutContextDispatch,
+ isPresentationDetached,
+ popupWindow,
+ } = this.props;
- window.removeEventListener('resize', this.onResize, false);
+ if (isPresentationDetached) {
+ popupWindow.removeEventListener('resize', this.onResize, false);
+ } else {
+ window.removeEventListener('resize', this.onResize, false);
+ }
+
if (this.refPresentationContainer) {
this.refPresentationContainer.removeEventListener(
FULLSCREEN_CHANGE_EVENT,
@@ -359,6 +374,161 @@ class Presentation extends PureComponent {
}
}
+ detachPresentation() {
+ const {
+ slidePosition,
+ isPresentationDetached,
+ popupWindow,
+ toggleDetachPresentation,
+ } = this.props;
+
+ if (!isPresentationDetached) {
+ const svgDimensions = this.calculateSize(slidePosition);
+ const toolbarHeight = getToolbarHeight();
+ const popup = window.open('', '_blank',
+ `innerwidth=${svgDimensions.width},innerheight=${svgDimensions.height + toolbarHeight},resizable,scrollbars`);
+ if (!popup) return;
+ popup.document.title = 'BigBlueButton Portal Window';
+ const container = popup.document.createElement('div');
+ popup.document.body.appendChild(container);
+
+ // headの中身をコピー
+ const headElements = document.head.cloneNode(true).childNodes;
+ headElements.forEach((node) => {
+ // script要素など重複実行したくないものを除外する
+ if (node.nodeName !== 'SCRIPT') {
+ popup.document.head.appendChild(node.cloneNode(true));
+ }
+ });
+
+ // Add base URL (perhaps only necessary for Firefox to show tldraw icons
+ const base = popup.document.createElement('base');
+ base.href = window.location.origin + '/';
+ popup.document.head.appendChild(base);
+
+ // 追加: document.styleSheets からすべての stylesheet を popup に複製
+ Array.from(document.styleSheets).forEach((styleSheet) => {
+ try {
+ if (styleSheet.href) {
+ // 形式
+ const link = popup.document.createElement('link');
+ link.rel = 'stylesheet';
+ link.href = styleSheet.href;
+ popup.document.head.appendChild(link);
+ } else if (styleSheet.cssRules) {
+ //