page-tree-create.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import { useCallback } from 'react';
  2. import { atom, useAtomValue, useSetAtom } from 'jotai';
  3. import type { IPageForTreeItem } from '~/interfaces/page';
  4. import { CREATING_PAGE_VIRTUAL_ID } from '../../constants/_inner';
  5. /**
  6. * Create a placeholder page data for the creating node
  7. */
  8. export const createPlaceholderPageData = (
  9. parentId: string,
  10. parentPath: string,
  11. ): IPageForTreeItem => ({
  12. _id: CREATING_PAGE_VIRTUAL_ID,
  13. path: `${parentPath === '/' ? '' : parentPath}/`,
  14. parent: parentId,
  15. descendantCount: 0,
  16. grant: 1,
  17. isEmpty: true,
  18. wip: false,
  19. });
  20. /**
  21. * State for managing page creation in the tree
  22. * Stores the parent page info where a new page is being created
  23. */
  24. type CreatingParentInfo = {
  25. id: string;
  26. path: string;
  27. } | null;
  28. const creatingParentInfoAtom = atom<CreatingParentInfo>(null);
  29. // Module-level flag for synchronous guard against rapid clicks
  30. // This is shared across all hook instances
  31. let isCreatingFlag = false;
  32. /**
  33. * Reset the creating flag (for testing purposes only)
  34. * @internal
  35. */
  36. export const resetCreatingFlagForTesting = (): void => {
  37. isCreatingFlag = false;
  38. };
  39. /**
  40. * Hook to get the current creating parent ID
  41. */
  42. export const useCreatingParentId = (): string | null => {
  43. const info = useAtomValue(creatingParentInfoAtom);
  44. return info?.id ?? null;
  45. };
  46. /**
  47. * Hook to get the current creating parent path
  48. */
  49. export const useCreatingParentPath = (): string | null => {
  50. const info = useAtomValue(creatingParentInfoAtom);
  51. return info?.path ?? null;
  52. };
  53. /**
  54. * Hook to check if a specific item is in "creating child" mode
  55. */
  56. export const useIsCreatingChild = (parentId: string | undefined): boolean => {
  57. const creatingParentId = useCreatingParentId();
  58. return parentId != null && creatingParentId === parentId;
  59. };
  60. type PageTreeCreateActions = {
  61. /**
  62. * Start creating a new page under the specified parent
  63. */
  64. startCreating: (parentId: string, parentPath: string) => void;
  65. /**
  66. * Cancel the current page creation
  67. */
  68. cancelCreating: () => void;
  69. };
  70. /**
  71. * Hook to get page tree create actions
  72. */
  73. export const usePageTreeCreateActions = (): PageTreeCreateActions => {
  74. const setCreatingParentInfo = useSetAtom(creatingParentInfoAtom);
  75. const startCreating = useCallback(
  76. (parentId: string, parentPath: string) => {
  77. // Synchronous check to prevent rapid clicks
  78. // Uses module-level flag shared across all hook instances
  79. if (isCreatingFlag) {
  80. return;
  81. }
  82. isCreatingFlag = true;
  83. setCreatingParentInfo({ id: parentId, path: parentPath });
  84. },
  85. [setCreatingParentInfo],
  86. );
  87. const cancelCreating = useCallback(() => {
  88. isCreatingFlag = false;
  89. setCreatingParentInfo(null);
  90. }, [setCreatingParentInfo]);
  91. return { startCreating, cancelCreating };
  92. };