You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ma...@apache.org on 2023/03/17 18:35:38 UTC

[camel-karavan] branch data-mapper-prototype created (now ba8562eb)

This is an automated email from the ASF dual-hosted git repository.

marat pushed a change to branch data-mapper-prototype
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git


      at ba8562eb Prototype #693

This branch includes the following new commits:

     new ba8562eb Prototype #693

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[camel-karavan] 01/01: Prototype #693

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

marat pushed a commit to branch data-mapper-prototype
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git

commit ba8562ebfd46ec438140666d62c5330a13edbf89
Author: Marat Gubaidullin <ma...@gmail.com>
AuthorDate: Fri Mar 17 14:35:25 2023 -0400

    Prototype #693
---
 karavan-designer/src/App.tsx                       |  12 +-
 karavan-designer/src/data/DataMapper.tsx           | 192 +++++++++++++++++++++
 karavan-designer/src/data/DataMapperModel.tsx      | 118 +++++++++++++
 .../src/data/DataMappingConnections.tsx            |  61 +++++++
 karavan-designer/src/data/DataTreeItem.tsx         | 108 ++++++++++++
 karavan-designer/src/data/datamapper.css           | 118 +++++++++++++
 karavan-designer/src/index.css                     |   1 +
 7 files changed, 607 insertions(+), 3 deletions(-)

diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx
index e536a093..b7bba3a9 100644
--- a/karavan-designer/src/App.tsx
+++ b/karavan-designer/src/App.tsx
@@ -34,6 +34,7 @@ import {KaravanIcon} from "./designer/utils/KaravanIcons";
 import './designer/karavan.css';
 import {DesignerPage} from "./DesignerPage";
 import {TemplateApi} from "karavan-core/lib/api/TemplateApi";
