Conversation
There was a problem hiding this comment.
Pull request overview
This PR implements a Page Settings dialog (BL-15642) that allows users to customize per-page appearance properties including background color, page number color, and page number background color. The settings are stored as CSS custom properties on the page element's inline style attribute.
Key Changes:
- Added a new Page Settings dialog accessible via a settings button in the page editing interface
- Implemented backend support for saving page-level CSS custom properties
- Restructured appearance theme CSS to allow page-level overrides of page number styling
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| yarn.lock | Added empty yarn.lock file (autogenerated file header only) |
| src/content/bookLayout/pageNumbers.less | Added support for --pageNumber-color CSS variable and included commented-out experimental code for automatic page number background matching |
| src/content/appearanceThemes/appearance-theme-zero-margin-ebook.css | Moved --pageNumber-background-color declaration to allow page settings to override it |
| src/content/appearanceThemes/appearance-theme-rounded-border-ebook.css | Moved --pageNumber-background-color declaration to allow page settings to override it |
| src/content/appearanceThemes/appearance-theme-default.css | Added default --pageNumber-color variable definition |
| src/content/appearanceMigrations/efl-zeromargin1/customBookStyles.css | Changed page number background from white to transparent for migration compatibility |
| src/BloomExe/web/controllers/EditingViewApi.cs | Added API endpoint handler for showing page settings dialog |
| src/BloomExe/Edit/EditingView.cs | Added SaveAndOpenPageSettingsDialog method and click handler |
| src/BloomExe/Book/HtmlDom.cs | Added logic to save/restore page-level inline style attributes |
| src/BloomBrowserUI/bookEdit/pageSettings/PageSettingsDialog.tsx | New component implementing the page settings dialog with color pickers for page and page number customization |
| src/BloomBrowserUI/bookEdit/js/origami.ts | Added page settings button to the above-page control container and wired up click handler |
| src/BloomBrowserUI/bookEdit/editViewFrame.ts | Exported showPageSettingsDialog function for use across the application |
| src/BloomBrowserUI/bookEdit/css/origamiEditing.less | Added styling for page settings button and changed container layout to space-between |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const setCurrentPageBackgroundColor = (color: string): void => { | ||
| const page = getCurrentPageElement(); | ||
| page.style.setProperty("--page-background-color", color); | ||
| page.style.setProperty("--marginBox-background-color", color); | ||
| }; | ||
|
|
||
| const getPageNumberColor = (): string => { | ||
| const page = getCurrentPageElement(); | ||
|
|
||
| const inline = normalizeToHexOrEmpty( | ||
| page.style.getPropertyValue("--pageNumber-color"), | ||
| ); | ||
| if (inline) return inline; | ||
|
|
||
| const computed = normalizeToHexOrEmpty( | ||
| getComputedStyleForPage(page).getPropertyValue("--pageNumber-color"), | ||
| ); | ||
| return computed || "#000000"; | ||
| }; | ||
|
|
||
| const setPageNumberColor = (color: string): void => { | ||
| const page = getCurrentPageElement(); | ||
| page.style.setProperty("--pageNumber-color", color); | ||
| }; | ||
|
|
||
| const getPageNumberBackgroundColor = (): string => { | ||
| const page = getCurrentPageElement(); | ||
|
|
||
| const inline = normalizeToHexOrEmpty( | ||
| page.style.getPropertyValue("--pageNumber-background-color"), | ||
| ); | ||
| if (inline) return inline; | ||
|
|
||
| const computed = normalizeToHexOrEmpty( | ||
| getComputedStyleForPage(page).getPropertyValue( | ||
| "--pageNumber-background-color", | ||
| ), | ||
| ); | ||
| return computed || ""; | ||
| }; | ||
|
|
||
| const setPageNumberBackgroundColor = (color: string): void => { | ||
| const page = getCurrentPageElement(); | ||
| page.style.setProperty("--pageNumber-background-color", color); | ||
| }; |
There was a problem hiding this comment.
When setting CSS custom properties to empty strings, consider using removeProperty instead of setProperty with an empty value. This is more explicit and ensures the property is actually removed from the inline style, allowing computed styles and CSS rules to take effect properly.
For example, when a color is empty or transparent (normalized to ""), the current code calls setProperty("--page-background-color", ""). According to the pattern used elsewhere in the codebase (e.g., StyleEditor.ts line 683), the preferred approach is to use removeProperty when clearing a value.
Consider checking if the color is empty and using:
page.style.removeProperty("--page-background-color")when color is emptypage.style.setProperty("--page-background-color", color)when color has a value
This applies to all three setter functions: setCurrentPageBackgroundColor, setPageNumberColor, and setPageNumberBackgroundColor.
There was a problem hiding this comment.
Seems to be done in SetOrRemoveCustomProperty?
JohnThomson
left a comment
There was a problem hiding this comment.
@JohnThomson reviewed 15 files and all commit messages, and made 13 comments.
Reviewable status: all files reviewed, 13 unresolved discussions (waiting on @hatton).
a discussion (no related file):
Some of my comments are high-level structural things, and this may not be the place to address them. But you've asked me to look for places code quality and architecture could be improved.
Most of the rest are not of great importance, but you should definitely consider whether you want this button on Game pages and if so whether it successfully coexists with the Game controls.
src/content/appearanceThemes/appearance-theme-rounded-border-ebook.css line 31 at r2 (raw file):
[class*="Device"].numberedPage:not(.bloom-interactive-page) { --pageNumber-extra-height: 0mm !important; /* we put the page number on top of the image so we don't need a margin boost */ --pageNumber-background-color: #ffffff; /* I'm not clear why this is white, but all I did in this change is to move it so that it can be overridden by page settings */
Possibly because it's otherwise transparent, and this theme often puts it on top of an image, where it might not be legible without a background color?
src/BloomBrowserUI/react_components/color-picking/colorPickerDialog.tsx line 289 at r2 (raw file):
// The MUI backdrop is rendered outside the dialog tree, so we use a body class // to suppress it while the color picker is open.
Seems to me to need explanation: why do we want to suppress the backdrop for this dialog?
src/BloomBrowserUI/bookEdit/pageSettings/PageSettingsDialog.tsx line 31 at r2 (raw file):
import tinycolor from "tinycolor2"; let isOpenAlready = false;
Just isOpen? or isPageSettingsDialogOpen? The code here seems to entirely manage this variable, whereas 'already' suggests that something else might have caused it to be open before this code got started.
src/BloomBrowserUI/bookEdit/pageSettings/PageSettingsDialog.tsx line 41 at r2 (raw file):
}; const getCurrentPageElement = (): HTMLElement => {
There's probably an existing function (quite likely more than one) that you could import to do this.
src/BloomBrowserUI/bookEdit/pageSettings/PageSettingsDialog.tsx line 73 at r2 (raw file):
const getComputedStyleForPage = (page: HTMLElement): CSSStyleDeclaration => { const view = page.ownerDocument.defaultView;
Deserves a comment. Why would a page not have an ownerDocument? Why would it give a more useful answer for getComputedStyle()?
src/BloomBrowserUI/bookEdit/pageSettings/PageSettingsDialog.tsx line 162 at r2 (raw file):
"--pageNumber-background-color", ), );
Should it have the same special cases as getCurrentPageBackgroundColor? Or better, just call that? If not, it might be helpful to explain why.
src/BloomBrowserUI/bookEdit/pageSettings/PageSettingsDialog.tsx line 390 at r2 (raw file):
applyPageSettings(settings); return; }
It feels wrong that we should have to handle two different kinds of argument in onChange. Maybe there's a good reason Configr is implemented that way, but I think it would be better to make it return one or the other. Maybe ConfigrPane could take a that tells it what "s" should be and could parse the string itself if need be? Or at least there could be a wrapper like that so every client doesn't have to do what you are doing here.
src/BloomBrowserUI/bookEdit/pageSettings/PageSettingsDialog.tsx line 422 at r2 (raw file):
} disabled={false} />
The structure feels wrong here. The knowledge that each control is a ConfigrCustomStringInput wrapping a ColorDisplayButton is both duplicated three times and split between this code and the individul functions, and the knowledge of how to get each label is used twice each. Consider making a single function that combines a ConfigrCustomStringInput and a nested ColorDisplayButton, with just enough props to implement the three variations, including looking up the label. You could then either call that three times here, or define the three methods each to call it with appropriate arguments, and here just call the three functions.
src/BloomBrowserUI/bookEdit/js/origami.ts line 360 at r2 (raw file):
${getPageSettingsButtonHtml()}\ </div>`, );
Do you want this button in Game pages, which is where the previously empty version is used? If so this probably needs attention, since I think the Games code uses this whole element to render the Start/Correct/Wrong/Play control.
(Maybe it's time to re-implement the origami on/off switch in React, and then we could have a single react control responsible for the whole decision about what to show in this space?)
src/BloomBrowserUI/bookEdit/js/origami.ts line 391 at r2 (raw file):
function pageSettingsButtonClickHandler(e: Event) { e.preventDefault(); post("editView/showPageSettingsDialog");
It looks like we go through C# here just so we can save the page first. Is that essential? I would think it would be better to save afterwards, if this action even calls for a Save, so the page thumbnail can update, especially if the background color changed.
src/BloomExe/Edit/EditingView.cs line 1928 at r2 (raw file):
} private void _pageSettingsButton_Click(object sender, EventArgs e)
not used? Maybe there was a C# button in early testing?
| const setCurrentPageBackgroundColor = (color: string): void => { | ||
| const page = getCurrentPageElement(); | ||
| page.style.setProperty("--page-background-color", color); | ||
| page.style.setProperty("--marginBox-background-color", color); | ||
| }; | ||
|
|
||
| const getPageNumberColor = (): string => { | ||
| const page = getCurrentPageElement(); | ||
|
|
||
| const inline = normalizeToHexOrEmpty( | ||
| page.style.getPropertyValue("--pageNumber-color"), | ||
| ); | ||
| if (inline) return inline; | ||
|
|
||
| const computed = normalizeToHexOrEmpty( | ||
| getComputedStyleForPage(page).getPropertyValue("--pageNumber-color"), | ||
| ); | ||
| return computed || "#000000"; | ||
| }; | ||
|
|
||
| const setPageNumberColor = (color: string): void => { | ||
| const page = getCurrentPageElement(); | ||
| page.style.setProperty("--pageNumber-color", color); | ||
| }; | ||
|
|
||
| const getPageNumberBackgroundColor = (): string => { | ||
| const page = getCurrentPageElement(); | ||
|
|
||
| const inline = normalizeToHexOrEmpty( | ||
| page.style.getPropertyValue("--pageNumber-background-color"), | ||
| ); | ||
| if (inline) return inline; | ||
|
|
||
| const computed = normalizeToHexOrEmpty( | ||
| getComputedStyleForPage(page).getPropertyValue( | ||
| "--pageNumber-background-color", | ||
| ), | ||
| ); | ||
| return computed || ""; | ||
| }; | ||
|
|
||
| const setPageNumberBackgroundColor = (color: string): void => { | ||
| const page = getCurrentPageElement(); | ||
| page.style.setProperty("--pageNumber-background-color", color); | ||
| }; |
There was a problem hiding this comment.
Seems to be done in SetOrRemoveCustomProperty?
This change is