Skip to content

Commit

Permalink
chore: final review session with JohnT
Browse files Browse the repository at this point in the history
  • Loading branch information
hatton committed Dec 11, 2024
1 parent 04a01f1 commit 3df06c8
Show file tree
Hide file tree
Showing 8 changed files with 36 additions and 308 deletions.
Binary file removed public/testBooks/multibook-index/history.db
Binary file not shown.
1 change: 0 additions & 1 deletion public/testBooks/multibook-index/publish-settings.json

This file was deleted.

4 changes: 2 additions & 2 deletions src/bloom-player-controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ book inside of the Bloom:Publish:Android screen.
import { BloomPlayerCore } from "./bloom-player-core";
import * as ReactDOM from "react-dom";
import {
sendBackToHost,
informHostOfBackAction,
showNavBar,
hideNavBar,
reportBookProperties,
Expand Down Expand Up @@ -933,7 +933,7 @@ export const BloomPlayerControls: React.FunctionComponent<BloomPlayerProps> = (
if (
!coreRef.current?.HandleBackButtonClickedIfHavePlayerHistory()
) {
sendBackToHost();
informHostOfBackAction();
}
}}
showPlayPause={hasAudio || hasMusic || hasVideo}
Expand Down
290 changes: 3 additions & 287 deletions src/bloom-player-core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ bloom-player-core is responsible for all the behavior of working through a book,
(other than page turning).
*/
import * as React from "react";
import * as ReactDOM from "react-dom";
import axios, { AxiosPromise } from "axios";
import axios from "axios";
import Swiper, { SwiperInstance } from "react-id-swiper";
// This loads some JS right here that is a polyfill for the (otherwise discontinued) scoped-styles html feature
import "style-scoped/scoped.min.js";
Expand All @@ -20,7 +19,6 @@ import { LocalizationManager } from "./l10n/localizationManager";
import {
reportAnalytics,
reportPlaybackComplete,
sendMessageToHost,
setAmbientAnalyticsProperties,
updateBookProgressReport,
} from "./externalContext";
Expand All @@ -30,7 +28,6 @@ import {
sendStringToBloomApi,
} from "./videoRecordingSupport";
import LangData from "./langData";
import Replay from "@material-ui/icons/Replay";

// See related comments in controlBar.tsx
import IconButton from "@material-ui/core/IconButton";
Expand Down Expand Up @@ -83,7 +80,7 @@ import { assembleStyleSheets } from "./stylesheets";
import {
canGoBack,
checkClickForBookOrPageJump,
goBackInHistoryIfPossible,
tryPopPlayerHistory,
} from "./navigation";