+import {DataMapper} from "./data/DataMapper";
 
 class ToastMessage {
     id: string = ''
@@ -76,7 +77,7 @@ interface State {
 class App extends React.Component<Props, State> {
 
     public state: State = {
-        pageId: "designer",
+        pageId: "datamapper",
         alerts: [],
         name: 'example.yaml',
         key: '',
@@ -106,13 +107,13 @@ class App extends React.Component<Props, State> {
             const kamelets: string[] = [];
             data[0].split("\n---\n").map(c => c.trim()).forEach(z => kamelets.push(z));
             KameletApi.saveKamelets(kamelets, true);
-            this.toast("Success", "Loaded " + kamelets.length + " kamelets", 'success');
+            // this.toast("Success", "Loaded " + kamelets.length + " kamelets", 'success');
 
             const jsons: string[] = [];
             JSON.parse(data[1]).forEach((c: any) => jsons.push(JSON.stringify(c)));
             ComponentApi.saveComponents(jsons, true);
 
-            this.toast("Success", "Loaded " + jsons.length + " components", 'success');
+            // this.toast("Success", "Loaded " + jsons.length + " components", 'success');
             this.setState({loaded: true});
 
             TemplateApi.saveTemplate("org.apache.camel.AggregationStrategy", data[2]);
@@ -147,6 +148,7 @@ class App extends React.Component<Props, State> {
             new MenuItem("eip", "Enterprise Integration Patterns", <EipIcon/>),
             new MenuItem("kamelets", "Kamelets", <KameletsIcon/>),
             new MenuItem("components", "Components", <ComponentsIcon/>),
+            new MenuItem("datamapper", "Data Mapper", <ComponentsIcon/>),
         ]
         return (<Flex className="nav-buttons" direction={{default: "column"}} style={{height: "100%"}}
                       spaceItems={{default: "spaceItemsNone"}}>
@@ -196,6 +198,10 @@ class App extends React.Component<Props, State> {
                 return (
                     <EipPage dark={dark}/>
                 )
+            case "datamapper":
+                return (
+                    <DataMapper dark={dark}/>
+                )
         }
     }
 
diff --git a/karavan-designer/src/data/DataMapper.tsx b/karavan-designer/src/data/DataMapper.tsx
new file mode 100644
index 00000000..3796b118
--- /dev/null
+++ b/karavan-designer/src/data/DataMapper.tsx
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React, {RefObject} from 'react';
+import {
+    Button,
+    PageSection,
+    PageSectionVariants,
+    Tooltip,
+    TreeView
+} from '@patternfly/react-core';
+import '../designer/karavan.css';
+import './datamapper.css';
+import AngleDoubleRightIcon from '@patternfly/react-icons/dist/esm/icons/arrow-alt-circle-right-icon';
+import CollapseIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-right-icon';
+import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-down-icon';
+import DownloadIcon from "@patternfly/react-icons/dist/esm/icons/download-icon";
+import DownloadImageIcon from "@patternfly/react-icons/dist/esm/icons/image-icon";
+import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon";
+import {DataMappingConnections} from "./DataMappingConnections";
+import {TreeViewDataItem} from "@patternfly/react-core/components";
+import {DataTreeItem} from "./DataTreeItem";
+import {ConnectionPoint, ConnectionsRect, Exchange, ExchangeElement} from "./DataMapperModel";
+
+interface Props {
+    dark: boolean
+    tab?: string
+}
+
+interface State {
+    source: any [],
+    target: any [],
+    transformation: any [],
+    activeItems1: any [],
+    activeItems2: any [],
+    ref1: RefObject<HTMLDivElement>,
+    ref2: RefObject<HTMLDivElement>,
+    connections: ConnectionsRect,
+    startingPoint: ConnectionPoint,
+    movingPoint: ConnectionPoint
+}
+
+export class DataMapper extends React.Component<Props, State> {
+
+    state: State = {
+        activeItems1: [],
+        activeItems2: [],
+        source: [new Exchange({defaultExpanded: true})],
+        target: [new Exchange({defaultExpanded: true})],
+        transformation: [new ExchangeElement({id: "xxx", name: "new java.util.Date()", customBadgeContent: "java"})],
+        ref1: React.createRef(),
+        ref2: React.createRef(),
+        connections: {top: 0, left: 0, width: 0, height: 0},
+        startingPoint: new ConnectionPoint(0,0),
+        movingPoint: new ConnectionPoint(2000,2000)
+    }
+
+    ref1: RefObject<HTMLDivElement> = React.createRef();
+    ref2: RefObject<HTMLDivElement> = React.createRef();
+
+    interval: any;
+
+    componentDidMount() {
+        this.onRefresh();
+        this.interval = setInterval(() => this.onRefresh(), 300);
+    }
+
+    componentWillUnmount() {
+        clearInterval(this.interval);
+    }
+
+    onRefresh = () => {
+        const source = this.ref1.current?.children[0]?.children[0]?.getBoundingClientRect();
+        const target = this.ref2.current?.children[0]?.children[0]?.getBoundingClientRect();
+        const sourceTop = source?.top || 0;
+        const sourceLeft = source?.left || 0;
+        const sourceHeight = source?.height || 0;
+        const sourceWidth = source?.width || 0;
+        const targetTop = target?.top || 0;
+        const targetLeft = target?.left || 0;
+        const targetHeight = target?.height || 0;
+        const targetWidth = target?.width || 0;
+        const height = (sourceTop + sourceHeight > targetTop + targetHeight)
+            ? (sourceTop + sourceHeight)
+            : (targetTop + targetHeight);
+        const width = targetLeft + targetWidth - sourceLeft;
+        this.setState({connections: new ConnectionsRect(width, height, sourceTop, sourceLeft)})
+    }
+
+
+    onSelect = (evt: any, treeViewItem: any) => {
+        // Ignore folders for selection
+        if (treeViewItem && !treeViewItem.children) {
+            this.setState({
+                activeItems1: [treeViewItem],
+                activeItems2: [treeViewItem]
+            });
+        }
+    }
+
+    onDragStart = (rect: DOMRect) => {
+        const top = rect.top + (rect.height / 2);
+        const left = rect.left + rect.width;
+        this.setState({startingPoint: new ConnectionPoint(top, left)})
+    }
+
+    onMoving = (clientX: number, clientY: number) => {
+        this.setState({movingPoint: new ConnectionPoint(clientY, clientX)})
+    }
+
+    private onMapElements(source: ExchangeElement, target: ExchangeElement) {
+        this.setState({startingPoint: new ConnectionPoint(0, 0), movingPoint: new ConnectionPoint(0, 0),})
+    }
+
+    convertTreeItem(items: any [], type: 'source' | 'target' | 'transformation'): TreeViewDataItem[] {
+        return items.map((value: any) => this.convertTreeItems(value, type));
+    }
+
+    convertTreeItems(value: any, type: 'source' | 'target' | 'transformation'): TreeViewDataItem {
+        return {
+            id: value.id,
+            name: <DataTreeItem element={value}
+                                type={type}
+                                onDragStart={this.onDragStart}
+                                onMoving={this.onMoving}
+                                onMapElements={(source, target) => this.onMapElements(source, target)}/>,
+            children: value.children ? this.convertTreeItem(value.children, type) : undefined,
+            defaultExpanded: value.id === 'exchange',
+            action: value.id === 'headers' ? <Button variant={"plain"} icon={<AddIcon/>}/> : undefined,
+            customBadgeContent: value.customBadgeContent
+        }
+    }
+
+    render() {
+        const {activeItems1, startingPoint, movingPoint, source, target, transformation, connections} = this.state;
+        return (
+            <PageSection variant={this.props.dark ? PageSectionVariants.darker : PageSectionVariants.light} className="page" isFilled padding={{default: 'noPadding'}}>
+                <DataMappingConnections rect={connections} moving={movingPoint} starting={startingPoint}/>
+                <div className="exchange-mapper">
+                    <div className="exchange-tree-panel">
+                        <div className="data-toolbar">
+                            <div className="panel-header">
+                                Source
+                            </div>
+                        </div>
+                        <div className="exchange-tree-parent source" ref={this.ref1}>
+                            <TreeView data={this.convertTreeItem(source, 'source')} activeItems={activeItems1} hasBadges hasGuides/>
+                        </div>
+                    </div>
+                    <div className="exchange-tree-panel">
+                        <div className="data-toolbar">
+                            <div className="panel-header">
+                                Transformation
+                            </div>
+                            <Tooltip content="Add Transformation" position={"left"}>
+                                <Button variant="plain" icon={<AddIcon/>} onClick={e => {
+                                }}>
+                                </Button>
+                            </Tooltip>
+                        </div>
+                        <div className="exchange-tree-parent transformation">
+                            <TreeView data={transformation} hasSelectableNodes={false}/>
+                        </div>
+                    </div>
+                    <div className="exchange-tree-panel">
+                        <div className="data-toolbar">
+                            <div className="panel-header">
+                                Target
+                            </div>
+                        </div>
+                        <div className="exchange-tree-parent target" ref={this.ref2}>
+                            <TreeView data={this.convertTreeItem(target, 'target')} hasBadges hasGuides/>
+                        </div>
+                    </div>
+                </div>
+            </PageSection>
+        )
+    }
+}
\ No newline at end of file
diff --git a/karavan-designer/src/data/DataMapperModel.tsx b/karavan-designer/src/data/DataMapperModel.tsx
new file mode 100644
index 00000000..34c1854b
--- /dev/null
+++ b/karavan-designer/src/data/DataMapperModel.tsx
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export class ExchangeElement {
+    id: string = ''
+    name: string = ''
+    customBadgeContent: string = ''
+
+    public constructor(init?: Partial<ExchangeElement>) {
+        Object.assign(this, init);
+    }
+}
+
+export class ExchangeHeader extends ExchangeElement {
+
+}
+
+export class ExchangeProperty extends ExchangeElement {
+
+}
+
+export class ExchangeElementWithChildren extends ExchangeElement {
+    children: ExchangeElement[] = []
+    defaultExpanded: boolean = false;
+
+    public constructor(init?: Partial<ExchangeElementWithChildren>) {
+        super(init);
+        Object.assign(this, init);
+    }
+}
+
+export class ExchangeHeaders extends ExchangeElementWithChildren {
+
+    public constructor(init?: Partial<Body>) {
+        super(init);
+        this.name = "Headers";
+        this.id = "headers";
+        this.customBadgeContent = "Map<String, Object>";
+        this.children = Array.from(Array(10).keys()).map(value => new ExchangeHeader({id: "id" + value, name: "header" + value, customBadgeContent:"String"}));
+    }
+}
+
+export class ExchangeProperties extends ExchangeElementWithChildren {
+    public constructor(init?: Partial<Body>) {
+        super(init);
+        this.name = "Properties";
+        this.id = "properties";
+        this.customBadgeContent = "Map<String, Object>";
+    }
+}
+
+export class Body extends ExchangeElement {
+
+    public constructor(init?: Partial<Body>) {
+        super(init);
+        this.name = "Body";
+        this.id = "body";
+        this.customBadgeContent = "Object";
+    }
+}
+
+export class Exchange extends ExchangeElementWithChildren {
+
+    public constructor(init?: Partial<Exchange>) {
+        super(init);
+        this.customBadgeContent = "Exchange";
+        if (init?.name === undefined) {
+            this.name = "Exchange";
+        }
+        if (init?.id === undefined) {
+            this.id = "exchange";
+        }
+        if (!init?.children) {
+            this.children.push(new ExchangeElement({id:"exchangeId", name: "Exchange ID", customBadgeContent: "String"}))
+            this.children.push(new ExchangeHeaders())
+            this.children.push(new ExchangeProperties())
+            this.children.push(new Body())
+        }
+    }
+}
+
+export class ConnectionsRect {
+    width: number = 0
+    height: number = 0
+    top: number = 0
+    left: number = 0
+
+    constructor(width: number, height: number, top: number, left: number) {
+        this.width = width;
+        this.height = height;
+        this.top = top;
+        this.left = left;
+    }
+}
+
+export class ConnectionPoint {
+    top: number = 0
+    left: number = 0
+
+    constructor(top: number, left: number) {
+        this.top = top;
+        this.left = left;
+    }
+}
diff --git a/karavan-designer/src/data/DataMappingConnections.tsx b/karavan-designer/src/data/DataMappingConnections.tsx
new file mode 100644
index 00000000..a0521434
--- /dev/null
+++ b/karavan-designer/src/data/DataMappingConnections.tsx
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React from 'react';
+import './datamapper.css';
+import {ConnectionPoint, ConnectionsRect} from "./DataMapperModel";
+
+interface Props {
+    rect: ConnectionsRect
+    starting: ConnectionPoint
+    moving: ConnectionPoint
+}
+
+interface State {
+    connections:[]
+}
+
+export class DataMappingConnections extends React.Component<Props, State> {
+
+    public state: State = {
+        connections: [],
+    };
+
+    render() {
+        const {starting, moving} = this.props;
+        const {top, left, height, width} = this.props.rect;
+        const startX = starting.left - left;
+        const startY = starting.top - top;
+        const endX = moving.left - left;
+        const endY = moving.top - top;
+        const middleX = (endX + startX) / 2;
+        console.log(`M ${startX},${startY} C ${middleX},${startY} ${middleX},${endY}   ${endX},${endY}`);
+        return (
+            <svg className="data-mapping-connection"
+                style={{width: width, height: height, position: "absolute", left: left, top: top, backgroundColor: "transparent", zIndex:0}}
+                viewBox={"0 0 " + width + " " + height}>
+                <defs>
+                    <marker id="arrowhead" markerWidth="9" markerHeight="6" refX="0" refY="3" orient="auto" className="arrow">
+                        <polygon points="0 0, 9 3, 0 6"/>
+                    </marker>
+                </defs>
+                {starting.top !==0 && moving.top !== 0
+                    && <path name={"moving"} d={`M ${startX},${startY} C ${middleX},${startY} ${middleX},${endY}   ${endX},${endY}`}
+                             className="path" key={"moving"} markerEnd="url(#arrowhead)"/>}
+            </svg>
+        );
+    }
+}
diff --git a/karavan-designer/src/data/DataTreeItem.tsx b/karavan-designer/src/data/DataTreeItem.tsx
new file mode 100644
index 00000000..52d300db
--- /dev/null
+++ b/karavan-designer/src/data/DataTreeItem.tsx
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React, {RefObject} from 'react';
+import './datamapper.css';
+import '../designer/karavan.css';
+import {ExchangeElement} from "./DataMapperModel";
+
+interface Props {
+    element: ExchangeElement
+    type: 'source' | 'target' | 'transformation'
+    onMapElements: (source: ExchangeElement, target: ExchangeElement) => void
+    onDragStart: (rect: DOMRect) => void
+    onMoving: (clientX: number, clientY: number) => void
+}
+
+interface State {
+    isDragging: boolean
+    isDraggedOver: boolean
+}
+
+export class DataTreeItem extends React.Component<Props, State> {
+
+    public state: State = {
+        isDragging: false,
+        isDraggedOver: false
+    }
+
+    ref:RefObject<HTMLDivElement> = React.createRef();
+
+    render() {
+        const {isDragging, isDraggedOver} = this.state;
+        const {element, type} = this.props;
+        const className = "exchange-item" ;
+        return (
+            <div key={"root-" + element.id}
+                 className={className}
+                 ref={this.ref}
+                 style={{
+                     // borderStyle: this.hasBorder() ? "dotted" : "none",
+                     // borderColor: this.isSelected() ? "var(--step-border-color-selected)" : "var(--step-border-color)",
+                     // marginTop: this.isInStepWithChildren() ? "16px" : "8px",
+                     // zIndex: element.dslName === 'ToDefinition' ? 20 : 10,
+                     boxShadow: isDraggedOver && type !== 'source' ? "0px 0px 1px 2px var(--step-border-color-selected)" : "none",
+                 }}
+                 onMouseOver={event => event.stopPropagation()}
+                 // onClick={event => this.selectElement(event)}body
+                 onDragStart={event => {
+                     event.stopPropagation();
+                     event.dataTransfer.setData("text/plain", element.id);
+                     (event.target as any).style.opacity = 1.5;
+                     (event.target as any).style.borderRadius = 16;
+                     this.setState({isDragging: true});
+                     const rect = this.ref.current?.getBoundingClientRect();
+                     if (rect) this.props.onDragStart?.call(this, rect);
+                 }}
+                 onDragEnd={event => {
+                     (event.target as any).style.opacity = '';
+                     this.setState({isDragging: false});
+                 }}
+                 onDragOver={event => {
+                     event.preventDefault();
+                     event.stopPropagation();
+                     if (element.id !== 'exchange') {
+                         this.setState({isDraggedOver: true});
+                     }
+                 }}
+                 onDragEnter={event => {
+                     event.preventDefault();
+                     event.stopPropagation();
+                     if (element.id !== 'exchange') {
+                         this.setState({isDraggedOver: true});
+                     }
+                 }}
+                 onDragLeave={event => {
+                     event.preventDefault();
+                     event.stopPropagation();
+                     this.setState({isDraggedOver: false});
+                 }}
+                 onDrag ={event => {
+                     this.props.onMoving?.call(this, event.nativeEvent.clientX, event.nativeEvent.clientY);
+                 }}
+                 onDrop={event => this.dragElement(event, element)}
+                 draggable={element.id !== 'exchange'}
+            >
+                {element.name}
+            </div>
+        );
+    }
+
+    private dragElement(event: React.DragEvent<HTMLDivElement>, element: ExchangeElement) {
+        this.props.onMapElements?.call(this, this.props.element, element);
+        this.setState({isDraggedOver: false});
+    }
+}
diff --git a/karavan-designer/src/data/datamapper.css b/karavan-designer/src/data/datamapper.css
new file mode 100644
index 00000000..7e624617
--- /dev/null
+++ b/karavan-designer/src/data/datamapper.css
@@ -0,0 +1,118 @@
+.exchange-mapper {
+    display: flex;
+    flex-direction: row;
+    padding: 16px;
+    justify-content: space-between;
+    background-color: transparent;
+}
+
+.exchange-tree-panel {
+    display: flex;
+    flex-direction: column;
+    flex: 1;
+}
+
+.exchange-tree-panel .pf-c-tree-view {
+    --pf-c-tree-view__node--indent--base: calc(var(--pf-global--spacer--md) * 2 + var(--pf-c-tree-view__node-toggle-icon--MinWidth));
+    --pf-c-tree-view__node--nested-indent--base: calc(var(--pf-c-tree-view__node--indent--base) - var(--pf-global--spacer--md));
+    --pf-c-tree-view__node--hover--BackgroundColor: transtparent;
+    --pf-c-tree-view__node--focus--BackgroundColor: transtparent;
+}
+
+.exchange-tree-panel .pf-c-tree-view__list-item .pf-c-tree-view__list-item .pf-c-tree-view__list-item {
+    /*--pf-c-tree-view__node--PaddingLeft: calc(var(--pf-c-tree-view__node--nested-indent--base) * 2 + var(--pf-c-tree-view__node--indent--base));*/
+    --pf-c-tree-view__node--PaddingLeft: calc(var(--pf-c-tree-view__node--nested-indent--base) * 2 + var(--pf-c-tree-view__node--indent--base)) ;
+}
+
+.exchange-tree-panel .pf-c-tree-view__node-count {
+    margin-top: auto;
+    margin-bottom: auto;
+}
+.exchange-tree-panel .pf-c-tree-view__node-count .pf-c-badge {
+    font-weight: 100;
+}
+
+/* width */
+.exchange-tree-parent::-webkit-scrollbar {
+    width: 10px;
+}
+/* Track */
+.exchange-tree-parent::-webkit-scrollbar-track {
+    background: #f1f1f1;
+}
+/* Handle */
+.exchange-tree-parent::-webkit-scrollbar-thumb {
+    background: #888;
+}
+/* Handle on hover */
+.exchange-tree-parent::-webkit-scrollbar-thumb:hover {
+    background: #555;
+}
+.exchange-tree-parent {
+    scrollbar-color: #9aa0a6 transparent;
+    scrollbar-width: thin;
+    scroll-behavior: smooth;
+    display: flex;
+    height: 100vh;
+    overflow: auto;
+}
+
+.exchange-tree-parent .pf-c-tree-view {
+    width: 100%;
+    padding: 0;
+    border: 1px solid var(--pf-global--disabled-color--300);
+}
+
+.draggable-element {
+    visibility: hidden;
+}
+
+.pf-c-tree-view__node {
+    padding-top: 0;
+    padding-bottom: 0;
+    padding-right: 0;
+}
+
+.exchange-item {
+    padding: 6px 6px 6px 6px;
+    border-radius: 16px;
+}
+
+.dragging-over {
+    border-radius: 16px;
+}
+
+.karavan .data-mapping-connection .path {
+    stroke: var(--pf-global--Color--200);
+    stroke-width: 1;
+    fill: transparent;
+}
+
+.source .pf-c-tree-view__content:hover .draggable-element,
+.transformation .pf-c-tree-view__content:hover .draggable-element {
+    visibility: visible;
+}
+
+.transformation .pf-c-tree-view__content .pf-c-tree-view__node-text {
+    border-color: var(--pf-global--Color--400);
+    border-radius: 16px;
+    border-width: 1px;
+    border-style: solid;
+    padding: 3px 6px 3px 6px;
+}
+
+.data-toolbar {
+    padding: 3px 6px 3px 6px;
+    display: flex;
+    justify-content: space-between;
+}
+.data-toolbar .pf-c-button {
+    padding: 0;
+}
+
+.panel-header {
+    padding-left: 6px;
+    padding-bottom: 3px;
+    font-size: 14px;
+    font-weight: bold;
+}
\ No newline at end of file
diff --git a/karavan-designer/src/index.css b/karavan-designer/src/index.css
index e6a61da8..8b2fe783 100644
--- a/karavan-designer/src/index.css
+++ b/karavan-designer/src/index.css
@@ -3,6 +3,7 @@ body,
 #root,
 .App {
   height: 100%;
+  font-size: 14px;
 }
 
 #root {