diff --git a/.github/workflows/publish-to-auto-release.yml b/.github/workflows/publish-to-auto-release.yml index 0b08bac..5bc503b 100644 --- a/.github/workflows/publish-to-auto-release.yml +++ b/.github/workflows/publish-to-auto-release.yml @@ -63,7 +63,7 @@ jobs: TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} with: tagName: app-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version. - releaseName: "Control v__VERSION__" + releaseName: "Construct v__VERSION__" releaseBody: "See the assets to download this version and install." releaseDraft: true prerelease: false diff --git a/README.md b/README.md index f3f64ca..3cb9a63 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +
+

Construct

+
+ +![image](https://github.com/user-attachments/assets/a0984944-4d89-442e-8b39-f869001ddf86) + # Tauri + React + Typescript This template should help get you started developing with Tauri, React and Typescript in Vite. diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 2b73d78..8f788ef 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -555,7 +555,7 @@ dependencies = [ [[package]] name = "construct" -version = "0.3.0" +version = "0.4.0" dependencies = [ "curl", "serde", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 23f7e8d..8482dee 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "construct" -version = "0.3.0" +version = "0.4.0" description = "A GUI API Client" authors = ["clearfeld"] edition = "2021" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index bf88421..4d574c3 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "construct", - "version": "0.3.0", + "version": "0.4.0", "identifier": "com.construct.app", "build": { "beforeDevCommand": "pnpm dev", diff --git a/src/App.tsx b/src/App.tsx index 644c713..2b85713 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,6 +16,7 @@ import { Routes, Route, Outlet } from "react-router"; import "./updater.tsx"; import { H5 } from "@controlkit/ui"; import TabBar from "./commons/tabbar/index.tsx"; +import EnvironmentsPage from "./pages/environments/index.tsx"; const styles = stylex.create({ container: { @@ -153,6 +154,27 @@ function App() { } /> + + + + +
+ + +
+
+ +
+ + {/* */} +
+
+ + } + />
); diff --git a/src/assets/enabled-arrow-circle-active.svg b/src/assets/enabled-arrow-circle-active.svg new file mode 100644 index 0000000..c36fdfd --- /dev/null +++ b/src/assets/enabled-arrow-circle-active.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/enabled-arrow-circle-hover.svg b/src/assets/enabled-arrow-circle-hover.svg new file mode 100644 index 0000000..880c4f9 --- /dev/null +++ b/src/assets/enabled-arrow-circle-hover.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/environment.svg b/src/assets/environment.svg new file mode 100644 index 0000000..2db0e6e --- /dev/null +++ b/src/assets/environment.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/commons/sidebar/collections/recursive_tree.tsx b/src/commons/sidebar/collections/recursive_tree.tsx index 73513c7..b51e7d1 100644 --- a/src/commons/sidebar/collections/recursive_tree.tsx +++ b/src/commons/sidebar/collections/recursive_tree.tsx @@ -639,7 +639,6 @@ export default function RecursiveTree(props: any) { navigate(`/http_request/${item.id}`); - const tabs = [...getTabs()]; const tab = tabs.find((t) => t.id === item.id); @@ -652,7 +651,9 @@ export default function RecursiveTree(props: any) { // // setTabState(item.id, E_TabStatus.SAVED); // }, 100); - const method = methods.find((method) => method.value === tab.data.method); + const method = methods.find( + (method) => method.value === tab.data.method, + ); setRequestParameters( tab.data.id, @@ -669,17 +670,16 @@ export default function RecursiveTree(props: any) { // return; } else { - - setRequestParameters( - item.id, - item.name, - item.url, - method, - autoHeaders, // [], // item.autoHeaders, - item.headers, - item.body, - // item.cookies, - ); + setRequestParameters( + item.id, + item.name, + item.url, + method, + autoHeaders, // [], // item.autoHeaders, + item.headers, + item.body, + // item.cookies, + ); const t: T_Tab = { id: item.id, diff --git a/src/commons/sidebar/content.tsx b/src/commons/sidebar/content.tsx index 3298597..beb2eb2 100644 --- a/src/commons/sidebar/content.tsx +++ b/src/commons/sidebar/content.tsx @@ -1,10 +1,10 @@ import * as stylex from "@stylexjs/stylex"; -import type { SidebarTab } from "./rail.tsx"; + import Collections from "./collections/index.tsx"; + +import Environments from "./environment"; +import { E_SidebarSection } from "@src/stores/request_store/sidebar_slice.ts"; // import { SidebarSearchRow } from "./components/sidebar-search-row.tsx"; -// import { CollectionTreeView } from "./components/collection-tree-view.tsx"; -// import data from "./components/collection-data.json"; -// import { EnvironmentList } from "./components/environment-list"; const styles = stylex.create({ container: { @@ -14,18 +14,17 @@ const styles = stylex.create({ }); interface SidebarContentProps { - selectedTab: SidebarTab | null; + selectedTab: E_SidebarSection | null; } export function SidebarContent({ selectedTab }: SidebarContentProps) { - // return (
{/* */} - {selectedTab === "collections" && } + {selectedTab === E_SidebarSection.COLLECTIONS && } - {/* {selectedTab === "environment" && } */} + {selectedTab === E_SidebarSection.ENVIRONMENT && }
); } diff --git a/src/commons/sidebar/environment/global-environment-button.tsx b/src/commons/sidebar/environment/global-environment-button.tsx new file mode 100644 index 0000000..3153720 --- /dev/null +++ b/src/commons/sidebar/environment/global-environment-button.tsx @@ -0,0 +1,82 @@ +import { memo } from "react"; +// import { useRecoilValue } from "recoil"; +import * as stylex from "@stylexjs/stylex"; +// import { EnvironmentButtonProps } from "./type.ts"; +// import { GlobalEnvironmentState } from "../../../../store/environment/global-environment.ts"; + +const styles = stylex.create({ + button: { + position: "relative", + border: "none", + outline: "none", + backgroundColor: "transparent", + boxShadow: "none", + color: "var(--color-sidebar-text)", + height: "1.75rem", + width: "100%", + padding: "0 1.25rem", + textAlign: "left", + fontSize: "0.875rem", + borderRadius: 0, + display: "flex", + alignItems: "center", + columnGap: "0.25rem", + ":hover": { + backgroundColor: "var(--button-hover-color)", + }, + }, + activeButton: { + backgroundColor: "var(--sidebar-selected-bg)", + color: "var(--color-white)", + ":hover": { + backgroundColor: "var(--sidebar-selected-bg)", + }, + }, + activeBorder: { + border: "0.0625rem solid var(--sidebar-selected-border)", + height: "calc(100% - 0.25rem)", + left: "rem", + position: "absolute", + zIndex: 1, + }, + globalButtonContainer: { + marginVertical: "0.25rem", + position: "relative", + display: "flex", + alignItems: "center", + }, +}); + +// interface GlobalEnvironmentButtonProps extends EnvironmentButtonProps {} + +export const GlobalEnvironmentButton = memo(function GlobalEnvironmentButton( + // props: GlobalEnvironmentButtonProps, +) { + // const globalEnv = useRecoilValue(GlobalEnvironmentState); + // const selectedGlobalEnv = () => { + // props.onClick({ + // ...globalEnv, + // name: "global", + // id: "", + // inUse: true, + // forkedChildren: [], + // }); + // }; + + return ( +
+
+ +
+ ); +}); diff --git a/src/commons/sidebar/environment/index.tsx b/src/commons/sidebar/environment/index.tsx new file mode 100644 index 0000000..2abb295 --- /dev/null +++ b/src/commons/sidebar/environment/index.tsx @@ -0,0 +1,121 @@ +import * as stylex from "@stylexjs/stylex"; + +// import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; +// import { +// EnvironmentIdListState, +// EnvironmentsState, +// } from "../../../../store/environment/environments.ts"; +// import { SelectedEnvironmentState } from "../../../../store/environment/selected-environment.ts"; + +// import { GlobalEnvironmentButton } from "./global-environment-button.tsx"; +import { WorkspaceEnvironmentButton } from "./workspace-environment-button.tsx"; +import useRequestStore from "@src/stores/request_store/index.ts"; +import type { T_Environment } from "@src/stores/request_store/environments_slice.ts"; +import { Button, Divider } from "@controlkit/ui"; + +const styles = stylex.create({ + container: { + flex: 1, + }, + divider: { + height: "0.0625rem", + backgroundColor: "var(--sidebar-border)", + width: "100%", + marginBottom: "0.25rem", + }, +}); + +export default function Environments() { + // const setEnvironment = useSetRecoilState(EnvironmentsState); + // const environmentIds = useRecoilValue(EnvironmentIdListState); + // const [selectedEnvironment, setSelectedEnvironment] = useRecoilState( + // SelectedEnvironmentState, + // ); + + const environments = useRequestStore((state) => state.environments); + + const addEmptyDefaultEnvironment = useRequestStore((state) => state.addEmptyDefaultEnvironment); + + // const toggleInUse = (id: string) => { + // setEnvironment((prev) => { + // return prev.map((env) => { + // if (env.id === id) { + // return { + // ...env, + // inUse: !env.inUse, + // }; + // } + // return { + // ...env, + // inUse: false, + // }; + // }); + // }); + // }; + + return ( +
+
+ {/* */} + + {/*
+
*/} + + +
+ + + + {/* + +

vault

+ + */} + +
+ {environments.map((env: T_Environment) => { + return ( + {}} + /> + ); + })} +
+ ); +} diff --git a/src/commons/sidebar/environment/workspace-environment-button.tsx b/src/commons/sidebar/environment/workspace-environment-button.tsx new file mode 100644 index 0000000..c8a4a45 --- /dev/null +++ b/src/commons/sidebar/environment/workspace-environment-button.tsx @@ -0,0 +1,354 @@ +import { memo, useState } from "react"; +// import { useRecoilValue } from "recoil"; +import * as stylex from "@stylexjs/stylex"; +import type { + T_ActiveEnvironment, + T_Environment, +} from "@src/stores/request_store/environments_slice"; +import useRequestStore from "@src/stores/request_store"; +import { useNavigate } from "react-router"; +import { + E_TabStatus, + E_TabType, + type T_Tab, +} from "@src/stores/request_store/tabbar_slice"; +// import GitForkSVG from "@assets/git-fork.svg?react"; +// import { CheckIcon } from "./checkbox-icon.tsx"; +// import { EnvironmentById } from "../../../../store/environment/environments.ts"; +// import { EnvironmentButtonProps } from "./type.ts"; +// import { EnvironmentDropdownButton } from "./environment-dropdown-button.tsx"; + +import ArrowCheckCircleHoverSVG from "../../../assets/enabled-arrow-circle-hover.svg?react"; +import ArrowCheckCircleActiveSVG from "../../../assets/enabled-arrow-circle-active.svg?react"; + +const styles = stylex.create({ + button: { + position: "relative", + border: "none", + outline: "none", + backgroundColor: "transparent", + boxShadow: "none", + color: "var(--color-sidebar-text)", + height: "1.75rem", + width: "100%", + padding: "0 1.25rem", + textAlign: "left", + fontSize: "0.875rem", + borderRadius: 0, + display: "flex", + alignItems: "center", + columnGap: "0.25rem", + cursor: "pointer", + + ":hover": { + backgroundColor: "var(--button-hover-color)", + }, + }, + + activeButton: { + backgroundColor: "var(--sidebar-selected-bg)", + color: "var(--color-white)", + + ":hover": { + backgroundColor: "var(--sidebar-selected-bg)", + }, + }, + + inactiveBorder: { + border: "0.125rem solid transparent", + }, + + activeBorder: { + border: "0.125rem solid var(--sidebar-selected-border)", + // height: "calc(100% - 0.25rem)", + backgroundColor: "#1F252D", + height: "1.5rem", + // left: "rem", + // position: "absolute", + // zIndex: 1, + }, + + buttonContainer: { + position: "relative", + marginBottom: "0.0625rem", + display: "flex", + alignItems: "center", + }, + + leftRow: { + display: "flex", + flexDirection: "row", + alignItems: "center", + boxSizing: "border-box", + columnGap: "0.25rem", + flex: 1, + minWidth: 0, + }, + + rightRow: { + display: "none", + flexDirection: "row", + alignItems: "center", + justifyContent: "flex-end", + backgroundColor: "transparent", + }, + + showRightRow: { + display: "flex", + }, + + buttonText: { + minWidth: 0, + color: "inherit", + textOverflow: "ellipsis", + overflow: "hidden", + whiteSpace: "nowrap", + }, + + forkContainer: { + flex: 1, + display: "flex", + flexDirection: "row", + alignItems: "center", + columnGap: "0.25rem", + maxWidth: "50%", + }, + + forkText: { + color: "var(--color-placeholder)", + fontSize: "0.625rem", + }, + + forkSvg: { + width: "0.625rem", + height: "0.625rem", + color: "var(--color-placeholder)", + }, + + sideButton: { + backgroundColor: "transparent", + border: "none", + height: "1.5rem", + width: "1.5rem", + borderRadius: "0.25rem", + display: "flex", + alignItems: "center", + justifyContent: "center", + padding: 0, + boxShadow: "none", + cursor: "pointer", + + ":hover": { + backgroundColor: "var(--cds-gray-300)", + }, + }, + + ellipsisSvg: { + width: "1rem", + height: "1rem", + }, +}); + +interface WorkspaceEnvironmentButtonProps { + // extends EnvironmentButtonProps + envId: string; + env: T_Environment; + toggleInUse: (id: string) => void; +} + +export const WorkspaceEnvironmentButton = memo( + function WorkspaceEnvironmentButton(props: WorkspaceEnvironmentButtonProps) { + const navigate = useNavigate(); + + const activeEnvirontmnet = useRequestStore( + (state) => state.activeEnvironment, + ); + + const getTabs = useRequestStore((state) => state.getTabs); + const setTabs = useRequestStore((state) => state.setTabs); + const setActiveTab = useRequestStore((state) => state.setActiveTab); + + const getActiveEnvironmentDetails = useRequestStore( + (state) => state.getActiveEnvironmentDetails, + ); + const setActiveEnvironmentDetails = useRequestStore( + (state) => state.setActiveEnvironmentDetails, + ); + + const setActiveEnvironment = useRequestStore( + (state) => state.setActiveEnvironment, + ); + // const getActiveEnvironmentInEnvironments = useRequestStore( + // (state) => state.getActiveEnvironmentInEnvironments, + // ); + + const [isHovering, setIsHovering] = useState(false); + // const [isDropdownOpen, setIsDropdownOpen] = useState(false); + + const getEnvironmentById = useRequestStore( + (state) => state.getEnvironmentById, + ); + + const enabledEnvironment = useRequestStore( + (state) => state.enabledEnvironment, + ); + const setEnabledEnvironmentDetails = useRequestStore( + (state) => state.setEnabledEnvironmentDetails, + ); + + return ( +
setIsHovering(true)} + onMouseLeave={() => setIsHovering(false)} + > +
+
setIsHovering(true)} + onMouseLeave={() => setIsHovering(false)} + {...stylex.props( + styles.button, + activeEnvirontmnet?.id === props.env.id && styles.activeButton, + )} + onClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + + const id = props.env.id; + navigate(`/environment/${id}`); + + const tabs = [...getTabs()]; + + const tab = tabs.find((t) => t.id === id); + // const status = tab?.status ?? E_TabStatus.NONE; + + const aed = getActiveEnvironmentDetails(); + + // let activeEnvironment = null; + + if (aed === null) { + const aed_c: T_ActiveEnvironment = { + env_id: props.env.id, + stage_id: null, + }; + setActiveEnvironmentDetails(aed_c); + + const target_env = getEnvironmentById(props.env.id); + + if (target_env) { + setActiveEnvironment(target_env); + } + } else { + aed.env_id = props.env.id; + setActiveEnvironmentDetails(aed); + + if (tab) { + if (Object.keys(tab.data).length === 0) { + const target_env = getEnvironmentById(props.env.id); + if (target_env) { + setActiveEnvironment(target_env); + } + } else { + setActiveEnvironment(tab.data); + } + } else { + const target_env = getEnvironmentById(props.env.id); + + if (target_env) { + setActiveEnvironment(target_env); + } + } + } + + // setActiveEnvironment(getActiveEnvironmentInEnvironments()); + // const activeEnvironment = getActiveEnvironmentInEnvironments(); + + // const activeEnvironment = getActiveEnvironmentInEnvironments(); + + // if (activeEnvironment) { + // // tab.data = structuredClone(activeEnvironment); + // setActiveEnvironment(activeEnvironment); + // // // setActiveEnvironment(activeEnvironment); + // // } else { + + // setActiveTab(activeEnvironment.id); + // } + + if (tab) { + setTabs(tabs); + setActiveTab(id); + } else { + const t: T_Tab = { + id: id, + status: E_TabStatus.NONE, + title: props.env.name, + type: E_TabType.ENVIRONMENT, + requestType: "", + data: {}, + }; + tabs.push(t); + setTabs(tabs); + setActiveTab(id); + } + + // props.onClick(environment); + }} + > +
+

