diff --git a/config.js b/config.js
index 8e2dca3214..6a0d801e7d 100644
--- a/config.js
+++ b/config.js
@@ -618,6 +618,7 @@ var config = {
// 'chat',
// 'closedcaptions',
// 'desktop',
+ // 'dock-iframe'
// 'download',
// 'embedmeeting',
// 'etherpad',
@@ -644,6 +645,7 @@ var config = {
// 'stats',
// 'tileview',
// 'toggle-camera',
+ // 'undock-iframe',
// 'videoquality',
// '__end'
// ],
diff --git a/lang/main.json b/lang/main.json
index 24901169ee..192c690679 100644
--- a/lang/main.json
+++ b/lang/main.json
@@ -1026,6 +1026,7 @@
"chat": "Open / Close chat",
"clap": "Clap",
"collapse": "Collapse",
+ "dock": "Dock in main window",
"document": "Toggle shared document",
"download": "Download our apps",
"embedMeeting": "Embed meeting",
@@ -1076,6 +1077,7 @@
"tileView": "Toggle tile view",
"toggleCamera": "Toggle camera",
"toggleFilmstrip": "Toggle filmstrip",
+ "undock": "Undock into separate window",
"videoblur": "Toggle video blur",
"videomute": "Start / Stop camera"
},
@@ -1092,6 +1094,7 @@
"closeChat": "Close chat",
"closeReactionsMenu": "Close reactions menu",
"disableReactionSounds": "You can disable reaction sounds for this meeting",
+ "dock": "Dock in main window",
"documentClose": "Close shared document",
"documentOpen": "Open shared document",
"download": "Download our apps",
@@ -1160,6 +1163,7 @@
"talkWhileMutedPopup": "Trying to speak? You are muted.",
"tileViewToggle": "Toggle tile view",
"toggleCamera": "Toggle camera",
+ "undock": "Undock into separate window",
"videoSettings": "Video settings",
"videomute": "Start / Stop camera"
},
diff --git a/modules/API/API.js b/modules/API/API.js
index 54b61d3717..022e9001af 100644
--- a/modules/API/API.js
+++ b/modules/API/API.js
@@ -1461,6 +1461,22 @@ class API {
});
}
+ /**
+ * Notify external application (if API is enabled) that the iframe
+ * docked state has been changed. The responsibility for implementing
+ * the dock / undock functionality lies with the external application.
+ *
+ * @param {boolean} docked - Whether or not the iframe has been set to
+ * be docked or undocked.
+ * @returns {void}
+ */
+ notifyIframeDockStateChanged(docked: boolean) {
+ this._sendEvent({
+ name: 'iframe-dock-state-changed',
+ docked
+ });
+ }
+
/**
* Notify external application of a participant, remote or local, being
* removed from the conference by another participant.
diff --git a/modules/API/external/external_api.js b/modules/API/external/external_api.js
index 1acf1d197e..5157c150a9 100644
--- a/modules/API/external/external_api.js
+++ b/modules/API/external/external_api.js
@@ -110,6 +110,7 @@ const events = {
'feedback-submitted': 'feedbackSubmitted',
'feedback-prompt-displayed': 'feedbackPromptDisplayed',
'filmstrip-display-changed': 'filmstripDisplayChanged',
+ 'iframe-dock-state-changed': 'iframeDockStateChanged',
'incoming-message': 'incomingMessage',
'knocking-participant': 'knockingParticipant',
'log': 'log',
diff --git a/react/features/base/icons/svg/dock.svg b/react/features/base/icons/svg/dock.svg
new file mode 100644
index 0000000000..00ff2d0a3b
--- /dev/null
+++ b/react/features/base/icons/svg/dock.svg
@@ -0,0 +1,3 @@
+
diff --git a/react/features/base/icons/svg/index.js b/react/features/base/icons/svg/index.js
index fc0b76b43c..e2e7c7b616 100644
--- a/react/features/base/icons/svg/index.js
+++ b/react/features/base/icons/svg/index.js
@@ -45,6 +45,7 @@ export { default as IconDeviceBluetooth } from './bluetooth.svg';
export { default as IconDeviceEarpiece } from './phone-talk.svg';
export { default as IconDeviceHeadphone } from './headset.svg';
export { default as IconDeviceSpeaker } from './volume.svg';
+export { default as IconDock } from './dock.svg';
export { default as IconDeviceDocument } from './document.svg';
export { default as IconDominantSpeaker } from './dominant-speaker.svg';
export { default as IconDownload } from './download.svg';
@@ -131,6 +132,7 @@ export { default as IconSwitchCamera } from './switch-camera.svg';
export { default as IconTileView } from './tiles-many.svg';
export { default as IconToggleRecording } from './camera-take-picture.svg';
export { default as IconTrash } from './trash.svg';
+export { default as IconUndock } from './undock.svg';
export { default as IconUnpin } from './unpin.svg';
export { default as IconVideoOff } from './video-off.svg';
export { default as IconVideoQualityAudioOnly } from './AUD.svg';
diff --git a/react/features/base/icons/svg/undock.svg b/react/features/base/icons/svg/undock.svg
new file mode 100644
index 0000000000..7d0729f340
--- /dev/null
+++ b/react/features/base/icons/svg/undock.svg
@@ -0,0 +1,3 @@
+
diff --git a/react/features/toolbox/components/web/DockIframeButton.js b/react/features/toolbox/components/web/DockIframeButton.js
new file mode 100644
index 0000000000..887abd892e
--- /dev/null
+++ b/react/features/toolbox/components/web/DockIframeButton.js
@@ -0,0 +1,29 @@
+// @flow
+
+import { translate } from '../../../base/i18n';
+import { IconDock } from '../../../base/icons';
+import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
+
+declare var APP: Object;
+
+/**
+ * Implementation of a button for notifying integrators that iframe should be docked.
+ */
+class DockIframeButton extends AbstractButton {
+ accessibilityLabel = 'toolbar.accessibilityLabel.dock';
+ icon = IconDock;
+ label = 'toolbar.dock';
+ tooltip = 'toolbar.dock';
+
+ /**
+ * Handles clicking / pressing the button by triggering external api event.
+ *
+ * @protected
+ * @returns {void}
+ */
+ _handleClick() {
+ APP.API.notifyIframeDockStateChanged(true);
+ }
+}
+
+export default translate(DockIframeButton);
diff --git a/react/features/toolbox/components/web/Toolbox.js b/react/features/toolbox/components/web/Toolbox.js
index bbc3789d4d..c76f2d43df 100644
--- a/react/features/toolbox/components/web/Toolbox.js
+++ b/react/features/toolbox/components/web/Toolbox.js
@@ -86,6 +86,7 @@ import HangupButton from '../HangupButton';
import HelpButton from '../HelpButton';
import AudioSettingsButton from './AudioSettingsButton';
+import DockIframeButton from './DockIframeButton';
import FullscreenButton from './FullscreenButton';
import LinkToSalesforceButton from './LinkToSalesforceButton';
import OverflowMenuButton from './OverflowMenuButton';
@@ -93,6 +94,7 @@ import ProfileButton from './ProfileButton';
import Separator from './Separator';
import ShareDesktopButton from './ShareDesktopButton';
import ToggleCameraButton from './ToggleCameraButton';
+import UndockIframeButton from './UndockIframeButton';
import VideoSettingsButton from './VideoSettingsButton';
/**
@@ -765,6 +767,18 @@ class Toolbox extends Component {
group: 3
};
+ const dockIframe = {
+ key: 'dock-iframe',
+ Content: DockIframeButton,
+ group: 3
+ };
+
+ const undockIframe = {
+ key: 'undock-iframe',
+ Content: UndockIframeButton,
+ group: 3
+ };
+
const speakerStats = {
key: 'stats',
Content: SpeakerStatsButton,
@@ -829,6 +843,8 @@ class Toolbox extends Component {
shareAudio,
etherpad,
virtualBackground,
+ dockIframe,
+ undockIframe,
speakerStats,
settings,
shortcuts,
diff --git a/react/features/toolbox/components/web/UndockIframeButton.js b/react/features/toolbox/components/web/UndockIframeButton.js
new file mode 100644
index 0000000000..0e4c4a95c6
--- /dev/null
+++ b/react/features/toolbox/components/web/UndockIframeButton.js
@@ -0,0 +1,29 @@
+// @flow
+
+import { translate } from '../../../base/i18n';
+import { IconUndock } from '../../../base/icons';
+import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
+
+declare var APP: Object;
+
+/**
+ * Implementation of a button for notifying integrators that iframe should be undocked.
+ */
+class UndockIframeButton extends AbstractButton {
+ accessibilityLabel = 'toolbar.accessibilityLabel.undock';
+ icon = IconUndock;
+ label = 'toolbar.undock';
+ tooltip = 'toolbar.undock';
+
+ /**
+ * Handles clicking / pressing the button by triggering external api event.
+ *
+ * @protected
+ * @returns {void}
+ */
+ _handleClick() {
+ APP.API.notifyIframeDockStateChanged(false);
+ }
+}
+
+export default translate(UndockIframeButton);