// BloomPlayer takes a URL param that directs it to Bloom book.
Expand Down Expand Up @@ -428,9 +425,7 @@ export class BloomPlayerCore extends React.Component<IProps, IPlayerState> {
}
// called by bloom-player-controls
public HandleBackButtonClickedIfHavePlayerHistory(): boolean {
const backLocation = goBackInHistoryIfPossible(
this.bookInfo.bookInstanceId,
);
const backLocation = tryPopPlayerHistory(this.bookInfo.bookInstanceId);
if (backLocation) {
this.navigate(backLocation.bookUrl, backLocation.pageId);
return true;
Expand Down Expand Up @@ -1667,285 +1662,6 @@ export class BloomPlayerCore extends React.Component<IProps, IPlayerState> {
}
}

// Make a <style> element in the head of the document containing an adjusted version
// of the "fonts.css" file shipped with the book, if any.
// This file typically contains @font-face declarations, which don't work when embedded
// in the <scoped> element we make for each page contents. So we need the rules to
// be placed in the <head> of the main document. (We don't want to do this with most
// rules because rules from a book, especially from a customStyles file, could mess up
// bloom-player itself.)
// Since the root file, typically bloomPlayer.htm, is typically not at the same location
// as the book files, the simple URLs it contains don't work, since they assume fonts.css
// is loaded as part of a file in the same location as the font files. So we modify
// the URLs in the file to be relative to the book folder.
// There are possible dangers here...if someone got something malicious into fonts.css
// it could at least mess up our player...but only for that one book, and in any case,
// fonts.css is generated by the harvester so it shouldn't be possible for anyone without
// harvester credentials to post a bad version of it.
// Enhance: currently we're just looking for the (typically ttf) font uploaded as part
// of the book if embedding is permitted. Eventually, we might want to try first for
// a corresponding woff font in a standard location. We may even be able to pay to
// provide some fonts there for book previewing that are NOT embeddable.
private makeFontStylesheet(href: string): void {
axios.get(href).then((result) => {
let stylesheet = document.getElementById(
"fontCssStyleSheet",
) as HTMLStyleElement;
if (!stylesheet) {
stylesheet = document.createElement("style");
document.head.appendChild(stylesheet);
stylesheet.setAttribute("id", "fontCssStyleSheet");
}
const prefix = href.substring(0, href.length - "/fonts.css".length);
stylesheet.innerText = result.data.replace(
// This is so complex because at one time we weren't adding the
// quotes around the original url. So, now we handle no quotes,
// single quotes, and double quotes. Note that we also have to
// handle possible parentheses in the file name.
/src:url\(['"]?(.*\.[^\)'"]*)['"]?\)/g,
"src:url('" + prefix + "/$1')",
);
});
// no catch clause...if there's no fonts.css, we should never get a 'then' and
// don't need to do anything.
}

// Make a stylesheet that tells the player where to find Andika New Basic.
// This can't be part of the <style scoped> where most style rules for the book
// content go, because @font-face rules don't work there.
private makeAndikaStylesheet(): void {
let stylesheet = document.getElementById(
"andikaCssStyleSheet",
) as HTMLStyleElement;
if (!stylesheet) {
stylesheet = document.createElement("style");
document.head.appendChild(stylesheet);
stylesheet.setAttribute("id", "andikaCssStyleSheet");
}

// Starting in Jul 2022, we provide Andika as a fallback to Andika New Basic.
// (And we ship our hosts with Andika instead of ANB.)
//
// 1. The font might be found already installed (local).
// 2. Failing that, if we're inside BloomReader, BloomPUB Viewer, or another host we control,
// we add the fake relative path `./host/fonts/*` so the host can intercept
// this request and serve the appropriate font file.
// (RAB doesn't use this stylesheet at all; rather, it modifies fonts.css.)
// 3. If instead we're embedded in a web page like BloomLibrary.org, we need to download from the web.
//
// (Jun 2022) I'm not sure this is (still) true:
// Note that currently that last option will only work when the page origin
// is *bloomlibrary.org. This helps limit our exposure to large charges from
// people using our font arbitrarily. This does include, however, books
// displayed in an iframe using https://bloomlibrary.org/bloom-player/bloomplayer.htm
//
// Previously, this conditionally included some file:// urls for Android which were
// conditionally included because they caused problems for Safari on iOS. That is why
// they are here instead of bloom-player-content.less. In their current state,
// I believe they could be moved there. But we may need conditional logic again...
stylesheet.innerText = `
@font-face {
font-family: "Andika New Basic";
font-weight: normal;
font-style: normal;
src:
local("Andika New Basic"),
local("Andika"),
url("./host/fonts/Andika New Basic"),
url("https://bloomlibrary.org/fonts/Andika%20New%20Basic/AndikaNewBasic-R.woff")
;
}
@font-face {
font-family: "Andika New Basic";
font-weight: bold;
font-style: normal;
src:
local("Andika New Basic Bold"),
local("Andika Bold"),
url("./host/fonts/Andika New Basic Bold"),
url("https://bloomlibrary.org/fonts/Andika%20New%20Basic/AndikaNewBasic-B.woff")
;
}
@font-face {
font-family: "Andika New Basic";
font-weight: normal;
font-style: italic;
src:
local("Andika New Basic Italic"),
local("Andika Italic"),
url("./host/fonts/Andika New Basic Italic"),
url("https://bloomlibrary.org/fonts/Andika%20New%20Basic/AndikaNewBasic-I.woff")
;
}
@font-face {
font-family: "Andika New Basic";
font-weight: bold;
font-style: italic;
src:
local("Andika New Basic Bold Italic"),
local("Andika Bold Italic"),
url("./host/fonts/Andika New Basic Bold Italic"),
url("https://bloomlibrary.org/fonts/Andika%20New%20Basic/AndikaNewBasic-BI.woff")
;
}
@font-face {
font-family: "Andika";
font-weight: normal;
font-style: normal;
src:
local("Andika"),
url("./host/fonts/Andika"),
url("https://bloomlibrary.org/fonts/Andika/Andika-Regular.woff")
;
}
@font-face {
font-family: "Andika";
font-weight: bold;
font-style: normal;
src:
local("Andika Bold"),
url("./host/fonts/Andika Bold"),
url("https://bloomlibrary.org/fonts/Andika/Andika-Bold.woff")
;
}
@font-face {
font-family: "Andika";
font-weight: normal;
font-style: italic;
src:
local("Andika Italic"),
url("./host/fonts/Andika Italic"),
url("https://bloomlibrary.org/fonts/Andika/Andika-Italic.woff")
;
}
@font-face {
font-family: "Andika";
font-weight: bold;
font-style: italic;
src:
local("Andika Bold Italic"),
url("./host/fonts/Andika Bold Italic"),
url("https://bloomlibrary.org/fonts/Andika/Andika-BoldItalic.woff")
;
}
.do-not-display {
display:none !important;
}
`

// (Jun 2022) these are not actually preventing <br> tags:
.replace("\n", "")
.replace("\r", ""); // newlines turn to <br> which is wrong in style element
}

// Assemble all the style rules from all the stylesheets the book contains or references.
// When the async completes, the result will be set as our state.styles with setState().
// Exception: a stylesheet called "fonts.css" will instead be loaded into the <head>
// of the main document, since it contains @font-face declarations that don't work
// in the <scoped> element.
private async assembleStyleSheets(doc: HTMLHtmlElement): Promise<string> {
this.makeAndikaStylesheet();
const linkElts = doc.ownerDocument!.evaluate(
".//link[@href and @type='text/css']",
doc,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null,
);
const promises: Array<AxiosPromise<any>> = [];
for (let i = 0; i < linkElts.snapshotLength; i++) {
const link = linkElts.snapshotItem(i) as HTMLElement;
const href = link.getAttribute("href");
const fullHref = this.fullUrl(href);
if (fullHref.endsWith("/fonts.css")) {
this.makeFontStylesheet(fullHref);
} else {
promises.push(axios.get(fullHref));
if (fullHref.endsWith("/appearance.css")) {
this.bookInfo.hasAppearanceSystem = true;
}
}
}
const p = this.legacyQuestionHandler.getPromiseForAnyQuizCss();
if (p) {
promises.push(p);
}

try {
const results = await axios.all(
promises.map((promise) =>
promise.catch(
// if one stylesheet doesn't exist or whatever, keep going
() => undefined,
),
),
);

let combinedStyle = "";

// start with embedded styles (typically before links in a bloom doc...)
const styleElts = doc.ownerDocument!.evaluate(
".//style[@type='text/css']",
doc,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null,
);
for (let k = 0; k < styleElts.snapshotLength; k++) {
const styleElt = styleElts.snapshotItem(k) as HTMLElement;
combinedStyle += styleElt.innerText;
}

// then add the stylesheet contents we just retrieved
results.forEach((result) => {
if (result && result.data) {
// console.log(
// "Loaded stylesheet: " + JSON.stringify(result.config),
// );
combinedStyle += result.data;

// It is somewhat awkward to do this in a method called assembleStyleSheets,
// but this is the best way to access the information currently.
// See further comments in getNationalLanguagesFromCssStyles.
//
// settingsCollectionStyles.css was replaced with defaultLangStyles.css.
// We have to check for both for older books.
if (
result.config!.url!.endsWith(
"/defaultLangStyles.css",
) ||
result.config!.url!.endsWith(
"/settingsCollectionStyles.css",
)
) {
this.bookInfo.setLanguage2And3(result.data);
}
}
});

// A ":root" selector doesn't work in this scoped css context.
// Change all ":root" references to ".bloomPlayer-page", which is the level just above .bloom-page.
// We're using this instead of .bloom-page directly so that a selector like `:root .bloom-page` would still work.
combinedStyle = combinedStyle.replace(
/:root/g,
".bloomPlayer-page",
);
//console.log("***combinedStyle", combinedStyle);
return combinedStyle;
} catch (err) {
this.HandleLoadingError(err);
return "";
}
}

private fullUrl(url: string | null): string {
// Enhance: possibly we should only do this if we somehow determine it is a relative URL?
// But the things we apply it to always are, in bloom books.
Expand Down
2 changes: 1 addition & 1 deletion src/externalContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export function updateBookProgressReport(event: string, properties: any) {

let gotCapabilities = false;

export function sendBackToHost() {
export function informHostOfBackAction() {
sendMessageToHost({ messageType: "backButtonClicked" });
}

Expand Down
Loading

0 comments on commit 3df06c8

Please sign in to comment.