mirror of
https://github.com/colanode/colanode.git
synced 2025-12-16 03:37:51 +01:00
Implement drag and drop for reordering container tabs
This commit is contained in:
@@ -7,7 +7,7 @@ import {
|
||||
ContainerHeader,
|
||||
ContainerSettings,
|
||||
} from '@/renderer/components/ui/container';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/layouts/breadcrumbs/container-breadrumb';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/layouts/containers/container-breadrumb';
|
||||
import { useEntryContainer } from '@/renderer/hooks/use-entry-container';
|
||||
import { ChannelSettings } from '@/renderer/components/channels/channel-settings';
|
||||
import { Conversation } from '@/renderer/components/messages/conversation';
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
ContainerHeader,
|
||||
ContainerSettings,
|
||||
} from '@/renderer/components/ui/container';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/layouts/breadcrumbs/container-breadrumb';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/layouts/containers/container-breadrumb';
|
||||
import { ChatNotFound } from '@/renderer/components/chats/chat-not-found';
|
||||
import { EntryCollaboratorsPopover } from '@/renderer/components/collaborators/entry-collaborators-popover';
|
||||
import { Conversation } from '@/renderer/components/messages/conversation';
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
ContainerHeader,
|
||||
ContainerSettings,
|
||||
} from '@/renderer/components/ui/container';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/layouts/breadcrumbs/container-breadrumb';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/layouts/containers/container-breadrumb';
|
||||
import { useEntryContainer } from '@/renderer/hooks/use-entry-container';
|
||||
import { useEntryRadar } from '@/renderer/hooks/use-entry-radar';
|
||||
import { DatabaseSettings } from '@/renderer/components/databases/database-settings';
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
ContainerHeader,
|
||||
ContainerSettings,
|
||||
} from '@/renderer/components/ui/container';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/layouts/breadcrumbs/container-breadrumb';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/layouts/containers/container-breadrumb';
|
||||
import { FileNotFound } from '@/renderer/components/files/file-not-found';
|
||||
import { useFileContainer } from '@/renderer/hooks/use-file-container';
|
||||
import { FileSettings } from '@/renderer/components/files/file-settings';
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
ContainerHeader,
|
||||
ContainerSettings,
|
||||
} from '@/renderer/components/ui/container';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/layouts/breadcrumbs/container-breadrumb';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/layouts/containers/container-breadrumb';
|
||||
import { useEntryContainer } from '@/renderer/hooks/use-entry-container';
|
||||
import { useEntryRadar } from '@/renderer/hooks/use-entry-radar';
|
||||
import { FolderSettings } from '@/renderer/components/folders/folder-settings';
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@/renderer/components/ui/dropdown-menu';
|
||||
import { useLayout } from '@/renderer/contexts/layout';
|
||||
import { ContainerBreadcrumbItem } from '@/renderer/components/layouts/breadcrumbs/container-breadcrumb-item';
|
||||
import { ContainerBreadcrumbItem } from '@/renderer/components/layouts/containers/container-breadcrumb-item';
|
||||
|
||||
interface ContainerBreadcrumbProps {
|
||||
breadcrumb: string[];
|
||||
@@ -0,0 +1,36 @@
|
||||
import { match } from 'ts-pattern';
|
||||
import { getIdType, IdType } from '@colanode/core';
|
||||
|
||||
import { ContainerTab } from '@/shared/types/workspaces';
|
||||
import { TabsContent } from '@/renderer/components/ui/tabs';
|
||||
import { ChannelContainer } from '@/renderer/components/channels/channel-container';
|
||||
import { ChatContainer } from '@/renderer/components/chats/chat-container';
|
||||
import { DatabaseContainer } from '@/renderer/components/databases/database-container';
|
||||
import { FileContainer } from '@/renderer/components/files/file-container';
|
||||
import { FolderContainer } from '@/renderer/components/folders/folder-container';
|
||||
import { PageContainer } from '@/renderer/components/pages/page-container';
|
||||
import { RecordContainer } from '@/renderer/components/records/record-container';
|
||||
|
||||
interface ContainerTabContentProps {
|
||||
tab: ContainerTab;
|
||||
}
|
||||
|
||||
export const ContainerTabContent = ({ tab }: ContainerTabContentProps) => {
|
||||
return (
|
||||
<TabsContent
|
||||
value={tab.id}
|
||||
key={tab.id}
|
||||
className="h-full min-h-full w-full min-w-full m-0 pt-2"
|
||||
>
|
||||
{match(getIdType(tab.id))
|
||||
.with(IdType.Channel, () => <ChannelContainer channelId={tab.id} />)
|
||||
.with(IdType.Page, () => <PageContainer pageId={tab.id} />)
|
||||
.with(IdType.Database, () => <DatabaseContainer databaseId={tab.id} />)
|
||||
.with(IdType.Record, () => <RecordContainer recordId={tab.id} />)
|
||||
.with(IdType.Chat, () => <ChatContainer chatId={tab.id} />)
|
||||
.with(IdType.Folder, () => <FolderContainer folderId={tab.id} />)
|
||||
.with(IdType.File, () => <FileContainer fileId={tab.id} />)
|
||||
.otherwise(() => null)}
|
||||
</TabsContent>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
import React from 'react';
|
||||
import { match } from 'ts-pattern';
|
||||
import { getIdType, IdType } from '@colanode/core';
|
||||
import { X } from 'lucide-react';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
|
||||
import { TabsTrigger } from '@/renderer/components/ui/tabs';
|
||||
import { ContainerTab } from '@/shared/types/workspaces';
|
||||
import { cn } from '@/shared/lib/utils';
|
||||
import { ChannelContainerTab } from '@/renderer/components/channels/channel-container-tab';
|
||||
import { FileContainerTab } from '@/renderer/components/files/file-container-tab';
|
||||
import { DatabaseContainerTab } from '@/renderer/components/databases/database-container-tab';
|
||||
import { RecordContainerTab } from '@/renderer/components/records/record-container-tab';
|
||||
import { FolderContainerTab } from '@/renderer/components/folders/folder-container-tab';
|
||||
import { ChatContainerTab } from '@/renderer/components/chats/chat-container-tab';
|
||||
import { PageContainerTab } from '@/renderer/components/pages/page-container-tab';
|
||||
|
||||
interface ContainerTabTriggerProps {
|
||||
tab: ContainerTab;
|
||||
onClose: () => void;
|
||||
onMove: (before: string | null) => void;
|
||||
}
|
||||
|
||||
export const ContainerTabTrigger = ({
|
||||
tab,
|
||||
onClose,
|
||||
onMove,
|
||||
}: ContainerTabTriggerProps) => {
|
||||
const [, dragRef] = useDrag<string>({
|
||||
type: 'container-tab',
|
||||
item: tab.id,
|
||||
canDrag: () => true,
|
||||
end: (_item, monitor) => {
|
||||
const dropResult = monitor.getDropResult<{ before: string | null }>();
|
||||
if (!dropResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
onMove(dropResult.before);
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
}),
|
||||
});
|
||||
|
||||
const [dropMonitor, dropRef] = useDrop({
|
||||
accept: 'container-tab',
|
||||
drop: () => ({
|
||||
before: tab.id,
|
||||
}),
|
||||
collect: (monitor) => ({
|
||||
isOver: monitor.isOver(),
|
||||
canDrop: monitor.canDrop(),
|
||||
}),
|
||||
});
|
||||
|
||||
const buttonRef = React.useRef<HTMLButtonElement>(null);
|
||||
const dragDropRef = dragRef(dropRef(buttonRef));
|
||||
|
||||
return (
|
||||
<TabsTrigger
|
||||
value={tab.id}
|
||||
key={tab.id}
|
||||
className={cn(
|
||||
'overflow-hidden rounded-b-none bg-muted py-2 data-[state=active]:z-10 data-[state=active]:shadow-none h-10 group/tab app-no-drag-region flex items-center justify-between gap-2',
|
||||
tab.preview && 'italic',
|
||||
dropMonitor.isOver &&
|
||||
dropMonitor.canDrop &&
|
||||
'border-l-2 border-blue-300'
|
||||
)}
|
||||
onAuxClick={(e) => {
|
||||
if (e.button === 1) {
|
||||
e.preventDefault();
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
ref={dragDropRef as React.LegacyRef<HTMLButtonElement>}
|
||||
>
|
||||
<div className="overflow-hidden truncate">
|
||||
{match(getIdType(tab.id))
|
||||
.with(IdType.Channel, () => (
|
||||
<ChannelContainerTab channelId={tab.id} />
|
||||
))
|
||||
.with(IdType.Page, () => <PageContainerTab pageId={tab.id} />)
|
||||
.with(IdType.Database, () => (
|
||||
<DatabaseContainerTab databaseId={tab.id} />
|
||||
))
|
||||
.with(IdType.Record, () => <RecordContainerTab recordId={tab.id} />)
|
||||
.with(IdType.Chat, () => <ChatContainerTab chatId={tab.id} />)
|
||||
.with(IdType.Folder, () => <FolderContainerTab folderId={tab.id} />)
|
||||
.with(IdType.File, () => <FileContainerTab fileId={tab.id} />)
|
||||
.otherwise(() => null)}
|
||||
</div>
|
||||
<div
|
||||
className="opacity-0 group-hover/tab:opacity-100 group-data-[state=active]/tab:opacity-100 transition-opacity duration-200 flex-shrink-0"
|
||||
onClick={() => onClose()}
|
||||
>
|
||||
<X className="size-4 text-muted-foreground hover:text-primary" />
|
||||
</div>
|
||||
</TabsTrigger>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
import { useDrop } from 'react-dnd';
|
||||
|
||||
import { ScrollArea, ScrollBar } from '@/renderer/components/ui/scroll-area';
|
||||
import { Tabs, TabsList } from '@/renderer/components/ui/tabs';
|
||||
import { ContainerTab } from '@/shared/types/workspaces';
|
||||
import { ContainerTabTrigger } from '@/renderer/components/layouts/containers/container-tab-trigger';
|
||||
import { ContainerTabContent } from '@/renderer/components/layouts/containers/container-tab-content';
|
||||
import { cn } from '@/shared/lib/utils';
|
||||
|
||||
interface ContainerTabsProps {
|
||||
tabs: ContainerTab[];
|
||||
onTabChange: (value: string) => void;
|
||||
onFocus: () => void;
|
||||
onClose: (value: string) => void;
|
||||
onMove: (tab: string, before: string | null) => void;
|
||||
}
|
||||
|
||||
export const ContainerTabs = ({
|
||||
tabs,
|
||||
onTabChange,
|
||||
onFocus,
|
||||
onClose,
|
||||
onMove,
|
||||
}: ContainerTabsProps) => {
|
||||
const activeTab = tabs.find((t) => t.active)?.id;
|
||||
|
||||
const [dropMonitor, dropRef] = useDrop({
|
||||
accept: 'container-tab',
|
||||
drop: () => ({
|
||||
before: null,
|
||||
}),
|
||||
collect: (monitor) => ({
|
||||
isOver: monitor.isOver(),
|
||||
canDrop: monitor.canDrop(),
|
||||
}),
|
||||
});
|
||||
|
||||
const buttonRef = React.useRef<HTMLDivElement>(null);
|
||||
const dragDropRef = dropRef(buttonRef);
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
defaultValue={tabs[0]?.id}
|
||||
value={activeTab}
|
||||
onValueChange={onTabChange}
|
||||
onFocus={onFocus}
|
||||
className="h-full min-h-full w-full min-w-full flex flex-col"
|
||||
>
|
||||
<ScrollArea>
|
||||
<TabsList className="h-10 bg-slate-50 w-full justify-start p-0 app-drag-region">
|
||||
{tabs.map((tab) => (
|
||||
<ContainerTabTrigger
|
||||
key={tab.id}
|
||||
tab={tab}
|
||||
onClose={() => onClose(tab.id)}
|
||||
onMove={(before) => onMove(tab.id, before)}
|
||||
/>
|
||||
))}
|
||||
<div
|
||||
ref={dragDropRef as React.LegacyRef<HTMLDivElement>}
|
||||
className={cn(
|
||||
'h-full w-10',
|
||||
dropMonitor.isOver &&
|
||||
dropMonitor.canDrop &&
|
||||
'border-l-2 border-blue-300'
|
||||
)}
|
||||
/>
|
||||
</TabsList>
|
||||
<ScrollBar orientation="horizontal" />
|
||||
</ScrollArea>
|
||||
<div className="flex-grow overflow-hidden">
|
||||
{tabs.map((tab) => (
|
||||
<ContainerTabContent key={tab.id} tab={tab} />
|
||||
))}
|
||||
</div>
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
@@ -1,125 +0,0 @@
|
||||
import { X } from 'lucide-react';
|
||||
import { match } from 'ts-pattern';
|
||||
import { getIdType, IdType } from '@colanode/core';
|
||||
|
||||
import { ScrollArea, ScrollBar } from '@/renderer/components/ui/scroll-area';
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from '@/renderer/components/ui/tabs';
|
||||
import { cn } from '@/shared/lib/utils';
|
||||
import { ChannelContainer } from '@/renderer/components/channels/channel-container';
|
||||
import { ChatContainer } from '@/renderer/components/chats/chat-container';
|
||||
import { DatabaseContainer } from '@/renderer/components/databases/database-container';
|
||||
import { FileContainer } from '@/renderer/components/files/file-container';
|
||||
import { FolderContainer } from '@/renderer/components/folders/folder-container';
|
||||
import { PageContainer } from '@/renderer/components/pages/page-container';
|
||||
import { RecordContainer } from '@/renderer/components/records/record-container';
|
||||
import { ChannelContainerTab } from '@/renderer/components/channels/channel-container-tab';
|
||||
import { DatabaseContainerTab } from '@/renderer/components/databases/database-container-tab';
|
||||
import { FileContainerTab } from '@/renderer/components/files/file-container-tab';
|
||||
import { FolderContainerTab } from '@/renderer/components/folders/folder-container-tab';
|
||||
import { PageContainerTab } from '@/renderer/components/pages/page-container-tab';
|
||||
import { RecordContainerTab } from '@/renderer/components/records/record-container-tab';
|
||||
import { ChatContainerTab } from '@/renderer/components/chats/chat-container-tab';
|
||||
import { ContainerTab } from '@/shared/types/workspaces';
|
||||
|
||||
interface LayoutTabsProps {
|
||||
tabs: ContainerTab[];
|
||||
onTabChange: (value: string) => void;
|
||||
onFocus: () => void;
|
||||
onClose: (value: string) => void;
|
||||
}
|
||||
|
||||
export const LayoutTabs = ({
|
||||
tabs,
|
||||
onTabChange,
|
||||
onFocus,
|
||||
onClose,
|
||||
}: LayoutTabsProps) => {
|
||||
const activeTab = tabs.find((t) => t.active)?.id;
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
defaultValue={tabs[0]?.id}
|
||||
value={activeTab}
|
||||
onValueChange={onTabChange}
|
||||
onFocus={onFocus}
|
||||
className="h-full min-h-full w-full min-w-full flex flex-col"
|
||||
>
|
||||
<ScrollArea>
|
||||
<TabsList className="h-10 bg-slate-50 w-full justify-start p-0 app-drag-region">
|
||||
{tabs.map((tab) => (
|
||||
<TabsTrigger
|
||||
value={tab.id}
|
||||
key={tab.id}
|
||||
className={cn(
|
||||
'overflow-hidden rounded-b-none bg-muted py-2 data-[state=active]:z-10 data-[state=active]:shadow-none h-10 group/tab app-no-drag-region flex items-center justify-between gap-2',
|
||||
tab.preview && 'italic'
|
||||
)}
|
||||
onAuxClick={(e) => {
|
||||
if (e.button === 1) {
|
||||
e.preventDefault();
|
||||
onClose(tab.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="overflow-hidden truncate">
|
||||
{match(getIdType(tab.id))
|
||||
.with(IdType.Channel, () => (
|
||||
<ChannelContainerTab channelId={tab.id} />
|
||||
))
|
||||
.with(IdType.Page, () => <PageContainerTab pageId={tab.id} />)
|
||||
.with(IdType.Database, () => (
|
||||
<DatabaseContainerTab databaseId={tab.id} />
|
||||
))
|
||||
.with(IdType.Record, () => (
|
||||
<RecordContainerTab recordId={tab.id} />
|
||||
))
|
||||
.with(IdType.Chat, () => <ChatContainerTab chatId={tab.id} />)
|
||||
.with(IdType.Folder, () => (
|
||||
<FolderContainerTab folderId={tab.id} />
|
||||
))
|
||||
.with(IdType.File, () => <FileContainerTab fileId={tab.id} />)
|
||||
.otherwise(() => null)}
|
||||
</div>
|
||||
<div
|
||||
className="opacity-0 group-hover/tab:opacity-100 group-data-[state=active]/tab:opacity-100 transition-opacity duration-200 flex-shrink-0"
|
||||
onClick={() => onClose(tab.id)}
|
||||
>
|
||||
<X className="size-4 text-muted-foreground hover:text-primary" />
|
||||
</div>
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
|
||||
<ScrollBar orientation="horizontal" />
|
||||
</ScrollArea>
|
||||
<div className="flex-grow overflow-hidden">
|
||||
{tabs.map((tab) => (
|
||||
<TabsContent
|
||||
value={tab.id}
|
||||
key={tab.id}
|
||||
className="h-full min-h-full w-full min-w-full m-0 pt-2"
|
||||
>
|
||||
{match(getIdType(tab.id))
|
||||
.with(IdType.Channel, () => (
|
||||
<ChannelContainer channelId={tab.id} />
|
||||
))
|
||||
.with(IdType.Page, () => <PageContainer pageId={tab.id} />)
|
||||
.with(IdType.Database, () => (
|
||||
<DatabaseContainer databaseId={tab.id} />
|
||||
))
|
||||
.with(IdType.Record, () => <RecordContainer recordId={tab.id} />)
|
||||
.with(IdType.Chat, () => <ChatContainer chatId={tab.id} />)
|
||||
.with(IdType.Folder, () => <FolderContainer folderId={tab.id} />)
|
||||
.with(IdType.File, () => <FileContainer fileId={tab.id} />)
|
||||
.otherwise(() => null)}
|
||||
</TabsContent>
|
||||
))}
|
||||
</div>
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Resizable } from 're-resizable';
|
||||
|
||||
import { LayoutTabs } from '@/renderer/components/layouts/layout-tabs';
|
||||
import { ContainerTabs } from '@/renderer/components/layouts/containers/container-tabs';
|
||||
import { Sidebar } from '@/renderer/components/layouts/sidebars/sidebar';
|
||||
import { LayoutContext } from '@/renderer/contexts/layout';
|
||||
import { useLayoutState } from '@/renderer/hooks/user-layout-state';
|
||||
@@ -30,6 +30,8 @@ export const Layout = () => {
|
||||
handlePreviewRight,
|
||||
handleActivateLeft,
|
||||
handleActivateRight,
|
||||
handleMoveLeft,
|
||||
handleMoveRight,
|
||||
} = useLayoutState();
|
||||
|
||||
const shouldDisplayLeft = leftContainerMetadata.tabs.length > 0;
|
||||
@@ -93,13 +95,14 @@ export const Layout = () => {
|
||||
|
||||
{shouldDisplayLeft && (
|
||||
<div className="h-full max-h-screen w-full flex-grow overflow-hidden bg-white">
|
||||
<LayoutTabs
|
||||
<ContainerTabs
|
||||
tabs={leftContainerMetadata.tabs}
|
||||
onFocus={() => {
|
||||
handleFocus('left');
|
||||
}}
|
||||
onClose={handleCloseLeft}
|
||||
onTabChange={handleActivateLeft}
|
||||
onMove={handleMoveLeft}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -133,13 +136,14 @@ export const Layout = () => {
|
||||
handleRightContainerResize(ref.offsetWidth);
|
||||
}}
|
||||
>
|
||||
<LayoutTabs
|
||||
<ContainerTabs
|
||||
tabs={rightContainerMetadata.tabs}
|
||||
onFocus={() => {
|
||||
handleFocus('right');
|
||||
}}
|
||||
onTabChange={handleActivateRight}
|
||||
onClose={handleCloseRight}
|
||||
onMove={handleMoveRight}
|
||||
/>
|
||||
</Resizable>
|
||||
)}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ContainerHeader,
|
||||
ContainerSettings,
|
||||
} from '@/renderer/components/ui/container';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/layouts/breadcrumbs/container-breadrumb';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/layouts/containers/container-breadrumb';
|
||||
import { PageBody } from '@/renderer/components/pages/page-body';
|
||||
|
||||
interface PageContainerProps {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
ContainerHeader,
|
||||
ContainerSettings,
|
||||
} from '@/renderer/components/ui/container';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/layouts/breadcrumbs/container-breadrumb';
|
||||
import { ContainerBreadcrumb } from '@/renderer/components/layouts/containers/container-breadrumb';
|
||||
import { RecordBody } from '@/renderer/components/records/record-body';
|
||||
import { RecordSettings } from '@/renderer/components/records/record-settings';
|
||||
|
||||
|
||||
@@ -391,6 +391,100 @@ export const useLayoutState = () => {
|
||||
[activeContainer, handleActivateLeft, handleActivateRight]
|
||||
);
|
||||
|
||||
const handleMoveLeft = useCallback(
|
||||
(id: string, before: string | null) => {
|
||||
const tabIndex = leftContainerMetadata.tabs.findIndex((t) => t.id === id);
|
||||
|
||||
if (tabIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tab = leftContainerMetadata.tabs[tabIndex];
|
||||
if (!tab) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (before === null) {
|
||||
const newTabs = [...leftContainerMetadata.tabs];
|
||||
newTabs.splice(tabIndex, 1);
|
||||
newTabs.push(tab);
|
||||
|
||||
replaceLeftContainerMetadata({
|
||||
...leftContainerMetadata,
|
||||
tabs: newTabs,
|
||||
});
|
||||
} else {
|
||||
const beforeIndex = leftContainerMetadata.tabs.findIndex(
|
||||
(t) => t.id === before
|
||||
);
|
||||
|
||||
if (beforeIndex === -1 || tabIndex === beforeIndex - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newTabs = [...leftContainerMetadata.tabs];
|
||||
newTabs.splice(tabIndex, 1);
|
||||
|
||||
const newIndex = newTabs.findIndex((t) => t.id === before);
|
||||
newTabs.splice(newIndex, 0, tab);
|
||||
|
||||
replaceLeftContainerMetadata({
|
||||
...leftContainerMetadata,
|
||||
tabs: newTabs,
|
||||
});
|
||||
}
|
||||
},
|
||||
[leftContainerMetadata]
|
||||
);
|
||||
|
||||
const handleMoveRight = useCallback(
|
||||
(id: string, before: string | null) => {
|
||||
const tabIndex = rightContainerMetadata.tabs.findIndex(
|
||||
(t) => t.id === id
|
||||
);
|
||||
|
||||
if (tabIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tab = rightContainerMetadata.tabs[tabIndex];
|
||||
if (!tab) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (before === null) {
|
||||
const newTabs = [...rightContainerMetadata.tabs];
|
||||
newTabs.splice(tabIndex, 1);
|
||||
newTabs.push(tab);
|
||||
|
||||
replaceRightContainerMetadata({
|
||||
...rightContainerMetadata,
|
||||
tabs: newTabs,
|
||||
});
|
||||
} else {
|
||||
const beforeIndex = rightContainerMetadata.tabs.findIndex(
|
||||
(t) => t.id === before
|
||||
);
|
||||
|
||||
if (beforeIndex === -1 || tabIndex === beforeIndex - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newTabs = [...rightContainerMetadata.tabs];
|
||||
newTabs.splice(tabIndex, 1);
|
||||
|
||||
const newIndex = newTabs.findIndex((t) => t.id === before);
|
||||
newTabs.splice(newIndex, 0, tab);
|
||||
|
||||
replaceRightContainerMetadata({
|
||||
...rightContainerMetadata,
|
||||
tabs: newTabs,
|
||||
});
|
||||
}
|
||||
},
|
||||
[rightContainerMetadata]
|
||||
);
|
||||
|
||||
const handleFocus = useCallback((side: 'left' | 'right') => {
|
||||
setActiveContainer(side);
|
||||
}, []);
|
||||
@@ -416,5 +510,7 @@ export const useLayoutState = () => {
|
||||
handleActivate,
|
||||
handleActivateLeft,
|
||||
handleActivateRight,
|
||||
handleMoveLeft,
|
||||
handleMoveRight,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user