diff --git a/src/main/frontend/app/components/datamapper/basic-components/import-button.tsx b/src/main/frontend/app/components/datamapper/basic-components/import-button.tsx new file mode 100644 index 00000000..5ead40c9 --- /dev/null +++ b/src/main/frontend/app/components/datamapper/basic-components/import-button.tsx @@ -0,0 +1,60 @@ +import { useId } from 'react' +import clsx from 'clsx' +import Button from '../../inputs/button' +import { showErrorToast } from '../../toast' + +interface ImportButtonProperties { + fileType: string + importFunc: (file: File) => void + file: File | null + setFile: (file: File | null) => void +} + +// Generic import button with visual feedback for uploaded files +function ImportButton({ fileType, importFunc, file, setFile }: ImportButtonProperties) { + const inputId = `UploadImportButton${useId()}` + + return ( +
+ + + setFile(event.target.files?.[0] || null)} + /> + { + + } +
+ ) +} + +export default ImportButton diff --git a/src/main/frontend/app/components/datamapper/react-flow/import-schematic-node.tsx b/src/main/frontend/app/components/datamapper/react-flow/import-schematic-node.tsx new file mode 100644 index 00000000..ebd7830b --- /dev/null +++ b/src/main/frontend/app/components/datamapper/react-flow/import-schematic-node.tsx @@ -0,0 +1,61 @@ +import { Handle, Position } from '@xyflow/react' + +import { GROUP_WIDTH } from '~/utils/datamapper_utils/constant' + +import ImportButton from '../basic-components/import-button' +import type { ImportSchematicFunc } from '~/hooks/use-datamapper-flow-management' +import CodeFile from '/icons/solar/Code File.svg?react' +import { useState } from 'react' +import clsx from 'clsx' +import { showErrorToast } from '~/components/toast' + +//DataType needed +export interface ImportSchematicNodeprops { + data: { + fileType: string + side: 'source' | 'target' + importFunc: ImportSchematicFunc + } & Record +} + +function ImportSchematicNode({ data }: ImportSchematicNodeprops) { + const [isDragging, setIsDragging] = useState(false) + const [file, setFile] = useState(null) + return ( +
{ + e.preventDefault() + setIsDragging(true) + }} + onDragLeave={() => setIsDragging(false)} + onDrop={(e) => { + e.preventDefault() + setIsDragging(false) + + const file = e.dataTransfer.files?.[0] + if (file.name.endsWith(data.fileType)) setFile(file) + else showErrorToast(file.name, 'Incorrect filetype!') + }} + > + {/* Header */} +
Import schema
+ + + data.importFunc(file, data.side, file.name.replace(data.fileType, ''))} + /> + + + +
+ ) +} +export default ImportSchematicNode diff --git a/src/main/frontend/app/components/datamapper/react-flow/node-types.tsx b/src/main/frontend/app/components/datamapper/react-flow/node-types.tsx index 062c34db..3f2e2bce 100644 --- a/src/main/frontend/app/components/datamapper/react-flow/node-types.tsx +++ b/src/main/frontend/app/components/datamapper/react-flow/node-types.tsx @@ -9,6 +9,7 @@ import type { CustomNodeData, MappingNodeData } from '~/types/datamapper_types/r import ArrayGroupNode, { type ArrayGroupNodeProperties } from './array-group-node' import type { ArrayMappingNodeProperties } from './array-mapping-node' import ArrayMappingNode from './array-mapping-node' +import ImportSchematicNode, { type ImportSchematicNodeprops } from './import-schematic-node' interface GetNodeTypesParameters { flow: ReturnType @@ -120,4 +121,5 @@ export const getNodeTypes = ({ }} /> ), + importSchematicNode: (node: ImportSchematicNodeprops) => , }) diff --git a/src/main/frontend/app/components/datamapper/react-flow/one-edge-node.tsx b/src/main/frontend/app/components/datamapper/react-flow/one-edge-node.tsx index c734bc19..6a09b2c4 100644 --- a/src/main/frontend/app/components/datamapper/react-flow/one-edge-node.tsx +++ b/src/main/frontend/app/components/datamapper/react-flow/one-edge-node.tsx @@ -45,7 +45,7 @@ function OneEdgeNode({ id, data, variant = 'source', onEdit, onDelete, onHighlig
void -} - -function SourceDefinitionComponent({ config, onDelete }: SourceDefinitionComponentProperties) { - const { deleteSourceSchema } = useFile() - const [sourceName, setSourceName] = useState('') - - const [confirmed, setConfirmed] = useState(false) - return ( -
- - { - setSourceName(event.target.value) - }} - disabled={confirmed} - /> - - - { - deleteSourceSchema(sourceName) - onDelete() - }} - /> -
- ) -} - -export default SourceDefinitionComponent diff --git a/src/main/frontend/app/components/datamapper/upload-import-button.tsx b/src/main/frontend/app/components/datamapper/upload-import-button.tsx deleted file mode 100644 index 5cee40fd..00000000 --- a/src/main/frontend/app/components/datamapper/upload-import-button.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { type ChangeEvent, type Dispatch, useId } from 'react' -import clsx from 'clsx' -import type { useFlowManagement } from '~/hooks/use-datamapper-flow-management' -import type { ConfigActions } from '~/stores/datamapper_state/mappingListConfig/reducer' -import { useFile } from '~/stores/datamapper_state/schemaQueue/schema-queue-context' -import { FLOW_KEY } from '~/utils/datamapper_utils/constant' -import { showErrorToast, showSuccessToast } from '../toast' -import type { MappingListConfig } from '~/types/datamapper_types/config-types' - -interface SchemaUploadButtonProperties { - label: string - flowType?: 'source' | 'target' - configDispatch?: Dispatch - flow?: ReturnType - config?: MappingListConfig - name?: string - disabled?: boolean -} - -// Generic import button with visual feedback for uploaded files -function UploadImportButton({ - label, - flowType, - configDispatch, - config, - name, - disabled = false, -}: SchemaUploadButtonProperties) { - const { sourceSchematics, addSourceSchematic, targetSchematic, setTargetSchematic } = useFile() - - const inputId = `UploadImportButton${useId()}` - - // Determine the uploaded file name (for source/target) - const uploadedFileName = (() => { - if (!flowType) return null - - if (flowType === 'source') { - const match = sourceSchematics?.find((s) => s.name === name) - return match?.file?.name ?? null - } - - if (flowType === 'target') { - return targetSchematic?.name ?? null - } - - return null - })() - - const isUploaded = Boolean(uploadedFileName) - - const handleFileChange = async (event: ChangeEvent) => { - if (disabled) return // prevent any action if disabled - const file = event.target.files?.[0] - if (!file) return - - try { - if (!flowType && configDispatch) { - const text = await file.text() - const parsed = JSON.parse(text) - configDispatch({ - type: 'IMPORT_CONFIG', - payload: parsed as MappingListConfig, - }) - localStorage.setItem(FLOW_KEY, text) - - showSuccessToast('Imported config JSON!') - } else if (flowType) { - if (flowType === 'source') { - addSourceSchematic({ file, name: name }) - showSuccessToast(`Added ${file.name} to source schematics`) - } else if (flowType === 'target') { - setTargetSchematic(file) - showSuccessToast(`Target schematic set: ${file.name}`) - } - } else { - showErrorToast('Invalid import!', 'Import failed!') - } - } catch { - showErrorToast('Invalid json file!', 'Import failed') - } - - event.target.value = '' - } - - return ( -
- - - -
- ) -} - -export default UploadImportButton diff --git a/src/main/frontend/app/components/inputs/input.tsx b/src/main/frontend/app/components/inputs/input.tsx index 8ead520b..6b43e651 100644 --- a/src/main/frontend/app/components/inputs/input.tsx +++ b/src/main/frontend/app/components/inputs/input.tsx @@ -28,7 +28,7 @@ export default function Input({ > > setEdges: Dispatch> } +export type SequentialRepositionFn = (nodes: Node[], parentId: string) => Node[] +export type GetNodeFunc = (id: string) => Node | undefined +export type AddNodeFunction = ( + side: 'source' | 'target', + label: string, + variableType: string, + defaultValue?: string | null, + parentId?: string | null, + id?: string | null, + isAttribute?: boolean, +) => Promise +export type ImportSchematicFunc = (file: File, side: 'source' | 'target', name: string) => void export function useFlowManagement({ reactFlowInstance, @@ -75,28 +85,30 @@ export function useFlowManagement({ document.removeEventListener('mousemove', updatePosition) } }, [reactFlowInstance]) - - function generateReactFlowObject(previous: Node[], data: CustomNodeData): Node { - //Calculate the position the node is to be placed at. This isn't always very accurate and will be corrected later after adding - const newY = calculateNodePosition(previous, data.parentId, reactFlowInstance.getNode) - //Set the correct type of the node - - //Create the node Obj - const newNode: Node = { - id: data.id, - position: { x: 10, y: newY }, - parentId: data.parentId, - extent: 'parent', - type: getReactflowType(data.variableType, data.parentId), - data, - } - - //Add empty padding in case the item is an object, purely visual - if (isGroup(data.variableType)) { - newNode.height = GROUP_PADDING_TOP * 3 + async function addSchematicImportButton(side: 'source' | 'target') { + deleteNode(`${side}-import-button`) //Failsafe to make sure there can never be more then 1 + const fileType = config.formatTypes[side]?.schemaFileExtension + if (!fileType) { + showErrorToast('Invalid configuration!') + return } + const node = generateImportButton( + reactFlowInstance.getNodes(), + fileType, + side, + reactFlowInstance.getNode, + importSchematic, + ) + .then((newNode: Node) => { + setReactFlowNodes((previous) => [...previous, newNode]) - return newNode + return waitForMeasuredNode(newNode.id) + }) + .then((measuredNode) => { + repositionForceUpdate(measuredNode) + }) + return node + // reposition after measurement to ensure proper placement/spacing } async function addNodeSequential( @@ -121,16 +133,20 @@ export function useFlowManagement({ //Generate reactflow object from the values setReactFlowNodes((previous) => { - const newNode = generateReactFlowObject(previous, { - id: resolvedId, - label, - parentId, - variableType, - variableTypeBasic: formatType?.properties.find((a) => a.name == variableType)?.type, - defaultValue: defaultValue ?? '', - width: getGroupWidth(parentId, reactFlowInstance.getNode), - isAttribute, - }) + const newNode = generateReactFlowObject( + previous, + { + id: resolvedId, + label, + parentId, + variableType, + variableTypeBasic: formatType?.properties.find((a) => a.name == variableType)?.type, + defaultValue: defaultValue ?? '', + width: getGroupWidth(parentId, reactFlowInstance.getNode), + isAttribute, + }, + reactFlowInstance.getNode, + ) return [...previous, newNode] }) @@ -369,11 +385,19 @@ export function useFlowManagement({ } async function importSchematic(file: File, side: 'source' | 'target', name = '') { + deleteNode(`${side}-import-button`) + let parentId = null if (side === 'target') { await clearTarget() } - if (name && side == 'source') { + + if ( + name && + side == 'source' && + reactFlowInstance.getNodes().filter((node) => node.id.includes('source') && !node.id.includes('import')) + .length !== 1 + ) { parentId = await addNodeSequential(side, name, 'schematic') } const text = await file.text() @@ -383,24 +407,12 @@ export function useFlowManagement({ } else if (config.formatTypes[side]?.schemaFileExtension === '.xsd') { await importXsdSchema(text, side, parentId, addNodeSequential, config.formatTypes[side]) // pass raw XML text } - } - - async function importMultipleSchematics(sourceSchematics: SourceSchematic[]) { - for (const schematic of sourceSchematics.toSorted((schematicA, schematicB) => { - // Wierd sorting function, but basically this make sure any without a name are placed first in the list, to ensure the base is at the top of the list - const aName = schematicA.name ?? '' - const bName = schematicB.name ?? '' - if (aName === '' && bName !== '') return -1 - if (aName !== '' && bName === '') return 1 - if (aName === '' && bName === '') return 0 - - // TS now knows both are strings - return aName.localeCompare(bName) - })) { - await importSchematic(schematic.file, 'source', schematic.name) + if (side === 'source') { + await addSchematicImportButton(side) } } + return { addNodeSequential, editNode, @@ -416,9 +428,9 @@ export function useFlowManagement({ deleteMapping, calculateTablePositions, importSchematic, - importMultipleSchematics, highlightUnset, checkForDragScroll, endCheckForDragScroll, + addSchematicImportButton, } } diff --git a/src/main/frontend/app/routes/datamapper/initialize.tsx b/src/main/frontend/app/routes/datamapper/initialize.tsx index 9d13fb89..7df1f53a 100644 --- a/src/main/frontend/app/routes/datamapper/initialize.tsx +++ b/src/main/frontend/app/routes/datamapper/initialize.tsx @@ -1,10 +1,7 @@ -import { type Dispatch, useState } from 'react' -import SourceDefinitionComponent from '~/components/datamapper/source-schema-definition' -import UploadImportButton from '~/components/datamapper/upload-import-button' +import { type Dispatch } from 'react' import Button from '~/components/inputs/button' import Dropdown from '~/components/inputs/dropdown' import type { ConfigActions } from '~/stores/datamapper_state/mappingListConfig/reducer' -import { useFile } from '~/stores/datamapper_state/schemaQueue/schema-queue-context' import type { MappingListConfig } from '~/types/datamapper_types/config-types' import type { DataTypeSchema } from '~/types/datamapper_types/data-types' import datatypesJson from '~/utils/datamapper_utils/config/data-types.json' @@ -18,8 +15,6 @@ interface InitializeProperties { function Initialize({ config, configDispatch, confirmed, setRoute }: InitializeProperties) { const datatypes: DataTypeSchema = datatypesJson as DataTypeSchema - const [sources, setSources] = useState([]) - const { sourceSchematics, targetSchematic } = useFile() const findDataType = (name: string) => datatypes.find((dataType) => dataType.name === name) ?? null @@ -38,19 +33,6 @@ function Initialize({ config, configDispatch, confirmed, setRoute }: InitializeP return (
- {/* Import / Export Buttons */} - - {/* Source & Target type Selection */}
{/* Source */} @@ -87,40 +69,14 @@ function Initialize({ config, configDispatch, confirmed, setRoute }: InitializeP
- - {/* Extra source section */} - {confirmed && ( -
- {/* Source Section */} -
- - - {sources.length > 0 && ( -
- {sources.map((id) => ( - setSources(sources.filter((item) => item !== id))} - /> - ))} -
- )} -
- - {/* Target Section */} -
- -
-
- )}
) } diff --git a/src/main/frontend/app/routes/datamapper/property-list.tsx b/src/main/frontend/app/routes/datamapper/property-list.tsx index 78d41784..313a5655 100644 --- a/src/main/frontend/app/routes/datamapper/property-list.tsx +++ b/src/main/frontend/app/routes/datamapper/property-list.tsx @@ -19,7 +19,6 @@ import { getNodeTypes } from '~/components/datamapper/react-flow/node-types' import { showErrorToast, showSuccessToast } from '~/components/toast' import { useFlowManagement } from '~/hooks/use-datamapper-flow-management' import { type ConfigActions } from '~/stores/datamapper_state/mappingListConfig/reducer' -import { useFile } from '~/stores/datamapper_state/schemaQueue/schema-queue-context' import type { CustomNodeData, MappingNodeData, NodeLabels } from '~/types/datamapper_types/react-node-types' import { TABLE_WIDTH } from '~/utils/datamapper_utils/constant' import { @@ -100,7 +99,6 @@ function PropertyList({ config, configDispatch }: PropertyListProperties) { setReactFlowNodes, setEdges, }) - const { sourceSchematics, targetSchematic, clearFiles } = useFile() const openMapping = useCallback(() => { requestAnimationFrame(() => { @@ -198,11 +196,19 @@ function PropertyList({ config, configDispatch }: PropertyListProperties) { const onRestore = useCallback(() => { const restoreFlow = async () => { - if (config.propertyData) flow.importJsonConfiguration(JSON.stringify(config.propertyData)) + if (config.propertyData) await flow.importJsonConfiguration(JSON.stringify(config.propertyData)) } - restoreFlow() - }, [config.propertyData, flow]) + restoreFlow().then(() => { + flow.deleteNode('target-import-button') + requestAnimationFrame(() => { + flow.addSchematicImportButton('source') + if (reactFlowInstance.getNodes().filter((node) => node.id.includes('target')).length == 1) { + flow.addSchematicImportButton('target') + } + }) + }) + }, [config.propertyData, flow, reactFlowInstance]) useEffect(() => { if (!reactFlowInstance || initHasRun.current) return @@ -210,32 +216,11 @@ function PropertyList({ config, configDispatch }: PropertyListProperties) { if (config.propertyData.nodes && config.propertyData.nodes.length > 1) { onRestore() + } else { + flow.addSchematicImportButton('source') + flow.addSchematicImportButton('target') } - const loadSchematics = async () => { - try { - if (config.propertyData.nodes && config.propertyData.nodes.length > 1) { - onRestore() - } - - if (targetSchematic) { - await flow.importSchematic(targetSchematic, 'target') - } - - if (sourceSchematics.length > 0) { - await flow.importMultipleSchematics(sourceSchematics) - } - } catch (error) { - if (error instanceof Error) { - showErrorToast(error.message) - } - } finally { - clearFiles() - } - } - - loadSchematics() - clearFiles() - }, [clearFiles, config.propertyData.nodes, flow, onRestore, reactFlowInstance, sourceSchematics, targetSchematic]) + }, [config.propertyData.nodes, flow, onRestore, reactFlowInstance]) const onReactFlowNodeChange = useCallback( (changes: NodeChange[]) => setReactFlowNodes((nodes) => applyNodeChanges(changes, nodes) as Node[]), diff --git a/src/main/frontend/app/routes/datamapper/root.tsx b/src/main/frontend/app/routes/datamapper/root.tsx index 42f958f0..c43a041f 100644 --- a/src/main/frontend/app/routes/datamapper/root.tsx +++ b/src/main/frontend/app/routes/datamapper/root.tsx @@ -5,11 +5,9 @@ import { DEFAULT_MAPPING_LIST_CONFIG, mappingListConfigReducer, } from '~/stores/datamapper_state/mappingListConfig/reducer' - import Initialize from './initialize' import MappingTable from './mapping-table' import { ReactFlowProvider } from '@xyflow/react' -import { FileProvider } from '~/stores/datamapper_state/schemaQueue/schema-queue-context' import Button from '~/components/inputs/button' import { saveDatamapperConfiguration, fetchDatamapperConfiguration } from '~/services/datamapper-service' import { useProjectStore } from '~/stores/project-store' @@ -46,14 +44,6 @@ export default function Root() { loadConfig() }, [project]) - const handleManualConfigExport = () => { - const dataString = `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(mappingListConfig))}` - const link = document.createElement('a') - link.href = dataString - link.download = 'flow_configuration.json' - link.click() - } - const [confirmed, setConfirmed] = useState(mappingListConfig.stage != 'INIT') useEffect(() => { @@ -80,68 +70,59 @@ export default function Root() { return ( - - {/* These buttons only exists for testing purposes, ignore the styling on these, they will be removed in later stages */} -
- - - -
+ {/* These buttons only exists for testing purposes, ignore the styling on these, they will be removed in later stages */} +
+ +
+ + {/* Center modal/container */} +
+
+ {/* Header */} +
+ Mapping (mappingName) +
- {/* Center modal/container */} -
-
- {/* Header */} -
- Mapping (mappingName) + {/* Route buttons */} + {mappingListConfig.stage == 'Mapping' && ( +
+ {routes + .filter((routeName) => routeName != 'Initialize') + .map((routeName) => ( + + ))}
- - {/* Route buttons */} - {mappingListConfig.stage == 'Mapping' && ( -
- {routes - .filter((routeName) => routeName != 'Initialize') - .map((routeName) => ( - - ))} -
+ )} + + {/* Main content */} +
+ {route === 'Initialize' && ( + + )} + {route === 'Properties' && ( + + )} + {route === 'Mappings' && ( + )} - - {/* Main content */} -
- {route === 'Initialize' && ( - - )} - {route === 'Properties' && ( - - )} - {route === 'Mappings' && ( - - )} -
- +
) } diff --git a/src/main/frontend/app/services/datamapper-service.ts b/src/main/frontend/app/services/datamapper-service.ts index 98d4e6c2..47230e5b 100644 --- a/src/main/frontend/app/services/datamapper-service.ts +++ b/src/main/frontend/app/services/datamapper-service.ts @@ -5,7 +5,7 @@ export async function fetchDatamapperConfiguration(projectName: string): Promise const data = await apiFetch(`/datamapper/${encodeURIComponent(projectName)}/configuration`, { method: 'GET', }) - console.dir(data) + return data } diff --git a/src/main/frontend/app/stores/datamapper_state/schemaQueue/schema-queue-context.tsx b/src/main/frontend/app/stores/datamapper_state/schemaQueue/schema-queue-context.tsx deleted file mode 100644 index f6454911..00000000 --- a/src/main/frontend/app/stores/datamapper_state/schemaQueue/schema-queue-context.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { createContext, useState, useContext, type ReactNode } from 'react' - -export interface SourceSchematic { - file: File - name?: string -} - -export interface FileContextType { - sourceSchematics: SourceSchematic[] - targetSchematic: File | null - addSourceSchematic: (schematic: SourceSchematic) => void - setTargetSchematic: (file: File | null) => void - clearFiles: () => void - deleteSourceSchema: (name: string) => void -} - -const FileContext = createContext(undefined) - -interface FileProviderProperties { - children: ReactNode -} - -export const FileProvider = ({ children }: FileProviderProperties) => { - const [sourceSchematics, setSourceSchematics] = useState([]) - const [targetSchematic, setTargetSchematic] = useState(null) - - const addSourceSchematic = (schematicToAdd: SourceSchematic) => { - setSourceSchematics((previous) => { - // Filter out any existing schematic with the same name - const filtered = previous.filter((schematic) => schematic.name !== schematicToAdd.name) - // Add the new schematic - return [...filtered, schematicToAdd] - }) - } - const clearFiles = () => { - setSourceSchematics([]) - setTargetSchematic(null) - } - - const deleteSourceSchema = (name: string) => { - setSourceSchematics(sourceSchematics.filter((schema) => schema.name != name)) - } - - return ( - - {children} - - ) -} - -export const useFile = (): FileContextType => { - const context = useContext(FileContext) - if (!context) { - throw new Error('useFile must be used within a FileProvider') - } - return context -} diff --git a/src/main/frontend/app/utils/datamapper_utils/function-utils.ts b/src/main/frontend/app/utils/datamapper_utils/function-utils.ts index 7096b7df..54d81d55 100644 --- a/src/main/frontend/app/utils/datamapper_utils/function-utils.ts +++ b/src/main/frontend/app/utils/datamapper_utils/function-utils.ts @@ -1,7 +1,6 @@ import type { Condition, Mutation } from '~/types/datamapper_types/function-types' export function generateMutationName(mutation: Mutation) { - console.dir(mutation) const inputs = mutation.inputs .map((i) => { return i ? i.value : '' diff --git a/src/main/frontend/app/utils/datamapper_utils/property-node-utils.ts b/src/main/frontend/app/utils/datamapper_utils/property-node-utils.ts index ad9b82c3..7cd92ece 100644 --- a/src/main/frontend/app/utils/datamapper_utils/property-node-utils.ts +++ b/src/main/frontend/app/utils/datamapper_utils/property-node-utils.ts @@ -3,6 +3,7 @@ import type { CustomNodeData, NodeLabels } from '~/types/datamapper_types/react- import { findNodeParent } from './generic-node-utils' import type { FormatDefinition } from '~/types/datamapper_types/data-types' import { GROUP_PADDING_TOP, GROUP_WIDTH, ITEM_GAP, OBJECT_HEIGHT } from './constant' +import type { GetNodeFunc, SequentialRepositionFn } from '~/hooks/use-datamapper-flow-management' export function recurseFindArray(node: Node, nodes: Node[]) { const parent = findNodeParent(node, nodes) @@ -126,7 +127,6 @@ export function updateNodeType(data: CustomNodeData, formatType?: FormatDefiniti return { updatedReactflowType, variableTypeBasic } } -type SequentialRepositionFn = (nodes: Node[], parentId: string) => Node[] export function deleteNodeById( nodes: Node[], @@ -145,7 +145,6 @@ export function deleteNodeById( return { updatedNodes, deletedNode: nodeToDelete } } -export type GetNodeFunc = (id: string) => Node | undefined export function sequentialReposition(nodes: Node[], startParentId: string, getNodeFunc: GetNodeFunc): Node[] { let parentId: string | null = startParentId @@ -197,6 +196,28 @@ export function calculateNodePosition(previous: Node[], parentId: string, getNod return newY } +export function generateReactFlowObject(previous: Node[], data: CustomNodeData, getNode: GetNodeFunc): Node { + //Calculate the position the node is to be placed at. This isn't always very accurate and will be corrected later after adding + const newY = calculateNodePosition(previous, data.parentId, getNode) + //Set the correct type of the node + + //Create the node Obj + const newNode: Node = { + id: data.id, + position: { x: 10, y: newY }, + parentId: data.parentId, + extent: 'parent', + type: getReactflowType(data.variableType, data.parentId), + data, + } + + //Add empty padding in case the item is an object, purely visual + if (isGroup(data.variableType)) { + newNode.height = GROUP_PADDING_TOP * 3 + } + + return newNode +} export function getGroupWidth(parentId: string, getNode: GetNodeFunc): number { const parentNode = getNode(parentId) diff --git a/src/main/frontend/app/utils/datamapper_utils/schema-utils.ts b/src/main/frontend/app/utils/datamapper_utils/schema-utils.ts index 5f99609e..a7f6580c 100644 --- a/src/main/frontend/app/utils/datamapper_utils/schema-utils.ts +++ b/src/main/frontend/app/utils/datamapper_utils/schema-utils.ts @@ -1,16 +1,9 @@ import { SAXParser } from 'sax-ts' +import type { AddNodeFunction, GetNodeFunc, ImportSchematicFunc } from '~/hooks/use-datamapper-flow-management' import type { FormatDefinition } from '~/types/datamapper_types/data-types' import type { JsonSchema, SaxAttributes, XsdComplexType, XsdElement } from '~/types/datamapper_types/schema-types' - -export type AddNodeFunction = ( - side: 'source' | 'target', - label: string, - variableType: string, - defaultValue?: string | null, - parentId?: string | null, - id?: string | null, - isAttribute?: boolean, -) => Promise +import { calculateNodePosition } from './property-node-utils' +import type { Node } from '@xyflow/react' export async function importJsonSchema( schema: JsonSchema, @@ -204,3 +197,25 @@ async function traverseComplexType( } } } + +export async function generateImportButton( + nodes: Node[], + fileType: string, + side: string, + getNode: GetNodeFunc, + importFunc: ImportSchematicFunc, +) { + const newY = calculateNodePosition(nodes, `${side}-table`, getNode) + return { + id: `${side}-import-button`, + parentId: `${side}-table`, + type: 'importSchematicNode', + position: { x: 10, y: newY }, + extent: 'parent', + data: { + fileType, + side, + importFunc, + }, + } as Node +} diff --git a/src/main/frontend/icons/solar/Code File.svg b/src/main/frontend/icons/solar/Code File.svg new file mode 100644 index 00000000..ad922cef --- /dev/null +++ b/src/main/frontend/icons/solar/Code File.svg @@ -0,0 +1,8 @@ + + + + \ No newline at end of file