Files
colanode/packages/ui/src/components/layouts/layout-desktop.tsx
2025-10-08 11:04:47 +02:00

148 lines
3.8 KiB
TypeScript

import { createMemoryHistory, createRouter } from '@tanstack/react-router';
import { useCallback, useRef } from 'react';
import { Tab } from '@colanode/client/types';
import {
compareString,
generateFractionalIndex,
generateId,
IdType,
} from '@colanode/core';
import { TabsContent } from '@colanode/ui/components/layouts/tabs/tabs-content';
import { TabsHeader } from '@colanode/ui/components/layouts/tabs/tabs-header';
import { TabManagerContext } from '@colanode/ui/contexts/tab-manager';
import { database } from '@colanode/ui/data';
import { router, routeTree } from '@colanode/ui/routes';
export const LayoutDesktop = () => {
const routersRef = useRef<Map<string, typeof router>>(new Map());
const handleTabAdd = useCallback((location: string) => {
const tabs = database.tabs.map((tab) => tab);
const orderedTabs = tabs.toSorted((a, b) =>
compareString(a.index, b.index)
);
const lastIndex = orderedTabs[orderedTabs.length - 1]?.index;
const tab: Tab = {
id: generateId(IdType.Tab),
location,
index: generateFractionalIndex(lastIndex, null),
createdAt: new Date().toISOString(),
updatedAt: null,
};
database.tabs.insert(tab);
}, []);
const handleTabDelete = useCallback((id: string) => {
const tabs = database.tabs.map((tab) => tab);
const tabMetadata = database.metadata.get('tab');
if (tabs.length === 1) {
return;
}
if (tabMetadata?.value === id) {
const nextTab = tabs
.filter((tab) => tab.id !== id)
.toSorted((a, b) => {
const aDate = new Date(a.updatedAt ?? a.createdAt);
const bDate = new Date(b.updatedAt ?? b.createdAt);
return aDate.getTime() - bDate.getTime();
})[0]?.id;
if (!nextTab) {
return;
}
const tabMetadata = database.metadata.get('tab');
if (tabMetadata) {
database.metadata.update('tab', (tab) => {
tab.value = nextTab;
tab.updatedAt = new Date().toISOString();
});
} else {
database.metadata.insert({
key: 'tab',
value: nextTab,
createdAt: new Date().toISOString(),
updatedAt: null,
});
}
}
database.tabs.delete(id);
}, []);
const handleTabSwitch = useCallback((id: string) => {
const tabMetadata = database.metadata.get('tab');
if (!tabMetadata) {
database.metadata.insert({
key: 'tab',
value: id,
createdAt: new Date().toISOString(),
updatedAt: null,
});
} else {
database.metadata.update('tab', (metadata) => {
metadata.value = id;
});
}
}, []);
const handleTabGetRouter = useCallback((id: string) => {
if (routersRef.current.has(id)) {
return routersRef.current.get(id)!;
}
const tab = database.tabs.get(id);
if (!tab) {
throw new Error(`Tab ${id} not found`);
}
const router = createRouter({
routeTree,
context: {},
history: createMemoryHistory({
initialEntries: [tab.location ?? '/'],
}),
defaultPreload: 'intent',
scrollRestoration: true,
defaultPreloadStaleTime: 0,
});
router.subscribe('onRendered', (event) => {
if (!event.hrefChanged) {
return;
}
const location = event.toLocation.href;
window.colanode.executeMutation({
type: 'tab.update',
id,
location,
});
});
routersRef.current.set(id, router);
return router;
}, []);
return (
<TabManagerContext.Provider
value={{
addTab: handleTabAdd,
deleteTab: handleTabDelete,
switchTab: handleTabSwitch,
getRouter: handleTabGetRouter,
}}
>
<div className="flex flex-col h-full">
<TabsHeader />
<TabsContent />
</div>
</TabManagerContext.Provider>
);
};