refactor: Cleanup up unused frontend files using Knip (#7506)
* extrasidebar cleanup * shad component cleanup * components and types batch * controllers cleanup * helpers and table components * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
29e0179794
commit
960d46250f
76 changed files with 12 additions and 4011 deletions
92
src/frontend/package-lock.json
generated
92
src/frontend/package-lock.json
generated
|
|
@ -14,22 +14,18 @@
|
|||
"@million/lint": "^1.0.0-rc.26",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-collapsible": "^1.1.1",
|
||||
"@radix-ui/react-dialog": "^1.1.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@radix-ui/react-form": "^0.0.3",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-menubar": "^1.0.4",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-progress": "^1.0.3",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slider": "^1.2.1",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-toggle": "^1.0.3",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@tabler/icons-react": "^3.6.0",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
|
|
@ -3038,6 +3034,7 @@
|
|||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.0.3.tgz",
|
||||
"integrity": "sha512-kgE+Z/haV6fxE5WqIXj05KkaXa3OkZASoTDy25yX2EIp/x0c54rOH/vFr5nOZTg7n7T1z8bSyXmiVIFP9bbhPQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
|
|
@ -3066,6 +3063,7 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz",
|
||||
"integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
|
|
@ -3074,6 +3072,7 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz",
|
||||
"integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
|
|
@ -3091,6 +3090,7 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz",
|
||||
"integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
|
|
@ -3108,6 +3108,7 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz",
|
||||
"integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||
|
|
@ -3126,6 +3127,7 @@
|
|||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.0.2.tgz",
|
||||
"integrity": "sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-primitive": "1.0.3"
|
||||
|
|
@ -3149,6 +3151,7 @@
|
|||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz",
|
||||
"integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.2"
|
||||
|
|
@ -3172,6 +3175,7 @@
|
|||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.1"
|
||||
|
|
@ -3190,6 +3194,7 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz",
|
||||
"integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
|
|
@ -3289,37 +3294,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menubar": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.6.tgz",
|
||||
"integrity": "sha512-FHq7+3DlXwh/7FOM4i0G4bC4vPjiq89VEEvNF4VMLchGnaUuUbE5uKXMUCjdKaOghEEMeiKa5XCa2Pk4kteWmg==",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.1",
|
||||
"@radix-ui/react-collection": "1.1.2",
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-direction": "1.1.0",
|
||||
"@radix-ui/react-id": "1.1.0",
|
||||
"@radix-ui/react-menu": "2.1.6",
|
||||
"@radix-ui/react-primitive": "2.0.2",
|
||||
"@radix-ui/react-roving-focus": "1.1.2",
|
||||
"@radix-ui/react-use-controllable-state": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-popover": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.6.tgz",
|
||||
|
|
@ -3455,29 +3429,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-progress": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.2.tgz",
|
||||
"integrity": "sha512-u1IgJFQ4zNAUTjGdDL5dcl/U8ntOR6jsnhxKb5RKp5Ozwl88xKR9EqRZOe/Mk8tnx0x5tNUe2F+MzsyjqMg0MA==",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-roving-focus": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.2.tgz",
|
||||
|
|
@ -3576,6 +3527,7 @@
|
|||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.2.3.tgz",
|
||||
"integrity": "sha512-nNrLAWLjGESnhqBqcCNW4w2nn7LxudyMzeB6VgdyAnFLC6kfQgnAjSL2v6UkQTnDctJBlxrmxfplWS4iYjdUTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/number": "1.1.0",
|
||||
"@radix-ui/primitive": "1.1.1",
|
||||
|
|
@ -3678,30 +3630,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toggle": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.2.tgz",
|
||||
"integrity": "sha512-lntKchNWx3aCHuWKiDY+8WudiegQvBpDRAYL8dKLRvKEH8VOpl0XX6SSU/bUBqIRJbcTy4+MW06Wv8vgp10rzQ==",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.0.2",
|
||||
"@radix-ui/react-use-controllable-state": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.8.tgz",
|
||||
|
|
|
|||
|
|
@ -9,22 +9,18 @@
|
|||
"@million/lint": "^1.0.0-rc.26",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-collapsible": "^1.1.1",
|
||||
"@radix-ui/react-dialog": "^1.1.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@radix-ui/react-form": "^0.0.3",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-menubar": "^1.0.4",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-progress": "^1.0.3",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slider": "^1.2.1",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-toggle": "^1.0.3",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@tabler/icons-react": "^3.6.0",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
export const convertToTableRows = (obj: Object) => {
|
||||
const tokensArray = [Object.values(obj)[0]];
|
||||
return tokensArray;
|
||||
};
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import { useEffect } from "react";
|
||||
|
||||
export function useOnClickOutside(ref, handler) {
|
||||
useEffect(() => {
|
||||
const listener = (event: Event) => {
|
||||
// Do nothing if clicking ref's element or its children
|
||||
if (!ref.current || ref.current.contains(event.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
handler(event);
|
||||
};
|
||||
|
||||
// Attach the listener to the document
|
||||
document.addEventListener("mousedown", listener, { passive: true });
|
||||
|
||||
// Attach the listener to the react-flow instance
|
||||
const reactFlowContainer = document.querySelector(".react-flow");
|
||||
if (reactFlowContainer) {
|
||||
reactFlowContainer.addEventListener("mousedown", listener, {
|
||||
passive: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Clean up the listener when the component is unmounted
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", listener);
|
||||
if (reactFlowContainer) {
|
||||
reactFlowContainer.removeEventListener("mousedown", listener);
|
||||
}
|
||||
};
|
||||
}, [ref, handler]); // Rerun only if ref or handler changes
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
import { useState } from "react";
|
||||
import { AccordionComponentType } from "../../../../types/components";
|
||||
import IconComponent from "../../../common/genericIconComponent";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "../../../ui/custom-accordion";
|
||||
|
||||
export default function FolderAccordionComponent({
|
||||
trigger,
|
||||
open = [],
|
||||
keyValue,
|
||||
options,
|
||||
}: AccordionComponentType): JSX.Element {
|
||||
const [value, setValue] = useState(
|
||||
open.length === 0 ? "" : getOpenAccordion(),
|
||||
);
|
||||
|
||||
function getOpenAccordion(): string {
|
||||
let value = "";
|
||||
open.forEach((el) => {
|
||||
if (el == trigger) {
|
||||
value = trigger;
|
||||
}
|
||||
});
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function handleClick(): void {
|
||||
value === "" ? setValue(keyValue!) : setValue("");
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Accordion
|
||||
type="single"
|
||||
className="w-full"
|
||||
value={value}
|
||||
onValueChange={setValue}
|
||||
>
|
||||
<AccordionItem value={keyValue!} className="">
|
||||
<AccordionTrigger
|
||||
onClick={() => {
|
||||
handleClick();
|
||||
}}
|
||||
className="px-2"
|
||||
>
|
||||
{trigger}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
{options!.map((option, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex cursor-pointer px-2 py-1 hover:bg-muted-foreground/10"
|
||||
>
|
||||
<IconComponent
|
||||
name={option.icon}
|
||||
className="relative top-[1.5px] mr-2 h-4 w-4"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="truncate">{option.title}</span>
|
||||
</div>
|
||||
))}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import TableAutoCellRender from "../tableComponent/components/tableAutoCellRender";
|
||||
|
||||
export default function ArrayReader({ array }: { array: any[] }): JSX.Element {
|
||||
//TODO check array type
|
||||
return (
|
||||
<div>
|
||||
<ul>
|
||||
{array.map((item, index) => (
|
||||
<li key={index}>{<TableAutoCellRender value={item} />}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export const CatchAllRoute = () => {
|
||||
const navigate = useCustomNavigate();
|
||||
|
||||
// Redirect to the root ("/") when the catch-all route is matched
|
||||
useEffect(() => {
|
||||
navigate("/");
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import ForwardedIconComponent from "../genericIconComponent";
|
||||
|
||||
const FetchIconComponent = ({
|
||||
source,
|
||||
name,
|
||||
}: {
|
||||
source: string;
|
||||
name: string;
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
{source ? (
|
||||
<img src={source} alt={name} />
|
||||
) : (
|
||||
<ForwardedIconComponent name="Unknown" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FetchIconComponent;
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import { Transition } from "@headlessui/react";
|
||||
|
||||
import IconComponent from "../../genericIconComponent";
|
||||
|
||||
export default function ChatTrigger({}): JSX.Element {
|
||||
return (
|
||||
<Transition
|
||||
show={true}
|
||||
appear={true}
|
||||
enter="transition ease-out duration-300"
|
||||
enterFrom="translate-y-96"
|
||||
enterTo="translate-y-0"
|
||||
leave="transition ease-in duration-300"
|
||||
leaveFrom="translate-y-0"
|
||||
leaveTo="translate-y-96"
|
||||
>
|
||||
<button
|
||||
className={
|
||||
"shadow-round-btn-shadow hover:shadow-round-btn-shadow message-button cursor-pointer"
|
||||
}
|
||||
>
|
||||
<div className="flex gap-3">
|
||||
<IconComponent
|
||||
name="Zap"
|
||||
className={"message-button-icon h-6 w-6 transition-all"}
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
import useAddFlow from "@/hooks/flows/use-add-flow";
|
||||
import { getComponent } from "../../../../controllers/API";
|
||||
import { storeComponent } from "../../../../types/store";
|
||||
import cloneFlowWithParent from "../../../../utils/storeUtils";
|
||||
|
||||
const useInstallComponent = (
|
||||
data: storeComponent,
|
||||
name: string,
|
||||
isStore: boolean,
|
||||
downloadsCount: number,
|
||||
setDownloadsCount: (value: any) => void,
|
||||
setLoading: (value: boolean) => void,
|
||||
setSuccessData: (value: { title: string }) => void,
|
||||
setErrorData: (value: { title: string; list: string[] }) => void,
|
||||
) => {
|
||||
const addFlow = useAddFlow();
|
||||
|
||||
const handleInstall = () => {
|
||||
const temp = downloadsCount;
|
||||
setDownloadsCount((old) => Number(old) + 1);
|
||||
setLoading(true);
|
||||
|
||||
getComponent(data.id)
|
||||
.then((res) => {
|
||||
const newFlow = cloneFlowWithParent(res, res.id, data.is_component);
|
||||
addFlow({ flow: newFlow })
|
||||
.then((id) => {
|
||||
setSuccessData({
|
||||
title: `${name} ${isStore ? "Downloaded" : "Installed"} Successfully.`,
|
||||
});
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
setLoading(false);
|
||||
setErrorData({
|
||||
title: `Error ${isStore ? "downloading" : "installing"} the ${name}`,
|
||||
list: [error.response.data.detail],
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
setLoading(false);
|
||||
setErrorData({
|
||||
title: `Error ${isStore ? "downloading" : "installing"} the ${name}`,
|
||||
list: [err.response.data.detail],
|
||||
});
|
||||
setDownloadsCount(temp);
|
||||
});
|
||||
};
|
||||
|
||||
return { handleInstall };
|
||||
};
|
||||
|
||||
export default useInstallComponent;
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
import { track } from "@/customization/utils/analytics";
|
||||
import { useState } from "react";
|
||||
import { Control } from "react-hook-form";
|
||||
import useAlertStore from "../../../stores/alertStore";
|
||||
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
|
||||
import { FlowType } from "../../../types/flow";
|
||||
import { getInputsAndOutputs } from "../../../utils/storeUtils";
|
||||
import { cn } from "../../../utils/utils";
|
||||
import IconComponent from "../../common/genericIconComponent";
|
||||
import ShadTooltip from "../../common/shadTooltipComponent";
|
||||
import {
|
||||
Card,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../../ui/card";
|
||||
import { Checkbox } from "../../ui/checkbox";
|
||||
import { FormControl, FormField } from "../../ui/form";
|
||||
import useDragStart from "./hooks/use-on-drag-start";
|
||||
import { convertTestName } from "./utils/convert-test-name";
|
||||
|
||||
export default function CollectionCardComponent({
|
||||
data,
|
||||
disabled = false,
|
||||
onClick,
|
||||
control,
|
||||
}: {
|
||||
data: FlowType;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
control?: Control<any, any>;
|
||||
}) {
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
|
||||
const getFlowById = useFlowsManagerStore((state) => state.getFlowById);
|
||||
// const [openPlayground, setOpenPlayground] = useState(false);
|
||||
const [loadingPlayground, setLoadingPlayground] = useState(false);
|
||||
const selectedFlowsComponentsCards = useFlowsManagerStore(
|
||||
(state) => state.selectedFlowsComponentsCards,
|
||||
);
|
||||
|
||||
function hasPlayground(flow?: FlowType) {
|
||||
if (!flow) {
|
||||
return false;
|
||||
}
|
||||
const { inputs, outputs } = getInputsAndOutputs(flow?.data?.nodes ?? []);
|
||||
return inputs.length > 0 || outputs.length > 0;
|
||||
}
|
||||
const playground = !(data.is_component ?? false);
|
||||
const isSelectedCard =
|
||||
selectedFlowsComponentsCards?.includes(data?.id) ?? false;
|
||||
|
||||
const { onDragStart } = useDragStart(data);
|
||||
|
||||
const handlePlaygroundClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
track("Playground Button Clicked", { flowId: data.id });
|
||||
setLoadingPlayground(true);
|
||||
|
||||
if (data) {
|
||||
if (!hasPlayground(data)) {
|
||||
setErrorData({
|
||||
title: "Error",
|
||||
list: ["This flow doesn't have a playground."],
|
||||
});
|
||||
setLoadingPlayground(false);
|
||||
return;
|
||||
}
|
||||
setCurrentFlow(data);
|
||||
// setOpenPlayground(true);
|
||||
setLoadingPlayground(false);
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Error",
|
||||
list: ["Error getting flow data."],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card
|
||||
onDragStart={onDragStart}
|
||||
draggable
|
||||
data-testid={`card-${convertTestName(data.name)}`}
|
||||
//TODO check color schema
|
||||
className={cn(
|
||||
"group relative flex h-[11rem] flex-col justify-between overflow-hidden",
|
||||
!data.is_component &&
|
||||
"hover:bg-muted/50 hover:shadow-md hover:dark:bg-[#5f5f5f0e]",
|
||||
disabled ? "pointer-events-none opacity-50" : "",
|
||||
onClick ? "cursor-pointer" : "",
|
||||
isSelectedCard ? "border border-selected" : "",
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div>
|
||||
<CardHeader>
|
||||
<div>
|
||||
<CardTitle className="flex w-full items-start justify-between gap-3 text-xl">
|
||||
<IconComponent
|
||||
className={cn(
|
||||
"visible flex-shrink-0",
|
||||
data.is_component
|
||||
? "mx-0.5 h-6 w-6 text-component-icon"
|
||||
: "h-7 w-7 flex-shrink-0 text-flow-icon",
|
||||
)}
|
||||
name={data.is_component ? "ToyBrick" : "Group"}
|
||||
/>
|
||||
<ShadTooltip content={data.name}>
|
||||
<div className="w-full truncate pr-3">{data.name}</div>
|
||||
</ShadTooltip>
|
||||
{control && (
|
||||
<div
|
||||
className="flex"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<FormField
|
||||
control={control}
|
||||
name={`${data.id}`}
|
||||
defaultValue={false}
|
||||
render={({ field }) => (
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
data-testid={`checkbox-component`}
|
||||
aria-label="checkbox-component"
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
className="h-5 w-5 border border-ring data-[state=checked]:border-selected data-[state=checked]:bg-selected"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</CardTitle>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex w-full flex-1 flex-wrap gap-2"></div>
|
||||
</div>
|
||||
<CardDescription className="pb-2 pt-2">
|
||||
<div className="truncate-doubleline">{data.description}</div>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</div>
|
||||
<CardFooter>
|
||||
<div className="z-50 flex w-full items-center justify-between gap-2">
|
||||
<div className="flex w-full flex-wrap items-end justify-end gap-2">
|
||||
{/* {playground && (
|
||||
<Button
|
||||
disabled={loadingPlayground || !hasPlayground(data)}
|
||||
key={data.id}
|
||||
tabIndex={-1}
|
||||
variant="primary"
|
||||
size="sm"
|
||||
className="gap-2 whitespace-nowrap bg-muted"
|
||||
data-testid={"playground-flow-button-" + data.id}
|
||||
onClick={handlePlaygroundClick}
|
||||
>
|
||||
{!loadingPlayground ? (
|
||||
<IconComponent
|
||||
name="BotMessageSquareIcon"
|
||||
className="h-4 w-4 select-none"
|
||||
/>
|
||||
) : (
|
||||
<Loading className="h-4 w-4 text-medium-indigo" />
|
||||
)}
|
||||
Playground
|
||||
</Button>
|
||||
)} */}
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
{/* {openPlayground && (
|
||||
<IOModal
|
||||
key={data.id}
|
||||
cleanOnClose={true}
|
||||
open={openPlayground}
|
||||
setOpen={setOpenPlayground}
|
||||
>
|
||||
<></>
|
||||
</IOModal>
|
||||
)} */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export function convertTestName(name: string): string {
|
||||
return name.replace(/ /g, "-").toLowerCase();
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
import { useState } from "react";
|
||||
import { dropdownButtonPropsType } from "../../../types/components";
|
||||
import IconComponent from "../../common/genericIconComponent";
|
||||
import { Button } from "../../ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "../../ui/dropdown-menu";
|
||||
|
||||
export default function DropdownButton({
|
||||
firstButtonName,
|
||||
onFirstBtnClick,
|
||||
options,
|
||||
plusButton = false,
|
||||
dropdownOptions = true,
|
||||
isFetchingFolders = false,
|
||||
}: dropdownButtonPropsType): JSX.Element {
|
||||
const [showOptions, setShowOptions] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DropdownMenu open={showOptions}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
id="new-project-btn"
|
||||
variant="primary"
|
||||
className={
|
||||
"relative" + dropdownOptions ? "pl-[12px]" : "pl-[12px] pr-10"
|
||||
}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
onFirstBtnClick();
|
||||
}}
|
||||
disabled={isFetchingFolders}
|
||||
>
|
||||
{plusButton && (
|
||||
<IconComponent name="Plus" className="main-page-nav-button" />
|
||||
)}
|
||||
{firstButtonName}
|
||||
{dropdownOptions && (
|
||||
<div
|
||||
className="absolute right-2 items-center text-muted-foreground"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
setShowOptions(!showOptions);
|
||||
}}
|
||||
>
|
||||
{!showOptions ? (
|
||||
<IconComponent name="ChevronDown" />
|
||||
) : (
|
||||
<IconComponent name="ChevronUp" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
onPointerDownOutside={(event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
setShowOptions(!showOptions);
|
||||
}}
|
||||
>
|
||||
{options.map(({ name, onBtnClick }, index) => (
|
||||
<DropdownMenuItem onClick={onBtnClick} key={index}>
|
||||
{name}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
import { cn } from "@/utils/utils";
|
||||
|
||||
export const getInputClassName = ({
|
||||
disabled,
|
||||
password,
|
||||
setSelectedOption,
|
||||
selectedOption,
|
||||
pwdVisible,
|
||||
value,
|
||||
editNode,
|
||||
setSelectedOptions,
|
||||
isSelected,
|
||||
areOptionsSelected,
|
||||
className,
|
||||
}) => {
|
||||
const classes = {
|
||||
base: className || "",
|
||||
password:
|
||||
password &&
|
||||
(!setSelectedOption || selectedOption === "") &&
|
||||
!pwdVisible &&
|
||||
value !== ""
|
||||
? "text-clip password"
|
||||
: "",
|
||||
editNode: editNode ? "input-edit-node" : "",
|
||||
paddingRight: (() => {
|
||||
if (password && (setSelectedOption || setSelectedOptions))
|
||||
return "pr-[70px]";
|
||||
if (
|
||||
(!password && (setSelectedOption || setSelectedOptions)) ||
|
||||
(password && !(setSelectedOption || setSelectedOptions))
|
||||
)
|
||||
return "pr-8";
|
||||
return "";
|
||||
})(),
|
||||
selected:
|
||||
isSelected || areOptionsSelected
|
||||
? "font-jetbrains text-sm font-medium text-foreground"
|
||||
: "",
|
||||
};
|
||||
|
||||
return cn(
|
||||
classes.base,
|
||||
classes.password,
|
||||
classes.editNode,
|
||||
classes.paddingRight,
|
||||
classes.selected,
|
||||
);
|
||||
};
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
import ForwardedIconComponent from "@/components/common/genericIconComponent";
|
||||
import RenderIcons from "@/components/common/renderIconComponent";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useKeyboardShortcut } from "@/hooks/use-overlap-shortcuts";
|
||||
import { useShortcutsStore } from "@/stores/shortcuts";
|
||||
import { cn } from "@/utils/utils";
|
||||
import { useCallback } from "react";
|
||||
|
||||
export const DropdownMenuInputList = ({
|
||||
index,
|
||||
dropdownOpen,
|
||||
setDropdownOpen,
|
||||
editNode,
|
||||
handleDuplicateInput,
|
||||
removeInput,
|
||||
canDelete,
|
||||
}: {
|
||||
index: number;
|
||||
dropdownOpen: number | null;
|
||||
setDropdownOpen: (open: number) => void;
|
||||
editNode: boolean;
|
||||
handleDuplicateInput: (
|
||||
index: number,
|
||||
e: React.MouseEvent<HTMLDivElement> | KeyboardEvent,
|
||||
) => void;
|
||||
removeInput: (
|
||||
index: number,
|
||||
e: React.MouseEvent<HTMLDivElement> | KeyboardEvent,
|
||||
) => void;
|
||||
canDelete: boolean;
|
||||
}) => {
|
||||
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||
|
||||
const shortcutKeys = {
|
||||
duplicate:
|
||||
shortcuts.find((obj) => obj.name === "Duplicate")?.shortcut || "",
|
||||
delete: shortcuts.find((obj) => obj.name === "Delete")?.shortcut || "",
|
||||
};
|
||||
|
||||
const handleShortcut = useCallback(
|
||||
(shortcutName: string, event: KeyboardEvent) => {
|
||||
if (shortcutName === "duplicate") {
|
||||
handleDuplicateInput(index, event);
|
||||
} else if (shortcutName === "delete" && canDelete) {
|
||||
removeInput(index, event);
|
||||
}
|
||||
setDropdownOpen(-1);
|
||||
},
|
||||
[index, handleDuplicateInput, removeInput, setDropdownOpen],
|
||||
);
|
||||
|
||||
useKeyboardShortcut({
|
||||
shortcutKeys,
|
||||
isEnabled: dropdownOpen === index,
|
||||
onShortcut: handleShortcut,
|
||||
preventDefault: true,
|
||||
stopPropagation: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenu
|
||||
open={dropdownOpen === index}
|
||||
onOpenChange={(open) => setDropdownOpen(open ? index : -1)}
|
||||
>
|
||||
<DropdownMenuTrigger
|
||||
asChild
|
||||
tabIndex={index}
|
||||
className={cn(
|
||||
"absolute bg-background transition-opacity peer-focus:opacity-0",
|
||||
editNode ? "translate-x-[14rem]" : "translate-x-60",
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
data-testid={`input-list-dropdown-menu-${index}-${editNode ? "edit" : "view"}`}
|
||||
size={editNode ? "iconSm" : "iconMd"}
|
||||
className={cn("group")}
|
||||
autoFocus={false}
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="Ellipsis"
|
||||
aria-hidden="true"
|
||||
className="icon-size text-muted-foreground group-hover:text-foreground"
|
||||
/>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-[185px]" side="bottom" align="start">
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
handleDuplicateInput(index, e);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
data-testid={`input-list-dropdown-menu-${index}-duplicate`}
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="CopyPlus"
|
||||
aria-hidden="true"
|
||||
className="mr-2 h-4 w-4"
|
||||
/>
|
||||
<span>Duplicate</span>
|
||||
|
||||
<div className="flex grow content-end justify-end self-center text-[12px]">
|
||||
<span
|
||||
className={`flex content-end items-center rounded-sm bg-muted px-1.5 py-[0.1em] text-muted-foreground`}
|
||||
>
|
||||
<RenderIcons
|
||||
filteredShortcut={shortcuts
|
||||
.find((obj) => obj.name === "Duplicate")
|
||||
?.shortcut!?.split("+")}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
{canDelete && (
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
removeInput(index, e);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="cursor-pointer text-destructive"
|
||||
data-testid={`input-list-dropdown-menu-${index}-delete`}
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="Trash2"
|
||||
aria-hidden="true"
|
||||
className="mr-2 h-4 w-4"
|
||||
/>
|
||||
<span>Delete</span>
|
||||
|
||||
<div className="flex grow content-end justify-end self-center text-[12px]">
|
||||
<span
|
||||
className={`flex content-end items-center rounded-sm px-1.5 py-[0.1em] text-muted-foreground`}
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="Delete"
|
||||
className="h-4 w-4 stroke-2 text-red-400"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import { cn } from "@/utils/utils";
|
||||
|
||||
export default function ResetColumns({
|
||||
resetGrid,
|
||||
}: {
|
||||
resetGrid: () => void;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<div className={cn("absolute bottom-4 left-6")}>
|
||||
<span
|
||||
className="cursor-pointer underline"
|
||||
onClick={() => {
|
||||
resetGrid();
|
||||
}}
|
||||
>
|
||||
Reset Columns
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import Loading from "../../../ui/loading";
|
||||
|
||||
export default function LoadingOverlay() {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center bg-background align-middle">
|
||||
<Loading />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
import { CustomCellEditorProps } from "ag-grid-react";
|
||||
import { uniqueId } from "lodash";
|
||||
import ToggleShadComponent from "../../../toggleShadComponent";
|
||||
|
||||
export default function TableToggleCellEditor({
|
||||
value,
|
||||
onValueChange,
|
||||
colDef,
|
||||
}: CustomCellEditorProps) {
|
||||
value =
|
||||
(typeof value === "string" && value.toLowerCase() === "true") ||
|
||||
value === true
|
||||
? true
|
||||
: false;
|
||||
return (
|
||||
<div className="flex h-full items-center px-2">
|
||||
<ToggleShadComponent
|
||||
value={value}
|
||||
handleOnNewValue={(data) => {
|
||||
onValueChange?.(data.value);
|
||||
}}
|
||||
editNode={true}
|
||||
id={"toggle" + colDef?.colId + uniqueId()}
|
||||
disabled={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import { CustomTooltipProps } from "ag-grid-react";
|
||||
|
||||
export default function TableTooltipRender({ value }: CustomTooltipProps) {
|
||||
return (
|
||||
<div className="z-45 overflow-y-auto rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1">
|
||||
{value}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
export default function TextInputComponent({
|
||||
text,
|
||||
emissor,
|
||||
}: {
|
||||
text: string;
|
||||
emissor: string;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<strong> {emissor}</strong>
|
||||
<br></br>
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import { cn } from "@/utils/utils";
|
||||
|
||||
export const getTextAreaContentClasses = ({
|
||||
editNode,
|
||||
disabled,
|
||||
password,
|
||||
value,
|
||||
textAreaContentClasses,
|
||||
}) => {
|
||||
return cn(
|
||||
textAreaContentClasses.base,
|
||||
editNode ? textAreaContentClasses.editNode : textAreaContentClasses.normal,
|
||||
disabled && textAreaContentClasses.disabled,
|
||||
password !== undefined &&
|
||||
password &&
|
||||
value !== "" &&
|
||||
textAreaContentClasses.password,
|
||||
);
|
||||
};
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
export default function TextOutputComponent({
|
||||
text,
|
||||
emissor,
|
||||
}: {
|
||||
text: string;
|
||||
emissor: string;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<strong>{emissor}</strong>
|
||||
<br></br>
|
||||
<div className="w-80 break-all">{text}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
import { cn } from "@/utils/utils";
|
||||
|
||||
interface BorderBeamProps {
|
||||
className?: string;
|
||||
size?: number;
|
||||
duration?: number;
|
||||
borderWidth?: number;
|
||||
anchor?: number;
|
||||
colorFrom?: string;
|
||||
colorTo?: string;
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
export const BorderBeam = ({
|
||||
className,
|
||||
size = 200,
|
||||
duration = 15,
|
||||
anchor = 90,
|
||||
borderWidth = 1.5,
|
||||
colorFrom = "#ffaa40",
|
||||
colorTo = "#9c40ff",
|
||||
delay = 0,
|
||||
}: BorderBeamProps) => {
|
||||
return (
|
||||
<div
|
||||
style={
|
||||
{
|
||||
"--size": size,
|
||||
"--duration": duration,
|
||||
"--anchor": anchor,
|
||||
"--border-width": borderWidth,
|
||||
"--color-from": colorFrom,
|
||||
"--color-to": colorTo,
|
||||
"--delay": `-${delay}s`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className={cn(
|
||||
"pointer-events-none absolute inset-0 rounded-[inherit] [border:calc(var(--border-width)*1px)_solid_transparent]",
|
||||
|
||||
// mask styles
|
||||
"![mask-clip:padding-box,border-box] ![mask-composite:intersect] [mask:linear-gradient(transparent,transparent),linear-gradient(white,white)]",
|
||||
|
||||
// pseudo styles
|
||||
"after:absolute after:aspect-square after:w-[calc(var(--size)*1px)] after:animate-border-beam after:[animation-delay:var(--delay)] after:[background:linear-gradient(to_left,var(--color-from),var(--color-to),transparent)] after:[offset-anchor:calc(var(--anchor)*1%)_50%] after:[offset-path:rect(0_auto_auto_0_round_calc(var(--size)*1px))]",
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
|
||||
|
||||
const Collapsible = CollapsiblePrimitive.Root;
|
||||
|
||||
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
|
||||
|
||||
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
|
||||
|
||||
export { Collapsible, CollapsibleContent, CollapsibleTrigger };
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { Check, ChevronsUpDown } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { cn } from "../../utils/utils";
|
||||
import { Button } from "./button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
} from "./command";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "./popover";
|
||||
|
||||
export function Combobox({
|
||||
items,
|
||||
onChange,
|
||||
}: {
|
||||
items: { value: string; label: string }[];
|
||||
onChange: (value: string[]) => void;
|
||||
}) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [value, setValue] = React.useState<string[]>([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
onChange(value);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="w-[200px] justify-between"
|
||||
>
|
||||
{value
|
||||
? items.find((framework) => value.includes(framework.value))?.label
|
||||
: "Select filter..."}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[200px] p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search filters..." />
|
||||
<CommandEmpty>No filters found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{items.map((framework) => (
|
||||
<CommandItem
|
||||
key={framework.value}
|
||||
value={framework.value}
|
||||
onSelect={(currentValue) => {
|
||||
setValue((old) => {
|
||||
if (old.includes(currentValue)) {
|
||||
return old.filter((item) => item !== currentValue);
|
||||
}
|
||||
return [...old, currentValue];
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
value.includes(framework.value)
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{framework.label}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
||||
import { ChevronDownIcon } from "@radix-ui/react-icons";
|
||||
import * as React from "react";
|
||||
import { cn } from "../../utils/utils";
|
||||
|
||||
const Accordion = AccordionPrimitive.Root;
|
||||
|
||||
const AccordionItem = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AccordionPrimitive.Item ref={ref} className={cn("", className)} {...props} />
|
||||
));
|
||||
AccordionItem.displayName = "AccordionItem";
|
||||
|
||||
const AccordionTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger asChild ref={ref} {...props}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-1 cursor-pointer items-center justify-between border-[1px] py-2 text-sm font-medium data-[state=closed]:rounded-md data-[state=open]:rounded-t-md data-[state=open]:border-b-0 data-[state=open]:bg-muted [&[data-state=open]>svg]:rotate-180",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
<ChevronDownIcon className="h-4 w-4 font-bold text-primary transition-transform duration-200" />
|
||||
</div>
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
));
|
||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
|
||||
|
||||
const AccordionContent = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden border-[1px] text-sm data-[state=open]:rounded-b-md data-[state=open]:border-t-0 data-[state=open]:bg-muted",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="pt-0">{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
));
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
|
||||
|
||||
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
|
||||
|
|
@ -1,176 +0,0 @@
|
|||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import * as React from "react";
|
||||
import {
|
||||
Controller,
|
||||
ControllerProps,
|
||||
FieldPath,
|
||||
FieldValues,
|
||||
FormProvider,
|
||||
useFormContext,
|
||||
} from "react-hook-form";
|
||||
import { cn } from "../../utils/utils";
|
||||
import { Label } from "./label";
|
||||
|
||||
const Form = FormProvider;
|
||||
|
||||
type FormFieldContextValue<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = {
|
||||
name: TName;
|
||||
};
|
||||
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||
{} as FormFieldContextValue,
|
||||
);
|
||||
|
||||
const FormField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>({
|
||||
...props
|
||||
}: ControllerProps<TFieldValues, TName>) => {
|
||||
return (
|
||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||
<Controller {...props} />
|
||||
</FormFieldContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const useFormField = () => {
|
||||
const fieldContext = React.useContext(FormFieldContext);
|
||||
const itemContext = React.useContext(FormItemContext);
|
||||
const { getFieldState, formState } = useFormContext();
|
||||
|
||||
const fieldState = getFieldState(fieldContext.name, formState);
|
||||
|
||||
if (!fieldContext) {
|
||||
throw new Error("useFormField should be used within <FormField>");
|
||||
}
|
||||
|
||||
const { id } = itemContext;
|
||||
|
||||
return {
|
||||
id,
|
||||
name: fieldContext.name,
|
||||
formItemId: `${id}-form-item`,
|
||||
formDescriptionId: `${id}-form-item-description`,
|
||||
formMessageId: `${id}-form-item-message`,
|
||||
...fieldState,
|
||||
};
|
||||
};
|
||||
|
||||
type FormItemContextValue = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||
{} as FormItemContextValue,
|
||||
);
|
||||
|
||||
const FormItem = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const id = React.useId();
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||
</FormItemContext.Provider>
|
||||
);
|
||||
});
|
||||
FormItem.displayName = "FormItem";
|
||||
|
||||
const FormLabel = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { error, formItemId } = useFormField();
|
||||
|
||||
return (
|
||||
<Label
|
||||
ref={ref}
|
||||
className={cn(error && "text-destructive", className)}
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
FormLabel.displayName = "FormLabel";
|
||||
|
||||
const FormControl = React.forwardRef<
|
||||
React.ElementRef<typeof Slot>,
|
||||
React.ComponentPropsWithoutRef<typeof Slot>
|
||||
>(({ ...props }, ref) => {
|
||||
const { error, formItemId, formDescriptionId, formMessageId } =
|
||||
useFormField();
|
||||
|
||||
return (
|
||||
<Slot
|
||||
ref={ref}
|
||||
id={formItemId}
|
||||
aria-describedby={
|
||||
!error
|
||||
? `${formDescriptionId}`
|
||||
: `${formDescriptionId} ${formMessageId}`
|
||||
}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
FormControl.displayName = "FormControl";
|
||||
|
||||
const FormDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { formDescriptionId } = useFormField();
|
||||
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formDescriptionId}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
FormDescription.displayName = "FormDescription";
|
||||
|
||||
const FormMessage = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { error, formMessageId } = useFormField();
|
||||
const body = error ? String(error?.message) : children;
|
||||
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formMessageId}
|
||||
className={cn("text-sm font-medium text-destructive", className)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
</p>
|
||||
);
|
||||
});
|
||||
FormMessage.displayName = "FormMessage";
|
||||
|
||||
export {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
useFormField,
|
||||
};
|
||||
|
|
@ -1,236 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import * as MenubarPrimitive from "@radix-ui/react-menubar";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "../../utils/utils";
|
||||
import IconComponent from "../common/genericIconComponent";
|
||||
|
||||
const MenubarMenu = MenubarPrimitive.Menu;
|
||||
|
||||
const MenubarGroup = MenubarPrimitive.Group;
|
||||
|
||||
const MenubarPortal = MenubarPrimitive.Portal;
|
||||
|
||||
const MenubarSub = MenubarPrimitive.Sub;
|
||||
|
||||
const MenubarRadioGroup = MenubarPrimitive.RadioGroup;
|
||||
|
||||
const Menubar = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenubarPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 items-center space-x-1 rounded-md border bg-background p-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Menubar.displayName = MenubarPrimitive.Root.displayName;
|
||||
|
||||
const MenubarTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenubarPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;
|
||||
|
||||
const MenubarSubTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
|
||||
inset?: boolean;
|
||||
}
|
||||
>(({ className, inset, children, ...props }, ref) => (
|
||||
<MenubarPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
||||
inset && "pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<IconComponent name="ChevronRight" className="ml-auto h-4 w-4" />
|
||||
</MenubarPrimitive.SubTrigger>
|
||||
));
|
||||
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;
|
||||
|
||||
const MenubarSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.SubContent>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenubarPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;
|
||||
|
||||
const MenubarContent = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
|
||||
>(
|
||||
(
|
||||
{ className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
|
||||
ref,
|
||||
) => (
|
||||
<MenubarPrimitive.Portal>
|
||||
<MenubarPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
alignOffset={alignOffset}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in slide-in-from-top-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</MenubarPrimitive.Portal>
|
||||
),
|
||||
);
|
||||
MenubarContent.displayName = MenubarPrimitive.Content.displayName;
|
||||
|
||||
const MenubarItem = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
|
||||
inset?: boolean;
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<MenubarPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
inset && "pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenubarItem.displayName = MenubarPrimitive.Item.displayName;
|
||||
|
||||
const MenubarCheckboxItem = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
|
||||
>(({ className, children, checked, ...props }, ref) => (
|
||||
<MenubarPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className,
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<MenubarPrimitive.ItemIndicator>
|
||||
<IconComponent name="Check" className="h-4 w-4" />
|
||||
</MenubarPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</MenubarPrimitive.CheckboxItem>
|
||||
));
|
||||
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;
|
||||
|
||||
const MenubarRadioItem = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.RadioItem>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<MenubarPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<MenubarPrimitive.ItemIndicator>
|
||||
<IconComponent name="Circle" className="h-2 w-2 fill-current" />
|
||||
</MenubarPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</MenubarPrimitive.RadioItem>
|
||||
));
|
||||
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;
|
||||
|
||||
const MenubarLabel = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
|
||||
inset?: boolean;
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<MenubarPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-semibold",
|
||||
inset && "pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenubarLabel.displayName = MenubarPrimitive.Label.displayName;
|
||||
|
||||
const MenubarSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof MenubarPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<MenubarPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;
|
||||
|
||||
const MenubarShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
MenubarShortcut.displayname = "MenubarShortcut";
|
||||
|
||||
export {
|
||||
Menubar,
|
||||
MenubarCheckboxItem,
|
||||
MenubarContent,
|
||||
MenubarGroup,
|
||||
MenubarItem,
|
||||
MenubarLabel,
|
||||
MenubarMenu,
|
||||
MenubarPortal,
|
||||
MenubarRadioGroup,
|
||||
MenubarRadioItem,
|
||||
MenubarSeparator,
|
||||
MenubarShortcut,
|
||||
MenubarSub,
|
||||
MenubarSubContent,
|
||||
MenubarSubTrigger,
|
||||
MenubarTrigger,
|
||||
};
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import ForwardedIconComponent from "@/components/common/genericIconComponent";
|
||||
import { cn } from "@/utils/utils";
|
||||
import * as React from "react";
|
||||
|
||||
interface MorphingMenuProps {
|
||||
trigger: React.ReactNode;
|
||||
items: {
|
||||
icon?: string;
|
||||
label: string;
|
||||
onClick?: () => void;
|
||||
}[];
|
||||
className?: string;
|
||||
buttonClassName?: string;
|
||||
itemsClassName?: string;
|
||||
variant?: "large" | "small";
|
||||
}
|
||||
|
||||
const MorphingMenu = React.forwardRef<HTMLDivElement, MorphingMenuProps>(
|
||||
(
|
||||
{ trigger, items, className, buttonClassName, itemsClassName, variant },
|
||||
ref,
|
||||
) => {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
|
||||
// Calculate menu height: header (40px) + (items * 36px) + padding (16px)
|
||||
const menuHeight = (variant == "large" ? 40 : 32) + items.length * 32 + 8;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-fit select-none flex-col items-center justify-center whitespace-nowrap transition-all",
|
||||
variant === "large" ? "h-10" : "h-8",
|
||||
isOpen ? "w-40" : variant === "large" ? "w-36" : "w-[134px]",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: isOpen
|
||||
? `${menuHeight}px`
|
||||
: variant === "large"
|
||||
? "40px"
|
||||
: "32px",
|
||||
}}
|
||||
className={cn(
|
||||
"absolute right-0 top-0 z-50 flex w-full flex-col items-start overflow-hidden bg-primary text-sm font-semibold text-primary-foreground transition-all duration-200",
|
||||
!isOpen && "hover:bg-primary-hover",
|
||||
variant === "large" ? "rounded-md" : "rounded-lg",
|
||||
buttonClassName,
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex w-full shrink-0 cursor-pointer items-center justify-between gap-2 pl-3 pr-3 transition-all",
|
||||
variant === "large" ? "h-10" : "h-8 text-[13px] font-medium",
|
||||
)}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
{trigger}
|
||||
<div className="flex h-4 w-4 items-center justify-center">
|
||||
<ForwardedIconComponent
|
||||
name="ChevronDown"
|
||||
className={cn(
|
||||
"absolute h-4 w-4 transition-all",
|
||||
isOpen && "opacity-0",
|
||||
)}
|
||||
/>
|
||||
<ForwardedIconComponent
|
||||
name="X"
|
||||
className={cn(
|
||||
"absolute h-4 w-4 opacity-0 transition-all",
|
||||
isOpen && "opacity-100",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"flex w-full flex-col gap-0 px-2 font-medium",
|
||||
itemsClassName,
|
||||
)}
|
||||
>
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="relative flex h-8 cursor-pointer select-none items-center gap-2 rounded-sm px-2 text-sm outline-none transition-colors hover:bg-primary-hover"
|
||||
onClick={() => {
|
||||
item.onClick?.();
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
{item.icon && (
|
||||
<ForwardedIconComponent
|
||||
name={item.icon}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
)}
|
||||
{item.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
MorphingMenu.displayName = "MorphingMenu";
|
||||
|
||||
export { MorphingMenu };
|
||||
export type { MorphingMenuProps };
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import * as ProgressPrimitive from "@radix-ui/react-progress";
|
||||
import * as React from "react";
|
||||
import { cn } from "../../utils/utils";
|
||||
|
||||
const Progress = React.forwardRef<
|
||||
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
||||
>(({ className, value, ...props }, ref) => (
|
||||
<ProgressPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ProgressPrimitive.Indicator
|
||||
className="h-full w-full flex-1 bg-primary transition-all"
|
||||
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||
/>
|
||||
</ProgressPrimitive.Root>
|
||||
));
|
||||
Progress.displayName = ProgressPrimitive.Root.displayName;
|
||||
|
||||
export { Progress };
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { cn } from "../../utils/utils";
|
||||
|
||||
export default function RenameLabel({
|
||||
value,
|
||||
setValue,
|
||||
className,
|
||||
rename,
|
||||
setRename,
|
||||
}) {
|
||||
const [internalState, setInternalState] = useState(false);
|
||||
const [componentValue, setComponentValue] = useState(value);
|
||||
const [isRename, setIsRename] = rename
|
||||
? [rename, setRename]
|
||||
: [internalState, setInternalState];
|
||||
|
||||
useEffect(() => {
|
||||
if (value) setComponentValue(value);
|
||||
}, [value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isRename) {
|
||||
setComponentValue(value);
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape") {
|
||||
setIsRename(false);
|
||||
setValue("");
|
||||
}
|
||||
});
|
||||
if (inputRef.current) {
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
resizeInput();
|
||||
return () => {
|
||||
if (isRename) document.removeEventListener("keydown", () => {});
|
||||
};
|
||||
}, [isRename]);
|
||||
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const resizeInput = () => {
|
||||
const input = inputRef.current;
|
||||
if (input) {
|
||||
const span = document.createElement("span");
|
||||
span.style.position = "absolute";
|
||||
span.style.visibility = "hidden";
|
||||
span.style.whiteSpace = "pre";
|
||||
span.style.font = window.getComputedStyle(input).font;
|
||||
span.textContent = input.value;
|
||||
|
||||
document.body.appendChild(span);
|
||||
const textWidth = span.getBoundingClientRect().width;
|
||||
document.body.removeChild(span);
|
||||
|
||||
input.style.width = `${textWidth + 16}px`;
|
||||
}
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
setIsRename(false);
|
||||
if (componentValue !== "") {
|
||||
setValue(componentValue);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (event) => {
|
||||
setComponentValue(event.target.value);
|
||||
};
|
||||
|
||||
const handleDoubleClick = () => {
|
||||
setIsRename(true);
|
||||
setComponentValue(value);
|
||||
};
|
||||
|
||||
const renderInput = () => (
|
||||
<input
|
||||
ref={inputRef}
|
||||
onInput={resizeInput}
|
||||
className={cn(
|
||||
"nopan nodelete nodrag noflow rounded-md bg-transparent px-2 outline-ring hover:outline focus:border-none focus:outline active:outline",
|
||||
className,
|
||||
)}
|
||||
onBlur={handleBlur}
|
||||
value={componentValue}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderSpan = () => (
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={cn("truncate px-2 text-left", className)}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return <div>{isRename ? renderInput() : renderSpan()}</div>;
|
||||
}
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { X } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "../../utils/utils";
|
||||
|
||||
const Sheet = SheetPrimitive.Root;
|
||||
|
||||
const SheetTrigger = SheetPrimitive.Trigger;
|
||||
|
||||
const SheetClose = SheetPrimitive.Close;
|
||||
|
||||
const SheetPortal = SheetPrimitive.Portal;
|
||||
|
||||
const SheetOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Overlay
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
));
|
||||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
||||
|
||||
const sheetVariants = cva(
|
||||
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||
{
|
||||
variants: {
|
||||
side: {
|
||||
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
||||
bottom:
|
||||
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
||||
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
||||
right:
|
||||
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
side: "right",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
interface SheetContentProps
|
||||
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||
VariantProps<typeof sheetVariants> {}
|
||||
|
||||
const SheetContent = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||
SheetContentProps
|
||||
>(({ side = "right", className, children, ...props }, ref) => (
|
||||
<SheetPortal>
|
||||
<SheetOverlay />
|
||||
<SheetPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(sheetVariants({ side }), className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
</SheetPrimitive.Content>
|
||||
</SheetPortal>
|
||||
));
|
||||
SheetContent.displayName = SheetPrimitive.Content.displayName;
|
||||
|
||||
const SheetHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-2 text-center sm:text-left",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
SheetHeader.displayName = "SheetHeader";
|
||||
|
||||
const SheetFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
SheetFooter.displayName = "SheetFooter";
|
||||
|
||||
const SheetTitle = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold text-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SheetTitle.displayName = SheetPrimitive.Title.displayName;
|
||||
|
||||
const SheetDescription = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SheetDescription.displayName = SheetPrimitive.Description.displayName;
|
||||
|
||||
export {
|
||||
Sheet,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetOverlay,
|
||||
SheetPortal,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
};
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { cn } from "@/utils/utils";
|
||||
import * as SliderPrimitive from "@radix-ui/react-slider";
|
||||
import * as React from "react";
|
||||
|
||||
const Slider = React.forwardRef<
|
||||
React.ElementRef<typeof SliderPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SliderPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full touch-none select-none items-center",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
|
||||
<SliderPrimitive.Range className="absolute h-full bg-primary" />
|
||||
</SliderPrimitive.Track>
|
||||
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
|
||||
</SliderPrimitive.Root>
|
||||
));
|
||||
Slider.displayName = SliderPrimitive.Root.displayName;
|
||||
|
||||
export { Slider };
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import * as TogglePrimitive from "@radix-ui/react-toggle";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "../../utils/utils";
|
||||
|
||||
const toggleVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-transparent",
|
||||
outline:
|
||||
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-3",
|
||||
sm: "h-9 px-2.5",
|
||||
lg: "h-11 px-5",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const Toggle = React.forwardRef<
|
||||
React.ElementRef<typeof TogglePrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
|
||||
VariantProps<typeof toggleVariants>
|
||||
>(({ className, variant, size, ...props }, ref) => (
|
||||
<TogglePrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(toggleVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
Toggle.displayName = TogglePrimitive.Root.displayName;
|
||||
|
||||
export { Toggle, toggleVariants };
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
export const validateWebhookData = (res) => {
|
||||
if (!res?.data?.vertex_builds) {
|
||||
return false;
|
||||
}
|
||||
return Object.keys(res?.data?.vertex_builds).some(
|
||||
(key) =>
|
||||
key.includes("Webhook") &&
|
||||
Array.isArray(res?.data?.vertex_builds[key]) &&
|
||||
res?.data?.vertex_builds[key]?.length > 0,
|
||||
);
|
||||
};
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from "./use-post-download-multiple-flows";
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
import buildQueryStringUrl from "@/controllers/utils/create-query-param-string";
|
||||
import useAlertStore from "@/stores/alertStore";
|
||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||
import { useTypesStore } from "@/stores/typesStore";
|
||||
import { useMutationFunctionType } from "@/types/api";
|
||||
import { FlowType, PaginatedFlowsType } from "@/types/flow";
|
||||
import {
|
||||
extractFieldsFromComponenents,
|
||||
processFlows,
|
||||
} from "@/utils/reactflowUtils";
|
||||
import { UseMutationOptions } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
import { api } from "../../api";
|
||||
import { getURL } from "../../helpers/constants";
|
||||
import { UseRequestProcessor } from "../../services/request-processor";
|
||||
|
||||
interface GetFlowsParams {
|
||||
components_only?: boolean;
|
||||
get_all?: boolean;
|
||||
header_flows?: boolean;
|
||||
folder_id?: string;
|
||||
remove_example_flows?: boolean;
|
||||
page?: number;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const addQueryParams = (url: string, params: GetFlowsParams): string => {
|
||||
return buildQueryStringUrl(url, params);
|
||||
};
|
||||
|
||||
export const useGetRefreshFlows: useMutationFunctionType<
|
||||
undefined,
|
||||
GetFlowsParams
|
||||
> = (options) => {
|
||||
const { mutate } = UseRequestProcessor();
|
||||
|
||||
const setFlows = useFlowsManagerStore((state) => state.setFlows);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
|
||||
const getRefreshFlowsFn = async (
|
||||
params: GetFlowsParams,
|
||||
): Promise<FlowType[] | PaginatedFlowsType> => {
|
||||
const url = addQueryParams(`${getURL("FLOWS")}/`, params);
|
||||
const { data } = await api.get<FlowType[]>(url);
|
||||
return data;
|
||||
};
|
||||
|
||||
const mutationFn = async (
|
||||
params?: GetFlowsParams,
|
||||
): Promise<FlowType[] | PaginatedFlowsType> => {
|
||||
try {
|
||||
await getRefreshFlowsFn(params!).then(async (dbDataFlows) => {
|
||||
const dbDataComponents = await getRefreshFlowsFn({
|
||||
components_only: true,
|
||||
get_all: true,
|
||||
});
|
||||
|
||||
if (dbDataComponents) {
|
||||
const { data } = processFlows(dbDataComponents as FlowType[]);
|
||||
useTypesStore.setState((state) => ({
|
||||
data: { ...state.data, ["saved_components"]: data },
|
||||
ComponentFields: extractFieldsFromComponenents({
|
||||
...state.data,
|
||||
["saved_components"]: data,
|
||||
}),
|
||||
}));
|
||||
}
|
||||
|
||||
if (dbDataFlows) {
|
||||
const flows = Array.isArray(dbDataFlows)
|
||||
? dbDataFlows
|
||||
: dbDataFlows.items;
|
||||
|
||||
setFlows(flows);
|
||||
return flows;
|
||||
}
|
||||
});
|
||||
|
||||
return [];
|
||||
} catch (e) {
|
||||
if (e instanceof AxiosError && e.status !== 403) {
|
||||
setErrorData({
|
||||
title: "Could not load flows from database",
|
||||
});
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
const mutation = mutate(["useGetRefreshFlows"], mutationFn, {
|
||||
...(options as UseMutationOptions<any, any, void, unknown>),
|
||||
});
|
||||
|
||||
return mutation;
|
||||
};
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
import { useMutationFunctionType } from "@/types/api";
|
||||
import { UseMutationResult } from "@tanstack/react-query";
|
||||
import { api } from "../../api";
|
||||
import { getURL } from "../../helpers/constants";
|
||||
import { UseRequestProcessor } from "../../services/request-processor";
|
||||
|
||||
interface IPostDownloadMultipleFlows {
|
||||
flow_ids: string[];
|
||||
}
|
||||
|
||||
export const usePostDownloadMultipleFlows: useMutationFunctionType<
|
||||
undefined,
|
||||
IPostDownloadMultipleFlows
|
||||
> = (options?) => {
|
||||
const { mutate } = UseRequestProcessor();
|
||||
|
||||
const postDownloadMultipleFlowsFn = async (
|
||||
payload: IPostDownloadMultipleFlows,
|
||||
): Promise<any> => {
|
||||
const response = await api.post<any>(
|
||||
`${getURL("FLOWS")}/download/`,
|
||||
payload.flow_ids,
|
||||
{ responseType: "blob" },
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const mutation: UseMutationResult<
|
||||
IPostDownloadMultipleFlows,
|
||||
any,
|
||||
IPostDownloadMultipleFlows
|
||||
> = mutate(
|
||||
["usePostDownloadMultipleFlows"],
|
||||
postDownloadMultipleFlowsFn,
|
||||
options,
|
||||
);
|
||||
|
||||
return mutation;
|
||||
};
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
import { useMessagesStore } from "@/stores/messagesStore";
|
||||
import { UseMutationResult } from "@tanstack/react-query";
|
||||
import { ColDef, ColGroupDef } from "ag-grid-community";
|
||||
import { extractColumnsFromRows } from "../../../../utils/utils";
|
||||
import { api } from "../../api";
|
||||
import { getURL } from "../../helpers/constants";
|
||||
import { UseRequestProcessor } from "../../services/request-processor";
|
||||
|
||||
interface MessagesQueryParams {
|
||||
id?: string;
|
||||
mode: "intersection" | "union";
|
||||
excludedFields?: string[];
|
||||
params?: object;
|
||||
}
|
||||
|
||||
interface MessagesResponse {
|
||||
rows: Array<object>;
|
||||
columns: Array<ColDef | ColGroupDef>;
|
||||
}
|
||||
|
||||
export const useGetMessagesMutation = (
|
||||
options?: any,
|
||||
): UseMutationResult<
|
||||
MessagesResponse,
|
||||
unknown,
|
||||
MessagesQueryParams,
|
||||
unknown
|
||||
> => {
|
||||
const { mutate } = UseRequestProcessor();
|
||||
|
||||
const getMessagesFn = async (
|
||||
payload: MessagesQueryParams,
|
||||
): Promise<MessagesResponse> => {
|
||||
const { id, mode, excludedFields, params } = payload;
|
||||
const config = {};
|
||||
if (id) {
|
||||
config["params"] = { flow_id: id };
|
||||
}
|
||||
if (params) {
|
||||
config["params"] = { ...config["params"], ...params };
|
||||
}
|
||||
|
||||
const data = await api.get<any>(`${getURL("MESSAGES")}`, config);
|
||||
const columns = extractColumnsFromRows(data.data, mode, excludedFields);
|
||||
useMessagesStore.getState().setMessages(data.data);
|
||||
|
||||
return { rows: data.data, columns };
|
||||
};
|
||||
|
||||
// Cast the mutation to the correct type
|
||||
const mutation = mutate(
|
||||
["useGetMessagesMutation"],
|
||||
getMessagesFn,
|
||||
options,
|
||||
) as UseMutationResult<
|
||||
MessagesResponse,
|
||||
unknown,
|
||||
MessagesQueryParams,
|
||||
unknown
|
||||
>;
|
||||
|
||||
return mutation;
|
||||
};
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from "./use-get-starter-projects";
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { useQueryFunctionType } from "@/types/api";
|
||||
import { api } from "../../api";
|
||||
import { getURL } from "../../helpers/constants";
|
||||
import { UseRequestProcessor } from "../../services/request-processor";
|
||||
|
||||
export interface IStarterProjectsDataArray {
|
||||
name: string;
|
||||
last_used_at: string | null;
|
||||
total_uses: number;
|
||||
is_active: boolean;
|
||||
id: string;
|
||||
api_key: string;
|
||||
user_id: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface IApiQueryResponse {
|
||||
total_count: number;
|
||||
user_id: string;
|
||||
api_keys: Array<IStarterProjectsDataArray>;
|
||||
}
|
||||
|
||||
export const useGetStarterProjectsQuery: useQueryFunctionType<
|
||||
undefined,
|
||||
IApiQueryResponse
|
||||
> = (options) => {
|
||||
const { query } = UseRequestProcessor();
|
||||
|
||||
const getStarterProjectsFn = async () => {
|
||||
return await api.get<IApiQueryResponse>(`${getURL("STARTER_PROJECTS")}/`);
|
||||
};
|
||||
|
||||
const responseFn = async () => {
|
||||
const { data } = await getStarterProjectsFn();
|
||||
return data;
|
||||
};
|
||||
|
||||
const queryResult = query(["useGetStarterProjectsQuery"], responseFn, {
|
||||
...options,
|
||||
});
|
||||
|
||||
return queryResult;
|
||||
};
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
export const getClassNamesFilePreview = (inputFocus) => {
|
||||
return `flex w-full items-center gap-4 rounded-t-lg bg-background px-14 py-5 overflow-auto custom-scroll border ${
|
||||
inputFocus ? "border-ring" : ""
|
||||
}`;
|
||||
};
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
import ShortUniqueId from "short-unique-id";
|
||||
import {
|
||||
ALLOWED_IMAGE_INPUT_EXTENSIONS,
|
||||
FS_ERROR_TEXT,
|
||||
SN_ERROR_TEXT,
|
||||
} from "../../../../../../constants/constants";
|
||||
import useAlertStore from "../../../../../../stores/alertStore";
|
||||
import handleFileUpload from "../helpers/handle-file-upload";
|
||||
|
||||
export const useHandleFileChange = (setFiles, currentFlowId) => {
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const handleFileChange = async (
|
||||
event: React.ChangeEvent<HTMLInputElement>,
|
||||
) => {
|
||||
const fileInput = event.target;
|
||||
const file = fileInput.files?.[0];
|
||||
if (file) {
|
||||
const fileExtension = file.name.split(".").pop()?.toLowerCase();
|
||||
|
||||
if (
|
||||
!fileExtension ||
|
||||
!ALLOWED_IMAGE_INPUT_EXTENSIONS.includes(fileExtension)
|
||||
) {
|
||||
setErrorData({
|
||||
title: "Error uploading file",
|
||||
list: [FS_ERROR_TEXT, SN_ERROR_TEXT],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const uid = new ShortUniqueId();
|
||||
const id = uid.randomUUID(10);
|
||||
|
||||
const type = file.type.split("/")[0];
|
||||
const blob = file;
|
||||
|
||||
setFiles((prevFiles) => [
|
||||
...prevFiles,
|
||||
{ file: blob, loading: true, error: false, id, type },
|
||||
]);
|
||||
|
||||
handleFileUpload(blob, currentFlowId, setFiles, id);
|
||||
}
|
||||
|
||||
// Clear the file input value to ensure the change event is triggered even for the same file
|
||||
fileInput.value = "";
|
||||
};
|
||||
|
||||
return {
|
||||
handleFileChange,
|
||||
};
|
||||
};
|
||||
|
||||
export default useHandleFileChange;
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
import useFlowStore from "@/stores/flowStore";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { useEffect } from "react";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import {
|
||||
ALLOWED_IMAGE_INPUT_EXTENSIONS,
|
||||
FS_ERROR_TEXT,
|
||||
SN_ERROR_TEXT,
|
||||
} from "../../../../../../constants/constants";
|
||||
import useAlertStore from "../../../../../../stores/alertStore";
|
||||
import { UploadFileTypeAPI } from "../../../../../../types/api";
|
||||
|
||||
const useUpload = (
|
||||
uploadFile: (
|
||||
file: File,
|
||||
id: string,
|
||||
) => Promise<AxiosResponse<UploadFileTypeAPI>>,
|
||||
currentFlowId: string,
|
||||
setFiles: any,
|
||||
) => {
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const isBuilding = useFlowStore((state) => state.isBuilding);
|
||||
useEffect(() => {
|
||||
const handlePaste = (event: ClipboardEvent): void => {
|
||||
if (isBuilding) {
|
||||
return;
|
||||
}
|
||||
const items = event.clipboardData?.items;
|
||||
if (items) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const type = items[0].type.split("/")[0];
|
||||
const uid = new ShortUniqueId();
|
||||
const blob = items[i].getAsFile();
|
||||
if (blob) {
|
||||
const fileExtension = blob.name.split(".").pop()?.toLowerCase();
|
||||
|
||||
if (
|
||||
!fileExtension ||
|
||||
!ALLOWED_IMAGE_INPUT_EXTENSIONS.includes(fileExtension)
|
||||
) {
|
||||
setErrorData({
|
||||
title: "Error uploading file",
|
||||
list: [FS_ERROR_TEXT, SN_ERROR_TEXT],
|
||||
});
|
||||
return;
|
||||
}
|
||||
const id = uid.randomUUID(3);
|
||||
setFiles((prevFiles) => [
|
||||
...prevFiles,
|
||||
{ file: blob, loading: true, error: false, id, type },
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("paste", handlePaste);
|
||||
return () => {
|
||||
document.removeEventListener("paste", handlePaste);
|
||||
};
|
||||
}, [uploadFile, currentFlowId, isBuilding]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default useUpload;
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
import { IconCheck, IconClipboard, IconDownload } from "@tabler/icons-react";
|
||||
import { useState } from "react";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { tomorrow } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||
import { programmingLanguages } from "../../../../../../constants/constants";
|
||||
import { Props } from "../../../../../../types/components";
|
||||
|
||||
export function CodeBlock({ language, value }: Props): JSX.Element {
|
||||
const [isCopied, setIsCopied] = useState<Boolean>(false);
|
||||
|
||||
const copyToClipboard = (): void => {
|
||||
if (!navigator.clipboard || !navigator.clipboard.writeText) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(value).then(() => {
|
||||
setIsCopied(true);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsCopied(false);
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
const downloadAsFile = (): void => {
|
||||
const fileExtension = programmingLanguages[language] || ".file";
|
||||
const suggestedFileName = `${"generated-code"}${fileExtension}`;
|
||||
const fileName = window.prompt("enter file name", suggestedFileName);
|
||||
|
||||
if (!fileName) {
|
||||
// user pressed cancel on prompt
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = new Blob([value], { type: "text/plain" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.download = fileName;
|
||||
link.href = url;
|
||||
link.style.display = "none";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
return (
|
||||
<div className="codeblock font-sans text-[16px]">
|
||||
<div className="code-block-modal">
|
||||
<span className="code-block-modal-span">{language}</span>
|
||||
|
||||
<div className="flex items-center">
|
||||
<button className="code-block-modal-button" onClick={copyToClipboard}>
|
||||
{isCopied ? <IconCheck size={18} /> : <IconClipboard size={18} />}
|
||||
{isCopied ? "Copied!" : "Copy Code"}
|
||||
</button>
|
||||
<button className="code-block-modal-button" onClick={downloadAsFile}>
|
||||
<IconDownload size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SyntaxHighlighter
|
||||
className="overflow-auto"
|
||||
language={language}
|
||||
style={tomorrow}
|
||||
customStyle={{ margin: 0 }}
|
||||
>
|
||||
{value}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
CodeBlock.displayName = "CodeBlock";
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
|
||||
import { track } from "@/customization/utils/analytics";
|
||||
import useAddFlow from "@/hooks/flows/use-add-flow";
|
||||
import { useParams } from "react-router-dom";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardTitle,
|
||||
} from "../../../../components/ui/card";
|
||||
|
||||
export default function NewFlowCardComponent() {
|
||||
const addFlow = useAddFlow();
|
||||
const navigate = useCustomNavigate();
|
||||
const { folderId } = useParams();
|
||||
|
||||
const handleClick = () => {
|
||||
addFlow({ new_blank: true }).then((id) => {
|
||||
navigate(`/flow/${id}${folderId ? `/folder/${folderId}` : ""}`);
|
||||
});
|
||||
track("New Flow Created", { template: "Blank Flow" });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
onClick={handleClick}
|
||||
className="h-64 w-80 cursor-pointer bg-background pt-4"
|
||||
data-testid="blank-flow"
|
||||
>
|
||||
<CardContent className="h-full w-full">
|
||||
<div className="flex h-full w-full flex-col items-center justify-center rounded-md bg-muted align-middle bg-dotted-spacing-6 bg-dotted-muted-foreground bg-dotted-radius-px"></div>
|
||||
</CardContent>
|
||||
<CardDescription className="px-6 pb-4">
|
||||
<CardTitle className="text-lg text-primary">Blank Flow</CardTitle>
|
||||
</CardDescription>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import { useNavigate } from "react-router-dom";
|
||||
import { track } from "../../../../customization/utils/analytics";
|
||||
import useAddFlow from "../../../../hooks/flows/use-add-flow";
|
||||
import { FlowType } from "../../../../types/flow";
|
||||
import { updateIds } from "../../../../utils/reactflowUtils";
|
||||
|
||||
export function useFlowCardClick() {
|
||||
const navigate = useNavigate();
|
||||
const addFlow = useAddFlow();
|
||||
|
||||
const handleFlowCardClick = async (flow: FlowType, folderIdUrl: string) => {
|
||||
try {
|
||||
updateIds(flow.data!);
|
||||
const id = await addFlow({ flow });
|
||||
navigate(`/flow/${id}/folder/${folderIdUrl}`);
|
||||
track("New Flow Created", { template: `${flow.name} Template` });
|
||||
} catch (error) {
|
||||
console.error("Error handling flow card click:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return handleFlowCardClick;
|
||||
}
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
/// <reference types="vite-plugin-svgr/client" />
|
||||
import { useParams } from "react-router-dom";
|
||||
import BlogPost from "../../../../assets/undraw_blog_post_re_fy5x.svg?react";
|
||||
import ChatBot from "../../../../assets/undraw_chat_bot_re_e2gj.svg?react";
|
||||
import PromptChaining from "../../../../assets/undraw_cloud_docs_re_xjht.svg?react";
|
||||
import HierarchicalTasks from "../../../../assets/undraw_educator_re_ju47.svg?react";
|
||||
import ComplexAgent from "../../../../assets/undraw_firmware_re_fgdy.svg?react";
|
||||
import SequentialTasks from "../../../../assets/undraw_project_completed_re_jr7u.svg?react";
|
||||
import APIRequest from "../../../../assets/undraw_real_time_analytics_re_yliv.svg?react";
|
||||
import BasicPrompt from "../../../../assets/undraw_short_bio_re_fmx0.svg?react";
|
||||
import TransferFiles from "../../../../assets/undraw_transfer_files_re_a2a9.svg?react";
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardTitle,
|
||||
} from "../../../../components/ui/card";
|
||||
import { useFolderStore } from "../../../../stores/foldersStore";
|
||||
import { UndrawCardComponentProps } from "../../../../types/components";
|
||||
import { useFlowCardClick } from "../hooks/use-redirect-flow-card-click";
|
||||
|
||||
export default function UndrawCardComponent({
|
||||
flow,
|
||||
}: UndrawCardComponentProps): JSX.Element {
|
||||
const { folderId } = useParams();
|
||||
const myCollectionId = useFolderStore((state) => state.myCollectionId);
|
||||
const folderIdUrl = folderId ?? myCollectionId;
|
||||
|
||||
const handleFlowCardClick = useFlowCardClick();
|
||||
|
||||
function selectImage() {
|
||||
switch (flow.name) {
|
||||
case "Blog Writer":
|
||||
return (
|
||||
<BlogPost
|
||||
style={{
|
||||
width: "65%",
|
||||
height: "65%",
|
||||
}}
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
/>
|
||||
);
|
||||
case "Basic Prompting (Hello, World)":
|
||||
return (
|
||||
<BasicPrompt
|
||||
style={{
|
||||
width: "65%",
|
||||
height: "65%",
|
||||
}}
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
/>
|
||||
);
|
||||
case "Memory Chatbot":
|
||||
return (
|
||||
<ChatBot
|
||||
style={{
|
||||
width: "70%",
|
||||
height: "70%",
|
||||
}}
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
/>
|
||||
);
|
||||
case "API requests":
|
||||
return (
|
||||
<APIRequest
|
||||
style={{
|
||||
width: "70%",
|
||||
height: "70%",
|
||||
}}
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
/>
|
||||
);
|
||||
case "Document QA":
|
||||
return (
|
||||
<TransferFiles
|
||||
style={{
|
||||
width: "80%",
|
||||
height: "80%",
|
||||
}}
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
/>
|
||||
);
|
||||
case "Vector Store RAG":
|
||||
return (
|
||||
<PromptChaining
|
||||
style={{
|
||||
width: "80%",
|
||||
height: "80%",
|
||||
}}
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
/>
|
||||
);
|
||||
case "Simple Agent":
|
||||
return (
|
||||
<SequentialTasks
|
||||
style={{
|
||||
width: "80%",
|
||||
height: "80%",
|
||||
}}
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
/>
|
||||
);
|
||||
case "Travel Planning Agents":
|
||||
return (
|
||||
<HierarchicalTasks
|
||||
style={{
|
||||
width: "80%",
|
||||
height: "80%",
|
||||
}}
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
/>
|
||||
);
|
||||
case "Dynamic Agent":
|
||||
return (
|
||||
<ComplexAgent
|
||||
style={{
|
||||
width: "80%",
|
||||
height: "80%",
|
||||
}}
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<TransferFiles
|
||||
style={{
|
||||
width: "80%",
|
||||
height: "80%",
|
||||
}}
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
onClick={() => handleFlowCardClick(flow, folderIdUrl!)}
|
||||
className="h-64 w-80 cursor-pointer bg-background pt-4"
|
||||
>
|
||||
<CardContent className="h-full w-full">
|
||||
<div className="flex h-full w-full flex-col items-center justify-center rounded-md bg-muted p-1 align-middle">
|
||||
{selectImage()}
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardDescription className="px-6 pb-4">
|
||||
<CardTitle className="text-lg text-primary">{flow.name}</CardTitle>
|
||||
</CardDescription>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
import { Disclosure } from "@headlessui/react";
|
||||
import IconComponent from "../../../../components/common/genericIconComponent";
|
||||
import { DisclosureComponentType } from "../../../../types/components";
|
||||
|
||||
export default function DisclosureComponent({
|
||||
button: { title, icon, buttons = [] },
|
||||
isChild = true,
|
||||
children,
|
||||
defaultOpen,
|
||||
}: DisclosureComponentType): JSX.Element {
|
||||
return (
|
||||
<Disclosure as="div" defaultOpen={defaultOpen} key={title}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div>
|
||||
<Disclosure.Button
|
||||
className={
|
||||
isChild
|
||||
? "components-disclosure-arrangement-child"
|
||||
: "components-disclosure-arrangement"
|
||||
}
|
||||
data-testid={`disclosure-${title.toLocaleLowerCase()}`}
|
||||
>
|
||||
<div className={"flex gap-4" + (isChild ? " pl-2" : "")}>
|
||||
{/* BUG ON THIS ICON */}
|
||||
<IconComponent name={icon} />
|
||||
<span className="components-disclosure-title">{title}</span>
|
||||
</div>
|
||||
<div className="components-disclosure-div">
|
||||
{buttons.map((btn, index) => (
|
||||
<button key={index} onClick={btn.onClick}>
|
||||
<IconComponent name={btn.icon} />
|
||||
</button>
|
||||
))}
|
||||
<div>
|
||||
<IconComponent
|
||||
name="ChevronRight"
|
||||
className={`${
|
||||
open || defaultOpen ? "rotate-90 transform" : ""
|
||||
} h-4 w-4 text-foreground`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Disclosure.Button>
|
||||
</div>
|
||||
<Disclosure.Panel as="div">{children}</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
import { Disclosure } from "@headlessui/react";
|
||||
import IconComponent from "../../../../components/common/genericIconComponent";
|
||||
import { DisclosureComponentType } from "../../../../types/components";
|
||||
|
||||
export default function ParentDisclosureComponent({
|
||||
button: { title, icon, buttons = [], beta },
|
||||
children,
|
||||
defaultOpen,
|
||||
testId,
|
||||
}: DisclosureComponentType): JSX.Element {
|
||||
return (
|
||||
<Disclosure as="div" defaultOpen={defaultOpen} key={title}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div>
|
||||
<Disclosure.Button
|
||||
className="parent-disclosure-arrangement"
|
||||
data-testid={testId}
|
||||
>
|
||||
<div className="flex items-baseline gap-1 align-baseline">
|
||||
<span className="text-sm font-medium">{title}</span>
|
||||
{beta && (
|
||||
<div className="h-fit rounded-full bg-beta-background px-2 py-1 text-xs/3 font-semibold text-beta-foreground-soft">
|
||||
Beta
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="components-disclosure-div">
|
||||
{buttons.map((btn, index) => (
|
||||
<button key={index} onClick={btn.onClick}>
|
||||
<IconComponent name={btn.icon} />
|
||||
</button>
|
||||
))}
|
||||
<div>
|
||||
<IconComponent
|
||||
skipFallback
|
||||
name={open ? "chevron-down" : "chevron-right"}
|
||||
className={`${
|
||||
open || defaultOpen ? "" : ""
|
||||
} h-4 w-4 text-foreground`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Disclosure.Button>
|
||||
</div>
|
||||
<Disclosure.Panel as="div">{children}</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
import ShadTooltip from "@/components/common/shadTooltipComponent";
|
||||
import { getNodeIcon, nodeColors } from "@/utils/styleUtils";
|
||||
import { removeCountFromString } from "@/utils/utils";
|
||||
import DisclosureComponent from "../../DisclosureComponent";
|
||||
import SidebarDraggableComponent from "../sideBarDraggableComponent";
|
||||
import sensitiveSort from "../utils/sensitive-sort";
|
||||
|
||||
export function SidebarCategoryComponent({
|
||||
search,
|
||||
getFilterEdge,
|
||||
category,
|
||||
name,
|
||||
onDragStart,
|
||||
}) {
|
||||
return (
|
||||
<DisclosureComponent
|
||||
isChild={false}
|
||||
defaultOpen={
|
||||
getFilterEdge.length !== 0 || search.length !== 0 ? true : false
|
||||
}
|
||||
button={{
|
||||
title: name,
|
||||
icon: name,
|
||||
}}
|
||||
>
|
||||
<div className="side-bar-components-gap">
|
||||
{Object.keys(category)
|
||||
.sort((a, b) =>
|
||||
sensitiveSort(category[a].display_name, category[b].display_name),
|
||||
)
|
||||
.map((SBItemName: string, idx) => (
|
||||
<ShadTooltip
|
||||
content={category[SBItemName].display_name}
|
||||
side="right"
|
||||
key={idx}
|
||||
>
|
||||
<SidebarDraggableComponent
|
||||
sectionName={name as string}
|
||||
apiClass={category[SBItemName]}
|
||||
key={idx}
|
||||
onDragStart={(event) =>
|
||||
onDragStart(event, {
|
||||
//split type to remove type in nodes saved with same name removing it's
|
||||
type: removeCountFromString(SBItemName),
|
||||
node: category[SBItemName],
|
||||
})
|
||||
}
|
||||
color={nodeColors[name]}
|
||||
itemName={SBItemName}
|
||||
//convert error to boolean
|
||||
error={!!category[SBItemName].error}
|
||||
display_name={category[SBItemName].display_name}
|
||||
official={
|
||||
category[SBItemName].official === false ? false : true
|
||||
}
|
||||
/>
|
||||
</ShadTooltip>
|
||||
))}
|
||||
</div>
|
||||
</DisclosureComponent>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,342 +0,0 @@
|
|||
import { ENABLE_INTEGRATIONS } from "@/customization/feature-flags";
|
||||
import { useStoreStore } from "@/stores/storeStore";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
import IconComponent from "../../../../components/common/genericIconComponent";
|
||||
import { Input } from "../../../../components/ui/input";
|
||||
import { Separator } from "../../../../components/ui/separator";
|
||||
import {
|
||||
BUNDLES_SIDEBAR_FOLDER_NAMES,
|
||||
PRIORITY_SIDEBAR_ORDER,
|
||||
} from "../../../../constants/constants";
|
||||
import useAlertStore from "../../../../stores/alertStore";
|
||||
import useFlowStore from "../../../../stores/flowStore";
|
||||
import { useTypesStore } from "../../../../stores/typesStore";
|
||||
import { APIClassType, APIObjectType } from "../../../../types/api";
|
||||
import ParentDisclosureComponent from "../ParentDisclosureComponent";
|
||||
import { SidebarCategoryComponent } from "./SidebarCategoryComponent";
|
||||
|
||||
import { useUtilityStore } from "@/stores/utilityStore";
|
||||
import { SidebarFilterComponent } from "./sidebarFilterComponent";
|
||||
import { sortKeys } from "./utils";
|
||||
|
||||
export default function ExtraSidebar(): JSX.Element {
|
||||
const data = useTypesStore((state) => state.data);
|
||||
const templates = useTypesStore((state) => state.templates);
|
||||
const getFilterEdge = useFlowStore((state) => state.getFilterEdge);
|
||||
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
|
||||
const hasStore = useStoreStore((state) => state.hasStore);
|
||||
const filterType = useFlowStore((state) => state.filterType);
|
||||
|
||||
const featureFlags = useUtilityStore((state) => state.featureFlags);
|
||||
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const [dataFilter, setFilterData] = useState(data);
|
||||
const [search, setSearch] = useState("");
|
||||
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.width = "215px";
|
||||
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("genericNode", JSON.stringify(data));
|
||||
}
|
||||
function normalizeString(str: string): string {
|
||||
return str.toLowerCase().replace(/_/g, " ").replace(/\s+/g, "");
|
||||
}
|
||||
|
||||
function searchInMetadata(metadata: any, searchTerm: string): boolean {
|
||||
if (!metadata || typeof metadata !== "object") return false;
|
||||
|
||||
return Object.entries(metadata).some(([key, value]) => {
|
||||
if (typeof value === "string") {
|
||||
return (
|
||||
normalizeString(key).includes(searchTerm) ||
|
||||
normalizeString(value).includes(searchTerm)
|
||||
);
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
return searchInMetadata(value, searchTerm);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function handleSearchInput(e: string) {
|
||||
if (e === "") {
|
||||
setFilterData(data);
|
||||
return;
|
||||
}
|
||||
|
||||
const searchTerm = normalizeString(e);
|
||||
|
||||
setFilterData((_) => {
|
||||
let ret: APIObjectType = {};
|
||||
Object.keys(data).forEach((d: keyof APIObjectType) => {
|
||||
ret[d] = {};
|
||||
let keys = Object.keys(data[d]).filter((nd) => {
|
||||
const item = data[d][nd];
|
||||
return (
|
||||
normalizeString(nd).includes(searchTerm) ||
|
||||
normalizeString(item.display_name).includes(searchTerm) ||
|
||||
normalizeString(d.toString()).includes(searchTerm) ||
|
||||
(item.metadata && searchInMetadata(item.metadata, searchTerm))
|
||||
);
|
||||
});
|
||||
keys.forEach((element) => {
|
||||
ret[d][element] = data[d][element];
|
||||
});
|
||||
});
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
|
||||
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() {
|
||||
// check if search is search to reset fitler on click input
|
||||
if ((!search && search === "") || search === "search") {
|
||||
setFilterData(data);
|
||||
setFilterEdge([]);
|
||||
setSearch("");
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (getFilterEdge.length !== 0) {
|
||||
setSearch("");
|
||||
}
|
||||
|
||||
if (getFilterEdge.length === 0 && search === "") {
|
||||
setSearch("");
|
||||
setFilterData(data);
|
||||
}
|
||||
}, [getFilterEdge, data]);
|
||||
|
||||
useEffect(() => {
|
||||
handleSearchInput(search);
|
||||
}, [data]);
|
||||
|
||||
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("");
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
}, [getFilterEdge, data]);
|
||||
|
||||
return (
|
||||
<div className="side-bar-arrangement">
|
||||
<div className="side-bar-search-div-placement">
|
||||
<Input
|
||||
onFocusCapture={() => handleBlur()}
|
||||
value={search}
|
||||
type="text"
|
||||
name="search"
|
||||
id="search"
|
||||
placeholder="Search"
|
||||
className="nopan nodelete nodrag noflow input-search"
|
||||
onChange={(event) => {
|
||||
handleSearchInput(event.target.value);
|
||||
// Set search input state
|
||||
setSearch(event.target.value);
|
||||
}}
|
||||
readOnly
|
||||
onClick={() =>
|
||||
document?.getElementById("search")?.removeAttribute("readonly")
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="search-icon"
|
||||
onClick={() => {
|
||||
if (search) {
|
||||
setFilterData(data);
|
||||
setSearch("");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<IconComponent
|
||||
name={search ? "X" : "Search"}
|
||||
className={`h-5 w-5 stroke-[1.5] text-primary ${
|
||||
search ? "cursor-pointer" : "cursor-default"
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
|
||||
<div className="side-bar-components-div-arrangement">
|
||||
<div className="parent-disclosure-arrangement">
|
||||
<div className="flex w-full flex-col items-start justify-between gap-2.5">
|
||||
<span className="text-sm font-medium">Components</span>
|
||||
{filterType && (
|
||||
<SidebarFilterComponent
|
||||
isInput={!!filterType.source}
|
||||
type={filterType.type}
|
||||
resetFilters={() => {
|
||||
setFilterEdge([]);
|
||||
setFilterData(data);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
{Object.keys(dataFilter)
|
||||
.sort(sortKeys)
|
||||
.filter((x) => PRIORITY_SIDEBAR_ORDER.includes(x))
|
||||
.map((SBSectionName: keyof APIObjectType, index) =>
|
||||
Object.keys(dataFilter[SBSectionName]).length > 0 ? (
|
||||
<SidebarCategoryComponent
|
||||
key={`DisclosureComponent${index + search + JSON.stringify(getFilterEdge)}`}
|
||||
search={search}
|
||||
getFilterEdge={getFilterEdge}
|
||||
category={dataFilter[SBSectionName]}
|
||||
name={SBSectionName}
|
||||
onDragStart={onDragStart}
|
||||
/>
|
||||
) : (
|
||||
<div key={index}></div>
|
||||
),
|
||||
)}
|
||||
{(ENABLE_INTEGRATIONS || featureFlags?.mvp_components) && (
|
||||
<ParentDisclosureComponent
|
||||
defaultOpen={true}
|
||||
key={`${search.length !== 0}-${getFilterEdge.length !== 0}-Bundle`}
|
||||
button={{
|
||||
title: "Integrations",
|
||||
icon: "unknown",
|
||||
}}
|
||||
testId="bundle-extended-disclosure"
|
||||
>
|
||||
{Object.keys(dataFilter)
|
||||
.sort(sortKeys)
|
||||
.filter((x) => BUNDLES_SIDEBAR_FOLDER_NAMES.includes(x))
|
||||
.map((SBSectionName: keyof APIObjectType, index) =>
|
||||
Object.keys(dataFilter[SBSectionName]).length > 0 ? (
|
||||
<SidebarCategoryComponent
|
||||
key={`DisclosureComponent${index + search + JSON.stringify(getFilterEdge)}`}
|
||||
search={search}
|
||||
getFilterEdge={getFilterEdge}
|
||||
category={dataFilter[SBSectionName]}
|
||||
name={SBSectionName}
|
||||
onDragStart={onDragStart}
|
||||
/>
|
||||
) : (
|
||||
<div key={index}></div>
|
||||
),
|
||||
)}
|
||||
</ParentDisclosureComponent>
|
||||
)}
|
||||
<ParentDisclosureComponent
|
||||
defaultOpen={search.length !== 0 || getFilterEdge.length !== 0}
|
||||
key={`${search.length !== 0}-${getFilterEdge.length !== 0}-Advanced`}
|
||||
button={{
|
||||
title: "Experimental",
|
||||
icon: "unknown",
|
||||
beta: true,
|
||||
}}
|
||||
testId="extended-disclosure"
|
||||
>
|
||||
{Object.keys(dataFilter)
|
||||
.sort(sortKeys)
|
||||
.filter(
|
||||
(x) =>
|
||||
!PRIORITY_SIDEBAR_ORDER.includes(x) &&
|
||||
!BUNDLES_SIDEBAR_FOLDER_NAMES.includes(x),
|
||||
)
|
||||
.map((SBSectionName: keyof APIObjectType, index) =>
|
||||
Object.keys(dataFilter[SBSectionName]).length > 0 ? (
|
||||
<SidebarCategoryComponent
|
||||
key={`DisclosureComponent${index + search + JSON.stringify(getFilterEdge)}`}
|
||||
search={search}
|
||||
getFilterEdge={getFilterEdge}
|
||||
category={dataFilter[SBSectionName]}
|
||||
name={SBSectionName}
|
||||
onDragStart={onDragStart}
|
||||
/>
|
||||
) : (
|
||||
<div key={index}></div>
|
||||
),
|
||||
)}
|
||||
{hasStore && (
|
||||
<a
|
||||
target={"_blank"}
|
||||
href="https://langflow.store"
|
||||
className="components-disclosure-arrangement"
|
||||
draggable="false"
|
||||
>
|
||||
<div className="flex gap-4">
|
||||
{/* BUG ON THIS ICON */}
|
||||
<IconComponent
|
||||
name="Sparkles"
|
||||
strokeWidth={1.5}
|
||||
className="w-[22px] text-primary"
|
||||
/>
|
||||
|
||||
<span className="components-disclosure-title">
|
||||
Discover More
|
||||
</span>
|
||||
</div>
|
||||
<div className="components-disclosure-div">
|
||||
<div>
|
||||
<IconComponent
|
||||
name="Link"
|
||||
className="h-4 w-4 text-foreground"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
)}
|
||||
</ParentDisclosureComponent>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
import useDeleteFlow from "@/hooks/flows/use-delete-flow";
|
||||
import { DragEventHandler, forwardRef, useRef, useState } from "react";
|
||||
import IconComponent from "../../../../../components/common/genericIconComponent";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
} from "../../../../../components/ui/select-custom";
|
||||
import { useDarkStore } from "../../../../../stores/darkStore";
|
||||
import useFlowsManagerStore from "../../../../../stores/flowsManagerStore";
|
||||
import { APIClassType } from "../../../../../types/api";
|
||||
import {
|
||||
createFlowComponent,
|
||||
downloadNode,
|
||||
getNodeId,
|
||||
} from "../../../../../utils/reactflowUtils";
|
||||
import { removeCountFromString } from "../../../../../utils/utils";
|
||||
|
||||
export const SidebarDraggableComponent = forwardRef(
|
||||
(
|
||||
{
|
||||
sectionName,
|
||||
display_name,
|
||||
itemName,
|
||||
error,
|
||||
color,
|
||||
onDragStart,
|
||||
apiClass,
|
||||
official,
|
||||
}: {
|
||||
sectionName: string;
|
||||
apiClass: APIClassType;
|
||||
display_name: string;
|
||||
itemName: string;
|
||||
error: boolean;
|
||||
color: string;
|
||||
onDragStart: DragEventHandler<HTMLDivElement>;
|
||||
official: boolean;
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { deleteFlow } = useDeleteFlow();
|
||||
const flows = useFlowsManagerStore((state) => state.flows);
|
||||
|
||||
const version = useDarkStore((state) => state.version);
|
||||
const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 });
|
||||
const popoverRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handlePointerDown = (e) => {
|
||||
if (!open) {
|
||||
const rect = popoverRef.current?.getBoundingClientRect() ?? {
|
||||
left: 0,
|
||||
top: 0,
|
||||
};
|
||||
setCursorPos({ x: e.clientX - rect.left, y: e.clientY - rect.top });
|
||||
}
|
||||
};
|
||||
|
||||
function handleSelectChange(value: string) {
|
||||
switch (value) {
|
||||
case "share":
|
||||
break;
|
||||
case "download":
|
||||
const type = removeCountFromString(itemName);
|
||||
downloadNode(
|
||||
createFlowComponent(
|
||||
{ id: getNodeId(type), type, node: apiClass },
|
||||
version,
|
||||
),
|
||||
);
|
||||
break;
|
||||
case "delete":
|
||||
const flowId = flows?.find((f) => f.name === display_name);
|
||||
if (flowId) deleteFlow({ id: flowId.id });
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Select
|
||||
onValueChange={handleSelectChange}
|
||||
onOpenChange={(change) => setOpen(change)}
|
||||
open={open}
|
||||
key={itemName}
|
||||
>
|
||||
<div
|
||||
onPointerDown={handlePointerDown}
|
||||
onContextMenuCapture={(e) => {
|
||||
e.preventDefault();
|
||||
setOpen(true);
|
||||
}}
|
||||
key={itemName}
|
||||
data-tooltip-id={itemName}
|
||||
>
|
||||
<div
|
||||
draggable={!error}
|
||||
className={
|
||||
"side-bar-components-border bg-background" +
|
||||
(error ? " cursor-not-allowed select-none" : "")
|
||||
}
|
||||
style={{
|
||||
borderLeftColor: color,
|
||||
}}
|
||||
onDragStart={onDragStart}
|
||||
onDragEnd={() => {
|
||||
document.body.removeChild(
|
||||
document.getElementsByClassName("cursor-grabbing")[0],
|
||||
);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
data-testid={sectionName + display_name}
|
||||
id={sectionName + display_name}
|
||||
className="side-bar-components-div-form"
|
||||
>
|
||||
<span className="side-bar-components-text">{display_name}</span>
|
||||
<div ref={popoverRef}>
|
||||
<IconComponent
|
||||
name="Menu"
|
||||
className="side-bar-components-icon"
|
||||
/>
|
||||
<SelectTrigger></SelectTrigger>
|
||||
<SelectContent
|
||||
position="popper"
|
||||
side="bottom"
|
||||
sideOffset={-25}
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: cursorPos.x,
|
||||
top: cursorPos.y,
|
||||
}}
|
||||
>
|
||||
<SelectItem value={"download"}>
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
name="Download"
|
||||
className="relative top-0.5 mr-2 h-4 w-4"
|
||||
/>{" "}
|
||||
Download{" "}
|
||||
</div>{" "}
|
||||
</SelectItem>
|
||||
{!official && (
|
||||
<SelectItem value={"delete"}>
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
name="Trash2"
|
||||
className="relative top-0.5 mr-2 h-4 w-4"
|
||||
/>{" "}
|
||||
Delete{" "}
|
||||
</div>{" "}
|
||||
</SelectItem>
|
||||
)}
|
||||
</SelectContent>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Select>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default SidebarDraggableComponent;
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
import { APIClassType } from "@/types/api";
|
||||
import IconComponent from "../../../../../components/common/genericIconComponent";
|
||||
export default function NoteDraggableComponent() {
|
||||
function onDragStart(event: React.DragEvent<any>): void {
|
||||
const noteNode: APIClassType = {
|
||||
description: "",
|
||||
display_name: "",
|
||||
documentation: "",
|
||||
template: {},
|
||||
};
|
||||
event.dataTransfer.setData(
|
||||
"noteNode",
|
||||
JSON.stringify({ node: noteNode, type: "note" }),
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
draggable
|
||||
className={"cursor-grab rounded-l-md bg-background p-2"}
|
||||
onDragStart={onDragStart}
|
||||
>
|
||||
<div
|
||||
data-testid={"note_component"}
|
||||
id={"note component"}
|
||||
className="flex w-full items-center justify-between rounded-md border border-dashed border-ring px-3 py-1 text-sm"
|
||||
>
|
||||
<IconComponent name="StickyNote" className="pr-2" />
|
||||
<span className="side-bar-components-text">Add Note</span>
|
||||
<IconComponent name="Menu" className="side-bar-components-icon" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import { PRIORITY_SIDEBAR_ORDER } from "../../../../constants/constants";
|
||||
|
||||
export function sortKeys(a: string, b: string) {
|
||||
// Define the order of specific keys
|
||||
|
||||
const indexA = PRIORITY_SIDEBAR_ORDER.indexOf(a.toLowerCase());
|
||||
const indexB = PRIORITY_SIDEBAR_ORDER.indexOf(b.toLowerCase());
|
||||
|
||||
// Check if both keys are in the predefined order
|
||||
if (indexA !== -1 && indexB !== -1) {
|
||||
return indexA - indexB;
|
||||
}
|
||||
|
||||
// If only 'a' is in the predefined order, it should come first
|
||||
if (indexA !== -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If only 'b' is in the predefined order, it should come first
|
||||
if (indexB !== -1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If neither 'a' nor 'b' are in the predefined order, sort them alphabetically
|
||||
return a.localeCompare(b);
|
||||
}
|
||||
|
|
@ -9,10 +9,10 @@ import ShadTooltip from "@/components/common/shadTooltipComponent";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { SidebarHeader, SidebarTrigger } from "@/components/ui/sidebar";
|
||||
import { memo } from "react";
|
||||
import { SidebarFilterComponent } from "../../../extraSidebarComponent/sidebarFilterComponent";
|
||||
import { SidebarHeaderComponentProps } from "../../types";
|
||||
import FeatureToggles from "../featureTogglesComponent";
|
||||
import { SearchInput } from "../searchInput";
|
||||
import { SidebarFilterComponent } from "../sidebarFilterComponent";
|
||||
|
||||
export const SidebarHeaderComponent = memo(function SidebarHeaderComponent({
|
||||
showConfig,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import useAlertStore from "../../../../stores/alertStore";
|
|||
import useFlowStore from "../../../../stores/flowStore";
|
||||
import { useTypesStore } from "../../../../stores/typesStore";
|
||||
import { APIClassType } from "../../../../types/api";
|
||||
import sensitiveSort from "../extraSidebarComponent/utils/sensitive-sort";
|
||||
import isWrappedWithClass from "../PageComponent/utils/is-wrapped-with-class";
|
||||
import { CategoryGroup } from "./components/categoryGroup";
|
||||
import NoResultsMessage from "./components/emptySearchComponent";
|
||||
|
|
@ -35,6 +34,7 @@ import { applyLegacyFilter } from "./helpers/apply-legacy-filter";
|
|||
import { combinedResultsFn } from "./helpers/combined-results";
|
||||
import { filteredDataFn } from "./helpers/filtered-data";
|
||||
import { normalizeString } from "./helpers/normalize-string";
|
||||
import sensitiveSort from "./helpers/sensitive-sort";
|
||||
import { traditionalSearchMetadata } from "./helpers/traditional-search-metadata";
|
||||
import { UniqueInputsComponents } from "./types";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
export const TEMPLATES_DATA = {
|
||||
examples: [
|
||||
{
|
||||
name: "Basic Prompting (Hello, World)",
|
||||
icon: "BotMessageSquare",
|
||||
icon_bg_color: "bg-blue-500",
|
||||
},
|
||||
{
|
||||
name: "Memory Chatbot",
|
||||
icon: "MessagesSquare",
|
||||
icon_bg_color: "bg-purple-500",
|
||||
},
|
||||
{
|
||||
name: "Vector Store RAG",
|
||||
icon: "Database",
|
||||
icon_bg_color: "bg-green-500",
|
||||
},
|
||||
{
|
||||
name: "Travel Planning Agents",
|
||||
icon: "Plane",
|
||||
icon_bg_color: "bg-yellow-500",
|
||||
},
|
||||
{
|
||||
name: "Dynamic Agent",
|
||||
icon: "Users",
|
||||
icon_bg_color: "bg-red-500",
|
||||
},
|
||||
{
|
||||
name: "Blog Writer",
|
||||
icon: "FileText",
|
||||
icon_bg_color: "bg-indigo-500",
|
||||
},
|
||||
{
|
||||
name: "Sequential Tasks Agent",
|
||||
icon: "ListOrdered",
|
||||
icon_bg_color: "bg-pink-500",
|
||||
},
|
||||
{
|
||||
name: "Hierarchical Tasks Agent",
|
||||
icon: "GitFork",
|
||||
icon_bg_color: "bg-orange-500",
|
||||
},
|
||||
{
|
||||
name: "Simple Agent",
|
||||
icon: "Users",
|
||||
icon_bg_color: "bg-teal-500",
|
||||
},
|
||||
{
|
||||
name: "Document QA",
|
||||
icon: "FileText",
|
||||
icon_bg_color: "bg-cyan-500",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import useUploadFlow from "@/hooks/flows/use-upload-flow";
|
||||
import { CONSOLE_ERROR_MSG } from "../../../constants/alerts_constants";
|
||||
import useAlertStore from "../../../stores/alertStore";
|
||||
|
||||
const useDropdownOptions = ({
|
||||
navigate,
|
||||
is_component,
|
||||
}: {
|
||||
navigate: (url: string) => void;
|
||||
is_component: boolean;
|
||||
}) => {
|
||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const uploadFlow = useUploadFlow();
|
||||
const handleImportFromJSON = () => {
|
||||
uploadFlow({
|
||||
isComponent: is_component,
|
||||
})
|
||||
.then((id) => {
|
||||
setSuccessData({
|
||||
title: `${is_component ? "Component" : "Flow"} uploaded successfully`,
|
||||
});
|
||||
if (!is_component) navigate("/flow/" + id);
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: CONSOLE_ERROR_MSG,
|
||||
list: [error],
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const dropdownOptions = [
|
||||
{
|
||||
name: "Import from JSON",
|
||||
onBtnClick: handleImportFromJSON,
|
||||
},
|
||||
];
|
||||
|
||||
return [...dropdownOptions];
|
||||
};
|
||||
|
||||
export default useDropdownOptions;
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
import cloneDeep from "lodash/cloneDeep";
|
||||
import { useEffect } from "react";
|
||||
import { FlowType } from "../../../types/flow";
|
||||
|
||||
const useFilteredFlows = (
|
||||
flowsFromFolder: FlowType[],
|
||||
searchFlowsComponents: string,
|
||||
setAllFlows: (value: any[]) => void,
|
||||
) => {
|
||||
useEffect(() => {
|
||||
const newFlows = cloneDeep(flowsFromFolder || []);
|
||||
const filteredFlows = newFlows.filter(
|
||||
(f) =>
|
||||
f.name.toLowerCase().includes(searchFlowsComponents.toLowerCase()) ||
|
||||
f.description
|
||||
.toLowerCase()
|
||||
.includes(searchFlowsComponents.toLowerCase()),
|
||||
);
|
||||
|
||||
if (searchFlowsComponents === "") {
|
||||
setAllFlows(flowsFromFolder);
|
||||
} else {
|
||||
setAllFlows(filteredFlows);
|
||||
}
|
||||
}, [flowsFromFolder, searchFlowsComponents, setAllFlows]);
|
||||
};
|
||||
|
||||
export default useFilteredFlows;
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
import { useCallback } from "react";
|
||||
import { FlowType } from "../../../types/flow";
|
||||
|
||||
const useSelectAll = (
|
||||
flowsFromFolder: FlowType[],
|
||||
getValues: () => Record<string, boolean>,
|
||||
setValue: (key: string, value: boolean) => void,
|
||||
) => {
|
||||
const handleSelectAll = useCallback(
|
||||
(select) => {
|
||||
const flowsFromFolderIds = flowsFromFolder?.map((f) => f.id);
|
||||
if (select) {
|
||||
Object.keys(getValues()).forEach((key) => {
|
||||
if (!flowsFromFolderIds?.includes(key)) return;
|
||||
setValue(key, true);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(getValues()).forEach((key) => {
|
||||
setValue(key, false);
|
||||
});
|
||||
},
|
||||
[flowsFromFolder, getValues, setValue],
|
||||
);
|
||||
|
||||
return { handleSelectAll };
|
||||
};
|
||||
|
||||
export default useSelectAll;
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import { useEffect } from "react";
|
||||
|
||||
const useSelectedFlows = (
|
||||
entireFormValues: Record<string, boolean> | undefined,
|
||||
setSelectedFlowsComponentsCards: (
|
||||
selectedFlowsComponentsCards: string[],
|
||||
) => void,
|
||||
) => {
|
||||
useEffect(() => {
|
||||
if (!entireFormValues || Object.keys(entireFormValues).length === 0) return;
|
||||
|
||||
const selectedFlows = Object.keys(entireFormValues).filter((key) => {
|
||||
return entireFormValues[key] === true;
|
||||
});
|
||||
|
||||
setSelectedFlowsComponentsCards(selectedFlows);
|
||||
}, [entireFormValues, setSelectedFlowsComponentsCards]);
|
||||
};
|
||||
|
||||
export default useSelectedFlows;
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
export const getNameByType = (type: string) => {
|
||||
switch (type) {
|
||||
case "all":
|
||||
return "Component or Flow";
|
||||
case "component":
|
||||
return "Component";
|
||||
default:
|
||||
return "Flow";
|
||||
}
|
||||
};
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import { useFolderStore } from "../../../stores/foldersStore";
|
||||
import { downloadFlowsFromFolders } from "../services";
|
||||
|
||||
export function handleDownloadFolderFn(folderId: string) {
|
||||
downloadFlowsFromFolders(folderId).then((data) => {
|
||||
const folders = useFolderStore.getState().folders;
|
||||
|
||||
const folder = folders.find((f) => f.id === folderId);
|
||||
|
||||
data.folder_name = folder?.name || "folder";
|
||||
data.folder_description = folder?.description || "";
|
||||
|
||||
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
|
||||
JSON.stringify(data),
|
||||
)}`;
|
||||
|
||||
const link = document.createElement("a");
|
||||
link.href = jsonString;
|
||||
link.download = `${data.folder_name}.json`;
|
||||
|
||||
link.click();
|
||||
});
|
||||
}
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
import {
|
||||
useResetPassword,
|
||||
useUpdateUser,
|
||||
} from "@/controllers/API/queries/auth";
|
||||
import * as Form from "@radix-ui/react-form";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { useContext, useState } from "react";
|
||||
import IconComponent from "../../components/common/genericIconComponent";
|
||||
import Header from "../../components/headerComponent";
|
||||
import InputComponent from "../../components/inputComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import {
|
||||
EDIT_PASSWORD_ALERT_LIST,
|
||||
EDIT_PASSWORD_ERROR_ALERT,
|
||||
SAVE_ERROR_ALERT,
|
||||
SAVE_SUCCESS_ALERT,
|
||||
} from "../../constants/alerts_constants";
|
||||
import { CONTROL_PATCH_USER_STATE } from "../../constants/constants";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
import useAlertStore from "../../stores/alertStore";
|
||||
import {
|
||||
inputHandlerEventType,
|
||||
patchUserInputStateType,
|
||||
} from "../../types/components";
|
||||
import { gradients } from "../../utils/styleUtils";
|
||||
import GradientChooserComponent from "../SettingsPage/pages/GeneralPage/components/ProfilePictureForm/components/profilePictureChooserComponent";
|
||||
export default function ProfileSettingsPage(): JSX.Element {
|
||||
const [inputState, setInputState] = useState<patchUserInputStateType>(
|
||||
CONTROL_PATCH_USER_STATE,
|
||||
);
|
||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const { userData, setUserData } = useContext(AuthContext);
|
||||
const { password, cnfPassword, gradient } = inputState;
|
||||
|
||||
const { mutate: mutateResetPassword } = useResetPassword();
|
||||
const { mutate: mutatePatchUser } = useUpdateUser();
|
||||
|
||||
async function handlePatchUser() {
|
||||
if (password !== cnfPassword) {
|
||||
setErrorData({
|
||||
title: EDIT_PASSWORD_ERROR_ALERT,
|
||||
list: [EDIT_PASSWORD_ALERT_LIST],
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (password !== "") {
|
||||
mutateResetPassword(
|
||||
{ user_id: userData!.id, password: { password } },
|
||||
{
|
||||
onSuccess: successUpdates,
|
||||
onError: errorUpdates,
|
||||
},
|
||||
);
|
||||
}
|
||||
if (gradient !== "") {
|
||||
mutatePatchUser(
|
||||
{ user_id: userData!.id, user: { profile_image: gradient } },
|
||||
{
|
||||
onSuccess: successUpdates,
|
||||
onError: errorUpdates,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const errorUpdates = (error) => {
|
||||
setErrorData({
|
||||
title: SAVE_ERROR_ALERT,
|
||||
list: [(error as any).response.data.detail],
|
||||
});
|
||||
};
|
||||
|
||||
const successUpdates = () => {
|
||||
if (gradient !== "") {
|
||||
let newUserData = cloneDeep(userData);
|
||||
newUserData!.profile_image = gradient;
|
||||
setUserData(newUserData);
|
||||
}
|
||||
handleInput({ target: { name: "password", value: "" } });
|
||||
handleInput({ target: { name: "cnfPassword", value: "" } });
|
||||
setSuccessData({ title: SAVE_SUCCESS_ALERT });
|
||||
};
|
||||
|
||||
function handleInput({
|
||||
target: { name, value },
|
||||
}: inputHandlerEventType): void {
|
||||
setInputState((prev) => ({ ...prev, [name]: value }));
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
|
||||
<div className="community-page-arrangement">
|
||||
<div className="community-page-nav-arrangement">
|
||||
<span className="community-page-nav-title">
|
||||
<IconComponent name="User" className="w-6" />
|
||||
Profile Settings
|
||||
</span>
|
||||
</div>
|
||||
<span className="community-page-description-text">
|
||||
Change your profile settings like your password and your profile
|
||||
picture.
|
||||
</span>
|
||||
<Form.Root
|
||||
onSubmit={(event) => {
|
||||
handlePatchUser();
|
||||
const data = Object.fromEntries(new FormData(event.currentTarget));
|
||||
event.preventDefault();
|
||||
}}
|
||||
className="flex h-full flex-col px-6 pb-16"
|
||||
>
|
||||
<div className="flex h-full flex-col gap-4">
|
||||
<div className="flex gap-4">
|
||||
<div className="mb-3 w-96">
|
||||
<Form.Field name="password">
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Password{" "}
|
||||
</Form.Label>
|
||||
<InputComponent
|
||||
id="pasword"
|
||||
onChange={(value) => {
|
||||
handleInput({ target: { name: "password", value } });
|
||||
}}
|
||||
value={password}
|
||||
isForm
|
||||
password={true}
|
||||
placeholder="Password"
|
||||
className="w-full"
|
||||
/>
|
||||
<Form.Message match="valueMissing" className="field-invalid">
|
||||
Please enter your password
|
||||
</Form.Message>
|
||||
</Form.Field>
|
||||
</div>
|
||||
<div className="mb-3 w-96">
|
||||
<Form.Field name="cnfPassword">
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Confirm Password{" "}
|
||||
</Form.Label>
|
||||
|
||||
<InputComponent
|
||||
id="cnfPassword"
|
||||
onChange={(value) => {
|
||||
handleInput({ target: { name: "cnfPassword", value } });
|
||||
}}
|
||||
value={cnfPassword}
|
||||
isForm
|
||||
password={true}
|
||||
placeholder="Confirm Password"
|
||||
className="w-full"
|
||||
/>
|
||||
|
||||
<Form.Message className="field-invalid" match="valueMissing">
|
||||
Please confirm your password
|
||||
</Form.Message>
|
||||
</Form.Field>
|
||||
</div>
|
||||
</div>
|
||||
<Form.Field name="gradient">
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Profile Gradient{" "}
|
||||
</Form.Label>
|
||||
|
||||
<div className="mt-4 w-[1010px]">
|
||||
<GradientChooserComponent
|
||||
value={
|
||||
gradient == ""
|
||||
? (userData?.profile_image ??
|
||||
gradients[
|
||||
parseInt(userData?.id ?? "", 30) % gradients.length
|
||||
])
|
||||
: gradient
|
||||
}
|
||||
onChange={(value) => {
|
||||
handleInput({ target: { name: "gradient", value } });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Form.Field>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full justify-end">
|
||||
<div className="w-32">
|
||||
<Form.Submit asChild>
|
||||
<Button className="mr-3 mt-6 w-full" type="submit">
|
||||
Save
|
||||
</Button>
|
||||
</Form.Submit>
|
||||
</div>
|
||||
</div>
|
||||
</Form.Root>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
import * as Form from "@radix-ui/react-form";
|
||||
import GradientChooserComponent from "../../../../../../components/gradientChooserComponent";
|
||||
import { Button } from "../../../../../../components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../../../../../../components/ui/card";
|
||||
import { gradients } from "../../../../../../utils/styleUtils";
|
||||
|
||||
type ProfileGradientFormComponentProps = {
|
||||
gradient: string;
|
||||
handleInput: (event: any) => void;
|
||||
handlePatchGradient: (gradient: string) => void;
|
||||
userData: any;
|
||||
};
|
||||
const ProfileGradientFormComponent = ({
|
||||
gradient,
|
||||
handleInput,
|
||||
handlePatchGradient,
|
||||
userData,
|
||||
}: ProfileGradientFormComponentProps) => {
|
||||
return (
|
||||
<>
|
||||
<Form.Root
|
||||
onSubmit={(event) => {
|
||||
handlePatchGradient(gradient);
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<Card x-chunk="dashboard-04-chunk-1">
|
||||
<CardHeader>
|
||||
<CardTitle>Profile Gradient</CardTitle>
|
||||
<CardDescription>
|
||||
Choose the gradient that appears as your profile picture.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="py-2">
|
||||
<GradientChooserComponent
|
||||
value={
|
||||
gradient == ""
|
||||
? (userData?.profile_image ??
|
||||
gradients[
|
||||
parseInt(userData?.id ?? "", 30) % gradients.length
|
||||
])
|
||||
: gradient
|
||||
}
|
||||
onChange={(value) => {
|
||||
handleInput({ target: { name: "gradient", value } });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="border-t px-6 py-4">
|
||||
<Form.Submit asChild>
|
||||
<Button type="submit">Save</Button>
|
||||
</Form.Submit>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Form.Root>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ProfileGradientFormComponent;
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
import { CONTROL_PATCH_USER_STATE } from "@/constants/constants";
|
||||
import { AuthContext } from "@/contexts/authContext";
|
||||
import { usePostAddApiKey } from "@/controllers/API/queries/api-keys";
|
||||
import useAlertStore from "@/stores/alertStore";
|
||||
import { useStoreStore } from "@/stores/storeStore";
|
||||
import { inputHandlerEventType } from "@/types/components";
|
||||
import { useContext, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import StoreApiKeyFormComponent from "../StoreApiKeyPage/components/StoreApiKeyForm";
|
||||
import useScrollToElement from "../hooks/use-scroll-to-element";
|
||||
|
||||
const StoreApiKeyPage = () => {
|
||||
const { scrollId } = useParams();
|
||||
const [inputState, setInputState] = useState(CONTROL_PATCH_USER_STATE);
|
||||
const { storeApiKey } = useContext(AuthContext);
|
||||
useScrollToElement(scrollId);
|
||||
|
||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const {
|
||||
validApiKey,
|
||||
hasApiKey,
|
||||
loadingApiKey,
|
||||
updateHasApiKey: setHasApiKey,
|
||||
updateValidApiKey: setValidApiKey,
|
||||
updateLoadingApiKey: setLoadingApiKey,
|
||||
} = useStoreStore();
|
||||
|
||||
const { mutate: addApiKey } = usePostAddApiKey({
|
||||
onSuccess: () => {
|
||||
setSuccessData({ title: "API key saved successfully" });
|
||||
setHasApiKey(true);
|
||||
setValidApiKey(true);
|
||||
setLoadingApiKey(false);
|
||||
handleInput({ target: { name: "apikey", value: "" } });
|
||||
},
|
||||
onError: (error) => {
|
||||
setErrorData({
|
||||
title: "API key save error",
|
||||
list: [(error as any)?.response?.data?.detail],
|
||||
});
|
||||
setHasApiKey(false);
|
||||
setValidApiKey(false);
|
||||
setLoadingApiKey(false);
|
||||
},
|
||||
});
|
||||
|
||||
const handleSaveKey = (apikey: string) => {
|
||||
if (apikey) {
|
||||
addApiKey({ key: apikey });
|
||||
storeApiKey(apikey);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInput = ({ target: { name, value } }: inputHandlerEventType) => {
|
||||
setInputState((prev) => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
return (
|
||||
<StoreApiKeyFormComponent
|
||||
apikey={inputState.apikey}
|
||||
handleInput={handleInput}
|
||||
handleSaveKey={handleSaveKey}
|
||||
loadingApiKey={loadingApiKey}
|
||||
validApiKey={validApiKey}
|
||||
hasApiKey={hasApiKey}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default StoreApiKeyPage;
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
type SuspenseImageComponentProps = { src: string };
|
||||
|
||||
const imgCache = {
|
||||
__cache: {},
|
||||
read(src) {
|
||||
if (!this.__cache[src]) {
|
||||
this.__cache[src] = new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
this.__cache[src] = true;
|
||||
resolve(true);
|
||||
};
|
||||
img.onerror = () => {
|
||||
delete this.__cache[src]; // Remove failed cache entry
|
||||
reject(new Error("Image failed to load"));
|
||||
};
|
||||
img.src = src;
|
||||
});
|
||||
}
|
||||
if (this.__cache[src] instanceof Promise) {
|
||||
throw this.__cache[src];
|
||||
}
|
||||
return this.__cache[src];
|
||||
},
|
||||
};
|
||||
|
||||
const SuspenseImageComponent = ({
|
||||
src,
|
||||
...rest
|
||||
}: SuspenseImageComponentProps) => {
|
||||
try {
|
||||
imgCache.read(src);
|
||||
} catch (promise) {
|
||||
if (promise instanceof Promise) {
|
||||
throw promise;
|
||||
}
|
||||
throw new Error("Unexpected error in image loading");
|
||||
}
|
||||
|
||||
return <img src={src} {...rest} />;
|
||||
};
|
||||
|
||||
export default SuspenseImageComponent;
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import { FlowType } from "../flow";
|
||||
|
||||
export type sidebarNavigationItemType = {
|
||||
name: string;
|
||||
href: string;
|
||||
icon: React.ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>;
|
||||
current: boolean;
|
||||
};
|
||||
|
||||
export type localStorageUserType = {
|
||||
components: { [key: string]: FlowType };
|
||||
};
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
export interface SidebarCategory {
|
||||
display_name: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
const template: { [char: string]: string } = {};
|
||||
|
||||
export type TemplateContextType = {
|
||||
templates: typeof template;
|
||||
setTemplates: (newState: {}) => void;
|
||||
};
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
export type ChatInputType = {
|
||||
result: string;
|
||||
};
|
||||
|
||||
export type ChatOutputType = {
|
||||
message: string;
|
||||
sender: string;
|
||||
sender_name: string;
|
||||
};
|
||||
|
||||
export type FlowPoolObjectType = {
|
||||
timestamp: string;
|
||||
valid: boolean;
|
||||
params: any;
|
||||
data: { artifacts: any; results: any | ChatOutputType | ChatInputType };
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type FlowPoolType = {
|
||||
[key: string]: Array<FlowPoolObjectType>;
|
||||
};
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
export type LocationStoreType = {
|
||||
routeHistory: string[];
|
||||
setRouteHistory: (location: string) => void;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue