diff --git a/apps/client/src/components/app_context.ts b/apps/client/src/components/app_context.ts index 1855876d36..b646780119 100644 --- a/apps/client/src/components/app_context.ts +++ b/apps/client/src/components/app_context.ts @@ -283,6 +283,9 @@ export type CommandMappings = { type EventMappings = { initialRenderComplete: {}; frocaReloaded: {}; + setLeftPaneVisibility: { + leftPaneVisible: boolean | null; + } protectedSessionStarted: {}; notesReloaded: { noteIds: string[]; diff --git a/apps/client/src/components/root_command_executor.ts b/apps/client/src/components/root_command_executor.ts index 1e16fae81e..8e7df94940 100644 --- a/apps/client/src/components/root_command_executor.ts +++ b/apps/client/src/components/root_command_executor.ts @@ -78,15 +78,15 @@ export default class RootCommandExecutor extends Component { } hideLeftPaneCommand() { - options.save(`leftPaneVisible`, "false"); + appContext.triggerEvent("setLeftPaneVisibility", { leftPaneVisible: false }); } showLeftPaneCommand() { - options.save(`leftPaneVisible`, "true"); + appContext.triggerEvent("setLeftPaneVisibility", { leftPaneVisible: true }); } toggleLeftPaneCommand() { - options.toggle("leftPaneVisible"); + appContext.triggerEvent("setLeftPaneVisibility", { leftPaneVisible: null }); } async showBackendLogCommand() { diff --git a/apps/client/src/services/resizer.ts b/apps/client/src/services/resizer.ts index 1cce0f9938..e0dc40995c 100644 --- a/apps/client/src/services/resizer.ts +++ b/apps/client/src/services/resizer.ts @@ -3,7 +3,11 @@ import Split from "split.js" export const DEFAULT_GUTTER_SIZE = 5; +let leftPaneWidth: number; +let reservedPx: number; +let layoutOrientation: string; let leftInstance: ReturnType | null; +let rightPaneWidth: number; let rightInstance: ReturnType | null; function setupLeftPaneResizer(leftPaneVisible: boolean) { @@ -14,27 +18,34 @@ function setupLeftPaneResizer(leftPaneVisible: boolean) { $("#left-pane").toggle(leftPaneVisible); + layoutOrientation = layoutOrientation ?? options.get("layoutOrientation"); + reservedPx = reservedPx ?? (layoutOrientation === "vertical" ? ($("#launcher-pane").outerWidth() || 0) : 0); + // Window resizing causes `window.innerWidth` to change, so `reservedWidth` needs to be recalculated each time. + const reservedWidth = reservedPx / window.innerWidth * 100; if (!leftPaneVisible) { - $("#rest-pane").css("width", "100%"); - + $("#rest-pane").css("width", layoutOrientation === "vertical" ? `${100 - reservedWidth}%` : "100%"); return; } - let leftPaneWidth = options.getInt("leftPaneWidth"); + leftPaneWidth = leftPaneWidth ?? (options.getInt("leftPaneWidth") ?? 0); if (!leftPaneWidth || leftPaneWidth < 5) { leftPaneWidth = 5; } + const restPaneWidth = 100 - leftPaneWidth - reservedWidth; if (leftPaneVisible) { // Delayed initialization ensures that all DOM elements are fully rendered and part of the layout, // preventing Split.js from retrieving incorrect dimensions due to #left-pane not being rendered yet, // which would cause the minSize setting to have no effect. requestAnimationFrame(() => { leftInstance = Split(["#left-pane", "#rest-pane"], { - sizes: [leftPaneWidth, 100 - leftPaneWidth], + sizes: [leftPaneWidth, restPaneWidth], gutterSize: DEFAULT_GUTTER_SIZE, minSize: [150, 300], - onDragEnd: (sizes) => options.save("leftPaneWidth", Math.round(sizes[0])) + onDragEnd: (sizes) => { + leftPaneWidth = Math.round(sizes[0]); + options.save("leftPaneWidth", Math.round(sizes[0])); + } }); }); } @@ -54,7 +65,7 @@ function setupRightPaneResizer() { return; } - let rightPaneWidth = options.getInt("rightPaneWidth"); + rightPaneWidth = rightPaneWidth ?? (options.getInt("rightPaneWidth") ?? 0); if (!rightPaneWidth || rightPaneWidth < 5) { rightPaneWidth = 5; } @@ -63,8 +74,11 @@ function setupRightPaneResizer() { rightInstance = Split(["#center-pane", "#right-pane"], { sizes: [100 - rightPaneWidth, rightPaneWidth], gutterSize: DEFAULT_GUTTER_SIZE, - minSize: [ 300, 180 ], - onDragEnd: (sizes) => options.save("rightPaneWidth", Math.round(sizes[1])) + minSize: [300, 180], + onDragEnd: (sizes) => { + rightPaneWidth = Math.round(sizes[1]); + options.save("rightPaneWidth", Math.round(sizes[1])); + } }); } } diff --git a/apps/client/src/stylesheets/theme-next/shell.css b/apps/client/src/stylesheets/theme-next/shell.css index 2bcdf942ff..a777151369 100644 --- a/apps/client/src/stylesheets/theme-next/shell.css +++ b/apps/client/src/stylesheets/theme-next/shell.css @@ -127,6 +127,7 @@ body.layout-horizontal > .horizontal { --launcher-pane-button-gap: var(--launcher-pane-vert-button-gap); width: var(--launcher-pane-size) !important; + min-width: var(--launcher-pane-size) !important; padding-bottom: var(--launcher-pane-button-gap); } diff --git a/apps/client/src/widgets/buttons/left_pane_toggle.ts b/apps/client/src/widgets/buttons/left_pane_toggle.ts index 88528add53..22a9026221 100644 --- a/apps/client/src/widgets/buttons/left_pane_toggle.ts +++ b/apps/client/src/widgets/buttons/left_pane_toggle.ts @@ -19,10 +19,10 @@ export default class LeftPaneToggleWidget extends CommandButtonWidget { return "bx-sidebar"; } - return options.is("leftPaneVisible") ? "bx-chevrons-left" : "bx-chevrons-right"; + return this.currentLeftPaneVisible ? "bx-chevrons-left" : "bx-chevrons-right"; }; - this.settings.title = () => (options.is("leftPaneVisible") ? t("left_pane_toggle.hide_panel") : t("left_pane_toggle.show_panel")); + this.settings.title = () => (this.currentLeftPaneVisible ? t("left_pane_toggle.hide_panel") : t("left_pane_toggle.show_panel")); this.settings.command = () => (this.currentLeftPaneVisible ? "hideLeftPane" : "showLeftPane"); @@ -32,16 +32,12 @@ export default class LeftPaneToggleWidget extends CommandButtonWidget { } refreshIcon() { - if (document.hasFocus() || this.currentLeftPaneVisible === true) { - super.refreshIcon(); - splitService.setupLeftPaneResizer(this.currentLeftPaneVisible); - } + super.refreshIcon(); + splitService.setupLeftPaneResizer(this.currentLeftPaneVisible); } - - entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { - if (loadResults.isOptionReloaded("leftPaneVisible") && document.hasFocus()) { - this.currentLeftPaneVisible = options.is("leftPaneVisible"); - this.refreshIcon(); - } + + setLeftPaneVisibilityEvent({ leftPaneVisible }: EventData<"setLeftPaneVisibility">) { + this.currentLeftPaneVisible = leftPaneVisible ?? !this.currentLeftPaneVisible; + this.refreshIcon(); } } diff --git a/apps/client/src/widgets/containers/left_pane_container.ts b/apps/client/src/widgets/containers/left_pane_container.ts index 2b82215530..d95f5091a0 100644 --- a/apps/client/src/widgets/containers/left_pane_container.ts +++ b/apps/client/src/widgets/containers/left_pane_container.ts @@ -4,28 +4,33 @@ import appContext, { type EventData } from "../../components/app_context.js"; import type Component from "../../components/component.js"; export default class LeftPaneContainer extends FlexContainer { + private currentLeftPaneVisible: boolean; + constructor() { super("column"); + this.currentLeftPaneVisible = options.is("leftPaneVisible"); + this.id("left-pane"); this.css("height", "100%"); this.collapsible(); } isEnabled() { - return super.isEnabled() && options.is("leftPaneVisible"); + return super.isEnabled() && this.currentLeftPaneVisible; } - entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { - if (loadResults.isOptionReloaded("leftPaneVisible") && document.hasFocus()) { - const visible = this.isEnabled(); - this.toggleInt(visible); + setLeftPaneVisibilityEvent({ leftPaneVisible }: EventData<"setLeftPaneVisibility">) { + this.currentLeftPaneVisible = leftPaneVisible ?? !this.currentLeftPaneVisible; + const visible = this.isEnabled(); + this.toggleInt(visible); - if (visible) { - this.triggerEvent("focusTree", {}); - } else { - this.triggerEvent("focusOnDetail", { ntxId: appContext.tabManager.getActiveContext()?.ntxId }); - } + if (visible) { + this.triggerEvent("focusTree", {}); + } else { + this.triggerEvent("focusOnDetail", { ntxId: appContext.tabManager.getActiveContext()?.ntxId }); } + + options.save("leftPaneVisible", this.currentLeftPaneVisible.toString()); } }