diff --git a/DistFiles/localization/en/Bloom.xlf b/DistFiles/localization/en/Bloom.xlf
index 9ce3692f8818..7a4587e5aeca 100644
--- a/DistFiles/localization/en/Bloom.xlf
+++ b/DistFiles/localization/en/Bloom.xlf
@@ -1355,6 +1355,15 @@
PictureID: EditTab.CustomPage.Picture
+ Obsolete as of 6.3
+
+
+ Image
+ ID: EditTab.CustomPage.Image
+
+
+ Canvas
+ ID: EditTab.CustomPage.CanvasText
@@ -1871,6 +1880,7 @@
Cut imageID: EditTab.Image.CutImage
+ Obsolete as of Bloom 6.3Edit image credits, copyright, & license
diff --git a/artwork/placeholder.svg b/artwork/placeholder.svg
deleted file mode 100644
index dcd3ba56fba1..000000000000
--- a/artwork/placeholder.svg
+++ /dev/null
@@ -1,64 +0,0 @@
-
-
-
-
diff --git a/src/BloomBrowserUI/bookEdit/css/editMode.less b/src/BloomBrowserUI/bookEdit/css/editMode.less
index 37c983044691..31d9b91173b3 100644
--- a/src/BloomBrowserUI/bookEdit/css/editMode.less
+++ b/src/BloomBrowserUI/bookEdit/css/editMode.less
@@ -1,6 +1,7 @@
/*This stylesheet applied when a book is being edited. It does things like show that images can be changed by click on them.*/
@import "../../bloomUI.less";
@import "../sourceBubbles/sourceBubbles.less";
+@import "../../placeHolderImages.less";
@bleed: 3mm; // Duplicated from basePage.less to remove circular dependency between BloomBrowserUI and content
@@ -491,7 +492,8 @@ body:not(.origami-drag) .bloom-canvas:hover {
// In an active background canvas element, don't show the edit metadata icon if the image failed
// to load. Clicking on it would just show an error message anyway, and the button would
// obscure part of the alt attribute error message.
-.bloom-canavas:has(.bloom-backgroundImage:has(img.bloom-imageLoadError)) {
+.bloom-canvas:has(.bloom-backgroundImage:has(img.bloom-imageLoadError)),
+.bloom-canvas:has(.bloom-backgroundImage:has(img[src*="placeHolder.png"])) {
.editMetadataButton.imgMetadataProblem {
display: none !important;
}
@@ -590,9 +592,11 @@ body.bloom-fullBleed {
// on each parent. We could conceivably compute a different transform for each child, but this is more
// straightforward and less error-prone.
.bloom-describedImage {
- // for a reason I haven't tracked down, the rule that usually does this doesn't work here.
- img[src="placeHolder.png"] {
- display: none;
+ // Repeated class name so we override even the rules for data-tool-id="canvas" in placeHolderImages.less
+ .bloom-backgroundImage.bloom-canvas-element.bloom-canvas-element:has(
+ img[src*="placeHolder.png"]
+ ) {
+ background-image: none;
}
height: 100%;
width: 100%;
@@ -664,16 +668,6 @@ body.bloom-fullBleed {
}
}
-// In a bloom-canvas that has canvas elements other than the background image, we want to
-// hide the placeholder except when the background image is active.
-.bloom-canvas:has(.bloom-canvas-element:not(.bloom-backgroundImage)) {
- .bloom-backgroundImage:not([data-bloom-active="true"]) {
- img[src^="placeHolder.png"] {
- display: none;
- }
- }
-}
-
button.deleteButton {
position: absolute;
left: 0;
@@ -1439,6 +1433,17 @@ canvas.moving {
display: block;
}
+ // We want the crop handles to look disabled on an empty/placeholder image. Disabling them is actually done in
+ // typescript code
+ &.bloom-image-control-frame-no-image {
+ .bloom-ui-canvas-element-side-handle,
+ .bloom-ui-canvas-element-move-crop-handle {
+ pointer-events: none;
+ cursor: unset;
+ opacity: 30%;
+ }
+ }
+
// The resize handles are the small circles in the four corners.
// The move crop handle is the small circle in the center, used to change how an
// image is cropped by (apparently) moving the image within the frame.
@@ -1668,23 +1673,15 @@ svg.bloom-videoControl {
}
// If the background image hasn't been set in a game, we don't want to show the placeholder image.
-.bloom-page[data-tool-id="game"]:not([data-activity="simple-dom-choice"])
- .bloom-backgroundImage
- img[src="placeHolder.png"] {
- visibility: hidden;
-}
-// We also want the crop handles to look disabled. Disabling them is actually done in
-// typescript code. The pointer-events: none; seems not to work on the handle divs but
-// is left as a reminder that we don't want to allow cropping in this case. (BL-14703)
-.bloom-page[data-tool-id="game"]:not([data-activity="simple-dom-choice"])
- #canvas-element-control-frame.bloom-backgroundImage-control-frame-no-image {
- .bloom-ui-canvas-element-side-handle,
- .bloom-ui-canvas-element-move-crop-handle {
- pointer-events: none;
- cursor: unset;
- opacity: 30%;
+.bloom-page[data-tool-id="game"]:not([data-activity="simple-dom-choice"]) {
+ .bloom-canvas
+ .bloom-backgroundImage.bloom-canvas-element:has(
+ img[src="placeHolder.png"]
+ ) {
+ background-image: none;
}
}
+
.bloom-passive-element[data-bloom-active="true"]
~ .bloom-ui-canvas-element-resize-handle,
.bloom-passive-element[data-bloom-active="true"]
diff --git a/src/BloomBrowserUI/bookEdit/js/CanvasElementContextControls.tsx b/src/BloomBrowserUI/bookEdit/js/CanvasElementContextControls.tsx
index 420bd4ee8ac2..b85bd803bc57 100644
--- a/src/BloomBrowserUI/bookEdit/js/CanvasElementContextControls.tsx
+++ b/src/BloomBrowserUI/bookEdit/js/CanvasElementContextControls.tsx
@@ -22,6 +22,7 @@ import {
doImageCommand,
getImageUrlFromImageContainer,
kImageContainerClass,
+ isPlaceHolderImage,
} from "./bloomImages";
import {
doVideoCommand,
@@ -122,7 +123,7 @@ const CanvasElementContextControls: React.FunctionComponent<{
const videoSource = video?.getElementsByTagName("source")[0];
const videoAlreadyChosen = !!videoSource?.getAttribute("src");
const isPlaceHolder =
- hasImage && img?.getAttribute("src")?.startsWith("placeHolder.png");
+ hasImage && isPlaceHolderImage(img?.getAttribute("src"));
const missingMetadata =
hasImage &&
!isPlaceHolder &&
@@ -374,9 +375,7 @@ const CanvasElementContextControls: React.FunctionComponent<{
menuOptions.splice(index, 0, fillItem);
// we can't delete the placeholder (or if there isn't an img, somehow)
- deleteEnabled = !!(
- img && !img.getAttribute("src")?.startsWith("placeHolder.png")
- );
+ deleteEnabled = hasRealImage(img);
} else if (isSpecialGameElementSelected || isLinkGrid) {
deleteEnabled = false; // don't allow deleting the single drag item in a sentence drag game or link grids
}
@@ -1073,6 +1072,10 @@ function addVideoMenuItems(
);
}
+function hasRealImage(img) {
+ return img && !isPlaceHolderImage(img.getAttribute("src"));
+}
+
function addImageMenuOptions(
menuOptions: IMenuItemWithSubmenu[],
canvasElement: HTMLElement,
@@ -1093,6 +1096,7 @@ function addImageMenuOptions(
);
};
+ const realImagePresent = hasRealImage(img);
const imageMenuOptions: IMenuItemWithSubmenu[] = [
{
l10nId: "EditTab.Image.ChooseImage",
@@ -1114,6 +1118,7 @@ function addImageMenuOptions(
english: "Copy image",
onClick: () => doImageCommand(img, "copy"),
icon: ,
+ disabled: !realImagePresent,
},
{
l10nId: "EditTab.Image.EditMetadataOverlay",
@@ -1121,6 +1126,7 @@ function addImageMenuOptions(
subLabelL10nId: "EditTab.Image.EditMetadataOverlayMore",
onClick: runMetadataDialog,
icon: ,
+ disabled: !realImagePresent,
},
{
l10nId: "EditTab.Image.Reset",
diff --git a/src/BloomBrowserUI/bookEdit/js/CanvasElementManager.ts b/src/BloomBrowserUI/bookEdit/js/CanvasElementManager.ts
index 8aac0d87058a..2d9ccef7955d 100644
--- a/src/BloomBrowserUI/bookEdit/js/CanvasElementManager.ts
+++ b/src/BloomBrowserUI/bookEdit/js/CanvasElementManager.ts
@@ -38,6 +38,7 @@ import {
SetupMetadataButton,
UpdateImageTooltipVisibility,
HandleImageError,
+ isPlaceHolderImage,
} from "./bloomImages";
import {
adjustTarget,
@@ -71,13 +72,18 @@ import { CanvasGuideProvider } from "./CanvasGuideProvider";
import { CanvasElementKeyboardProvider } from "./CanvasElementKeyboardProvider";
import { CanvasSnapProvider } from "./CanvasSnapProvider";
import { get, postData, postJson } from "../../utils/bloomApi";
-import AudioRecording from "../toolbox/talkingBook/audioRecording";
+import AudioRecording, {
+ kTalkingBookToolId,
+} from "../toolbox/talkingBook/audioRecording";
import PlaceholderProvider from "./PlaceholderProvider";
import { getExactClientSize } from "../../utils/elementUtils";
import { copyContentToTarget, getTarget } from "bloom-player";
import { showRequiresSubscriptionDialogInEditView } from "../../react_components/requiresSubscription";
import { FeatureStatus } from "../../react_components/featureStatus";
import $ from "jquery";
+import { kCanvasToolId } from "../toolbox/toolIds";
+import { getToolboxBundleExports } from "./bloomFrames";
+import { ImageDescriptionAdapter } from "../toolbox/imageDescription/imageDescription";
export interface ITextColorInfo {
color: string;
@@ -547,6 +553,9 @@ export class CanvasElementManager {
bloomCanvas.scrollLeft = 0;
bloomCanvas.scrollTop = 0;
});
+ if (bloomCanvas.getAttribute("data-tool-id") === kCanvasToolId) {
+ SetupClickToShowCanvasTool(bloomCanvas);
+ }
});
// todo: select the right one...in particular, currently we just select the last one.
@@ -1309,12 +1318,8 @@ export class CanvasElementManager {
return;
}
const target = event.currentTarget as HTMLElement;
- if (
- target.closest(
- `.${kBackgroundImageClass}-control-frame-no-image`,
- )
- ) {
- return; // don't crop empty background image container
+ if (target.closest(`.bloom-image-control-frame-no-image`)) {
+ return; // don't crop empty image container
}
this.startSideControlDrag(event, side);
});
@@ -2683,11 +2688,12 @@ export class CanvasElementManager {
imgWidth = imgOrVideo.naturalWidth;
imgHeight = imgOrVideo.naturalHeight;
if (
- imgOrVideo.naturalHeight === 0 && // not loaded successfully (yet)
- !useSizeOfNewImage && // not waiting for new dimensions
- imgOrVideo.classList.contains("bloom-imageLoadError") // error occurred while trying to load
+ isPlaceHolderImage(imgOrVideo.getAttribute("src")) ||
+ (imgOrVideo.naturalHeight === 0 && // not loaded successfully (yet)
+ !useSizeOfNewImage && // not waiting for new dimensions
+ imgOrVideo.classList.contains("bloom-imageLoadError")) // error occurred while trying to load
) {
- // Image is in an error state; we probably won't ever get useful dimensions. Just leave
+ // Image is in an error state or is just a placeholder; we probably won't ever get useful dimensions. Just leave
// the canvas element the shape it is.
return;
}
@@ -2827,6 +2833,7 @@ export class CanvasElementManager {
canvasElement,
true,
);
+ SetupMetadataButton(canvasElement);
} else {
this.adjustContainerAspectRatio(canvasElement, true);
}
@@ -2883,20 +2890,16 @@ export class CanvasElementManager {
kBackgroundImageClass + "-control-frame",
this.activeElement.classList.contains(kBackgroundImageClass),
);
- let backgroundImageExists = true;
- if (this.activeElement.classList.contains(kBackgroundImageClass)) {
- const img = getImageFromCanvasElement(this.activeElement);
- if (img && img.getAttribute("src") === "placeHolder.png") {
- backgroundImageExists = false;
- }
+
+ // mark empty image control frames with a special class
+ let imageIsPlaceHolder = false;
+ const img = getImageFromCanvasElement(this.activeElement);
+ if (img && isPlaceHolderImage(img.getAttribute("src"))) {
+ imageIsPlaceHolder = true;
}
- // mark empty background images in games with a special class (BL-14703)
controlFrame.classList.toggle(
- kBackgroundImageClass + "-control-frame-no-image",
- !backgroundImageExists &&
- !!this.activeElement.closest(
- ".bloom-page[data-tool-id='game']",
- ),
+ "bloom-image-control-frame-no-image",
+ imageIsPlaceHolder,
);
const hasText = controlFrame.classList.contains("has-text");
@@ -5009,7 +5012,7 @@ export class CanvasElementManager {
canvasElements[0].classList.contains(kBackgroundImageClass)
) {
const bgimg = canvasElements[0].getElementsByTagName("img")[0];
- if (bgimg.getAttribute("src")?.startsWith("placeHolder.png")) {
+ if (isPlaceHolderImage(bgimg.getAttribute("src"))) {
changeImageInfo(bgimg, imageInfo);
this.adjustBackgroundImageSize(
bloomCanvas,
@@ -5030,7 +5033,7 @@ export class CanvasElementManager {
const img = activeElement
.getElementsByClassName(kImageContainerClass)[0]
?.getElementsByTagName("img")[0];
- if (img && img.getAttribute("src")?.startsWith("placeHolder.png")) {
+ if (img && isPlaceHolderImage(img.getAttribute("src"))) {
changeImageInfo(img, imageInfo);
this.adjustContainerAspectRatio(
activeElement as HTMLElement,
@@ -6833,9 +6836,12 @@ export class CanvasElementManager {
// image has the special class indicating that it failed to load. (The class is supposed
// to be removed when we change the src attribute, which leads to a new load attempt.)
failedImage =
- img.naturalHeight === 0 && // not loaded successfully (yet)
- !useSizeOfNewImage && // not waiting for new dimensions
- img.classList.contains("bloom-imageLoadError"); // error occurred while trying to load
+ // As of BL-15441, we use css instead of real placeHolder.png files but still set src="placeHolder.png"
+ // to indicate placeholders. Treat this case as a failed image for dimensions purposes
+ isPlaceHolderImage(img.getAttribute("src")) ||
+ (img.naturalHeight === 0 && // not loaded successfully (yet)
+ !useSizeOfNewImage && // not waiting for new dimensions
+ img.classList.contains("bloom-imageLoadError")); // error occurred while trying to load
if (failedImage) {
// If the image failed to load, just use the container aspect ratio to fill up
// the container with the error message (alt attribute string).
@@ -6992,7 +6998,7 @@ export class CanvasElementManager {
// for greater accuracy in scaling. (BL-15464)
const newCeWidth = CanvasElementManager.pxToNumber(
bgCanvasElement.style.width,
- bgCanvasElement.clientWidth
+ bgCanvasElement.clientWidth,
);
const scale = newCeWidth / oldCeWidth;
img.style.width =
@@ -7106,7 +7112,7 @@ export class CanvasElementManager {
const img = getImageFromCanvasElement(child);
if (
!img ||
- img.getAttribute("src") === "placeHolder.png" ||
+ isPlaceHolderImage(img.getAttribute("src")) ||
children.length === 1
) {
// If there's no image or it's a placeholder, and there are other overlays, it won't be visible,
@@ -7534,3 +7540,28 @@ async function copyAudioFileAsync(
// `DEBUG copyAudioFileAsync: finished copying ${sourcePath} to ${targetPath}`
// );
}
+
+function SetupClickToShowCanvasTool(canvasElement: Element) {
+ // if the user clicks on a canvas element, bring up the canvas tool
+ $(canvasElement).click((ev) => {
+ const toolbox = getToolboxBundleExports()?.getTheOneToolbox();
+ const currentToolId = toolbox?.getCurrentTool()?.id();
+
+ if (
+ toolbox?.toolboxIsShowing() &&
+ (currentToolId === ImageDescriptionAdapter.kToolID ||
+ currentToolId === kTalkingBookToolId)
+ ) {
+ // Image description tool or talking book tool is already open; we don't want to interfere with its functioning
+ return;
+ }
+
+ showCanvasTool();
+ });
+}
+
+export function showCanvasTool() {
+ getToolboxBundleExports()
+ ?.getTheOneToolbox()
+ .activateToolFromId(kCanvasToolId);
+}
diff --git a/src/BloomBrowserUI/bookEdit/js/bloomEditing.ts b/src/BloomBrowserUI/bookEdit/js/bloomEditing.ts
index 45a2f90ff191..e5d6cb54aba0 100644
--- a/src/BloomBrowserUI/bookEdit/js/bloomEditing.ts
+++ b/src/BloomBrowserUI/bookEdit/js/bloomEditing.ts
@@ -430,10 +430,6 @@ export function changeImage(imageInfo: IImageInfo) {
);
}
changeImageInfo(imgOrImageContainer, imageInfo);
- const ancestor = imgOrImageContainer.parentElement?.parentElement;
- if (ancestor) {
- SetupMetadataButton(ancestor);
- }
// id is just a temporary expedient to find the right image easily in this method.
imgOrImageContainer.removeAttribute("id");
theOneCanvasElementManager.updateCanvasElementForChangedImage(
diff --git a/src/BloomBrowserUI/bookEdit/js/bloomImages.ts b/src/BloomBrowserUI/bookEdit/js/bloomImages.ts
index ad9feb5fe615..a3e2bbde20ed 100644
--- a/src/BloomBrowserUI/bookEdit/js/bloomImages.ts
+++ b/src/BloomBrowserUI/bookEdit/js/bloomImages.ts
@@ -33,6 +33,15 @@ const kBrowserDpi = 96;
export const kImageContainerClass = "bloom-imageContainer";
export const kImageContainerSelector = `.${kImageContainerClass}`;
+// We don't use actual placeHolder.png files anymore, but we do continue to use
+// src="placeHolder.png" to mark placeholders
+export function isPlaceHolderImage(url: string | null | undefined): boolean {
+ if (!url) {
+ return false;
+ }
+ return url.toLowerCase().includes("placeholder.png");
+}
+
export function cleanupImages() {
$(".bloom-imageContainer").css("opacity", ""); //comes in on img containers from an old version of myimgscale, and is a major problem if the image is missing
$(".bloom-imageContainer").css("overflow", ""); //review: also comes form myimgscale; is it a problem?
@@ -162,7 +171,7 @@ export function HandleImageError(event: Event) {
export function doImageCommand(
img: HTMLElement | undefined,
- command: "cut" | "copy" | "paste" | "change",
+ command: "copy" | "paste" | "change",
) {
if (!img) {
return;
@@ -493,7 +502,7 @@ async function DetermineImageTooltipAsync(
const targetDpiHeight = Math.ceil(
(300 * containerJQ.height()) / kBrowserDpi,
);
- const isPlaceHolder = url.indexOf("placeHolder.png") > -1;
+ const isPlaceHolder = isPlaceHolderImage(url);
const result = await getWithConfigAsync("image/info", {
params: { image: url },
@@ -645,13 +654,6 @@ function getFileLengthString(bytes): string {
return "";
}
-// IsImageReal returns true if the img tag refers to a non-placeholder image
-// If the image is a placeholder:
-// - we don't want to offer to edit placeholder credits
-function IsImageReal(img) {
- return GetRawImageUrl(img).toLowerCase().indexOf("placeholder") == -1; //don't offer to edit placeholder credits
-}
-
// Gets the src attribute out of images, and the background-image:url() of everything else
function GetRawImageUrl(imgOrDivWithBackgroundImage): string {
if ($(imgOrDivWithBackgroundImage).hasAttr("src")) {
@@ -707,7 +709,7 @@ export function SetupMetadataButton(parent: HTMLElement) {
continue; // pathological
}
const img = getImageFromContainer(container);
- if (!img || !IsImageReal(img)) continue; // placeholder, doesn't get one of these buttons.
+ if (!img || !!isPlaceHolderImage(GetRawImageUrl(img))) continue; // placeholder, doesn't get one of these buttons.
//review: should we also require copyright, illustrator, etc? In many contexts the id of the work-for-hire illustrator isn't available
const copyright = img.getAttribute("data-copyright");
@@ -746,7 +748,14 @@ export function SetupMetadataButton(parent: HTMLElement) {
// Instead of "missing", we want to show it in the right ui language. We also want the text
// to indicate that it might not be missing, just didn't load (this happens on slow machines)
function SetAlternateTextOnImages(element) {
- if (GetRawImageUrl(element).length > 0) {
+ const rawImageUrl = GetRawImageUrl(element);
+ if (rawImageUrl.length > 0) {
+ if (isPlaceHolderImage(rawImageUrl)) {
+ // We now use css to display the placeholder image instead of an actual placeHolder.png file,
+ // but we are continuing to set and use src=placeHolder.png to trigger place holder behavior. So we
+ // don't expect to find a placeHolder.png file, and we don't want to display alt text.
+ return;
+ }
//don't show this on the empty license image when we don't know the license yet
const englishText =
"This picture, {0}, is missing or was loading too slowly."; // Also update HtmlDom.cs::IsPlaceholderImageAltText
diff --git a/src/BloomBrowserUI/bookEdit/js/origami.ts b/src/BloomBrowserUI/bookEdit/js/origami.ts
index 5a1c76b7c1f3..e5bb91a50156 100644
--- a/src/BloomBrowserUI/bookEdit/js/origami.ts
+++ b/src/BloomBrowserUI/bookEdit/js/origami.ts
@@ -10,45 +10,58 @@ import { theOneCanvasElementManager } from "./CanvasElementManager";
import { getFeatureStatusAsync } from "../../react_components/featureStatus";
import $ from "jquery";
import { splitPane } from "../../lib/split-pane/split-pane";
+import { kCanvasToolId } from "../toolbox/toolIds";
$(() => {
splitPane($("div.split-pane"));
});
export function setupOrigami() {
- getFeatureStatusAsync("widget").then((featureStatus) => {
- const isWidgetFeatureEnabled: boolean = featureStatus?.enabled || false;
- const customPages = document.getElementsByClassName("customPage");
- if (customPages.length > 0) {
- const width = customPages[0].clientWidth;
- const origamiControl = getAbovePageControlContainer()
- .append(createTypeSelectors(isWidgetFeatureEnabled))
- .append(createTextBoxIdentifier());
- // The order of this is not important in most ways, since it is positioned absolutely.
- // However, we position the page label, also absolutely, in the same screen area, and
- // we want it on top of origami control, so that in template pages the user can edit it.
- // The page label is part of the page, so we want the page to come after the origami control.
- // (Could also do this with z-order, but I prefer to do what I can by ordering elements,
- // and save z-order for when it is really needed.)
- $("#page-scaling-container").prepend(origamiControl);
- // The container width is set to 100% in the CSS, but we need to
- // limit it to no more than the actual width of the page.
- const toggleContainer = $(".above-page-control-container").get(0);
- toggleContainer.style.maxWidth = width + "px";
- }
- // I'm not clear why the rest of this needs to wait until we have
- // the two results, but none of the controls shows up if we leave it all
- // outside the bloomApi functions.
- $(".origami-toggle .onoffswitch").change(layoutToggleClickHandler);
-
- if ($(".customPage .marginBox.origami-layout-mode").length) {
- setupLayoutMode();
- $("#myonoffswitch").prop("checked", true);
- }
-
- $(".customPage, .above-page-control-container")
- .find("*[data-i18n]")
- .localize();
+ getFeatureStatusAsync("widget").then((widgetFeatureStatus) => {
+ getFeatureStatusAsync("canvas").then((canvasFeatureStatus) => {
+ const isWidgetFeatureEnabled: boolean =
+ widgetFeatureStatus?.enabled || false;
+ const isCanvasFeatureEnabled: boolean =
+ canvasFeatureStatus?.enabled || false;
+ const customPages = document.getElementsByClassName("customPage");
+ if (customPages.length > 0) {
+ const width = customPages[0].clientWidth;
+ const origamiControl = getAbovePageControlContainer()
+ .append(
+ createTypeSelectors(
+ isWidgetFeatureEnabled,
+ isCanvasFeatureEnabled,
+ ),
+ )
+ .append(createTextBoxIdentifier());
+ // The order of this is not important in most ways, since it is positioned absolutely.
+ // However, we position the page label, also absolutely, in the same screen area, and
+ // we want it on top of origami control, so that in template pages the user can edit it.
+ // The page label is part of the page, so we want the page to come after the origami control.
+ // (Could also do this with z-order, but I prefer to do what I can by ordering elements,
+ // and save z-order for when it is really needed.)
+ $("#page-scaling-container").prepend(origamiControl);
+ // The container width is set to 100% in the CSS, but we need to
+ // limit it to no more than the actual width of the page.
+ const toggleContainer = $(".above-page-control-container").get(
+ 0,
+ );
+ toggleContainer.style.maxWidth = width + "px";
+ }
+ // I'm not clear why the rest of this needs to wait until we have
+ // the two results, but none of the controls shows up if we leave it all
+ // outside the bloomApi functions.
+ $(".origami-toggle .onoffswitch").change(layoutToggleClickHandler);
+
+ if ($(".customPage .marginBox.origami-layout-mode").length) {
+ setupLayoutMode();
+ $("#myonoffswitch").prop("checked", true);
+ }
+
+ $(".customPage, .above-page-control-container")
+ .find("*[data-i18n]")
+ .localize();
+ });
});
}
@@ -412,13 +425,17 @@ function getCloseButton() {
}
// N.B. If we ever add a new type, make sure you also modify 'bloomContainerClasses'.
-function createTypeSelectors(includeWidget: boolean) {
+function createTypeSelectors(includeWidget: boolean, includeCanvas: boolean) {
const space = " ";
const links = $("");
- const pictureLink = $(
- "Picture",
+ const imageLink = $(
+ "Image",
);
- pictureLink.click(makePictureFieldClickHandler);
+ imageLink.click(makeImageFieldClickHandler);
+ const canvasLink = $(
+ "Canvas",
+ );
+ canvasLink.click(makeCanvasFieldClickHandler);
const textLink = $(
"Text",
);
@@ -432,13 +449,9 @@ function createTypeSelectors(includeWidget: boolean) {
"HTML Widget",
);
htmlWidgetLink.click(makeHtmlWidgetFieldClickHandler);
- links
- .append(pictureLink)
- .append(",")
- .append(space)
- .append(videoLink)
- .append(",")
- .append(space);
+ links.append(imageLink).append(",").append(space);
+ if (includeCanvas) links.append(canvasLink).append(",");
+ links.append(space).append(videoLink).append(",").append(space);
if (includeWidget) {
links
.append(textLink)
@@ -487,20 +500,31 @@ function makeTextFieldClickHandler(e) {
dialog.AttachToBox(this);
});
}
-function makePictureFieldClickHandler(e) {
- e.preventDefault();
- const container = $(this).closest(".split-pane-component-inner");
+function makeImageOrCanvasFieldClickHandler(
+ clickedElement: JQuery,
+ isCanvasClick: boolean,
+) {
+ const container = clickedElement.closest(".split-pane-component-inner");
addUndoPoint();
const bloomCanvas = $(
- "",
- );
- const image = $(
- "",
+ ``,
);
+ const image = $("");
bloomCanvas.append(image);
SetupImage(image); // Must attach it first so event handler gets added to parent
container.append(bloomCanvas);
- $(this).closest(".selector-links").remove();
+ clickedElement.closest(".selector-links").remove();
+}
+function makeImageFieldClickHandler(e) {
+ e.preventDefault();
+ makeImageOrCanvasFieldClickHandler($(this), false);
+}
+
+function makeCanvasFieldClickHandler(e) {
+ e.preventDefault();
+ makeImageOrCanvasFieldClickHandler($(this), true);
}
function makeVideoFieldClickHandler(e) {
diff --git a/src/BloomBrowserUI/bookEdit/toolbox/games/GameTool.tsx b/src/BloomBrowserUI/bookEdit/toolbox/games/GameTool.tsx
index a29f6a57fe79..ee964b5a0d8a 100644
--- a/src/BloomBrowserUI/bookEdit/toolbox/games/GameTool.tsx
+++ b/src/BloomBrowserUI/bookEdit/toolbox/games/GameTool.tsx
@@ -68,6 +68,7 @@ import { getGameType, isPageBloomGame } from "./GameInfo";
import {
getImageFromCanvasElement,
kImageContainerSelector,
+ isPlaceHolderImage,
} from "../../js/bloomImages";
import { doesContainingPageHaveSameSizeMode } from "./gameUtilities";
import { CanvasSnapProvider } from "../../js/CanvasSnapProvider";
@@ -245,7 +246,7 @@ export const adjustTarget = (
const img = getImageFromCanvasElement(x);
// I don't think we want to increase the minimum size of targets to account
// for images that are still placeholders.
- if (img && img.getAttribute("src") !== "placeholder.png") {
+ if (img && !isPlaceHolderImage(img.getAttribute("src"))) {
draggableImages.push(x as HTMLElement);
} else if (x !== draggable) {
otherDraggables.push(x as HTMLElement);
diff --git a/src/BloomBrowserUI/bookEdit/toolbox/impairmentVisualizer/impairmentVisualizer.tsx b/src/BloomBrowserUI/bookEdit/toolbox/impairmentVisualizer/impairmentVisualizer.tsx
index bd877b1aebb2..ffa48cbaf556 100644
--- a/src/BloomBrowserUI/bookEdit/toolbox/impairmentVisualizer/impairmentVisualizer.tsx
+++ b/src/BloomBrowserUI/bookEdit/toolbox/impairmentVisualizer/impairmentVisualizer.tsx
@@ -8,7 +8,7 @@ import "./impairmentVisualizer.less";
import { RadioGroup } from "../../../react_components/RadioGroup";
import { deuteranopia, tritanopia, achromatopsia } from "color-blind";
import { ToolBottomHelpLink } from "../../../react_components/helpLink";
-import { kImageContainerClass } from "../../js/bloomImages";
+import { kImageContainerClass, isPlaceHolderImage } from "../../js/bloomImages";
import { CanvasElementManager } from "../../js/CanvasElementManager";
import { ThemeProvider } from "@mui/material";
import { ApiCheckbox } from "../../../react_components/ApiCheckbox";
@@ -232,7 +232,7 @@ export class ImpairmentVisualizerControls extends React.Component<
window.setTimeout(() => this.makeColorBlindnessOverlay(img), 100);
return;
}
- if (img.getAttribute("src") === "placeHolder.png") {
+ if (isPlaceHolderImage(img.getAttribute("src"))) {
// I don't think any purpose is served by visualizing what color blindness does to
// a greyscale image that won't show in the real book, and it makes for more
// updates to correctly handle when it shows and hides.
diff --git a/src/BloomBrowserUI/bookEdit/toolbox/motion/motionTool.tsx b/src/BloomBrowserUI/bookEdit/toolbox/motion/motionTool.tsx
index 59d2b9801cd0..c318ff242441 100644
--- a/src/BloomBrowserUI/bookEdit/toolbox/motion/motionTool.tsx
+++ b/src/BloomBrowserUI/bookEdit/toolbox/motion/motionTool.tsx
@@ -16,6 +16,7 @@ import {
DisableImageEditing,
EnableImageEditing,
getBackgroundImageFromBloomCanvas,
+ isPlaceHolderImage,
} from "../../js/bloomImages";
import { kMotionToolId } from "../toolIds";
import { RequiresSubscriptionOverlayWrapper } from "../../../react_components/requiresSubscription";
@@ -497,7 +498,7 @@ export class MotionTool extends ToolboxToolReactAdaptor {
const bgImage = this.getBackgroundImage();
if (bgImage) {
const src = bgImage.getAttribute("src");
- haveBgImage = !!src && src.toLowerCase() !== "placeholder.png";
+ haveBgImage = !!src && !isPlaceHolderImage(src);
}
const newState = this.getStateFromHtml();
if (haveBgImage) {
@@ -569,7 +570,7 @@ export class MotionTool extends ToolboxToolReactAdaptor {
const bgImage = this.getBackgroundImage();
if (bgImage) {
const src = bgImage.getAttribute("src");
- if (!src || src.toLowerCase() === "placeholder.png") {
+ if (!src || isPlaceHolderImage(src)) {
this.hideRectangles();
}
this.observer.observe(bgImage, {
@@ -938,7 +939,7 @@ export class MotionTool extends ToolboxToolReactAdaptor {
src = bgImage?.getAttribute("src") || "";
}
- const doNotHaveAPicture = !src || src.startsWith("placeHolder.png");
+ const doNotHaveAPicture = !src || isPlaceHolderImage(src);
let motionChecked = true;
let motionPossible = !doNotHaveAPicture;
diff --git a/src/BloomBrowserUI/bookEdit/toolbox/talkingBook/audioRecording.ts b/src/BloomBrowserUI/bookEdit/toolbox/talkingBook/audioRecording.ts
index 01a2c45ee0e1..73dec3e7cdc3 100644
--- a/src/BloomBrowserUI/bookEdit/toolbox/talkingBook/audioRecording.ts
+++ b/src/BloomBrowserUI/bookEdit/toolbox/talkingBook/audioRecording.ts
@@ -122,7 +122,7 @@ const kBloomVisibleClass = "bloom-visibility-code-on";
const kAudioSplitId = "audio-split";
-const kTalkingBookToolId = "talkingBook";
+export const kTalkingBookToolId = "talkingBook";
export const kPlaybackOrderContainerClass: string =
"bloom-playbackOrderControlsContainer";
diff --git a/src/BloomBrowserUI/collectionsTab/collectionsTabBookPane/previewMode.less b/src/BloomBrowserUI/collectionsTab/collectionsTabBookPane/previewMode.less
index 4f9e85f509cc..d60753f81f71 100644
--- a/src/BloomBrowserUI/collectionsTab/collectionsTabBookPane/previewMode.less
+++ b/src/BloomBrowserUI/collectionsTab/collectionsTabBookPane/previewMode.less
@@ -1,4 +1,5 @@
@import (reference) "../../bloomUI.less";
+@import "../../placeHolderImages.less";
// This is wanted only for previewing templates, not real books.
// Real books always have a data-l1 attribute added, and the standard basePage rules work for them.
@@ -135,9 +136,11 @@ and as a result, the labels are shifted to this many pixels to the width. This s
}
// when making a PDF or a thumbnail, if no image is chosen, just don't show anything
+// repeated class is necessary to override the data-tool-id="canvas" case in placeHolderImages.less
// note that the *= here is needed in case there is a path, as happens in folio pdfs e.g. ../foo/placheholder.png
-.hidePlaceHolders img[src*="placeHolder.png"] {
- display: none;
+.hidePlaceHolders.hidePlaceHolders.hidePlaceHolders.hidePlaceHolders
+ .bloom-canvas-element:has(img[src*="placeHolder.png"]) {
+ background-image: none;
}
@stripeGrey: #f0f0f0;
diff --git a/src/BloomBrowserUI/images/placeHolder.png b/src/BloomBrowserUI/images/placeHolder.png
new file mode 100644
index 000000000000..b76e98b72faa
Binary files /dev/null and b/src/BloomBrowserUI/images/placeHolder.png differ
diff --git a/src/BloomBrowserUI/placeHolderImages.less b/src/BloomBrowserUI/placeHolderImages.less
new file mode 100644
index 000000000000..f1112edd63cc
--- /dev/null
+++ b/src/BloomBrowserUI/placeHolderImages.less
@@ -0,0 +1,66 @@
+@image-placeholder: url('data:image/svg+xml,');
+@canvas-placeholder: url('data:image/svg+xml,');
+
+.image-placeholder-background() {
+ background-image: @image-placeholder;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: contain;
+
+ // Don't show the default "missing image" icon
+ img[src*="placeHolder.png"] {
+ display: none;
+ }
+}
+
+.bloom-canvas .bloom-canvas-element:has(img[src*="placeHolder.png"]) {
+ .image-placeholder-background();
+}
+
+// When previewing templates in the Collection Tab, and also when in Change Layout mode in the Edit tab,
+// img elements may not be wrapped in canvas-element divs (see BL-15514). Also below.
+.preview:not([data-l1]), // Real books always have a data-l1 attribute added
+.origami-layout-mode {
+ .bloom-canvas:not(:has(.bloom-canvas-element)):has(
+ img[src*="placeHolder.png"]
+ ) {
+ .image-placeholder-background();
+ }
+}
+
+// Use the easel placeholder instead of the usual flower placeholder for the background image whenever there is data-tool-id="canvas"
+// (there are no legacy-style templates that should have the easel icon)
+.bloom-canvas[data-tool-id="canvas"]
+ .bloom-backgroundImage.bloom-canvas-element:has(
+ img[src*="placeHolder.png"]
+ ),
+.origami-layout-mode
+ .bloom-canvas[data-tool-id="canvas"]:not(:has(.bloom-canvas-element)):has(
+ img[src*="placeHolder.png"]
+ ) {
+ background-image: @canvas-placeholder;
+}
+
+// In a bloom-canvas that has canvas elements other than the background image, we want to
+// hide the placeholder except for when the background image is active/selected
+.bloom-canvas.bloom-canvas:has(
+ .bloom-canvas-element:not(.bloom-backgroundImage)
+ ) {
+ .bloom-canvas-element.bloom-backgroundImage:not(
+ [data-bloom-active="true"]
+ ):has(img[src*="placeHolder.png"]) {
+ background-image: none;
+ }
+}
+
+// For template books (made from the Template Starter), we do want to display the placeholder images in the bloompub.
+// Hopefully we don't in any way put the string "placeHolder.png" in the inline style of canvases which should not get it!
+// We need !important here to override the inline style attribute background-image which we normally use to put images in bloompubs
+.bloomPlayer-page.template
+ .bloom-canvas[style*="background-image"][style*="placeHolder.png"] {
+ background-image: @image-placeholder !important;
+
+ &[data-tool-id="canvas"] {
+ background-image: @canvas-placeholder !important;
+ }
+}
diff --git a/src/BloomExe/Book/Book.cs b/src/BloomExe/Book/Book.cs
index ec638899be94..1ac1a2025e8f 100644
--- a/src/BloomExe/Book/Book.cs
+++ b/src/BloomExe/Book/Book.cs
@@ -16,6 +16,7 @@
using Bloom.Collection;
using Bloom.FontProcessing;
using Bloom.History;
+using Bloom.ImageProcessing;
using Bloom.Publish;
using Bloom.SafeXml;
using Bloom.SubscriptionAndFeatures;
@@ -3185,7 +3186,7 @@ private bool NonTrivialImageFileExists(SafeXmlElement image)
var file = imageUrl.PathOnly.NotEncoded;
if (string.IsNullOrEmpty(file))
return false;
- if (file == "placeHolder.png" && !image.HasAttribute("data-license"))
+ if (ImageUtils.IsPlaceholderImageFilename(file) && !image.HasAttribute("data-license"))
return false;
return RobustFile.Exists(Path.Combine(Storage.FolderPath, file));
}
@@ -3427,7 +3428,11 @@ private static void RemoveImageResolutionMessageAndAddMissingImageMessage(HtmlDo
{
var src = img.GetAttribute("src");
var alt = img.GetAttribute("alt");
- if (!String.IsNullOrEmpty(src) && String.IsNullOrEmpty(alt))
+ if (
+ !String.IsNullOrEmpty(src)
+ && String.IsNullOrEmpty(alt)
+ && !ImageUtils.IsPlaceholderImageFilename(src)
+ )
{
var localizedFormatString = LocalizationManager.GetString(
"EditTab.Image.AltMsg",
@@ -5183,6 +5188,12 @@ public string GetCoverImagePathAndElt(out SafeXmlElement coverImgElt)
StoragePageFolder,
ref coverImageFileName
);
+ // We no longer put placeHolder.png files in books (BL-15441) but we still need to detect when the placeholder
+ // is called for, so here we return placeHolder.png instead of null. Callers of this method should handle this special case.
+ if (ImageUtils.IsPlaceholderImageFilename(coverImagePath))
+ {
+ return coverImagePath;
+ }
if (!RobustFile.Exists(coverImagePath))
{
// And the filename might be multiply-HTML encoded.
@@ -5332,7 +5343,7 @@ private bool PageHasImages(SafeXmlElement page)
{
foreach (var img in page.SafeSelectNodes(".//img"))
{
- if (img.GetAttribute("src") != "placeHolder.png")
+ if (!ImageUtils.IsPlaceholderImageFilename(img.GetAttribute("src")))
return true;
}
foreach (
@@ -5343,7 +5354,7 @@ SafeXmlElement div in page.SafeSelectNodes(
{
var imgUrl = HtmlDom.GetImageElementUrl(div).PathOnly.NotEncoded;
// Actually getting a background img url is a good indication that it's one we want.
- if (!string.IsNullOrEmpty(imgUrl) && imgUrl != "placeHolder.png")
+ if (!string.IsNullOrEmpty(imgUrl) && !ImageUtils.IsPlaceholderImageFilename(imgUrl))
return true;
}
return false;
diff --git a/src/BloomExe/Book/BookCompressor.cs b/src/BloomExe/Book/BookCompressor.cs
index b41cc8f2f080..6fb3f84e3a51 100644
--- a/src/BloomExe/Book/BookCompressor.cs
+++ b/src/BloomExe/Book/BookCompressor.cs
@@ -46,7 +46,7 @@ int heightAndWidth
BookThumbNailer.GenerateImageForWeb(book);
var coverImagePath = book.GetCoverImagePath();
- if (coverImagePath == null)
+ if (coverImagePath == null || ImageUtils.IsPlaceholderImageFilename(coverImagePath))
{
var blankImage = Path.Combine(
FileLocationUtilities.DirectoryOfApplicationOrSolution,
diff --git a/src/BloomExe/Book/BookStorage.cs b/src/BloomExe/Book/BookStorage.cs
index 5984f55545fc..5b0244453d4e 100644
--- a/src/BloomExe/Book/BookStorage.cs
+++ b/src/BloomExe/Book/BookStorage.cs
@@ -321,7 +321,7 @@ public static void RemoveLocalOnlyFiles(string folderPath)
/// code for those tasks has no business knowing about that status file. OTOH,
/// some of those functions don't need some of the audio files; but a generic
/// cleanup function like this doesn't know which ones. For now, it just deals
- /// with TC cleanup and with any unused copies of the placeHolder.png image file.
+ /// with TC cleanup and any copies of the placeHolder.png image file left from older versions of Bloom.
///
public static List LocalOnlyFiles(string folderPath)
{
@@ -332,30 +332,17 @@ public static List LocalOnlyFiles(string folderPath)
}
///
- /// Add unused copies of placeHolder.png in bookFolderPath to accumulator.
+ /// Add any copies of the placeHolder.png image file left from older versions of Bloom to accumulator. We don't use them anymore.
///
private static void AddUnusedPlaceholderImages(
string bookFolderPath,
List accumulator
)
{
- // See https://issues.bloomlibrary.org/youtrack/issue/BL-7616 and also
- // https://issues.bloomlibrary.org/youtrack/issue/BL-9479 for what has
- // happened in the past that still can use cleanup in new work.
+ // We now no longer use placeHolder.png file in books (BL-15441)
+ // They may be there from older version of Bloom, remove them.
var placeholders = Directory.GetFiles(bookFolderPath, "placeHolder*.png");
- if (placeholders.Length == 0)
- return;
- var htmlPath = FindBookHtmlInFolder(bookFolderPath);
- if (String.IsNullOrEmpty(htmlPath))
- return; // shouldn't happen, but if it does we'll surely flag it elsewhere
- var htmlContent = RobustFile.ReadAllText(htmlPath);
- foreach (var filepath in placeholders)
- {
- var filename = Path.GetFileName(filepath);
- if (htmlContent.Contains($" src=\"{filename}\""))
- continue; // file is used
- accumulator.Add(filepath);
- }
+ accumulator.AddRange(placeholders);
}
public string PathToExistingHtml
@@ -1276,8 +1263,6 @@ var path in RobustIO
)
)
continue;
- if (filename.ToLowerInvariant() == "placeholder.png")
- continue; // delete unused copies, but keep base placeholder image file even if unused at the moment
imageFiles.Add(Path.GetFileName(GetNormalizedPathForOS(path)));
}
//Remove from that list each image actually in use
@@ -2718,7 +2703,6 @@ public void UpdateSupportFiles()
var supportFilesToUpdate = new List(
new[]
{
- "placeHolder.png",
BookInfo.AppearanceSettings.BasePageCssName,
"previewMode.css",
"origami.css",
diff --git a/src/BloomExe/Book/RuntimeInformationInjector.cs b/src/BloomExe/Book/RuntimeInformationInjector.cs
index fbff3871496f..dd8c9e0d22e2 100644
--- a/src/BloomExe/Book/RuntimeInformationInjector.cs
+++ b/src/BloomExe/Book/RuntimeInformationInjector.cs
@@ -326,7 +326,6 @@ private static void AddHtmlUiStrings(Dictionary d)
"Edit image credits, copyright, & license"
);
AddTranslationToDictionaryUsingKey(d, "EditTab.Image.CopyImage", "Copy image");
- AddTranslationToDictionaryUsingKey(d, "EditTab.Image.CutImage", "Cut image");
// tool tips for style editor
AddTranslationToDictionaryUsingKey(
diff --git a/src/BloomExe/BookThumbNailer.cs b/src/BloomExe/BookThumbNailer.cs
index 8a628b81f685..9df6aa20645a 100644
--- a/src/BloomExe/BookThumbNailer.cs
+++ b/src/BloomExe/BookThumbNailer.cs
@@ -193,7 +193,7 @@ HtmlThumbNailer.ThumbnailOptions options
{
if (string.IsNullOrEmpty(imageSrc))
return false;
- else if (Path.GetFileName(imageSrc) == "placeHolder.png")
+ else if (ImageUtils.IsPlaceholderImageFilename(imageSrc))
{
// Valid examples:
// thumbnail.png
@@ -235,6 +235,16 @@ internal static bool CreateThumbnailOfCoverImage(
if (!Directory.Exists(book.FolderPath))
return false;
var imageSrc = book.GetCoverImagePathAndElt(out SafeXmlElement coverImgElt);
+ // We still use src="placeHolder.png" to indicate no image has been chosen, but we use css rather than a file to display it.
+ // Detect this case and use a placeHolder file to make the thumbnail.
+ // Enhance: skip unnecessary checking and processing below in the case where this is a placeholder image
+ if (!String.IsNullOrEmpty(imageSrc) && ImageUtils.IsPlaceholderImageFilename(imageSrc))
+ {
+ var bloomRoot = FileLocationUtilities.GetDirectoryDistributedWithApplication(
+ BloomFileLocator.BrowserRoot
+ );
+ imageSrc = Path.Combine(bloomRoot, "images", "placeHolder.png");
+ }
if (!IsCoverImageSrcValid(imageSrc, options))
{
Debug.WriteLine(book.StoragePageFolder + " does not have a cover image.");
diff --git a/src/BloomExe/Edit/EditingModel.cs b/src/BloomExe/Edit/EditingModel.cs
index 7bbe2284fb3a..171d419eaafc 100644
--- a/src/BloomExe/Edit/EditingModel.cs
+++ b/src/BloomExe/Edit/EditingModel.cs
@@ -1977,7 +1977,7 @@ public void AdjustPageZoom(int delta)
///
/// Make sure the book folder contains a current version of the video placeholder.
- /// We don't copy this to every book like placeHolder.png, since relatively few books need it,
+ /// We don't copy this to every book, since relatively few books need it,
/// but if it's used it needs to be there so things look right when opened in a browser.
/// I don't think our image deletion code is smart enough to detect that something a CSS
/// file says is needed as a background should not be deleted, so I've just made this
diff --git a/src/BloomExe/Edit/EditingView.cs b/src/BloomExe/Edit/EditingView.cs
index 51faded5c954..7d3f0221051f 100644
--- a/src/BloomExe/Edit/EditingView.cs
+++ b/src/BloomExe/Edit/EditingView.cs
@@ -738,29 +738,6 @@ public void CopyImageMetadataToAllImages(Metadata metadata)
}
}
- public void OnCutImage(string imageId, UrlPathString imageSrc, bool imageIsGif)
- {
- var bookFolderPath = _model.CurrentBook.FolderPath;
-
- if (CopyImageToClipboard(imageSrc, bookFolderPath, imageIsGif)) // returns 'true' if successful
- {
- // Replace current image with placeHolder.png
- // N.B. It is unnecessary to check for the existence of this file, since selecting a book in
- // collection view triggers an automatic book update process that ensures that the file
- // is put there if not already present.
- var path = Path.Combine(bookFolderPath, "placeHolder.png");
- using (var palasoImage = PalasoImage.FromFileRobustly(path))
- {
- _model.ChangePicture(imageId, imageSrc, palasoImage);
- }
- }
- else
- {
- // remove imageId from the element since it's no longer needed
- RemoveUnneededImageId(imageId);
- }
- }
-
public void OnCopyImage(UrlPathString imageSrc, bool imageIsGif)
{
// NB: bloomImages.js contains code that prevents us arriving here
diff --git a/src/BloomExe/ImageProcessing/ImageUtils.cs b/src/BloomExe/ImageProcessing/ImageUtils.cs
index f945fda33ff5..8fff704ba87f 100644
--- a/src/BloomExe/ImageProcessing/ImageUtils.cs
+++ b/src/BloomExe/ImageProcessing/ImageUtils.cs
@@ -257,6 +257,14 @@ public static bool HasJpegExtension(string filename)
);
}
+ ///
+ /// Check whether the given path represents a placeholder image.
+ ///
+ public static bool IsPlaceholderImageFilename(string filename)
+ {
+ return Path.GetFileName(filename)?.ToLowerInvariant() == "placeholder.png";
+ }
+
public static bool AppearsToBePng(PalasoImage imageInfo)
{
if (imageInfo == null || imageInfo.Image == null)
@@ -331,13 +339,11 @@ bool isSameFile
{
//LogMemoryUsage();
- // If we go through all the processing and saving machinations for the placeholder image,
- // we just get more and more placeholders when we cut images (BL-9011).
- // And the normal update process that a book goes through when selecting it (in the Collection tab)
- // for editing ensures that the placeHolder.png file is present in the book.
+ // As of BL-15441, we aren't using real placeHolder image files anymore. But if one is there,
+ // don't go through all the processing and saving machinations for it.
if (
!string.IsNullOrEmpty(imageInfo.OriginalFilePath)
- && imageInfo.OriginalFilePath.ToLowerInvariant().EndsWith("placeholder.png")
+ && IsPlaceholderImageFilename(imageInfo.OriginalFilePath)
)
{
return Path.GetFileName(imageInfo.OriginalFilePath);
@@ -776,7 +782,7 @@ public static bool NeedToShrinkImages(string folderPath)
.ToArray();
foreach (string path in pngFiles)
{
- if (Path.GetFileName(path)?.ToLowerInvariant() == "placeholder.png")
+ if (IsPlaceholderImageFilename(path))
continue;
// Very large PNG files can cause "out of memory" errors here, while making thumbnails,
// and when creating ePUBs or BloomPub books. So, we check for sizes bigger than our
@@ -906,7 +912,7 @@ IProgress progress
progress.ProgressIndicator.PercentCompleted = (int)(
100.0 * (float)completed / (float)totalFileCount
);
- if (Path.GetFileName(path)?.ToLowerInvariant() == "placeholder.png")
+ if (IsPlaceholderImageFilename(path))
{
++completed;
continue;
@@ -963,7 +969,7 @@ IProgress progress
!AppearsToBeJpeg(pi)
&& !IsIndexedAndOpaque(pi.Image)
&& Path.GetFileName(path).ToLowerInvariant() != "thumbnail.png"
- && Path.GetFileName(path).ToLowerInvariant() != "placeholder.png"
+ && !IsPlaceholderImageFilename(path)
)
{
RemoveTransparency(pi, path, progress);
@@ -2049,7 +2055,7 @@ string imageDestFolder
{
var src = img.GetAttribute("src");
// We don't want to crop placeHolder.png, just not display it. (BL-15201)
- if (src == "placeHolder.png")
+ if (IsPlaceholderImageFilename(src))
continue;
var style = img.GetAttribute("style");
if (!SignifiesCropping(style))
@@ -2061,7 +2067,7 @@ string imageDestFolder
foreach (var img in images)
{
var src = img.GetAttribute("src");
- if (src == "placeHolder.png")
+ if (IsPlaceholderImageFilename(src))
continue; // we still don't want to crop placeHolder.png (BL-15229)
var style = img.GetAttribute("style");
var imgContainer = img.ParentNode as SafeXmlElement;
diff --git a/src/BloomExe/Publish/BloomPub/BloomPubMaker.cs b/src/BloomExe/Publish/BloomPub/BloomPubMaker.cs
index 6abebc7afe95..657a7525b355 100644
--- a/src/BloomExe/Publish/BloomPub/BloomPubMaker.cs
+++ b/src/BloomExe/Publish/BloomPub/BloomPubMaker.cs
@@ -8,6 +8,7 @@
using System.Xml;
using Bloom.Book;
using Bloom.FontProcessing;
+using Bloom.ImageProcessing;
using Bloom.Publish.Epub;
using Bloom.SafeXml;
using Bloom.SubscriptionAndFeatures;
@@ -726,9 +727,13 @@ private static void StripImgIfWeCannotFindFile(SafeXmlDocument dom, string bookF
var imgElt in dom.SafeSelectNodes("//img[@src]").Cast().ToArray()
)
{
- var file = UrlPathString
- .CreateFromUrlEncodedString(imgElt.GetAttribute("src"))
- .PathOnly.NotEncoded;
+ // As of BL-15441, we use css rather than real files to display placeholders, but we still mark the img elements with src="placeHolder.png".
+ // Don't strip such img elements here - we want them to persist in template books. For other books we
+ // are already removing them in PublishHelper.RemoveUnwantedContent.
+ string src = imgElt.GetAttribute("src");
+ if (ImageUtils.IsPlaceholderImageFilename(src))
+ continue;
+ var file = UrlPathString.CreateFromUrlEncodedString(src).PathOnly.NotEncoded;
if (!RobustFile.Exists(Path.Combine(folderPath, file)))
{
imgElt.ParentNode.RemoveChild(imgElt);
diff --git a/src/BloomExe/Publish/Epub/EpubMaker.cs b/src/BloomExe/Publish/Epub/EpubMaker.cs
index f45bc140b02d..10f208177f63 100644
--- a/src/BloomExe/Publish/Epub/EpubMaker.cs
+++ b/src/BloomExe/Publish/Epub/EpubMaker.cs
@@ -13,6 +13,7 @@
using Bloom.Api;
using Bloom.Book;
using Bloom.FontProcessing;
+using Bloom.ImageProcessing;
using Bloom.SafeXml;
using Bloom.SubscriptionAndFeatures;
using Bloom.ToPalaso;
@@ -478,7 +479,8 @@ out double height
// If we don't have an epub thumbnail, create a nice large thumbnail of the cover image
// with the desired name. This is a temporary file stored only in the staged book folder
// before being added to the epub.
- if (!RobustFile.Exists(epubThumbnailImagePath))
+ var thumbnailFileExists = RobustFile.Exists(epubThumbnailImagePath);
+ if (!thumbnailFileExists)
{
string coverPageImageFile = "thumbnail-256.png"; // name created by _thumbNailer
ApplicationException thumbNailException = null;
@@ -497,7 +499,7 @@ out double height
return; // especially to avoid reporting problems making thumbnail, e.g., because aborted.
var coverPageImagePath = Path.Combine(Book.FolderPath, coverPageImageFile);
- if (thumbNailException != null || !RobustFile.Exists(coverPageImagePath))
+ if (thumbNailException != null)
{
NonFatalProblem.Report(
ModalIf.All,
@@ -506,25 +508,23 @@ out double height
"We will try to make the book anyway, but you may want to try again.",
thumbNailException
);
-
- coverPageImageFile = "thumbnail.png"; // Try a low-res image, which should always exist
+ }
+ if (thumbNailException != null || !RobustFile.Exists(coverPageImagePath))
+ {
+ coverPageImageFile = "thumbnail.png"; // Try a low-res image
coverPageImagePath = Path.Combine(Book.FolderPath, coverPageImageFile);
- if (!RobustFile.Exists(coverPageImagePath))
- {
- // I don't think we can make an epub without a cover page so at this point we've had it.
- // I suppose we could recover without actually crashing but it doesn't seem worth it unless this
- // actually happens to real users.
- throw new FileNotFoundException(
- "Could not find or create thumbnail for cover page (BL-3209)",
- coverPageImageFile
- );
- }
}
- RobustFile.Move(coverPageImagePath, epubThumbnailImagePath);
+ if (RobustFile.Exists(coverPageImagePath))
+ {
+ RobustFile.Move(coverPageImagePath, epubThumbnailImagePath);
+ thumbnailFileExists = true;
+ }
+ }
+ // Cover image file and therefore thumbnail file may be non-existent simply because no cover image was chosen/it was still the placeHolder, not a problem
+ if (thumbnailFileExists)
+ {
+ CopyFileToEpub(epubThumbnailImagePath, true, true, kImagesFolder, imageSettings);
}
-
- CopyFileToEpub(epubThumbnailImagePath, true, true, kImagesFolder, imageSettings);
-
var warnings = EmbedFonts(progress); // must call after copying stylesheets
if (warnings.Any())
PublishHelper.SendBatchedWarningMessagesToProgress(warnings, progress);
@@ -552,7 +552,11 @@ out double height
"
);
- MakeManifest(kImagesFolder + "/" + Path.GetFileName(epubThumbnailImagePath));
+ MakeManifest(
+ thumbnailFileExists
+ ? kImagesFolder + "/" + Path.GetFileName(epubThumbnailImagePath)
+ : null
+ );
foreach (
var filename in Directory.EnumerateFiles(
@@ -2133,7 +2137,7 @@ SafeXmlElement img in HtmlDom.SelectChildImgAndBackgroundImageElements(pageEleme
{
bool isBrandingFile; // not used here, but part of method signature
var path = FindRealImageFileIfPossible(img, out isBrandingFile);
- if (!String.IsNullOrEmpty(path) && Path.GetFileName(path) != "placeHolder.png") // consider blank if only placeholder image
+ if (!String.IsNullOrEmpty(path) && !ImageUtils.IsPlaceholderImageFilename(path)) // consider blank if only placeholder image
return false;
}
foreach (
diff --git a/src/BloomExe/Publish/PublishHelper.cs b/src/BloomExe/Publish/PublishHelper.cs
index b09afd026a4d..113df979b62a 100644
--- a/src/BloomExe/Publish/PublishHelper.cs
+++ b/src/BloomExe/Publish/PublishHelper.cs
@@ -381,7 +381,7 @@ var div in dom.Body.SafeSelectNodes("//div[@role='textbox']").Cast
c is SafeXmlElement ce && ce.LocalName == "img"
) as SafeXmlElement;
- if (bcImg != null && bcImg.GetAttribute("src") != "placeHolder.png")
+ if (
+ bcImg != null
+ && !ImageUtils.IsPlaceholderImageFilename(bcImg.GetAttribute("src"))
+ )
continue; // paranoia
if (bcImg != null)
{
diff --git a/src/BloomExe/Spreadsheet/SpreadsheetExporter.cs b/src/BloomExe/Spreadsheet/SpreadsheetExporter.cs
index 801cf544dfef..40a3903ab180 100644
--- a/src/BloomExe/Spreadsheet/SpreadsheetExporter.cs
+++ b/src/BloomExe/Spreadsheet/SpreadsheetExporter.cs
@@ -239,7 +239,7 @@ Color colorForPage
);
var fileName = Path.GetFileName(imagePath);
var outputPath = Path.Combine("images", fileName);
- if (fileName == "placeHolder.png")
+ if (ImageUtils.IsPlaceholderImageFilename(fileName))
outputPath = InternalSpreadsheet.BlankContentIndicator;
row.SetCell(InternalSpreadsheet.ImageSourceColumnLabel, outputPath);
CopyImageFileToSpreadsheetFolder(imagePath);
@@ -830,7 +830,7 @@ private void CopyImageFileToSpreadsheetFolder(string imageSourcePath)
{
if (_outputImageFolder != null)
{
- if (Path.GetFileName(imageSourcePath) == "placeHolder.png")
+ if (ImageUtils.IsPlaceholderImageFilename(imageSourcePath))
return; // don't need to copy this around.
if (!RobustFile.Exists(imageSourcePath))
{
diff --git a/src/BloomExe/Spreadsheet/SpreadsheetIO.cs b/src/BloomExe/Spreadsheet/SpreadsheetIO.cs
index ad0697a686f6..b9ef488fc178 100644
--- a/src/BloomExe/Spreadsheet/SpreadsheetIO.cs
+++ b/src/BloomExe/Spreadsheet/SpreadsheetIO.cs
@@ -142,7 +142,11 @@ public static void WriteSpreadsheet(
var imageSrc = sourceCell.Content;
// if this row has an image source value that is not a header
- if (imageSrc != "" && !row.IsHeader)
+ if (
+ imageSrc != ""
+ && !ImageUtils.IsPlaceholderImageFilename(imageSrc)
+ && !row.IsHeader
+ )
{
var sheetFolder = Path.GetDirectoryName(outputPath);
var imagePath = Path.Combine(sheetFolder, imageSrc);
diff --git a/src/BloomExe/Spreadsheet/SpreadsheetImporter.cs b/src/BloomExe/Spreadsheet/SpreadsheetImporter.cs
index 9c6614d7e06c..520714aaa6ba 100644
--- a/src/BloomExe/Spreadsheet/SpreadsheetImporter.cs
+++ b/src/BloomExe/Spreadsheet/SpreadsheetImporter.cs
@@ -852,19 +852,11 @@ SafeXmlElement currentBloomCanvas
// notes about its resolution, etc. We think it will be regenerated as needed, but certainly
// the one from a previous background image is no use.
currentBloomCanvas.RemoveAttribute("title");
- if (_pathToSpreadsheetFolder != null) //currently will only be null in tests
+ if (
+ _pathToSpreadsheetFolder != null //currently will only be null in tests
+ && !ImageUtils.IsPlaceholderImageFilename(spreadsheetImgPath)
+ )
{
- if (spreadsheetImgPath == "placeHolder.png")
- {
- // Don't assume the source has it, let's get a copy from files shipped with Bloom
- fullSpreadsheetPath = Path.Combine(
- BloomFileLocator.FactoryCollectionsDirectory,
- "template books",
- "Basic Book",
- "placeHolder.png"
- );
- }
-
CopyImageFileToDestination(destFileName, fullSpreadsheetPath, imgElement);
}
@@ -1025,7 +1017,10 @@ private async Task UpdateDataDivFromRowAsync(ContentRow currentRow, string dataB
if (templateNodeIsNew)
AddDataBookNode(templateNode);
- if (_pathToSpreadsheetFolder != null)
+ if (
+ _pathToSpreadsheetFolder != null
+ && !ImageUtils.IsPlaceholderImageFilename(imageFileName)
+ )
{
// Make sure the image gets copied over too.
var fullSpreadsheetPath = Path.Combine(_pathToSpreadsheetFolder, imageSrc);
diff --git a/src/BloomExe/web/BloomServer.cs b/src/BloomExe/web/BloomServer.cs
index 8b674a7b6441..7d7dc780f881 100644
--- a/src/BloomExe/web/BloomServer.cs
+++ b/src/BloomExe/web/BloomServer.cs
@@ -746,6 +746,13 @@ private bool ProcessImageFileRequest(IRequestInfo info)
{
imageFile = imageFile.Replace("flat", "icy_orange");
}
+ else if (ImageUtils.IsPlaceholderImageFilename(imageFile))
+ {
+ // We now use css to put in the placeholder images, but still use "placeHolder.png" to mark them
+ // So we actually don't want to provide an image file for placeHolder.png.
+ info.WriteCompleteOutput("");
+ return true;
+ }
// If the user does add a video or widget, these placeholder .svgs will get copied to the
// book folder and used from there. But we don't copy to the book folder while the user
// is still in origami in case the user doesn't actually add the video or widget.
diff --git a/src/BloomExe/web/controllers/BookMetadataApi.cs b/src/BloomExe/web/controllers/BookMetadataApi.cs
index b105484df1d8..ef39dc6fdea8 100644
--- a/src/BloomExe/web/controllers/BookMetadataApi.cs
+++ b/src/BloomExe/web/controllers/BookMetadataApi.cs
@@ -1,7 +1,9 @@
-using System;
+using System;
+using System.IO;
using Bloom.Api;
using Bloom.Book;
using Bloom.CollectionTab;
+using Bloom.ImageProcessing;
using L10NSharp;
namespace Bloom.web.controllers
@@ -51,12 +53,15 @@ private void HandleBookMetadata(ApiRequest request)
var licenseUrl = book.GetLicenseMetadata().License.Url;
if (string.IsNullOrEmpty(licenseUrl))
licenseUrl = null; // allows us to use ?? below.
+ string coverImagePath = book.GetCoverImagePath();
var metadata = new
{
metapicture = new
{
type = "image",
- value = "/bloom/" + book.GetCoverImagePath(),
+ value = ImageUtils.IsPlaceholderImageFilename(coverImagePath)
+ ? ""
+ : "/bloom/" + coverImagePath,
translatedLabel = LocalizationManager.GetString(
"BookMetadata.metapicture",
"Picture"
diff --git a/src/BloomExe/web/controllers/EditingViewApi.cs b/src/BloomExe/web/controllers/EditingViewApi.cs
index a0430fc21b79..b1a5994cec05 100644
--- a/src/BloomExe/web/controllers/EditingViewApi.cs
+++ b/src/BloomExe/web/controllers/EditingViewApi.cs
@@ -75,7 +75,6 @@ public void RegisterWithApiHandler(BloomApiHandler apiHandler)
);
apiHandler.RegisterEndpointHandler("editView/topics", HandleTopics, false);
apiHandler.RegisterEndpointHandler("editView/changeImage", HandleChangeImage, true);
- apiHandler.RegisterEndpointHandler("editView/cutImage", HandleCutImage, true);
apiHandler.RegisterEndpointHandler("editView/copyImage", HandleCopyImage, true);
apiHandler.RegisterEndpointHandler("editView/pasteImage", HandlePasteImage, true);
apiHandler.RegisterEndpointHandler("editView/paste", HandlePaste, true);
@@ -282,17 +281,6 @@ private void HandleCopyImage(ApiRequest request)
request.PostSucceeded();
}
- private void HandleCutImage(ApiRequest request)
- {
- dynamic data = DynamicJson.Parse(request.RequiredPostJson());
- View.OnCutImage(
- data.imageId,
- UrlPathString.CreateFromUrlEncodedString(data.imageSrc),
- data.imageIsGif
- );
- request.PostSucceeded();
- }
-
private void HandleChangeImage(ApiRequest request)
{
dynamic data = DynamicJson.Parse(request.RequiredPostJson());
diff --git a/src/BloomTests/Book/BookStorageTests.cs b/src/BloomTests/Book/BookStorageTests.cs
index 970dc73b96aa..8148b8317e94 100644
--- a/src/BloomTests/Book/BookStorageTests.cs
+++ b/src/BloomTests/Book/BookStorageTests.cs
@@ -760,7 +760,7 @@ public void CleanupUnusedImageFiles_ImageOnlyReferencedInDataDiv_ImageNotRemoved
}
[Test]
- public void CleanupUnusedImageFiles_ThumbnailsAndPlaceholdersNotRemoved()
+ public void CleanupUnusedImageFiles_ThumbnailsNotRemoved()
{
var storage = GetInitialStorageWithCustomHtml(
XmlHtmlConverter.CreateHtmlString(
@@ -769,15 +769,27 @@ public void CleanupUnusedImageFiles_ThumbnailsAndPlaceholdersNotRemoved()
);
var p1 = MakeSamplePngImage(Path.Combine(_folder.Path, "thumbnail.png"));
var p2 = MakeSamplePngImage(Path.Combine(_folder.Path, "thumbnail88.png"));
- var p3 = MakeSamplePngImage(Path.Combine(_folder.Path, "placeholder.png"));
var dropmeTemp = MakeSamplePngImage(Path.Combine(_folder.Path, "dropme.png"));
storage.CleanupUnusedImageFiles();
Assert.IsTrue(File.Exists(p1.Path));
Assert.IsTrue(File.Exists(p2.Path));
- Assert.IsTrue(File.Exists(p3.Path));
Assert.IsFalse(File.Exists(dropmeTemp.Path));
}
+ [Test]
+ public void CleanupUnusedImageFiles_PlaceholderFilesRemoved()
+ {
+ // BL-15441
+ var storage = GetInitialStorageWithCustomHtml(
+ XmlHtmlConverter.CreateHtmlString(
+ "
"
+ )
+ );
+ var placeholder = MakeSamplePngImage(Path.Combine(_folder.Path, "placeholder.png"));
+ storage.CleanupUnusedImageFiles();
+ Assert.IsFalse(File.Exists(placeholder.Path));
+ }
+
[Test]
public void CleanupUnusedImageFiles_UnusedImageIsLocked_NotException()
{
diff --git a/src/BloomTests/Publish/ExportEpubTestsBaseClass.cs b/src/BloomTests/Publish/ExportEpubTestsBaseClass.cs
index 09f422b7922b..6bce38ee79cf 100644
--- a/src/BloomTests/Publish/ExportEpubTestsBaseClass.cs
+++ b/src/BloomTests/Publish/ExportEpubTestsBaseClass.cs
@@ -641,9 +641,6 @@ protected void CheckBasicsInManifest(params string[] imageFiles)
assertThatManifest.HasAtLeastOneMatchForXpath(
"package/manifest/item[@properties='nav']"
);
- assertThatManifest.HasAtLeastOneMatchForXpath(
- "package/manifest/item[@properties='cover-image']"
- );
assertThatManifest.HasAtLeastOneMatchForXpath(
"package/manifest/item[@id='defaultLangStyles' and @href='"
diff --git a/src/BloomTests/Spreadsheet/SpreadsheetAudioTests.cs b/src/BloomTests/Spreadsheet/SpreadsheetAudioTests.cs
index 36a93ed8cf48..c1cb38b995ad 100644
--- a/src/BloomTests/Spreadsheet/SpreadsheetAudioTests.cs
+++ b/src/BloomTests/Spreadsheet/SpreadsheetAudioTests.cs
@@ -149,16 +149,6 @@ protected void Setup(string pagesContent, string titleOverride = null)
);
_exporter = new SpreadsheetExporter(mockLangDisplayNameResolver.Object);
- var placeHolderSource = Path.Combine(
- BloomFileLocator.FactoryCollectionsDirectory,
- "template books",
- "Basic Book",
- "placeHolder.png"
- );
- RobustFile.Copy(
- placeHolderSource,
- Path.Combine(_bookFolder.FolderPath, "placeHolder.png")
- );
_progressSpy = new ProgressSpy();
Sheet = _exporter.ExportToFolder(
diff --git a/src/BloomTests/Spreadsheet/SpreadsheetImagesTests.cs b/src/BloomTests/Spreadsheet/SpreadsheetImagesTests.cs
index 61fdd22babb8..5cceee9b4421 100644
--- a/src/BloomTests/Spreadsheet/SpreadsheetImagesTests.cs
+++ b/src/BloomTests/Spreadsheet/SpreadsheetImagesTests.cs
@@ -209,16 +209,6 @@ var name in new[]
Path.Combine(path, name),
Path.Combine(_bookFolder.FolderPath, name)
);
- var placeHolderSource = Path.Combine(
- BloomFileLocator.FactoryCollectionsDirectory,
- "template books",
- "Basic Book",
- "placeHolder.png"
- );
- RobustFile.Copy(
- placeHolderSource,
- Path.Combine(_bookFolder.FolderPath, "placeHolder.png")
- );
_progressSpy = new ProgressSpy();
_sheetFromExport = _exporter.ExportToFolder(
diff --git a/src/BloomTests/Spreadsheet/SpreadsheetImporterTests.cs b/src/BloomTests/Spreadsheet/SpreadsheetImporterTests.cs
index b294ab63d312..6abed15dd9c0 100644
--- a/src/BloomTests/Spreadsheet/SpreadsheetImporterTests.cs
+++ b/src/BloomTests/Spreadsheet/SpreadsheetImporterTests.cs
@@ -1233,7 +1233,6 @@ public void XMatterUpdatesTitleCorrectlyOnImport()
[TestCase("Othello 199.jpg")]
[TestCase("Mars 3.png")]
[TestCase("Mars 4.png")]
- [TestCase("placeHolder.png")]
public void ImageCopiedToOutput(string fileName)
{
Assert.That(RobustFile.Exists(Path.Combine(_bookFolder.FolderPath, fileName)));
diff --git a/src/BloomVisualRegressionTests/collections/basic/16x9 Landscape/placeHolder.png b/src/BloomVisualRegressionTests/collections/basic/16x9 Landscape/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/BloomVisualRegressionTests/collections/basic/16x9 Landscape/placeHolder.png and /dev/null differ
diff --git a/src/BloomVisualRegressionTests/collections/basic/A5 Portrait/placeHolder.png b/src/BloomVisualRegressionTests/collections/basic/A5 Portrait/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/BloomVisualRegressionTests/collections/basic/A5 Portrait/placeHolder.png and /dev/null differ
diff --git a/src/content/templates/bloom-foundation-mixins.pug b/src/content/templates/bloom-foundation-mixins.pug
index 6428b79c8e64..8553829f62b3 100644
--- a/src/content/templates/bloom-foundation-mixins.pug
+++ b/src/content/templates/bloom-foundation-mixins.pug
@@ -117,7 +117,7 @@ mixin field-version1(placeholder)
mixin image
- requireZeroArguments('image', arguments);
.bloom-canvas&attributes(attributes)
- img(src="placeHolder.png", width=attributes.initialImageWidth, alt='This picture, placeHolder.png, is missing or was loading too slowly.')
+ img(src="placeHolder.png", width=attributes.initialImageWidth)
block
mixin video
diff --git a/src/content/templates/template books/Activity/Activity.pug b/src/content/templates/template books/Activity/Activity.pug
index ea62facc6695..8ebdecf6da61 100644
--- a/src/content/templates/template books/Activity/Activity.pug
+++ b/src/content/templates/template books/Activity/Activity.pug
@@ -60,7 +60,7 @@ html
//- div.bloom-canvas-element.ui-resizable.ui-draggable(data-img-txt="1" data-bubble="{`version`:`1.0`,`style`:`none`,`tails`:[],`level`:1,`backgroundColors`:[`transparent`]}" style="left: 60px; top: 80px; width: 100px; height: 100px; position: absolute;")
//- //- tabindex 0 is necessary for canvas element image containers so they can properly be set as the active canvas element.
//- .bloom-imageContainer(tabindex="0")
- //- img(src="placeHolder.png", width="100px", alt='This picture, placeHolder.png, is missing or was loading too slowly.')
+ //- img(src="placeHolder.png", width="100px")
//- //- a corresponding text box
//- div.bloom-canvas-element.ui-resizable.ui-draggable.bloom-allowAutoShrink.bloom-wordChoice.bloom-activeTextBox(data-txt-img="1" data-bubble="{`version`:`1.0`,`style`:`none`,`tails`:[],`level`:1,`backgroundColors`:[`transparent`]}" style="left: 60px; top: 20px; width: 91px; height: 31px; position: absolute;")
//- .bloom-translationGroup(data-default-languages='auto')
diff --git a/src/content/templates/template books/Activity/placeHolder.png b/src/content/templates/template books/Activity/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/content/templates/template books/Activity/placeHolder.png and /dev/null differ
diff --git a/src/content/templates/template books/Arithmetic Template/placeHolder.png b/src/content/templates/template books/Arithmetic Template/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/content/templates/template books/Arithmetic Template/placeHolder.png and /dev/null differ
diff --git a/src/content/templates/template books/Basic Book/placeHolder.png b/src/content/templates/template books/Basic Book/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/content/templates/template books/Basic Book/placeHolder.png and /dev/null differ
diff --git a/src/content/templates/template books/Big Book/placeHolder.png b/src/content/templates/template books/Big Book/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/content/templates/template books/Big Book/placeHolder.png and /dev/null differ
diff --git a/src/content/templates/template books/Decodable Reader/placeHolder.png b/src/content/templates/template books/Decodable Reader/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/content/templates/template books/Decodable Reader/placeHolder.png and /dev/null differ
diff --git a/src/content/templates/template books/Digital Comic Book/placeHolder.png b/src/content/templates/template books/Digital Comic Book/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/content/templates/template books/Digital Comic Book/placeHolder.png and /dev/null differ
diff --git a/src/content/templates/template books/Games/placeHolder.png b/src/content/templates/template books/Games/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/content/templates/template books/Games/placeHolder.png and /dev/null differ
diff --git a/src/content/templates/template books/Leveled Reader/placeHolder.png b/src/content/templates/template books/Leveled Reader/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/content/templates/template books/Leveled Reader/placeHolder.png and /dev/null differ
diff --git a/src/content/templates/template books/Paper Comic Book/placeHolder.png b/src/content/templates/template books/Paper Comic Book/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/content/templates/template books/Paper Comic Book/placeHolder.png and /dev/null differ
diff --git a/src/content/templates/template books/Picture Dictionary/placeHolder.png b/src/content/templates/template books/Picture Dictionary/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/content/templates/template books/Picture Dictionary/placeHolder.png and /dev/null differ
diff --git a/src/content/templates/template books/Playground/placeHolder.png b/src/content/templates/template books/Playground/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/content/templates/template books/Playground/placeHolder.png and /dev/null differ
diff --git a/src/content/templates/template books/Sign Language/placeHolder.png b/src/content/templates/template books/Sign Language/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/content/templates/template books/Sign Language/placeHolder.png and /dev/null differ
diff --git a/src/content/templates/template books/Template Starter/placeHolder.png b/src/content/templates/template books/Template Starter/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/content/templates/template books/Template Starter/placeHolder.png and /dev/null differ
diff --git a/src/content/templates/template books/UnusedTemplates/Template Maker/placeHolder.png b/src/content/templates/template books/UnusedTemplates/Template Maker/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/content/templates/template books/UnusedTemplates/Template Maker/placeHolder.png and /dev/null differ
diff --git a/src/content/templates/template books/Wall Calendar/Wall Calendar.pug b/src/content/templates/template books/Wall Calendar/Wall Calendar.pug
index 93b5b647cdc0..df5d35d68156 100644
--- a/src/content/templates/template books/Wall Calendar/Wall Calendar.pug
+++ b/src/content/templates/template books/Wall Calendar/Wall Calendar.pug
@@ -18,7 +18,7 @@ mixin field-classOnChild(internalClass)
mixin image-largePage
- requireZeroArguments('image', arguments);
.bloom-canvas(style="opacity: 1; overflow: hidden;")
- img(src="placeHolder.png", width=attributes.initialImageWidth, alt='Could not load the picture')
+ img(src="placeHolder.png", width=attributes.initialImageWidth)
block
html
diff --git a/src/content/templates/template books/Wall Calendar/placeHolder.png b/src/content/templates/template books/Wall Calendar/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/content/templates/template books/Wall Calendar/placeHolder.png and /dev/null differ
diff --git a/src/content/templates/template books/eBook/placeHolder.png b/src/content/templates/template books/eBook/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/content/templates/template books/eBook/placeHolder.png and /dev/null differ
diff --git a/src/content/templates/template books/placeHolder.png b/src/content/templates/template books/placeHolder.png
deleted file mode 100644
index f4ebcbf84861..000000000000
Binary files a/src/content/templates/template books/placeHolder.png and /dev/null differ