FileExplorer.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import React from "react";
  2. import {
  3. IoIosArrowBack,
  4. IoIosArrowForward,
  5. IoIosRefresh,
  6. IoIosCloudUpload,
  7. } from "react-icons/io";
  8. import { twMerge } from "tailwind-merge";
  9. import {
  10. WorkspaceFile,
  11. getWorkspace,
  12. uploadFile,
  13. } from "#/services/fileService";
  14. import IconButton from "../IconButton";
  15. import ExplorerTree from "./ExplorerTree";
  16. import { removeEmptyNodes } from "./utils";
  17. interface ExplorerActionsProps {
  18. onRefresh: () => void;
  19. onUpload: () => void;
  20. toggleHidden: () => void;
  21. isHidden: boolean;
  22. }
  23. function ExplorerActions({
  24. toggleHidden,
  25. onRefresh,
  26. onUpload,
  27. isHidden,
  28. }: ExplorerActionsProps) {
  29. return (
  30. <div
  31. className={twMerge(
  32. "transform flex h-[24px] items-center gap-1 absolute top-4 right-2",
  33. isHidden ? "right-3" : "right-2",
  34. )}
  35. >
  36. {!isHidden && (
  37. <>
  38. <IconButton
  39. icon={
  40. <IoIosRefresh
  41. size={16}
  42. className="text-neutral-400 hover:text-neutral-100 transition"
  43. />
  44. }
  45. testId="refresh"
  46. ariaLabel="Refresh workspace"
  47. onClick={onRefresh}
  48. />
  49. <IconButton
  50. icon={
  51. <IoIosCloudUpload
  52. size={16}
  53. className="text-neutral-400 hover:text-neutral-100 transition"
  54. />
  55. }
  56. testId="upload"
  57. ariaLabel="Upload File"
  58. onClick={onUpload}
  59. />
  60. </>
  61. )}
  62. <IconButton
  63. icon={
  64. isHidden ? (
  65. <IoIosArrowForward
  66. size={20}
  67. className="text-neutral-400 hover:text-neutral-100 transition"
  68. />
  69. ) : (
  70. <IoIosArrowBack
  71. size={20}
  72. className="text-neutral-400 hover:text-neutral-100 transition"
  73. />
  74. )
  75. }
  76. testId="toggle"
  77. ariaLabel={isHidden ? "Open workspace" : "Close workspace"}
  78. onClick={toggleHidden}
  79. />
  80. </div>
  81. );
  82. }
  83. interface FileExplorerProps {
  84. onFileClick: (path: string) => void;
  85. }
  86. function FileExplorer({ onFileClick }: FileExplorerProps) {
  87. const [workspace, setWorkspace] = React.useState<WorkspaceFile>();
  88. const [isHidden, setIsHidden] = React.useState(false);
  89. const fileInputRef = React.useRef<HTMLInputElement | null>(null);
  90. const getWorkspaceData = async () => {
  91. const wsFile = await getWorkspace();
  92. setWorkspace(removeEmptyNodes(wsFile));
  93. };
  94. const selectFileInput = () => {
  95. fileInputRef.current?.click(); // Trigger the file browser
  96. };
  97. const uploadFileData = async (event: React.ChangeEvent<HTMLInputElement>) => {
  98. const file = event.target.files ? event.target.files[0] : null;
  99. if (!file) {
  100. console.log("No file selected.");
  101. return;
  102. }
  103. console.log("File selected:", file);
  104. try {
  105. const response = await uploadFile(file);
  106. console.log(response);
  107. await getWorkspaceData(); // Refresh the workspace to show the new file
  108. } catch (error) {
  109. console.error("Error uploading file:", error);
  110. }
  111. };
  112. React.useEffect(() => {
  113. (async () => {
  114. await getWorkspaceData();
  115. })();
  116. }, []);
  117. return (
  118. <div
  119. className={twMerge(
  120. "bg-neutral-800 h-full border-r-1 border-r-neutral-600 flex flex-col transition-all ease-soft-spring overflow-auto",
  121. isHidden ? "min-w-[48px]" : "min-w-[228px]",
  122. )}
  123. >
  124. <div className="flex p-2 items-center justify-between relative">
  125. <div style={{ display: isHidden ? "none" : "block" }}>
  126. {workspace && (
  127. <ExplorerTree
  128. root={workspace}
  129. onFileClick={onFileClick}
  130. defaultOpen
  131. />
  132. )}
  133. </div>
  134. <ExplorerActions
  135. isHidden={isHidden}
  136. toggleHidden={() => setIsHidden((prev) => !prev)}
  137. onRefresh={getWorkspaceData}
  138. onUpload={selectFileInput}
  139. />
  140. </div>
  141. <input
  142. type="file"
  143. ref={fileInputRef}
  144. style={{ display: "none" }}
  145. onChange={uploadFileData}
  146. />
  147. </div>
  148. );
  149. }
  150. export default FileExplorer;