mirror of
https://github.com/colanode/colanode.git
synced 2025-12-29 00:25:03 +01:00
Refactor sidebar layout
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import { NodeCollaboratorsPopover } from '@/renderer/components/collaborators/node-collaborators-popover';
|
||||
import { ChannelNode, Node, NodeRole } from '@colanode/core';
|
||||
import { Header } from '@/renderer/components/ui/header';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/workspaces/containers/container-breadcrumb';
|
||||
import { ChannelSettings } from './channel-settings';
|
||||
import { NodeBreadcrumb } from '@/renderer/components/layouts/node-breadcrumb';
|
||||
import { ChannelSettings } from '@/renderer/components/channels/channel-settings';
|
||||
import { useContainer } from '@/renderer/contexts/container';
|
||||
import { NodeFullscreenButton } from '@/renderer/components/layouts/node-fullscreen-button';
|
||||
|
||||
interface ChannelHeaderProps {
|
||||
nodes: Node[];
|
||||
@@ -11,10 +13,17 @@ interface ChannelHeaderProps {
|
||||
}
|
||||
|
||||
export const ChannelHeader = ({ nodes, channel }: ChannelHeaderProps) => {
|
||||
const container = useContainer();
|
||||
|
||||
return (
|
||||
<Header>
|
||||
<div className="flex w-full items-center gap-2 px-4">
|
||||
<ContainerBreadcrumb nodes={nodes} />
|
||||
<div className="flex-grow">
|
||||
{container.mode === 'main' && <NodeBreadcrumb nodes={nodes} />}
|
||||
{container.mode === 'modal' && (
|
||||
<NodeFullscreenButton nodeId={channel.id} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<NodeCollaboratorsPopover nodeId={channel.id} nodes={nodes} />
|
||||
<ChannelSettings nodeId={channel.id} />
|
||||
|
||||
@@ -1,17 +1,26 @@
|
||||
import { NodeCollaboratorsPopover } from '@/renderer/components/collaborators/node-collaborators-popover';
|
||||
import { ChatNode } from '@colanode/core';
|
||||
import { Header } from '@/renderer/components/ui/header';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/workspaces/containers/container-breadcrumb';
|
||||
import { NodeBreadcrumb } from '@/renderer/components/layouts/node-breadcrumb';
|
||||
import { useContainer } from '@/renderer/contexts/container';
|
||||
import { NodeFullscreenButton } from '@/renderer/components/layouts/node-fullscreen-button';
|
||||
|
||||
interface ChatHeaderProps {
|
||||
chat: ChatNode;
|
||||
}
|
||||
|
||||
export const ChatHeader = ({ chat }: ChatHeaderProps) => {
|
||||
const container = useContainer();
|
||||
|
||||
return (
|
||||
<Header>
|
||||
<div className="flex w-full items-center gap-2 px-4">
|
||||
<ContainerBreadcrumb nodes={[chat]} />
|
||||
<div className="flex-grow">
|
||||
<NodeBreadcrumb nodes={[chat]} />
|
||||
{container.mode === 'modal' && (
|
||||
<NodeFullscreenButton nodeId={chat.id} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<NodeCollaboratorsPopover nodeId={chat.id} nodes={[chat]} />
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { NodeCollaboratorsPopover } from '@/renderer/components/collaborators/node-collaborators-popover';
|
||||
import { DatabaseNode, Node, NodeRole } from '@colanode/core';
|
||||
import { Header } from '@/renderer/components/ui/header';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/workspaces/containers/container-breadcrumb';
|
||||
import { NodeBreadcrumb } from '@/renderer/components/layouts/node-breadcrumb';
|
||||
import { DatabaseSettings } from '@/renderer/components/databases/database-settings';
|
||||
import { useContainer } from '@/renderer/contexts/container';
|
||||
import { NodeFullscreenButton } from '@/renderer/components/layouts/node-fullscreen-button';
|
||||
|
||||
interface DatabaseHeaderProps {
|
||||
nodes: Node[];
|
||||
@@ -11,10 +13,17 @@ interface DatabaseHeaderProps {
|
||||
}
|
||||
|
||||
export const DatabaseHeader = ({ nodes, database }: DatabaseHeaderProps) => {
|
||||
const container = useContainer();
|
||||
|
||||
return (
|
||||
<Header>
|
||||
<div className="flex w-full items-center gap-2 px-4">
|
||||
<ContainerBreadcrumb nodes={nodes} />
|
||||
<div className="flex-grow">
|
||||
{container.mode === 'main' && <NodeBreadcrumb nodes={nodes} />}
|
||||
{container.mode === 'modal' && (
|
||||
<NodeFullscreenButton nodeId={database.id} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<NodeCollaboratorsPopover nodeId={database.id} nodes={nodes} />
|
||||
<DatabaseSettings nodeId={database.id} />
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { NodeCollaboratorsPopover } from '@/renderer/components/collaborators/node-collaborators-popover';
|
||||
import { FileNode, Node, NodeRole } from '@colanode/core';
|
||||
import { Header } from '@/renderer/components/ui/header';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/workspaces/containers/container-breadcrumb';
|
||||
import { NodeBreadcrumb } from '@/renderer/components/layouts/node-breadcrumb';
|
||||
import { FileSettings } from '@/renderer/components/files/file-settings';
|
||||
import { useContainer } from '@/renderer/contexts/container';
|
||||
import { NodeFullscreenButton } from '@/renderer/components/layouts/node-fullscreen-button';
|
||||
|
||||
interface FileHeaderProps {
|
||||
nodes: Node[];
|
||||
@@ -11,10 +13,17 @@ interface FileHeaderProps {
|
||||
}
|
||||
|
||||
export const FileHeader = ({ nodes, file }: FileHeaderProps) => {
|
||||
const container = useContainer();
|
||||
|
||||
return (
|
||||
<Header>
|
||||
<div className="flex w-full items-center gap-2 px-4">
|
||||
<ContainerBreadcrumb nodes={nodes} />
|
||||
<div className="flex-grow">
|
||||
<NodeBreadcrumb nodes={nodes} />
|
||||
{container.mode === 'modal' && (
|
||||
<NodeFullscreenButton nodeId={file.id} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<NodeCollaboratorsPopover nodeId={file.id} nodes={nodes} />
|
||||
<FileSettings nodeId={file.id} />
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { NodeCollaboratorsPopover } from '@/renderer/components/collaborators/node-collaborators-popover';
|
||||
import { FolderNode, Node, NodeRole } from '@colanode/core';
|
||||
import { Header } from '@/renderer/components/ui/header';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/workspaces/containers/container-breadcrumb';
|
||||
import { NodeBreadcrumb } from '@/renderer/components/layouts/node-breadcrumb';
|
||||
import { FolderSettings } from '@/renderer/components/folders/folder-settings';
|
||||
import { NodeFullscreenButton } from '@/renderer/components/layouts/node-fullscreen-button';
|
||||
import { useContainer } from '@/renderer/contexts/container';
|
||||
|
||||
interface FolderHeaderProps {
|
||||
nodes: Node[];
|
||||
@@ -11,10 +13,17 @@ interface FolderHeaderProps {
|
||||
}
|
||||
|
||||
export const FolderHeader = ({ nodes, folder }: FolderHeaderProps) => {
|
||||
const container = useContainer();
|
||||
|
||||
return (
|
||||
<Header>
|
||||
<div className="flex w-full items-center gap-2 px-4">
|
||||
<ContainerBreadcrumb nodes={nodes} />
|
||||
<div className="flex-grow">
|
||||
<NodeBreadcrumb nodes={nodes} />
|
||||
{container.mode === 'modal' && (
|
||||
<NodeFullscreenButton nodeId={folder.id} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<NodeCollaboratorsPopover nodeId={folder.id} nodes={nodes} />
|
||||
<FolderSettings nodeId={folder.id} />
|
||||
|
||||
14
apps/desktop/src/renderer/components/layouts/layout-main.tsx
Normal file
14
apps/desktop/src/renderer/components/layouts/layout-main.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { ContainerContext } from '@/renderer/contexts/container';
|
||||
import { NodeContainer } from '@/renderer/components/layouts/node-container';
|
||||
|
||||
interface LayoutMainProps {
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
export const LayoutMain = ({ nodeId }: LayoutMainProps) => {
|
||||
return (
|
||||
<ContainerContext.Provider value={{ nodeId, mode: 'main' }}>
|
||||
<NodeContainer nodeId={nodeId} />
|
||||
</ContainerContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { Dialog, DialogContent } from '@/renderer/components/ui/dialog';
|
||||
import { ContainerContext } from '@/renderer/contexts/container';
|
||||
import { NodeContainer } from '@/renderer/components/layouts/node-container';
|
||||
import { useWorkspace } from '@/renderer/contexts/workspace';
|
||||
|
||||
interface LayoutModalProps {
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
export const LayoutModal = ({ nodeId }: LayoutModalProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const [open, setOpen] = React.useState(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!open) {
|
||||
workspace.closeModal();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(open) => {
|
||||
setOpen(open);
|
||||
}}
|
||||
>
|
||||
<DialogContent
|
||||
className="flex h-[calc(100vh-100px)] max-h-full w-8/12 max-w-full flex-col gap-1 overflow-hidden px-0.5 pt-0 md:w-8/12"
|
||||
aria-describedby={undefined}
|
||||
>
|
||||
<ContainerContext.Provider value={{ nodeId, mode: 'modal' }}>
|
||||
<NodeContainer nodeId={nodeId} />
|
||||
</ContainerContext.Provider>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ContainerContext } from '@/renderer/contexts/container';
|
||||
import { NodeContainer } from '@/renderer/components/layouts/node-container';
|
||||
|
||||
interface LayoutRightProps {
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
export const LayoutRight = ({ nodeId }: LayoutRightProps) => {
|
||||
return (
|
||||
<ContainerContext.Provider value={{ nodeId, mode: 'panel' }}>
|
||||
<NodeContainer nodeId={nodeId} />
|
||||
</ContainerContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { ChatNode } from '@colanode/core';
|
||||
import { ChatSidebarItem } from '@/renderer/components/chats/chat-sidebar-item';
|
||||
|
||||
export const SidebarChats = () => {
|
||||
export const LayoutSidebarChats = () => {
|
||||
const workspace = useWorkspace();
|
||||
|
||||
const { data } = useQuery({
|
||||
@@ -23,7 +23,7 @@ import { useAccount } from '@/renderer/contexts/account';
|
||||
import { ChevronsUpDown, LogOut, Plus, Settings } from 'lucide-react';
|
||||
import { useApp } from '@/renderer/contexts/app';
|
||||
|
||||
export function SidebarFooter() {
|
||||
export function LayoutSidebarFooter() {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const app = useApp();
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
} from '@/renderer/components/ui/sidebar';
|
||||
import { ChevronsUpDown, Settings, Plus } from 'lucide-react';
|
||||
|
||||
export const SidebarHeader = () => {
|
||||
export const LayoutSidebarHeader = () => {
|
||||
const workspace = useWorkspace();
|
||||
const account = useAccount();
|
||||
const navigate = useNavigate();
|
||||
@@ -10,7 +10,7 @@ import { SpaceCreateButton } from '@/renderer/components/spaces/space-create-but
|
||||
import { SpaceNode } from '@colanode/core';
|
||||
import { SpaceSidebarItem } from '@/renderer/components/spaces/space-sidebar-item';
|
||||
|
||||
export const SidebarSpaces = () => {
|
||||
export const LayoutSidebarSpaces = () => {
|
||||
const workspace = useWorkspace();
|
||||
const canCreateSpace =
|
||||
workspace.role !== 'guest' && workspace.role !== 'none';
|
||||
@@ -0,0 +1,30 @@
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarHeader,
|
||||
SidebarRail,
|
||||
} from '@/renderer/components/ui/sidebar';
|
||||
|
||||
import { LayoutSidebarHeader } from '@/renderer/components/layouts/layout-sidebar-header';
|
||||
import { LayoutSidebarSpaces } from '@/renderer/components/layouts/layout-sidebar-spaces';
|
||||
import { LayoutSidebarChats } from '@/renderer/components/layouts/layout-sidebar-chats';
|
||||
import { LayoutSidebarFooter } from '@/renderer/components/layouts/layout-sidebar-footer';
|
||||
|
||||
export const LayoutSidebar = () => {
|
||||
return (
|
||||
<Sidebar collapsible="icon">
|
||||
<SidebarHeader>
|
||||
<LayoutSidebarHeader />
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<LayoutSidebarSpaces />
|
||||
<LayoutSidebarChats />
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<LayoutSidebarFooter />
|
||||
</SidebarFooter>
|
||||
<SidebarRail />
|
||||
</Sidebar>
|
||||
);
|
||||
};
|
||||
26
apps/desktop/src/renderer/components/layouts/layout.tsx
Normal file
26
apps/desktop/src/renderer/components/layouts/layout.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { LayoutSidebar } from '@/renderer/components/layouts/layout-sidebar';
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
} from '@/renderer/components/ui/sidebar';
|
||||
import { LayoutMain } from '@/renderer/components/layouts/layout-main';
|
||||
import { LayoutModal } from '@/renderer/components/layouts/layout-modal';
|
||||
|
||||
interface LayoutProps {
|
||||
nodeId?: string | null;
|
||||
modal?: string | null;
|
||||
}
|
||||
|
||||
export const Layout = ({ nodeId, modal }: LayoutProps) => {
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<LayoutSidebar />
|
||||
<SidebarInset>
|
||||
<main className="h-full max-h-screen w-full min-w-128 flex-grow overflow-hidden bg-white">
|
||||
{nodeId && <LayoutMain nodeId={nodeId} />}
|
||||
</main>
|
||||
{modal && <LayoutModal nodeId={modal} />}
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
);
|
||||
};
|
||||
@@ -8,13 +8,11 @@ import { RecordBreadcrumbItem } from '@/renderer/components/records/record-bread
|
||||
import { FolderBreadcrumbItem } from '@/renderer/components/folders/folder-breadcrumb-item';
|
||||
import { FileBreadcrumbItem } from '@/renderer/components/files/file-breadcrumb-item';
|
||||
|
||||
interface ContainerBreadcrumbItemProps {
|
||||
interface NodeBreadcrumbItemProps {
|
||||
node: Node;
|
||||
}
|
||||
|
||||
export const ContainerBreadcrumbItem = ({
|
||||
node,
|
||||
}: ContainerBreadcrumbItemProps) => {
|
||||
export const NodeBreadcrumbItem = ({ node }: NodeBreadcrumbItemProps) => {
|
||||
switch (node.type) {
|
||||
case 'space':
|
||||
return <SpaceBreadcrumbItem node={node} />;
|
||||
@@ -14,16 +14,16 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@/renderer/components/ui/dropdown-menu';
|
||||
import { useWorkspace } from '@/renderer/contexts/workspace';
|
||||
import { ContainerBreadcrumbItem } from '@/renderer/components/workspaces/containers/container-breadcrumb-item';
|
||||
import { NodeBreadcrumbItem } from '@/renderer/components/layouts/node-breadcrumb-item';
|
||||
|
||||
interface ContainerBreadcrumbProps {
|
||||
interface NodeBreadcrumbProps {
|
||||
nodes: Node[];
|
||||
}
|
||||
|
||||
const isClickable = (type: string) =>
|
||||
type !== NodeTypes.Space && type !== NodeTypes.Message;
|
||||
|
||||
export const ContainerBreadcrumb = ({ nodes }: ContainerBreadcrumbProps) => {
|
||||
export const NodeBreadcrumb = ({ nodes }: NodeBreadcrumbProps) => {
|
||||
const workspace = useWorkspace();
|
||||
|
||||
// Show ellipsis if we have more than 3 nodes (first + last two)
|
||||
@@ -36,7 +36,7 @@ export const ContainerBreadcrumb = ({ nodes }: ContainerBreadcrumbProps) => {
|
||||
const ellipsisNodes = showEllipsis ? nodes.slice(1, -2) : [];
|
||||
|
||||
return (
|
||||
<Breadcrumb className="flex-grow">
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
{visibleNodes.map((node, index) => {
|
||||
const isFirst = index === 0;
|
||||
@@ -57,7 +57,7 @@ export const ContainerBreadcrumb = ({ nodes }: ContainerBreadcrumbProps) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ContainerBreadcrumbItem node={node} />
|
||||
<NodeBreadcrumbItem node={node} />
|
||||
</BreadcrumbItem>
|
||||
{showEllipsis && isFirst && (
|
||||
<React.Fragment>
|
||||
@@ -89,7 +89,7 @@ export const ContainerBreadcrumb = ({ nodes }: ContainerBreadcrumbProps) => {
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<ContainerBreadcrumbItem node={ellipsisNode} />
|
||||
<NodeBreadcrumbItem node={ellipsisNode} />
|
||||
</BreadcrumbItem>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { PageContainer } from '@/renderer/components/pages/page-container';
|
||||
import { ChannelContainer } from '@/renderer/components/channels/channel-container';
|
||||
import { DatabaseContainer } from '@/renderer/components/databases/database-container';
|
||||
@@ -8,13 +7,13 @@ import { FolderContainer } from '@/renderer/components/folders/folder-container'
|
||||
import { FileContainer } from '@/renderer/components/files/file-container';
|
||||
import { getIdType, IdType } from '@colanode/core';
|
||||
|
||||
export const Container = () => {
|
||||
const { nodeId } = useParams<{ nodeId: string }>();
|
||||
if (!nodeId) {
|
||||
return null;
|
||||
}
|
||||
interface NodeContainerProps {
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
export const NodeContainer = ({ nodeId }: NodeContainerProps) => {
|
||||
const idType = getIdType(nodeId);
|
||||
|
||||
switch (idType) {
|
||||
case IdType.Channel:
|
||||
return <ChannelContainer nodeId={nodeId} />;
|
||||
@@ -0,0 +1,19 @@
|
||||
import { useWorkspace } from '@/renderer/contexts/workspace';
|
||||
import { Fullscreen } from 'lucide-react';
|
||||
|
||||
interface NodeFullscreenButtonProps {
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
export const NodeFullscreenButton = ({ nodeId }: NodeFullscreenButtonProps) => {
|
||||
const workspace = useWorkspace();
|
||||
|
||||
return (
|
||||
<Fullscreen
|
||||
className="size-5 cursor-pointer text-muted-foreground hover:text-foreground"
|
||||
onClick={() => {
|
||||
workspace.navigateToNode(nodeId);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -7,11 +7,13 @@ import { PageSidebarItem } from '@/renderer/components/pages/page-sidebar-item';
|
||||
import { DatabaseSidebarItem } from '@/renderer/components/databases/database-sidiebar-item';
|
||||
import { FolderSidebarItem } from '@/renderer/components/folders/folder-sidebar-item';
|
||||
|
||||
interface SidebarItemProps {
|
||||
interface NodeSidebarItemProps {
|
||||
node: Node;
|
||||
}
|
||||
|
||||
export const SidebarItem = ({ node }: SidebarItemProps): React.ReactNode => {
|
||||
export const NodeSidebarItem = ({
|
||||
node,
|
||||
}: NodeSidebarItemProps): React.ReactNode => {
|
||||
switch (node.type) {
|
||||
case 'space':
|
||||
return <SpaceSidebarItem node={node} />;
|
||||
@@ -1,8 +1,10 @@
|
||||
import { NodeCollaboratorsPopover } from '@/renderer/components/collaborators/node-collaborators-popover';
|
||||
import { PageNode, Node, NodeRole } from '@colanode/core';
|
||||
import { Header } from '@/renderer/components/ui/header';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/workspaces/containers/container-breadcrumb';
|
||||
import { NodeBreadcrumb } from '@/renderer/components/layouts/node-breadcrumb';
|
||||
import { PageSettings } from '@/renderer/components/pages/page-settings';
|
||||
import { useContainer } from '@/renderer/contexts/container';
|
||||
import { NodeFullscreenButton } from '@/renderer/components/layouts/node-fullscreen-button';
|
||||
|
||||
interface PageHeaderProps {
|
||||
nodes: Node[];
|
||||
@@ -11,10 +13,17 @@ interface PageHeaderProps {
|
||||
}
|
||||
|
||||
export const PageHeader = ({ nodes, page }: PageHeaderProps) => {
|
||||
const container = useContainer();
|
||||
|
||||
return (
|
||||
<Header>
|
||||
<div className="flex w-full items-center gap-2 px-4">
|
||||
<ContainerBreadcrumb nodes={nodes} />
|
||||
<div className="flex-grow">
|
||||
<NodeBreadcrumb nodes={nodes} />
|
||||
{container.mode === 'modal' && (
|
||||
<NodeFullscreenButton nodeId={page.id} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<NodeCollaboratorsPopover nodeId={page.id} nodes={nodes} />
|
||||
<PageSettings nodeId={page.id} />
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { NodeCollaboratorsPopover } from '@/renderer/components/collaborators/node-collaborators-popover';
|
||||
import { RecordNode, Node, NodeRole } from '@colanode/core';
|
||||
import { Header } from '@/renderer/components/ui/header';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/workspaces/containers/container-breadcrumb';
|
||||
import { NodeBreadcrumb } from '@/renderer/components/layouts/node-breadcrumb';
|
||||
import { RecordSettings } from '@/renderer/components/records/record-settings';
|
||||
import { useContainer } from '@/renderer/contexts/container';
|
||||
import { NodeFullscreenButton } from '@/renderer/components/layouts/node-fullscreen-button';
|
||||
|
||||
interface RecordHeaderProps {
|
||||
nodes: Node[];
|
||||
@@ -11,10 +13,17 @@ interface RecordHeaderProps {
|
||||
}
|
||||
|
||||
export const RecordHeader = ({ nodes, record }: RecordHeaderProps) => {
|
||||
const container = useContainer();
|
||||
|
||||
return (
|
||||
<Header>
|
||||
<div className="flex w-full items-center gap-2 px-4">
|
||||
<ContainerBreadcrumb nodes={nodes} />
|
||||
<div className="flex-grow">
|
||||
{container.mode === 'main' && <NodeBreadcrumb nodes={nodes} />}
|
||||
{container.mode === 'modal' && (
|
||||
<NodeFullscreenButton nodeId={record.id} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<NodeCollaboratorsPopover nodeId={record.id} nodes={nodes} />
|
||||
<RecordSettings nodeId={record.id} />
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { ChannelCreateDialog } from '@/renderer/components/channels/channel-create-dialog';
|
||||
import { PageCreateDialog } from '@/renderer/components/pages/page-create-dialog';
|
||||
import { DatabaseCreateDialog } from '@/renderer/components/databases/database-create-dialog';
|
||||
import { SidebarItem } from '@/renderer/components/workspaces/sidebars/sidebar-item';
|
||||
import { NodeSidebarItem } from '@/renderer/components/layouts/node-sidebar-item';
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
@@ -170,7 +170,7 @@ export const SpaceSidebarItem = ({ node }: SpaceSidebarItemProps) => {
|
||||
<SidebarMenuSubButton
|
||||
isActive={workspace.isNodeActive(child.id)}
|
||||
>
|
||||
<SidebarItem node={child} />
|
||||
<NodeSidebarItem node={child} />
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
|
||||
@@ -10,8 +10,6 @@ const DialogTrigger = DialogPrimitive.Trigger;
|
||||
|
||||
const DialogPortal = DialogPrimitive.Portal;
|
||||
|
||||
const DialogClose = DialogPrimitive.Close;
|
||||
|
||||
const DialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
@@ -42,15 +40,29 @@ const DialogContent = React.forwardRef<
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<Cross2Icon className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
));
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
const DialogClose = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Close>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Close>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Close
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<Cross2Icon className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
));
|
||||
DialogClose.displayName = DialogPrimitive.Close.displayName;
|
||||
|
||||
const DialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { match } from 'ts-pattern';
|
||||
import { ChannelContainer } from '@/renderer/components/channels/channel-container';
|
||||
import { PageContainer } from '@/renderer/components/pages/page-container';
|
||||
import { DatabaseContainer } from '@/renderer/components/databases/database-container';
|
||||
import { RecordContainer } from '@/renderer/components/records/record-container';
|
||||
import { ChatContainer } from '@/renderer/components/chats/chat-container';
|
||||
import { FolderContainer } from '@/renderer/components/folders/folder-container';
|
||||
import { FileContainer } from '@/renderer/components/files/file-container';
|
||||
import { getIdType, IdType } from '@colanode/core';
|
||||
|
||||
interface ModalContentProps {
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
export const ModalContent = ({ nodeId }: ModalContentProps) => {
|
||||
const idType = getIdType(nodeId);
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col">
|
||||
{match(idType)
|
||||
.with(IdType.Channel, () => <ChannelContainer nodeId={nodeId} />)
|
||||
.with(IdType.Page, () => <PageContainer nodeId={nodeId} />)
|
||||
.with(IdType.Database, () => <DatabaseContainer nodeId={nodeId} />)
|
||||
.with(IdType.Record, () => <RecordContainer nodeId={nodeId} />)
|
||||
.with(IdType.Chat, () => <ChatContainer nodeId={nodeId} />)
|
||||
.with(IdType.Folder, () => <FolderContainer nodeId={nodeId} />)
|
||||
.with(IdType.File, () => <FileContainer nodeId={nodeId} />)
|
||||
.otherwise(() => null)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
import { useWorkspace } from '@/renderer/contexts/workspace';
|
||||
import { Fullscreen } from 'lucide-react';
|
||||
|
||||
interface ModalHeaderProps {
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
export const ModalHeader = ({ nodeId }: ModalHeaderProps) => {
|
||||
const workspace = useWorkspace();
|
||||
return (
|
||||
<div className="flex h-10 min-h-10 items-center justify-between bg-white p-2">
|
||||
<button
|
||||
className="cursor-pointer hover:bg-gray-50"
|
||||
onClick={() => workspace.navigateToNode(nodeId)}
|
||||
>
|
||||
<Fullscreen className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
} from '@/renderer/components/ui/dialog';
|
||||
import { ModalHeader } from '@/renderer/components/workspaces/modals/modal-header';
|
||||
import { ModalContent } from '@/renderer/components/workspaces/modals/modal-content';
|
||||
|
||||
interface ModalProps {
|
||||
nodeId: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const Modal = ({ nodeId, onClose }: ModalProps) => {
|
||||
const [open, setOpen] = React.useState(true);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(open) => {
|
||||
setOpen(open);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<DialogContent
|
||||
className="flex h-[calc(100vh-100px)] max-h-full w-8/12 max-w-full flex-col gap-1 overflow-hidden px-0.5 pt-2 md:w-8/12"
|
||||
aria-describedby={undefined}
|
||||
>
|
||||
<DialogTitle>
|
||||
<ModalHeader nodeId={nodeId} />
|
||||
</DialogTitle>
|
||||
<ModalContent nodeId={nodeId} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
import {
|
||||
Sidebar as SidebarWrapper,
|
||||
SidebarContent as SidebarContentWrapper,
|
||||
SidebarFooter as SidebarFooterWrapper,
|
||||
SidebarHeader as SidebarHeaderWrapper,
|
||||
SidebarRail as SidebarRailWrapper,
|
||||
} from '@/renderer/components/ui/sidebar';
|
||||
|
||||
import { SidebarHeader } from '@/renderer/components/workspaces/sidebars/sidebar-header';
|
||||
import { SidebarSpaces } from '@/renderer/components/workspaces/sidebars/sidebar-spaces';
|
||||
import { SidebarChats } from '@/renderer/components/workspaces/sidebars/sidebar-chats';
|
||||
import { SidebarFooter } from '@/renderer/components/workspaces/sidebars/sidebar-footer';
|
||||
|
||||
export const Sidebar = () => {
|
||||
return (
|
||||
<SidebarWrapper collapsible="icon">
|
||||
<SidebarHeaderWrapper>
|
||||
<SidebarHeader />
|
||||
</SidebarHeaderWrapper>
|
||||
<SidebarContentWrapper>
|
||||
<SidebarSpaces />
|
||||
<SidebarChats />
|
||||
</SidebarContentWrapper>
|
||||
<SidebarFooterWrapper>
|
||||
<SidebarFooter />
|
||||
</SidebarFooterWrapper>
|
||||
<SidebarRailWrapper />
|
||||
</SidebarWrapper>
|
||||
);
|
||||
};
|
||||
@@ -1,19 +1,9 @@
|
||||
import React from 'react';
|
||||
import { Sidebar } from '@/renderer/components/workspaces/sidebars/sidebar';
|
||||
import { WorkspaceContext } from '@/renderer/contexts/workspace';
|
||||
import {
|
||||
Outlet,
|
||||
useNavigate,
|
||||
useParams,
|
||||
useSearchParams,
|
||||
} from 'react-router-dom';
|
||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||
import { useAccount } from '@/renderer/contexts/account';
|
||||
import { Modal } from '@/renderer/components/workspaces/modals/modal';
|
||||
import { WorkspaceSettingsDialog } from '@/renderer/components/workspaces/workspace-settings-dialog';
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
} from '@/renderer/components/ui/sidebar';
|
||||
import { Layout } from '@/renderer/components/layouts/layout';
|
||||
|
||||
export const Workspace = () => {
|
||||
const { userId, nodeId } = useParams<{ userId: string; nodeId?: string }>();
|
||||
@@ -35,6 +25,12 @@ export const Workspace = () => {
|
||||
...workspace,
|
||||
navigateToNode(nodeId) {
|
||||
navigate(`/${userId}/${nodeId}`);
|
||||
if (nodeId === modal) {
|
||||
setSearchParams((prev) => {
|
||||
prev.delete('modal');
|
||||
return prev;
|
||||
});
|
||||
}
|
||||
},
|
||||
isNodeActive(id) {
|
||||
return id === nodeId;
|
||||
@@ -47,6 +43,12 @@ export const Workspace = () => {
|
||||
};
|
||||
});
|
||||
},
|
||||
closeModal() {
|
||||
setSearchParams((prev) => {
|
||||
prev.delete('modal');
|
||||
return prev;
|
||||
});
|
||||
},
|
||||
openSettings() {
|
||||
setOpenSettings(true);
|
||||
},
|
||||
@@ -60,32 +62,13 @@ export const Workspace = () => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SidebarProvider>
|
||||
<Sidebar />
|
||||
<SidebarInset>
|
||||
<main className="h-full max-h-screen w-full min-w-128 flex-grow overflow-hidden bg-white">
|
||||
<Outlet />
|
||||
</main>
|
||||
{modal && (
|
||||
<Modal
|
||||
nodeId={modal}
|
||||
key={modal}
|
||||
onClose={() => {
|
||||
setSearchParams((prev) => {
|
||||
prev.delete('modal');
|
||||
return prev;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</SidebarInset>
|
||||
{openSettings && (
|
||||
<WorkspaceSettingsDialog
|
||||
open={openSettings}
|
||||
onOpenChange={setOpenSettings}
|
||||
/>
|
||||
)}
|
||||
</SidebarProvider>
|
||||
<Layout nodeId={nodeId} modal={modal} />
|
||||
{openSettings && (
|
||||
<WorkspaceSettingsDialog
|
||||
open={openSettings}
|
||||
onOpenChange={setOpenSettings}
|
||||
/>
|
||||
)}
|
||||
</WorkspaceContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
12
apps/desktop/src/renderer/contexts/container.ts
Normal file
12
apps/desktop/src/renderer/contexts/container.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
interface ContainerContext {
|
||||
nodeId: string;
|
||||
mode: 'main' | 'modal' | 'panel';
|
||||
}
|
||||
|
||||
export const ContainerContext = createContext<ContainerContext>(
|
||||
{} as ContainerContext
|
||||
);
|
||||
|
||||
export const useContainer = () => useContext(ContainerContext);
|
||||
@@ -5,6 +5,7 @@ interface WorkspaceContext extends Workspace {
|
||||
navigateToNode: (nodeId: string) => void;
|
||||
isNodeActive: (nodeId: string) => boolean;
|
||||
openModal: (nodeId: string) => void;
|
||||
closeModal: () => void;
|
||||
openSettings: () => void;
|
||||
markAsSeen: (nodeId: string, versionId: string) => void;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import { WorkspaceCreate } from '@/renderer/components/workspaces/workspace-crea
|
||||
import { Workspace } from '@/renderer/components/workspaces/workspace';
|
||||
import { WorkspaceRedirect } from '@/renderer/components/workspaces/workspace-redirect';
|
||||
import { createHashRouter, RouterProvider } from 'react-router-dom';
|
||||
import { Container } from '@/renderer/components/workspaces/containers/container';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { Login } from '@/renderer/components/accounts/login';
|
||||
@@ -33,14 +32,8 @@ const router = createHashRouter([
|
||||
element: <Login />,
|
||||
},
|
||||
{
|
||||
path: ':userId',
|
||||
path: ':userId/:nodeId?',
|
||||
element: <Workspace />,
|
||||
children: [
|
||||
{
|
||||
path: ':nodeId',
|
||||
element: <Container />,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user