From d6f9124405be066237b003d98ebc261af8af72e5 Mon Sep 17 00:00:00 2001 From: igorrCarvalho Date: Tue, 29 Aug 2023 18:34:55 -0300 Subject: [PATCH 1/6] Feat[DropdownButtonComponent]: Create Dropdown Button component --- src/backend/langflow/api/v1/users.py | 4 +- .../DropdownButtonComponent/index.tsx | 49 ++ .../src/components/headerComponent/index.tsx | 28 +- src/frontend/src/pages/AdminPage/index.tsx | 471 +++++++++--------- .../components/PageComponent/index.tsx | 2 +- src/frontend/src/pages/loginPage/index.tsx | 3 +- src/frontend/src/types/components/index.ts | 2 +- src/frontend/src/utils/styleUtils.ts | 2 + 8 files changed, 310 insertions(+), 251 deletions(-) create mode 100644 src/frontend/src/components/DropdownButtonComponent/index.tsx diff --git a/src/backend/langflow/api/v1/users.py b/src/backend/langflow/api/v1/users.py index 7365e7cc1..5094409cb 100644 --- a/src/backend/langflow/api/v1/users.py +++ b/src/backend/langflow/api/v1/users.py @@ -43,7 +43,9 @@ def add_user( db.refresh(new_user) except IntegrityError as e: db.rollback() - raise HTTPException(status_code=400, detail="This username is unavailable.") from e + raise HTTPException( + status_code=400, detail="This username is unavailable." + ) from e return new_user diff --git a/src/frontend/src/components/DropdownButtonComponent/index.tsx b/src/frontend/src/components/DropdownButtonComponent/index.tsx new file mode 100644 index 000000000..20c956365 --- /dev/null +++ b/src/frontend/src/components/DropdownButtonComponent/index.tsx @@ -0,0 +1,49 @@ +import { useState } from "react"; +import IconComponent from "../genericIconComponent"; +import { Button } from "../ui/button"; + +export default function DropdownButton({ + firstButtonName, + onFirstBtnClick, + options, +}): JSX.Element { + const [showOptions, setShowOptions] = useState(false); + + return ( +
+
+ +
+
+ +
+ {showOptions && ( +
+ {options.map((optionName) => ( + + ))} +
+ )} +
+ ); +} diff --git a/src/frontend/src/components/headerComponent/index.tsx b/src/frontend/src/components/headerComponent/index.tsx index c74cdaa76..039b8895c 100644 --- a/src/frontend/src/components/headerComponent/index.tsx +++ b/src/frontend/src/components/headerComponent/index.tsx @@ -33,14 +33,14 @@ export default function Header(): JSX.Element { )} {!autoLogin && location.pathname !== `/flow/${tabId}` && ( { - logout(); - navigate("/login"); - }} - className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary cursor-pointer mx-5" - > - Sign out - + onClick={() => { + logout(); + navigate("/login"); + }} + className="mx-5 cursor-pointer text-sm font-medium text-muted-foreground transition-colors hover:text-primary" + > + Sign out + )} {location.pathname === "/admin" && ( @@ -48,7 +48,7 @@ export default function Header(): JSX.Element { onClick={() => { navigate("/"); }} - className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary cursor-pointer" + className="cursor-pointer text-sm font-medium text-muted-foreground transition-colors hover:text-primary" > Home @@ -59,11 +59,11 @@ export default function Header(): JSX.Element { location.pathname !== "/admin" && location.pathname !== `/flow/${tabId}` && ( navigate("/admin")} - > - Admin page - + className="cursor-pointer text-sm font-medium text-muted-foreground transition-colors hover:text-primary" + onClick={() => navigate("/admin")} + > + Admin page + )}
diff --git a/src/frontend/src/pages/AdminPage/index.tsx b/src/frontend/src/pages/AdminPage/index.tsx index fc963fee8..08e83f700 100644 --- a/src/frontend/src/pages/AdminPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/index.tsx @@ -4,6 +4,7 @@ import { useContext, useEffect, useRef, useState } from "react"; import PaginatorComponent from "../../components/PaginatorComponent"; import ShadTooltip from "../../components/ShadTooltipComponent"; import IconComponent from "../../components/genericIconComponent"; +import Header from "../../components/headerComponent"; import { Button } from "../../components/ui/button"; import { Checkbox } from "../../components/ui/checkbox"; import { Input } from "../../components/ui/input"; @@ -25,9 +26,8 @@ import { } from "../../controllers/API"; import ConfirmationModal from "../../modals/ConfirmationModal"; import UserManagementModal from "../../modals/UserManagementModal"; -import { UserInputType } from "../../types/components"; -import Header from "../../components/headerComponent"; import { Users } from "../../types/api"; +import { UserInputType } from "../../types/components"; export default function AdminPage() { const [inputValue, setInputValue] = useState(""); @@ -89,7 +89,7 @@ export default function AdminPage() { if (input === "") { setFilterUserList(userList.current); } else { - const filteredList = userList.current.filter((user:Users) => + const filteredList = userList.current.filter((user: Users) => user.username.toLowerCase().includes(input.toLowerCase()) ); setFilterUserList(filteredList); @@ -183,249 +183,254 @@ export default function AdminPage() { return ( <> -
-
- {userData && ( -
-
-
-
-
-
-

- Welcome back! -

-

- Navigate through this section to efficiently oversee all - application users. From here, you can seamlessly manage - user accounts. -

+
+
+ {userData && ( +
+
+
+
+
+
+

+ Welcome back! +

+

+ Navigate through this section to efficiently oversee all + application users. From here, you can seamlessly manage + user accounts. +

+
+
-
-
- {userList.current.length === 0 && !loadingUsers && ( + {userList.current.length === 0 && !loadingUsers && ( + <> +
+

There's no users registered :)

+
+ + )} <>
-

There's no users registered :)

-
- - )} - <> -
-
- handleFilterUsers(e.target.value)} - /> - {inputValue.length > 0 && ( - + )} +
+
+ { + handleNewUser(user); }} - variant="ghost" - className="h-8 px-2 lg:px-3" > - Reset - - - )} + + +
-
- { - handleNewUser(user); - }} - > - - -
-
- {loadingUsers && ( -
- Loading... -
- )} -
- - - - Id - Username - Active - Superuser - Created At - Updated At - - - - {!loadingUsers && ( - - {filterUserList.map((user:UserInputType, index) => ( - - - - - {user.id} - - - - - - - {user.username} - - - - - { - handleDisableUser( - user.is_active, - user.id, - user - ); - }} - > - - - - - { - handleSuperUserEdit( - user.is_superuser, - user.id, - user - ); - }} - > - - - - - { - new Date(user.create_at!) - .toISOString() - .split("T")[0] - } - - - { - new Date(user.updated_at!) - .toISOString() - .split("T")[0] - } - - -
- { - handleEditUser(user.id, editUser); - }} - > - - + {loadingUsers && ( +
+ Loading... +
+ )} +
+
+ + + Id + Username + Active + Superuser + Created At + Updated At + + + + {!loadingUsers && ( + + {filterUserList.map( + (user: UserInputType, index) => ( + + + + + {user.id} + - - - { - handleDeleteUser(user); - }} - > - - + + + + + {user.username} + - - - - - ))} - - )} -
-
+ + + { + handleDisableUser( + user.is_active, + user.id, + user + ); + }} + > + + + + + { + handleSuperUserEdit( + user.is_superuser, + user.id, + user + ); + }} + > + + + + + { + new Date(user.create_at!) + .toISOString() + .split("T")[0] + } + + + { + new Date(user.updated_at!) + .toISOString() + .split("T")[0] + } + + +
+ { + handleEditUser(user.id, editUser); + }} + > + + + + - { - handleChangePagination(pageSize, pageIndex); - }} - > - + { + handleDeleteUser(user); + }} + > + + + + +
+
+ + ) + )} + + )} + +
+ + { + handleChangePagination(pageSize, pageIndex); + }} + > + +
-
- )} + )}
); diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index 058be6e5c..620170fbb 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -421,7 +421,7 @@ export default function Page({ zoomOnScroll={!view} zoomOnPinch={!view} panOnDrag={!view} - proOptions={{hideAttribution: true}} + proOptions={{ hideAttribution: true }} > {!view && ( diff --git a/src/frontend/src/pages/loginPage/index.tsx b/src/frontend/src/pages/loginPage/index.tsx index 14e04708b..a3ac0fbcd 100644 --- a/src/frontend/src/pages/loginPage/index.tsx +++ b/src/frontend/src/pages/loginPage/index.tsx @@ -19,7 +19,8 @@ export default function LoginPage(): JSX.Element { useState(CONTROL_LOGIN_STATE); const { password, username } = inputState; - const { login, getAuthentication, setUserData, setIsAdmin } = useContext(AuthContext); + const { login, getAuthentication, setUserData, setIsAdmin } = + useContext(AuthContext); const navigate = useNavigate(); const { setErrorData } = useContext(alertContext); diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index f7b974a93..24e538e98 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -268,7 +268,7 @@ export type UserInputType = { is_superuser?: boolean; id?: string; create_at?: string; - updated_at?:string; + updated_at?: string; }; export type ApiKeyType = { diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 31a7adf7e..e74bb0e34 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -5,6 +5,7 @@ import { ChevronDown, ChevronLeft, ChevronRight, + ChevronUp, ChevronsLeft, ChevronsRight, ChevronsUpDown, @@ -300,4 +301,5 @@ export const nodeIconsLucide: iconsType = { UserCog2, Key, Unplug, + ChevronUp, }; From 6240991e0c29dcff852511c9d67f4dcc8d70578e Mon Sep 17 00:00:00 2001 From: igorrCarvalho Date: Tue, 29 Aug 2023 18:47:36 -0300 Subject: [PATCH 2/6] Feat: Add Dropdown Button to main page --- .../components/DropdownButtonComponent/index.tsx | 11 ++++++++--- src/frontend/src/pages/MainPage/index.tsx | 13 ++++++------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/frontend/src/components/DropdownButtonComponent/index.tsx b/src/frontend/src/components/DropdownButtonComponent/index.tsx index 20c956365..20865e12d 100644 --- a/src/frontend/src/components/DropdownButtonComponent/index.tsx +++ b/src/frontend/src/components/DropdownButtonComponent/index.tsx @@ -37,9 +37,14 @@ export default function DropdownButton({
{showOptions && (
- {options.map((optionName) => ( - ))}
diff --git a/src/frontend/src/pages/MainPage/index.tsx b/src/frontend/src/pages/MainPage/index.tsx index 67412401a..007298f29 100644 --- a/src/frontend/src/pages/MainPage/index.tsx +++ b/src/frontend/src/pages/MainPage/index.tsx @@ -6,6 +6,7 @@ import Header from "../../components/headerComponent"; import { Button } from "../../components/ui/button"; import { USER_PROJECTS_HEADER } from "../../constants/constants"; import { TabsContext } from "../../contexts/tabsContext"; +import DropdownButton from "../../components/DropdownButtonComponent"; export default function HomePage(): JSX.Element { const { flows, setTabId, downloadFlows, uploadFlows, addFlow, removeFlow } = useContext(TabsContext); @@ -45,17 +46,15 @@ export default function HomePage(): JSX.Element { Upload Collection - + options={[{name: "yesyes", onBtnClick: () => console.log('dips')}, {name: "dips", onBtnClick: () => console.log('yesyes')}]} + />
From da703d65e3d6f94c5066765bae5589f27c418385 Mon Sep 17 00:00:00 2001 From: igorrCarvalho Date: Tue, 29 Aug 2023 18:53:39 -0300 Subject: [PATCH 3/6] Feat: Add DropdownButton prop type --- .../src/components/DropdownButtonComponent/index.tsx | 3 ++- src/frontend/src/pages/MainPage/index.tsx | 2 +- src/frontend/src/types/components/index.ts | 6 ++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/components/DropdownButtonComponent/index.tsx b/src/frontend/src/components/DropdownButtonComponent/index.tsx index 20865e12d..71e02c43b 100644 --- a/src/frontend/src/components/DropdownButtonComponent/index.tsx +++ b/src/frontend/src/components/DropdownButtonComponent/index.tsx @@ -1,12 +1,13 @@ import { useState } from "react"; import IconComponent from "../genericIconComponent"; import { Button } from "../ui/button"; +import { dropdownButtonPropsType } from "../../types/components"; export default function DropdownButton({ firstButtonName, onFirstBtnClick, options, -}): JSX.Element { +}: dropdownButtonPropsType): JSX.Element { const [showOptions, setShowOptions] = useState(false); return ( diff --git a/src/frontend/src/pages/MainPage/index.tsx b/src/frontend/src/pages/MainPage/index.tsx index 007298f29..bdcf76c19 100644 --- a/src/frontend/src/pages/MainPage/index.tsx +++ b/src/frontend/src/pages/MainPage/index.tsx @@ -53,7 +53,7 @@ export default function HomePage(): JSX.Element { navigate("/flow/" + id); }); }} - options={[{name: "yesyes", onBtnClick: () => console.log('dips')}, {name: "dips", onBtnClick: () => console.log('yesyes')}]} + options={[{name: "Import from JSON", onBtnClick: () => console.log('imported')}]} />
diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index 24e538e98..b61ddf07d 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -544,3 +544,9 @@ export type fetchErrorComponentType = { message: string; description: string; }; + +export type dropdownButtonPropsType = { + firstButtonName: string; + onFirstBtnClick: () => void; + options: Array<{ name: string; onBtnClick: () => void; }>; +}; From bfc8ad8e307183d913e9624edc0ddc70e9937d59 Mon Sep 17 00:00:00 2001 From: igorrCarvalho Date: Wed, 30 Aug 2023 14:20:10 -0300 Subject: [PATCH 4/6] feat: Add uploadFlow function to import from JSON button --- .../DropdownButtonComponent/index.tsx | 26 +++++++++++++------ src/frontend/src/pages/MainPage/index.tsx | 7 ++--- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/frontend/src/components/DropdownButtonComponent/index.tsx b/src/frontend/src/components/DropdownButtonComponent/index.tsx index 71e02c43b..b9fffbb00 100644 --- a/src/frontend/src/components/DropdownButtonComponent/index.tsx +++ b/src/frontend/src/components/DropdownButtonComponent/index.tsx @@ -1,14 +1,15 @@ -import { useState } from "react"; +import { Fragment, useState } from "react"; import IconComponent from "../genericIconComponent"; import { Button } from "../ui/button"; import { dropdownButtonPropsType } from "../../types/components"; +import { Transition } from "@headlessui/react"; export default function DropdownButton({ firstButtonName, onFirstBtnClick, options, }: dropdownButtonPropsType): JSX.Element { - const [showOptions, setShowOptions] = useState(false); + const [showOptions, setShowOptions] = useState(false); return (
@@ -36,20 +37,29 @@ export default function DropdownButton({ )}
- {showOptions && ( -
- {options.map(({ name, onBtnClick }) => ( + +
+ {options.map(({ name, onBtnClick }, index) => ( ))}
- )} +
); } diff --git a/src/frontend/src/pages/MainPage/index.tsx b/src/frontend/src/pages/MainPage/index.tsx index bdcf76c19..f3fe68a6c 100644 --- a/src/frontend/src/pages/MainPage/index.tsx +++ b/src/frontend/src/pages/MainPage/index.tsx @@ -8,8 +8,9 @@ import { USER_PROJECTS_HEADER } from "../../constants/constants"; import { TabsContext } from "../../contexts/tabsContext"; import DropdownButton from "../../components/DropdownButtonComponent"; export default function HomePage(): JSX.Element { - const { flows, setTabId, downloadFlows, uploadFlows, addFlow, removeFlow } = + const { flows, setTabId, downloadFlows, uploadFlows, addFlow, removeFlow, uploadFlow } = useContext(TabsContext); + const dropdownOptions = [{name: "Import from JSON", onBtnClick: () => uploadFlow(true)}] // Set a null id useEffect(() => { @@ -53,12 +54,12 @@ export default function HomePage(): JSX.Element { navigate("/flow/" + id); }); }} - options={[{name: "Import from JSON", onBtnClick: () => console.log('imported')}]} + options={dropdownOptions} /> - Manage your personal projects. Download or upload your collection. + Manage your personal projects. Download or upload your collection.
{flows.map((flow, idx) => ( From fc0e8685151a61c1be7ef5e733e895f8275a758c Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Thu, 31 Aug 2023 11:15:12 -0300 Subject: [PATCH 5/6] Fixed UI of Dropdown Button --- .../DropdownButtonComponent/index.tsx | 90 ++++++++++--------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/src/frontend/src/components/DropdownButtonComponent/index.tsx b/src/frontend/src/components/DropdownButtonComponent/index.tsx index b9fffbb00..1ddddb3c1 100644 --- a/src/frontend/src/components/DropdownButtonComponent/index.tsx +++ b/src/frontend/src/components/DropdownButtonComponent/index.tsx @@ -1,8 +1,13 @@ -import { Fragment, useState } from "react"; +import { useState } from "react"; +import { dropdownButtonPropsType } from "../../types/components"; import IconComponent from "../genericIconComponent"; import { Button } from "../ui/button"; -import { dropdownButtonPropsType } from "../../types/components"; -import { Transition } from "@headlessui/react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "../ui/dropdown-menu"; export default function DropdownButton({ firstButtonName, @@ -12,54 +17,51 @@ export default function DropdownButton({ const [showOptions, setShowOptions] = useState(false); return ( -
-
- -
-
- + + { event.stopPropagation(); + event.preventDefault(); setShowOptions(!showOptions); }} > - {!showOptions ? ( -
- -
{options.map(({ name, onBtnClick }, index) => ( - + ))} -
-
+ +
); } From 8fedf9562fcda03e6512099bbcfd40e60d4346ad Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Thu, 31 Aug 2023 11:38:40 -0300 Subject: [PATCH 6/6] Fixed usability issues on import json method --- src/frontend/src/contexts/tabsContext.tsx | 47 +++++++++++------------ src/frontend/src/pages/MainPage/index.tsx | 4 +- src/frontend/src/types/tabs/index.ts | 2 +- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/frontend/src/contexts/tabsContext.tsx b/src/frontend/src/contexts/tabsContext.tsx index 97ad50329..339ad67b5 100644 --- a/src/frontend/src/contexts/tabsContext.tsx +++ b/src/frontend/src/contexts/tabsContext.tsx @@ -48,7 +48,7 @@ const TabsContextInitialValue: TabsContextType = { downloadFlow: (flow: FlowType) => {}, downloadFlows: () => {}, uploadFlows: () => {}, - uploadFlow: () => {}, + uploadFlow: async () => "", isBuilt: false, setIsBuilt: (state: boolean) => {}, hardReset: () => {}, @@ -298,39 +298,38 @@ export function TabsProvider({ children }: { children: ReactNode }) { * If the file type is application/json, the file is read and parsed into a JSON object. * The resulting JSON object is passed to the addFlow function. */ - function uploadFlow(newProject?: boolean, file?: File) { + async function uploadFlow( + newProject?: boolean, + file?: File + ): Promise { + let id; if (file) { - file.text().then((text) => { - // parse the text into a JSON object - let flow: FlowType = JSON.parse(text); + let text = await file.text(); + // parse the text into a JSON object + let flow: FlowType = JSON.parse(text); - addFlow(flow, newProject); - }); + id = await addFlow(flow, newProject); } else { // create a file input const input = document.createElement("input"); input.type = "file"; input.accept = ".json"; // add a change event listener to the file input - input.onchange = (e: Event) => { - // check if the file type is application/json - if ( - (e.target as HTMLInputElement).files![0].type === "application/json" - ) { - // get the file from the file input - const currentfile = (e.target as HTMLInputElement).files![0]; - // read the file as text - currentfile.text().then((text) => { - // parse the text into a JSON object + id = await new Promise(resolve => { + input.onchange = async (e: Event) => { + if ((e.target as HTMLInputElement).files![0].type === "application/json") { + const currentfile = (e.target as HTMLInputElement).files![0]; + let text = await currentfile.text(); let flow: FlowType = JSON.parse(text); - - addFlow(flow, newProject); - }); - } - }; - // trigger the file input click event to open the file dialog - input.click(); + const flowId = await addFlow(flow, newProject); + resolve(flowId); + } + }; + // trigger the file input click event to open the file dialog + input.click(); + }); } + return id; } function uploadFlows() { diff --git a/src/frontend/src/pages/MainPage/index.tsx b/src/frontend/src/pages/MainPage/index.tsx index f6f111185..364519ea8 100644 --- a/src/frontend/src/pages/MainPage/index.tsx +++ b/src/frontend/src/pages/MainPage/index.tsx @@ -18,7 +18,9 @@ export default function HomePage(): JSX.Element { removeFlow, uploadFlow, isLoading, } = useContext(TabsContext); - const dropdownOptions = [{name: "Import from JSON", onBtnClick: () => uploadFlow(true)}] + const dropdownOptions = [{name: "Import from JSON", onBtnClick: () => uploadFlow(true).then((id) => { + navigate("/flow/" + id); + })}] // Set a null id useEffect(() => { diff --git a/src/frontend/src/types/tabs/index.ts b/src/frontend/src/types/tabs/index.ts index 036f82717..4c5b99dd7 100644 --- a/src/frontend/src/types/tabs/index.ts +++ b/src/frontend/src/types/tabs/index.ts @@ -24,7 +24,7 @@ export type TabsContextType = { uploadFlows: () => void; isBuilt: boolean; setIsBuilt: (state: boolean) => void; - uploadFlow: (newFlow?: boolean, file?: File) => void; + uploadFlow: (newFlow?: boolean, file?: File) => Promise; hardReset: () => void; getNodeId: (nodeType: string) => string; tabsState: TabsState;