langflow/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx
anovazzi1 7ec7af587d fix(typesContext.tsx): import the missing removeCountFromString function from utils/utils to fix a reference error
fix(typesContext.tsx): update the display name of the component node to remove the count and add the increment value
fix(typesContext.tsx): clone the component node before assigning it to newData to prevent reference error
fix(extraSidebarComponent/index.tsx): import the missing removeCountFromString function from utils/utils to fix a reference error
fix(extraSidebarComponent/index.tsx): update the type of the dragged item to remove the count from the name
feat(utils.ts): add removeCountFromString function to remove the count from a string
2023-10-02 17:36:23 -03:00

295 lines
10 KiB
TypeScript

import { cloneDeep } from "lodash";
import { useContext, useEffect, useState } from "react";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import IconComponent from "../../../../components/genericIconComponent";
import { Input } from "../../../../components/ui/input";
import { Separator } from "../../../../components/ui/separator";
import { alertContext } from "../../../../contexts/alertContext";
import { TabsContext } from "../../../../contexts/tabsContext";
import { typesContext } from "../../../../contexts/typesContext";
import ApiModal from "../../../../modals/ApiModal";
import ExportModal from "../../../../modals/exportModal";
import { APIClassType, APIObjectType } from "../../../../types/api";
import {
nodeColors,
nodeIconsLucide,
nodeNames,
} from "../../../../utils/styleUtils";
import { classNames, removeCountFromString } from "../../../../utils/utils";
import DisclosureComponent from "../DisclosureComponent";
export default function ExtraSidebar(): JSX.Element {
const { data, templates, getFilterEdge, setFilterEdge } =
useContext(typesContext);
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt } =
useContext(TabsContext);
const { setSuccessData, setErrorData } = useContext(alertContext);
const [dataFilter, setFilterData] = useState(data);
const [search, setSearch] = useState("");
const isPending = tabsState[tabId]?.isPending;
function onDragStart(
event: React.DragEvent<any>,
data: { type: string; node?: APIClassType }
): void {
//start drag event
var crt = event.currentTarget.cloneNode(true);
crt.style.position = "absolute";
crt.style.top = "-500px";
crt.style.right = "-500px";
crt.classList.add("cursor-grabbing");
document.body.appendChild(crt);
event.dataTransfer.setDragImage(crt, 0, 0);
event.dataTransfer.setData("nodedata", JSON.stringify(data));
}
// Handle showing components after use search input
function handleSearchInput(e: string) {
if (e === "") {
setFilterData(data);
return;
}
setFilterData((_) => {
let ret = {};
Object.keys(data).forEach((d: keyof APIObjectType, i) => {
ret[d] = {};
let keys = Object.keys(data[d]).filter((nd) =>
nd.toLowerCase().includes(e.toLowerCase())
);
keys.forEach((element) => {
ret[d][element] = data[d][element];
});
});
return ret;
});
}
const flow = flows.find((flow) => flow.id === tabId);
useEffect(() => {
// show components with error on load
let errors: string[] = [];
Object.keys(templates).forEach((component) => {
if (templates[component].error) {
errors.push(component);
}
});
if (errors.length > 0)
setErrorData({ title: " Components with errors: ", list: errors });
}, []);
function handleBlur() {
setFilterData(data);
setFilterEdge([]);
setSearch("");
}
useEffect(() => {
if (getFilterEdge.length === 0 && search === "") {
setFilterData(data);
setFilterEdge([]);
setSearch("");
}
}, [getFilterEdge]);
useEffect(() => {
if (getFilterEdge?.length > 0) {
setFilterData((_) => {
let dataClone = cloneDeep(data);
let ret = {};
Object.keys(dataClone).forEach((d: keyof APIObjectType, i) => {
ret[d] = {};
if (getFilterEdge.some((x) => x.family === d)) {
ret[d] = dataClone[d];
const filtered = getFilterEdge
.filter((x) => x.family === d)
.pop()
.type.split(",");
for (let i = 0; i < filtered.length; i++) {
filtered[i] = filtered[i].trimStart();
}
if (filtered.some((x) => x !== "")) {
let keys = Object.keys(dataClone[d]).filter((nd) =>
filtered.includes(nd)
);
Object.keys(dataClone[d]).forEach((element) => {
if (!keys.includes(element)) {
delete ret[d][element];
}
});
}
}
});
setSearch("search");
return ret;
});
}
}, [getFilterEdge]);
return (
<div className="side-bar-arrangement">
<div className="side-bar-buttons-arrangement">
<div className="side-bar-button">
<ShadTooltip content="Import" side="top">
<button
className="extra-side-bar-buttons"
onClick={() => {
uploadFlow();
}}
>
<IconComponent name="FileUp" className="side-bar-button-size " />
</button>
</ShadTooltip>
</div>
<div className="side-bar-button">
<ExportModal>
<ShadTooltip content="Export" side="top">
<div className={classNames("extra-side-bar-buttons")}>
<IconComponent
name="FileDown"
className="side-bar-button-size"
/>
</div>
</ShadTooltip>
</ExportModal>
</div>
<ShadTooltip content={"Code"} side="top">
<div className="side-bar-button">
{flow && flow.data && (
<ApiModal flow={flow}>
<button
className={"w-full " + (!isBuilt ? "button-disable" : "")}
>
<div className={classNames("extra-side-bar-buttons")}>
<IconComponent
name="Code2"
className={
"side-bar-button-size" +
(isBuilt ? " " : " extra-side-bar-save-disable")
}
/>
</div>
</button>
</ApiModal>
)}
</div>
</ShadTooltip>
<div className="side-bar-button">
<ShadTooltip content="Save" side="top">
<button
className={
"extra-side-bar-buttons " + (isPending ? "" : "button-disable")
}
onClick={(event) => {
saveFlow(flow!);
}}
>
<IconComponent
name="Save"
className={
"side-bar-button-size" +
(isPending ? " " : " extra-side-bar-save-disable")
}
/>
</button>
</ShadTooltip>
</div>
</div>
<Separator />
<div className="side-bar-search-div-placement">
<Input
onFocusCapture={() => handleBlur()}
type="text"
name="search"
id="search"
placeholder="Search"
className="nopan nodrag noundo nocopy input-search"
onChange={(event) => {
handleSearchInput(event.target.value);
// Set search input state
setSearch(event.target.value);
}}
/>
<div className="search-icon">
<IconComponent
name="Search"
className={"h-5 w-5 stroke-[1.5] text-primary"}
aria-hidden="true"
/>
</div>
</div>
<div className="side-bar-components-div-arrangement">
{Object.keys(dataFilter)
.sort()
.map((SBSectionName: keyof APIObjectType, index) =>
Object.keys(dataFilter[SBSectionName]).length > 0 ? (
<DisclosureComponent
openDisc={search.length == 0 ? false : true}
key={index}
button={{
title: nodeNames[SBSectionName] ?? nodeNames.unknown,
Icon:
nodeIconsLucide[SBSectionName] ?? nodeIconsLucide.unknown,
}}
>
<div className="side-bar-components-gap">
{Object.keys(dataFilter[SBSectionName])
.sort()
.map((SBItemName: string, index) => (
<ShadTooltip
content={data[SBSectionName][SBItemName].display_name}
side="right"
key={index}
>
<div key={SBItemName} data-tooltip-id={SBItemName}>
<div
draggable={!data[SBSectionName][SBItemName].error}
className={
"side-bar-components-border bg-background" +
(data[SBSectionName][SBItemName].error
? " cursor-not-allowed select-none"
: "")
}
style={{
borderLeftColor:
nodeColors[SBSectionName] ?? nodeColors.unknown,
}}
onDragStart={(event) =>
onDragStart(event, {
//split type to remove type in nodes saved with same name removing it's
type: removeCountFromString(SBItemName),
node: data[SBSectionName][SBItemName],
})
}
onDragEnd={() => {
document.body.removeChild(
document.getElementsByClassName(
"cursor-grabbing"
)[0]
);
}}
>
<div className="side-bar-components-div-form">
<span className="side-bar-components-text">
{data[SBSectionName][SBItemName].display_name}
</span>
<IconComponent
name="Menu"
className="side-bar-components-icon "
/>
</div>
</div>
</div>
</ShadTooltip>
))}
</div>
</DisclosureComponent>
) : (
<div key={index}></div>
)
)}
</div>
</div>
);
}