feat(meeting-intelligence): add toolbar button and close page link

- Add meetingintelligence to toolbarButtons and interface_config on server
- Add "View Meeting Recordings & Transcripts" link to close page
- Fix shared-video TS errors that blocked webpack builds

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-04-07 17:54:02 -04:00
parent e53dc5ae97
commit 4efcdfbf36
3 changed files with 211 additions and 4 deletions

View File

@ -0,0 +1,160 @@
import React from 'react';
import { connect } from 'react-redux';
import { PLAYBACK_STATUSES } from '../../constants';
import { getVideoEmbedUrl } from '../../functions';
import AbstractVideoManager, {
IProps as IBaseProps,
_mapDispatchToProps,
_mapStateToProps
} from './AbstractVideoManager';
interface IProps extends IBaseProps {
/**
* The video source type (vimeo, dailymotion, twitch).
*/
sourceType: string;
}
/**
* Manager for embedded video players (Vimeo, Dailymotion, Twitch).
* Uses iframe embeds for playback.
*/
class EmbedVideoManager extends AbstractVideoManager {
iframeRef: React.RefObject<HTMLIFrameElement>;
_isPlaying: boolean;
_isMuted: boolean;
_currentTime: number;
/**
* Initializes a new EmbedVideoManager instance.
*
* @param {Object} props - This component's props.
* @returns {void}
*/
constructor(props: IProps) {
super(props);
this.iframeRef = React.createRef();
this._isPlaying = true;
this._isMuted = false;
this._currentTime = 0;
}
/**
* Indicates the playback state of the video.
*
* @returns {string}
*/
override getPlaybackStatus() {
return this._isPlaying ? PLAYBACK_STATUSES.PLAYING : PLAYBACK_STATUSES.PAUSED;
}
/**
* Indicates whether the video is muted.
*
* @returns {boolean}
*/
override isMuted() {
return this._isMuted;
}
/**
* Retrieves current volume.
*
* @returns {number}
*/
override getVolume() {
return this._isMuted ? 0 : 1;
}
/**
* Retrieves current time.
*
* @returns {number}
*/
override getTime() {
return this._currentTime;
}
/**
* Seeks video to provided time.
*
* @param {number} time - The time to seek to.
* @returns {void}
*/
override seek(time: number) {
this._currentTime = time;
}
/**
* Plays video.
*
* @returns {void}
*/
override play() {
this._isPlaying = true;
}
/**
* Pauses video.
*
* @returns {void}
*/
override pause() {
this._isPlaying = false;
}
/**
* Mutes video.
*
* @returns {void}
*/
override mute() {
this._isMuted = true;
}
/**
* Unmutes video.
*
* @returns {void}
*/
override unMute() {
this._isMuted = false;
}
/**
* Implements React Component's render.
*
* @inheritdoc
*/
override render() {
const { videoId } = this.props;
const sourceType = (this.props as IProps).sourceType;
const embedUrl = getVideoEmbedUrl(videoId, sourceType);
return (
<div
style = {{
width: '100%',
height: '100%'
}}>
<iframe
allow = 'autoplay; encrypted-media; fullscreen; picture-in-picture'
allowFullScreen = { true }
frameBorder = '0'
height = '100%'
id = 'sharedVideoPlayer'
ref = { this.iframeRef }
src = { embedUrl }
title = 'Shared Video Player'
width = '100%' />
</div>
);
}
}
// @ts-ignore - EmbedVideoManager has extra sourceType prop not in AbstractVideoManager's mapStateToProps
export default connect(_mapStateToProps, _mapDispatchToProps)(EmbedVideoManager);

View File

@ -8,11 +8,19 @@ import { FakeParticipant } from '../../../base/participants/types';
import { getVerticalViewMaxWidth } from '../../../filmstrip/functions.web';
import { getLargeVideoParticipant } from '../../../large-video/functions';
import { getToolboxHeight } from '../../../toolbox/functions.web';
import { isSharedVideoEnabled, isVideoPlaying } from '../../functions';
import { VIDEO_SOURCE_TYPES } from '../../constants';
import { getVideoSourceType, isSharedVideoEnabled, isVideoPlaying } from '../../functions';
import EmbedVideoManager from './EmbedVideoManager';
import VideoManager from './VideoManager';
import YoutubeVideoManager from './YoutubeVideoManager';
const EMBED_VIDEO_TYPES = [
VIDEO_SOURCE_TYPES.VIMEO,
VIDEO_SOURCE_TYPES.DAILYMOTION,
VIDEO_SOURCE_TYPES.TWITCH
];
interface IProps {
/**
@ -116,11 +124,21 @@ class SharedVideo extends Component<IProps> {
return null;
}
if (videoUrl.match(/http/)) {
return <VideoManager videoId = { videoUrl } />;
const sourceType = getVideoSourceType(videoUrl);
if (sourceType === VIDEO_SOURCE_TYPES.YOUTUBE) {
return <YoutubeVideoManager videoId = { videoUrl } />;
}
return <YoutubeVideoManager videoId = { videoUrl } />;
if (EMBED_VIDEO_TYPES.includes(sourceType as any)) {
return (<EmbedVideoManager
// @ts-ignore
sourceType = { sourceType }
// @ts-ignore
videoId = { videoUrl } />);
}
return <VideoManager videoId = { videoUrl } />;
}
/**

View File

@ -5,12 +5,35 @@
<!--#include virtual="/title.html" -->
<script><!--#include virtual="/interface_config.js" --></script>
<script src="static/close.js"></script>
<style>
.meeting-intelligence-link {
display: flex;
justify-content: center;
margin-top: 24px;
}
.meeting-intelligence-link a {
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
border-radius: 8px;
color: #fff;
font-size: 16px;
font-weight: 500;
padding: 12px 24px;
text-decoration: none;
transition: opacity 0.2s;
}
.meeting-intelligence-link a:hover {
opacity: 0.9;
}
</style>
</head>
<body>
<div class="redirectPageMessage">
<div class="thanks-msg">
<p id="thanksMessage"></p>
</div>
<div class="meeting-intelligence-link">
<a href="/" id="miLink" onclick="openDashboard(event)">View Meeting Recordings &amp; Transcripts</a>
</div>
<div class="hint-msg">
<p>
<span id="hintQuestion">Did you know?</span>
@ -19,5 +42,11 @@
<div class="happy-software"></div>
</div>
</div>
<script>
function openDashboard(e) {
e.preventDefault();
window.location.href = '/';
}
</script>
</body>
</html>