From 09311dcb3aba15cba29879e01905e52d17f5aecd Mon Sep 17 00:00:00 2001 From: nicosammito Date: Wed, 12 Aug 2026 13:52:30 +0200 Subject: [PATCH 1/3] feat: update @code0-tech/triangulum to version 0.31.0 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index f9463617..9d1a7128 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@apollo/client": "^4.0.9", "@code0-tech/pictor": "^0.15.0", - "@code0-tech/triangulum": "^0.30.2", + "@code0-tech/triangulum": "^0.31.0", "@codemirror/lang-javascript": "^6.2.5", "@codemirror/lint": "^6.9.5", "@icons-pack/react-simple-icons": "^13.13.0", @@ -1954,9 +1954,9 @@ "peer": true }, "node_modules/@code0-tech/triangulum": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/@code0-tech/triangulum/-/triangulum-0.30.2.tgz", - "integrity": "sha512-F0HRpfEpIq8dzd+e0m4uhXagLKGi4gxGGNhuuS2TO6phBlZjtnqHcwxyHISeSNraDIGeJAhlDI9HsHghgtdtBA==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@code0-tech/triangulum/-/triangulum-0.31.0.tgz", + "integrity": "sha512-nqCjxRTLrXRKJh6hBMWl3fjDT/dcuPYxT4URjBjhZjjqzPyfGd1sOpGSHYNmOLoZ+m9tuhDmAhnBzQlWdlmI1A==", "peerDependencies": { "@code0-tech/sagittarius-graphql-types": "0.0.0-experimental-2744343400-55133b9bb6062772c50f83d945a22a531a6e48e2", "@typescript/vfs": "^1.6.4", diff --git a/package.json b/package.json index 46264401..d5b96505 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "dependencies": { "@apollo/client": "^4.0.9", "@code0-tech/pictor": "^0.15.0", - "@code0-tech/triangulum": "^0.30.2", + "@code0-tech/triangulum": "^0.31.0", "@codemirror/lang-javascript": "^6.2.5", "@codemirror/lint": "^6.9.5", "@icons-pack/react-simple-icons": "^13.13.0", From 373e8d55782e1cd3e604631f39a699a1dee6776c Mon Sep 17 00:00:00 2001 From: nicosammito Date: Wed, 12 Aug 2026 14:01:41 +0200 Subject: [PATCH 2/3] feat: add DataTypeFileInputComponent for file input handling --- .../file/DataTypeFileInputComponent.tsx | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 src/packages/ce/src/datatype/components/inputs/file/DataTypeFileInputComponent.tsx diff --git a/src/packages/ce/src/datatype/components/inputs/file/DataTypeFileInputComponent.tsx b/src/packages/ce/src/datatype/components/inputs/file/DataTypeFileInputComponent.tsx new file mode 100644 index 00000000..e2d27fa1 --- /dev/null +++ b/src/packages/ce/src/datatype/components/inputs/file/DataTypeFileInputComponent.tsx @@ -0,0 +1,174 @@ +import React from "react"; +import {DataTypeInputComponentProps} from "../DataTypeInputComponent"; +import { + Button, + FileInput, + FileInputContext, + FileInputDropzone, + FileInputHiddenInput, + FileInputItem, + FileInputItemDeleteTrigger, + FileInputItemGroup, + FileInputItemName, + FileInputTrigger, + Flex, + InputDescription, + InputLabel, + Text +} from "@code0-tech/pictor"; +import {InputWrapper} from "@code0-tech/pictor/dist/components/form/InputWrapper"; +import {IconPaperclip, IconX} from "@tabler/icons-react"; +import {FileUploadFileChangeDetails} from "@ark-ui/react"; +import {useDebouncedCallback} from "use-debounce"; +import {NodeSchema, Schema} from "@code0-tech/triangulum"; +import { + LiteralValue, + NodeFunction, + NodeParameterValue, + ReferenceValue, + SubFlowValue +} from "@code0-tech/sagittarius-graphql-types"; +import {DataTypeInputControlsComponent} from "@edition/datatype/components/inputs/DataTypeInputControlsComponent"; +import {DataTypeInputValueComponent} from "@edition/datatype/components/inputs/DataTypeInputValueComponent"; + +export type DataTypeFileInputComponentProps = DataTypeInputComponentProps + +export const DataTypeFileInputComponent: React.FC = (props) => { + + const {schema, formValidation, title, initialValue, description, suggestions, onChange} = props + + const dataSchema = "schema" in (schema ?? {}) ? (schema as NodeSchema)?.schema : (schema as Schema) + const multiple = dataSchema?.input === "list-file" + const mimetype = (dataSchema as { mimetype?: string })?.mimetype + + const maxUploadSize = 5 * 1024 * 1024 + const base64ByName = React.useRef>(new Map()) + const defaultValue: NodeParameterValue | NodeFunction | undefined = React.useMemo(() => initialValue ?? undefined, [initialValue]) + const defaultAcceptedFiles: File[] = React.useMemo(() => { + const literal = initialValue?.__typename === "LiteralValue" ? initialValue.value : undefined + if (literal == null) return [] + const entries = Array.isArray(literal) ? literal : [literal] + return entries.flatMap((entry, index) => { + const contentType = (entry as { contentType?: string })?.contentType ?? "" + const base64 = (entry as { value?: string })?.value + if (typeof base64 !== "string") return [] + const extension = contentType.includes("/") ? contentType.split("/")[1] : "" + const name = (entry as { fileName?: string })?.fileName || (extension ? `file-${index}.${extension}` : `file-${index}`) + base64ByName.current.set(name, base64) + return [new File([], name, {type: contentType})] + }) + }, [initialValue]) + const fileKey = defaultAcceptedFiles.map(file => `${file.type}:${file.name}`).join("|") || "empty" + const onChangeDebounced = useDebouncedCallback((value: LiteralValue | SubFlowValue | NodeFunction | ReferenceValue | null) => { + onChange?.(value) + }, 400) + + return React.useMemo(() => <> + {title} + {description} + { + formValidation?.setValue?.(value) + onChangeDebounced(value) + }} suggestions={suggestions} formValidation={formValidation}> + {/*@ts-ignore*/} + { + void (async () => { + const files = await Promise.all((details?.acceptedFiles ?? []).map(async file => { + const value = base64ByName.current.get(file.name) ?? await new Promise((resolve, reject) => { + const reader = new FileReader() + reader.onload = () => { + const result = reader.result as string + resolve(result.slice(result.indexOf(",") + 1)) + } + reader.onerror = reject + reader.readAsDataURL(file) + }) + base64ByName.current.set(file.name, value) + return { + contentType: file.type || (mimetype && mimetype !== "*/*" ? mimetype : ""), + valueType: "base64" as const, + fileName: file.name, + value + } + })) + const literal: LiteralValue | null = files.length <= 0 ? null : { + __typename: "LiteralValue", + value: multiple ? files : files[0] + } + formValidation?.setValue?.(literal) + onChangeDebounced(literal) + })() + } + }}> + +
+ event.stopPropagation()}> + { + formValidation?.setValue?.(value) + onChangeDebounced(value) + }}> + + + + +
+ }> +
+ + {({acceptedFiles}) => acceptedFiles.length <= 0 ? ( + + {title} + + ) : !multiple ? ( + + {acceptedFiles[0]?.name} + + ) : ( + event.stopPropagation()} + py={0.7} + style={{gap: "0.35rem", flexWrap: "wrap", alignItems: "center"}}> + {acceptedFiles.map(file => ( + + + + + + {acceptedFiles.length > 1 && ( + + + + )} + + + ))} + + )} + +
+ + +
+ +
+
+ , [formValidation, defaultValue]) +} From c37f6c79944fd63e3caa54c879ae81f909405244 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Wed, 12 Aug 2026 14:01:58 +0200 Subject: [PATCH 3/3] feat: integrate DataTypeFileInputComponent into DataTypeInputComponent and enhance children prop handling in DataTypeInputControlsComponent --- .../datatype/components/inputs/DataTypeInputComponent.tsx | 7 +++++++ .../components/inputs/DataTypeInputControlsComponent.tsx | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/packages/ce/src/datatype/components/inputs/DataTypeInputComponent.tsx b/src/packages/ce/src/datatype/components/inputs/DataTypeInputComponent.tsx index bdbf5a99..94518472 100644 --- a/src/packages/ce/src/datatype/components/inputs/DataTypeInputComponent.tsx +++ b/src/packages/ce/src/datatype/components/inputs/DataTypeInputComponent.tsx @@ -13,6 +13,7 @@ import {DataTypeSelectInputComponent} from "@edition/datatype/components/inputs/ import {InputWrapperProps} from "@code0-tech/pictor/dist/components/form/InputWrapper"; import {DataTypeNumberInputComponent} from "@edition/datatype/components/inputs/number/DataTypeNumberInputComponent"; import {DataTypeDateInputComponent} from "@edition/datatype/components/inputs/date/DataTypeDateInputComponent"; +import {DataTypeFileInputComponent} from "@edition/datatype/components/inputs/file/DataTypeFileInputComponent"; import {DataTypeJSONInputComponent} from "@edition/datatype/components/inputs/json/DataTypeJSONInputComponent"; import {DataTypeGenericInputComponent} from "@edition/datatype/components/inputs/generic/DataTypeGenericInputComponent"; import { @@ -62,6 +63,12 @@ export const DataTypeInputComponent: React.FC = (pr schema={schema} suggestions={suggestions} {...rest}/> + case "file": + case "list-file": + return case "list": case "data": return ) : <>} - {children ?? <>} + { + (children ?? null as unknown as ReactElement) + }