Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions DistFiles/localization/en/BloomMediumPriority.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,25 @@
<source xml:lang="en">Drag any of these onto a canvas:</source>
<note>ID: EditTab.Toolbox.CanvasTool.DragInstructions2</note>
</trans-unit>
<trans-unit id="EditTab.Toolbox.CanvasTool.ImageFit" sil:dynamic="true" translate="no">
<source xml:lang="en">Image Fit</source>
<note>ID: EditTab.Toolbox.CanvasTool.ImageFit</note>
</trans-unit>
<trans-unit id="EditTab.Toolbox.CanvasTool.ImageFit.Margin" sil:dynamic="true" translate="no">
<source xml:lang="en">Fit with Margin</source>
<note>ID: EditTab.Toolbox.CanvasTool.ImageFit.Margin</note>
<note>Leave a margin around the image within the button.</note>
</trans-unit>
<trans-unit id="EditTab.Toolbox.CanvasTool.ImageFit.FitToEdge" sil:dynamic="true" translate="no">
<source xml:lang="en">Fit to Edge</source>
<note>ID: EditTab.Toolbox.CanvasTool.ImageFit.FitToEdge</note>
<note>Fill up the button with the image, leaving a margin on the sides as needed.</note>
</trans-unit>
<trans-unit id="EditTab.Toolbox.CanvasTool.ImageFit.Fill" sil:dynamic="true" translate="no">
<source xml:lang="en">Fill</source>
<note>Fill up the button with the image, leaving no margin, cropping if necessary</note>
<note>ID: EditTab.Toolbox.CanvasTool.ImageFit.Fill</note>
</trans-unit>
<trans-unit id="EditTab.Toolbox.CanvasTool.LinkGrid.ChooseBooks" sil:dynamic="true" translate="no">
<source xml:lang="en">Choose books...</source>
<note>ID: EditTab.Toolbox.CanvasTool.LinkGrid.ChooseBooks</note>
Expand Down
13 changes: 13 additions & 0 deletions src/BloomBrowserUI/bookEdit/js/CanvasElementManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ import {
kBloomCanvasClass,
kBloomCanvasSelector,
kBloomButtonClass,
kImageFitModeAttribute,
kImageFitModeContainValue,
kImageFitModeCoverValue,
} from "../toolbox/canvas/canvasElementUtils";
import OverflowChecker from "../OverflowChecker/OverflowChecker";
import theOneLocalizationManager from "../../lib/localizationManager/localizationManager";
Expand Down Expand Up @@ -5835,6 +5838,16 @@ export class CanvasElementManager {
patriarchDuplicateElement.classList.add("bloom-gif");
if (sourceElement.classList.contains(kBloomButtonClass))
patriarchDuplicateElement.classList.add(kBloomButtonClass);
const imageFitMode = sourceElement.getAttribute(kImageFitModeAttribute);
if (
imageFitMode === kImageFitModeCoverValue ||
imageFitMode === kImageFitModeContainValue
) {
patriarchDuplicateElement.setAttribute(
kImageFitModeAttribute,
imageFitMode,
);
}

// copy any data-sound
const sourceDataSound = sourceElement.getAttribute("data-sound");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export const kHasCanvasElementClass = "bloom-has-canvas-element";
export const kBloomCanvasClass = "bloom-canvas";
export const kBloomCanvasSelector = `.${kBloomCanvasClass}`;
export const kBloomButtonClass = "bloom-canvas-button";
export const kImageFitModeAttribute = "data-image-fit";
export const kImageFitModeContainValue = "contain";
export const kImageFitModeCoverValue = "cover";

// Enhance: we could reduce cross-bundle dependencies by separately defining the CanvasElementManager interface
// and just importing that here.
Expand Down
110 changes: 106 additions & 4 deletions src/BloomBrowserUI/bookEdit/toolbox/canvas/canvasTool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ import {
import {
getCanvasElementManager,
kBloomButtonClass,
kImageFitModeAttribute,
kImageFitModeContainValue,
kImageFitModeCoverValue,
} from "./canvasElementUtils";
import { deselectVideoContainers } from "../../js/videoUtils";
import { CanvasElementKeyHints } from "./CanvasElementKeyHints";
Expand All @@ -63,6 +66,23 @@ import { TriangleCollapse } from "../../../react_components/TriangleCollapse";
import { BloomTooltip } from "../../../react_components/BloomToolTip";
import { text } from "stream/consumers";

const kImageFillModePaddedValue = "padded";
type ImageFillMode =
| typeof kImageFillModePaddedValue
| typeof kImageFitModeContainValue
| typeof kImageFitModeCoverValue;

const getImageFillModeForElement = (element: HTMLElement): ImageFillMode => {
const currentFillMode = element.getAttribute(kImageFitModeAttribute);
if (
currentFillMode === kImageFitModeContainValue ||
currentFillMode === kImageFitModeCoverValue
) {
return currentFillMode;
}
return kImageFillModePaddedValue;
};

const CanvasToolControls: React.FunctionComponent = () => {
const l10nPrefix = "ColorPicker.";
type CanvasElementType = "text" | "image" | "video" | undefined;
Expand All @@ -77,6 +97,9 @@ const CanvasToolControls: React.FunctionComponent = () => {
const [showTailChecked, setShowTailChecked] = useState(false);
const [isRoundedCornersChecked, setIsRoundedCornersChecked] =
useState(false);
const [imageFillMode, setImageFillMode] = useState<ImageFillMode>(
kImageFillModePaddedValue,
);
const [isXmatter, setIsXmatter] = useState(true);
// This 'counter' increments on new page ready so we can re-check if the book is locked.
const [pageRefreshIndicator, setPageRefreshIndicator] = useState(0);
Expand All @@ -85,16 +108,22 @@ const CanvasToolControls: React.FunctionComponent = () => {
const [isStyleSelectOpen, setIsStyleSelectOpen] = useState(false);
const [isOutlineColorSelectOpen, setIsOutlineColorSelectOpen] =
useState(false);
function openStyleSelect() {
const [isImageFillSelectOpen, setIsImageFillSelectOpen] = useState(false);
const openStyleSelect = () => {
setIsStyleSelectOpen(true);
// Make sure we don't leave the select open when the tool closes.
callWhenFocusLost(() => setIsStyleSelectOpen(false));
}
function openOutlineColorSelect() {
};
const openOutlineColorSelect = () => {
setIsOutlineColorSelectOpen(true);
// Make sure we don't leave the select open when the tool closes.
callWhenFocusLost(() => setIsOutlineColorSelectOpen(false));
}
};
const openImageFillSelect = () => {
setIsImageFillSelectOpen(true);
// Make sure we don't leave the select open when the tool closes.
callWhenFocusLost(() => setIsImageFillSelectOpen(false));
};

// Calls to useL10n
const deleteTooltip = useL10n("Delete", "Common.Delete");
Expand Down Expand Up @@ -205,6 +234,7 @@ const CanvasToolControls: React.FunctionComponent = () => {

const canvasElementManager = getCanvasElementManager();
setCanvasElementType(getBubbleType(canvasElementManager));
setImageFillMode(getImageFillModeForElement(currentBubble.content));
if (canvasElementManager) {
// Get the current canvas element's textColor and set it
const canvasElementTextColorInformation: ITextColorInfo =
Expand All @@ -219,6 +249,7 @@ const CanvasToolControls: React.FunctionComponent = () => {
}
} else {
setCanvasElementType(undefined);
setImageFillMode(kImageFillModePaddedValue);
}
}, [currentBubble]);

Expand Down Expand Up @@ -436,6 +467,27 @@ const CanvasToolControls: React.FunctionComponent = () => {
const bubble = canvasElementManager.getPatriarchBubbleOfActiveElement();
setCurrentBubble(bubble);
};
const handleImageFillChanged = (event) => {
const newMode = event.target.value as ImageFillMode;
setImageFillMode(newMode);
const activeElement = getCanvasElementManager()?.getActiveElement();
if (!activeElement) {
return;
}
if (newMode === kImageFitModeCoverValue) {
activeElement.setAttribute(
kImageFitModeAttribute,
kImageFitModeCoverValue,
);
} else if (newMode === kImageFitModeContainValue) {
activeElement.setAttribute(
kImageFitModeAttribute,
kImageFitModeContainValue,
);
} else {
activeElement.removeAttribute(kImageFitModeAttribute);
}
};

// Callback when outline color of the bubble is changed
const handleOutlineColorChanged = (event) => {
Expand Down Expand Up @@ -576,6 +628,52 @@ const CanvasToolControls: React.FunctionComponent = () => {
/>
</FormControl>
);
const imageFillControl = (
<FormControl variant="standard">
<InputLabel htmlFor="image-fill-mode-dropdown">
<Span l10nKey="EditTab.Toolbox.CanvasTool.ImageFit">
Image Fit
</Span>
</InputLabel>
<ThemeProvider theme={toolboxMenuPopupTheme}>
<Select
variant="standard"
value={imageFillMode}
open={isImageFillSelectOpen}
onOpen={openImageFillSelect}
onClose={() => setIsImageFillSelectOpen(false)}
onChange={(event) => {
handleImageFillChanged(event);
setIsImageFillSelectOpen(false);
}}
className="canvasElementOptionDropdown"
inputProps={{
name: "imageFillMode",
id: "image-fill-mode-dropdown",
}}
MenuProps={{
className: "canvasElement-options-dropdown-menu",
}}
>
<MenuItem value={kImageFillModePaddedValue}>
<Div l10nKey="EditTab.Toolbox.CanvasTool.ImageFit.Margin">
Fit with Margin
</Div>
</MenuItem>
<MenuItem value={kImageFitModeContainValue}>
<Div l10nKey="EditTab.Toolbox.CanvasTool.ImageFit.FitToEdge">
Fit to Edge
</Div>
</MenuItem>
<MenuItem value={kImageFitModeCoverValue}>
<Div l10nKey="EditTab.Toolbox.CanvasTool.ImageFit.Fill">
Fill
</Div>
</MenuItem>
</Select>
</ThemeProvider>
</FormControl>
);
const textColorControl = (
<FormControl variant="standard">
<InputLabel htmlFor="text-color-bar" shrink={true}>
Expand All @@ -595,6 +693,9 @@ const CanvasToolControls: React.FunctionComponent = () => {
const activeElement = canvasElementManager?.getActiveElement();
const isButton =
activeElement?.classList.contains(kBloomButtonClass) ?? false;
const hasImage =
(activeElement?.getElementsByClassName("bloom-imageContainer")
?.length ?? 0) > 0;
const hasText =
(activeElement?.getElementsByClassName("bloom-translationGroup")
?.length ?? 0) > 0;
Expand Down Expand Up @@ -625,6 +726,7 @@ const CanvasToolControls: React.FunctionComponent = () => {
<>
{hasText && textColorControl}
{backgroundColorControl}
{hasImage && imageFillControl}
</>
);
switch (canvasElementType) {
Expand Down
48 changes: 48 additions & 0 deletions src/content/bookLayout/canvasElement.less
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,54 @@
}
}

.bloom-page .bloom-canvas-button[data-image-fit="contain"],
.bloom-page .bloom-canvas-button[data-image-fit="cover"] {
padding: 0;
overflow: hidden;
}

.bloom-page
.bloom-canvas-button[data-image-fit="contain"]
.bloom-imageContainer,
.bloom-page .bloom-canvas-button[data-image-fit="cover"] .bloom-imageContainer {
img,
video {
width: 100%;
height: 100%;
max-width: none;
max-height: none;
}
}

.bloom-page
.bloom-canvas-button[data-image-fit="contain"]
.bloom-imageContainer {
img,
video {
object-fit: contain;
}
}

.bloom-page .bloom-canvas-button[data-image-fit="cover"] .bloom-imageContainer {
img,
video {
object-fit: cover;
}
}

// In contain/cover modes, move padding from the button to the text group so
// the image can reach the edge while labels keep their spacing.
.bloom-page
.bloom-canvas-button:is(
[data-image-fit="contain"],
[data-image-fit="cover"]
):has(> .bloom-translationGroup)
.bloom-translationGroup {
padding: 0.6em;
width: 100%;
box-sizing: border-box;
}

.bloom-page .bloom-canvas-button {
display: inline-flex;
// for when it has both image and text
Expand Down