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 @@ Picture ID: EditTab.CustomPage.Picture + Obsolete as of 6.3 + + + Image + ID: EditTab.CustomPage.Image + + + Canvas + ID: EditTab.CustomPage.Canvas Text @@ -1871,6 +1880,7 @@ Cut image ID: EditTab.Image.CutImage + Obsolete as of Bloom 6.3 Edit 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 @@ - - - - - - - - image/svg+xml - - - - - - - - - - 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 = $( - "Could not load the picture", + `
`, ); + 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