Feature Enhancements and Bug Fixes (#2099)
#### Overview: This PR introduces several new features and fixes to improve the user experience and functionality of the application. Key updates include enhanced interactivity, export functionalities, UI improvements, and bug fixes. #### New Features: 1. **Play on Hover**: - Added functionality to play media when hovering over the entire node for better interactivity. 2. **Export Handler**: - Implemented an export handler on the main page. - `handleExport` function added as a parameter to the header, along with an export button for easy data export. 3. **Upload Flow**: - Introduced an "Upload Flow" button on the main page for streamlined file uploads. 4. **User Menu**: - Added a user option in the header menu for better user profile management. #### Bug Fixes and UI Improvements: 1. **Naming and Position Fixes**: - Corrected naming issues and positioning of alerts in the export modal for clarity. 2. **Button Styling**: - Updated buttons in the components section header to utilize ShadCN button styles for consistency. 3. **Select All Classes**: - Resolved issues with the "Select All" functionality for improved batch operations. 4. **Import Function**: - Fixed the import function to ensure reliable data import processes. 5. **Error Naming**: - Standardized error naming conventions for better error handling and debugging. #### Commits: 1. **Added play on hover of entire node** - Enhances interactivity by enabling play on hover. 2. **Added Export handler on Main Page** - Implements core export functionality. 3. **Added handleExport as parameter of header and added button to export** - Integrates export control into the header. 4. **Fixed naming of errors** - Improves error handling by fixing naming issues. 5. **Fixed buttons not using ShadCN buttons on header of components section** - Ensures consistent button styling. 6. **Fixed naming and position of alert on Export modal** - Corrects alert display issues in export modal. 7. **Fixed select all classes** - Resolves issues with the "Select All" feature. 8. **Added Upload Flow button to main page** - Introduces an upload button for file handling. 9. **Fixed import function** - Ensures reliable import operations. 10. **Added user on header menu** - Adds user options to the header for profile management. These updates collectively enhance the application's functionality, user interface, and overall user experience.
This commit is contained in:
commit
106ce9e64b
20 changed files with 321 additions and 184 deletions
1
src/frontend/package-lock.json
generated
1
src/frontend/package-lock.json
generated
|
|
@ -480,7 +480,6 @@
|
|||
},
|
||||
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
|
||||
"version": "1.3.0",
|
||||
"extraneous": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
|
|
|||
|
|
@ -56,14 +56,14 @@ export default function GenericNode({
|
|||
const [nodeName, setNodeName] = useState(data.node!.display_name);
|
||||
const [inputDescription, setInputDescription] = useState(false);
|
||||
const [nodeDescription, setNodeDescription] = useState(
|
||||
data.node?.description!
|
||||
data.node?.description!,
|
||||
);
|
||||
const [isOutdated, setIsOutdated] = useState(false);
|
||||
const buildStatus = useFlowStore(
|
||||
(state) => state.flowBuildStatus[data.id]?.status
|
||||
(state) => state.flowBuildStatus[data.id]?.status,
|
||||
);
|
||||
const lastRunTime = useFlowStore(
|
||||
(state) => state.flowBuildStatus[data.id]?.timestamp
|
||||
(state) => state.flowBuildStatus[data.id]?.timestamp,
|
||||
);
|
||||
const [validationStatus, setValidationStatus] =
|
||||
useState<validationStatusType | null>(null);
|
||||
|
|
@ -116,7 +116,7 @@ export default function GenericNode({
|
|||
|
||||
updateNodeInternals(data.id);
|
||||
},
|
||||
[data.id, data.node, setNode, setIsOutdated]
|
||||
[data.id, data.node, setNode, setIsOutdated],
|
||||
);
|
||||
|
||||
if (!data.node!.template) {
|
||||
|
|
@ -255,8 +255,17 @@ export default function GenericNode({
|
|||
const isDark = useDarkStore((state) => state.dark);
|
||||
const renderIconStatus = (
|
||||
buildStatus: BuildStatus | undefined,
|
||||
validationStatus: validationStatusType | null
|
||||
validationStatus: validationStatusType | null,
|
||||
) => {
|
||||
const conditionSuccess = validationStatus && validationStatus.valid;
|
||||
const conditionInactive =
|
||||
validationStatus &&
|
||||
!validationStatus.valid &&
|
||||
buildStatus === BuildStatus.INACTIVE;
|
||||
const conditionError =
|
||||
buildStatus === BuildStatus.ERROR ||
|
||||
(validationStatus && !validationStatus.valid);
|
||||
|
||||
if (buildStatus === BuildStatus.BUILDING) {
|
||||
return <Loading className="text-medium-indigo" />;
|
||||
} else {
|
||||
|
|
@ -264,31 +273,30 @@ export default function GenericNode({
|
|||
<>
|
||||
<IconComponent
|
||||
name="Play"
|
||||
className="absolute ml-0.5 h-5 fill-current stroke-2 text-medium-indigo opacity-0 transition-all group-hover:opacity-100"
|
||||
className={cn(
|
||||
!conditionSuccess && !conditionInactive && !conditionError
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
"absolute ml-0.5 h-5 fill-current stroke-2 text-muted-foreground transition-all group-hover:text-medium-indigo group-hover/node:opacity-100",
|
||||
)}
|
||||
/>
|
||||
{validationStatus && validationStatus.valid ? (
|
||||
{conditionSuccess ? (
|
||||
<Checkmark
|
||||
className="absolute ml-0.5 h-5 stroke-2 text-status-green opacity-100 transition-all group-hover:opacity-0"
|
||||
className="absolute ml-0.5 h-5 stroke-2 text-status-green opacity-100 transition-all group-hover/node:opacity-0"
|
||||
isVisible={true}
|
||||
/>
|
||||
) : validationStatus &&
|
||||
!validationStatus.valid &&
|
||||
buildStatus === BuildStatus.INACTIVE ? (
|
||||
) : conditionInactive ? (
|
||||
<IconComponent
|
||||
name="Play"
|
||||
className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-green opacity-30 transition-all group-hover:opacity-0"
|
||||
className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-gray opacity-30 transition-all group-hover/node:opacity-0"
|
||||
/>
|
||||
) : buildStatus === BuildStatus.ERROR ||
|
||||
(validationStatus && !validationStatus.valid) ? (
|
||||
) : conditionError ? (
|
||||
<Xmark
|
||||
isVisible={true}
|
||||
className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-red opacity-100 transition-all group-hover:opacity-0"
|
||||
className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-red opacity-100 transition-all group-hover/node:opacity-0"
|
||||
/>
|
||||
) : (
|
||||
<IconComponent
|
||||
name="Play"
|
||||
className="absolute ml-0.5 h-5 fill-current stroke-2 text-muted-foreground opacity-100 transition-all group-hover:opacity-0"
|
||||
/>
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
@ -296,7 +304,7 @@ export default function GenericNode({
|
|||
};
|
||||
const getSpecificClassFromBuildStatus = (
|
||||
buildStatus: BuildStatus | undefined,
|
||||
validationStatus: validationStatusType | null
|
||||
validationStatus: validationStatusType | null,
|
||||
) => {
|
||||
let isInvalid = validationStatus && !validationStatus.valid;
|
||||
|
||||
|
|
@ -320,11 +328,11 @@ export default function GenericNode({
|
|||
selected: boolean,
|
||||
showNode: boolean,
|
||||
buildStatus: BuildStatus | undefined,
|
||||
validationStatus: validationStatusType | null
|
||||
validationStatus: validationStatusType | null,
|
||||
) => {
|
||||
const specificClassFromBuildStatus = getSpecificClassFromBuildStatus(
|
||||
buildStatus,
|
||||
validationStatus
|
||||
validationStatus,
|
||||
);
|
||||
|
||||
const baseBorderClass = getBaseBorderClass(selected);
|
||||
|
|
@ -332,8 +340,8 @@ export default function GenericNode({
|
|||
const names = classNames(
|
||||
baseBorderClass,
|
||||
nodeSizeClass,
|
||||
"generic-node-div",
|
||||
specificClassFromBuildStatus
|
||||
"generic-node-div group/node",
|
||||
specificClassFromBuildStatus,
|
||||
);
|
||||
return names;
|
||||
};
|
||||
|
|
@ -393,7 +401,7 @@ export default function GenericNode({
|
|||
selected,
|
||||
showNode,
|
||||
buildStatus,
|
||||
validationStatus
|
||||
validationStatus,
|
||||
)}
|
||||
>
|
||||
{data.node?.beta && showNode && (
|
||||
|
|
@ -524,7 +532,7 @@ export default function GenericNode({
|
|||
}
|
||||
title={getFieldTitle(
|
||||
data.node?.template!,
|
||||
templateField
|
||||
templateField,
|
||||
)}
|
||||
info={data.node?.template[templateField].info}
|
||||
name={templateField}
|
||||
|
|
@ -552,7 +560,7 @@ export default function GenericNode({
|
|||
proxy={data.node?.template[templateField].proxy}
|
||||
showNode={showNode}
|
||||
/>
|
||||
)
|
||||
),
|
||||
)}
|
||||
<ParameterComponent
|
||||
key={scapedJSONStringfy({
|
||||
|
|
@ -709,7 +717,7 @@ export default function GenericNode({
|
|||
!data.node?.description) &&
|
||||
nameEditable
|
||||
? "font-light italic"
|
||||
: ""
|
||||
: "",
|
||||
)}
|
||||
onClick={(e) => {
|
||||
setInputDescription(true);
|
||||
|
|
@ -771,13 +779,13 @@ export default function GenericNode({
|
|||
}
|
||||
title={getFieldTitle(
|
||||
data.node?.template!,
|
||||
templateField
|
||||
templateField,
|
||||
)}
|
||||
info={data.node?.template[templateField].info}
|
||||
name={templateField}
|
||||
tooltipTitle={
|
||||
data.node?.template[templateField].input_types?.join(
|
||||
"\n"
|
||||
"\n",
|
||||
) ?? data.node?.template[templateField].type
|
||||
}
|
||||
required={data.node!.template[templateField].required}
|
||||
|
|
@ -804,7 +812,7 @@ export default function GenericNode({
|
|||
<div
|
||||
className={classNames(
|
||||
Object.keys(data.node!.template).length < 1 ? "hidden" : "",
|
||||
"flex-max-width justify-center"
|
||||
"flex-max-width justify-center",
|
||||
)}
|
||||
>
|
||||
{" "}
|
||||
|
|
|
|||
|
|
@ -51,13 +51,15 @@ export default function ErrorAlert({
|
|||
/>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="error-build-foreground">{title}</h3>
|
||||
<h3 className="error-build-foreground line-clamp-2">{title}</h3>
|
||||
{list?.length !== 0 &&
|
||||
list?.some((item) => item !== null && item !== undefined) ? (
|
||||
<div className="error-build-message-div">
|
||||
<ul className="error-build-message-list">
|
||||
{list.map((item, index) => (
|
||||
<li key={index}>{item}</li>
|
||||
<li key={index} className="line-clamp-5">
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export default function NoticeAlert({
|
|||
/>
|
||||
</div>
|
||||
<div className="ml-3 flex-1 md:flex md:justify-between">
|
||||
<p className="text-sm text-info-foreground word-break-break-word">
|
||||
<p className="line-clamp-2 text-sm text-info-foreground word-break-break-word">
|
||||
{title}
|
||||
</p>
|
||||
<p className="mt-3 text-sm md:ml-6 md:mt-0">
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export default function SuccessAlert({
|
|||
/>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<p className="success-alert-message">{title}</p>
|
||||
<p className="success-alert-message line-clamp-2">{title}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export default function Header(): JSX.Element {
|
|||
const lastFlowVisitedIndex = routeHistory
|
||||
.reverse()
|
||||
.findIndex(
|
||||
(path) => path.includes("/flow/") && path !== location.pathname
|
||||
(path) => path.includes("/flow/") && path !== location.pathname,
|
||||
);
|
||||
|
||||
const lastFlowVisited = routeHistory[lastFlowVisitedIndex];
|
||||
|
|
@ -200,6 +200,28 @@ export default function Header(): JSX.Element {
|
|||
/>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
{!autoLogin && (
|
||||
<>
|
||||
<DropdownMenuLabel>
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className={
|
||||
"h-5 w-5 rounded-full focus-visible:outline-0 " +
|
||||
(userData?.profile_image ??
|
||||
(userData?.id
|
||||
? gradients[
|
||||
parseInt(userData?.id ?? "", 30) %
|
||||
gradients.length
|
||||
]
|
||||
: "bg-gray-500"))
|
||||
}
|
||||
/>
|
||||
{userData?.username ?? "User"}
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuLabel>General</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import IconComponent, {
|
|||
import { Button, buttonVariants } from "../../../ui/button";
|
||||
import { Input } from "../../../ui/input";
|
||||
import useFileDrop from "../../hooks/use-on-file-drop";
|
||||
import useAlertStore from "../../../../stores/alertStore";
|
||||
|
||||
type SideBarFoldersButtonsComponentProps = {
|
||||
folders: FolderType[];
|
||||
|
|
@ -33,7 +34,7 @@ const SideBarFoldersButtonsComponent = ({
|
|||
const [foldersNames, setFoldersNames] = useState({});
|
||||
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||
const [editFolders, setEditFolderName] = useState(
|
||||
folders.map((obj) => ({ name: obj.name, edit: false }))
|
||||
folders.map((obj) => ({ name: obj.name, edit: false })),
|
||||
);
|
||||
const uploadFolder = useFolderStore((state) => state.uploadFolder);
|
||||
const currentFolder = pathname.split("/");
|
||||
|
|
@ -51,6 +52,7 @@ const SideBarFoldersButtonsComponent = ({
|
|||
const location = useLocation();
|
||||
const folderId = location?.state?.folderId ?? myCollectionId;
|
||||
const getFolderById = useFolderStore((state) => state.getFolderById);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
|
||||
const handleFolderChange = (folderId: string) => {
|
||||
getFolderById(folderId);
|
||||
|
|
@ -58,11 +60,21 @@ const SideBarFoldersButtonsComponent = ({
|
|||
|
||||
const { dragOver, dragEnter, dragLeave, onDrop } = useFileDrop(
|
||||
folderId,
|
||||
handleFolderChange
|
||||
handleFolderChange,
|
||||
);
|
||||
|
||||
const handleUploadFlowsToFolder = () => {
|
||||
uploadFolder(folderId);
|
||||
uploadFolder(folderId)
|
||||
.then(() => {
|
||||
getFolderById(folderId);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
setErrorData({
|
||||
title: `Error on upload`,
|
||||
list: [err["response"]["data"]],
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleDownloadFolder = (id: string) => {
|
||||
|
|
@ -73,7 +85,7 @@ const SideBarFoldersButtonsComponent = ({
|
|||
addFolder({ name: "New Folder", parent_id: null, description: "" }).then(
|
||||
(res) => {
|
||||
getFoldersApi(true);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -119,7 +131,7 @@ const SideBarFoldersButtonsComponent = ({
|
|||
<>
|
||||
{folders.map((item, index) => {
|
||||
const editFolderName = editFolders?.filter(
|
||||
(folder) => folder.name === item.name
|
||||
(folder) => folder.name === item.name,
|
||||
)[0];
|
||||
return (
|
||||
<div
|
||||
|
|
@ -135,7 +147,7 @@ const SideBarFoldersButtonsComponent = ({
|
|||
? "border border-border bg-muted hover:bg-muted"
|
||||
: "border hover:bg-transparent lg:border-transparent lg:hover:border-border",
|
||||
"group flex w-full shrink-0 cursor-pointer gap-2 opacity-100 lg:min-w-full",
|
||||
folderIdDragging === item.id! ? "bg-border" : ""
|
||||
folderIdDragging === item.id! ? "bg-border" : "",
|
||||
)}
|
||||
onClick={() => handleChangeFolder!(item.id!)}
|
||||
>
|
||||
|
|
@ -205,7 +217,7 @@ const SideBarFoldersButtonsComponent = ({
|
|||
folders.map((obj) => ({
|
||||
name: obj.name,
|
||||
edit: false,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
}
|
||||
if (e.key === "Enter") {
|
||||
|
|
@ -238,10 +250,10 @@ const SideBarFoldersButtonsComponent = ({
|
|||
};
|
||||
const updatedFolder = await updateFolder(
|
||||
body,
|
||||
item.id!
|
||||
item.id!,
|
||||
);
|
||||
const updateFolders = folders.filter(
|
||||
(f) => f.name !== item.name
|
||||
(f) => f.name !== item.name,
|
||||
);
|
||||
setFolders([...updateFolders, updatedFolder]);
|
||||
setFoldersNames({});
|
||||
|
|
@ -249,7 +261,7 @@ const SideBarFoldersButtonsComponent = ({
|
|||
folders.map((obj) => ({
|
||||
name: obj.name,
|
||||
edit: false,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
} else {
|
||||
setFoldersNames((old) => ({
|
||||
|
|
|
|||
|
|
@ -668,7 +668,7 @@ export const ZERO_NOTIFICATIONS = "No new notifications";
|
|||
export const SUCCESS_BUILD = "Built sucessfully ✨";
|
||||
|
||||
export const ALERT_SAVE_WITH_API =
|
||||
"Caution: Uncheck this box only removes API keys from fields specifically designated for API keys.";
|
||||
"Caution: Unchecking this box only removes API keys from fields specifically designated for API keys.";
|
||||
|
||||
export const SAVE_WITH_API_CHECKBOX = "Save with my API keys";
|
||||
export const EDIT_TEXT_MODAL_TITLE = "Edit Text";
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ const ExportModal = forwardRef(
|
|||
is_component: false,
|
||||
},
|
||||
name!,
|
||||
description
|
||||
description,
|
||||
);
|
||||
setNoticeData({
|
||||
title: API_WARNING_NOTICE_ALERT,
|
||||
|
|
@ -61,7 +61,7 @@ const ExportModal = forwardRef(
|
|||
is_component: false,
|
||||
}),
|
||||
name!,
|
||||
description
|
||||
description,
|
||||
);
|
||||
setOpen(false);
|
||||
}}
|
||||
|
|
@ -94,7 +94,7 @@ const ExportModal = forwardRef(
|
|||
{SAVE_WITH_API_CHECKBOX}
|
||||
</label>
|
||||
</div>
|
||||
<span className=" text-xs text-destructive ">
|
||||
<span className="mt-1 text-xs text-destructive ">
|
||||
{ALERT_SAVE_WITH_API}
|
||||
</span>
|
||||
</BaseModal.Content>
|
||||
|
|
@ -102,6 +102,6 @@ const ExportModal = forwardRef(
|
|||
<BaseModal.Footer submit={{ label: "Download Flow" }} />
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
export default ExportModal;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import { FormProvider, useForm, useWatch } from "react-hook-form";
|
|||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import CollectionCardComponent from "../../../../components/cardComponent";
|
||||
import CardsWrapComponent from "../../../../components/cardsWrapComponent";
|
||||
import IconComponent from "../../../../components/genericIconComponent";
|
||||
import IconComponent, {
|
||||
ForwardedIconComponent,
|
||||
} from "../../../../components/genericIconComponent";
|
||||
import PaginatorComponent from "../../../../components/paginatorComponent";
|
||||
import { SkeletonCardComponent } from "../../../../components/skeletonCardComponent";
|
||||
import { Button } from "../../../../components/ui/button";
|
||||
|
|
@ -18,6 +20,9 @@ import { getNameByType } from "../../utils/get-name-by-type";
|
|||
import { sortFlows } from "../../utils/sort-flows";
|
||||
import EmptyComponent from "../emptyComponent";
|
||||
import HeaderComponent from "../headerComponent";
|
||||
import { downloadFlow, removeApiKeys } from "../../../../utils/reactflowUtils";
|
||||
import { useDarkStore } from "../../../../stores/darkStore";
|
||||
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
|
||||
|
||||
export default function ComponentsComponent({
|
||||
type = "all",
|
||||
|
|
@ -31,22 +36,22 @@ export default function ComponentsComponent({
|
|||
const allFlows = useFlowsManagerStore((state) => state.allFlows);
|
||||
|
||||
const flowsFromFolder = useFolderStore(
|
||||
(state) => state.selectedFolder?.flows
|
||||
(state) => state.selectedFolder?.flows,
|
||||
);
|
||||
|
||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const [openDelete, setOpenDelete] = useState(false);
|
||||
const searchFlowsComponents = useFlowsManagerStore(
|
||||
(state) => state.searchFlowsComponents
|
||||
(state) => state.searchFlowsComponents,
|
||||
);
|
||||
|
||||
const setSelectedFlowsComponentsCards = useFlowsManagerStore(
|
||||
(state) => state.setSelectedFlowsComponentsCards
|
||||
(state) => state.setSelectedFlowsComponentsCards,
|
||||
);
|
||||
|
||||
const selectedFlowsComponentsCards = useFlowsManagerStore(
|
||||
(state) => state.selectedFlowsComponentsCards
|
||||
(state) => state.selectedFlowsComponentsCards,
|
||||
);
|
||||
|
||||
const [handleFileDrop] = useFileDrop(uploadFlow, type)!;
|
||||
|
|
@ -82,7 +87,7 @@ export default function ComponentsComponent({
|
|||
f.name.toLowerCase().includes(searchFlowsComponents.toLowerCase()) ||
|
||||
f.description
|
||||
.toLowerCase()
|
||||
.includes(searchFlowsComponents.toLowerCase())
|
||||
.includes(searchFlowsComponents.toLowerCase()),
|
||||
);
|
||||
|
||||
if (searchFlowsComponents === "") {
|
||||
|
|
@ -129,6 +134,8 @@ export default function ComponentsComponent({
|
|||
setOpenDelete(true);
|
||||
} else if (action === "duplicate") {
|
||||
handleDuplicate();
|
||||
} else if (action === "export") {
|
||||
handleExport();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -137,9 +144,9 @@ export default function ComponentsComponent({
|
|||
selectedFlowsComponentsCards.map((selectedFlow) =>
|
||||
addFlow(
|
||||
true,
|
||||
allFlows.find((flow) => flow.id === selectedFlow)
|
||||
)
|
||||
)
|
||||
allFlows.find((flow) => flow.id === selectedFlow),
|
||||
),
|
||||
),
|
||||
).then(() => {
|
||||
resetFilter();
|
||||
getFoldersApi(true);
|
||||
|
|
@ -152,6 +159,47 @@ export default function ComponentsComponent({
|
|||
});
|
||||
};
|
||||
|
||||
const handleImport = () => {
|
||||
uploadFlow({ newProject: true, isComponent: false })
|
||||
.then(() => {
|
||||
resetFilter();
|
||||
getFoldersApi(true);
|
||||
if (!folderId || folderId === myCollectionId) {
|
||||
getFolderById(folderId ? folderId : myCollectionId);
|
||||
}
|
||||
setSelectedFlowsComponentsCards([]);
|
||||
|
||||
setSuccessData({ title: "Flows imported successfully" });
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: UPLOAD_ERROR_ALERT,
|
||||
list: [error],
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const version = useDarkStore((state) => state.version);
|
||||
|
||||
const handleExport = () => {
|
||||
selectedFlowsComponentsCards.map((selectedFlowId) => {
|
||||
const selectedFlow = allFlows.find((flow) => flow.id === selectedFlowId);
|
||||
downloadFlow(
|
||||
removeApiKeys({
|
||||
id: selectedFlow!.id,
|
||||
data: selectedFlow!.data!,
|
||||
description: selectedFlow!.description,
|
||||
name: selectedFlow!.name,
|
||||
last_tested_version: version,
|
||||
is_component: false,
|
||||
}),
|
||||
selectedFlow!.name,
|
||||
selectedFlow!.description,
|
||||
);
|
||||
});
|
||||
setSuccessData({ title: "Flows exported successfully" });
|
||||
};
|
||||
|
||||
const handleDeleteMultiple = () => {
|
||||
removeFlow(selectedFlowsComponentsCards)
|
||||
.then(() => {
|
||||
|
|
@ -161,7 +209,7 @@ export default function ComponentsComponent({
|
|||
getFolderById(folderId ? folderId : myCollectionId);
|
||||
}
|
||||
setSuccessData({
|
||||
title: "Selected items deleted successfully!",
|
||||
title: "Selected items deleted successfully",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
|
|
@ -180,7 +228,7 @@ export default function ComponentsComponent({
|
|||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
setSelectedFlowsComponentsCards(selectedFlows);
|
||||
|
|
@ -215,20 +263,23 @@ export default function ComponentsComponent({
|
|||
if (type === "all") return allFlows?.length;
|
||||
|
||||
return allFlows?.filter(
|
||||
(f) => (f.is_component ?? false) === (type === "component")
|
||||
(f) => (f.is_component ?? false) === (type === "component"),
|
||||
)?.length;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{allFlows?.length > 0 && (
|
||||
<HeaderComponent
|
||||
handleDelete={() => handleSelectOptionsChange("delete")}
|
||||
handleSelectAll={handleSelectAll}
|
||||
handleDuplicate={() => handleSelectOptionsChange("duplicate")}
|
||||
disableFunctions={!(selectedFlowsComponentsCards?.length > 0)}
|
||||
/>
|
||||
)}
|
||||
<div className="flex w-full gap-4 pb-5">
|
||||
{allFlows?.length > 0 && (
|
||||
<HeaderComponent
|
||||
handleDelete={() => handleSelectOptionsChange("delete")}
|
||||
handleSelectAll={handleSelectAll}
|
||||
handleDuplicate={() => handleSelectOptionsChange("duplicate")}
|
||||
handleExport={() => handleSelectOptionsChange("export")}
|
||||
disableFunctions={!(selectedFlowsComponentsCards?.length > 0)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<CardsWrapComponent
|
||||
onFileDrop={handleFileDrop}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const EmptyComponent = ({}: EmptyComponentProps) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="mt-6 flex w-full items-center justify-center text-center">
|
||||
<div className="mt-2 flex w-full items-center justify-center text-center">
|
||||
<div className="flex-max-width h-full flex-col">
|
||||
<div className="align-center flex w-full justify-center gap-1 ">
|
||||
<span className="text-muted-foreground">
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
import { useState } from "react";
|
||||
import IconComponent from "../../../../components/genericIconComponent";
|
||||
import IconComponent, {
|
||||
ForwardedIconComponent,
|
||||
} from "../../../../components/genericIconComponent";
|
||||
import ShadTooltip from "../../../../components/shadTooltipComponent";
|
||||
import { Checkbox } from "../../../../components/ui/checkbox";
|
||||
import { cn } from "../../../../utils/utils";
|
||||
import { Button } from "../../../../components/ui/button";
|
||||
|
||||
type HeaderComponentProps = {
|
||||
handleSelectAll: (select) => void;
|
||||
handleDelete: () => void;
|
||||
handleDuplicate: () => void;
|
||||
handleExport: () => void;
|
||||
disableFunctions: boolean;
|
||||
};
|
||||
|
||||
|
|
@ -15,6 +19,7 @@ const HeaderComponent = ({
|
|||
handleSelectAll,
|
||||
handleDelete,
|
||||
handleDuplicate,
|
||||
handleExport,
|
||||
disableFunctions,
|
||||
}: HeaderComponentProps) => {
|
||||
const [shouldSelectAll, setShouldSelectAll] = useState(true);
|
||||
|
|
@ -26,29 +31,46 @@ const HeaderComponent = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-3 pb-5">
|
||||
<div className="col-auto grid-cols-1 self-center justify-self-start">
|
||||
<a onClick={handleClick} className="text-sm">
|
||||
<div className="header-menu-bar-display ">
|
||||
<div
|
||||
className="header-menu-flow-name"
|
||||
data-testid="select_all_collection"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox checked={!shouldSelectAll} id="terms" />
|
||||
<label
|
||||
onClick={handleClick}
|
||||
htmlFor="terms"
|
||||
className="label cursor-pointer text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
{shouldSelectAll ? "Select All" : "Unselect All"}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full items-center justify-between gap-4">
|
||||
<div className="flex items-center justify-self-start">
|
||||
<Button
|
||||
variant="none"
|
||||
size="none"
|
||||
onClick={handleClick}
|
||||
className="text-sm"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox checked={!shouldSelectAll} id="terms" />
|
||||
<span className="label text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
||||
{shouldSelectAll ? "Select All" : "Unselect All"}
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="col-span-2 flex grid-cols-1 gap-2 justify-self-end">
|
||||
<div className="flex items-center gap-2">
|
||||
<div>
|
||||
<ShadTooltip
|
||||
content={
|
||||
disableFunctions ? (
|
||||
<span>Select items to export</span>
|
||||
) : (
|
||||
<span>Export selected items</span>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant="none"
|
||||
size="none"
|
||||
onClick={handleExport}
|
||||
disabled={disableFunctions}
|
||||
>
|
||||
<IconComponent
|
||||
name="FileDown"
|
||||
className={cn("h-5 w-5 text-primary transition-all")}
|
||||
/>
|
||||
</Button>
|
||||
</ShadTooltip>
|
||||
</div>
|
||||
<div>
|
||||
<ShadTooltip
|
||||
content={
|
||||
|
|
@ -59,12 +81,17 @@ const HeaderComponent = ({
|
|||
)
|
||||
}
|
||||
>
|
||||
<button onClick={handleDuplicate} disabled={disableFunctions}>
|
||||
<Button
|
||||
variant="none"
|
||||
size="none"
|
||||
onClick={handleDuplicate}
|
||||
disabled={disableFunctions}
|
||||
>
|
||||
<IconComponent
|
||||
name="Copy"
|
||||
className={cn("h-5 w-5 text-primary transition-all")}
|
||||
/>
|
||||
</button>
|
||||
</Button>
|
||||
</ShadTooltip>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -77,15 +104,20 @@ const HeaderComponent = ({
|
|||
)
|
||||
}
|
||||
>
|
||||
<button onClick={handleDelete} disabled={disableFunctions}>
|
||||
<Button
|
||||
variant="none"
|
||||
size="none"
|
||||
onClick={handleDelete}
|
||||
disabled={disableFunctions}
|
||||
>
|
||||
<IconComponent
|
||||
name="Trash2"
|
||||
className={cn(
|
||||
"h-5 w-5 text-primary transition-all",
|
||||
disableFunctions ? "" : "hover:text-destructive"
|
||||
disableFunctions ? "" : "hover:text-destructive",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</Button>
|
||||
</ShadTooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
|||
export default function SettingsPage(): JSX.Element {
|
||||
const pathname = location.pathname;
|
||||
const setCurrentFlowId = useFlowsManagerStore(
|
||||
(state) => state.setCurrentFlowId
|
||||
(state) => state.setCurrentFlowId,
|
||||
);
|
||||
useEffect(() => {
|
||||
setCurrentFlowId("");
|
||||
|
|
@ -59,7 +59,10 @@ export default function SettingsPage(): JSX.Element {
|
|||
title: "Messages",
|
||||
href: "/settings/messages",
|
||||
icon: (
|
||||
<ForwardedIconComponent name="Keyboard" className="w-5 stroke-[1.5]" />
|
||||
<ForwardedIconComponent
|
||||
name="MessagesSquare"
|
||||
className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
|
@ -72,8 +75,10 @@ export default function SettingsPage(): JSX.Element {
|
|||
<aside className="flex h-full shrink-0 flex-col space-y-6 lg:w-[20vw]">
|
||||
<SidebarNav items={sidebarNavItems} />
|
||||
</aside>
|
||||
<div className="h-full w-full flex-1 pb-8">
|
||||
<Outlet />
|
||||
<div className="flex h-full w-full flex-1 flex-col">
|
||||
<div className="flex-1 pb-8">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PageLayout>
|
||||
|
|
|
|||
|
|
@ -52,23 +52,17 @@ export default function ApiKeysPage() {
|
|||
/>
|
||||
|
||||
<div className="flex h-full w-full flex-col justify-between">
|
||||
<Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4">
|
||||
<CardContent className="h-full">
|
||||
<TableComponent
|
||||
overlayNoRowsTemplate="No data available"
|
||||
onSelectionChanged={(event: SelectionChangedEvent) => {
|
||||
setSelectedRows(
|
||||
event.api.getSelectedRows().map((row) => row.id),
|
||||
);
|
||||
}}
|
||||
rowSelection="multiple"
|
||||
suppressRowClickSelection={true}
|
||||
pagination={true}
|
||||
columnDefs={columnDefs}
|
||||
rowData={keysList.current}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<TableComponent
|
||||
overlayNoRowsTemplate="No data available"
|
||||
onSelectionChanged={(event: SelectionChangedEvent) => {
|
||||
setSelectedRows(event.api.getSelectedRows().map((row) => row.id));
|
||||
}}
|
||||
rowSelection="multiple"
|
||||
suppressRowClickSelection={true}
|
||||
pagination={true}
|
||||
columnDefs={columnDefs}
|
||||
rowData={keysList.current}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -16,13 +16,13 @@ import { cn } from "../../../../utils/utils";
|
|||
|
||||
export default function GlobalVariablesPage() {
|
||||
const globalVariablesEntries = useGlobalVariablesStore(
|
||||
(state) => state.globalVariablesEntries
|
||||
(state) => state.globalVariablesEntries,
|
||||
);
|
||||
const removeGlobalVariable = useGlobalVariablesStore(
|
||||
(state) => state.removeGlobalVariable
|
||||
(state) => state.removeGlobalVariable,
|
||||
);
|
||||
const globalVariables = useGlobalVariablesStore(
|
||||
(state) => state.globalVariables
|
||||
(state) => state.globalVariables,
|
||||
);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const getVariableId = useGlobalVariablesStore((state) => state.getVariableId);
|
||||
|
|
@ -154,7 +154,7 @@ export default function GlobalVariablesPage() {
|
|||
<IconComponent
|
||||
name="Trash2"
|
||||
className={cn(
|
||||
"h-5 w-5 text-destructive group-disabled:text-primary"
|
||||
"h-5 w-5 text-destructive group-disabled:text-primary",
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
|
|
@ -168,23 +168,17 @@ export default function GlobalVariablesPage() {
|
|||
</div>
|
||||
|
||||
<div className="flex h-full w-full flex-col justify-between">
|
||||
<Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4">
|
||||
<CardContent className="h-full">
|
||||
<TableComponent
|
||||
overlayNoRowsTemplate="No data available"
|
||||
onSelectionChanged={(event: SelectionChangedEvent) => {
|
||||
setSelectedRows(
|
||||
event.api.getSelectedRows().map((row) => row.name)
|
||||
);
|
||||
}}
|
||||
rowSelection="multiple"
|
||||
suppressRowClickSelection={true}
|
||||
pagination={true}
|
||||
columnDefs={colDefs}
|
||||
rowData={rowData}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<TableComponent
|
||||
overlayNoRowsTemplate="No data available"
|
||||
onSelectionChanged={(event: SelectionChangedEvent) => {
|
||||
setSelectedRows(event.api.getSelectedRows().map((row) => row.name));
|
||||
}}
|
||||
rowSelection="multiple"
|
||||
suppressRowClickSelection={true}
|
||||
pagination={true}
|
||||
columnDefs={colDefs}
|
||||
rowData={rowData}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -115,15 +115,11 @@ export default function ShortcutsPage() {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex h-full w-full flex-col justify-between">
|
||||
<Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4">
|
||||
<CardContent className="h-full">
|
||||
<TableComponent
|
||||
pagination={false}
|
||||
columnDefs={colDefs}
|
||||
rowData={nodesRowData}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<TableComponent
|
||||
pagination={false}
|
||||
columnDefs={colDefs}
|
||||
rowData={nodesRowData}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export default function MessagesPage() {
|
|||
setSelectedRows,
|
||||
setSuccessData,
|
||||
setErrorData,
|
||||
selectedRows
|
||||
selectedRows,
|
||||
);
|
||||
|
||||
const { handleUpdate } = useUpdateMessage(setSuccessData, setErrorData);
|
||||
|
|
@ -52,29 +52,25 @@ export default function MessagesPage() {
|
|||
handleRemoveMessages={handleRemoveMessages}
|
||||
/>
|
||||
|
||||
<div className="flex h-full w-full flex-col justify-between pb-8">
|
||||
<Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4">
|
||||
<CardContent className="h-full">
|
||||
<TableComponent
|
||||
readOnlyEdit
|
||||
onCellEditRequest={(event) => {
|
||||
handleUpdateMessage(event);
|
||||
}}
|
||||
editable={["Sender Name", "Message"]}
|
||||
overlayNoRowsTemplate="No data available"
|
||||
onSelectionChanged={(event: SelectionChangedEvent) => {
|
||||
setSelectedRows(
|
||||
event.api.getSelectedRows().map((row) => row.index)
|
||||
);
|
||||
}}
|
||||
rowSelection="multiple"
|
||||
suppressRowClickSelection={true}
|
||||
pagination={true}
|
||||
columnDefs={columns}
|
||||
rowData={messages}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="flex h-full w-full flex-col justify-between">
|
||||
<TableComponent
|
||||
readOnlyEdit
|
||||
onCellEditRequest={(event) => {
|
||||
handleUpdateMessage(event);
|
||||
}}
|
||||
editable={["Sender Name", "Message"]}
|
||||
overlayNoRowsTemplate="No data available"
|
||||
onSelectionChanged={(event: SelectionChangedEvent) => {
|
||||
setSelectedRows(
|
||||
event.api.getSelectedRows().map((row) => row.index),
|
||||
);
|
||||
}}
|
||||
rowSelection="multiple"
|
||||
suppressRowClickSelection={true}
|
||||
pagination={true}
|
||||
columnDefs={columns}
|
||||
rowData={messages}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
} from "../pages/MainPage/services";
|
||||
import { FoldersStoreType } from "../types/zustand/folders";
|
||||
import useFlowsManagerStore from "./flowsManagerStore";
|
||||
import { uploadFlowsToDatabase } from "../controllers/API";
|
||||
|
||||
export const useFolderStore = create<FoldersStoreType>((set, get) => ({
|
||||
folders: [],
|
||||
|
|
@ -17,18 +18,18 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
|
|||
getFolders().then(
|
||||
(res) => {
|
||||
const foldersWithoutStarterProjects = res.filter(
|
||||
(folder) => folder.name !== STARTER_FOLDER_NAME
|
||||
(folder) => folder.name !== STARTER_FOLDER_NAME,
|
||||
);
|
||||
|
||||
const starterProjects = res.find(
|
||||
(folder) => folder.name === STARTER_FOLDER_NAME
|
||||
(folder) => folder.name === STARTER_FOLDER_NAME,
|
||||
);
|
||||
|
||||
set({ starterProjectId: starterProjects!.id ?? "" });
|
||||
set({ folders: foldersWithoutStarterProjects });
|
||||
|
||||
const myCollectionId = res?.find(
|
||||
(f) => f.name === DEFAULT_FOLDER
|
||||
(f) => f.name === DEFAULT_FOLDER,
|
||||
)?.id;
|
||||
|
||||
set({ myCollectionId });
|
||||
|
|
@ -45,7 +46,7 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
|
|||
set({ folders: [] });
|
||||
get().setLoading(false);
|
||||
reject();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
@ -65,7 +66,7 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
|
|||
},
|
||||
() => {
|
||||
get().setLoadingById(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
@ -100,7 +101,7 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
|
|||
folderIdDragging: "",
|
||||
setFolderIdDragging: (id) => set(() => ({ folderIdDragging: id })),
|
||||
uploadFolder: () => {
|
||||
return new Promise<void>(() => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.onchange = (event: Event) => {
|
||||
|
|
@ -111,8 +112,31 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
|
|||
const file = (event.target as HTMLInputElement).files![0];
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
uploadFlowsFromFolders(formData).then(() => {
|
||||
get().getFoldersApi(true);
|
||||
file.text().then((text) => {
|
||||
const data = JSON.parse(text);
|
||||
if (data.data?.nodes) {
|
||||
useFlowsManagerStore
|
||||
.getState()
|
||||
.addFlow(true, data)
|
||||
.then(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
uploadFlowsFromFolders(formData)
|
||||
.then(() => {
|
||||
get()
|
||||
.getFoldersApi(true)
|
||||
.then(() => {
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export type FoldersStoreType = {
|
|||
setFolderUrl: (folderUrl: string) => void;
|
||||
folderDragging: boolean;
|
||||
setFolderDragging: (set: boolean) => void;
|
||||
uploadFolder: (folderId: string) => void;
|
||||
uploadFolder: (folderId: string) => Promise<void>;
|
||||
folderIdDragging: string;
|
||||
setFolderIdDragging: (id: string) => void;
|
||||
starterProjectId: string;
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ import {
|
|||
FolderIcon,
|
||||
FolderPlus,
|
||||
FolderPlusIcon,
|
||||
FolderUp,
|
||||
FormInput,
|
||||
Forward,
|
||||
Gift,
|
||||
|
|
@ -431,6 +432,7 @@ export const nodeIconsLucide: iconsType = {
|
|||
ChevronLeft,
|
||||
SlidersHorizontal,
|
||||
Palette,
|
||||
FolderUp,
|
||||
Blocks,
|
||||
ChevronDown,
|
||||
ArrowLeft,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue