From 3ddcf5ddc8591c56f06c80f0861e1ccf3af31852 Mon Sep 17 00:00:00 2001 From: Noel Chou Date: Tue, 20 Jan 2026 17:47:21 -0500 Subject: [PATCH] BL-15721 show bubbles on top of videos --- src/BloomBrowserUI/bookEdit/css/editMode.less | 31 +++++++++++++------ .../bookEdit/js/CanvasElementManager.ts | 3 +- src/BloomBrowserUI/bookEdit/js/bloomVideo.ts | 19 +++++++----- src/content/bookLayout/basePage.less | 9 ++++-- 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/BloomBrowserUI/bookEdit/css/editMode.less b/src/BloomBrowserUI/bookEdit/css/editMode.less index 31d9b91173b3..b0d83f418c43 100644 --- a/src/BloomBrowserUI/bookEdit/css/editMode.less +++ b/src/BloomBrowserUI/bookEdit/css/editMode.less @@ -1640,21 +1640,34 @@ svg.bloom-videoControl { } } +// Detector above the canvas so we can detect hover and clicks of playback controls +.bloom-videoMouseDetector { + height: 100%; + width: 100%; + position: absolute; + top: 0; + z-index: @canvasElementCanvasZIndex + 1; +} + // Show pause and replay when playing and hovered. -.bloom-videoContainer.playing:hover { - .bloom-videoControlContainer { - &.bloom-videoPauseIcon, - &.bloom-videoReplayIcon { - display: flex; +.bloom-videoContainer.playing { + .bloom-videoMouseDetector:hover { + .bloom-videoControlContainer { + &.bloom-videoPauseIcon, + &.bloom-videoReplayIcon { + display: flex; + } } } } // Show play, centered, when hovering a video that is not paused or playing -.bloom-videoContainer:not(.paused):not(.playing):hover { - .bloom-videoPlayIcon { - display: flex; - left: calc(50% - @videoIconSize / 2); +.bloom-videoContainer:not(.paused):not(.playing) { + .bloom-videoMouseDetector:hover { + .bloom-videoPlayIcon { + display: flex; + left: calc(50% - @videoIconSize / 2); + } } } diff --git a/src/BloomBrowserUI/bookEdit/js/CanvasElementManager.ts b/src/BloomBrowserUI/bookEdit/js/CanvasElementManager.ts index ec2c75c1344c..52eea4005e33 100644 --- a/src/BloomBrowserUI/bookEdit/js/CanvasElementManager.ts +++ b/src/BloomBrowserUI/bookEdit/js/CanvasElementManager.ts @@ -6426,7 +6426,8 @@ export class CanvasElementManager { .find(".bloom-ui") .filter( (_, x) => - !x.classList.contains("bloom-videoControlContainer"), + !x.classList.contains("bloom-videoControlContainer") && + !x.classList.contains("bloom-videoMouseDetector"), ) .remove(); thisCanvasElement.find(".bloom-dragHandleTOP").remove(); // BL-7903 remove any left over drag handles (this was the class used in 4.7 alpha) diff --git a/src/BloomBrowserUI/bookEdit/js/bloomVideo.ts b/src/BloomBrowserUI/bookEdit/js/bloomVideo.ts index ff691d68b2cd..a109974932c9 100644 --- a/src/BloomBrowserUI/bookEdit/js/bloomVideo.ts +++ b/src/BloomBrowserUI/bookEdit/js/bloomVideo.ts @@ -30,9 +30,14 @@ export function SetupVideoEditing(container) { // debugging, and just might prevent a problem in normal operation. videoElement.parentElement?.classList.remove("playing"); videoElement.parentElement?.classList.remove("paused"); - videoElement.addEventListener("click", handleVideoClick); + const mouseDetector = + videoElement.ownerDocument.createElement("div"); + mouseDetector.classList.add("bloom-videoMouseDetector"); + mouseDetector.classList.add("bloom-ui"); // don't save as part of document + mouseDetector.addEventListener("click", handleVideoClick); + videoElement.parentElement?.appendChild(mouseDetector); const playButton = wrapVideoIcon( - videoElement, + mouseDetector, // Alternatively, we could import the Material UI icon, make this file a TSX, and use // ReactDom.render to render the icon into the div. But just creating the SVG // ourselves (as these methods do) seems more natural to me. We would not be using @@ -43,13 +48,13 @@ export function SetupVideoEditing(container) { ); playButton.addEventListener("click", handlePlayClick); const pauseButton = wrapVideoIcon( - videoElement, + mouseDetector, getPauseIcon("#ffffff", videoElement), "bloom-videoPauseIcon", ); pauseButton.addEventListener("click", handlePauseClick); const replayButton = wrapVideoIcon( - videoElement, + mouseDetector, getReplayIcon("#ffffff", videoElement), "bloom-videoReplayIcon", ); @@ -296,17 +301,17 @@ export function updateVideoInContainer(container: Element, url: string): void { // configure one of the icons we display over videos. We put a div around it and apply // various classes and append it to the parent of the video. function wrapVideoIcon( - videoElement: HTMLVideoElement, + parent: HTMLElement, icon: HTMLElement, iconClass: string, ): HTMLElement { - const wrapper = videoElement.ownerDocument.createElement("div"); + const wrapper = parent.ownerDocument.createElement("div"); wrapper.classList.add("bloom-videoControlContainer"); wrapper.classList.add("bloom-ui"); // don't save as part of document wrapper.appendChild(icon); wrapper.classList.add(iconClass); icon.classList.add("bloom-videoControl"); - videoElement.parentElement?.appendChild(wrapper); + parent.appendChild(wrapper); return icon; } diff --git a/src/content/bookLayout/basePage.less b/src/content/bookLayout/basePage.less index 5615c26e3ae1..77dcdd3e7b59 100644 --- a/src/content/bookLayout/basePage.less +++ b/src/content/bookLayout/basePage.less @@ -323,8 +323,13 @@ books may contain divs with box-header-off, so we need a rule to hide them.*/ background-size: contain; } - // above canvas so we can click playback controls - z-index: @canvasElementCanvasZIndex + 1; + // When the book is viewed in an older version of BloomPlayer, we want to set z-index to make the video controls + // clickable like we used to do before BL-15721, even that means any speech bubbles will be behind the video. + // Newer versions of BloomPlayer will add a .videoMouseDetector element to detect clicks so that in that case + // we can avoid setting z-index here and so allow speech bubbles to be in front of the video. + &:not(:has(.bloom-videoMouseDetector)):not(:has(.videoMouseDetector)) { + z-index: @canvasElementCanvasZIndex + 1; + } video { // I don't know exactly why this works, but it makes the video shrink to fit,