Просмотр исходного кода

Merge pull request #4644 from weseek/feat/pt-item-get-children

feat: Pt item get children
Yuki Takei 4 лет назад
Родитель
Сommit
5db122d002

+ 33 - 11
packages/app/src/components/Sidebar/PageTree/Item.tsx

@@ -1,5 +1,7 @@
-import React, { memo } from 'react';
+import React, { useCallback, useState, FC } from 'react';
+
 import { ItemNode } from './ItemNode';
+import { useSWRxPageChildren } from '../../../stores/page-listing';
 
 
 interface ItemProps {
@@ -7,27 +9,47 @@ interface ItemProps {
   isOpen?: boolean
 }
 
-const Item = memo<ItemProps>((props: ItemProps) => {
-  const { itemNode, isOpen = false } = props;
+const Item: FC<ItemProps> = (props: ItemProps) => {
+  const { itemNode, isOpen: _isOpen = false } = props;
 
   const { page, children } = itemNode;
 
-  if (page == null) {
-    return null;
+  const [currentChildren, setCurrentChildren] = useState(children);
+
+  const [isOpen, setIsOpen] = useState(_isOpen);
+
+  const { data, error } = useSWRxPageChildren(isOpen ? page._id : null);
+
+  const hasChildren = useCallback((): boolean => {
+    return currentChildren != null && currentChildren.length > 0;
+  }, [currentChildren]);
+
+  const onClickLoadChildren = useCallback(async() => {
+    setIsOpen(!isOpen);
+  }, [isOpen]);
+
+  /*
+   * When swr fetch succeeded
+   */
+  if (isOpen && error == null && data != null) {
+    const { children } = data;
+    itemNode.children = ItemNode.generateNodesFromPages(children);
+  }
+
+  // make sure itemNode.children and currentChildren are synced
+  if (children.length > currentChildren.length) {
+    setCurrentChildren(children);
   }
 
   // TODO: improve style
   const style = { margin: '10px', opacity: 1.0 };
   if (page.isTarget) style.opacity = 0.7;
 
-  /*
-   * Normal render
-   */
   return (
     <div style={style}>
-      <p>{page.path}</p>
+      <p><button type="button" className="btn btn-light p-1" onClick={onClickLoadChildren}>Load</button>  {page.path}</p>
       {
-        itemNode.hasChildren() && (children as ItemNode[]).map(node => (
+        hasChildren() && currentChildren.map(node => (
           <Item
             key={node.page._id}
             itemNode={node}
@@ -38,6 +60,6 @@ const Item = memo<ItemProps>((props: ItemProps) => {
     </div>
   );
 
-});
+};
 
 export default Item;

+ 2 - 9
packages/app/src/components/Sidebar/PageTree/ItemNode.ts

@@ -1,23 +1,16 @@
-import { IPage } from '../../../interfaces/page';
-import { HasObjectId } from '../../../interfaces/has-object-id';
-
-type IPageForItem = Partial<IPage & {isTarget?: boolean} & HasObjectId>;
+import { IPageForItem } from '../../../interfaces/page';
 
 export class ItemNode {
 
   page: IPageForItem;
 
-  children?: ItemNode[];
+  children: ItemNode[];
 
   constructor(page: IPageForItem, children: ItemNode[] = []) {
     this.page = page;
     this.children = children;
   }
 
-  hasChildren(): boolean {
-    return this.children != null && this.children?.length > 0;
-  }
-
   static generateNodesFromPages(pages: IPageForItem[]): ItemNode[] {
     return pages.map(page => new ItemNode(page));
   }

+ 1 - 1
packages/app/src/components/Sidebar/PageTree/ItemsTree.tsx

@@ -48,7 +48,7 @@ const generateInitialNodeAfterResponse = (ancestorsChildren: Record<string, Part
  */
 const ItemsTree: FC = () => {
   // TODO: get from static SWR
-  const path = '/Sandbox/Mathematics';
+  const path = '/Sandbox';
 
   const { data, error } = useTargetAndAncestors();
 

+ 11 - 6
packages/app/src/interfaces/page-listing-results.ts

@@ -1,13 +1,18 @@
-import { IPage } from './page';
-import { HasObjectId } from './has-object-id';
+import { IPageForItem } from './page';
 
 
 type ParentPath = string;
 export interface AncestorsChildrenResult {
-  ancestorsChildren: Record<ParentPath, Partial<IPage & HasObjectId>[]>
+  ancestorsChildren: Record<ParentPath, Partial<IPageForItem>[]>
 }
 
-export type TargetAndAncestors = {
-  targetAndAncestors: Partial<IPage & HasObjectId>[]
-  rootPage: Partial<IPage & HasObjectId>,
+
+export interface ChildrenResult {
+  children: Partial<IPageForItem>[]
+}
+
+
+export interface TargetAndAncestors {
+  targetAndAncestors: Partial<IPageForItem>[]
+  rootPage: Partial<IPageForItem>,
 }

+ 3 - 0
packages/app/src/interfaces/page.ts

@@ -2,6 +2,7 @@ import { Ref } from './common';
 import { IUser } from './user';
 import { IRevision } from './revision';
 import { ITag } from './tag';
+import { HasObjectId } from './has-object-id';
 
 
 export type IPage = {
@@ -29,3 +30,5 @@ export type IPage = {
   deleteUser: Ref<IUser>,
   deletedAt: Date,
 }
+
+export type IPageForItem = Partial<IPage & {isTarget?: boolean} & HasObjectId>;

+ 1 - 1
packages/app/src/server/routes/apiv3/page-listing.ts

@@ -69,7 +69,7 @@ export default (crowi: Crowi): Router => {
 
     try {
       const pages = await Page.findChildrenByParentPathOrIdAndViewer((id || path)as string, req.user);
-      return res.apiv3({ pages });
+      return res.apiv3({ children: pages });
     }
     catch (err) {
       logger.error('Error occurred while finding children.', err);

+ 14 - 1
packages/app/src/stores/page-listing.tsx

@@ -1,7 +1,7 @@
 import useSWR, { SWRResponse } from 'swr';
 
 import { apiv3Get } from '../client/util/apiv3-client';
-import { AncestorsChildrenResult } from '../interfaces/page-listing-results';
+import { AncestorsChildrenResult, ChildrenResult } from '../interfaces/page-listing-results';
 
 
 export const useSWRxPageAncestorsChildren = (
@@ -17,3 +17,16 @@ export const useSWRxPageAncestorsChildren = (
     { revalidateOnFocus: false },
   );
 };
+
+export const useSWRxPageChildren = (
+    id?: string | null,
+): SWRResponse<ChildrenResult, Error> => {
+  return useSWR(
+    id ? `/page-listing/children?id=${id}` : null,
+    endpoint => apiv3Get(endpoint).then((response) => {
+      return {
+        children: response.data.children,
+      };
+    }),
+  );
+};