2
0

SimplifiedItemsTree.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import type { FC } from 'react';
  2. import { useCallback, useRef } from 'react';
  3. import { asyncDataLoaderFeature } from '@headless-tree/core';
  4. import { useTree } from '@headless-tree/react';
  5. import { useVirtualizer } from '@tanstack/react-virtual';
  6. import { apiv3Get } from '~/client/util/apiv3-client';
  7. import type { IPageForTreeItem } from '~/interfaces/page';
  8. import { SimplifiedTreeItem } from './SimplifiedTreeItem';
  9. import styles from './SimplifiedItemsTree.module.scss';
  10. type Props = {
  11. targetPathOrId?: string | null;
  12. };
  13. export const SimplifiedItemsTree: FC<Props> = ({ targetPathOrId }) => {
  14. const scrollElementRef = useRef<HTMLDivElement>(null);
  15. const getItem = useCallback(async (itemId: string): Promise<IPageForTreeItem> => {
  16. if (itemId === '/') {
  17. const response = await apiv3Get<{ rootPage: IPageForTreeItem }>('/page-listing/root');
  18. return response.data.rootPage;
  19. }
  20. const response = await apiv3Get<{ item: IPageForTreeItem }>('/page-listing/item', { id: itemId });
  21. return response.data.item;
  22. }, []);
  23. const getChildrenWithData = useCallback(async (itemId: string) => {
  24. if (itemId === '/') {
  25. const rootResponse = await apiv3Get<{ rootPage: IPageForTreeItem }>('/page-listing/root');
  26. const rootPageId = rootResponse.data.rootPage._id;
  27. const childrenResponse = await apiv3Get<{ children: IPageForTreeItem[] }>('/page-listing/children', { id: rootPageId });
  28. return childrenResponse.data.children.map(child => ({
  29. id: child._id,
  30. data: child,
  31. }));
  32. }
  33. const response = await apiv3Get<{ children: IPageForTreeItem[] }>('/page-listing/children', { id: itemId });
  34. return response.data.children.map(child => ({
  35. id: child._id,
  36. data: child,
  37. }));
  38. }, []);
  39. const tree = useTree<IPageForTreeItem>({
  40. rootItemId: '/',
  41. getItemName: item => item.getItemData().path,
  42. isItemFolder: item => item.getItemData().descendantCount > 0,
  43. createLoadingItemData: () => ({
  44. _id: '',
  45. path: 'Loading...',
  46. parent: '',
  47. descendantCount: 0,
  48. revision: '',
  49. grant: 1,
  50. isEmpty: false,
  51. wip: false,
  52. }),
  53. dataLoader: {
  54. getItem,
  55. getChildrenWithData,
  56. },
  57. features: [asyncDataLoaderFeature],
  58. });
  59. const items = tree.getItems();
  60. const virtualizer = useVirtualizer({
  61. count: items.length,
  62. getScrollElement: () => scrollElementRef.current,
  63. estimateSize: () => 36,
  64. overscan: 5,
  65. });
  66. return (
  67. <div
  68. {...tree.getContainerProps()}
  69. ref={scrollElementRef}
  70. className={styles['simplified-items-tree']}
  71. style={{ height: '100%', overflow: 'auto' }}
  72. >
  73. <div
  74. style={{
  75. height: `${virtualizer.getTotalSize()}px`,
  76. width: '100%',
  77. position: 'relative',
  78. }}
  79. >
  80. {virtualizer.getVirtualItems().map((virtualItem) => {
  81. const item = items[virtualItem.index];
  82. const itemData = item.getItemData();
  83. const isSelected = targetPathOrId === itemData._id || targetPathOrId === itemData.path;
  84. const props = item.getProps();
  85. return (
  86. <div
  87. key={virtualItem.key}
  88. data-index={virtualItem.index}
  89. ref={(node) => {
  90. virtualizer.measureElement(node);
  91. if (node && props.ref) {
  92. (props.ref as (node: HTMLElement) => void)(node);
  93. }
  94. }}
  95. style={{
  96. position: 'absolute',
  97. top: 0,
  98. left: 0,
  99. width: '100%',
  100. transform: `translateY(${virtualItem.start}px)`,
  101. }}
  102. >
  103. <SimplifiedTreeItem
  104. item={itemData}
  105. isSelected={isSelected}
  106. level={item.getItemMeta().level}
  107. isExpanded={item.isExpanded()}
  108. isFolder={item.isFolder()}
  109. onToggle={() => {
  110. if (item.isExpanded()) {
  111. item.collapse();
  112. }
  113. else {
  114. item.expand();
  115. }
  116. }}
  117. />
  118. </div>
  119. );
  120. })}
  121. </div>
  122. </div>
  123. );
  124. };