Track HiveGrid Cloud planning reference

This commit is contained in:
david committed 2026-06-26 20:48:01 +02:00
1 parent b3d5ea6764
commit e341c5938a
5784 files changed
+1104929 -3

No files matched your search

@@ -0,0 +1,96 @@
import React, { useState } from "react";
import WorkflowVariableTextInput from "~/modules/workflowEngine/components/variables/WorkflowVariableTextInput";
import { WorkflowDto } from "~/modules/workflowEngine/dtos/WorkflowDto";
import { WorkflowBlockDto } from "~/modules/workflowEngine/dtos/WorkflowBlockDto";
import { useTranslation } from "react-i18next";
import { TrashIcon } from "~/components/ui/icons/vendor";
export default function WorkflowKeyValueInputs({
workflow,
block,
initialData,
onChange,
}: {
workflow: WorkflowDto;
block: WorkflowBlockDto;
initialData: {
[key: string]: string;
};
onChange: (data: any) => void;
}) {
const { t } = useTranslation();
const initialPairs = Object.keys(initialData).map((key) => ({
id: crypto.randomUUID(),
key,
value: initialData[key],
}));
const [pairs, setPairs] = useState(initialPairs);
const handleAddPair = () => {
setPairs([...pairs, { id: crypto.randomUUID(), key: "", value: "" }]);
};
const handleKeyChange = (id: string, newKey: string) => {
const updatedPairs = pairs.map((pair) => (pair.id === id ? { ...pair, key: newKey } : pair));
setPairs(updatedPairs);
triggerOnChange(updatedPairs);
};
const handleValueChange = (id: string, newValue: string) => {
const updatedPairs = pairs.map((pair) => (pair.id === id ? { ...pair, value: newValue } : pair));
setPairs(updatedPairs);
triggerOnChange(updatedPairs);
};
const triggerOnChange = (updatedPairs: { id: string; key: string; value: string }[]) => {
const newData = updatedPairs.reduce((acc: any, { key, value }) => ({ ...acc, [key]: value }), {});
onChange(newData);
};
return (
<div className="border-border bg-secondary space-y-1 rounded-md border p-2">
{pairs.map(({ id, key, value }) => (
<div key={id} className="group relative grid grid-cols-1 gap-1 truncate sm:grid-cols-12">
<button
type="button"
aria-label={t("workflows.variables.removePair")}
onClick={() => {
const updatedPairs = pairs.filter((pair) => pair.id !== id);
setPairs(updatedPairs);
triggerOnChange(updatedPairs);
}}
className="absolute right-1 top-1.5 hidden rounded-md p-1 focus:outline-hidden focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 group-focus-within:block group-hover:block"
>
<TrashIcon className="h-4 w-4" />
</button>
<div className="p-0.5 sm:col-span-6">
<WorkflowVariableTextInput
workflow={workflow}
block={block}
name="key"
label={t("workflows.variables.key")}
value={key}
onChange={(e) => handleKeyChange(id, e)}
placeholder={t("workflows.variables.key")}
/>
</div>
<div className="p-0.5 sm:col-span-6">
<WorkflowVariableTextInput
workflow={workflow}
block={block}
name="value"
label={t("workflows.variables.value")}
value={value}
onChange={(e) => handleValueChange(id, e)}
placeholder={t("workflows.variables.value")}
/>
</div>
</div>
))}
<button type="button" onClick={handleAddPair} className="border-border text-muted-foreground bg-secondary hover:bg-secondary/90 rounded-md border px-2 py-1 text-xs">
{t("shared.add")}
</button>
</div>
);
}
@@ -0,0 +1,107 @@
import { Fragment, useEffect, useState } from "react";
import SlideOverWideEmpty from "~/components/ui/slideOvers/SlideOverWideEmpty";
import WorkflowUtils from "../../helpers/WorkflowUtils";
import { WorkflowDto } from "../../dtos/WorkflowDto";
import { WorkflowBlockDto } from "../../dtos/WorkflowBlockDto";
import { useTranslation } from "react-i18next";
import { VariableIcon } from "~/components/ui/icons/vendor";
export default function WorkflowVariableButton({
workflow,
block,
onSelected,
}: {
workflow: WorkflowDto;
block: WorkflowBlockDto;
onSelected: (variable: { name: string }) => void;
}) {
const [isOpen, setIsOpen] = useState(false);
const { t } = useTranslation();
return (
<Fragment>
<button
type="button"
aria-label={t("workflows.variables.insert")}
onClick={() => setIsOpen(true)}
className="border-border text-muted-foreground hover:text-foreground hover:bg-background bg-secondary rounded-md border p-1 text-xs font-medium focus:outline-hidden focus:ring-2 focus:ring-gray-500 focus:ring-offset-2"
>
<VariableIcon className="h-4 w-4" />
</button>
<SlideOverWideEmpty className="sm:max-w-sm" title={t("workflows.variables.title")} open={isOpen} onClose={() => setIsOpen(false)}>
<VariableList
workflow={workflow}
block={block}
onSelected={(variable) => {
onSelected(variable);
setIsOpen(false);
}}
/>
</SlideOverWideEmpty>
</Fragment>
);
}
function VariableList({ workflow, block, onSelected }: { workflow: WorkflowDto; block: WorkflowBlockDto; onSelected: (variable: { name: string }) => void }) {
const [groups, setGroups] = useState<{ name: string; variables: { name: string; label: string }[] }[]>([]);
const { t } = useTranslation();
useEffect(() => {
const variables = WorkflowUtils.getVariables({ workflow, currentBlock: block });
const groups = variables.reduce<{ name: string; variables: { name: string; label: string }[] }[]>((acc, variable) => {
const group = acc.find((x) => x.name === variable.group);
if (group) {
group.variables.push(variable);
} else {
acc.push({ name: variable.group, variables: [variable] });
}
return acc;
}, []);
if (workflow.$credentials && workflow.$credentials.length > 0) {
groups.unshift({
name: t("workflows.variables.credentials"),
variables: workflow.$credentials.map((f) => ({
name: `{{$credentials.${f}}}`,
label: f,
})),
});
}
let globalVariables = workflow.$variables ?? [];
const variableBlocks = workflow.blocks.filter((x) => x.type === "variable");
variableBlocks.forEach((variableBlock) => {
globalVariables.push(variableBlock.input.variableName);
});
if (globalVariables.length > 0) {
groups.unshift({
name: t("workflows.variables.global"),
variables: globalVariables.map((f) => ({
name: `{{$vars.${f}}}`,
label: f,
})),
});
}
setGroups(groups);
}, [workflow, block, t]);
return (
<div className="space-y-2">
{groups.map((group, idx) => {
return (
<div key={idx} className="space-y-1">
<div className="font-medium">{group.name}</div>
<div className="space-y-1">
{group.variables.map((variable) => (
<button
type="button"
key={variable.name}
className="hover:bg-secondary border-border bg-background flex w-full items-center space-x-2 truncate rounded-md border px-2 py-2 text-sm"
onClick={() => onSelected(variable)}
>
<div>{variable.label}</div>
</button>
))}
</div>
</div>
);
})}
</div>
);
}
@@ -0,0 +1,65 @@
import { useRef } from "react";
import { WorkflowDto } from "../../dtos/WorkflowDto";
import { WorkflowBlockDto } from "../../dtos/WorkflowBlockDto";
import WorkflowVariableButton from "./WorkflowVariableButton";
import clsx from "clsx";
export default function WorkflowVariableTextInput({
name,
placeholder,
label,
workflow,
block,
value,
onChange,
className,
}: {
name: string;
placeholder: string | undefined;
label?: string;
workflow: WorkflowDto;
block: WorkflowBlockDto;
value: string;
onChange: (value: string) => void;
className?: string;
}) {
const refString = useRef<HTMLInputElement>(null);
function onSelected(variable: { name: string }) {
if (!refString.current) {
return;
}
var start = refString.current.selectionStart || 0;
var end = refString.current.selectionEnd || 0;
var text = refString.current.value;
var before = text.substring(0, start);
var after = text.substring(end, text.length);
onChange(before + variable.name + after);
}
return (
<div className={clsx("relative rounded-md shadow-2xs", className)}>
<input
ref={refString}
name={name}
id={name}
className="border-border bg-secondary hover:bg-secondary/90 w-full rounded-lg border px-2 py-2 pr-10 text-sm focus:outline-hidden"
value={value}
onClick={(e: React.MouseEvent<HTMLInputElement>) => {
// @ts-ignore
e.target.select();
}}
placeholder={placeholder}
aria-label={label ?? placeholder ?? name}
onChange={(e) => onChange(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.currentTarget.blur();
}
}}
/>
<div className="absolute inset-y-0 right-0 flex py-1.5 pr-1.5 ">
<WorkflowVariableButton workflow={workflow} block={block} onSelected={onSelected} />
</div>
</div>
);
}