Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as React from "react";
import { BloomTooltip } from "../../BloomToolTip";
import { ArrowDropDown, HelpOutline } from "@mui/icons-material";
import { postJson, useApiString } from "../../../utils/bloomApi";
import { TopRightMenuButton, topRightMenuArrowCss } from "./TopRightMenuButton";
import { useL10n } from "../../l10nHooks";

export const HelpMenu: React.FunctionComponent = () => {
const helpText = useL10n("?", "HelpMenu.Help Menu");

const uiLanguage = useApiString("currentUiLanguage", "en");

const showIconOnly =
helpText === "?" || ["en", "fr", "de", "es"].includes(uiLanguage);

const onOpen = () => {
postJson("workspace/topRight/openHelpMenu", {});
};

const button = (
<TopRightMenuButton
text={showIconOnly ? "" : helpText}
onClick={onOpen}
startIcon={showIconOnly ? <HelpOutline /> : undefined}
endIcon={<ArrowDropDown css={topRightMenuArrowCss} />}
hasText={!showIconOnly}
/>
);

if (!showIconOnly) {
return button;
}

return (
<BloomTooltip tip={{ l10nKey: "HelpMenu.Help Menu" }}>
{button}
</BloomTooltip>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { css } from "@emotion/react";
import * as React from "react";
import BloomButton from "../../bloomButton";

interface TopRightMenuButtonProps {
text: string;
onClick: () => void;
startIcon?: React.ReactNode;
endIcon?: React.ReactNode;
hasText?: boolean;
}

export const topRightMenuArrowCss = css`
font-size: 14px !important;
`;

export const TopRightMenuButton: React.FunctionComponent<
TopRightMenuButtonProps
> = (props) => {
return (
<BloomButton
l10nKey=""
alreadyLocalized={true}
enabled={true}
onClick={props.onClick}
startIcon={props.startIcon}
endIcon={props.endIcon}
hasText={props.hasText === undefined ? true : props.hasText}
variant="text"
css={css`
font-size: 12px;
padding-top: 0px;
padding-bottom: 0px;
text-transform: none;
display: inline-flex;
align-items: center;
justify-content: end;
width: 100%;
`}
>
{props.text}
</BloomButton>
);
};
Comment on lines +17 to +44
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use flex-end instead of end for broader compatibility.

Line 37 uses justify-content: end, but the standard flexbox value is flex-end. While modern browsers support end, using flex-end ensures compatibility with older browsers.

🔎 Proposed fix
                 display: inline-flex;
                 align-items: center;
-                justify-content: end;
+                justify-content: flex-end;
                 width: 100%;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const TopRightMenuButton: React.FunctionComponent<
TopRightMenuButtonProps
> = (props) => {
return (
<BloomButton
l10nKey=""
alreadyLocalized={true}
enabled={true}
onClick={props.onClick}
startIcon={props.startIcon}
endIcon={props.endIcon}
hasText={props.hasText === undefined ? true : props.hasText}
variant="text"
css={css`
font-size: 12px;
padding-top: 0px;
padding-bottom: 0px;
text-transform: none;
display: inline-flex;
align-items: center;
justify-content: end;
width: 100%;
`}
>
{props.text}
</BloomButton>
);
};
export const TopRightMenuButton: React.FunctionComponent<
TopRightMenuButtonProps
> = (props) => {
return (
<BloomButton
l10nKey=""
alreadyLocalized={true}
enabled={true}
onClick={props.onClick}
startIcon={props.startIcon}
endIcon={props.endIcon}
hasText={props.hasText === undefined ? true : props.hasText}
variant="text"
css={css`
font-size: 12px;
padding-top: 0px;
padding-bottom: 0px;
text-transform: none;
display: inline-flex;
align-items: center;
justify-content: flex-end;
width: 100%;
`}
>
{props.text}
</BloomButton>
);
};
🤖 Prompt for AI Agents
In
src/BloomBrowserUI/react_components/TopBar/workspaceTopRightControls/TopRightMenuButton.tsx
around lines 17 to 44, the CSS uses justify-content: end which is less
compatible; change that declaration to justify-content: flex-end to use the
standard flexbox value for broader browser support and keep behavior identical.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as React from "react";
import { ArrowDropDown } from "@mui/icons-material";
import { postJson, useApiString } from "../../../utils/bloomApi";
import { TopRightMenuButton, topRightMenuArrowCss } from "./TopRightMenuButton";

export const UiLanguageMenu: React.FunctionComponent = () => {
const label = useApiString("workspace/topRight/uiLanguageLabel", "");

const onOpen = () => {
postJson("workspace/topRight/openLanguageMenu", {});
};

return (
<TopRightMenuButton
text={label}
onClick={onOpen}
endIcon={<ArrowDropDown css={topRightMenuArrowCss} />}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { css } from "@emotion/react";
import * as React from "react";
import { useState } from "react";
import { lightTheme } from "../../../bloomMaterialUITheme";
import { createTheme, ThemeProvider } from "@mui/material/styles";
import { WireUpForWinforms } from "../../../utils/WireUpWinform";
import { CssBaseline } from "@mui/material";
import { ZoomControl } from "./ZoomControl";
import { UiLanguageMenu } from "./UiLanguageMenu";
import { HelpMenu } from "./HelpMenu";
import { kTextOnPurple } from "../../../bloomMaterialUITheme";
import { useSubscribeToWebSocketForStringMessage } from "../../../utils/WebSocketManager";

export const WorkspaceTopRightControls: React.FunctionComponent = () => {
const lightThemeOverride = React.useMemo(
() =>
createTheme(lightTheme, {
components: {
// kTextOnPurple: The background isn't always purple,
// but this matches what the original winforms control was doing.
// Without the override, we get Bloom blue.
MuiButton: {
styleOverrides: {
root: {
color: kTextOnPurple,
fontWeight: "normal",
},
text: {
color: kTextOnPurple,
fontWeight: "normal",
},
},
},
},
}),
[],
);

// Forces a refresh. Currently used for localization changes.
const [generation, setGeneration] = useState(0);
useSubscribeToWebSocketForStringMessage("app", "uiLanguageChanged", () => {
setGeneration((current) => current + 1);
});

return (
<ThemeProvider theme={lightThemeOverride}>
{/* CssBaseline injects MUI's base styles (it sets html/body to the theme typography,
normalizes margins, etc.). Without it, the browser keeps default fonts and spacing,
so our theme's font family/size and resets never reach this control. */}
<CssBaseline />
<div
key={`workspace-top-right-controls-${generation}`}
css={css`
display: flex;
flex-direction: column;
gap: 1px;
align-items: end;
font-size: 12px;

// kTextOnPurple: see comment above
color: ${kTextOnPurple};
button {
color: ${kTextOnPurple};
}
`}
>
{/* This grid keeps the two menu buttons the same width which keeps the down arrows aligned horizontally */}
<div
css={css`
display: grid;
grid-template-columns: 1fr;
grid-auto-rows: auto;
width: max-content;
row-gap: 1px;
`}
>
<UiLanguageMenu />
<HelpMenu />
</div>
<ZoomControl />
</div>
</ThemeProvider>
);
};

WireUpForWinforms(WorkspaceTopRightControls);
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { css } from "@emotion/react";
import * as React from "react";
import { useEffect, useState } from "react";
import BloomButton from "../../bloomButton";
import { get, postJson } from "../../../utils/bloomApi";
import { useSubscribeToWebSocketForObject } from "../../../utils/WebSocketManager";

interface IZoomInfo {
zoom: number;
minZoom: number;
maxZoom: number;
zoomEnabled: boolean;
}

export const ZoomControl: React.FunctionComponent = () => {
const [zoomInfo, setZoomInfo] = useState<IZoomInfo | undefined>(undefined);

// Fetch zoom info once on initial load.
useEffect(() => {
get("workspace/topRight/zoom", (result) => {
const zoomInfo = result.data as IZoomInfo;
setZoomInfo(zoomInfo);
});
}, []);
// Get subsequent updates via WebSocket.
useSubscribeToWebSocketForObject(
"workspaceTopRightControls",
"zoom",
setZoomInfo,
);

const clampZoom = (value: number, current: IZoomInfo) => {
return Math.min(Math.max(value, current.minZoom), current.maxZoom);
};

const applyDelta = (delta: number) => {
if (!zoomInfo) {
return;
}
const clamped = clampZoom(zoomInfo.zoom + delta, zoomInfo);
setZoomInfo({ ...zoomInfo, zoom: clamped });
postJson("workspace/topRight/zoom", { zoom: clamped });
};

if (!zoomInfo || !zoomInfo.zoomEnabled) {
return null;
}

return (
<div
css={css`
display: flex;
align-items: center;
gap: 8px;
`}
>
<PlusMinusButton label="–" onClick={() => applyDelta(-10)} />

<span
css={css`
cursor: default; // Don't want the user to think he can type
`}
>{`${zoomInfo.zoom}%`}</span>

<PlusMinusButton label="+" onClick={() => applyDelta(10)} />
</div>
);
};

interface PlusMinusButtonProps {
label: string;
onClick: () => void;
}

const PlusMinusButton: React.FunctionComponent<PlusMinusButtonProps> = (
props,
) => {
return (
<BloomButton
l10nKey=""
alreadyLocalized={true}
enabled={true}
transparent={true}
hasText={true}
onClick={props.onClick}
css={css`
font-size: 18px;
border: none;
background-color: transparent;
cursor: pointer;
`}
>
{props.label}
</BloomButton>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { bootstrapReactComponent } from "../../../utils/entryPointBootstrap";
import { WorkspaceTopRightControls } from "./WorkspaceTopRightControls";

bootstrapReactComponent(WorkspaceTopRightControls);
2 changes: 2 additions & 0 deletions src/BloomBrowserUI/vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,8 @@ export default defineConfig(async ({ command }) => {
editTopBarControlsBundle: "./bookEdit/topbar/editTopBarControls.tsx",
collectionTopBarControlsBundle:
"./react_components/TopBar/CollectionTopBarControls/CollectionTopBarControls.tsx",
workspaceTopRightControlsBundle:
"./react_components/TopBar/workspaceTopRightControls/workspaceTopRightControls.entry.tsx",
};

// MAIN VITE CONFIGURATION
Expand Down
5 changes: 1 addition & 4 deletions src/BloomExe/CollectionChoosing/OpenCreateCloneControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -342,10 +342,7 @@ public void UpdateUiLanguageMenuSelection()
if (tag.LangTag == Settings.Default.UserInterfaceLanguage)
{
item.Select();
WorkspaceView.UpdateMenuTextToShorterNameOfSelection(
_uiLanguageMenu,
item.Text
);
_uiLanguageMenu.Text = WorkspaceView.GetShortenedLanguageName(item.Text);
return;
}
}
Expand Down
25 changes: 11 additions & 14 deletions src/BloomExe/Edit/EditingView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public partial class EditingView : UserControl, IBloomTabArea, IZoomManager
private Color _disabledToolbarColor = Color.FromArgb(114, 74, 106);
private bool _visible;
private BloomWebSocketServer _webSocketServer;
private ZoomControl _zoomControl;
private ZoomModel _zoomModel;
private PageListApi _pageListApi;
private DateTime? _lastTopBarMenuClosedTime;

Expand Down Expand Up @@ -1827,10 +1827,7 @@ out zoomFloat
)
{
zoomInt = (int)Math.Round(zoomFloat * 10F) * 10;
if (
zoomInt < ZoomControl.kMinimumZoom
|| zoomInt > ZoomControl.kMaximumZoom
)
if (zoomInt < ZoomModel.kMinimumZoom || zoomInt > ZoomModel.kMaximumZoom)
return 100; // bad antique value - normalize to real size.
return zoomInt;
}
Expand All @@ -1850,10 +1847,10 @@ out zoomInt
)
{
// we can't go below 30 (30%), so those must be old floating point values that rounded to an integer.
if (zoomInt < ZoomControl.kMinimumZoom)
if (zoomInt < ZoomModel.kMinimumZoom)
zoomInt = zoomInt * 100;
if (zoomInt > ZoomControl.kMaximumZoom)
return ZoomControl.kMaximumZoom;
if (zoomInt > ZoomModel.kMaximumZoom)
return ZoomModel.kMaximumZoom;
return zoomInt;
}
else
Expand All @@ -1878,20 +1875,20 @@ public void SetZoom(int zoom)

public void AdjustPageZoom(int delta)
{
var currentZoom = _zoomControl.Zoom;
var currentZoom = _zoomModel.Zoom;
if (
delta < 0 && currentZoom <= Bloom.Workspace.ZoomControl.kMinimumZoom
|| delta > 0 && currentZoom >= Bloom.Workspace.ZoomControl.kMaximumZoom
delta < 0 && currentZoom <= ZoomModel.kMinimumZoom
|| delta > 0 && currentZoom >= ZoomModel.kMaximumZoom
)
{
return;
}
_zoomControl.Zoom = currentZoom + delta;
_zoomModel.Zoom = currentZoom + delta;
}

internal void SetZoomControl(ZoomControl zoomCtl)
internal void SetZoomModel(ZoomModel zoomModel)
{
_zoomControl = zoomCtl;
_zoomModel = zoomModel;
}

// intended for use only by the EditingModel
Expand Down
4 changes: 2 additions & 2 deletions src/BloomExe/Wizard/WinForms/WizardPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ private static void CreateUiLanguageMenuButton()

private static void FinishUiLanguageMenuItemClick()
{
ToolStripDropDownItem selectedItem = null;
foreach (ToolStripDropDownItem dropDownItem in s_toolStripDropDownButton.DropDownItems)
ToolStripItem selectedItem = null;
foreach (ToolStripItem dropDownItem in s_toolStripDropDownButton.DropDownItems)
{
if (dropDownItem.Selected)
{
Expand Down
Loading