From d061dd19dfb41c3214f7ba61684803419aba6a7c Mon Sep 17 00:00:00 2001 From: Hatton Date: Wed, 27 Nov 2024 15:45:37 -0700 Subject: [PATCH 1/3] chore: Core is obese. Extract static fns related to scrolling --- src/bloom-player-controls.tsx | 3 +- src/bloom-player-core.tsx | 301 +--------------------------------- src/scrolling.ts | 288 ++++++++++++++++++++++++++++++++ 3 files changed, 296 insertions(+), 296 deletions(-) create mode 100644 src/scrolling.ts diff --git a/src/bloom-player-controls.tsx b/src/bloom-player-controls.tsx index 7e462cc..095ba64 100644 --- a/src/bloom-player-controls.tsx +++ b/src/bloom-player-controls.tsx @@ -33,6 +33,7 @@ import DragBar from "@material-ui/core/Slider"; import { bloomRed } from "./bloomPlayerTheme"; import { setDurationOfPagesWithoutNarration } from "./narration"; import { roundToNearestK, normalizeDigits } from "./utilities/mathUtils"; +import { fixNiceScrollOffsets } from "./scrolling"; // This component is designed to wrap a BloomPlayer with some controls // for things like pausing audio and motion, hiding and showing @@ -603,7 +604,7 @@ export const BloomPlayerControls: React.FunctionComponent = ( actualPageHeight / scaleFactor }px; overflow: hidden;}`; //alert("scale page to window completed"); - BloomPlayerCore.fixNiceScrollOffsets(page, scaleFactor); + fixNiceScrollOffsets(page, scaleFactor); if (!pageScaled) { setPageScaled(true); } diff --git a/src/bloom-player-core.tsx b/src/bloom-player-core.tsx index c7cc8eb..ea4e021 100644 --- a/src/bloom-player-core.tsx +++ b/src/bloom-player-core.tsx @@ -45,7 +45,6 @@ import { CircularProgress } from "@material-ui/core"; import { BookInfo } from "./bookInfo"; import { BookInteraction } from "./bookInteraction"; import $ from "jquery"; -import "jquery.nicescroll"; import { getQueryStringParamAndUnencode } from "./utilities/urlUtils"; import { kLocalStorageDurationKey, @@ -76,6 +75,11 @@ import { } from "./narration"; import { logSound } from "./videoRecordingSupport"; import { playSoundOf } from "./dragActivityRuntime"; +import { + addScrollbarsToPage, + ComputeNiceScrollOffsets, + kSelectorForPotentialNiceScrollElements, +} from "./scrolling"; // BloomPlayer takes a URL param that directs it to Bloom book. // (See comment on sourceUrl for exactly how.) // It displays pages from the book and allows them to be turned by dragging. @@ -239,10 +243,6 @@ function handleInputMouseEvent(event: Event) { } } -const kSelectorForPotentialNiceScrollElements = - ".bloom-translationGroup:not(.bloom-imageDescription) .bloom-editable.bloom-visibility-code-on, " + - ".scrollable"; // we added .scrollable for branding cases where the boilerplate text also needs to scroll - export class BloomPlayerCore extends React.Component { private readonly activityManager: ActivityManager = new ActivityManager(); private readonly legacyQuestionHandler: LegacyQuestionHandler; @@ -785,30 +785,6 @@ export class BloomPlayerCore extends React.Component { } } - // nicescroll doesn't properly scale the padding at the top and left of the - // scrollable area of the languageGroup divs when the page is scaled. This - // method sets offset values to correct for this. It is called whenever the - // entire window resizes, which also scales the page before this is called. - // See BL-13796. - public static fixNiceScrollOffsets(page: HTMLElement, scale: number) { - page.querySelectorAll(kSelectorForPotentialNiceScrollElements).forEach( - (group) => { - // The type definition is not correct for getNiceScroll; we expect it to return an array. - const groupNiceScroll = $(group).getNiceScroll() as any; - if (groupNiceScroll && groupNiceScroll.length > 0) { - let { topAdjust, leftAdjust } = - BloomPlayerCore.ComputeNiceScrollOffsets( - scale, - group as HTMLElement, - ); - groupNiceScroll[0].opt.railoffset.top = -topAdjust; - groupNiceScroll[0].opt.railoffset.left = -leftAdjust; - groupNiceScroll[0].resize(); - } - }, - ); - } - private collectBodyAttributes(originalBodyElement: HTMLBodyElement) { // When working on the ABC-BARMM branding/XMatter pack, we discovered that the classes on the // Desktop body element were not getting passed into bloom-player. @@ -2590,7 +2566,7 @@ export class BloomPlayerCore extends React.Component { this.swiperInstance.keyboard.enable(); } - BloomPlayerCore.addScrollbarsToPage(bloomPage); + addScrollbarsToPage(bloomPage); const soundItems = Array.from( bloomPage.querySelectorAll("[data-sound]"), ); @@ -2600,249 +2576,6 @@ export class BloomPlayerCore extends React.Component { }, 0); // do this on the next cycle, so we don't block scrolling and display of the next page } - private static addScrollbarsToPage(bloomPage: Element): void { - // Expected behavior for cover: "on the cover, which is has a very dynamic layout, we just don't do scrollbars" - if (bloomPage.classList.contains("cover")) { - return; - } - // ENHANCE: If you drag the scrollbar mostly horizontal instead of mostly vertical, - // both the page swiping and the scrollbar will be operating, which is somewhat confusing - // and not perfectly ideal, although it doesn't really break anything. - // It'd be nice so that if you're dragging the scrollbar in any way, swiping is disabled. - - // on a browser so obsolete that it doesn't have IntersectionObserver (e.g., IE or Safari before 12.2), - // we just won't get scrolling. - if ("IntersectionObserver" in window) { - // Attach overlaid scrollbar to all editables except textOverPictures (e.g. comics) - // Expected behavior for comic bubbles: "we want overflow to show, but not generate scroll bars" - let scrollBlocks: HTMLElement[] = []; - let countOfObserversExpectedToReport = 0; - let countOfObserversThatHaveReported = 0; - $(bloomPage) - .find(kSelectorForPotentialNiceScrollElements) - .each((index, elt) => { - // Process the blocks that are possibly overflowing. - // Blocks that are overflowing will be configured to use niceScroll - // so the user can scroll and see everything. That is costly, because - // niceScroll leaks event listeners every time it is called. So we don't - // want to use it any more than we need to. Also, niceScroll - // somehow fails to work when our vertical alignment classes are applied; - // probably something to do with the bloom-editables being display:flex - // to achieve vertical positioning. We can safely remove those classes - // if the block is overflowing, because there's no excess white space - // to distribute. - // Note: there are complications Bloom desktop handles in determining - // accurately whether a block is overflowing. We don't need those here. - // If it is close enough to overflow to get a scroll bar, it's close - // enough not to care whether extra white space is at the top, bottom, - // or split (hence we can safely remove classes used for that). - // And we'll risk sometimes adding niceScroll when we could (just) - // have done without it. - const firstChild = elt.firstElementChild; - const lastChild = elt.lastElementChild; - if (!lastChild) { - // no children, can't be overflowing - return; - } - // nicescroll doesn't properly scale the padding at the top and left of the - // scrollable area of the languageGroup divs when the page is scaled. This - // code sets offset values to correct for this. The scale is determined by - // looking for a style element with an id of "scale-style-sheet" and extracting - // the scale from the transform property. See BL-13796. - let scale = 1; - const scaleStyle = document.querySelector( - "style#scale-style-sheet", - ); - if (scaleStyle) { - const match = scaleStyle.innerHTML.match( - /transform:[a-z0-9, ()]* scale\((\d+(\.\d+)?)\)/, - ); - if (match) { - scale = parseFloat(match[1]); - } - } - let { topAdjust, leftAdjust } = - BloomPlayerCore.ComputeNiceScrollOffsets(scale, elt); - // We don't really want continuous observation, but this is an elegant - // way to find out whether each child is entirely contained within its - // parent. Unlike computations involving coordinates, we don't have to - // worry about whether borders, margins, and padding are included in - // various measurements. We do need to check the first as well as the - // last child, because if text is aligned bottom, any overflow will be - // at the top. - const observer = new IntersectionObserver( - (entries, ob) => { - // called more-or-less immediately for each child, but after the - // loop creates them all. - entries.forEach((entry) => { - countOfObserversThatHaveReported++; - ob.unobserve(entry.target); // don't want to keep getting them, or leak observers - // console.log("bounding: "); - // console.log(entry.boundingClientRect); - // console.log(entry.intersectionRect); - // console.log(entry.rootBounds); - // console.log( - // "ratio: " + entry.intersectionRatio - // ); - var isBubble = !!entry.target.closest( - ".bloom-textOverPicture", - ); - // In bloom desktop preview, we set width to 200% and then scale down by 50%. - // This can lead to intersection ratios very slightly less than 1, probably due - // to pixel rounding of some sort, when in fact the content fits comfortably. - // For example, in one case we got a boundingClientRect 72.433 high - // and an intersectionRect 72.416, for a ratio of 0.9998. - // If a block is 1000 pixels high and really overflowing by 1 pixel, the ratio - // will be 0.999. I think it's safe to take anything closer to 1 than that as - // 'not overflowing'. - let overflowing = - entry.intersectionRatio < 0.999; - - if (overflowing && isBubble) { - // We want to be less aggressive about putting scroll bars on bubbles. - // Most of the time, a bubble is very carefully sized to just fit the - // text. But the intersection observer wants it to fit a certain amount - // of white space as well. We want a scroll bar if it's overflowing - // really badly for some reason, but that's much more the exception - // than the rule, so better a little clipping when the bubble is badly - // sized than a scroll bar that isn't needed in one that is just right. - // Example: a bubble which appears to fit perfectly, 3 lines high: - // its clientHeight is 72; containing bloom-editable's is 59; - // lineHeight is 24px. IntersectionRatio computes to 59/72, - // which makes the 'overflow' 13. A ratio of 0.5 as we originally - // proposed would give us a scroll bar we don't want. - let maxBubbleOverflowLineFraction = 0.6; - if ( - entry.target != - entry.target.parentElement - ?.firstElementChild || - entry.target != - entry.target.parentElement! - .lastElementChild - ) { - // Bubbles are center-aligned vertically. If this is not the only - // child,the first and last will overflow above and below by about the - // same amount. So we're only really looking at half the overflow on this para, - // and should reduce the threshold. - maxBubbleOverflowLineFraction /= 2; - } - const overflow = - (1 - entry.intersectionRatio) * - entry.target.clientHeight; - const lineHeightPx = - window.getComputedStyle( - entry.target, - ).lineHeight; - const lineHeight = parseFloat( - // remove the trailing "px" - lineHeightPx.substring( - 0, - lineHeightPx.length - 2, - ), - ); - overflowing = - overflow > - lineHeight * - maxBubbleOverflowLineFraction; - } - if ( - overflowing && - scrollBlocks.indexOf( - entry.target.parentElement!, - ) < 0 - ) { - scrollBlocks.push( - entry.target.parentElement!, - ); - // remove classes incompatible with niceScroll - const group = - entry.target.parentElement! - .parentElement!; - group.classList.remove( - "bloom-vertical-align-center", - ); - group.classList.remove( - "bloom-vertical-align-bottom", - ); - if (isBubble) { - // This is a way of forcing it not to be display-flex, which doesn't - // work with the nice-scroll-bar library we're using. - // That library messes with the element style, so it seemed safer - // not to do that myself. - entry.target.parentElement!.classList.add( - "scrolling-bubble", - ); - } - } - if ( - countOfObserversThatHaveReported == - countOfObserversExpectedToReport - ) { - // configure nicescroll...ideally only once for all of them - $(scrollBlocks).niceScroll({ - autohidemode: false, - railoffset: { - top: -topAdjust, - left: -leftAdjust, - }, - cursorwidth: "12px", - cursorcolor: "#000000", - cursoropacitymax: 0.1, - cursorborderradius: "12px", // Make the corner more rounded than the 5px default. - }); - this.setupSpecialMouseTrackingForNiceScroll( - bloomPage, - ); - scrollBlocks = []; // Just in case it's possible to get callbacks before we created them all. - } - }); - }, - { root: elt }, - ); - countOfObserversExpectedToReport++; - observer.observe(firstChild!); - if (firstChild != lastChild) { - countOfObserversExpectedToReport++; - observer.observe(lastChild); - } - }); - } - } - - // nicescroll doesn't properly scale the padding at the top and left of the - // scrollable area of the languageGroup divs when the page is scaled. This - // method computes offset values to correct for this. See BL-13796. - private static ComputeNiceScrollOffsets(scale: number, elt: HTMLElement) { - let topAdjust = 0; - let leftAdjust = 0; - if (scale !== 1) { - const compStyles = window.getComputedStyle(elt.parentElement!); - const topPadding = - compStyles.getPropertyValue("padding-top") ?? "0"; - const leftPadding = - compStyles.getPropertyValue("padding-left") ?? "0"; - topAdjust = parseFloat(topPadding) * (scale - 1); - leftAdjust = parseFloat(leftPadding) * (scale - 1); - } - return { topAdjust, leftAdjust }; - } - - static setupSpecialMouseTrackingForNiceScroll(bloomPage: Element) { - bloomPage.removeEventListener("pointerdown", this.listenForPointerDown); // only want one! - bloomPage.addEventListener("pointerdown", this.listenForPointerDown); - // The purpose of this is to prevent Swiper causing the page to be moved or - // flicked when the user is trying to scroll on the page. See BL-14079. - for (const eventName of ["pointermove", "pointerup"]) { - bloomPage.ownerDocument.body.addEventListener( - eventName, - BloomPlayerCore.handlePointerMoveEvent, - { - capture: true, - }, - ); - } - } - // This method is attached to the pointermove and pointerup events. If the event was // caused by a mouse and is in the NiceScroll thumb area, we need to stop it from // propagating any further. This is because Swiper interprets the horizontal component @@ -2865,28 +2598,6 @@ export class BloomPlayerCore extends React.Component { } } - // If the mouse down is in the thumb of a NiceScroll, we don't want to get a click - // event later even if the mouse up is outside that element. Also, we want the - // scrolling to follow the mouse movement even if the mouse cursor leaves the thumb - // before the mouse button is released. - static listenForPointerDown(ev: PointerEvent) { - if ( - ev.target instanceof HTMLDivElement && - (ev.target as HTMLDivElement).classList.contains( - "nicescroll-cursors", - ) - ) { - (ev.target as HTMLDivElement).setPointerCapture(ev.pointerId); - if (ev.pointerType === "mouse") { - // Investigation shows that Swiper uses pointer event handlers and NiceScroll - // uses mouse event handlers, so stopping the propagation of pointer events - // doesn't effect the scrolling, but does stop the swiping. See BL-14079. - // Pointer capture affects mouse events as well as pointer events. - ev.stopPropagation(); - } - } - } - // called by narration.ts public storeAudioAnalytics(duration: number): void { if (duration < 0.001 || Number.isNaN(duration)) { diff --git a/src/scrolling.ts b/src/scrolling.ts new file mode 100644 index 0000000..5a95884 --- /dev/null +++ b/src/scrolling.ts @@ -0,0 +1,288 @@ +import $ from "jquery"; +import "jquery.nicescroll"; +/// +import { BloomPlayerCore } from "./bloom-player-core"; + +export const kSelectorForPotentialNiceScrollElements = + ".bloom-translationGroup:not(.bloom-imageDescription) .bloom-editable.bloom-visibility-code-on, " + + ".scrollable"; // we added .scrollable for branding cases where the boilerplate text also needs to scroll + +export function addScrollbarsToPage(bloomPage: Element): void { + // Expected behavior for cover: "on the cover, which is has a very dynamic layout, we just don't do scrollbars" + if (bloomPage.classList.contains("cover")) { + return; + } + // ENHANCE: If you drag the scrollbar mostly horizontal instead of mostly vertical, + // both the page swiping and the scrollbar will be operating, which is somewhat confusing + // and not perfectly ideal, although it doesn't really break anything. + // It'd be nice so that if you're dragging the scrollbar in any way, swiping is disabled. + + // on a browser so obsolete that it doesn't have IntersectionObserver (e.g., IE or Safari before 12.2), + // we just won't get scrolling. + if ("IntersectionObserver" in window) { + // Attach overlaid scrollbar to all editables except textOverPictures (e.g. comics) + // Expected behavior for comic bubbles: "we want overflow to show, but not generate scroll bars" + let scrollBlocks: HTMLElement[] = []; + let countOfObserversExpectedToReport = 0; + let countOfObserversThatHaveReported = 0; + $(bloomPage) + .find(kSelectorForPotentialNiceScrollElements) + .each((index, elt) => { + // Process the blocks that are possibly overflowing. + // Blocks that are overflowing will be configured to use niceScroll + // so the user can scroll and see everything. That is costly, because + // niceScroll leaks event listeners every time it is called. So we don't + // want to use it any more than we need to. Also, niceScroll + // somehow fails to work when our vertical alignment classes are applied; + // probably something to do with the bloom-editables being display:flex + // to achieve vertical positioning. We can safely remove those classes + // if the block is overflowing, because there's no excess white space + // to distribute. + // Note: there are complications Bloom desktop handles in determining + // accurately whether a block is overflowing. We don't need those here. + // If it is close enough to overflow to get a scroll bar, it's close + // enough not to care whether extra white space is at the top, bottom, + // or split (hence we can safely remove classes used for that). + // And we'll risk sometimes adding niceScroll when we could (just) + // have done without it. + const firstChild = elt.firstElementChild; + const lastChild = elt.lastElementChild; + if (!lastChild) { + // no children, can't be overflowing + return; + } + // nicescroll doesn't properly scale the padding at the top and left of the + // scrollable area of the languageGroup divs when the page is scaled. This + // code sets offset values to correct for this. The scale is determined by + // looking for a style element with an id of "scale-style-sheet" and extracting + // the scale from the transform property. See BL-13796. + let scale = 1; + const scaleStyle = document.querySelector( + "style#scale-style-sheet", + ); + if (scaleStyle) { + const match = scaleStyle.innerHTML.match( + /transform:[a-z0-9, ()]* scale\((\d+(\.\d+)?)\)/, + ); + if (match) { + scale = parseFloat(match[1]); + } + } + let { topAdjust, leftAdjust } = ComputeNiceScrollOffsets( + scale, + elt, + ); + // We don't really want continuous observation, but this is an elegant + // way to find out whether each child is entirely contained within its + // parent. Unlike computations involving coordinates, we don't have to + // worry about whether borders, margins, and padding are included in + // various measurements. We do need to check the first as well as the + // last child, because if text is aligned bottom, any overflow will be + // at the top. + const observer = new IntersectionObserver( + (entries, ob) => { + // called more-or-less immediately for each child, but after the + // loop creates them all. + entries.forEach((entry) => { + countOfObserversThatHaveReported++; + ob.unobserve(entry.target); // don't want to keep getting them, or leak observers + // console.log("bounding: "); + // console.log(entry.boundingClientRect); + // console.log(entry.intersectionRect); + // console.log(entry.rootBounds); + // console.log( + // "ratio: " + entry.intersectionRatio + // ); + var isBubble = !!entry.target.closest( + ".bloom-textOverPicture", + ); + // In bloom desktop preview, we set width to 200% and then scale down by 50%. + // This can lead to intersection ratios very slightly less than 1, probably due + // to pixel rounding of some sort, when in fact the content fits comfortably. + // For example, in one case we got a boundingClientRect 72.433 high + // and an intersectionRect 72.416, for a ratio of 0.9998. + // If a block is 1000 pixels high and really overflowing by 1 pixel, the ratio + // will be 0.999. I think it's safe to take anything closer to 1 than that as + // 'not overflowing'. + let overflowing = entry.intersectionRatio < 0.999; + + if (overflowing && isBubble) { + // We want to be less aggressive about putting scroll bars on bubbles. + // Most of the time, a bubble is very carefully sized to just fit the + // text. But the intersection observer wants it to fit a certain amount + // of white space as well. We want a scroll bar if it's overflowing + // really badly for some reason, but that's much more the exception + // than the rule, so better a little clipping when the bubble is badly + // sized than a scroll bar that isn't needed in one that is just right. + // Example: a bubble which appears to fit perfectly, 3 lines high: + // its clientHeight is 72; containing bloom-editable's is 59; + // lineHeight is 24px. IntersectionRatio computes to 59/72, + // which makes the 'overflow' 13. A ratio of 0.5 as we originally + // proposed would give us a scroll bar we don't want. + let maxBubbleOverflowLineFraction = 0.6; + if ( + entry.target != + entry.target.parentElement + ?.firstElementChild || + entry.target != + entry.target.parentElement! + .lastElementChild + ) { + // Bubbles are center-aligned vertically. If this is not the only + // child,the first and last will overflow above and below by about the + // same amount. So we're only really looking at half the overflow on this para, + // and should reduce the threshold. + maxBubbleOverflowLineFraction /= 2; + } + const overflow = + (1 - entry.intersectionRatio) * + entry.target.clientHeight; + const lineHeightPx = window.getComputedStyle( + entry.target, + ).lineHeight; + const lineHeight = parseFloat( + // remove the trailing "px" + lineHeightPx.substring( + 0, + lineHeightPx.length - 2, + ), + ); + overflowing = + overflow > + lineHeight * maxBubbleOverflowLineFraction; + } + if ( + overflowing && + scrollBlocks.indexOf( + entry.target.parentElement!, + ) < 0 + ) { + scrollBlocks.push(entry.target.parentElement!); + // remove classes incompatible with niceScroll + const group = + entry.target.parentElement!.parentElement!; + group.classList.remove( + "bloom-vertical-align-center", + ); + group.classList.remove( + "bloom-vertical-align-bottom", + ); + if (isBubble) { + // This is a way of forcing it not to be display-flex, which doesn't + // work with the nice-scroll-bar library we're using. + // That library messes with the element style, so it seemed safer + // not to do that myself. + entry.target.parentElement!.classList.add( + "scrolling-bubble", + ); + } + } + if ( + countOfObserversThatHaveReported == + countOfObserversExpectedToReport + ) { + // configure nicescroll...ideally only once for all of them + $(scrollBlocks).niceScroll({ + autohidemode: false, + railoffset: { + top: -topAdjust, + left: -leftAdjust, + }, + cursorwidth: "12px", + cursorcolor: "#000000", + cursoropacitymax: 0.1, + cursorborderradius: "12px", // Make the corner more rounded than the 5px default. + }); + this.setupSpecialMouseTrackingForNiceScroll( + bloomPage, + ); + scrollBlocks = []; // Just in case it's possible to get callbacks before we created them all. + } + }); + }, + { root: elt }, + ); + countOfObserversExpectedToReport++; + observer.observe(firstChild!); + if (firstChild != lastChild) { + countOfObserversExpectedToReport++; + observer.observe(lastChild); + } + }); + } +} + +// nicescroll doesn't properly scale the padding at the top and left of the +// scrollable area of the languageGroup divs when the page is scaled. This +// method computes offset values to correct for this. See BL-13796. +export function ComputeNiceScrollOffsets(scale: number, elt: HTMLElement) { + let topAdjust = 0; + let leftAdjust = 0; + if (scale !== 1) { + const compStyles = window.getComputedStyle(elt.parentElement!); + const topPadding = compStyles.getPropertyValue("padding-top") ?? "0"; + const leftPadding = compStyles.getPropertyValue("padding-left") ?? "0"; + topAdjust = parseFloat(topPadding) * (scale - 1); + leftAdjust = parseFloat(leftPadding) * (scale - 1); + } + return { topAdjust, leftAdjust }; +} + +export function setupSpecialMouseTrackingForNiceScroll(bloomPage: Element) { + bloomPage.removeEventListener("pointerdown", this.listenForPointerDown); // only want one! + bloomPage.addEventListener("pointerdown", this.listenForPointerDown); + // The purpose of this is to prevent Swiper causing the page to be moved or + // flicked when the user is trying to scroll on the page. See BL-14079. + for (const eventName of ["pointermove", "pointerup"]) { + bloomPage.ownerDocument.body.addEventListener( + eventName, + BloomPlayerCore.handlePointerMoveEvent, + { + capture: true, + }, + ); + } +} + +// nicescroll doesn't properly scale the padding at the top and left of the +// scrollable area of the languageGroup divs when the page is scaled. This +// method sets offset values to correct for this. It is called whenever the +// entire window resizes, which also scales the page before this is called. +// See BL-13796. +export function fixNiceScrollOffsets(page: HTMLElement, scale: number) { + page.querySelectorAll(kSelectorForPotentialNiceScrollElements).forEach( + (group) => { + // The type definition is not correct for getNiceScroll; we expect it to return an array. + const groupNiceScroll = $(group).getNiceScroll() as any; + if (groupNiceScroll && groupNiceScroll.length > 0) { + let { topAdjust, leftAdjust } = ComputeNiceScrollOffsets( + scale, + group as HTMLElement, + ); + groupNiceScroll[0].opt.railoffset.top = -topAdjust; + groupNiceScroll[0].opt.railoffset.left = -leftAdjust; + groupNiceScroll[0].resize(); + } + }, + ); +} + +// If the mouse down is in the thumb of a NiceScroll, we don't want to get a click +// event later even if the mouse up is outside that element. Also, we want the +// scrolling to follow the mouse movement even if the mouse cursor leaves the thumb +// before the mouse button is released. +function listenForPointerDown(ev: PointerEvent) { + if ( + ev.target instanceof HTMLDivElement && + (ev.target as HTMLDivElement).classList.contains("nicescroll-cursors") + ) { + (ev.target as HTMLDivElement).setPointerCapture(ev.pointerId); + if (ev.pointerType === "mouse") { + // Investigation shows that Swiper uses pointer event handlers and NiceScroll + // uses mouse event handlers, so stopping the propagation of pointer events + // doesn't effect the scrolling, but does stop the swiping. See BL-14079. + // Pointer capture affects mouse events as well as pointer events. + ev.stopPropagation(); + } + } +} From f8a1a4bfb559baf31da72b8c9aa3b79a3b9e5b1d Mon Sep 17 00:00:00 2001 From: Hatton Date: Wed, 27 Nov 2024 16:15:18 -0700 Subject: [PATCH 2/3] chore: Extract some stylesheet code from Core to slim it down --- src/bloom-player-core.tsx | 297 ++------------------------------------ src/stylesheets.ts | 279 +++++++++++++++++++++++++++++++++++ 2 files changed, 293 insertions(+), 283 deletions(-) create mode 100644 src/stylesheets.ts diff --git a/src/bloom-player-core.tsx b/src/bloom-player-core.tsx index ea4e021..d7edbf2 100644 --- a/src/bloom-player-core.tsx +++ b/src/bloom-player-core.tsx @@ -2,7 +2,6 @@ bloom-player-core is responsible for all the behavior of working through a book, but without any UI controls (other than page turning). */ -/// import * as React from "react"; import * as ReactDOM from "react-dom"; import axios, { AxiosPromise } from "axios"; @@ -80,6 +79,8 @@ import { ComputeNiceScrollOffsets, kSelectorForPotentialNiceScrollElements, } from "./scrolling"; +import { assembleStyleSheets } from "./stylesheets"; +import { c } from "vite/dist/node/types.d-aGj9QkWt"; // BloomPlayer takes a URL param that directs it to Bloom book. // (See comment on sourceUrl for exactly how.) // It displays pages from the book and allows them to be turned by dragging. @@ -987,9 +988,18 @@ export class BloomPlayerCore extends React.Component { // Make sure you only set state.styleRules once per book. // Otherwise, it wreaks havoc on scoped styles. See BL-9504. if (isNewBook) { - const combinedStyle = await this.assembleStyleSheets( - this.htmlElement, - ); + var combinedStyle; + try { + combinedStyle = await assembleStyleSheets( + this.htmlElement!, + this.urlPrefix, + this.bookInfo, + () => this.legacyQuestionHandler.getPromiseForAnyQuizCss(), + ); + } catch (e) { + this.HandleLoadingError(e); + combinedStyle = ""; + } // We're about to start rendering with isLoading false. That means the spinning circle is // replaced with a swiper control showing the actual book contents. We need to do certain // things differently while swiper is getting stabilized on the first page; this gets set @@ -1623,285 +1633,6 @@ export class BloomPlayerCore extends React.Component { } } - // Make a