pagination and context with store
This commit is contained in:
commit
1081ace70f
21 changed files with 319 additions and 207 deletions
|
|
@ -22,49 +22,53 @@ from langflow.services.store.service import StoreService
|
|||
router = APIRouter(prefix="/store", tags=["Components Store"])
|
||||
|
||||
|
||||
def get_user_store_api_key(user: User = Depends(auth_utils.get_current_active_user)):
|
||||
def get_user_store_api_key(
|
||||
user: User = Depends(auth_utils.get_current_active_user),
|
||||
settings_service=Depends(get_settings_service),
|
||||
):
|
||||
if not user.store_api_key:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="You must have a store API key set."
|
||||
)
|
||||
return user.store_api_key
|
||||
decrypted = auth_utils.decrypt_api_key(user.store_api_key, settings_service)
|
||||
return decrypted
|
||||
|
||||
|
||||
def get_optional_user_store_api_key(
|
||||
user: User = Depends(auth_utils.get_current_active_user),
|
||||
settings_service=Depends(get_settings_service),
|
||||
):
|
||||
return user.store_api_key
|
||||
if not user.store_api_key:
|
||||
return None
|
||||
decrypted = auth_utils.decrypt_api_key(user.store_api_key, settings_service)
|
||||
return decrypted
|
||||
|
||||
|
||||
@router.post("/components/", response_model=ComponentResponse, status_code=201)
|
||||
def create_component(
|
||||
component: StoreComponentCreate,
|
||||
store_service: StoreService = Depends(get_store_service),
|
||||
settings_service=Depends(get_settings_service),
|
||||
store_api_Key: str = Depends(get_user_store_api_key),
|
||||
):
|
||||
try:
|
||||
decrypted = auth_utils.decrypt_api_key(store_api_Key, settings_service)
|
||||
return store_service.upload(decrypted, component)
|
||||
return store_service.upload(store_api_Key, component)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc))
|
||||
|
||||
|
||||
@router.get("/components/", response_model=List[ListComponentResponse])
|
||||
def list_components(
|
||||
filter_by_user: bool = Query(False),
|
||||
page: int = 1,
|
||||
limit: int = 10,
|
||||
store_service: StoreService = Depends(get_store_service),
|
||||
store_api_Key: str = Depends(get_optional_user_store_api_key),
|
||||
settings_service=Depends(get_settings_service),
|
||||
):
|
||||
try:
|
||||
fields = ["id", "name", "description", "user_created.name", "is_component"]
|
||||
if store_api_Key:
|
||||
decrypted = auth_utils.decrypt_api_key(store_api_Key, settings_service)
|
||||
else:
|
||||
decrypted = None
|
||||
result = store_service.list_components(decrypted, page, limit, fields=fields)
|
||||
result = store_service.list_components(
|
||||
store_api_Key, page, limit, fields=fields, filter_by_user=filter_by_user
|
||||
)
|
||||
return result
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc))
|
||||
|
|
@ -73,16 +77,13 @@ def list_components(
|
|||
@router.get("/components/{component_id}", response_model=DownloadComponentResponse)
|
||||
def read_component(
|
||||
component_id: UUID,
|
||||
filter_by_user: bool = Query(False),
|
||||
store_service: StoreService = Depends(get_store_service),
|
||||
store_api_Key: str = Depends(get_user_store_api_key),
|
||||
settings_service=Depends(get_settings_service),
|
||||
):
|
||||
# If the component is from the store, we need to get it from the store
|
||||
|
||||
try:
|
||||
decrypted = auth_utils.decrypt_api_key(store_api_Key, settings_service)
|
||||
component = store_service.download(decrypted, component_id, filter_by_user)
|
||||
component = store_service.download(store_api_Key, component_id)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
|
||||
|
|
@ -102,6 +103,7 @@ async def search_endpoint(
|
|||
date_from: Optional[datetime] = Query(None),
|
||||
date_to: Optional[datetime] = Query(None),
|
||||
sort: Optional[List[str]] = Query(None),
|
||||
filter_by_user: bool = Query(False),
|
||||
fields: Optional[List[str]] = Query(None),
|
||||
store_service: "StoreService" = Depends(get_store_service),
|
||||
store_api_Key: str = Depends(get_optional_user_store_api_key),
|
||||
|
|
@ -118,6 +120,7 @@ async def search_endpoint(
|
|||
date_to=date_to,
|
||||
sort=sort,
|
||||
fields=fields,
|
||||
filter_by_user=filter_by_user,
|
||||
)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=500, detail=str(exc))
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ class StoreService(Service):
|
|||
date_to: Optional[datetime] = None,
|
||||
sort: Optional[List[str]] = ["-likes"],
|
||||
fields: Optional[List[str]] = None,
|
||||
filter_by_user: bool = False,
|
||||
) -> List[ComponentResponse]:
|
||||
# ?sort=sort,-date_created,author.name
|
||||
|
||||
|
|
@ -104,34 +105,6 @@ class StoreService(Service):
|
|||
if fields:
|
||||
params["fields"] = ",".join(fields)
|
||||
|
||||
results = self._get(self.components_url, api_key, params)
|
||||
return [ComponentResponse(**component) for component in results]
|
||||
|
||||
def list_components(
|
||||
self,
|
||||
api_key: str,
|
||||
page: int = 1,
|
||||
limit: int = 10,
|
||||
fields: Optional[List[str]] = None,
|
||||
) -> List[ListComponentResponse]:
|
||||
params = {"page": page, "limit": limit}
|
||||
# ?aggregate[count]=likes
|
||||
params["fields"] = (
|
||||
",".join(fields)
|
||||
if fields
|
||||
else ",".join(["id", "name", "description", "count(likes)", "is_component"])
|
||||
)
|
||||
|
||||
results = self._get(self.components_url, api_key, params)
|
||||
return [ListComponentResponse(**component) for component in results]
|
||||
|
||||
def download(
|
||||
self, api_key: str, component_id: str, filter_by_user: bool
|
||||
) -> DownloadComponentResponse:
|
||||
url = f"{self.components_url}/{component_id}"
|
||||
params = {
|
||||
"fields": ",".join(["id", "name", "description", "data", "is_component"])
|
||||
}
|
||||
if filter_by_user:
|
||||
params["deep"] = json.dumps(
|
||||
{
|
||||
|
|
@ -140,6 +113,52 @@ class StoreService(Service):
|
|||
}
|
||||
}
|
||||
)
|
||||
else:
|
||||
params["filter"] = json.dumps({"status": {"_eq": "public"}})
|
||||
|
||||
results = self._get(self.components_url, api_key, params)
|
||||
return [ComponentResponse(**component) for component in results]
|
||||
|
||||
def list_components(
|
||||
self,
|
||||
api_key: str,
|
||||
page: int = 1,
|
||||
limit: int = 15,
|
||||
fields: Optional[List[str]] = None,
|
||||
filter_by_user: bool = False,
|
||||
) -> List[ListComponentResponse]:
|
||||
params = {"page": page, "limit": limit}
|
||||
# ?aggregate[count]=likes
|
||||
params["fields"] = (
|
||||
",".join(fields)
|
||||
if fields
|
||||
else ",".join(["id", "name", "description", "count(likes)", "is_component"])
|
||||
)
|
||||
# Only public components or the ones created by the user
|
||||
# check for "public" or "Public"
|
||||
|
||||
if filter_by_user:
|
||||
params["deep"] = json.dumps(
|
||||
{
|
||||
"components": {
|
||||
"_filter": {"user_created": {"token": {"_eq": api_key}}}
|
||||
}
|
||||
}
|
||||
)
|
||||
else:
|
||||
params["filter"] = params["filter"] = json.dumps(
|
||||
{"status": {"_in": ["public", "Public"]}}
|
||||
)
|
||||
|
||||
results = self._get(self.components_url, api_key, params)
|
||||
return [ListComponentResponse(**component) for component in results]
|
||||
|
||||
def download(self, api_key: str, component_id: str) -> DownloadComponentResponse:
|
||||
url = f"{self.components_url}/{component_id}"
|
||||
params = {
|
||||
"fields": ",".join(["id", "name", "description", "data", "is_component"])
|
||||
}
|
||||
|
||||
component = self._get(url, api_key, params)
|
||||
self.call_webhook(api_key, self.webhook_url, component_id)
|
||||
|
||||
|
|
|
|||
36
src/frontend/package-lock.json
generated
36
src/frontend/package-lock.json
generated
|
|
@ -3796,9 +3796,9 @@
|
|||
"integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g=="
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.2.31",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.31.tgz",
|
||||
"integrity": "sha512-c2UnPv548q+5DFh03y8lEDeMfDwBn9G3dRwfkrxQMo/dOtRHUUO57k6pHvBIfH/VF4Nh+98mZ5aaSe+2echD5g==",
|
||||
"version": "18.2.32",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.32.tgz",
|
||||
"integrity": "sha512-F0FVIZQ1x5Gxy/VYJb7XcWvCcHR28Sjwt1dXLspdIatfPq1MVACfnBDwKe6ANLxQ64riIJooXClpUR6oxTiepg==",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
|
|
@ -4517,9 +4517,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001553",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001553.tgz",
|
||||
"integrity": "sha512-N0ttd6TrFfuqKNi+pMgWJTb9qrdJu4JSpgPFLe/lrD19ugC6fZgF0pUewRowDwzdDnb9V41mFcdlYgl/PyKf4A==",
|
||||
"version": "1.0.30001554",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001554.tgz",
|
||||
"integrity": "sha512-A2E3U//MBwbJVzebddm1YfNp7Nud5Ip+IPn4BozBmn4KqVX7AvluoIDFWjsv5OkGnKUXQVmMSoMKLa3ScCblcQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
|
@ -10220,9 +10220,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.4.tgz",
|
||||
"integrity": "sha512-JXZNOkggUAc9T5E7nCrimoXHcSf9h3NWFe5sh36CGD/3M5TRLuQeFnQoDsit2uVTqgoOZHLx5rTykLUu16vsMQ==",
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.5.tgz",
|
||||
"integrity": "sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==",
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
"arg": "^5.0.2",
|
||||
|
|
@ -13755,9 +13755,9 @@
|
|||
"integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g=="
|
||||
},
|
||||
"@types/react": {
|
||||
"version": "18.2.31",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.31.tgz",
|
||||
"integrity": "sha512-c2UnPv548q+5DFh03y8lEDeMfDwBn9G3dRwfkrxQMo/dOtRHUUO57k6pHvBIfH/VF4Nh+98mZ5aaSe+2echD5g==",
|
||||
"version": "18.2.32",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.32.tgz",
|
||||
"integrity": "sha512-F0FVIZQ1x5Gxy/VYJb7XcWvCcHR28Sjwt1dXLspdIatfPq1MVACfnBDwKe6ANLxQ64riIJooXClpUR6oxTiepg==",
|
||||
"requires": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
|
|
@ -14248,9 +14248,9 @@
|
|||
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001553",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001553.tgz",
|
||||
"integrity": "sha512-N0ttd6TrFfuqKNi+pMgWJTb9qrdJu4JSpgPFLe/lrD19ugC6fZgF0pUewRowDwzdDnb9V41mFcdlYgl/PyKf4A=="
|
||||
"version": "1.0.30001554",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001554.tgz",
|
||||
"integrity": "sha512-A2E3U//MBwbJVzebddm1YfNp7Nud5Ip+IPn4BozBmn4KqVX7AvluoIDFWjsv5OkGnKUXQVmMSoMKLa3ScCblcQ=="
|
||||
},
|
||||
"ccount": {
|
||||
"version": "2.0.1",
|
||||
|
|
@ -18070,9 +18070,9 @@
|
|||
"integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ=="
|
||||
},
|
||||
"tailwindcss": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.4.tgz",
|
||||
"integrity": "sha512-JXZNOkggUAc9T5E7nCrimoXHcSf9h3NWFe5sh36CGD/3M5TRLuQeFnQoDsit2uVTqgoOZHLx5rTykLUu16vsMQ==",
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.5.tgz",
|
||||
"integrity": "sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==",
|
||||
"requires": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
"arg": "^5.0.2",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export default function PaginatorComponent({
|
|||
rowsCount = [10, 20, 50, 100],
|
||||
totalRowsCount = 0,
|
||||
paginate,
|
||||
storeComponent = false,
|
||||
}: PaginatorComponentType) {
|
||||
const [size, setPageSize] = useState(pageSize);
|
||||
const [maxIndex, setMaxPageIndex] = useState(
|
||||
|
|
@ -30,7 +31,13 @@ export default function PaginatorComponent({
|
|||
<>
|
||||
<div className="flex items-center justify-between px-2">
|
||||
<div className="flex-1 text-sm text-muted-foreground"></div>
|
||||
<div className="flex items-center space-x-6 lg:space-x-8">
|
||||
<div
|
||||
className={
|
||||
storeComponent
|
||||
? "flex items-center lg:space-x-8 "
|
||||
: "flex items-center space-x-6 lg:space-x-8 "
|
||||
}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className="text-sm font-medium">Rows per page</p>
|
||||
<Select
|
||||
|
|
@ -54,7 +61,8 @@ export default function PaginatorComponent({
|
|||
</Select>
|
||||
</div>
|
||||
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
|
||||
Page {pageIndex} of {maxIndex}
|
||||
Page {pageIndex}
|
||||
{!storeComponent && <> of {maxIndex}</>}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export default function Header(): JSX.Element {
|
|||
|
||||
return (
|
||||
<div className="header-arrangement">
|
||||
<div className="header-start-display">
|
||||
<div className="header-start-display lg:w-[30%]">
|
||||
<Link to="/">
|
||||
<span className="ml-4 text-2xl">⛓️</span>
|
||||
</Link>
|
||||
|
|
@ -47,14 +47,19 @@ export default function Header(): JSX.Element {
|
|||
<Link to="/">
|
||||
<Button
|
||||
className="gap-2"
|
||||
variant={location.pathname === "/" ? "primary" : "secondary"}
|
||||
variant={
|
||||
location.pathname === "/flows" ||
|
||||
location.pathname === "/components"
|
||||
? "primary"
|
||||
: "secondary"
|
||||
}
|
||||
size="sm"
|
||||
>
|
||||
<IconComponent name="Home" className="h-4 w-4" />
|
||||
<div className="flex-1">{USER_PROJECTS_HEADER}</div>
|
||||
<div className="hidden flex-1 md:block">{USER_PROJECTS_HEADER}</div>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link to="/community">
|
||||
{/* <Link to="/community">
|
||||
<Button
|
||||
className="gap-2"
|
||||
variant={
|
||||
|
|
@ -65,7 +70,7 @@ export default function Header(): JSX.Element {
|
|||
<IconComponent name="Users2" className="h-4 w-4" />
|
||||
<div className="flex-1">Community Examples</div>
|
||||
</Button>
|
||||
</Link>
|
||||
</Link> */}
|
||||
{hasStore && (
|
||||
<Link to="/store">
|
||||
<Button
|
||||
|
|
@ -79,16 +84,16 @@ export default function Header(): JSX.Element {
|
|||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<div className="header-end-division">
|
||||
<div className="header-end-division lg:w-[30%]">
|
||||
<div className="header-end-display">
|
||||
<a
|
||||
href="https://github.com/logspace-ai/langflow"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="header-github-link"
|
||||
className="header-github-link gap-2"
|
||||
>
|
||||
<FaGithub className="mr-2 h-5 w-5" />
|
||||
Star
|
||||
<FaGithub className="h-5 w-5" />
|
||||
<div className="hidden lg:block">Star</div>
|
||||
<div className="header-github-display">{stars}</div>
|
||||
</a>
|
||||
<a
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import { createContext, useState } from "react";
|
||||
import { checkHasStore } from "../controllers/API";
|
||||
import { storeContextType } from "../types/contexts/store";
|
||||
import { FlowType } from "../types/flow";
|
||||
|
||||
//store context to share user components and update them
|
||||
const initialValue = {
|
||||
savedFlows: {},
|
||||
savedFlows: new Set<string>(),
|
||||
setSavedFlows: () => {},
|
||||
hasStore: false,
|
||||
setHasStore: () => {},
|
||||
|
|
@ -14,7 +13,8 @@ const initialValue = {
|
|||
export const StoreContext = createContext<storeContextType>(initialValue);
|
||||
|
||||
export function StoreProvider({ children }) {
|
||||
const [savedFlows, setSavedFlows] = useState<{ [key: string]: FlowType }>({});
|
||||
const [savedFlows, setSavedFlows] = useState<Set<string>>(new Set());
|
||||
|
||||
const [hasStore, setHasStore] = useState(false);
|
||||
|
||||
checkHasStore().then((res) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { AxiosError } from "axios";
|
||||
import _ from "lodash";
|
||||
import _, { cloneDeep } from "lodash";
|
||||
import {
|
||||
ReactNode,
|
||||
createContext,
|
||||
|
|
@ -40,7 +40,7 @@ import {
|
|||
updateTemplate,
|
||||
} from "../utils/reactflowUtils";
|
||||
import {
|
||||
IncrementObjectKey,
|
||||
createRandomKey,
|
||||
getRandomDescription,
|
||||
getRandomName,
|
||||
getSetFromObject,
|
||||
|
|
@ -80,7 +80,7 @@ const TabsContextInitialValue: TabsContextType = {
|
|||
selection: { nodes: any; edges: any },
|
||||
position: { x: number; y: number; paneX?: number; paneY?: number }
|
||||
) => {},
|
||||
saveComponent: (component: NodeDataType) => {},
|
||||
saveComponent: async (component: NodeDataType) => "",
|
||||
deleteComponent: (id: string, key: string) => {},
|
||||
};
|
||||
|
||||
|
|
@ -154,8 +154,14 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
return;
|
||||
}
|
||||
if (flow.data && flow.is_component) {
|
||||
storeComponents[(flow.data.nodes[0].data as NodeDataType).type] =
|
||||
_.cloneDeep((flow.data.nodes[0].data as NodeDataType).node!);
|
||||
(flow.data.nodes[0].data as NodeDataType).node!.display_name =
|
||||
flow.name;
|
||||
storeComponents[
|
||||
createRandomKey(
|
||||
(flow.data.nodes[0].data as NodeDataType).type,
|
||||
uid()
|
||||
)
|
||||
] = _.cloneDeep((flow.data.nodes[0].data as NodeDataType).node!);
|
||||
return;
|
||||
}
|
||||
if (!skipUpdate) processDataFromFlow(flow, false);
|
||||
|
|
@ -165,9 +171,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
});
|
||||
setData((prev) => {
|
||||
let newData = _.cloneDeep(prev);
|
||||
Object.keys(storeComponents).forEach((key) => {
|
||||
newData["custom_components"][key] = storeComponents[key];
|
||||
});
|
||||
|
||||
const customComponent = newData["custom_components"]["CustomComponent"];
|
||||
newData["custom_components"] = cloneDeep(storeComponents);
|
||||
newData["custom_components"]["CustomComponent"] = customComponent;
|
||||
return newData;
|
||||
});
|
||||
}
|
||||
|
|
@ -458,6 +465,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
id: source,
|
||||
});
|
||||
sourceHandleObject.id = source;
|
||||
|
||||
edge.data.sourceHandle = sourceHandleObject;
|
||||
const targetHandleObject: targetHandleType = scapeJSONParse(
|
||||
edge.targetHandle!
|
||||
|
|
@ -482,6 +490,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
sourceHandle,
|
||||
targetHandle,
|
||||
id,
|
||||
data: cloneDeep(edge.data),
|
||||
style: { stroke: "#555" },
|
||||
className:
|
||||
targetHandleObject.type === "Text"
|
||||
|
|
@ -538,7 +547,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
let data = flow?.data ? flow.data : null;
|
||||
if (data) {
|
||||
processFlowEdges(flow);
|
||||
processFlowNodes(flow);
|
||||
//prevent node update for now
|
||||
// processFlowNodes(flow);
|
||||
//add animation to text type edges
|
||||
updateEdges(data.edges);
|
||||
// updateNodes(data.nodes, data.edges);
|
||||
|
|
@ -605,7 +615,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
const addFlowToLocalState = (newFlow: FlowType) => {
|
||||
let newFlows: FlowType[] = [];
|
||||
setFlows((prevState) => {
|
||||
newFlows.concat(prevState);
|
||||
newFlows = newFlows.concat(prevState);
|
||||
newFlows.push(newFlow);
|
||||
return [...prevState, newFlow];
|
||||
});
|
||||
|
|
@ -671,12 +681,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
component.node!.official = false;
|
||||
let key = component.type;
|
||||
if (data["custom_components"][key] !== undefined) {
|
||||
let { newKey, increment } = IncrementObjectKey(
|
||||
data["custom_components"],
|
||||
key
|
||||
);
|
||||
key = newKey;
|
||||
component.type = newKey;
|
||||
let increment: number;
|
||||
component.type = createRandomKey(key, uid());
|
||||
let componentNodes: { [key: string]: APIClassType } = {};
|
||||
Object.keys(data["custom_components"]).forEach((key) => {
|
||||
componentNodes[key] = data["custom_components"][key];
|
||||
|
|
@ -697,7 +703,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
` (${increment})`;
|
||||
}
|
||||
}
|
||||
addFlow(true, createFlowComponent(component));
|
||||
return addFlow(true, createFlowComponent(component));
|
||||
}
|
||||
|
||||
function deleteComponent(id: string, key: string) {
|
||||
|
|
|
|||
|
|
@ -597,6 +597,20 @@ export async function getStoreComponents(page: number = 1, limit: number = 10) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function getStoreSavedComponents() {
|
||||
try {
|
||||
const res = await api.get(
|
||||
`${BASE_URL_API}store/components/?filter_by_user=true`
|
||||
);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function postStoreComponents(component: Component) {
|
||||
try {
|
||||
const res = await api.post(`${BASE_URL_API}store/components/`, component);
|
||||
|
|
@ -676,3 +690,15 @@ export async function checkHasStore() {
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getNumberOfComponents() {
|
||||
try {
|
||||
const res = await api.get(`${BASE_URL_API}store/components/count`);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useUpdateNodeInternals } from "reactflow";
|
||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||
import CodeAreaComponent from "../../components/codeAreaComponent";
|
||||
import DictComponent from "../../components/dictComponent";
|
||||
|
|
@ -64,6 +65,7 @@ const EditNodeModal = forwardRef(
|
|||
ref
|
||||
) => {
|
||||
const [modalOpen, setModalOpen] = useState(open ?? false);
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
|
||||
const myData = useRef(data);
|
||||
|
||||
|
|
@ -83,11 +85,14 @@ const EditNodeModal = forwardRef(
|
|||
const handleOnNewValue = (newValue: any, name) => {
|
||||
myData.current.node!.template[name].value = newValue;
|
||||
setDataValue(newValue);
|
||||
updateNodeInternals(data.id);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
myData.current = data; // reset data to what it is on node when opening modal
|
||||
onClose!(modalOpen);
|
||||
if (modalOpen) {
|
||||
myData.current = data; // reset data to what it is on node when opening modal
|
||||
onClose!(modalOpen);
|
||||
}
|
||||
}, [modalOpen]);
|
||||
|
||||
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
||||
|
|
|
|||
|
|
@ -310,7 +310,6 @@ export default function Page({
|
|||
event.dataTransfer.getData("nodedata")
|
||||
);
|
||||
|
||||
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
|
||||
// Calculate the position where the node should be created
|
||||
const position = reactFlowInstance!.project({
|
||||
x: event.clientX - reactflowBounds!.left,
|
||||
|
|
|
|||
|
|
@ -309,7 +309,12 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
>
|
||||
<div className="side-bar-components-gap">
|
||||
{Object.keys(dataFilter[SBSectionName])
|
||||
.sort(sensitiveSort)
|
||||
.sort((a, b) =>
|
||||
sensitiveSort(
|
||||
dataFilter[SBSectionName][a].display_name,
|
||||
dataFilter[SBSectionName][b].display_name
|
||||
)
|
||||
)
|
||||
.map((SBItemName: string, index) => (
|
||||
<ShadTooltip
|
||||
content={
|
||||
|
|
|
|||
|
|
@ -70,20 +70,21 @@ export default function NodeToolbarComponent({
|
|||
|
||||
function handleShareComponent() {
|
||||
const componentFlow = cloneDeep(data);
|
||||
saveFlowStore(createFlowComponent(componentFlow)).then(
|
||||
() => {
|
||||
saveComponent(componentFlow);
|
||||
setSuccessData({
|
||||
title: "Component shared successfully",
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
setErrorData({
|
||||
title: "Error sharing component",
|
||||
list: [err["response"]["data"]["detail"]],
|
||||
});
|
||||
}
|
||||
);
|
||||
saveComponent(componentFlow).then(() => {
|
||||
saveFlowStore(createFlowComponent(componentFlow)).then(
|
||||
(_) => {
|
||||
setSuccessData({
|
||||
title: "Component shared successfully",
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
setErrorData({
|
||||
title: "Error sharing component",
|
||||
list: [err["response"]["data"]["detail"]],
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
const handleSelectChange = (event) => {
|
||||
switch (event) {
|
||||
|
|
@ -98,7 +99,6 @@ export default function NodeToolbarComponent({
|
|||
downloadNode(createFlowComponent(cloneDeep(data)));
|
||||
break;
|
||||
case "Share":
|
||||
console.log("Share");
|
||||
setShowconfirmShare(true);
|
||||
break;
|
||||
case "SaveAll":
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@ export default function ComponentsComponent() {
|
|||
<SkeletonCardComponent />
|
||||
<SkeletonCardComponent />
|
||||
<SkeletonCardComponent />
|
||||
<SkeletonCardComponent />
|
||||
</>
|
||||
) : (
|
||||
flows
|
||||
|
|
|
|||
|
|
@ -94,7 +94,6 @@ export default function FlowsComponent() {
|
|||
<SkeletonCardComponent />
|
||||
<SkeletonCardComponent />
|
||||
<SkeletonCardComponent />
|
||||
<SkeletonCardComponent />
|
||||
</>
|
||||
) : (
|
||||
flows
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Link, ToyBrick } from "lucide-react";
|
||||
import { useContext, useRef, useState } from "react";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import IconComponent from "../../../components/genericIconComponent";
|
||||
import { Badge } from "../../../components/ui/badge";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
|
|
@ -10,6 +10,8 @@ import {
|
|||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../../../components/ui/card";
|
||||
import { alertContext } from "../../../contexts/alertContext";
|
||||
import { StoreContext } from "../../../contexts/storeContext";
|
||||
import { TabsContext } from "../../../contexts/tabsContext";
|
||||
import { getComponent, saveFlowStore } from "../../../controllers/API";
|
||||
import { FlowType } from "../../../types/flow";
|
||||
|
|
@ -17,12 +19,19 @@ import { FlowComponent } from "../../../types/store";
|
|||
import cloneFLowWithParent from "../../../utils/storeUtils";
|
||||
|
||||
export const MarketCardComponent = ({ data }: { data: FlowComponent }) => {
|
||||
const [added, setAdded] = useState(false);
|
||||
const { savedFlows } = useContext(StoreContext);
|
||||
const [added, setAdded] = useState(savedFlows.has(data.id) ? true : false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { addFlow } = useContext(TabsContext);
|
||||
const { setSuccessData } = useContext(alertContext);
|
||||
const flowData = useRef<FlowType>();
|
||||
|
||||
useEffect(() => {
|
||||
setAdded(savedFlows.has(data.id) ? true : false);
|
||||
}, [savedFlows]);
|
||||
|
||||
function handleAdd() {
|
||||
setLoading(true);
|
||||
getComponent(data.id).then(
|
||||
(res) => {
|
||||
console.log(res);
|
||||
|
|
@ -45,7 +54,19 @@ export const MarketCardComponent = ({ data }: { data: FlowComponent }) => {
|
|||
}
|
||||
|
||||
function handleInstall() {
|
||||
addFlow(true, flowData.current!);
|
||||
if (flowData.current) {
|
||||
addFlow(true, flowData.current!).then(() => {
|
||||
setSuccessData({ title: "Flow Installed" });
|
||||
});
|
||||
} else {
|
||||
getComponent(data.id).then((res) => {
|
||||
console.log(res);
|
||||
const newFLow = cloneFLowWithParent(res, res.id, data.is_component);
|
||||
flowData.current = newFLow;
|
||||
addFlow(true, newFLow);
|
||||
setSuccessData({ title: "Flow Installed" });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleFork(flowId: string, is_component: boolean) {
|
||||
|
|
@ -145,7 +166,6 @@ export const MarketCardComponent = ({ data }: { data: FlowComponent }) => {
|
|||
size="sm"
|
||||
className="whitespace-nowrap "
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
if (!added) {
|
||||
handleAdd();
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useContext, useEffect, useState } from "react";
|
|||
import PaginatorComponent from "../../components/PaginatorComponent";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import Header from "../../components/headerComponent";
|
||||
import { SkeletonCardComponent } from "../../components/skeletonCardComponent";
|
||||
import { Badge } from "../../components/ui/badge";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Input } from "../../components/ui/input";
|
||||
|
|
@ -16,53 +17,61 @@ import {
|
|||
} from "../../components/ui/select";
|
||||
import { Switch } from "../../components/ui/switch";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
import { StoreContext } from "../../contexts/storeContext";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { getStoreComponents, searchComponent } from "../../controllers/API";
|
||||
import {
|
||||
getStoreComponents,
|
||||
getStoreSavedComponents,
|
||||
searchComponent,
|
||||
} from "../../controllers/API";
|
||||
import StoreApiKeyModal from "../../modals/StoreApiKeyModal";
|
||||
import { FlowComponent } from "../../types/store";
|
||||
import { cn } from "../../utils/utils";
|
||||
import { MarketCardComponent } from "./components/market-card";
|
||||
export default function StorePage(): JSX.Element {
|
||||
const { setTabId } = useContext(TabsContext);
|
||||
|
||||
const { setApiKey, apiKey } = useContext(AuthContext);
|
||||
|
||||
// set null id
|
||||
useEffect(() => {
|
||||
setTabId("");
|
||||
}, []);
|
||||
const [data, setData] = useState<FlowComponent[]>([]);
|
||||
const [dataSelect, setDataSelect] = useState<FlowComponent[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [search, setSearch] = useState(false);
|
||||
const [filteredCategories, setFilteredCategories] = useState(new Set());
|
||||
const [inputText, setInputText] = useState<string>("");
|
||||
const [searchData, setSearchData] = useState(data);
|
||||
const [errorApiKey, setErrorApiKey] = useState(false);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const { addFlow } = useContext(TabsContext);
|
||||
const [totalRowsCount, setTotalRowsCount] = useState(0);
|
||||
const [size, setPageSize] = useState(10);
|
||||
const [index, setPageIndex] = useState(1);
|
||||
const { setSavedFlows } = useContext(StoreContext);
|
||||
|
||||
async function getSavedComponents() {
|
||||
setLoading(true);
|
||||
const result = await getStoreSavedComponents();
|
||||
let savedIds = new Set<string>();
|
||||
result.forEach((flow) => {
|
||||
savedIds.add(flow.id);
|
||||
});
|
||||
setSavedFlows(savedIds);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleGetComponents();
|
||||
getSavedComponents().then((_) => handleGetComponents());
|
||||
}, []);
|
||||
|
||||
const handleGetComponents = () => {
|
||||
setLoading(true);
|
||||
getStoreComponents(index - 1, 10000)
|
||||
.then((res) => {
|
||||
setSearchData(res.slice(0, 10));
|
||||
setLoading(false);
|
||||
setErrorApiKey(false);
|
||||
setData(res);
|
||||
setSearchData(res.slice(0, size));
|
||||
setTotalRowsCount(res.length);
|
||||
setData(res);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
setSearchData([]);
|
||||
setLoading(false);
|
||||
setErrorApiKey(true);
|
||||
setErrorData({
|
||||
title: "Error to get components.",
|
||||
list: [err["response"]["data"]["detail"]],
|
||||
|
|
@ -71,6 +80,12 @@ export default function StorePage(): JSX.Element {
|
|||
};
|
||||
|
||||
const handleSearch = (inputText: string) => {
|
||||
if (inputText === "") {
|
||||
handleGetComponents();
|
||||
setSearch(false);
|
||||
return;
|
||||
}
|
||||
setSearch(true);
|
||||
setLoading(true);
|
||||
searchComponent(inputText).then(
|
||||
(res) => {
|
||||
|
|
@ -87,17 +102,15 @@ export default function StorePage(): JSX.Element {
|
|||
setLoading(true);
|
||||
getStoreComponents(pageIndex, pageSize)
|
||||
.then((res) => {
|
||||
setSearchData(res.slice(0, size));
|
||||
setData(res);
|
||||
setPageIndex(pageIndex);
|
||||
setPageSize(pageSize);
|
||||
setSearchData(res);
|
||||
setLoading(false);
|
||||
setErrorApiKey(false);
|
||||
setData(res);
|
||||
})
|
||||
.catch((err) => {
|
||||
setSearchData([]);
|
||||
setLoading(false);
|
||||
setErrorApiKey(true);
|
||||
setErrorData({
|
||||
title: "Error to get components.",
|
||||
list: [err["response"]["data"]["detail"]],
|
||||
|
|
@ -105,14 +118,9 @@ export default function StorePage(): JSX.Element {
|
|||
});
|
||||
}
|
||||
|
||||
function resetFilter() {
|
||||
setPageIndex(1);
|
||||
setPageSize(10);
|
||||
handleGetComponents();
|
||||
}
|
||||
|
||||
const loadingWithApiKey = loading;
|
||||
const renderComponents = !loading;
|
||||
const renderPagination = searchData.length > 0 && !loading && !search;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -189,56 +197,61 @@ export default function StorePage(): JSX.Element {
|
|||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
{Array.from(new Set(searchData.map((i) => i.is_component))).map(
|
||||
(i, idx) => (
|
||||
<Badge
|
||||
onClick={() => {
|
||||
filteredCategories.has(i)
|
||||
? setFilteredCategories((old) => {
|
||||
let newFilteredCategories = cloneDeep(old);
|
||||
newFilteredCategories.delete(i);
|
||||
return newFilteredCategories;
|
||||
})
|
||||
: setFilteredCategories((old) => {
|
||||
let newFilteredCategories = cloneDeep(old);
|
||||
newFilteredCategories.add(i);
|
||||
return newFilteredCategories;
|
||||
});
|
||||
}}
|
||||
variant="gray"
|
||||
size="md"
|
||||
className={cn(
|
||||
"cursor-pointer border-none",
|
||||
filteredCategories.has(i)
|
||||
? "bg-beta-foreground text-background hover:bg-beta-foreground"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
<Link className="mr-1.5 w-4" />
|
||||
{i}
|
||||
</Badge>
|
||||
)
|
||||
<div className="flex h-2 items-center justify-center gap-4">
|
||||
{renderComponents &&
|
||||
Array.from(new Set(searchData.map((i) => i.is_component))).map(
|
||||
(i, idx) => (
|
||||
<Badge
|
||||
onClick={() => {
|
||||
filteredCategories.has(i)
|
||||
? setFilteredCategories((old) => {
|
||||
let newFilteredCategories = cloneDeep(old);
|
||||
newFilteredCategories.delete(i);
|
||||
return newFilteredCategories;
|
||||
})
|
||||
: setFilteredCategories((old) => {
|
||||
let newFilteredCategories = cloneDeep(old);
|
||||
newFilteredCategories.add(i);
|
||||
return newFilteredCategories;
|
||||
});
|
||||
}}
|
||||
variant="gray"
|
||||
size="md"
|
||||
className={cn(
|
||||
"cursor-pointer border-none",
|
||||
filteredCategories.has(i)
|
||||
? "bg-beta-foreground text-background hover:bg-beta-foreground"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
<Link className="mr-1.5 w-4" />
|
||||
{i}
|
||||
</Badge>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-6 grid w-full gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{loadingWithApiKey ? (
|
||||
<>
|
||||
<SkeletonCardComponent />
|
||||
<SkeletonCardComponent />
|
||||
<SkeletonCardComponent />
|
||||
</>
|
||||
) : (
|
||||
searchData
|
||||
.filter(
|
||||
(f) =>
|
||||
Array.from(filteredCategories).length === 0 ||
|
||||
filteredCategories.has(f.is_component)
|
||||
)
|
||||
.map((item, idx) => (
|
||||
<MarketCardComponent key={idx} data={item} />
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
{renderComponents && (
|
||||
<>
|
||||
<div className="mt-6 grid w-full gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{searchData
|
||||
.filter(
|
||||
(f) =>
|
||||
Array.from(filteredCategories).length === 0 ||
|
||||
filteredCategories.has(f.is_component)
|
||||
)
|
||||
.map((item, idx) => (
|
||||
<MarketCardComponent key={idx} data={item} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{totalRowsCount > 0 && !loading && (
|
||||
|
||||
{renderPagination && (
|
||||
<div className="relative bottom-2">
|
||||
<PaginatorComponent
|
||||
storeComponent={true}
|
||||
|
|
@ -251,9 +264,6 @@ export default function StorePage(): JSX.Element {
|
|||
></PaginatorComponent>
|
||||
</div>
|
||||
)}
|
||||
{loadingWithApiKey && (
|
||||
<div className="flex w-full flex-col gap-4 p-4">Loading...</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@
|
|||
@apply flex gap-2;
|
||||
}
|
||||
.primary-input {
|
||||
@apply form-input block w-full truncate rounded-md border-border bg-background px-3 text-left shadow-sm placeholder:text-muted-foreground focus:border-ring focus:placeholder-transparent focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 sm:text-sm;
|
||||
@apply form-input block w-full truncate rounded-md border-border bg-background px-3 text-left shadow-sm placeholder:text-muted-foreground focus:border-ring focus:placeholder-transparent focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 text-sm;
|
||||
}
|
||||
|
||||
.skeleton-card {
|
||||
|
|
@ -237,7 +237,7 @@
|
|||
}
|
||||
|
||||
.main-page-flows-display {
|
||||
@apply grid w-full gap-4 md:grid-cols-2 lg:grid-cols-4;
|
||||
@apply grid w-full gap-4 md:grid-cols-2 lg:grid-cols-3;
|
||||
}
|
||||
|
||||
.community-page-arrangement {
|
||||
|
|
@ -495,7 +495,7 @@
|
|||
@apply flex items-center gap-0.5 rounded-md px-1.5 py-1 text-sm font-medium;
|
||||
}
|
||||
.header-menu-bar-display {
|
||||
@apply flex max-w-[200px] cursor-pointer items-center gap-2;
|
||||
@apply flex max-w-[120px] lg:max-w-[200px] cursor-pointer items-center gap-2;
|
||||
}
|
||||
.header-menu-flow-name {
|
||||
@apply flex-1 truncate;
|
||||
|
|
@ -505,13 +505,13 @@
|
|||
}
|
||||
|
||||
.header-arrangement {
|
||||
@apply flex-max-width h-12 items-center justify-between border-border bg-background;
|
||||
@apply flex-max-width h-12 items-center justify-between border-b border-border bg-muted;
|
||||
}
|
||||
.header-start-display {
|
||||
@apply flex w-[30%] items-center justify-start gap-2;
|
||||
@apply flex items-center justify-start gap-2;
|
||||
}
|
||||
.header-end-division {
|
||||
@apply flex w-[30%] justify-end px-2;
|
||||
@apply flex justify-end px-2;
|
||||
}
|
||||
.header-end-display {
|
||||
@apply ml-auto mr-2 flex items-center gap-5;
|
||||
|
|
@ -535,7 +535,7 @@
|
|||
@apply hover:bg-accent hover:text-accent-foreground;
|
||||
}
|
||||
.header-github-display {
|
||||
@apply -mr-px ml-2 flex h-9 items-center justify-center rounded-md rounded-l-none border bg-background px-2 text-sm;
|
||||
@apply -mr-px ml-1 flex h-9 items-center justify-center rounded-md rounded-l-none border bg-background px-2 text-sm;
|
||||
}
|
||||
.header-notifications-box {
|
||||
@apply fixed left-0 top-0 h-screen w-screen;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import { FlowType } from "../flow";
|
||||
|
||||
export type storeContextType = {
|
||||
savedFlows: { [key: string]: FlowType };
|
||||
setSavedFlows: (newState: { [key: string]: FlowType }) => void;
|
||||
savedFlows: Set<string>;
|
||||
setSavedFlows: (newState: Set<string>) => void;
|
||||
setHasStore: (store: boolean) => void;
|
||||
hasStore: boolean;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export type TabsContextType = {
|
|||
setLastCopiedSelection: (selection: { nodes: any; edges: any }) => void;
|
||||
setTweak: (tweak: tweakType) => tweakType | void;
|
||||
getTweak: tweakType;
|
||||
saveComponent: (component: NodeDataType) => void;
|
||||
saveComponent: (component: NodeDataType) => Promise<String | undefined>;
|
||||
deleteComponent: (id: string, key: string) => void;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1009,7 +1009,8 @@ export function expandGroupNode(
|
|||
gNodes[nodeIndex].data.node!.template[field].show = show;
|
||||
gNodes[nodeIndex].data.node!.template[field].advanced = advanced;
|
||||
gNodes[nodeIndex].data.node!.template[field].display_name = display_name;
|
||||
gNodes[nodeIndex].selected = false;
|
||||
// keep the nodes selected after ungrouping
|
||||
// gNodes[nodeIndex].selected = false;
|
||||
if (proxy) {
|
||||
gNodes[nodeIndex].data.node!.template[field].proxy = proxy;
|
||||
} else {
|
||||
|
|
@ -1096,3 +1097,8 @@ export function downloadNode(NodeFLow: FlowType) {
|
|||
element.download = `${NodeFLow.name}.json`;
|
||||
element.click();
|
||||
}
|
||||
|
||||
export function updateComponentNameAndType(
|
||||
data: any,
|
||||
component: NodeDataType
|
||||
) {}
|
||||
|
|
|
|||
|
|
@ -571,7 +571,7 @@ export function IncrementObjectKey(
|
|||
|
||||
export function removeCountFromString(input: string): string {
|
||||
// Define a regex pattern to match the count in parentheses
|
||||
const pattern = /\s*\(\d+\)\s*$/;
|
||||
const pattern = /\s*\(\w+\)\s*$/;
|
||||
|
||||
// Use the `replace` method to remove the matched pattern
|
||||
const result = input.replace(pattern, "");
|
||||
|
|
@ -579,9 +579,13 @@ export function removeCountFromString(input: string): string {
|
|||
return result.trim(); // Trim any leading/trailing spaces
|
||||
}
|
||||
|
||||
export function createRandomKey(key: string, uid: string): string {
|
||||
return removeCountFromString(key) + ` (${uid})`;
|
||||
}
|
||||
|
||||
export function sensitiveSort(a: string, b: string): number {
|
||||
// Extract the name and number from each string using regular expressions
|
||||
const regex = /(.+) \((\d+)\)/;
|
||||
const regex = /(.+) \((\w+)\)/;
|
||||
const matchA = a.match(regex);
|
||||
const matchB = b.match(regex);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue