Files
11cafe-comfyui-workspace-ma…/ui/src/RecentFilesDrawer/RecentFilesDrawer.tsx
T
2024-01-21 11:55:07 +08:00

343 lines
11 KiB
TypeScript

import {
Text,
Button,
HStack,
IconButton,
Menu,
MenuButton,
MenuList,
MenuItemOption,
MenuOptionGroup,
Card,
Box,
Flex,
Tooltip,
} from "@chakra-ui/react";
import { useEffect, useState, useRef, useCallback } from "react";
import {
Workflow,
deleteFlow,
isFolder,
listWorkflows,
listFolderContent,
tagsTable,
workspace,
foldersTable,
} from "../db-tables/WorkspaceDB";
import {
IconChevronDown,
IconChevronUp,
IconFolderPlus,
IconPlus,
IconX,
IconFolder,
} from "@tabler/icons-react";
import { RecentFilesContext } from "../WorkspaceContext";
import RecentFilesDrawerMenu from "./RecentFilesDrawerMenu";
import { sortFileItem } from "../utils";
import WorkflowListItem from "./WorkflowListItem";
import MultipleSelectionOperation from "./MultipleSelectionOperation";
import { ESortTypes, sortTypeLocalStorageKey } from "./types";
// @ts-ignore
import { app } from "/scripts/app.js";
import { insertWorkflowToCanvas3 } from "./InsertWorkflowToCanvas";
import FilesListFolderItem from "./FilesListFolderItem";
import { useDebounce } from "../customHooks/useDebounce";
import SearchInput from "../components/SearchInput";
import { openWorkflowsFolder } from "../Api";
import { Folder } from "../types/dbTypes";
import ImportFileButton from "./ImportFileButton";
import MyTagsRow from "./MyTagsRow";
type Props = {
onClose: () => void;
onClickNewFlow: () => void;
};
export default function RecentFilesDrawer({ onClose, onClickNewFlow }: Props) {
const [currentRenderingData, setCurrentRenderingData] = useState<
Array<Folder | Workflow>
>([]);
const aloneFlowsAndFoldersRef = useRef<Array<Folder | Workflow>>([]);
const allFlowsRef = useRef<Array<Workflow>>([]);
const [selectedTag, setSelectedTag] = useState<string>();
const [multipleState, setMultipleState] = useState(false);
const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
const [refreshFolderStamp, setRefreshFolderStamp] = useState(0);
const [searchValue, setSearchValue] = useState("");
const debounceSearchValue = useDebounce(searchValue, 400);
const draggingWorkflowID = useRef<string | null>(null);
const [draggingFile, setDraggingFile] = useState<Workflow | Folder | null>(
null,
);
const sortTypeRef = useRef<ESortTypes>(
(window.localStorage.getItem(sortTypeLocalStorageKey) as ESortTypes) ??
ESortTypes.RECENTLY_MODIFIED,
);
const loadLatestWorkflows = async () => {
const all = await listFolderContent();
aloneFlowsAndFoldersRef.current = all;
allFlowsRef.current = listWorkflows();
filterFlows();
setRefreshFolderStamp(Date.now());
};
const isFilter = !!selectedTag || !!debounceSearchValue;
const filterFlows = () => {
if (isFilter) {
const filterResult = allFlowsRef.current.filter((flow) => {
let tagFlag = true;
let keywordFlag = true;
if (selectedTag) {
tagFlag = !!flow.tags?.includes(selectedTag);
}
if (debounceSearchValue) {
keywordFlag = !!flow.name
.toLocaleLowerCase()
.includes(debounceSearchValue.toLocaleLowerCase());
}
return tagFlag && keywordFlag;
});
setCurrentRenderingData(sortFileItem(filterResult, sortTypeRef.current));
} else {
setCurrentRenderingData(
sortFileItem(aloneFlowsAndFoldersRef.current, sortTypeRef.current),
);
}
};
const onSort = (type: ESortTypes) => {
setCurrentRenderingData(sortFileItem(currentRenderingData, type));
sortTypeRef.current = type;
window.localStorage.setItem(sortTypeLocalStorageKey, type);
!isFilter && setRefreshFolderStamp(Date.now());
};
useEffect(() => {
filterFlows();
}, [debounceSearchValue, selectedTag]);
const onDelete = useCallback(
(id: string) => {
deleteFlow(id);
loadLatestWorkflows();
},
[selectedTag, debounceSearchValue],
);
useEffect(() => {
loadLatestWorkflows();
const handleDrop = (e: any) => {
workspace &&
draggingWorkflowID.current &&
insertWorkflowToCanvas3(workspace[draggingWorkflowID.current].json, [
e.canvasX,
e.canvasY,
]);
};
app.canvasEl.addEventListener("drop", handleDrop);
app.canvasEl.addEventListener("click", onClose);
// Cleanup function to remove event listeners
return () => {
app.canvasEl.removeEventListener("drop", handleDrop);
app.canvasEl.removeEventListener("click", onClose);
};
}, []);
const onSelect = useCallback((flowId: string, selected: boolean) => {
setSelectedKeys((preState) => {
const copyKeys = [...preState];
if (selected) {
copyKeys.push(flowId);
} else {
copyKeys.splice(copyKeys.indexOf(flowId), 1);
}
return copyKeys;
});
}, []);
const batchOperationCallback = (type: string, value: unknown) => {
switch (type) {
case "batchDelete":
loadLatestWorkflows();
setMultipleState(false);
setSelectedKeys([]);
return;
case "selectAll":
setSelectedKeys(
value ? allFlowsRef.current.map((flow) => flow.id) : [],
);
return;
}
};
const onDraggingFile = (file: Workflow | Folder) => {
setDraggingFile(file);
if (!isFolder(file)) {
draggingWorkflowID.current = file.id;
}
};
const onUpdateSearchValue = (newValue: string) => {
setSearchValue(newValue);
};
const DRAWER_WIDTH = 450;
return (
<RecentFilesContext.Provider
value={{
onRefreshFilesList: loadLatestWorkflows,
draggingFile: draggingFile ?? undefined,
setDraggingFile: onDraggingFile,
isMultiSelecting: multipleState,
onMultiSelectFlow: onSelect,
multiSelectedFlowsID: selectedKeys,
onDeleteFlow: onDelete,
refreshFolderStamp: refreshFolderStamp,
setRefreshFolderStamp: setRefreshFolderStamp,
}}
>
<Box style={{ width: DRAWER_WIDTH }}>
<Card
width={DRAWER_WIDTH}
height={"100vh"}
pl={4}
pr={5}
pt={4}
gap={4}
position={"absolute"}
top={0}
left={0}
shadow={"xl"}
zIndex={1000}
// boxShadow={"rgba(200, 200, 200, 0.4) 1px 4px 8px 1px"}
>
<Flex alignItems={"center"} justifyContent={"space-between"}>
<HStack gap={4}>
<HStack gap={2}>
<Text fontSize={19} fontWeight={600}>
☕️Workspace
</Text>
{/* <Tooltip label="Login to share and sync your workflows to cloud">
<Link onClick={openCognitoPopup}>Login</Link>
</Tooltip> */}
</HStack>
<ImportFileButton />
<Tooltip label="Open workspace save directory">
<IconButton
aria-label="Open workspace save directory"
size={"sm"}
icon={<IconFolder />}
variant={"outline"}
colorScheme="teal"
onClick={() => openWorkflowsFolder()}
/>
</Tooltip>
<Tooltip label="New workflow">
<IconButton
aria-label="New workflow"
size={"sm"}
icon={<IconPlus />}
variant={"outline"}
colorScheme="teal"
onClick={onClickNewFlow}
/>
</Tooltip>
</HStack>
<HStack alignItems={"center"}>
<RecentFilesDrawerMenu />
{/* <Button onClick={onclose}>CLOSE</Button> */}
</HStack>
</Flex>
<Flex direction="column" h="calc(100% - 64px)">
<MyTagsRow
selectedTag={selectedTag}
setSelectedTag={setSelectedTag}
/>
<HStack mb={2} mt={2} p={0} justifyContent="space-between">
<HStack>
{allFlowsRef.current.length > 0 ? (
<MultipleSelectionOperation
multipleState={multipleState}
changeMultipleState={(state) => {
setMultipleState(state);
!state && setSelectedKeys([]);
}}
selectedKeys={selectedKeys}
isSelectedAll={
selectedKeys.length === allFlowsRef.current.length
}
batchOperationCallback={batchOperationCallback}
/>
) : (
<Box />
)}
{!multipleState && (
<Tooltip hasArrow label="New folder" placement="bottom-start">
<IconButton
size={"sm"}
variant={"outline"}
icon={<IconFolderPlus size={21} />}
onClick={async () => {
await foldersTable?.create({
name: "New Folder",
});
loadLatestWorkflows();
}}
aria-label="new folder button"
/>
</Tooltip>
)}
</HStack>
<Menu closeOnSelect={true}>
<MenuButton
as={Button}
variant={"ghost"}
size="xs"
pr={0}
rightIcon={<IconChevronDown size="16" />}
>
<HStack>
<Text>Sort by:</Text>
<Text display="inline-block">{sortTypeRef.current}</Text>
</HStack>
</MenuButton>
<MenuList>
<MenuOptionGroup
value={sortTypeRef.current}
type="radio"
onChange={(type) => onSort(type as ESortTypes)}
>
{Object.values(ESortTypes).map((sortType, index) => (
<MenuItemOption key={index} value={sortType}>
{sortType}
</MenuItemOption>
))}
</MenuOptionGroup>
</MenuList>
</Menu>
</HStack>
<SearchInput
searchValue={searchValue}
onUpdateSearchValue={onUpdateSearchValue}
/>
<Flex overflowY={"auto"} overflowX={"hidden"} direction="column">
{currentRenderingData.map((n) => {
if (isFolder(n)) {
return <FilesListFolderItem folder={n} key={n.id} />;
}
return <WorkflowListItem key={n.id} workflow={n} />;
})}
</Flex>
</Flex>
</Card>
</Box>
</RecentFilesContext.Provider>
);
}