mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 19:57:52 +01:00
desktop: fix custom title bar on linux
This commit is contained in:
@@ -84,6 +84,7 @@ async function createWindow() {
|
||||
}),
|
||||
|
||||
titleBarStyle: "hidden",
|
||||
frame: process.platform === "win32" || process.platform === "darwin",
|
||||
titleBarOverlay: {
|
||||
height: 37,
|
||||
color: "#00000000",
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
}
|
||||
|
||||
.tabsScroll,
|
||||
.titlebarLogo {
|
||||
.titlebarLogo,
|
||||
.theme-scope-titleBar {
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
.theme-scope-titleBar button,
|
||||
.tabsScroll .tab {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import { FlexScrollContainer } from "./components/scroll-container";
|
||||
import CachedRouter from "./components/cached-router";
|
||||
import { WebExtensionRelay } from "./utils/web-extension-relay";
|
||||
import { usePersistentState } from "./hooks/use-persistent-state";
|
||||
import { EditorActionBar } from "./components/editor/action-bar";
|
||||
|
||||
new WebExtensionRelay();
|
||||
|
||||
@@ -136,7 +135,6 @@ function DesktopAppContents({
|
||||
|
||||
return (
|
||||
<>
|
||||
{IS_DESKTOP_APP ? <EditorActionBar /> : null}
|
||||
<Flex
|
||||
variant="rowFill"
|
||||
sx={{
|
||||
|
||||
@@ -35,11 +35,7 @@ import {
|
||||
Search,
|
||||
TableOfContents,
|
||||
Trash,
|
||||
Unlock,
|
||||
WindowClose,
|
||||
WindowMaximize,
|
||||
WindowMinimize,
|
||||
WindowRestore
|
||||
Unlock
|
||||
} from "../icons";
|
||||
import { ScrollContainer } from "@notesnook/ui";
|
||||
import {
|
||||
@@ -68,16 +64,12 @@ import {
|
||||
} from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import { AppEventManager, AppEvents } from "../../common/app-events";
|
||||
import { desktop } from "../../common/desktop-bridge";
|
||||
import { useWindowControls } from "../../hooks/use-window-controls";
|
||||
import { ScopedThemeProvider } from "../theme-provider";
|
||||
import { getPlatform } from "../../utils/platform";
|
||||
|
||||
export function EditorActionBar() {
|
||||
const editorMargins = useEditorStore((store) => store.editorMargins);
|
||||
const isFocusMode = useAppStore((store) => store.isFocusMode);
|
||||
const { isMaximized, isFullscreen, hasNativeWindowControls } =
|
||||
useWindowControls();
|
||||
const { isFullscreen } = useWindowControls();
|
||||
const activeSession = useEditorStore((store) =>
|
||||
store.activeSessionId ? store.getSession(store.activeSessionId) : undefined
|
||||
);
|
||||
@@ -153,68 +145,12 @@ export function EditorActionBar() {
|
||||
activeSession.type !== "conflicted" &&
|
||||
!isFocusMode,
|
||||
onClick: () => useEditorStore.getState().toggleProperties()
|
||||
},
|
||||
|
||||
{
|
||||
title: "Minimize",
|
||||
icon: WindowMinimize,
|
||||
hidden: hasNativeWindowControls || isFullscreen,
|
||||
enabled: true,
|
||||
onClick: () => desktop?.window.minimze.mutate()
|
||||
},
|
||||
{
|
||||
title: isMaximized ? "Restore" : "Maximize",
|
||||
icon: isMaximized ? WindowRestore : WindowMaximize,
|
||||
enabled: true,
|
||||
hidden: hasNativeWindowControls || isFullscreen,
|
||||
onClick: () =>
|
||||
isMaximized
|
||||
? desktop?.window.restore.mutate()
|
||||
: desktop?.window.maximize.mutate()
|
||||
},
|
||||
{
|
||||
title: "Close",
|
||||
icon: WindowClose,
|
||||
hidden: hasNativeWindowControls || isFullscreen,
|
||||
enabled: true,
|
||||
onClick: () => window.close()
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<ScopedThemeProvider scope="titleBar" injectCssVars>
|
||||
<Flex
|
||||
sx={{
|
||||
gap: 2,
|
||||
borderBottom: IS_DESKTOP_APP ? "1px solid var(--border)" : "none",
|
||||
px: 1,
|
||||
...(IS_DESKTOP_APP && !isFullscreen && hasNativeWindowControls
|
||||
? getPlatform() === "darwin"
|
||||
? { pl: "calc(100vw - env(titlebar-area-width))" }
|
||||
: { pr: "calc(100vw - env(titlebar-area-width))" }
|
||||
: {})
|
||||
}}
|
||||
>
|
||||
{IS_DESKTOP_APP && (getPlatform() !== "darwin" || isFullscreen) ? (
|
||||
<svg
|
||||
className="titlebarLogo"
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
height: 24,
|
||||
width: 24
|
||||
}}
|
||||
>
|
||||
<use href="#themed-logo" />
|
||||
</svg>
|
||||
) : null}
|
||||
<>
|
||||
<TabStrip />
|
||||
<Flex
|
||||
bg="transparent"
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end"
|
||||
}}
|
||||
>
|
||||
{tools.map((tool) => (
|
||||
<Button
|
||||
data-test-id={tool.title}
|
||||
@@ -244,9 +180,7 @@ export function EditorActionBar() {
|
||||
<tool.icon size={18} />
|
||||
</Button>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</ScopedThemeProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,17 @@ export default function TabsView() {
|
||||
|
||||
return (
|
||||
<>
|
||||
{IS_DESKTOP_APP ? null : <EditorActionBar />}
|
||||
{IS_DESKTOP_APP ? (
|
||||
ReactDOM.createPortal(
|
||||
<EditorActionBar />,
|
||||
document.getElementById("titlebar-portal-container")!
|
||||
)
|
||||
) : (
|
||||
<Flex>
|
||||
<EditorActionBar />
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<ScopedThemeProvider
|
||||
scope="editor"
|
||||
ref={dropRef}
|
||||
|
||||
@@ -49,10 +49,16 @@ export function ErrorComponent({ error, resetErrorBoundary }: FallbackProps) {
|
||||
height: "100%",
|
||||
bg: "background",
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
width: "100%",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
overflowY: "auto"
|
||||
overflowY: "auto",
|
||||
py: 5
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
@@ -143,6 +149,7 @@ ${getDeviceInfo()}`
|
||||
</>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</BaseThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
128
apps/web/src/components/title-bar/index.tsx
Normal file
128
apps/web/src/components/title-bar/index.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Button } from "@theme-ui/components";
|
||||
import { desktop } from "../../common/desktop-bridge";
|
||||
import { useWindowControls } from "../../hooks/use-window-controls";
|
||||
import { getPlatform } from "../../utils/platform";
|
||||
import {
|
||||
WindowClose,
|
||||
WindowMaximize,
|
||||
WindowMinimize,
|
||||
WindowRestore
|
||||
} from "../icons";
|
||||
import { BaseThemeProvider } from "../theme-provider";
|
||||
|
||||
export function TitleBar() {
|
||||
const { isMaximized, isFullscreen, hasNativeWindowControls } =
|
||||
useWindowControls();
|
||||
|
||||
const tools = [
|
||||
{
|
||||
title: "Minimize",
|
||||
icon: WindowMinimize,
|
||||
hidden: hasNativeWindowControls || isFullscreen,
|
||||
enabled: true,
|
||||
onClick: () => desktop?.window.minimze.mutate()
|
||||
},
|
||||
{
|
||||
title: isMaximized ? "Restore" : "Maximize",
|
||||
icon: isMaximized ? WindowRestore : WindowMaximize,
|
||||
enabled: true,
|
||||
hidden: hasNativeWindowControls || isFullscreen,
|
||||
onClick: () =>
|
||||
isMaximized
|
||||
? desktop?.window.restore.mutate()
|
||||
: desktop?.window.maximize.mutate()
|
||||
},
|
||||
{
|
||||
title: "Close",
|
||||
icon: WindowClose,
|
||||
hidden: hasNativeWindowControls || isFullscreen,
|
||||
enabled: true,
|
||||
onClick: () => window.close()
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<BaseThemeProvider
|
||||
scope="titleBar"
|
||||
sx={{
|
||||
background: "background",
|
||||
height: 37.8,
|
||||
display: "flex",
|
||||
borderBottom: "1px solid var(--border)",
|
||||
...(!isFullscreen && hasNativeWindowControls
|
||||
? getPlatform() === "darwin"
|
||||
? { pl: "calc(100vw - env(titlebar-area-width))" }
|
||||
: { pr: "calc(100vw - env(titlebar-area-width))" }
|
||||
: { pl: 2, pr: 0 })
|
||||
}}
|
||||
injectCssVars
|
||||
>
|
||||
{getPlatform() !== "darwin" || isFullscreen ? (
|
||||
<svg
|
||||
className="titlebarLogo"
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
height: 24,
|
||||
width: 24,
|
||||
marginRight: 10
|
||||
}}
|
||||
>
|
||||
<use href="#themed-logo" />
|
||||
</svg>
|
||||
) : null}
|
||||
<div
|
||||
id="titlebar-portal-container"
|
||||
style={{
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
/>
|
||||
{tools.map((tool) => (
|
||||
<Button
|
||||
data-test-id={tool.title}
|
||||
disabled={!tool.enabled}
|
||||
variant={tool.title === "Close" ? "error" : "secondary"}
|
||||
title={tool.title}
|
||||
key={tool.title}
|
||||
sx={{
|
||||
height: "100%",
|
||||
alignItems: "center",
|
||||
bg: "transparent",
|
||||
display: tool.hidden ? "none" : "flex",
|
||||
borderRadius: 0,
|
||||
flexShrink: 0,
|
||||
"&:hover svg path": {
|
||||
fill:
|
||||
tool.title === "Close"
|
||||
? "var(--accentForeground-error) !important"
|
||||
: "var(--icon)"
|
||||
}
|
||||
}}
|
||||
onClick={tool.onClick}
|
||||
>
|
||||
<tool.icon size={18} />
|
||||
</Button>
|
||||
))}
|
||||
</BaseThemeProvider>
|
||||
);
|
||||
}
|
||||
@@ -130,6 +130,11 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@keyframes fadeUp {
|
||||
0% {
|
||||
transform: translateY(500px);
|
||||
|
||||
@@ -25,6 +25,7 @@ import { BaseThemeProvider } from "./components/theme-provider";
|
||||
import { register } from "./utils/stream-saver/mitm";
|
||||
import { getServiceWorkerVersion } from "./utils/version";
|
||||
import { ErrorBoundary, ErrorComponent } from "./components/error-boundary";
|
||||
import { TitleBar } from "./components/title-bar";
|
||||
|
||||
renderApp();
|
||||
|
||||
@@ -36,29 +37,38 @@ async function renderApp() {
|
||||
try {
|
||||
const { component, props, path } = await init();
|
||||
|
||||
const { useKeyStore } = await import("./interfaces/key-store");
|
||||
await useKeyStore.getState().init();
|
||||
|
||||
if (serviceWorkerWhitelist.includes(path)) await initializeServiceWorker();
|
||||
|
||||
const { default: Component } = await component();
|
||||
|
||||
const { default: AppLock } = await import("./views/app-lock");
|
||||
|
||||
root.render(
|
||||
<>
|
||||
{IS_DESKTOP_APP ? <TitleBar /> : null}
|
||||
<ErrorBoundary>
|
||||
<BaseThemeProvider
|
||||
onRender={() => document.getElementById("splash")?.remove()}
|
||||
sx={{ height: "100%", bg: "background" }}
|
||||
sx={{ bg: "background", flex: 1 }}
|
||||
>
|
||||
<AppLock>
|
||||
<Component route={props?.route || "login:email"} />
|
||||
</AppLock>
|
||||
</BaseThemeProvider>
|
||||
</ErrorBoundary>
|
||||
</>
|
||||
);
|
||||
} catch (e) {
|
||||
root.render(
|
||||
<>
|
||||
{IS_DESKTOP_APP ? <TitleBar /> : null}
|
||||
<ErrorComponent
|
||||
error={e}
|
||||
resetErrorBoundary={() => window.location.reload()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ import { getDocumentTitle, setDocumentTitle } from "../utils/dom";
|
||||
import { CredentialWithoutSecret, useKeyStore } from "../interfaces/key-store";
|
||||
|
||||
export default function AppLock(props: PropsWithChildren<unknown>) {
|
||||
const init = usePromise(() => useKeyStore.getState().init());
|
||||
const credentials = useKeyStore((store) => store.activeCredentials());
|
||||
const isLocked = useKeyStore((store) => store.isLocked);
|
||||
const _lockAfter = useKeyStore((store) => store.secrets.lockAfter);
|
||||
@@ -120,8 +119,6 @@ export default function AppLock(props: PropsWithChildren<unknown>) {
|
||||
}
|
||||
}, [lockAfter, credentials]);
|
||||
|
||||
if (init.status !== "fulfilled") return null;
|
||||
|
||||
if (isLocked)
|
||||
return (
|
||||
<Flex
|
||||
|
||||
Reference in New Issue
Block a user