[WEB-5524] improvement: update desktop app layout as per new navigation (#4901)
- Removed the DesktopAppProviderRoot component and replaced it with DesktopHeaderProvider for better structure. - Introduced DesktopSidebarWorkspaceMenu and updated sidebar toggle visibility logic. - Enhanced WorkspaceMenuRoot to accept a variant prop for better layout control. - Cleaned up unused imports and adjusted CSS class names for consistency. - Removed SidebarDropdown component as part of the refactor.
This commit is contained in:
@@ -15,7 +15,6 @@ import icon180 from "@/app/assets/icons/icon-180x180.png?url";
|
||||
import icon512 from "@/app/assets/icons/icon-512x512.png?url";
|
||||
import ogImage from "@/app/assets/og-image.png?url";
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
import { DesktopAppProviderRoot } from "@/plane-web/components/desktop/root";
|
||||
import { TrialBanner } from "@/plane-web/components/license/banner/trial-banner";
|
||||
import globalStyles from "@/styles/globals.css?url";
|
||||
import type { Route } from "./+types/root";
|
||||
@@ -60,11 +59,10 @@ export function Layout({ children }: { children: ReactNode }) {
|
||||
<div id="context-menu-portal" />
|
||||
<div id="editor-portal" />
|
||||
<AppProvider>
|
||||
<DesktopAppProviderRoot />
|
||||
<div
|
||||
className={cn(
|
||||
"h-screen w-full overflow-hidden bg-custom-background-100 relative flex flex-col",
|
||||
"app-container"
|
||||
"desktop-app-container"
|
||||
)}
|
||||
>
|
||||
{/* free trial banner */}
|
||||
|
||||
@@ -6,8 +6,6 @@ import { AppSidebarToggleButton } from "@/components/sidebar/sidebar-toggle-butt
|
||||
// hooks
|
||||
import { useAppTheme } from "@/hooks/store/use-app-theme";
|
||||
import { useProjectNavigationPreferences } from "@/hooks/use-navigation-preferences";
|
||||
// local imports
|
||||
import { isSidebarToggleVisible } from "../desktop/helper";
|
||||
|
||||
export const ExtendedAppHeader = observer((props: { header: ReactNode }) => {
|
||||
const { header } = props;
|
||||
@@ -22,7 +20,7 @@ export const ExtendedAppHeader = observer((props: { header: ReactNode }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{isSidebarToggleVisible() && sidebarCollapsed && shouldShowSidebarToggleButton && <AppSidebarToggleButton />}
|
||||
{sidebarCollapsed && shouldShowSidebarToggleButton && <AppSidebarToggleButton />}
|
||||
<div className="flex items-center gap-2 divide-x divide-custom-border-100 w-full">
|
||||
<div className="w-full flex-1">{header}</div>
|
||||
</div>
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
import { createContext, useEffect, useState, useMemo, useCallback } from "react";
|
||||
// todesktop
|
||||
import { nativeWindow, webContents } from "@todesktop/client-core";
|
||||
import { nativeWindow } from "@todesktop/client-core";
|
||||
import { isDesktopApp } from "@todesktop/client-core/platform/todesktop";
|
||||
// mobx
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
export type TDesktopAppContext = {
|
||||
pageTitle: string;
|
||||
isFullScreen: boolean;
|
||||
};
|
||||
|
||||
@@ -20,9 +20,8 @@ export type TDesktopAppProviderProps = {
|
||||
export const DesktopAppProvider = observer((props: TDesktopAppProviderProps) => {
|
||||
const { children } = props;
|
||||
// states
|
||||
const [pageTitle, setPageTitle] = useState<string>(document.title);
|
||||
const [isFullScreen, setIsFullScreen] = useState<boolean>(false);
|
||||
|
||||
// derived
|
||||
// handle theme change
|
||||
const handleThemeChange = useCallback(() => {
|
||||
const currentTheme = localStorage.getItem("theme") || "light";
|
||||
@@ -30,16 +29,11 @@ export const DesktopAppProvider = observer((props: TDesktopAppProviderProps) =>
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDesktopApp()) return;
|
||||
|
||||
const cleanupFunctions: (() => Promise<void> | void)[] = [];
|
||||
|
||||
// Setup observers
|
||||
const setupTitleChangeObserver = async () => {
|
||||
const unsubscribe = await webContents.on("page-title-updated", () => {
|
||||
setPageTitle(document.title);
|
||||
});
|
||||
cleanupFunctions.push(unsubscribe);
|
||||
};
|
||||
|
||||
const setupFullScreenObservers = async () => {
|
||||
setIsFullScreen(await nativeWindow.isFullscreen());
|
||||
|
||||
@@ -60,7 +54,6 @@ export const DesktopAppProvider = observer((props: TDesktopAppProviderProps) =>
|
||||
|
||||
// Call observers
|
||||
handleThemeChange();
|
||||
setupTitleChangeObserver();
|
||||
setupFullScreenObservers();
|
||||
setupThemeObserver();
|
||||
|
||||
@@ -70,7 +63,7 @@ export const DesktopAppProvider = observer((props: TDesktopAppProviderProps) =>
|
||||
};
|
||||
}, [handleThemeChange]);
|
||||
|
||||
const contextValue = useMemo(() => ({ pageTitle, isFullScreen }), [pageTitle, isFullScreen]);
|
||||
const contextValue = useMemo(() => ({ isFullScreen }), [isFullScreen]);
|
||||
|
||||
return <DesktopAppContext.Provider value={contextValue}>{children}</DesktopAppContext.Provider>;
|
||||
});
|
||||
|
||||
@@ -4,3 +4,5 @@ export * from "./sidebar-toggle";
|
||||
export * from "./navigation";
|
||||
export * from "./use-desktop-app";
|
||||
export * from "./helper";
|
||||
export * from "./sidebar-workspace-menu";
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@ export const DesktopAppNavigation = observer(() => {
|
||||
observerNavigationEvents();
|
||||
|
||||
return () => {
|
||||
unsubscribeNavigationEvent && unsubscribeNavigationEvent();
|
||||
if (!unsubscribeNavigationEvent) return;
|
||||
unsubscribeNavigationEvent();
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -1,49 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import type { FC } from "react";
|
||||
// to desktop
|
||||
import { isDesktopApp } from "@todesktop/client-core/platform/todesktop";
|
||||
// mobx
|
||||
import { useMemo } from "react";
|
||||
import { isDesktopApp as isDesktopAppFn } from "@todesktop/client-core/platform/todesktop";
|
||||
import { observer } from "mobx-react";
|
||||
// helpers
|
||||
import { cn } from "@plane/utils";
|
||||
// desktop app components
|
||||
import { SidebarToggle, DesktopAppNavigation, useDesktopApp, DesktopAppProvider } from "@/plane-web/components/desktop";
|
||||
import {
|
||||
DesktopSidebarToggle,
|
||||
DesktopAppNavigation,
|
||||
useDesktopApp,
|
||||
DesktopAppProvider,
|
||||
} from "@/plane-web/components/desktop";
|
||||
|
||||
const DesktopAppRoot: FC = observer(() => {
|
||||
const DesktopHeaderRoot = observer(function DesktopHeaderRoot() {
|
||||
// store hooks
|
||||
const { pageTitle, isFullScreen } = useDesktopApp();
|
||||
const { isFullScreen } = useDesktopApp();
|
||||
// derived values
|
||||
const isDesktopApp = useMemo(() => isDesktopAppFn(), []);
|
||||
|
||||
if (!isDesktopApp) return null;
|
||||
return (
|
||||
<>
|
||||
<div className="header fixed top-0 left-0 flex gap-4 items-center w-full h-8">
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-shrink-0 gap-0.5 items-center justify-end w-[160px] transition-all duration-300 ease-in-out",
|
||||
{
|
||||
"pl-3 justify-start": isFullScreen,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<SidebarToggle />
|
||||
<DesktopAppNavigation />
|
||||
</div>
|
||||
<div className="flex gap-2 grow items-center justify-center">
|
||||
{/* page title */}
|
||||
<div className="text-xs font-semibold text-custom-text-300 truncate">{pageTitle}</div>
|
||||
</div>
|
||||
<div className="w-[70px] flex flex-shrink-0 items-center justify-start" />
|
||||
</div>
|
||||
</>
|
||||
<div
|
||||
className={cn("flex flex-shrink-0 gap-0.5 transition-all duration-300 ease-in-out", {
|
||||
"pl-12": isFullScreen,
|
||||
"pl-20": !isFullScreen,
|
||||
})}
|
||||
>
|
||||
<DesktopSidebarToggle />
|
||||
<DesktopAppNavigation />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export const DesktopAppProviderRoot: FC = observer(() => {
|
||||
if (!isDesktopApp()) return null;
|
||||
|
||||
export const DesktopHeaderProvider = observer(function DesktopHeaderProvider() {
|
||||
return (
|
||||
<DesktopAppProvider>
|
||||
<DesktopAppRoot />
|
||||
<DesktopHeaderRoot />
|
||||
</DesktopAppProvider>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -11,20 +11,20 @@ import { cn } from "@plane/utils";
|
||||
// hooks
|
||||
import { useAppTheme } from "@/hooks/store/use-app-theme";
|
||||
|
||||
export const SidebarToggle = observer(() => {
|
||||
export const DesktopSidebarToggle = observer(function DesktopSidebarToggle() {
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
const pathname = usePathname();
|
||||
// store hooks
|
||||
const { sidebarCollapsed, toggleSidebar } = useAppTheme();
|
||||
// derived values
|
||||
const isSidebarAccessible = !!workspaceSlug?.toString() || pathname.includes("/profile/");
|
||||
const isSettingsPath = workspaceSlug && pathname.includes(`/${workspaceSlug}/settings`);
|
||||
const isNotificationsPath = workspaceSlug && pathname.includes(`/${workspaceSlug}/notifications`);
|
||||
const sidebarInaccessiblePaths = isSettingsPath || isNotificationsPath;
|
||||
const isSidebarAccessible = workspaceSlug && !sidebarInaccessiblePaths;
|
||||
const isSidebarCollapsed = useMemo(() => sidebarCollapsed, [sidebarCollapsed]);
|
||||
|
||||
if (!isSidebarAccessible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isSidebarAccessible) return null;
|
||||
return (
|
||||
<>
|
||||
<Tooltip tooltipContent={isSidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"} position="bottom">
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { isDesktopApp } from "@todesktop/client-core/platform/todesktop";
|
||||
// components
|
||||
import { WorkspaceMenuRoot } from "@/components/workspace/sidebar/workspace-menu-root";
|
||||
|
||||
export function DesktopSidebarWorkspaceMenu() {
|
||||
if (!isDesktopApp()) return null;
|
||||
return (
|
||||
<div className="pb-1.5">
|
||||
<WorkspaceMenuRoot variant="sidebar" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,10 @@
|
||||
import { useContext } from "react";
|
||||
// todesktop
|
||||
import { isDesktopApp } from "@todesktop/client-core/platform/todesktop";
|
||||
// context
|
||||
import type { TDesktopAppContext } from "./context";
|
||||
import { DesktopAppContext } from "./context";
|
||||
|
||||
export const useDesktopApp = (): TDesktopAppContext => {
|
||||
if (!isDesktopApp()) throw new Error("useDesktopApp must be used in desktop app");
|
||||
const context = useContext(DesktopAppContext);
|
||||
if (context === undefined) throw new Error("useDesktopApp must be used within IssueModalProvider");
|
||||
if (context === undefined) throw new Error("useDesktopApp must be used within DesktopAppProvider");
|
||||
return context;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useMemo } from "react";
|
||||
import { isDesktopApp as isDesktopAppFn } from "@todesktop/client-core/platform/todesktop";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
@@ -20,6 +22,7 @@ import { useAppRailPreferences } from "@/hooks/use-navigation-preferences";
|
||||
import { useFlag, useWorkspaceFeatures } from "@/plane-web/hooks/store";
|
||||
import { usePiChat } from "@/plane-web/hooks/store/use-pi-chat";
|
||||
import { EWorkspaceFeatures } from "@/plane-web/types/workspace-feature";
|
||||
import { DesktopHeaderProvider } from "../desktop/root";
|
||||
import { TopNavSearch } from "./top-nav-search";
|
||||
|
||||
export const TopNavigationRoot = observer(() => {
|
||||
@@ -29,10 +32,11 @@ export const TopNavigationRoot = observer(() => {
|
||||
const { isWorkspaceFeatureEnabled } = useWorkspaceFeatures();
|
||||
const { unreadNotificationsCount, getUnreadNotificationsCount } = useWorkspaceNotifications();
|
||||
const { preferences } = useAppRailPreferences();
|
||||
|
||||
// router
|
||||
const { workspaceSlug, projectId, workItem } = useParams();
|
||||
const pathname = usePathname();
|
||||
// derived
|
||||
const isDesktopApp = useMemo(() => isDesktopAppFn(), []);
|
||||
|
||||
// Fetch notification count
|
||||
useSWR(
|
||||
@@ -58,18 +62,21 @@ export const TopNavigationRoot = observer(() => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn("flex items-center min-h-11 w-full px-3.5 z-[27] transition-all duration-300", {
|
||||
className={cn("desktop-header flex items-center min-h-11 w-full px-3.5 z-[27] transition-all duration-300", {
|
||||
"px-2": !showLabel,
|
||||
})}
|
||||
>
|
||||
{/* Workspace Menu */}
|
||||
<div className="shrink-0 flex-1">
|
||||
<WorkspaceMenuRoot />
|
||||
{!isDesktopApp && <WorkspaceMenuRoot variant="top-navigation" />}
|
||||
{isDesktopApp && <DesktopHeaderProvider />}
|
||||
</div>
|
||||
{/* Power K Search */}
|
||||
<div className="shrink-0">{isAdvancedSearchEnabled && isOpenSearch ? <TopNavSearch /> : <TopNavPowerK />}</div>
|
||||
<div className="desktop-header-actions shrink-0">
|
||||
{isAdvancedSearchEnabled && isOpenSearch ? <TopNavSearch /> : <TopNavPowerK />}
|
||||
</div>
|
||||
{/* Additional Actions */}
|
||||
<div className="shrink-0 flex-1 flex items-center justify-end">
|
||||
<div className="desktop-header-actions shrink-0 flex-1 flex items-center justify-end">
|
||||
<Tooltip tooltipContent="Inbox" position="bottom">
|
||||
<AppSidebarItem
|
||||
variant="link"
|
||||
|
||||
@@ -1039,10 +1039,9 @@ div.web-view-spinner div.bar12 {
|
||||
html.todesktop body {
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
html.todesktop .app-container {
|
||||
html.todesktop .desktop-app-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: unset;
|
||||
@@ -1051,12 +1050,13 @@ html.todesktop .app-container {
|
||||
position: relative; /* Ensure it's the containing block */
|
||||
}
|
||||
|
||||
html.todesktop .header {
|
||||
html.todesktop .desktop-header {
|
||||
background-color: rgb(var(--color-background-90));
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
html.todesktop .header button {
|
||||
html.todesktop .desktop-header button,
|
||||
html.todesktop .desktop-header .desktop-header-actions {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user