Merge branch 'bug/component_share' into dev

This commit is contained in:
Lucas Oliveira 2023-12-10 22:35:20 -03:00
commit 54064737e8
16 changed files with 216 additions and 170 deletions

View file

@ -156,11 +156,16 @@ export default function ParameterComponent({
};
const handleNodeClass = (newNodeClass: APIClassType, code?: string): void => {
if (!data.node) return;
if (data.node!.template[name].value !== newNodeClass.template[name].value) {
takeSnapshot();
}
data.node = newNodeClass;
data.node.template[name].value = code;
data.node! = {
...newNodeClass,
description: newNodeClass.description ?? data.node!.description,
display_name: newNodeClass.display_name ?? data.node!.display_name,
};
data.node!.template[name].value = code;
updateNodeInternals(data.id);
// Set state to pending
//@ts-ignore
@ -174,19 +179,22 @@ export default function ParameterComponent({
}
renderTooltips();
let flow = flows.find((flow) => flow.id === tabId);
if (reactFlowInstance && flow && flow.data) {
cleanEdges({
flow: {
edges: flow.data!.edges,
nodes: flow.data!.nodes,
},
updateEdge: (edge) => {
reactFlowInstance.setEdges(edge);
updateNodeInternals(data.id);
},
});
updateFlow(flow);
}
setTimeout(() => {
//timeout necessary because ReactFlow updates are not async
if (reactFlowInstance && flow && flow.data) {
cleanEdges({
flow: {
edges: flow.data!.edges,
nodes: flow.data!.nodes,
},
updateEdge: (edge) => {
reactFlowInstance.setEdges(edge);
updateNodeInternals(data.id);
},
});
updateFlow(flow);
}
}, 50);
};
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);

View file

@ -16,7 +16,7 @@ import { validationStatusType } from "../../types/components";
import { NodeDataType } from "../../types/flow";
import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils";
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
import { classNames, getFieldTitle } from "../../utils/utils";
import { classNames, cn, getFieldTitle } from "../../utils/utils";
import ParameterComponent from "./components/parameterComponent";
export default function GenericNode({
@ -107,6 +107,8 @@ export default function GenericNode({
const showNode = data.showNode ?? true;
const nameEditable = data.node?.flow || data.type === "CustomComponent";
return (
<>
<NodeToolbar>
@ -164,7 +166,7 @@ export default function GenericNode({
/>
{showNode && (
<div className="generic-node-tooltip-div">
{data.node?.flow && inputName ? (
{nameEditable && inputName ? (
<div>
<InputComponent
onBlur={() => {
@ -172,6 +174,7 @@ export default function GenericNode({
if (nodeName.trim() !== "") {
setNodeName(nodeName);
data.node!.display_name = nodeName;
updateNodeInternals(data.id);
} else {
setNodeName(data.node!.display_name);
}
@ -194,7 +197,7 @@ export default function GenericNode({
<div className="generic-node-tooltip-div pr-2 text-primary">
{data.node?.display_name}
</div>
{data.node?.flow && (
{nameEditable && (
<IconComponent
name="Pencil"
className="h-4 w-4 text-ring"
@ -361,57 +364,62 @@ export default function GenericNode({
<div
className={
showNode
? "generic-node-desc overflow-hidden " +
(data.node?.description !== "" ? "py-5" : "pb-5")
? "overflow-hidden " +
(data.node?.description === "" && !nameEditable
? "pb-5"
: "py-5")
: ""
}
>
{data.node?.description !== "" &&
showNode &&
data.node?.flow &&
inputDescription ? (
<Textarea
autoFocus
onBlur={() => {
setInputDescription(false);
if (nodeDescription.trim() !== "") {
<div className="generic-node-desc">
{showNode && nameEditable && inputDescription ? (
<Textarea
autoFocus
onBlur={() => {
setInputDescription(false);
setNodeDescription(nodeDescription);
data.node!.description = nodeDescription;
} else {
setNodeDescription(data.node!.description);
}
}}
value={nodeDescription}
onChange={(e) => setNodeDescription(e.target.value)}
onKeyDown={(e) => {
handleKeyDown(e, nodeDescription, "");
if (
e.key === "Enter" &&
e.shiftKey === false &&
e.ctrlKey === false &&
e.altKey === false
) {
setInputDescription(false);
if (nodeDescription.trim() !== "") {
updateNodeInternals(data.id);
}}
value={nodeDescription}
onChange={(e) => setNodeDescription(e.target.value)}
onKeyDown={(e) => {
handleKeyDown(e, nodeDescription, "");
if (
e.key === "Enter" &&
e.shiftKey === false &&
e.ctrlKey === false &&
e.altKey === false
) {
setInputDescription(false);
setNodeDescription(nodeDescription);
data.node!.description = nodeDescription;
} else {
setNodeDescription(data.node!.description);
updateNodeInternals(data.id);
}
}
}}
/>
) : (
<div
className="generic-node-desc-text truncate-multiline word-break-break-word"
onDoubleClick={() => {
setInputDescription(true);
takeSnapshot();
}}
>
{data.node?.description}
</div>
)}
}}
/>
) : (
<div
className={cn(
"generic-node-desc-text truncate-multiline word-break-break-word",
(data.node?.description === "" ||
!data.node?.description) &&
nameEditable
? "font-light italic"
: ""
)}
onDoubleClick={() => {
setInputDescription(true);
takeSnapshot();
}}
>
{(data.node?.description === "" || !data.node?.description) &&
nameEditable
? "Double Click to Edit Description"
: data.node?.description}
</div>
)}
</div>
<>
{Object.keys(data.node!.template)
.filter((templateField) => templateField.charAt(0) !== "_")

View file

@ -3,6 +3,7 @@ import { Input } from "../../components/ui/input";
import { Label } from "../../components/ui/label";
import { Textarea } from "../../components/ui/textarea";
import { InputProps } from "../../types/components";
import { cn } from "../../utils/utils";
export const EditFlowSettings: React.FC<InputProps> = ({
name,
@ -21,47 +22,65 @@ export const EditFlowSettings: React.FC<InputProps> = ({
} else {
setIsMaxLength(false);
}
setName(value);
setName!(value);
};
const handleDescriptionChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
setDescription(event.target.value);
setDescription!(event.target.value);
};
return (
<>
<Label>
<div className="edit-flow-arrangement">
<span className="font-medium">Name</span>{" "}
<span className="font-medium">Name{setName ? "" : ":"}</span>{" "}
{isMaxLength && (
<span className="edit-flow-span">Character limit reached</span>
)}
</div>
<Input
className="nopan nodelete nodrag noundo nocopy mt-2 font-normal"
onChange={handleNameChange}
type="text"
name="name"
value={name ?? ""}
placeholder="Flow name"
id="name"
maxLength={maxLength}
/>
{setName ? (
<Input
className="nopan nodelete nodrag noundo nocopy mt-2 font-normal"
onChange={handleNameChange}
type="text"
name="name"
value={name ?? ""}
placeholder="Flow name"
id="name"
maxLength={maxLength}
/>
) : (
<span className="font-normal text-muted-foreground word-break-break-word">
{name}
</span>
)}
</Label>
<Label>
<div className="edit-flow-arrangement mt-3">
<span className="font-medium ">Description (optional)</span>
<span className="font-medium ">
Description{setDescription ? " (optional)" : ":"}
</span>
</div>
<Textarea
name="description"
id="description"
onChange={handleDescriptionChange}
value={description!}
placeholder="Flow description"
className="mt-2 max-h-[100px] font-normal"
rows={3}
/>
{setDescription ? (
<Textarea
name="description"
id="description"
onChange={handleDescriptionChange}
value={description!}
placeholder="Flow description"
className="mt-2 max-h-[100px] font-normal"
rows={3}
/>
) : (
<span
className={cn(
"font-normal text-muted-foreground word-break-break-word",
description === "" ? "font-light italic" : ""
)}
>
{description === "" ? "No description" : description}
</span>
)}
</Label>
</>
);

View file

@ -357,10 +357,10 @@ export async function postCustomComponent(
code: string,
apiClass: APIClassType
): Promise<AxiosResponse<APIClassType>> {
let template = apiClass.template;
// let template = apiClass.template;
return await api.post(`${BASE_URL_API}custom_component`, {
code,
template,
frontend_node: apiClass,
});
}

View file

@ -44,11 +44,9 @@ export default function ShareModal({
const { setSuccessData, setErrorData } = useContext(alertContext);
const { reactFlowInstance } = useContext(typesContext);
const [checked, setChecked] = useState(false);
const [name, setName] = useState(component?.name ?? "");
const [description, setDescription] = useState(component?.description ?? "");
const [internalOpen, internalSetOpen] = useState(children ? false : true);
const [openConfirmationModal, setOpenConfirmationModal] = useState(false);
const nameComponent = is_component ? "Component" : "Flow";
const nameComponent = is_component ? "component" : "flow";
const [tags, setTags] = useState<{ id: string; name: string }[]>([]);
const [loadingTags, setLoadingTags] = useState<boolean>(false);
@ -61,6 +59,9 @@ export default function ShareModal({
const [loadingNames, setLoadingNames] = useState(false);
const name = component?.name ?? "";
const description = component?.description ?? "";
useEffect(() => {
if (open || internalOpen) {
if (hasApiKey && hasStore) {
@ -94,11 +95,6 @@ export default function ShareModal({
});
}
useEffect(() => {
setName(component?.name ?? "");
setDescription(component?.description ?? "");
}, [component, open, internalOpen]);
const handleShareComponent = async (update = false) => {
//remove file names from flows before sharing
removeFileNameFromComponents(component);
@ -120,11 +116,9 @@ export default function ShareModal({
is_component: is_component,
});
await saveFlow(flow!, true);
function successShare() {
if (is_component) {
addFlow(true, flow);
if (!is_component) {
saveFlow(flow!, true);
}
setSuccessData({
title: `${nameComponent} shared successfully`,
@ -223,13 +217,9 @@ export default function ShareModal({
/>
</BaseModal.Header>
<BaseModal.Content>
<EditFlowSettings
name={name}
invalidNameList={unavaliableNames.map((element) => element.name)}
description={description}
setName={setName}
setDescription={setDescription}
/>
<div className="w-full rounded-lg border border-border p-4">
<EditFlowSettings name={name} description={description} />
</div>
<div className="mt-3 flex h-8 w-full">
<TagsSelector
tags={tags}
@ -297,8 +287,8 @@ export default function ShareModal({
</>
) : (
<>
{is_component && !loadingNames ? "Save and " : ""}Share{" "}
{!is_component && !loadingNames ? "Flow" : ""}
Share{" "}
{!loadingNames && (!is_component ? "Flow" : "Component")}
</>
)}
</Button>

View file

@ -282,6 +282,17 @@ export default function Page({
let newX = _.cloneDeep(node);
return newX;
});
//@ts-ignore
setTabsState((prev: FlowsState) => {
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: true,
},
};
});
saveCurrentFlowTimeout();
},
[setEdges, setNodes, takeSnapshot, addEdge]
);

View file

@ -77,7 +77,15 @@ export default function NodeToolbarComponent({
useEffect(() => {
setFlowComponent(createFlowComponent(cloneDeep(data), version));
}, [data, data.node, showModalAdvanced]);
}, [
data,
data.node,
data.node?.display_name,
data.node?.description,
data.node?.template,
showModalAdvanced,
showconfirmShare,
]);
const handleSelectChange = (event) => {
switch (event) {

View file

@ -311,10 +311,10 @@
@apply hover:text-accent-foreground hover:transition-all;
}
.generic-node-desc {
@apply h-full w-full text-foreground;
@apply h-full px-5 mb-4 w-full text-foreground;
}
.generic-node-desc-text {
@apply w-full px-5 mb-4 text-sm text-muted-foreground;
@apply w-full text-sm text-muted-foreground;
}
.alert-icon {

View file

@ -240,8 +240,8 @@ export type InputProps = {
name: string | null;
description: string | null;
maxLength?: number;
setName: (name: string) => void;
setDescription: (description: string) => void;
setName?: (name: string) => void;
setDescription?: (description: string) => void;
invalidNameList?: string[];
};

View file

@ -41,37 +41,35 @@ export function cleanEdges({
const targetNode = nodes.find((node) => node.id === edge.target);
if (!sourceNode || !targetNode) {
newEdges = newEdges.filter((edg) => edg.id !== edge.id);
return;
}
// check if the source and target handle still exists
if (sourceNode && targetNode) {
const sourceHandle = edge.sourceHandle; //right
const targetHandle = edge.targetHandle; //left
if (targetHandle) {
const targetHandleObject: targetHandleType =
scapeJSONParse(targetHandle);
const field = targetHandleObject.fieldName;
const id: targetHandleType = {
type: targetNode.data.node!.template[field]?.type,
fieldName: field,
id: targetNode.data.id,
inputTypes: targetNode.data.node!.template[field]?.input_types,
};
if (targetNode.data.node!.template[field]?.proxy) {
id.proxy = targetNode.data.node!.template[field]?.proxy;
}
if (scapedJSONStringfy(id) !== targetHandle) {
newEdges = newEdges.filter((e) => e.id !== edge.id);
}
const sourceHandle = edge.sourceHandle; //right
const targetHandle = edge.targetHandle; //left
if (targetHandle) {
const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle);
const field = targetHandleObject.fieldName;
const id: targetHandleType = {
type: targetNode.data.node!.template[field]?.type,
fieldName: field,
id: targetNode.data.id,
inputTypes: targetNode.data.node!.template[field]?.input_types,
};
if (targetNode.data.node!.template[field]?.proxy) {
id.proxy = targetNode.data.node!.template[field]?.proxy;
}
if (sourceHandle) {
const id: sourceHandleType = {
id: sourceNode.data.id,
baseClasses: sourceNode.data.node!.base_classes,
dataType: sourceNode.data.type,
};
if (scapedJSONStringfy(id) !== sourceHandle) {
newEdges = newEdges.filter((e) => e.id !== edge.id);
}
if (scapedJSONStringfy(id) !== targetHandle) {
newEdges = newEdges.filter((e) => e.id !== edge.id);
}
}
if (sourceHandle) {
const id: sourceHandleType = {
id: sourceNode.data.id,
baseClasses: sourceNode.data.node!.base_classes,
dataType: sourceNode.data.type,
};
if (scapedJSONStringfy(id) !== sourceHandle) {
newEdges = newEdges.filter((e) => e.id !== edge.id);
}
}
});
@ -761,7 +759,7 @@ export function generateNodeFromFlow(
display_name: "Group",
documentation: "",
base_classes: outputNode!.data.node!.base_classes,
description: "double click to edit description",
description: "",
template: generateNodeTemplate(data),
flow: data,
},