mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-02 21:03:34 +03:00
refactor(desktop): keep window-chrome entirely in the desktop layer
Instead of threading a --wincontrols-w var and a data-panel-collapsed flag through core files, keep the custom Windows title bar in the desktop layer per the @app override pattern. - WindowTitleBar mounts in the desktop AppProviders (not core AppFrame) and flags the chrome with data-window-controls on <html>. - All corner reservations live in one desktop-only stylesheet (windowChrome.css), gated by that flag and using :has() to detect the collapsed panel, so no flags or vars leak into core. - Revert every core file (AppFrame, AppLayout, FilesPage.css, ToolPanel.css, RightSidebar, WorkbenchBar) to original; drop the core stub. - Mobile: keep the brand top-left and drop the view switcher below the controls. Net: the whole feature is desktop/ + src-tauri; core is untouched.
This commit is contained in:
@@ -1,6 +0,0 @@
|
||||
// Core stub: the browser build has no custom window chrome. The desktop layer
|
||||
// shadows this (desktop/components/WindowTitleBar.tsx) with a real title bar on
|
||||
// Windows. Rendered by AppFrame, so it must resolve in every build.
|
||||
export function WindowTitleBar() {
|
||||
return null;
|
||||
}
|
||||
@@ -21,9 +21,7 @@
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
min-height: 48px;
|
||||
/* Right padding also clears the desktop window-controls overlay so the whole
|
||||
grid (search + actions) reflows inside it (0 unless Windows custom chrome). */
|
||||
padding: 0 calc(0.75rem + var(--wincontrols-w, 0px)) 0 0.75rem;
|
||||
padding: 0 0.75rem;
|
||||
border-bottom: 1px solid var(--c-border-subtle);
|
||||
background: var(--c-bg-raised);
|
||||
flex-shrink: 0;
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Outlet } from "react-router-dom";
|
||||
import { LoadingFallback } from "@app/components/shared/LoadingFallback";
|
||||
import { QuickNavHostProvider } from "@app/contexts/QuickNavHostContext";
|
||||
import { QuickNavRailHost } from "@app/components/shared/quickNav/QuickNavRailHost";
|
||||
import { WindowTitleBar } from "@app/components/WindowTitleBar";
|
||||
import "@app/components/layout/AppFrame.css";
|
||||
|
||||
/** The rail renders once outside both apps; Suspense sits inside it, not above. */
|
||||
@@ -11,10 +10,6 @@ export function AppFrame() {
|
||||
return (
|
||||
<QuickNavHostProvider>
|
||||
<div className="app-frame">
|
||||
{/* Desktop (Windows) window controls, drawn as a fixed overlay in the
|
||||
top-right corner so the rail and panels run to the window edge; null
|
||||
on web + macOS/Linux. */}
|
||||
<WindowTitleBar />
|
||||
<QuickNavRailHost />
|
||||
<div className="app-frame__content">
|
||||
<Suspense fallback={<LoadingFallback />}>
|
||||
|
||||
@@ -31,11 +31,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
/* Extra top space keeps the expand toggle clear of the desktop window
|
||||
controls overlay. Derive the ~control-height from --wincontrols-w (which is
|
||||
138px when the custom Windows chrome is active, 0 otherwise) via min(), so
|
||||
it needs only that one variable. */
|
||||
padding-top: calc(10px + min(var(--wincontrols-w, 0px), 2rem));
|
||||
padding-top: 10px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
@@ -161,19 +157,9 @@
|
||||
gap: 0.5rem;
|
||||
min-height: 52px;
|
||||
padding: 0.5rem 0.75rem;
|
||||
/* Keep the collapse toggle clear of the desktop window-controls overlay
|
||||
(--wincontrols-w is 0 unless the custom Windows title bar is active). */
|
||||
padding-right: calc(0.75rem + var(--wincontrols-w, 0px));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Active-tool header (e.g. Automate) sits at the panel's top-right too; keep its
|
||||
close button clear of the window-controls overlay. Scoped to the tool panel so
|
||||
the shared header elsewhere (AI chat, etc.) is untouched. */
|
||||
.tool-panel .sui-panelhdr__close {
|
||||
margin-right: var(--wincontrols-w, 0px);
|
||||
}
|
||||
|
||||
.tool-panel__compact-title {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ReactNode, useEffect, useRef, useState } from "react";
|
||||
import { AppProviders as ProprietaryAppProviders } from "@proprietary/components/AppProviders";
|
||||
import { DesktopConfigSync } from "@app/components/DesktopConfigSync";
|
||||
import { WindowTitleBar } from "@app/components/WindowTitleBar";
|
||||
import { DesktopQueryCacheReset } from "@app/components/DesktopQueryCacheReset";
|
||||
import { DesktopBannerInitializer } from "@app/components/DesktopBannerInitializer";
|
||||
import { SaveShortcutListener } from "@app/components/SaveShortcutListener";
|
||||
@@ -328,6 +329,7 @@ export function AppProviders({ children }: { children: ReactNode }) {
|
||||
>
|
||||
{/* Also here: the auth check below switches mode pre-authChecked. */}
|
||||
<DesktopQueryCacheReset />
|
||||
<WindowTitleBar />
|
||||
<div style={{ minHeight: "100vh" }} />
|
||||
{updatePopupModal}
|
||||
</ProprietaryAppProviders>
|
||||
@@ -354,6 +356,7 @@ export function AppProviders({ children }: { children: ReactNode }) {
|
||||
}}
|
||||
>
|
||||
<DesktopQueryCacheReset />
|
||||
<WindowTitleBar />
|
||||
<SaaSTeamProvider key={appKey}>
|
||||
<DesktopConfigSync />
|
||||
<DesktopBannerInitializer />
|
||||
|
||||
@@ -8,6 +8,9 @@ import FilterNoneIcon from "@mui/icons-material/FilterNone";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { getDesktopOs, DesktopOs } from "@app/services/platformService";
|
||||
import styles from "@app/components/WindowTitleBar.module.css";
|
||||
// Desktop-only skin that reserves the controls' corner across core layout
|
||||
// surfaces; every rule is gated by the data-window-controls flag set below.
|
||||
import "@app/components/windowChrome.css";
|
||||
|
||||
// Seed from the UA so the bar (and its reserved height) is present on the first
|
||||
// frame on Windows, avoiding a layout shift. getDesktopOs() confirms it right
|
||||
@@ -19,10 +22,11 @@ const seedIsWindows =
|
||||
* Custom window controls for the Windows desktop build. The native caption is
|
||||
* removed in Rust (decorations:false), so this draws minimize/maximize/close as
|
||||
* a fixed overlay pinned to the top-right corner — the rail and panels run all
|
||||
* the way to the window edge, and the app chrome reserves the corner via
|
||||
* --wincontrols-w. Renders nothing on macOS/Linux (native decorations kept) and
|
||||
* in the browser build (core stub). tao provides edge/corner resize for the
|
||||
* undecorated window, so no manual resize handles are needed here.
|
||||
* the way to the window edge, and the app chrome reserves the corner via the
|
||||
* data-window-controls flag this sets on <html> (consumed by windowChrome.css).
|
||||
* Renders nothing on macOS/Linux (native decorations kept); not bundled in the
|
||||
* browser build. tao provides edge/corner resize for the undecorated window, so
|
||||
* no manual resize handles are needed here.
|
||||
*/
|
||||
export function WindowTitleBar() {
|
||||
const [isWindows, setIsWindows] = useState(seedIsWindows);
|
||||
@@ -44,21 +48,19 @@ export function WindowTitleBar() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// The controls are a fixed overlay in the top-right corner. Publish their
|
||||
// width so app chrome at the top-right (the tool panel header) can pad clear
|
||||
// of them; unset (0) everywhere else. Layout effect so it lands before paint.
|
||||
// Flag the custom chrome on <html> so windowChrome.css can reserve the
|
||||
// controls' corner across the app. Set only when active (Windows desktop);
|
||||
// absent otherwise, so the skin is inert on macOS/Linux. Layout effect so it
|
||||
// lands before paint.
|
||||
useIsomorphicEffect(() => {
|
||||
const root = document.documentElement;
|
||||
if (active) {
|
||||
root.style.setProperty("--wincontrols-w", "8.625rem"); // 3 x 46px
|
||||
root.style.setProperty("--wincontrols-h", "2rem");
|
||||
root.setAttribute("data-window-controls", "custom");
|
||||
} else {
|
||||
root.style.removeProperty("--wincontrols-w");
|
||||
root.style.removeProperty("--wincontrols-h");
|
||||
root.removeAttribute("data-window-controls");
|
||||
}
|
||||
return () => {
|
||||
root.style.removeProperty("--wincontrols-w");
|
||||
root.style.removeProperty("--wincontrols-h");
|
||||
root.removeAttribute("data-window-controls");
|
||||
};
|
||||
}, [active]);
|
||||
|
||||
@@ -113,7 +115,7 @@ export function WindowTitleBar() {
|
||||
|
||||
const appWindow = getCurrentWindow();
|
||||
return (
|
||||
<div className={styles.titleBar} data-tauri-drag-region>
|
||||
<div className={styles.titleBar}>
|
||||
<div className={styles.controls}>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/* Desktop (Windows) custom window chrome.
|
||||
*
|
||||
* The window controls are a fixed overlay in the top-right corner (see
|
||||
* WindowTitleBar). These rules keep the app's own chrome clear of that corner.
|
||||
*
|
||||
* Loaded only in the desktop build (imported by WindowTitleBar) and gated on the
|
||||
* data-window-controls flag the overlay sets on <html> only when active, so it
|
||||
* is inert on macOS/Linux (flag never set) and absent from web builds. It
|
||||
* targets core layout class names on purpose: this is the single, contained
|
||||
* coupling point between the desktop chrome and the core layout, kept here in
|
||||
* the desktop layer so the core components stay unaware of window chrome. */
|
||||
|
||||
html[data-window-controls="custom"] {
|
||||
--wincontrols-w: 8.625rem; /* three 46px controls */
|
||||
--wincontrols-h: 2rem; /* control height */
|
||||
}
|
||||
|
||||
/* Files-page toolbar spans the width; reflow search + actions left of the
|
||||
controls by shrinking the grid's right edge. */
|
||||
html[data-window-controls="custom"] .files-page-header {
|
||||
padding-right: calc(0.75rem + var(--wincontrols-w));
|
||||
}
|
||||
|
||||
/* Right panel, expanded: move the header (PDF Tools, or an active tool such as
|
||||
Automate) below the controls instead of squashing its title. */
|
||||
html[data-window-controls="custom"] .tool-panel__compact-header,
|
||||
html[data-window-controls="custom"] .tool-panel .sui-panelhdr {
|
||||
margin-top: var(--wincontrols-h);
|
||||
}
|
||||
|
||||
/* Right panel, collapsed: the strip is narrower than the controls, so push its
|
||||
expand toggle straight down. */
|
||||
html[data-window-controls="custom"] .tool-panel__collapsed-strip {
|
||||
padding-top: calc(10px + var(--wincontrols-h));
|
||||
}
|
||||
|
||||
/* Viewer top bar: when the right panel is collapsed the bar widens under the
|
||||
controls. :has() detects the collapsed strip (no flag needed in core) and we
|
||||
push the right cluster clear. --nav-rail-w is the collapsed strip's width, so
|
||||
only the controls' overhang past it is reserved. */
|
||||
html[data-window-controls="custom"]
|
||||
.app-frame__content:has(.tool-panel__collapsed-strip)
|
||||
.workbench-bar-globals {
|
||||
margin-right: calc(var(--wincontrols-w) - var(--nav-rail-w));
|
||||
}
|
||||
|
||||
/* Mobile layout: the top bar spans the full width. Keep the brand top-left
|
||||
(already clear of the top-right controls) and drop the view switcher below
|
||||
them by top-aligning the row and nudging the switcher down. */
|
||||
html[data-window-controls="custom"] .mobile-toggle {
|
||||
align-items: flex-start;
|
||||
}
|
||||
html[data-window-controls="custom"] .mobile-toggle-buttons {
|
||||
margin-top: var(--wincontrols-h);
|
||||
}
|
||||
Reference in New Issue
Block a user