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:39 UTC
[camel-karavan] 01/01: Prototype #693
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 {