- {isMissingTheme(theme)
- ? `(Missing) ${getThemeLabel(theme)}`
- : getThemeLabel(theme)}
+
{
+ handleChooseTheme(event);
+ }}
+ inputProps={{
+ name: "style",
+ id: "game-theme-dropdown",
+ }}
+ css={css`
+ flex: 1;
+ // Allow the select to shrink below its content width so a long theme
+ // name truncates (with an ellipsis) instead of pushing the edit button
+ // out of the narrow toolbox.
+ min-width: 0;
+ .MuiSelect-select {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ // While the editor is open the dropdown is disabled (so themes can't be
+ // switched out from under it), but MUI's default disabled styling greys the
+ // text to near-invisible on the dark toolbox. Keep it readable.
+ .MuiSelect-select.Mui-disabled {
+ color: white !important;
+ -webkit-text-fill-color: white !important;
+ }
+ svg.MuiSvgIcon-root {
+ color: white !important;
+ }
+ ul {
+ background-color: ${kOptionPanelBackgroundColor} !important;
+ }
+ fieldset {
+ border-color: rgba(255, 255, 255, 0.5) !important;
+ }
+ `}
+ size="small"
+ >
+ {/* Factory themes first, then a divider, then "New…", then custom themes. */}
+ {factoryThemes.map(renderThemeItem)}
+
+ {/* Not themes: "New…" starts from Blue On White; "Customize…" copies the
+ current theme. Both open the editor on a new, unsaved theme. */}
+
+
- ))}
-
+ {customThemes.map(renderThemeItem)}
+
+ {/* Non-developers can't change factory themes, so don't offer the edit button
+ for them on a factory theme (they can still use "New…" to make a copy). */}
+ {(isDeveloper || !currentThemeIsFactory) && (
+
showGameThemeEditor()}
+ title={editThemeColorsTitle}
+ css={css`
+ color: white !important;
+ margin-left: 4px;
+ flex-shrink: 0;
+ `}
+ >
+
+
+ )}
+
);
};
diff --git a/src/BloomBrowserUI/bookEdit/toolbox/games/gameThemeEditorHost.ts b/src/BloomBrowserUI/bookEdit/toolbox/games/gameThemeEditorHost.ts
new file mode 100644
index 000000000000..1f88b66b2315
--- /dev/null
+++ b/src/BloomBrowserUI/bookEdit/toolbox/games/gameThemeEditorHost.ts
@@ -0,0 +1,512 @@
+// Bloom-side host for the self-contained game theme editor project (src/gameThemeEditor).
+//
+// This runs in the TOOLBOX iframe (which is served live by vite dev), and reaches the live
+// editable page cross-frame via ToolBox.getPage() — exactly the pattern ThemeChooser already
+// uses to apply theme classes. It mounts the editor panel into a bloom-ui container inside the
+// editable-page document so the panel floats over the real game and can recolor it in real time.
+//
+// We deliberately do NOT route through the page-frame bundle (editablePageBundle): on this
+// branch that bundle is not served by vite dev (see ViteReadMe.txt), so a page-frame export
+// would be stale/missing during development. Driving from the toolbox works in dev and prod.
+//
+// Coupling to the editor is one-directional: we import its mount/unmount + types + the variable
+// name list, and we hand it a concrete IGameThemeEditorHost. The editor imports nothing from Bloom.
+
+import { mount, unmount, themeVariableNames } from "gameThemeEditor";
+import type { IGameThemeEditorHost, Theme } from "gameThemeEditor";
+import { getAsync, postJsonAsync } from "../../../utils/bloomApi";
+import { ToolBox } from "../toolbox";
+
+const kGameThemePrefix = "game-theme-";
+const kEditorContainerClass = "bloom-ui-game-theme-editor";
+// Live-preview