{props.env.name}

+ + {/* {environment.forkedFrom && ( */} +
+

+ {/* {environment.forkedFrom.forkedName} */} +

+ + {/* */} +
+ {/* )} */} +
+ +
+ + + {/* +

dropdown

+
*/} +
+
+
+ ); + }, +); diff --git a/src/commons/sidebar/index.tsx b/src/commons/sidebar/index.tsx index 35528d5..e221e62 100644 --- a/src/commons/sidebar/index.tsx +++ b/src/commons/sidebar/index.tsx @@ -1,8 +1,10 @@ import { useEffect, useRef, useState } from "react"; import * as stylex from "@stylexjs/stylex"; -import { SidebarRail, type SidebarTab } from "./rail.tsx"; +import { SidebarRail } from "./rail.tsx"; import { SidebarContent } from "./content.tsx"; +import { E_SidebarSection } from "@src/stores/request_store/sidebar_slice.ts"; +import useRequestStore from "@src/stores/request_store/index.ts"; const styles = stylex.create({ wrapper: { @@ -48,10 +50,14 @@ const MAX_EXPANDED_WIDTH = 16 * 40; const MIN_EXPANDED_THRESHOLD = MIN_EXPANDED_WIDTH / 2; function Sidebar() { - const [selectedTab, setSelectedTab] = useState( - "collections", - ); - const prevSelectedTab = useRef(null); + // const [selectedTab, setSelectedTab] = useState( + // E_SidebarSection.COLLECTIONS, + // ); + + const currentSidebarTab = useRequestStore((state) => state.currentSidebarTab); + const setCurrentSidebarTab = useRequestStore((state) => state.setCurrentSidebarTab); + + const prevSelectedTab = useRef(null); const prevSelectedState = useRef(false); const html_style = document.getElementsByTagName("html")[0].style; const resizerRef = useRef(null); @@ -87,7 +93,7 @@ function Sidebar() { // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect(() => { - if (selectedTab) { + if (currentSidebarTab) { if (!prevSelectedState.current) { html_style.setProperty( // TODO: move these values out into a constants file instead @@ -95,10 +101,10 @@ function Sidebar() { `${MIN_EXPANDED_WIDTH}px`, ); } - prevSelectedTab.current = selectedTab; + prevSelectedTab.current = currentSidebarTab; } - prevSelectedState.current = !!selectedTab; - }, [selectedTab]); + prevSelectedState.current = !!currentSidebarTab; + }, [currentSidebarTab]); function resize(e: MouseEvent) { e.preventDefault(); @@ -110,10 +116,10 @@ function Sidebar() { if (curSize < MIN_EXPANDED_WIDTH && curSize > MIN_EXPANDED_THRESHOLD) { curSize = MIN_EXPANDED_WIDTH; - setSelectedTab(prevSelectedTab.current || "collections"); + setCurrentSidebarTab(prevSelectedTab.current || E_SidebarSection.COLLECTIONS); } else if (curSize <= MIN_EXPANDED_THRESHOLD) { curSize = MIN_WIDTH; - setSelectedTab(null); + setCurrentSidebarTab(null); } // console.log(size); html_style.setProperty( @@ -126,8 +132,8 @@ function Sidebar() { return (
- - + +
void; + selectedTab: E_SidebarSection | null; + onSelectTab: (tab: E_SidebarSection) => void; } export function SidebarRail({ selectedTab, onSelectTab }: SidebarRailProps) { @@ -103,12 +94,19 @@ export function SidebarRail({ selectedTab, onSelectTab }: SidebarRailProps) {
onSelectTab("collections")} + selected={selectedTab === E_SidebarSection.COLLECTIONS} + onClick={() => onSelectTab(E_SidebarSection.COLLECTIONS)} + /> + + onSelectTab(E_SidebarSection.ENVIRONMENT)} /> - {/* onSelectTab('collections')} /> + + {/* onSelectTab('note')} /> - onSelectTab('environment')} /> + onSelectTab('db')} /> onSelectTab('trend')} /> onSelectTab('connected')} /> diff --git a/src/commons/tabbar/environment-dropdown/index.tsx b/src/commons/tabbar/environment-dropdown/index.tsx index 1d4cfa0..ba729b2 100644 --- a/src/commons/tabbar/environment-dropdown/index.tsx +++ b/src/commons/tabbar/environment-dropdown/index.tsx @@ -1,97 +1,144 @@ -import { useState } from "react"; -import stylex from "@stylexjs/stylex"; +import * as stylex from "@stylexjs/stylex"; +import useRequestStore from "@src/stores/request_store"; -// import DownArrow from "../../../assets/arrow-down.svg?react"; -// import SettingsPage from "../../../assets/settings-page.svg?react"; -// import { useOutsideClick } from "../../../hooks/use-outside-click"; +import DownArrow from "../../../assets/arrow-down.svg?react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@controlkit/ui"; + +import ArrowCheckCircleActiveSVG from "../../../assets/enabled-arrow-circle-active.svg?react"; +// import ArrowCheckCircleHoverSVG from "../../../assets/enabled-arrow-circle-hover.svg?react"; const styles = stylex.create({ wrapper: { display: "flex", - //backgroundColor: "pink", - //borderRadius: "0.5rem", - paddingLeft: "0.5rem", boxSizing: "border-box", gap: "0.5rem", - //maxWidth: "2rem", + padding: 0, + margin: 0, + width: "12rem", + height: "100%", }, - reset: { - border: "unset", - padding: "unset", - backgroundColor: "unset", - borderRadius: "unset", - outline: "unset", - boxShadow: "unset", + env_text_main: { + textOverflow: "ellipsis", + overflow: "hidden", + textWrap: "nowrap", + width: "calc(100% - 1rem)", }, - button: { - boxSizing: "border-box", - padding: "0.25rem", - borderRadius: "0.5rem", + trigger_wrapper: { display: "flex", alignItems: "center", - justifyContent: "center", - boxShadow: "none", + justifyItems: "center", + justifyContent: "space-between", + padding: "0 0.5rem", + cursor: "pointer", + width: "100%", + + transition: "background-color var(--transition-speed) ease", + + ":hover": { + backgroundColor: "var(--cds-gray-200)", + }, }, - pipe: { - backgroundColor: "#A6A6A6", - height: "1rem", - width: "1px", - position: "absolute", - boxSizing: "border-box", - left: 0, - marginLeft: "-0.25rem", + menu_content: { + width: "20rem", }, - menu: { - position: "absolute", - padding: "0.5rem", - backgroundColor: "#252525", - borderRadius: "0.5rem", - top: "100%", + no_env: { + color: "var(--text-sub)", + }, + + item: { display: "flex", - flexDirection: "column", - boxSizing: "border-box", - boxShadow: "0.5rem 0.5rem 0.25rem 0rem rgba(0,0,0,0.75)", - zIndex: 99, + gap: "0.5rem", + alignItems: "center", + justifyContent: "space-between", + width: "100%", + paddingRight: "0.25rem", }, - relativeParent: { - position: "relative", + borderActive: { + // borderLeft: "0.125rem solid blue", + borderLeft: "0.125rem solid transparent", }, }); + export default function EnvironmentDropdown() { - const [isMenuOpen, setIsMenuOpen] = useState(false); - // const modalRef = useOutsideClick(() => setIsMenuOpen(!isMenuOpen)); + const environments = useRequestStore((state) => state.environments); + + const enabledEnvironment = useRequestStore( + (state) => state.enabledEnvironment, + ); + const setEnabledEnvironmentDetails = useRequestStore( + (state) => state.setEnabledEnvironmentDetails, + ); + + const currentlyEnabledEnvironment = + enabledEnvironment === null + ? null + : environments.find((env) => env.id === enabledEnvironment?.env_id); return ( -
- - - - - {isMenuOpen && ( -
- menu -
- )} - - +
+ + +
+
+ {currentlyEnabledEnvironment === null ? ( + No Environment + ) : ( + {currentlyEnabledEnvironment?.name} + )} +
+ + +
+
+ + + { + setEnabledEnvironmentDetails(null); + }} + extend={[styles.no_env, styles.borderActive]} + > +
+ No Environment + + {enabledEnvironment === null && } +
+
+ + {environments.map((env) => { + return ( + { + setEnabledEnvironmentDetails({ + env_id: env.id, + stage_id: null, // env.stages[0].id, + }); + }} + extend={styles.borderActive} + > +
+ {env.name} + + {enabledEnvironment?.env_id === env.id && ( + + )} +
+
+ ); + })} +
+
); } diff --git a/src/commons/tabbar/index.tsx b/src/commons/tabbar/index.tsx index bca9781..538ee1d 100644 --- a/src/commons/tabbar/index.tsx +++ b/src/commons/tabbar/index.tsx @@ -2,14 +2,15 @@ import * as stylex from "@stylexjs/stylex"; import Tab from "./tab"; // , { type RequestType, type Status } -import { useRef -// , useState +import { + useRef, + // , useState } from "react"; import LeftArrow from "../../assets/arrow-left.svg?react"; import RightArrow from "../../assets/arrow-right.svg?react"; // import Plus from "../../assets/plus.svg?react"; // import DownArrow from "../../assets/arrow-down.svg?react"; -// import EnvironmentDropdown from "./environment-dropdown"; +import EnvironmentDropdown from "./environment-dropdown"; import { Button } from "@controlkit/ui"; import useRequestStore from "@src/stores/request_store"; @@ -27,6 +28,7 @@ const styles = stylex.create({ backgroundColor: "#141414", borderBottom: "0.0625rem solid var(--main-border-color)", height: "var(--tabbar-height)", + }, button: { @@ -82,7 +84,7 @@ function TabBar() {
@@ -118,6 +120,7 @@ function TabBar() { id={tab.id} status={tab.status} title={tab.title} + tabType={tab.type} requestType={tab.requestType} /> @@ -154,7 +157,15 @@ function TabBar() {
- {/* */} +
+ +
); } diff --git a/src/commons/tabbar/tab/index.tsx b/src/commons/tabbar/tab/index.tsx index 5a15004..9e33318 100644 --- a/src/commons/tabbar/tab/index.tsx +++ b/src/commons/tabbar/tab/index.tsx @@ -4,19 +4,25 @@ import { type T_Method, } from "@src/stores/request_store/request_slice"; import stylex from "@stylexjs/stylex"; -import { useState -// , type MouseEventHandler +import { + useState, + // , type MouseEventHandler } from "react"; // import { useHover } from "../../../hooks/use-hover"; import CloseX from "../../../assets/mdi_close.svg?react"; import { Button } from "@controlkit/ui"; -import { E_TabStatus } from "@src/stores/request_store/tabbar_slice"; +import { E_TabStatus, E_TabType } from "@src/stores/request_store/tabbar_slice"; import useRequestStore from "@src/stores/request_store"; +import { useNavigate } from "react-router"; +import type { T_ActiveEnvironment } from "@src/stores/request_store/environments_slice"; +import EnvironmentSVG from "../../../assets/environment.svg?react"; +import { E_SidebarSection } from "@src/stores/request_store/sidebar_slice"; interface I_TabProps { id: string; status: Status; title: string; + tabType: E_TabType; requestType: string; } @@ -28,10 +34,10 @@ export type RequestType = "GET" | "POST" | "PUT"; const styles = stylex.create({ tab: { display: "grid", - gridTemplateColumns: "3rem 1fr 1rem", + gridTemplateColumns: "auto 1fr 1rem", alignItems: "center", - gap: "0.5rem", + gap: "1rem", padding: "0rem 0.5rem", boxSizing: "border-box", @@ -104,9 +110,22 @@ const styles = stylex.create({ backgroundColor: "#28292A", }, }, + + environment_icon: { + width: "1rem", + height: "1.125rem", + }, }); -export default function Tab({ id, status, title, requestType }: I_TabProps) { +export default function Tab({ + id, + status, + title, + tabType, + requestType, +}: I_TabProps) { + const navigate = useNavigate(); + const activeTab = useRequestStore((state) => state.activeTab); const setActiveTab = useRequestStore((state) => state.setActiveTab); @@ -152,15 +171,95 @@ export default function Tab({ id, status, title, requestType }: I_TabProps) { const [isHovering, setIsHovering] = useState(false); + const setActiveEnvironmentDetails = useRequestStore( + (state) => state.setActiveEnvironmentDetails, + ); + const getActiveEnvironmentDetails = useRequestStore( + (state) => state.getActiveEnvironmentDetails, + ); + const setActiveEnvironment = useRequestStore( + (state) => state.setActiveEnvironment, + ); + // const getActiveEnvironmentInEnvironments = useRequestStore( + // (state) => state.getActiveEnvironmentInEnvironments, + // ); + // const setTabData = useRequestStore((state) => state.setTabData); + + const getEnvironmentById = useRequestStore( + (state) => state.getEnvironmentById, + ); + + const setCurrentSidebarTab = useRequestStore((state) => state.setCurrentSidebarTab); + return (
) => { setActiveTab(id); - const tabs = [ ...getTabs() ]; + const tabs = [...getTabs()]; const tab = tabs.find((tab) => tab.id === id); const data = tab.data; + // TODO: create separate comps for each tab type instead + if (tab.type === E_TabType.ENVIRONMENT) { + navigate(`/environment/${id}`); + + const aed = getActiveEnvironmentDetails(); + if (aed === null) { + const aed_c: T_ActiveEnvironment = { + env_id: id, + stage_id: null, + }; + setActiveEnvironmentDetails(aed_c); + + const target_env = getEnvironmentById(id); + + if (target_env) { + setActiveEnvironment(target_env); + } + } else { + aed.env_id = id; + setActiveEnvironmentDetails(aed); + + if (tab) { + if (Object.keys(tab.data).length === 0) { + const target_env = getEnvironmentById(id); + if (target_env) { + setActiveEnvironment(target_env); + } + } else { + setActiveEnvironment(tab.data); + } + } else { + const target_env = getEnvironmentById(id); + + if (target_env) { + setActiveEnvironment(target_env); + } + } + } + + // const activeEnvironment = getActiveEnvironmentInEnvironments(); + // if (activeEnvironment) { + // data = structuredClone(activeEnvironment); + // // setActiveEnvironment(activeEnvironment); + // } + + // setActiveEnvironment(data); + + // setTabData(id, activeEnvironment); + + // setActiveEnvironment(getActiveEnvironmentInEnvironments()); + + setActiveTab(id); + + setCurrentSidebarTab(E_SidebarSection.ENVIRONMENT); + + return; + } + + navigate(`/http_request/${id}`); + let method: T_Method = methods[0]; if (typeof data.method === "string") { method = methods.find((m) => m.value === data.method) ?? methods[0]; @@ -183,26 +282,43 @@ export default function Tab({ id, status, title, requestType }: I_TabProps) { data.response_headers, ); - for(const tab of tabs) { - if(tab.status === E_TabStatus.SAVED) { + // TODO: remove saved state doesn't add anything and makes a lot of UI require a lot of extra logic + for (const tab of tabs) { + if (tab.status === E_TabStatus.SAVED) { tab.status = E_TabStatus.NONE; } } + setCurrentSidebarTab(E_SidebarSection.COLLECTIONS); + setTabs(tabs); }} {...stylex.props(styles.tab, activeTab === id && styles.active)} onMouseEnter={() => setIsHovering(true)} onMouseLeave={() => setIsHovering(false)} > -

- {shortRequestString} -

+ {/* TODO: add more state details to activeTab */} + {tabType === E_TabType.ENVIRONMENT && ( +
+ +
+ )} + + {tabType === E_TabType.HTTP_REQUEST && ( +

+ {shortRequestString} +

+ )}
+ { + setEnvName(e.target.value); + + const ae = getActiveEnvironment(); + if (ae) { + ae.name = e.target.value; + // setTabTitle(ae.id, e.target.value); + setTabData(ae.id, ae); + setTabState(ae.id, E_TabStatus.MODIFIED); + } + }} + onKeyDown={(e: React.KeyboardEvent) => { + if (e.key === "Enter") { + // const ns = structuredClone(getCollection()); + // updateTargetIfExists(ns, getId(), "name", name); + // setCollection(ns); + // (e.target as HTMLInputElement).blur(); + } + }} + /> + + {/*
+
+ + +

|

+ + +
+ + + + + + +
*/} +
+ ); +}; + +export interface I_Row { + key?: string; + value?: string; + description?: string; +} + +export default function EnvironmentsPage() { + const activeEnvironment = useRequestStore((state) => state.activeEnvironment); + const aeRef = useRef(activeEnvironment); + + const addVariableToActiveEnvironment = useRequestStore( + (state) => state.addVariableToActiveEnvironment, + ); + + const getActiveEnvironment = useRequestStore( + (state) => state.getActiveEnvironment, + ); + const setTabData = useRequestStore((state) => state.setTabData); + + const setTabState = useRequestStore((state) => state.setTabState); + + const saveActiveEnvironmentAndUpdateEnvironmentsList = useRequestStore( + (state) => state.saveActiveEnvironmentAndUpdateEnvironmentsList, + ); + + useEffect(() => { + aeRef.current = activeEnvironment; + }, [activeEnvironment]); + + useEffect(() => { + SetupShortcuts(); + + return () => { + CleanUpShortcuts(); + }; + }, []); + + async function SetupShortcuts() { + // console.log("here"); + + addEventListener("keydown", SaveEnvironment); + } + + async function CleanUpShortcuts() { + removeEventListener("keydown", SaveEnvironment); + } + + function SaveEnvironment(e: KeyboardEvent) { + if (e.ctrlKey && e.key === "s") { + e.preventDefault(); + if (aeRef.current) { + saveActiveEnvironmentAndUpdateEnvironmentsList(); + // SyncHTTPRequestStateToSidebar(); + setTabState(aeRef.current.id, E_TabStatus.NONE); + } + } + } + + return ( +
+
+
+ +
+ +
+
+
+ {/* */} + {/* */} +
+ +
+

Variable

+
+ + {/*
+

Type

+
*/} + +
+

Initial Value

+
+ +
+

Current Value

+
+
+ + {activeEnvironment?.variables.map((row: T_ManagedVariable) => { + return ( + + ); + })} + +
+ + +
+
+
+
+ ); +} diff --git a/src/pages/environments/params-table-row.tsx b/src/pages/environments/params-table-row.tsx new file mode 100644 index 0000000..d8b2585 --- /dev/null +++ b/src/pages/environments/params-table-row.tsx @@ -0,0 +1,341 @@ +import stylex from "@stylexjs/stylex"; +import { useState } from "react"; +// import DragHandle from "../../../../../assets/drag-handle.svg?react"; +// import { useHover } from "../../../../../hooks/use-hover"; +// import Trashbin from "../../../../../assets/trashbin.svg?react"; +import { Checkbox } from "@controlkit/ui"; + +import type { ReactNode } from "react"; +import type { T_ManagedVariable } from "@src/stores/request_store/environments_slice"; +import useRequestStore from "@src/stores/request_store"; +import { E_TabStatus } from "@src/stores/request_store/tabbar_slice"; +// import DownArrow from "../../../../../assets/arrow-down.svg?react"; +// import { useOutsideClick } from "../../../../../hooks/use-outside-click"; + +// TODO: need to parse request contents using environment variable names + +const styles = stylex.create({ + table: { + width: "100%", + height: "100%", + + //backgroundColor: "gray", + }, + + headerRow: { + display: "grid", + gridTemplateColumns: "3rem 1fr 1fr 1fr", + alignItems: "center", + border: "0.0625rem solid #292929", + borderTop: "unset", + position: "relative", + }, + + header: { + fontSize: "0.75rem", + color: "var(--table-header)", + padding: 0, + margin: 0, + }, + + cell: { + padding: "0.25rem 0.5rem", + display: "flex", + alignItems: "center", + flexGrow: 1, + }, + borderRightReset: { + borderRight: "unset", + }, + borderRight: { + borderRight: "0.0625rem solid #292929", + }, + + dragHandle: { + cursor: "pointer", + padding: "0.25rem 0.5rem", + backgroundColor: { + ":hover": "#292929", + }, + }, + + input: { + margin: "unset", + padding: "0.25rem", + backgroundColor: "transparent", + boxShadow: "unset", + border: "unset", + outline: "unset", + fontSize: "0.75rem", + width: "100%", + color: "var(--color-text)", + }, + + trashbin: { + cursor: "pointer", + padding: "0.25rem", + backgroundColor: { + ":hover": "#292929", + }, + position: "absolute", + right: "0", + marginRight: "0.5rem", + }, + hidden: { + opacity: 0, + pointerEvents: "none", + }, + + shown: { + opacity: 1, + pointerEvents: "all", + }, + + // button: { + // backgroundColor: "transparent", + // ":hover": { + // backgroundColor: "var(--button-hover-color)", + // }, + // alignItems: "start", + // display: "flex", + // boxShadow: "none", + // border: "none", + // }, + + parent: { + position: "relative", + width: "100%", + }, + + button: { + backgroundColor: "transparent", + cursor: "pointer", + gap: "0.5rem", + display: "flex", + alignItems: "center", + justifyContent: "space-between", + boxShadow: "none", + fontSize: "0.75rem", + padding: "0rem", + width: "100%", + border: "transparent", + //border: { + // ":hover": "transparent", + //}, + }, + + menu: { + position: "absolute", + //padding: "0.5rem", + backgroundColor: "#252525", + //borderRadius: "0.5rem", + width: "100%", + marginTop: "0.25rem", + display: "flex", + flexDirection: "column", + boxSizing: "border-box", + boxShadow: "0.5rem 0.5rem 0.25rem 0rem rgba(0,0,0,0.75)", + fontSize: "0.75rem", + zIndex: "1", + }, + + filled: { + backgroundColor: "#252525", + }, + + pipe: { + height: "1rem", + flexGrow: 1, + borderLeft: "0.0625rem solid #A6A6A6", + }, +}); + +interface I_TableRowProps { + envId: string; + vr: T_ManagedVariable; +} + +export default function TableRow(props: I_TableRowProps) { + // const [variable, setVariable] = useState(""); + // const [type, setType] = useState("default"); + // const [initialValue, setInitialValue] = useState(""); + // const [currentValue, setCurrentValue] = useState(""); + + // const [hoverRef, isHovering] = useHover(); + + const getActiveEnvironment = useRequestStore( + (state) => state.getActiveEnvironment, + ); + + const updateVariableFieldInActiveEnvironment = useRequestStore( + (state) => state.updateVariableFieldInActiveEnvironment, + ); + + const setTabState = useRequestStore((state) => state.setTabState); + + const setTabData = useRequestStore((state) => state.setTabData); + + return ( +
+
+ {/* */} + + { + updateVariableFieldInActiveEnvironment( + props.vr.id, + "enabled", + checked, + ); + + const ae = getActiveEnvironment(); + if (ae) { + setTabData(props.envId, ae); + } + + setTabState(props.envId, E_TabStatus.MODIFIED); + }} + /> +
+ +
+ { + updateVariableFieldInActiveEnvironment( + props.vr.id, + "key", + event.target.value, + ); + + const ae = getActiveEnvironment(); + if (ae) { + setTabData(props.envId, ae); + } + + setTabState(props.envId, E_TabStatus.MODIFIED); + }} + value={props.vr.key} + placeholder="Variable" + /> +
+ + {/*
+ + + + +
*/} + +
+ { + updateVariableFieldInActiveEnvironment( + props.vr.id, + "initial_value", + event.target.value, + ); + + const ae = getActiveEnvironment(); + if (ae) { + setTabData(props.envId, ae); + } + + setTabState(props.envId, E_TabStatus.MODIFIED); + }} + value={props.vr.initial_value} + placeholder="Initial Value" + /> +
+ +
+ { + updateVariableFieldInActiveEnvironment( + props.vr.id, + "current_value", + event.target.value, + ); + + const ae = getActiveEnvironment(); + if(ae) { + setTabData(props.envId, ae); + } + + setTabState(props.envId, E_TabStatus.MODIFIED); + }} + value={props.vr.current_value} + placeholder="Current Value" + /> +
+ + {/* */} +
+ ); +} + +// TODO: Check with aaron what he did with this stuff +interface I_TypeValueDropdownProps { + children?: ReactNode; + title: string; + variant?: "filled"; +} + +export function TypeValueDropdown({ + children, + title, + variant, +}: I_TypeValueDropdownProps) { + const [isMenuOpen, setIsMenuOpen] = useState(false); + // const modalRef = useOutsideClick(() => setIsMenuOpen(!isMenuOpen)); + + return ( +
+ + + {isMenuOpen && ( +
+ {children} +
+ )} +
+ ); +} diff --git a/src/pages/http_requests/request-row/index.tsx b/src/pages/http_requests/request-row/index.tsx index d52e227..3e7d107 100644 --- a/src/pages/http_requests/request-row/index.tsx +++ b/src/pages/http_requests/request-row/index.tsx @@ -10,11 +10,11 @@ import { RequestRowUrlInput } from "./components/request-row-url-input.tsx"; // import SaveSVG from "@assets/save.svg?react"; // import React from "react"; -// import { invoke } from "@tauri-apps/api/core"; -import { Button, Input, Label } from "@controlkit/ui"; +import { Input, Label } from "@controlkit/ui"; import useRequestStore from "@src/stores/request_store"; import { updateTargetIfExists } from "@src/stores/request_store/sidebar_slice.ts"; import { E_TabStatus } from "@src/stores/request_store/tabbar_slice.ts"; +import SendRequestBtn from "./send_request.tsx"; // import { useSetRecoilState } from 'recoil'; // import { HTTP_API_Response_Body_StateData } from "@store/http-api-request-and-response/response-body.ts"; @@ -94,7 +94,6 @@ interface RequestRowProps { // @ts-ignore export function RequestRow(props: RequestRowProps) { - const sendRequest = useRequestStore((state) => state.sendRequest); const name = useRequestStore((state) => state.name); const setName = useRequestStore((state) => state.setName); @@ -204,13 +203,7 @@ export function RequestRow(props: RequestRowProps) { - + {/* ( diff --git a/src/pages/http_requests/request-row/send_request.tsx b/src/pages/http_requests/request-row/send_request.tsx new file mode 100644 index 0000000..2293a28 --- /dev/null +++ b/src/pages/http_requests/request-row/send_request.tsx @@ -0,0 +1,156 @@ +import { Button } from "@controlkit/ui"; +import useRequestStore from "@src/stores/request_store"; +import type { T_Header } from "@src/stores/request_store/request_slice"; +import { invoke } from "@tauri-apps/api/core"; + +export default function SendRequestBtn() { + // const sendRequest = useRequestStore((state) => state.sendRequest); + + const getHTTPRequest = useRequestStore((state) => state.getHTTPRequest); + const getEnabledEnvironmentAndDetails = useRequestStore( + (state) => state.getEnabledEnvironmentAndDetails, + ); + + const setLoading = useRequestStore((state) => state.setLoading); + const setResponse = useRequestStore((state) => state.setResponse); + const setResponseHeaders = useRequestStore((state) => state.setResponseHeaders); + const setResponseCookies = useRequestStore((state) => state.setResponseCookies); + + function ReplaceManagedVariable(env: any, sub_target: any): string { + console.log(env.variables); + + let s = sub_target; + for(const variable of env.variables) { + console.log("VAR - ", s, variable.key, variable.initial_value, variable.current_value); + + if(variable.current_value !== "") { + if(variable.current_value === "NULL") { + s = s.replace(`{{${variable.key}}}`, "null"); + continue; + } + + s = s.replace(`{{${variable.key}}}`, variable.current_value); + } + + if(variable.initial_value !== "") { + if(variable.initial_value === "NULL") { + s = s.replace(`{{${variable.key}}}`, "null"); + continue; + } + + s = s.replace(`{{${variable.key}}}`, variable.initial_value); + } + + // console.log(s.replace(`{{${variable.key}}}`, variable.value)); + // s = s.replace(`{{${variable.key}}}`, variable.value); + } + + return s; + } + + function AttemptToSendHTTPRequest() { + setLoading(true); + + const { url, method, autoHeaders, headers, body, cookies } = + getHTTPRequest(); + + // const method = get().method; + // const s = get(); + // const { url, body, cookies, autoHeaders, headers } = get(); + + // set({ loading: true, error: null }); + + console.group("AttemptToSendHTTPRequest"); + console.log("METHOD - ", method); + console.log("URL - ", url); + console.log("AUTO HEADERS - ", autoHeaders); + console.log("HEADERS - ", headers); + console.log("BODY - ", body); + console.log("COOKIES - ", cookies); + + // TODO: verify data before invoking + // console.log(url); + const trimmedUrl = url.trim(); + if (trimmedUrl.length === 0) { + // TODO: error state ui + // set({ error: "URL cannot be empty", loading: false }); + return; + } + + const env = getEnabledEnvironmentAndDetails(); + + console.log("ENV - ", env); + + // url + // body + // headers + + // get().environments(); + + // console.log(trimmedUrl); + + console.log("SUBBED"); + + let subbed_url = trimmedUrl; + let subbed_body = body; + let subbed_headers = structuredClone(headers); + + subbed_url = ReplaceManagedVariable(env, subbed_url); + subbed_body = ReplaceManagedVariable(env, subbed_body); + for(let idx = 0; idx < subbed_headers.length; ++idx) { + subbed_headers[idx].value = ReplaceManagedVariable(env, subbed_headers[idx].value); + } + + const filtered_headers = subbed_headers + .filter((h: T_Header) => h.enabled) + .map((h: T_Header) => `${h.key}: ${h.value}`) + .join(", "); + + console.log("METHOD - ", method); + console.log("URL - ", subbed_url); + console.log("AUTO HEADERS - ", autoHeaders); + console.log("HEADERS - ", subbed_headers); + console.log("FILTERED HEADERS - ", filtered_headers); + console.log("BODY - ", subbed_body); + console.log("COOKIES - ", cookies); + + invoke("http_request", { + method: method.value, + headers: filtered_headers, + url: subbed_url, + body: subbed_body, + cookies: "", + }) + // biome-ignore lint/suspicious/noExplicitAny: + .then((message: any) => { + console.log(message); + + setResponse(message.response_data_string); + setResponseHeaders(message.response_headers); + setResponseCookies(message.response_cookies); + setLoading(false); + + // setResponse(message.response_data_string); + // set_HTTP_API_Response_Body(message.response_data_string); + // set_HTTP_API_Response_Headers(message.response_headers); + }) + .catch((error_message) => { + console.error(error_message); + + setLoading(false); + }); + + console.groupEnd(); + } + + return ( + + ); +} diff --git a/src/pages/http_requests/response/index.tsx b/src/pages/http_requests/response/index.tsx index c8c6306..7b26f66 100644 --- a/src/pages/http_requests/response/index.tsx +++ b/src/pages/http_requests/response/index.tsx @@ -171,7 +171,7 @@ function ResponseSection() { return; } - if (size > body.clientHeight - 38 - 20) { + if (size > body.clientHeight - 38 - 20 - 30) { // navbar - tab bar - borders return; } @@ -195,7 +195,11 @@ function ResponseSection() { // children={undefined} hideRightSegment={false} > -
+
{response === null ? ( <> {no_res_tabs.map((tab, index) => { diff --git a/src/stores/request_store/environments_slice.ts b/src/stores/request_store/environments_slice.ts new file mode 100644 index 0000000..e37ddb9 --- /dev/null +++ b/src/stores/request_store/environments_slice.ts @@ -0,0 +1,396 @@ +import type { StateCreator } from "zustand"; +// TODO: replace with uuid v7 when possible +import { v4 as uuidv4 } from "uuid"; + +export type T_ActiveEnvironmentSliceItem = + | "GLOBAL" + | "VAULT" + | "ENVIRONMENT" + | null; + +export type T_ActiveEnvironment = { + env_id: string | null; + stage_id: string | null; +}; + +export type T_ManagedVariable = { + id: string; + key: string; + description: string; + enabled: boolean; + secret: boolean; + initial_value: string; + current_value: string; +}; + +export type T_EnvironmentStageVariable = { + id: string; + value: string; +}; + +export type T_EnvironmentStage = { + id: string; + env_id: string; + name: string; + description: string; + + variables: T_EnvironmentStageVariable[]; +}; + +export type T_Environment = { + id: string; + name: string; + + variables: T_ManagedVariable[]; + stages: T_EnvironmentStage[]; +}; + +export interface EnvironmentsSlice { + activeEnvironmentSliceItem: T_ActiveEnvironmentSliceItem; + setActiveEnvironmentSliceItem: (item: T_ActiveEnvironmentSliceItem) => void; + getActiveEnvironmentSliceItem: () => T_ActiveEnvironmentSliceItem; + + enabledEnvironment: T_ActiveEnvironment | null; + setEnabledEnvironmentDetails: ( + environment_id: T_ActiveEnvironment | null, + ) => void; + getEnabledEnvironmentDetails: () => T_ActiveEnvironment | null; + getEnabledEnvironmentAndDetails: () => T_ActiveEnvironment & T_Environment | null; + + // TODO: rename this to something better emabled is the environment to pull variables from + // active is for the tabs sidebar and active page edit + activeEnvironmentDetails: T_ActiveEnvironment | null; + setActiveEnvironmentDetails: ( + environment_id: T_ActiveEnvironment | null, + ) => void; + getActiveEnvironmentDetails: () => T_ActiveEnvironment | null; + + activeEnvironment: T_Environment | null; + setActiveEnvironment: (environment_id: T_Environment) => void; + getActiveEnvironment: () => T_Environment | null; + setActiveEnvironmentInEnvironments: (environment_id: T_Environment) => void; + getActiveEnvironmentInEnvironments: () => T_Environment | null; + addVariableToActiveEnvironment: () => void; + updateVariableFieldInActiveEnvironment: ( + variable_id: string, + field: string, + value: string | boolean, + ) => void; + saveActiveEnvironmentAndUpdateEnvironmentsList: () => void; + + // HELPERS + + // TODO: separate out different environments data object will certainly grow too large + // TODO: use immer for state update slices + environments: T_Environment[]; + setEnvironments: (collection: T_Environment[]) => void; + getEnvironments: () => T_Environment[]; + addEmptyDefaultEnvironment: () => void; + + getEnvironmentById: (id: string) => T_Environment | null; + + globals: T_ManagedVariable[]; + setGlobals: (collection: T_ManagedVariable[]) => void; + getGlobals: () => T_ManagedVariable[]; + + vault: T_ManagedVariable[]; + setVault: (vault_items: T_ManagedVariable[]) => void; + getVault: () => T_ManagedVariable[]; + + vaultSecret: string | null; + setVaultSecret: (secret: string | null) => void; + getVaultSecret: () => string | null; +} + +export const createEnvironmentsSlice: StateCreator< + EnvironmentsSlice, + [], + [], + EnvironmentsSlice +> = (set, get) => ({ + activeEnvironmentSliceItem: null, + setActiveEnvironmentSliceItem: (item: T_ActiveEnvironmentSliceItem) => + set({ activeEnvironmentSliceItem: item }), + getActiveEnvironmentSliceItem: () => get().activeEnvironmentSliceItem, + + enabledEnvironment: null, + setEnabledEnvironmentDetails: ( + environment_details: T_ActiveEnvironment | null, + ) => { + set({ enabledEnvironment: environment_details }); + }, + getEnabledEnvironmentDetails: () => get().enabledEnvironment, + getEnabledEnvironmentAndDetails: () => { + const ee = get().enabledEnvironment; + if(ee === null) return null; + + const env = get().environments.find( + (env) => env.id === get().enabledEnvironment?.env_id, + ); + if(env == null) return null; + + return { + ...ee, + ...env, + }; + }, + + activeEnvironmentDetails: null, + setActiveEnvironmentDetails: (environment_id: T_ActiveEnvironment | null) => + set({ activeEnvironmentDetails: environment_id }), + getActiveEnvironmentDetails: () => get().activeEnvironmentDetails, + + activeEnvironment: null, + setActiveEnvironment: (environment: T_Environment) => { + set({ + activeEnvironment: environment, + }); + }, + getActiveEnvironment: () => get().activeEnvironment, + + setActiveEnvironmentInEnvironments: (environment: T_Environment) => { + const envs = structuredClone(get().environments); + + const env_index = envs.findIndex((env) => env.id === environment.id); + if (env_index) { + envs[env_index] = structuredClone(environment); + + set({ + environments: envs, + }); + } + }, + getActiveEnvironmentInEnvironments: () => { + return ( + get().environments.find( + (env) => env.id === get().activeEnvironmentDetails?.env_id, + ) || null + ); + }, + addVariableToActiveEnvironment: () => { + const aenv = get().activeEnvironment; + + if (aenv) { + const managed_var_empty: T_ManagedVariable = { + id: uuidv4(), + key: "", + description: "", + enabled: true, + secret: false, + initial_value: "", + current_value: "", + }; + + aenv.variables.push(managed_var_empty); + + set({ + activeEnvironment: structuredClone(aenv), + }); + } + }, + updateVariableFieldInActiveEnvironment: ( + variable_id: string, + field: string, + value: string | boolean, + ) => { + const aenv = get().activeEnvironment; + + if (aenv) { + const var_index = aenv.variables.findIndex((v) => v.id === variable_id); + if (var_index !== -1) { + // @ts-ignore + aenv.variables[var_index][field] = value; + + set({ + activeEnvironment: structuredClone(aenv), + }); + } + } + }, + saveActiveEnvironmentAndUpdateEnvironmentsList: () => { + const aenv = get().activeEnvironment; + + if (aenv) { + const envs = structuredClone(get().environments); + + const env_index = envs.findIndex((env) => env.id === aenv.id); + + if (env_index !== -1) { + envs[env_index] = structuredClone(aenv); + + set({ + environments: envs, + }); + } + } + }, + + environments: [], // environment_test_data, + setEnvironments: (environments) => set({ environments }), + getEnvironments: () => get().environments, + addEmptyDefaultEnvironment: () => { + const envs = structuredClone(get().environments); + + const new_env: T_Environment = { + id: uuidv4(), + name: "New Environment", + variables: [], + stages: [], + }; + + envs.push(new_env); + + set({ + environments: envs, + }); + }, + + getEnvironmentById: (id: string) => { + return get().environments.find((env) => env.id === id) || null; + }, + + globals: [], // globals_test_data, + setGlobals: (globals) => set({ globals }), + getGlobals: () => get().globals, + + // TODO: vault and secret mangement - stronghold or other secure storage requied for secret - prereq + vault: [], + setVault: (vault_items) => set({ vault: vault_items }), + getVault: () => get().vault, + + vaultSecret: null, + setVaultSecret: (secret) => set({ vaultSecret: secret }), + getVaultSecret: () => get().vaultSecret, +}); + +/* +const env_id = uuidv4(); +const v1 = uuidv4(); +const v2 = uuidv4(); +const v3 = uuidv4(); + +const _globals_test_data: T_ManagedVariable[] = [ + { + id: v1, + key: "access_token", + description: "jwt access token", + enabled: true, + secret: false, + initial_value: "default value", + current_value: "", + }, + + { + id: v2, + key: "refresh_token", + description: "jwt refresh token", + // TODO: if a disabled variable is used in a request, show user a prompt warning with the specific variable. + enabled: false, + secret: false, + // TODO: UI - if a stage active, dim none active stage and default values + initial_value: "default value", + current_value: "", + }, + + { + id: v3, + key: "secret_var", + description: "hidden secret for UI testing", + enabled: true, + secret: true, + initial_value: "default value", + current_value: "", + }, +]; + +const _environment_test_data: T_Environment[] = [ + { + id: env_id, + name: "1 - New Environment", + + variables: [ + { + id: v1, + key: "access_token", + description: "jwt access token", + enabled: true, + secret: false, + initial_value: "default value", + current_value: "", + }, + + { + id: v2, + key: "refresh_token", + description: "jwt refresh token", + // TODO: if a disabled variable is used in a request, show user a prompt warning with the specific variable. + enabled: false, + secret: false, + // TODO: UI - if a stage active, dim none active stage and default values + initial_value: "default value", + current_value: "", + }, + + { + id: v3, + key: "secret_var", + description: "hidden secret for UI testing", + enabled: true, + secret: true, + initial_value: "default value", + current_value: "", + }, + ], + + stages: [ + { + id: uuidv4(), + env_id: env_id, + name: "dev", + description: "development stage", + + variables: [ + { + id: v1, + value: "access", + }, + + { + id: v2, + value: "refresh", + }, + + { + id: v3, + value: "secret value", + }, + ], + }, + + { + id: uuidv4(), + env_id: env_id, + name: "prod", + description: "live / production", + + variables: [ + { + id: v1, + value: "prod-access", + }, + + { + id: v2, + value: "prod-refresh", + }, + + { + id: v3, + value: "prod secret value", + }, + ], + }, + ], + }, +]; +*/ \ No newline at end of file diff --git a/src/stores/request_store/index.ts b/src/stores/request_store/index.ts index dbfb944..7519f0c 100644 --- a/src/stores/request_store/index.ts +++ b/src/stores/request_store/index.ts @@ -2,11 +2,13 @@ import { create } from "zustand"; import { createRequestSlice, type RequestSlice } from "./request_slice"; import { createSidebarSlice, type SidebarSlice } from "./sidebar_slice"; import { createTabbarSlice, type TabbarSlice } from "./tabbar_slice"; +import { createEnvironmentsSlice, type EnvironmentsSlice } from "./environments_slice"; -const useRequestStore = create()((...a) => ({ +const useRequestStore = create()((...a) => ({ ...createRequestSlice(...a), ...createSidebarSlice(...a), ...createTabbarSlice(...a), + ...createEnvironmentsSlice(...a), })); export default useRequestStore; diff --git a/src/stores/request_store/request_slice.ts b/src/stores/request_store/request_slice.ts index 0ea1a70..d064727 100644 --- a/src/stores/request_store/request_slice.ts +++ b/src/stores/request_store/request_slice.ts @@ -192,12 +192,18 @@ export interface RequestSlice { setCookies: (cookies: Record) => void; response: any; + setResponse: (response: any) => void; response_headers: any; + setResponseHeaders: (response: any) => void; response_cookies: any; + setResponseCookies: (response: any) => void; loading: boolean; + setLoading: (arg: boolean) => void; error: string | null; sendRequest: () => Promise; + getHTTPRequest: () => any; + setRequestParameters: ( id: string, name: string, @@ -322,10 +328,22 @@ export const createRequestSlice: StateCreator< setCookies: (cookies) => set({ cookies }), response: null, + setResponse: (response: any) => { + set({ response: response }); + }, response_headers: null, + setResponseHeaders: (headers: any) => { + set({ response_headers: headers }); + }, response_cookies: null, + setResponseCookies: (cookies: any) => { + set({ response_cookies: cookies }); + }, loading: false, + setLoading: (arg: boolean) => set({ loading: arg }), error: null, + + // TODO: old clean sendRequest: async () => { set({ loading: true }); @@ -350,8 +368,12 @@ export const createRequestSlice: StateCreator< // set({ error: "URL cannot be empty", loading: false }); return; } - // console.log(trimmedUrl); - // return; + + // url + // body + // headers + + console.log(trimmedUrl); invoke("http_request", { method: method.value, @@ -397,6 +419,17 @@ export const createRequestSlice: StateCreator< // } }, + getHTTPRequest: () => { + return { + url: get().url, + method: get().method, + autoHeaders: get().autoHeaders, + headers: get().headers, + body: get().body, + cookies: get().cookies, + }; + }, + setRequestParameters: ( id: string, name: string, diff --git a/src/stores/request_store/sidebar_slice.ts b/src/stores/request_store/sidebar_slice.ts index 2399846..36253f6 100644 --- a/src/stores/request_store/sidebar_slice.ts +++ b/src/stores/request_store/sidebar_slice.ts @@ -2,7 +2,21 @@ import type { StateCreator } from "zustand"; import { v4 as uuidv4 } from "uuid"; import type { T_Header, T_Method } from "./request_slice"; +export enum E_SidebarSection { + HOME = "HOME", + COLLECTIONS = "COLLECTIONS", + CONNECTED = "CONNECTED", + DB = "DB", + ENVIRONMENT = "ENVIRONMENT", + HISTORY = "HISTORY", + NOTE = "NOTE", + TREND = "TREND", +} + export interface SidebarSlice { + currentSidebarTab: E_SidebarSection | null; + setCurrentSidebarTab: (tab: E_SidebarSection | null) => void; + // biome-ignore lint/suspicious/noExplicitAny: collection: any[]; // biome-ignore lint/suspicious/noExplicitAny: @@ -22,6 +36,11 @@ export const createSidebarSlice: StateCreator< [], SidebarSlice > = (set, get) => ({ + currentSidebarTab: E_SidebarSection.COLLECTIONS, + setCurrentSidebarTab: (tab: E_SidebarSection | null) => { + set({ currentSidebarTab: tab }); + }, + collection: [], // test_data, setCollection: (collection) => set({ collection }), getCollection: () => get().collection, @@ -150,7 +169,12 @@ function addToTargetIfExists(nc: any, id: string, value: any) { } } -export function updateTargetIfExists(nc: any, id: string, field: string, value: any) { +export function updateTargetIfExists( + nc: any, + id: string, + field: string, + value: any, +) { for (let i = 0; i < nc.length; ++i) { // console.log("NC", nc[i].id); diff --git a/src/stores/request_store/tabbar_slice.ts b/src/stores/request_store/tabbar_slice.ts index 00ed280..dcb5fc6 100644 --- a/src/stores/request_store/tabbar_slice.ts +++ b/src/stores/request_store/tabbar_slice.ts @@ -13,6 +13,7 @@ export type T_Tab = { export enum E_TabType { HTTP_REQUEST = "HTTP_REQUEST", + ENVIRONMENT = "ENVIRONMENT", }; export enum E_TabStatus { @@ -28,12 +29,18 @@ export interface TabbarSlice { setTabs: (collection: any[]) => void; getTabs: () => any[]; + getActiveTab: () => T_Tab | null; + activeTab: string | null; setActiveTab: (tab_id: string | null) => void; + setTabTitle: (tab_id: string, title: string) => void; + setTabState: (tab_id: string, state: E_TabStatus) => void; setTabDataField: (tab_id: string, field: string, value: any) => void; + + setTabData: (tab_id: string, data: any) => void; } export const createTabbarSlice: StateCreator< @@ -49,6 +56,24 @@ export const createTabbarSlice: StateCreator< activeTab: null, setActiveTab: (tab_id) => set({ activeTab: tab_id }), + getActiveTab: () => { + const tab_id = get().activeTab; + const tabs = get().tabs; + + return tabs.find((tab) => tab.id === tab_id) ?? null; + }, + + setTabTitle: (tab_id: string, title: string) => { + const tabs = [ ...get().tabs ]; + const tab = tabs.find((tab) => tab.id === tab_id); + + if (tab) { + tab.title = title; + } + + set({ tabs }); + }, + setTabState: (tab_id: string, state: E_TabStatus) => { const tabs = [ ...get().tabs ]; const tab = tabs.find((tab) => tab.id === tab_id); @@ -70,6 +95,17 @@ export const createTabbarSlice: StateCreator< set({ tabs }); }, + + setTabData: (tab_id: string, data: any) => { + const tabs = [ ...get().tabs ]; + const tab = tabs.find((tab) => tab.id === tab_id); + + if (tab) { + tab.data = data; + } + + set({ tabs }); + }, }); export const test_data: T_Tab[] = [