Przeglądaj źródła

improve root page node

Yuki Takei 5 miesięcy temu
rodzic
commit
8336c38e45

+ 43 - 15
apps/app/src/client/components/Common/SimplifiedItemsTree/SimplifiedItemsTree.tsx

@@ -7,12 +7,26 @@ import { useVirtualizer } from '@tanstack/react-virtual';
 
 
 import { apiv3Get } from '~/client/util/apiv3-client';
 import { apiv3Get } from '~/client/util/apiv3-client';
 import type { IPageForTreeItem } from '~/interfaces/page';
 import type { IPageForTreeItem } from '~/interfaces/page';
+import { useSWRxRootPage } from '~/stores/page-listing';
 
 
 import { SimplifiedTreeItem } from './SimplifiedTreeItem';
 import { SimplifiedTreeItem } from './SimplifiedTreeItem';
 
 
 import styles from './SimplifiedItemsTree.module.scss';
 import styles from './SimplifiedItemsTree.module.scss';
 
 
 
 
+const ROOT_PAGE_VIRTUAL_ID = '__virtual_root__';
+
+function constructRootPageForVirtualRoot(rootPageId: string, allPagesCount: number): IPageForTreeItem {
+  return {
+    _id: rootPageId,
+    path: '/',
+    descendantCount: allPagesCount,
+    grant: 1,
+    isEmpty: false,
+    wip: false,
+  };
+}
+
 type Props = {
 type Props = {
   targetPathOrId?: string | null;
   targetPathOrId?: string | null;
 };
 };
@@ -20,37 +34,45 @@ type Props = {
 export const SimplifiedItemsTree: FC<Props> = ({ targetPathOrId }) => {
 export const SimplifiedItemsTree: FC<Props> = ({ targetPathOrId }) => {
   const scrollElementRef = useRef<HTMLDivElement>(null);
   const scrollElementRef = useRef<HTMLDivElement>(null);
 
 
+  const { data: rootPageResult } = useSWRxRootPage({ suspense: true });
+  const rootPage = rootPageResult?.rootPage;
+  const rootPageId = rootPage?._id ?? ROOT_PAGE_VIRTUAL_ID;
+  const allPagesCount = rootPage?.descendantCount ?? 0;
+
   const getItem = useCallback(async (itemId: string): Promise<IPageForTreeItem> => {
   const getItem = useCallback(async (itemId: string): Promise<IPageForTreeItem> => {
-    if (itemId === '/') {
-      const response = await apiv3Get<{ rootPage: IPageForTreeItem }>('/page-listing/root');
-      return response.data.rootPage;
+    // Virtual root (should rarely be called since it's provided by getChildrenWithData)
+    if (itemId === ROOT_PAGE_VIRTUAL_ID) {
+      return constructRootPageForVirtualRoot(rootPageId, allPagesCount);
     }
     }
 
 
+    // For all pages (including root), use /page-listing/item endpoint
+    // Note: This should rarely be called thanks to getChildrenWithData caching
     const response = await apiv3Get<{ item: IPageForTreeItem }>('/page-listing/item', { id: itemId });
     const response = await apiv3Get<{ item: IPageForTreeItem }>('/page-listing/item', { id: itemId });
     return response.data.item;
     return response.data.item;
-  }, []);
+  }, [allPagesCount, rootPageId]);
 
 
   const getChildrenWithData = useCallback(async (itemId: string) => {
   const getChildrenWithData = useCallback(async (itemId: string) => {
-    if (itemId === '/') {
-      const rootResponse = await apiv3Get<{ rootPage: IPageForTreeItem }>('/page-listing/root');
-      const rootPageId = rootResponse.data.rootPage._id;
-      const childrenResponse = await apiv3Get<{ children: IPageForTreeItem[] }>('/page-listing/children', { id: rootPageId });
-      return childrenResponse.data.children.map(child => ({
-        id: child._id,
-        data: child,
-      }));
+    // Virtual root returns root page as its only child
+    // Use actual MongoDB _id as tree item ID to avoid duplicate API calls
+    if (itemId === ROOT_PAGE_VIRTUAL_ID) {
+      return [{
+        id: rootPageId,
+        data: constructRootPageForVirtualRoot(rootPageId, allPagesCount),
+      }];
     }
     }
 
 
+    // For all pages (including root), fetch children using their _id
     const response = await apiv3Get<{ children: IPageForTreeItem[] }>('/page-listing/children', { id: itemId });
     const response = await apiv3Get<{ children: IPageForTreeItem[] }>('/page-listing/children', { id: itemId });
     return response.data.children.map(child => ({
     return response.data.children.map(child => ({
       id: child._id,
       id: child._id,
       data: child,
       data: child,
     }));
     }));
-  }, []);
+  }, [allPagesCount, rootPageId]);
 
 
   const tree = useTree<IPageForTreeItem>({
   const tree = useTree<IPageForTreeItem>({
-    rootItemId: '/',
-    getItemName: item => item.getItemData().path,
+    rootItemId: ROOT_PAGE_VIRTUAL_ID,
+    getItemName: item => item.getItemData().path || '/',
+    initialState: { expandedItems: [ROOT_PAGE_VIRTUAL_ID] },
     isItemFolder: item => item.getItemData().descendantCount > 0,
     isItemFolder: item => item.getItemData().descendantCount > 0,
     createLoadingItemData: () => ({
     createLoadingItemData: () => ({
       _id: '',
       _id: '',
@@ -95,6 +117,12 @@ export const SimplifiedItemsTree: FC<Props> = ({ targetPathOrId }) => {
         {virtualizer.getVirtualItems().map((virtualItem) => {
         {virtualizer.getVirtualItems().map((virtualItem) => {
           const item = items[virtualItem.index];
           const item = items[virtualItem.index];
           const itemData = item.getItemData();
           const itemData = item.getItemData();
+
+          // Skip rendering virtual root
+          if (itemData._id === ROOT_PAGE_VIRTUAL_ID) {
+            return null;
+          }
+
           const isSelected = targetPathOrId === itemData._id || targetPathOrId === itemData.path;
           const isSelected = targetPathOrId === itemData._id || targetPathOrId === itemData.path;
           const props = item.getProps();
           const props = item.getProps();