refactor: back to jsx 🥇
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import { ConnectionLineComponentProps } from '../../src';
|
import { ConnectionLineComponentProps } from '../../src';
|
||||||
|
|
||||||
const ConnectionLine = defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
sourceX: {
|
sourceX: {
|
||||||
type: Number as PropType<ConnectionLineComponentProps['sourceX']>,
|
type: Number as PropType<ConnectionLineComponentProps['sourceX']>,
|
||||||
@@ -34,6 +34,4 @@ const ConnectionLine = defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ConnectionLine;
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<RevueFlow
|
<RevueFlow
|
||||||
v-model="elements"
|
v-model="elements"
|
||||||
:connectionLineComponent="ConnectionLine"
|
:connection-line-component="ConnectionLine"
|
||||||
:onElementsRemove="onElementsRemove"
|
@elementsRemove="onElementsRemove"
|
||||||
:onConnect="onConnect"
|
@connect="onConnect"
|
||||||
>
|
>
|
||||||
<Background :variant="bgVariant" />
|
<Background :variant="bgVariant" />
|
||||||
</RevueFlow>
|
</RevueFlow>
|
||||||
@@ -14,10 +14,17 @@ import RevueFlow, { removeElements, addEdge, Background, BackgroundVariant, Elem
|
|||||||
import ConnectionLine from './ConnectionLine.vue';
|
import ConnectionLine from './ConnectionLine.vue';
|
||||||
import { defineComponent, ref } from 'vue';
|
import { defineComponent, ref } from 'vue';
|
||||||
|
|
||||||
const ConnectionLineFlow = defineComponent({
|
export default defineComponent({
|
||||||
components: { RevueFlow, Background },
|
components: { RevueFlow, Background },
|
||||||
setup() {
|
setup() {
|
||||||
const elements = ref<Elements>([{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } }]);
|
const elements = ref<Elements>([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: 'input',
|
||||||
|
data: { label: 'Node 1' },
|
||||||
|
position: { x: 250, y: 5 }
|
||||||
|
}
|
||||||
|
] as Elements);
|
||||||
const onElementsRemove = (elementsToRemove: Elements) =>
|
const onElementsRemove = (elementsToRemove: Elements) =>
|
||||||
(elements.value = removeElements(elementsToRemove, elements.value as Elements));
|
(elements.value = removeElements(elementsToRemove, elements.value as Elements));
|
||||||
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value as Elements));
|
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value as Elements));
|
||||||
@@ -31,6 +38,4 @@ const ConnectionLineFlow = defineComponent({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ConnectionLineFlow;
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ const CustomNodeFlow = defineComponent({
|
|||||||
{ id: 'e1-2', source: '1', target: '2', animated: true, style: { stroke: '#fff' } },
|
{ id: 'e1-2', source: '1', target: '2', animated: true, style: { stroke: '#fff' } },
|
||||||
{ id: 'e2a-3', source: '2', sourceHandle: 'a', target: '3', animated: true, style: { stroke: '#fff' } },
|
{ id: 'e2a-3', source: '2', sourceHandle: 'a', target: '3', animated: true, style: { stroke: '#fff' } },
|
||||||
{ id: 'e2b-4', source: '2', sourceHandle: 'b', target: '4', animated: true, style: { stroke: '#fff' } }
|
{ id: 'e2b-4', source: '2', sourceHandle: 'b', target: '4', animated: true, style: { stroke: '#fff' } }
|
||||||
];
|
] as Elements;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onElementsRemove = (elementsToRemove: Elements) =>
|
const onElementsRemove = (elementsToRemove: Elements) =>
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dndflow">
|
<div class="dndflow">
|
||||||
<div class="revueflow-wrapper" @drop="onDrop">
|
<div class="revueflow-wrapper" @drop="onDrop">
|
||||||
<RevueFlow
|
<RevueFlow v-model="elements" @elementsRemove="onElementsRemove" @load="onLoad" @connect="onConnect" @dragover="onDragOver">
|
||||||
v-model="elements"
|
|
||||||
:onConnect="onConnect"
|
|
||||||
:onElementsRemove="onElementsRemove"
|
|
||||||
:onLoad="onLoad"
|
|
||||||
@dragover="onDragOver"
|
|
||||||
@click="onDrop"
|
|
||||||
>
|
|
||||||
<Controls />
|
<Controls />
|
||||||
</RevueFlow>
|
</RevueFlow>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,7 +36,7 @@ const DnDFlow = defineComponent({
|
|||||||
data: { label: 'input node' },
|
data: { label: 'input node' },
|
||||||
position: { x: 250, y: 5 }
|
position: { x: 250, y: 5 }
|
||||||
}
|
}
|
||||||
]);
|
] as Elements);
|
||||||
|
|
||||||
let id = 0;
|
let id = 0;
|
||||||
const getId = (): ElementId => `dndnode_${id++}`;
|
const getId = (): ElementId => `dndnode_${id++}`;
|
||||||
@@ -67,12 +60,12 @@ const DnDFlow = defineComponent({
|
|||||||
console.log(event.dataTransfer?.getData('application/revueflow'));
|
console.log(event.dataTransfer?.getData('application/revueflow'));
|
||||||
const type = event.dataTransfer?.getData('application/revueflow');
|
const type = event.dataTransfer?.getData('application/revueflow');
|
||||||
const position = revueFlowInstance.value.project({ x: event.clientX, y: event.clientY - 40 });
|
const position = revueFlowInstance.value.project({ x: event.clientX, y: event.clientY - 40 });
|
||||||
const newNode: Node = {
|
const newNode = {
|
||||||
id: getId(),
|
id: getId(),
|
||||||
type,
|
type,
|
||||||
position,
|
position,
|
||||||
data: { label: `${type} node` }
|
data: { label: `${type} node` }
|
||||||
};
|
} as Node;
|
||||||
|
|
||||||
elements.value.push(newNode);
|
elements.value.push(newNode);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@
|
|||||||
"lint": "yarn lint:js"
|
"lint": "yarn lint:js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braks/revue-draggable": "^0.2.4",
|
"@braks/revue-draggable": "^0.2.5",
|
||||||
"@types/d3": "^7.0.0",
|
"@types/d3": "^7.0.0",
|
||||||
"@vueuse/core": "^5.1.4",
|
"@vueuse/core": "^5.1.4",
|
||||||
"d3": "^7.0.0",
|
"d3": "^7.0.0",
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ const Controls = defineComponent({
|
|||||||
const store = inject<RevueFlowStore>('store')!;
|
const store = inject<RevueFlowStore>('store')!;
|
||||||
const isVisible = ref<boolean>(false);
|
const isVisible = ref<boolean>(false);
|
||||||
const zoomHelper = ref<ZoomPanHelperFunctions>();
|
const zoomHelper = ref<ZoomPanHelperFunctions>();
|
||||||
const { onReady } = useZoomPanHelper(store);
|
const { onReady } = useZoomPanHelper();
|
||||||
|
|
||||||
onReady((helper) => {
|
onReady((helper) => {
|
||||||
zoomHelper.value = helper;
|
zoomHelper.value = helper;
|
||||||
|
|||||||
+36
-44
@@ -1,23 +1,4 @@
|
|||||||
<template>
|
import { ref, defineComponent, CSSProperties, PropType, computed, inject, h } from 'vue';
|
||||||
<g v-show="sourceNode && nodesConnectable" class="revue-flow__connection">
|
|
||||||
<g v-if="CustomConnectionLineComponent" class="revue-flow__connection">
|
|
||||||
<component
|
|
||||||
:is="CustomConnectionLineComponent"
|
|
||||||
:sourceX="sourceX"
|
|
||||||
:sourceY="sourceY"
|
|
||||||
:sourcePosition="sourceHandle.position"
|
|
||||||
:targetX="targetX"
|
|
||||||
:targetY="targetY"
|
|
||||||
:targetPosition="targetPosition"
|
|
||||||
:connectionLineType="connectionLineType"
|
|
||||||
:connectionLineStyle="connectionLineStyle"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<path v-else class="revue-flow__connection-path" :d="dAttr" :style="connectionLineStyle" />
|
|
||||||
</g>
|
|
||||||
</template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { ref, defineComponent, CSSProperties, PropType, computed, inject } from 'vue';
|
|
||||||
|
|
||||||
import { getBezierPath } from '../Edges/BezierEdge';
|
import { getBezierPath } from '../Edges/BezierEdge';
|
||||||
import { getSmoothStepPath } from '../Edges/SmoothStepEdge';
|
import { getSmoothStepPath } from '../Edges/SmoothStepEdge';
|
||||||
@@ -26,23 +7,23 @@ import { Node, HandleElement, Position, ConnectionLineType, ConnectionLineCompon
|
|||||||
interface ConnectionLineProps {
|
interface ConnectionLineProps {
|
||||||
connectionLineType: ConnectionLineType;
|
connectionLineType: ConnectionLineType;
|
||||||
connectionLineStyle?: CSSProperties;
|
connectionLineStyle?: CSSProperties;
|
||||||
CustomConnectionLineComponent?: ConnectionLineComponent;
|
customConnectionLine?: ConnectionLineComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConnectionLine = defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
connectionLineType: {
|
|
||||||
type: String as PropType<ConnectionLineProps['connectionLineType']>,
|
|
||||||
required: false,
|
|
||||||
default: ConnectionLineType.Bezier
|
|
||||||
},
|
|
||||||
connectionLineStyle: {
|
connectionLineStyle: {
|
||||||
type: Object as PropType<ConnectionLineProps['connectionLineStyle']>,
|
type: Object as PropType<ConnectionLineProps['connectionLineStyle']>,
|
||||||
required: false,
|
required: false,
|
||||||
default: () => {}
|
default: () => {}
|
||||||
},
|
},
|
||||||
CustomConnectionLineComponent: {
|
connectionLineType: {
|
||||||
type: Object as PropType<ConnectionLineProps['CustomConnectionLineComponent']>,
|
type: String as PropType<ConnectionLineProps['connectionLineType']>,
|
||||||
|
required: false,
|
||||||
|
default: ConnectionLineType.Bezier
|
||||||
|
},
|
||||||
|
customConnectionLine: {
|
||||||
|
type: Object as PropType<ConnectionLineProps['customConnectionLine']>,
|
||||||
required: false,
|
required: false,
|
||||||
default: undefined
|
default: undefined
|
||||||
}
|
}
|
||||||
@@ -114,20 +95,31 @@ const ConnectionLine = defineComponent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
if (props.customConnectionLine) {
|
||||||
dAttr,
|
return () => (
|
||||||
targetX,
|
<g class="revue-flow__connection">
|
||||||
targetY,
|
{props.customConnectionLine &&
|
||||||
sourceX,
|
h(props.customConnectionLine, {
|
||||||
sourceY,
|
sourceX: sourceX.value,
|
||||||
sourceHandle,
|
sourceY: sourceY.value,
|
||||||
sourceNode,
|
sourcePosition: sourceHandle.value?.position,
|
||||||
sourceHandleX,
|
targetX: targetX.value,
|
||||||
sourceHandleY,
|
targetY: targetY.value,
|
||||||
nodesConnectable
|
targetPosition: targetPosition.value,
|
||||||
};
|
connectionLineType: props.connectionLineType,
|
||||||
|
connectionLineStyle: props.connectionLineStyle
|
||||||
|
})}
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () =>
|
||||||
|
nodesConnectable.value && sourceNode.value ? (
|
||||||
|
<g class="revue-flow__connection">
|
||||||
|
<path d={dAttr.value} class="revue-flow__connection-path" style={props.connectionLineStyle} />
|
||||||
|
</g>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ConnectionLine;
|
|
||||||
</script>
|
|
||||||
@@ -121,7 +121,7 @@ const BezierEdge = defineComponent({
|
|||||||
style: {
|
style: {
|
||||||
type: Object as PropType<CSSProperties>,
|
type: Object as PropType<CSSProperties>,
|
||||||
required: false,
|
required: false,
|
||||||
default: () => ({})
|
default: () => {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
|
|||||||
@@ -0,0 +1,255 @@
|
|||||||
|
import { EdgeAnchor } from './EdgeAnchor';
|
||||||
|
import { ConnectionMode, Edge, Elements, Node, Position, RevueFlowStore, Transform } from '../../types';
|
||||||
|
import { computed, defineComponent, h, inject, PropType, ref } from 'vue';
|
||||||
|
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks';
|
||||||
|
import { getEdgePositions, getHandle, getSourceTargetNodes, isEdgeVisible } from '../../container/EdgeRenderer/utils';
|
||||||
|
import { isEdge } from '../../utils/graph';
|
||||||
|
import { onMouseDown } from '../Handle/handler';
|
||||||
|
|
||||||
|
interface EdgeProps {
|
||||||
|
edge: Edge;
|
||||||
|
nodes: Node[];
|
||||||
|
selectedElements: Elements | null;
|
||||||
|
elementsSelectable: boolean;
|
||||||
|
transform: Transform;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
onlyRenderVisibleElements: boolean;
|
||||||
|
connectionMode?: ConnectionMode;
|
||||||
|
markerEndId?: string;
|
||||||
|
edgeUpdaterRadius?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { EdgeAnchor },
|
||||||
|
props: {
|
||||||
|
edge: {
|
||||||
|
type: Object as PropType<EdgeProps['edge']>,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
markerEndId: {
|
||||||
|
type: Object as PropType<EdgeProps['markerEndId']>,
|
||||||
|
required: false,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
edgeUpdaterRadius: {
|
||||||
|
type: Number as PropType<EdgeProps['edgeUpdaterRadius']>,
|
||||||
|
required: false,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
onlyRenderVisibleElements: {
|
||||||
|
type: Boolean as PropType<EdgeProps['onlyRenderVisibleElements']>,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
connectionMode: {
|
||||||
|
type: String as PropType<EdgeProps['connectionMode']>,
|
||||||
|
required: false,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const store = inject<RevueFlowStore>('store')!;
|
||||||
|
const hooks = inject<RevueFlowHooks>('hooks')!;
|
||||||
|
const nodes = computed(() => getSourceTargetNodes(props.edge, store.nodes));
|
||||||
|
|
||||||
|
hooks.connect.on((connection) => {
|
||||||
|
hooks.edgeUpdate.trigger({ edge: props.edge, connection });
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!nodes.value.sourceNode) {
|
||||||
|
console.warn(`couldn't create edge for source id: ${props.edge.source}; edge id: ${props.edge.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nodes.value.targetNode) {
|
||||||
|
console.warn(`couldn't create edge for target id: ${props.edge.target}; edge id: ${props.edge.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetNodeBounds = computed(() => nodes.value.targetNode?.__rf.handleBounds);
|
||||||
|
// when connection type is loose we can define all handles as sources
|
||||||
|
const targetNodeHandles = computed(() =>
|
||||||
|
props.connectionMode === ConnectionMode.Strict
|
||||||
|
? targetNodeBounds.value?.target
|
||||||
|
: targetNodeBounds.value?.target || targetNodeBounds.value?.source
|
||||||
|
);
|
||||||
|
const sourceHandle = computed(
|
||||||
|
() => nodes.value.sourceNode && getHandle(nodes.value.sourceNode.__rf.handleBounds.source, props.edge.sourceHandle || null)
|
||||||
|
);
|
||||||
|
const targetHandle = computed(() => getHandle(targetNodeHandles.value, props.edge.targetHandle || null));
|
||||||
|
const sourcePosition = computed(() => (sourceHandle.value ? sourceHandle.value.position : Position.Bottom));
|
||||||
|
const targetPosition = computed(() => (targetHandle.value ? targetHandle.value.position : Position.Top));
|
||||||
|
|
||||||
|
const edgePositions = computed<{
|
||||||
|
sourceX: number;
|
||||||
|
sourceY: number;
|
||||||
|
targetX: number;
|
||||||
|
targetY: number;
|
||||||
|
}>(() => {
|
||||||
|
return getEdgePositions(
|
||||||
|
nodes.value.sourceNode as Node,
|
||||||
|
sourceHandle.value,
|
||||||
|
sourcePosition.value,
|
||||||
|
nodes.value.targetNode as Node,
|
||||||
|
targetHandle.value,
|
||||||
|
targetPosition.value
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const isVisible = computed(() => {
|
||||||
|
return props.onlyRenderVisibleElements
|
||||||
|
? edgePositions.value &&
|
||||||
|
isEdgeVisible({
|
||||||
|
sourcePos: { x: edgePositions.value?.sourceX, y: edgePositions.value?.sourceY },
|
||||||
|
targetPos: { x: edgePositions.value?.targetX, y: edgePositions.value?.targetY },
|
||||||
|
width: store.width || 0,
|
||||||
|
height: store.height || 0,
|
||||||
|
transform: store.transform
|
||||||
|
})
|
||||||
|
: true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isSelected = computed(() => store.selectedElements?.some((elm) => isEdge(elm) && elm.id === props.edge.id) || false);
|
||||||
|
|
||||||
|
const updating = ref<boolean>(false);
|
||||||
|
const inactive = computed(() => !store.elementsSelectable);
|
||||||
|
const edgeClasses = computed(() => [
|
||||||
|
'revue-flow__edge',
|
||||||
|
`revue-flow__edge-${props.type}`,
|
||||||
|
{ selected: isSelected.value, animated: props.edge.animated, inactive: inactive.value, updating: updating.value }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const edgeElement = computed<Edge>(() => {
|
||||||
|
const el: Edge = {
|
||||||
|
id: props.edge.id || '',
|
||||||
|
source: props.edge.source,
|
||||||
|
target: props.edge.target,
|
||||||
|
type: props.edge.type
|
||||||
|
};
|
||||||
|
|
||||||
|
if (props.edge.sourceHandle) {
|
||||||
|
el.sourceHandle = props.edge.sourceHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.edge.targetHandle) {
|
||||||
|
el.targetHandle = props.edge.targetHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof props.edge.data !== 'undefined') {
|
||||||
|
el.data = props.edge.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return el;
|
||||||
|
});
|
||||||
|
|
||||||
|
const onEdgeClick = (event: MouseEvent) => {
|
||||||
|
if (store.elementsSelectable) {
|
||||||
|
store.unsetNodesSelection();
|
||||||
|
store.addSelectedElements(edgeElement.value as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
hooks.edgeClick.trigger({ event, edge: edgeElement.value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEdgeContextMenu = (event: MouseEvent) =>
|
||||||
|
hooks.edgeContextMenu.trigger({
|
||||||
|
event,
|
||||||
|
edge: edgeElement.value
|
||||||
|
});
|
||||||
|
|
||||||
|
const onEdgeMouseEnter = (event: MouseEvent) => hooks.edgeMouseEnter.trigger({ event, edge: edgeElement.value });
|
||||||
|
|
||||||
|
const onEdgeMouseMove = (event: MouseEvent) => hooks.edgeMouseMove.trigger({ event, edge: edgeElement.value });
|
||||||
|
|
||||||
|
const onEdgeMouseLeave = (event: MouseEvent) => hooks.edgeMouseLeave.trigger({ event, edge: edgeElement.value });
|
||||||
|
|
||||||
|
const handleEdgeUpdater = (event: MouseEvent, isSourceHandle: boolean) => {
|
||||||
|
const nodeId = isSourceHandle ? props.edge.target : props.edge.source;
|
||||||
|
const handleId = isSourceHandle ? props.edge.targetHandle : props.edge.sourceHandle;
|
||||||
|
const isValidConnection = () => true;
|
||||||
|
const isTarget = isSourceHandle;
|
||||||
|
|
||||||
|
hooks.edgeUpdateStart.trigger({ event, edge: edgeElement.value });
|
||||||
|
onMouseDown(
|
||||||
|
event,
|
||||||
|
store,
|
||||||
|
hooks,
|
||||||
|
handleId as string,
|
||||||
|
nodeId,
|
||||||
|
isTarget,
|
||||||
|
isValidConnection,
|
||||||
|
isSourceHandle ? 'target' : 'source'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEdgeUpdaterSourceMouseDown = (event: MouseEvent) => {
|
||||||
|
handleEdgeUpdater(event, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEdgeUpdaterTargetMouseDown = (event: MouseEvent) => {
|
||||||
|
handleEdgeUpdater(event, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEdgeUpdaterMouseEnter = () => (updating.value = true);
|
||||||
|
const onEdgeUpdaterMouseOut = () => (updating.value = false);
|
||||||
|
|
||||||
|
return () =>
|
||||||
|
isVisible.value ? (
|
||||||
|
<g
|
||||||
|
class={edgeClasses.value}
|
||||||
|
onClick={onEdgeClick}
|
||||||
|
onContextmenu={onEdgeContextMenu}
|
||||||
|
onMouseenter={onEdgeMouseEnter}
|
||||||
|
onMousemove={onEdgeMouseMove}
|
||||||
|
onMouseleave={onEdgeMouseLeave}
|
||||||
|
>
|
||||||
|
{h(props.type, {
|
||||||
|
id: props.edge.id,
|
||||||
|
source: props.edge.source,
|
||||||
|
target: props.edge.target,
|
||||||
|
selected: isSelected,
|
||||||
|
animated: props.edge.animated,
|
||||||
|
label: props.edge.label,
|
||||||
|
labelStyle: props.edge.labelStyle,
|
||||||
|
labelShowBg: props.edge.labelShowBg,
|
||||||
|
labelBgStyle: props.edge.labelBgStyle,
|
||||||
|
labelBgPadding: props.edge.labelBgPadding,
|
||||||
|
labelBgBorderRadius: props.edge.labelBgBorderRadius,
|
||||||
|
data: props.edge.data,
|
||||||
|
style: props.edge.style,
|
||||||
|
arrowHeadType: props.edge.arrowHeadType,
|
||||||
|
sourceX: edgePositions.value.sourceX,
|
||||||
|
sourceY: edgePositions.value.sourceY,
|
||||||
|
targetX: edgePositions.value.targetX,
|
||||||
|
targetY: edgePositions.value.targetY,
|
||||||
|
sourcePosition: sourcePosition.value,
|
||||||
|
targetPosition: targetPosition.value,
|
||||||
|
markerEndId: props.markerEndId,
|
||||||
|
sourceHandleId: props.edge.sourceHandle,
|
||||||
|
targetHandleId: props.edge.targetHandle
|
||||||
|
}, {})}
|
||||||
|
<g onMousedown={onEdgeUpdaterSourceMouseDown} onMouseenter={onEdgeUpdaterMouseEnter} onMouseout={onEdgeUpdaterMouseOut}>
|
||||||
|
<EdgeAnchor
|
||||||
|
position={sourcePosition.value}
|
||||||
|
centerX={edgePositions.value.sourceX}
|
||||||
|
centerY={edgePositions.value.sourceY}
|
||||||
|
radius={props.edgeUpdaterRadius}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g onMousedown={onEdgeUpdaterTargetMouseDown} onMouseenter={onEdgeUpdaterMouseEnter} onMouseout={onEdgeUpdaterMouseOut}>
|
||||||
|
<EdgeAnchor
|
||||||
|
position={sourcePosition.value}
|
||||||
|
centerX={edgePositions.value.sourceX}
|
||||||
|
centerY={edgePositions.value.sourceY}
|
||||||
|
radius={props.edgeUpdaterRadius}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -144,8 +144,6 @@ export default defineComponent({
|
|||||||
targetY: number;
|
targetY: number;
|
||||||
}>(() => {
|
}>(() => {
|
||||||
return (
|
return (
|
||||||
nodes.value.sourceNode &&
|
|
||||||
nodes.value.targetNode &&
|
|
||||||
getEdgePositions(
|
getEdgePositions(
|
||||||
nodes.value.sourceNode,
|
nodes.value.sourceNode,
|
||||||
sourceHandle.value,
|
sourceHandle.value,
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
import { ElementId, Position, RevueFlowStore } from '../../types';
|
|
||||||
import { onMouseDown, ValidConnectionFunc } from './handler';
|
|
||||||
import { computed, defineComponent, h, inject, PropType } from 'vue';
|
|
||||||
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks';
|
|
||||||
|
|
||||||
const alwaysValid = () => true;
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: 'source'
|
|
||||||
},
|
|
||||||
position: {
|
|
||||||
type: String as PropType<Position>,
|
|
||||||
required: false,
|
|
||||||
default: Position.Top
|
|
||||||
},
|
|
||||||
isValidConnection: {
|
|
||||||
type: Function as PropType<ValidConnectionFunc>,
|
|
||||||
required: false,
|
|
||||||
default: alwaysValid
|
|
||||||
},
|
|
||||||
isConnectable: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
id: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: undefined
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props, { slots }) {
|
|
||||||
const store = inject<RevueFlowStore>('store')!;
|
|
||||||
const hooks = inject<RevueFlowHooks>('hooks')!;
|
|
||||||
const nodeId = inject<ElementId>('NodeIdContext') as ElementId;
|
|
||||||
const isTarget = computed(() => props.type === 'target');
|
|
||||||
|
|
||||||
const onMouseDownHandler = (event: MouseEvent) => {
|
|
||||||
onMouseDown(event, store, hooks, props.id || '', nodeId, isTarget.value, props.isValidConnection);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClasses = computed(() => [
|
|
||||||
'revue-flow__handle',
|
|
||||||
`revue-flow__handle-${props.position}`,
|
|
||||||
'nodrag',
|
|
||||||
{
|
|
||||||
source: !isTarget.value,
|
|
||||||
target: isTarget.value,
|
|
||||||
connectable: props.isConnectable
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
return () =>
|
|
||||||
h(
|
|
||||||
'div',
|
|
||||||
{
|
|
||||||
dataHandleid: props.id,
|
|
||||||
dataNodeid: nodeId,
|
|
||||||
dataHandlepos: props.position,
|
|
||||||
class: handleClasses.value,
|
|
||||||
onMouseDown: onMouseDownHandler
|
|
||||||
},
|
|
||||||
slots.default?.()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
import { getHostForElement } from '../../utils';
|
import { getHostForElement } from '../../utils';
|
||||||
import { ElementId, XYPosition, ConnectionMode, SetConnectionId, Connection, HandleType, RevueFlowStore } from '../../types';
|
import { ElementId, ConnectionMode, Connection, HandleType, RevueFlowStore } from '../../types';
|
||||||
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks';
|
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks';
|
||||||
|
|
||||||
export type ValidConnectionFunc = (connection: Connection) => boolean;
|
export type ValidConnectionFunc = (connection: Connection) => boolean;
|
||||||
export type SetSourceIdFunc = (params: SetConnectionId) => void;
|
|
||||||
|
|
||||||
export type SetPosition = (pos: XYPosition) => void;
|
|
||||||
|
|
||||||
type Result = {
|
type Result = {
|
||||||
elementBelow: Element | null;
|
elementBelow: Element | null;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { RevueFlowHooks } from '../../hooks/RevueFlowHooks';
|
|||||||
|
|
||||||
const alwaysValid = () => true;
|
const alwaysValid = () => true;
|
||||||
|
|
||||||
const Handle = defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -41,21 +41,18 @@ const Handle = defineComponent({
|
|||||||
setup(props, { slots }) {
|
setup(props, { slots }) {
|
||||||
const store = inject<RevueFlowStore>('store')!;
|
const store = inject<RevueFlowStore>('store')!;
|
||||||
const hooks = inject<RevueFlowHooks>('hooks')!;
|
const hooks = inject<RevueFlowHooks>('hooks')!;
|
||||||
const nodeId = inject<ElementId>('NodeIdContext') as ElementId;
|
const nodeId = inject<ElementId>('NodeIdContext')!;
|
||||||
const isTarget = computed(() => props.type === 'target');
|
|
||||||
|
|
||||||
const onMouseDownHandler = (event: MouseEvent) => {
|
const onMouseDownHandler = (event: MouseEvent) =>
|
||||||
console.log('mousedown');
|
onMouseDown(event, store, hooks, props.id as string, nodeId, props.type === 'target', props.isValidConnection);
|
||||||
onMouseDown(event, store, hooks, props.id as string, nodeId, isTarget.value, props.isValidConnection);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClasses = computed(() => [
|
const handleClasses = computed(() => [
|
||||||
'revue-flow__handle',
|
'revue-flow__handle',
|
||||||
`revue-flow__handle-${props.position}`,
|
`revue-flow__handle-${props.position}`,
|
||||||
'nodrag',
|
'nodrag',
|
||||||
{
|
{
|
||||||
source: !isTarget.value,
|
source: props.type !== 'target',
|
||||||
target: isTarget.value,
|
target: props.type === 'target',
|
||||||
connectable: props.isConnectable
|
connectable: props.isConnectable
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@@ -73,5 +70,3 @@ const Handle = defineComponent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default Handle;
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export default defineComponent({
|
|||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
type: Object,
|
type: [Object, String],
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
snapToGrid: {
|
snapToGrid: {
|
||||||
|
|||||||
@@ -38,9 +38,6 @@ export default defineComponent({
|
|||||||
const store = inject<RevueFlowStore>('store')!;
|
const store = inject<RevueFlowStore>('store')!;
|
||||||
const el = templateRef('user-selection', null);
|
const el = templateRef('user-selection', null);
|
||||||
const shouldRender = computed(() => store.selectionActive || store.elementsSelectable);
|
const shouldRender = computed(() => store.selectionActive || store.elementsSelectable);
|
||||||
if (!shouldRender.value) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onMouseDown = (event: MouseEvent) => {
|
const onMouseDown = (event: MouseEvent) => {
|
||||||
const mousePos = getMousePosition(event);
|
const mousePos = getMousePosition(event);
|
||||||
@@ -52,12 +49,10 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onMouseMove = (event: MouseEvent) => {
|
const onMouseMove = (event: MouseEvent) => {
|
||||||
|
|
||||||
if (!store.selectionActive) {
|
if (!store.selectionActive) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const mousePos = getMousePosition(event);
|
const mousePos = getMousePosition(event);
|
||||||
|
|
||||||
if (!mousePos) {
|
if (!mousePos) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -80,19 +75,22 @@ export default defineComponent({
|
|||||||
useEventListener(el, 'mouseup', onMouseUp);
|
useEventListener(el, 'mouseup', onMouseUp);
|
||||||
useEventListener(el, 'mouseleave', onMouseLeave);
|
useEventListener(el, 'mouseleave', onMouseLeave);
|
||||||
|
|
||||||
return () => (
|
return () =>
|
||||||
<div class="revue-flow__selectionpane" ref="user-selection">
|
shouldRender.value ? (
|
||||||
{store?.userSelectionRect.draw ? (
|
<div class="revue-flow__selectionpane" ref="user-selection">
|
||||||
<SelectionRect
|
{store?.userSelectionRect.draw ? (
|
||||||
width={store.userSelectionRect.width}
|
<SelectionRect
|
||||||
height={store.userSelectionRect.height}
|
width={store.userSelectionRect.width}
|
||||||
x={store.userSelectionRect.x}
|
height={store.userSelectionRect.height}
|
||||||
y={store.userSelectionRect.y}
|
x={store.userSelectionRect.x}
|
||||||
/>
|
y={store.userSelectionRect.y}
|
||||||
) : (
|
/>
|
||||||
''
|
) : (
|
||||||
)}
|
''
|
||||||
</div>
|
)}
|
||||||
);
|
</div>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { FunctionalComponent } from 'vue';
|
||||||
|
|
||||||
|
interface MarkerProps {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ((props: MarkerProps, { slots }) => {
|
||||||
|
return () => (
|
||||||
|
<marker
|
||||||
|
id={props.id}
|
||||||
|
class="revue-flow__arrowhead"
|
||||||
|
markerWidth="12.5"
|
||||||
|
markerHeight="12.5"
|
||||||
|
viewBox="-10 -10 20 20"
|
||||||
|
orient="auto"
|
||||||
|
refX="0"
|
||||||
|
refY="0"
|
||||||
|
>
|
||||||
|
{slots.default ? slots.default() : ''}
|
||||||
|
</marker>
|
||||||
|
);
|
||||||
|
}) as FunctionalComponent<MarkerProps>;
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<template>
|
|
||||||
<marker
|
|
||||||
:id="id"
|
|
||||||
class="revue-flow__arrowhead"
|
|
||||||
markerWidth="12.5"
|
|
||||||
markerHeight="12.5"
|
|
||||||
viewBox="-10 -10 20 20"
|
|
||||||
orient="auto"
|
|
||||||
refX="0"
|
|
||||||
refY="0"
|
|
||||||
>
|
|
||||||
<slot></slot>
|
|
||||||
</marker>
|
|
||||||
</template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, PropType } from 'vue';
|
|
||||||
|
|
||||||
interface MarkerProps {
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
id: {
|
|
||||||
type: String as PropType<MarkerProps['id']>,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { defineComponent, PropType } from 'vue';
|
||||||
|
import Marker from './Marker';
|
||||||
|
|
||||||
|
interface MarkerDefinitionsProps {
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { Marker },
|
||||||
|
props: {
|
||||||
|
color: {
|
||||||
|
type: String as PropType<MarkerDefinitionsProps['color']>,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
return () => (
|
||||||
|
<defs>
|
||||||
|
<Marker id="revue-flow__arrowclosed">
|
||||||
|
<polyline
|
||||||
|
stroke={props.color}
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1"
|
||||||
|
fill={props.color}
|
||||||
|
points="-5,-4 0,0 -5,4 -5,-4"
|
||||||
|
/>
|
||||||
|
</Marker>
|
||||||
|
<Marker id="revue-flow__arrow">
|
||||||
|
<polyline
|
||||||
|
stroke={props.color}
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="1.5"
|
||||||
|
fill="none"
|
||||||
|
points="-5,-4 0,0 -5,4"
|
||||||
|
/>
|
||||||
|
</Marker>
|
||||||
|
</defs>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
<template>
|
|
||||||
<defs>
|
|
||||||
<Marker id="revue-flow__arrowclosed">
|
|
||||||
<polyline
|
|
||||||
:stroke="color"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="1"
|
|
||||||
:fill="color"
|
|
||||||
points="-5,-4 0,0 -5,4 -5,-4"
|
|
||||||
/>
|
|
||||||
</Marker>
|
|
||||||
<Marker id="revue-flow__arrow">
|
|
||||||
<polyline
|
|
||||||
:stroke="color"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="1.5"
|
|
||||||
fill="none"
|
|
||||||
points="-5,-4 0,0 -5,4"
|
|
||||||
/>
|
|
||||||
</Marker>
|
|
||||||
</defs>
|
|
||||||
</template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, PropType } from 'vue';
|
|
||||||
import Marker from './Marker.vue';
|
|
||||||
|
|
||||||
interface MarkerDefinitionsProps {
|
|
||||||
color: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MarkerDefinitions = defineComponent({
|
|
||||||
name: 'MarkerDefinitions',
|
|
||||||
components: { Marker },
|
|
||||||
props: {
|
|
||||||
color: {
|
|
||||||
type: String as PropType<MarkerDefinitionsProps['color']>,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default MarkerDefinitions;
|
|
||||||
</script>
|
|
||||||
+32
-46
@@ -1,30 +1,8 @@
|
|||||||
<template>
|
|
||||||
<svg :width="width" :height="height" class="revue-flow__edges">
|
|
||||||
<MarkerDefinitions :color="arrowHeadColor" />
|
|
||||||
<g :transform="transformStyle">
|
|
||||||
<template v-for="edge of edges" :key="edge.id">
|
|
||||||
<Edge
|
|
||||||
:edge="edge"
|
|
||||||
:marker-end-id="markerEndId"
|
|
||||||
:onlyRenderVisibleElements="onlyRenderVisibleElements"
|
|
||||||
:type="edgeTypes[edge.type]"
|
|
||||||
/>
|
|
||||||
<ConnectionLine
|
|
||||||
v-if="renderConnectionLine"
|
|
||||||
:connectionLineStyle="connectionLineStyle"
|
|
||||||
:connectionLineType="connectionLineType"
|
|
||||||
:CustomConnectionLineComponent="connectionLineComponent"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, CSSProperties, defineComponent, inject, PropType } from 'vue';
|
import { computed, CSSProperties, defineComponent, inject, PropType } from 'vue';
|
||||||
import Edge from '../../components/Edges/Edge.vue';
|
|
||||||
import ConnectionLine from '../../components/ConnectionLine/ConnectionLine.vue';
|
|
||||||
import { ConnectionLineType, ConnectionLineComponent, ConnectionMode, RevueFlowStore } from '../../types';
|
import { ConnectionLineType, ConnectionLineComponent, ConnectionMode, RevueFlowStore } from '../../types';
|
||||||
import MarkerDefinitions from './MarkerDefinitions.vue';
|
import ConnectionLine from '../../components/ConnectionLine';
|
||||||
|
import MarkerDefinitions from './MarkerDefinitions';
|
||||||
|
import Edge from '../../components/Edges/Edge';
|
||||||
|
|
||||||
interface EdgeRendererProps {
|
interface EdgeRendererProps {
|
||||||
edgeTypes: any;
|
edgeTypes: any;
|
||||||
@@ -38,7 +16,7 @@ interface EdgeRendererProps {
|
|||||||
edgeUpdaterRadius?: number;
|
edgeUpdaterRadius?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EdgeRenderer = defineComponent({
|
export default defineComponent({
|
||||||
name: 'EdgeRenderer',
|
name: 'EdgeRenderer',
|
||||||
components: {
|
components: {
|
||||||
Edge,
|
Edge,
|
||||||
@@ -48,8 +26,7 @@ const EdgeRenderer = defineComponent({
|
|||||||
props: {
|
props: {
|
||||||
edgeTypes: {
|
edgeTypes: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: false,
|
required: true
|
||||||
default: undefined
|
|
||||||
},
|
},
|
||||||
connectionLineType: {
|
connectionLineType: {
|
||||||
type: String as PropType<EdgeRendererProps['connectionLineType']>,
|
type: String as PropType<EdgeRendererProps['connectionLineType']>,
|
||||||
@@ -92,27 +69,36 @@ const EdgeRenderer = defineComponent({
|
|||||||
default: undefined
|
default: undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup() {
|
setup(props) {
|
||||||
const store = inject<RevueFlowStore>('store')!;
|
const store = inject<RevueFlowStore>('store');
|
||||||
const edges = computed(() => store.edges);
|
const transform = computed(() => store?.transform);
|
||||||
const transform = computed(() => store.transform);
|
|
||||||
const transformStyle = computed(() => {
|
const transformStyle = computed(() => {
|
||||||
return `translate(${transform.value?.[0]},${transform.value?.[1]}) scale(${transform.value?.[2]})`;
|
return `translate(${transform.value?.[0]},${transform.value?.[1]}) scale(${transform.value?.[2]})`;
|
||||||
});
|
});
|
||||||
const renderConnectionLine = computed(() => store.connectionNodeId && store.connectionHandleType);
|
const renderConnectionLine = computed(() => store?.connectionNodeId && store?.connectionHandleType);
|
||||||
const width = computed(() => store.width);
|
|
||||||
const height = computed(() => store.height);
|
|
||||||
|
|
||||||
return {
|
return () => (
|
||||||
transform,
|
<svg width={store?.width} height={store?.height} class="revue-flow__edges">
|
||||||
transformStyle,
|
<MarkerDefinitions color={props.arrowHeadColor as string} />
|
||||||
renderConnectionLine,
|
<g transform={transformStyle.value}>
|
||||||
edges,
|
{store?.edges.map((edge) => (
|
||||||
width,
|
<Edge
|
||||||
height
|
key={edge.id}
|
||||||
};
|
edge={edge}
|
||||||
|
type={props.edgeTypes[edge.type || 'default']}
|
||||||
|
onlyRenderVisibleElements={props.onlyRenderVisibleElements}
|
||||||
|
markerEndId={props.markerEndId}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{renderConnectionLine.value && (
|
||||||
|
<ConnectionLine
|
||||||
|
connectionLineStyle={props.connectionLineStyle}
|
||||||
|
connectionLineType={props.connectionLineType}
|
||||||
|
customConnectionLine={props.connectionLineComponent}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default EdgeRenderer;
|
|
||||||
</script>
|
|
||||||
+24
-35
@@ -1,26 +1,3 @@
|
|||||||
<template>
|
|
||||||
<ZoomPane
|
|
||||||
:selectionKeyPressed="keyPressed"
|
|
||||||
:elementsSelectable="elementsSelectable"
|
|
||||||
:zoomOnScroll="zoomOnScroll"
|
|
||||||
:zoomOnPinch="zoomOnPinch"
|
|
||||||
:panOnScroll="panOnScroll"
|
|
||||||
:panOnScrollSpeed="panOnScrollSpeed"
|
|
||||||
:panOnScrollMode="panOnScrollMode"
|
|
||||||
:zoomOnDoubleClick="zoomOnDoubleClick"
|
|
||||||
:paneMoveable="paneMoveable"
|
|
||||||
:defaultPosition="defaultPosition"
|
|
||||||
:defaultZoom="defaultZoom"
|
|
||||||
:translateExtent="translateExtent"
|
|
||||||
:zoomActivationKeyCode="zoomActivationKeyCode"
|
|
||||||
>
|
|
||||||
<slot></slot>
|
|
||||||
<UserSelection v-if="keyPressed" />
|
|
||||||
<NodesSelection v-if="selectionActive" />
|
|
||||||
<div class="revue-flow__pane" @click="onClick" @onContextmenu="onContextMenu" @wheel="onWheel" />
|
|
||||||
</ZoomPane>
|
|
||||||
</template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, inject, PropType } from 'vue';
|
import { computed, defineComponent, inject, PropType } from 'vue';
|
||||||
import ZoomPane from '../ZoomPane';
|
import ZoomPane from '../ZoomPane';
|
||||||
import UserSelection from '../../components/UserSelection';
|
import UserSelection from '../../components/UserSelection';
|
||||||
@@ -43,7 +20,7 @@ type FlowRendererProps = Omit<
|
|||||||
| 'selectNodesOnDrag'
|
| 'selectNodesOnDrag'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const FlowRenderer = defineComponent({
|
export default defineComponent({
|
||||||
name: 'FlowRenderer',
|
name: 'FlowRenderer',
|
||||||
components: { UserSelection, ZoomPane, NodesSelection },
|
components: { UserSelection, ZoomPane, NodesSelection },
|
||||||
props: {
|
props: {
|
||||||
@@ -123,7 +100,7 @@ const FlowRenderer = defineComponent({
|
|||||||
default: undefined
|
default: undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props, { slots }) {
|
||||||
const store = inject<RevueFlowStore>('store')!;
|
const store = inject<RevueFlowStore>('store')!;
|
||||||
const hooks = inject<RevueFlowHooks>('hooks')!;
|
const hooks = inject<RevueFlowHooks>('hooks')!;
|
||||||
const keyPressed = useKeyPress(props.selectionKeyCode);
|
const keyPressed = useKeyPress(props.selectionKeyCode);
|
||||||
@@ -147,15 +124,27 @@ const FlowRenderer = defineComponent({
|
|||||||
|
|
||||||
const selectionActive = computed(() => store.nodesSelectionActive);
|
const selectionActive = computed(() => store.nodesSelectionActive);
|
||||||
|
|
||||||
return {
|
return () => (
|
||||||
onClick,
|
<ZoomPane
|
||||||
onContextMenu,
|
selectionKeyPressed={keyPressed.value}
|
||||||
onWheel,
|
elementsSelectable={props.elementsSelectable}
|
||||||
keyPressed,
|
zoomOnScroll={props.zoomOnScroll}
|
||||||
selectionActive
|
zoomOnPinch={props.zoomOnPinch}
|
||||||
};
|
panOnScroll={props.panOnScroll}
|
||||||
|
panOnScrollSpeed={props.panOnScrollSpeed}
|
||||||
|
panOnScrollMode={props.panOnScrollMode}
|
||||||
|
zoomOnDoubleClick={props.zoomOnDoubleClick}
|
||||||
|
paneMoveable={props.paneMoveable}
|
||||||
|
defaultPosition={props.defaultPosition}
|
||||||
|
defaultZoom={props.defaultZoom}
|
||||||
|
translateExtent={props.translateExtent}
|
||||||
|
zoomActivationKeyCode={props.zoomActivationKeyCode}
|
||||||
|
>
|
||||||
|
{slots.default ? slots.default() : ''}
|
||||||
|
{keyPressed.value ? <UserSelection /> : ''}
|
||||||
|
{selectionActive.value ? <NodesSelection /> : ''}
|
||||||
|
<div class="revue-flow__pane" onClick={onClick} onContextmenu={onContextMenu} onWheel={onWheel} />
|
||||||
|
</ZoomPane>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default FlowRenderer;
|
|
||||||
</script>
|
|
||||||
@@ -1,52 +1,52 @@
|
|||||||
<template>
|
<template>
|
||||||
<FlowRenderer
|
<FlowRenderer
|
||||||
:deleteKeyCode="deleteKeyCode"
|
:delete-key-code="deleteKeyCode"
|
||||||
:selectionKeyCode="selectionKeyCode"
|
:selection-key-code="selectionKeyCode"
|
||||||
:multiSelectionKeyCode="multiSelectionKeyCode"
|
:multi-selection-key-code="multiSelectionKeyCode"
|
||||||
:zoomActivationKeyCode="zoomActivationKeyCode"
|
:zoom-activation-key-code="zoomActivationKeyCode"
|
||||||
:elementsSelectable="elementsSelectable"
|
:elements-selectable="elementsSelectable"
|
||||||
:zoomOnScroll="zoomOnScroll"
|
:zoom-on-scroll="zoomOnScroll"
|
||||||
:zoomOnPinch="zoomOnPinch"
|
:zoom-on-pinch="zoomOnPinch"
|
||||||
:zoomOnDoubleClick="zoomOnDoubleClick"
|
:zoom-on-double-click="zoomOnDoubleClick"
|
||||||
:panOnScroll="panOnScroll"
|
:pan-on-scroll="panOnScroll"
|
||||||
:panOnScrollSpeed="panOnScrollSpeed"
|
:pan-on-scroll-speed="panOnScrollSpeed"
|
||||||
:panOnScrollMode="panOnScrollMode"
|
:pan-on-scroll-mode="panOnScrollMode"
|
||||||
:paneMoveable="paneMoveable"
|
:pane-moveable="paneMoveable"
|
||||||
:defaultPosition="defaultPosition"
|
:default-position="defaultPosition"
|
||||||
:defaultZoom="defaultZoom"
|
:default-zoom="defaultZoom"
|
||||||
:translateExtent="translateExtent"
|
:translate-extent="translateExtent"
|
||||||
>
|
>
|
||||||
<NodeRenderer
|
<NodeRenderer
|
||||||
:nodeTypes="nodeTypes"
|
:node-types="nodeTypes"
|
||||||
:selectNodesOnDrag="selectNodesOnDrag"
|
:select-nodes-on-drag="selectNodesOnDrag"
|
||||||
:snapToGrid="snapToGrid"
|
:snap-to-grid="snapToGrid"
|
||||||
:snapGrid="snapGrid"
|
:snap-grid="snapGrid"
|
||||||
:onlyRenderVisibleElements="onlyRenderVisibleElements"
|
:only-render-visible-elements="onlyRenderVisibleElements"
|
||||||
/>
|
/>
|
||||||
<EdgeRenderer
|
<EdgeRenderer
|
||||||
:edgeTypes="edgeTypes"
|
:edge-types="edgeTypes"
|
||||||
:connectionLineType="connectionLineType"
|
:connection-line-type="connectionLineType"
|
||||||
:connectionLineStyle="connectionLineStyle"
|
:connection-line-style="connectionLineStyle"
|
||||||
:connectionLineComponent="connectionLineComponent"
|
:connection-line-component="connectionLineComponent"
|
||||||
:connectionMode="connectionMode"
|
:connection-mode="connectionMode"
|
||||||
:arrowHeadColor="arrowHeadColor"
|
:arrow-head-color="arrowHeadColor"
|
||||||
:markerEndId="markerEndId"
|
:marker-end-id="markerEndId"
|
||||||
:onlyRenderVisibleElements="onlyRenderVisibleElements"
|
:only-render-visible-elements="onlyRenderVisibleElements"
|
||||||
:edgeUpdaterRadius="edgeUpdaterRadius"
|
:edge-updater-radius="edgeUpdaterRadius"
|
||||||
/>
|
/>
|
||||||
</FlowRenderer>
|
</FlowRenderer>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, inject, PropType, CSSProperties, watchEffect } from 'vue';
|
import { defineComponent, inject, PropType, CSSProperties, watchEffect } from 'vue';
|
||||||
import FlowRenderer from '../FlowRenderer/FlowRenderer.vue';
|
import FlowRenderer from '../FlowRenderer';
|
||||||
import NodeRenderer from '../NodeRenderer/NodeRenderer.vue';
|
import NodeRenderer from '../NodeRenderer';
|
||||||
import EdgeRenderer from '../EdgeRenderer/EdgeRenderer.vue';
|
import EdgeRenderer from '../EdgeRenderer';
|
||||||
import { onLoadProject, onLoadGetElements, onLoadToObject } from '../../utils/graph';
|
import { onLoadProject, onLoadGetElements, onLoadToObject } from '../../utils/graph';
|
||||||
import { GraphViewProps, RevueFlowStore } from '../../types';
|
import { GraphViewProps, RevueFlowStore } from '../../types';
|
||||||
import useZoomPanHelper from '../../hooks/useZoomPanHelper';
|
import useZoomPanHelper from '../../hooks/useZoomPanHelper';
|
||||||
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks';
|
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks';
|
||||||
|
|
||||||
const GraphView = defineComponent({
|
export default defineComponent({
|
||||||
name: 'GraphView',
|
name: 'GraphView',
|
||||||
components: { NodeRenderer, EdgeRenderer, FlowRenderer },
|
components: { NodeRenderer, EdgeRenderer, FlowRenderer },
|
||||||
props: {
|
props: {
|
||||||
@@ -220,20 +220,18 @@ const GraphView = defineComponent({
|
|||||||
const store = inject<RevueFlowStore>('store')!;
|
const store = inject<RevueFlowStore>('store')!;
|
||||||
const hooks = inject<RevueFlowHooks>('hooks')!;
|
const hooks = inject<RevueFlowHooks>('hooks')!;
|
||||||
|
|
||||||
const { onReady } = useZoomPanHelper(store);
|
const { onReady } = useZoomPanHelper();
|
||||||
onReady(({ zoomIn, zoomOut, zoomTo, transform, fitView, initialized }) => {
|
onReady(({ zoomIn, zoomOut, zoomTo, transform, fitView }) => {
|
||||||
if (initialized) {
|
hooks.load.trigger({
|
||||||
hooks.load.trigger({
|
fitView: (params = { padding: 0.1 }) => fitView(params),
|
||||||
fitView: (params = { padding: 0.1 }) => fitView(params),
|
zoomIn,
|
||||||
zoomIn,
|
zoomOut,
|
||||||
zoomOut,
|
zoomTo,
|
||||||
zoomTo,
|
setTransform: transform,
|
||||||
setTransform: transform,
|
project: onLoadProject(store),
|
||||||
project: onLoadProject(store),
|
getElements: onLoadGetElements(store),
|
||||||
getElements: onLoadGetElements(store),
|
toObject: onLoadToObject(store)
|
||||||
toObject: onLoadToObject(store)
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const init = () => {
|
const init = () => {
|
||||||
@@ -281,6 +279,4 @@ const GraphView = defineComponent({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default GraphView;
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+26
-24
@@ -1,21 +1,7 @@
|
|||||||
<template>
|
|
||||||
<div class="revue-flow__nodes" :style="transformStyle">
|
|
||||||
<template v-for="(node, i) of visibleNodes" :key="`node-${i}`">
|
|
||||||
<Node
|
|
||||||
:node="node"
|
|
||||||
:snap-grid="snapGrid"
|
|
||||||
:snap-to-grid="snapToGrid"
|
|
||||||
:select-nodes-on-drag="selectNodesOnDrag"
|
|
||||||
:type="nodeTypes[node.type]"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { getNodesInside } from '../../utils/graph';
|
|
||||||
import { NodeTypesType, RevueFlowStore } from '../../types';
|
|
||||||
import { computed, defineComponent, inject, PropType } from 'vue';
|
import { computed, defineComponent, inject, PropType } from 'vue';
|
||||||
import Node from '../../components/Nodes/Node.vue';
|
import Node from '../../components/Nodes/Node.vue';
|
||||||
|
import { getNodesInside } from '../../utils/graph';
|
||||||
|
import { NodeTypesType, RevueFlowStore } from '../../types';
|
||||||
|
|
||||||
interface NodeRendererProps {
|
interface NodeRendererProps {
|
||||||
nodeTypes: NodeTypesType;
|
nodeTypes: NodeTypesType;
|
||||||
@@ -25,7 +11,7 @@ interface NodeRendererProps {
|
|||||||
onlyRenderVisibleElements: boolean;
|
onlyRenderVisibleElements: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodeRenderer = defineComponent({
|
export default defineComponent({
|
||||||
name: 'NodeRenderer',
|
name: 'NodeRenderer',
|
||||||
components: { Node },
|
components: { Node },
|
||||||
props: {
|
props: {
|
||||||
@@ -79,12 +65,28 @@ const NodeRenderer = defineComponent({
|
|||||||
transform: `translate(${store.transform[0]}px,${store.transform[1]}px) scale(${store.transform[2]})`
|
transform: `translate(${store.transform[0]}px,${store.transform[1]}px) scale(${store.transform[2]})`
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return () => (
|
||||||
transformStyle,
|
<div class="revue-flow__nodes" style={transformStyle.value}>
|
||||||
visibleNodes
|
{visibleNodes.value?.map((node) => {
|
||||||
};
|
const nodeType = node.type || 'default';
|
||||||
|
if (props.nodeTypes) {
|
||||||
|
const type = props.nodeTypes[nodeType] || props.nodeTypes.default;
|
||||||
|
if (!props.nodeTypes[nodeType]) {
|
||||||
|
console.warn(`Node type "${nodeType}" not found. Using fallback type "default".`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Node
|
||||||
|
node={node}
|
||||||
|
snapGrid={props.snapGrid}
|
||||||
|
snapToGrid={props.snapToGrid}
|
||||||
|
selectNodesOnDrag={props.selectNodesOnDrag}
|
||||||
|
type={type}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default NodeRenderer;
|
|
||||||
</script>
|
|
||||||
@@ -1,39 +1,39 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="revue-flow">
|
<div class="revue-flow">
|
||||||
<GraphView
|
<GraphView
|
||||||
:nodeTypes="nodeTypesParsed"
|
:node-types="nodeTypesParsed"
|
||||||
:edgeTypes="edgeTypesParsed"
|
:edge-types="edgeTypesParsed"
|
||||||
:connectionMode="connectionMode"
|
:connection-mode="connectionMode"
|
||||||
:connectionLineType="connectionLineType"
|
:connection-line-type="connectionLineType"
|
||||||
:connectionLineStyle="connectionLineStyle"
|
:connection-line-style="connectionLineStyle"
|
||||||
:connectionLineComponent="connectionLineComponent"
|
:connection-line-component="connectionLineComponent"
|
||||||
:selectionKeyCode="selectionKeyCode"
|
:selection-key-code="selectionKeyCode"
|
||||||
:deleteKeyCode="deleteKeyCode"
|
:delete-key-code="deleteKeyCode"
|
||||||
:multiSelectionKeyCode="multiSelectionKeyCode"
|
:multi-selection-key-code="multiSelectionKeyCode"
|
||||||
:zoomActivationKeyCode="zoomActivationKeyCode"
|
:zoom-activation-key-code="zoomActivationKeyCode"
|
||||||
:snapToGrid="snapToGrid"
|
:snap-to-grid="snapToGrid"
|
||||||
:snapGrid="snapGrid"
|
:snap-grid="snapGrid"
|
||||||
:onlyRenderVisibleElements="onlyRenderVisibleElements"
|
:only-render-visible-elements="onlyRenderVisibleElements"
|
||||||
:nodesDraggable="nodesDraggable"
|
:nodes-draggable="nodesDraggable"
|
||||||
:nodesConnectable="nodesConnectable"
|
:nodes-connectable="nodesConnectable"
|
||||||
:elementsSelectable="elementsSelectable"
|
:elements-selectable="elementsSelectable"
|
||||||
:selectNodesOnDrag="selectNodesOnDrag"
|
:select-nodes-on-drag="selectNodesOnDrag"
|
||||||
:minZoom="minZoom"
|
:min-zoom="minZoom"
|
||||||
:maxZoom="maxZoom"
|
:max-zoom="maxZoom"
|
||||||
:defaultZoom="defaultZoom"
|
:default-zoom="defaultZoom"
|
||||||
:defaultPosition="defaultPosition"
|
:default-position="defaultPosition"
|
||||||
:translateExtent="translateExtent"
|
:translate-extent="translateExtent"
|
||||||
:nodeExtent="nodeExtent"
|
:node-extent="nodeExtent"
|
||||||
:arrowHeadColor="arrowHeadColor"
|
:arrow-head-color="arrowHeadColor"
|
||||||
:markerEndId="markerEndId"
|
:marker-end-id="markerEndId"
|
||||||
:zoomOnScroll="zoomOnScroll"
|
:zoom-on-scroll="zoomOnScroll"
|
||||||
:zoomOnPinch="zoomOnPinch"
|
:zoom-on-pinch="zoomOnPinch"
|
||||||
:zoomOnDoubleClick="zoomOnDoubleClick"
|
:zoom-on-double-click="zoomOnDoubleClick"
|
||||||
:panOnScroll="panOnScroll"
|
:pan-on-scroll="panOnScroll"
|
||||||
:panOnScrollSpeed="panOnScrollSpeed"
|
:pan-on-scroll-speed="panOnScrollSpeed"
|
||||||
:panOnScrollMode="panOnScrollMode"
|
:pan-on-scroll-mode="panOnScrollMode"
|
||||||
:paneMoveable="paneMoveable"
|
:pane-moveable="paneMoveable"
|
||||||
:edgeUpdaterRadius="edgeUpdaterRadius"
|
:edge-updater-radius="edgeUpdaterRadius"
|
||||||
/>
|
/>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,8 +68,7 @@ const defaultEdgeTypes = {
|
|||||||
smoothstep: SmoothStepEdge
|
smoothstep: SmoothStepEdge
|
||||||
};
|
};
|
||||||
|
|
||||||
const eventHooks = useRevueFlow();
|
export default defineComponent({
|
||||||
const RevueFlow = defineComponent({
|
|
||||||
name: 'RevueFlow',
|
name: 'RevueFlow',
|
||||||
components: { GraphView },
|
components: { GraphView },
|
||||||
props: {
|
props: {
|
||||||
@@ -254,12 +253,12 @@ const RevueFlow = defineComponent({
|
|||||||
default: 10
|
default: 10
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: Object.keys(eventHooks),
|
emits: Object.keys(useRevueFlow()),
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const store = configureStore(initialState)();
|
const store = configureStore(initialState)();
|
||||||
provide<RevueFlowStore>('store', store);
|
provide<RevueFlowStore>('store', store);
|
||||||
eventHooks.bind(emit);
|
const hooks = useRevueFlow().bind(emit);
|
||||||
provide<RevueFlowHooks>('hooks', eventHooks);
|
provide<RevueFlowHooks>('hooks', hooks);
|
||||||
const elements = useVModel(props, 'modelValue', emit);
|
const elements = useVModel(props, 'modelValue', emit);
|
||||||
|
|
||||||
store.setElements(elements.value);
|
store.setElements(elements.value);
|
||||||
@@ -289,5 +288,4 @@ const RevueFlow = defineComponent({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
export default RevueFlow;
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { zoom, zoomIdentity } from 'd3-zoom';
|
import { D3ZoomEvent, zoom, zoomIdentity, ZoomTransform } from 'd3-zoom';
|
||||||
import { select, pointer } from 'd3-selection';
|
import { select, pointer } from 'd3-selection';
|
||||||
|
|
||||||
import { clamp } from '../../utils';
|
import { clamp } from '../../utils';
|
||||||
import { FlowTransform, TranslateExtent, PanOnScrollMode, KeyCode, RevueFlowStore } from '../../types';
|
import { FlowTransform, TranslateExtent, PanOnScrollMode, KeyCode, RevueFlowStore } from '../../types';
|
||||||
import { defineComponent, inject, PropType, ref, watchEffect } from 'vue';
|
import { defineComponent, inject, PropType, ref, watchEffect } from 'vue';
|
||||||
import useKeyPress from '../../hooks/useKeyPress';
|
import useKeyPress from '../../hooks/useKeyPress';
|
||||||
import useResizeHandler from '../../hooks/useResizeHandler';
|
import useResizeHandler from '../../hooks/useResizeHandler';
|
||||||
|
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks';
|
||||||
|
import { templateRef, whenever } from '@vueuse/core';
|
||||||
|
|
||||||
interface ZoomPaneProps {
|
interface ZoomPaneProps {
|
||||||
selectionKeyPressed: boolean;
|
selectionKeyPressed: boolean;
|
||||||
@@ -20,38 +21,12 @@ interface ZoomPaneProps {
|
|||||||
defaultPosition?: [number, number];
|
defaultPosition?: [number, number];
|
||||||
defaultZoom?: number;
|
defaultZoom?: number;
|
||||||
translateExtent?: TranslateExtent;
|
translateExtent?: TranslateExtent;
|
||||||
onMove?: (flowTransform?: FlowTransform) => void;
|
|
||||||
onMoveStart?: (flowTransform?: FlowTransform) => void;
|
|
||||||
onMoveEnd?: (flowTransform?: FlowTransform) => void;
|
|
||||||
zoomActivationKeyCode?: KeyCode;
|
zoomActivationKeyCode?: KeyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewChanged = (prevTransform: FlowTransform, eventTransform: any): boolean =>
|
export default defineComponent({
|
||||||
prevTransform.x !== eventTransform.x || prevTransform.y !== eventTransform.y || prevTransform.zoom !== eventTransform.k;
|
name: 'ZoomPane',
|
||||||
|
|
||||||
const eventToFlowTransform = (eventTransform: any): FlowTransform => ({
|
|
||||||
x: eventTransform.x,
|
|
||||||
y: eventTransform.y,
|
|
||||||
zoom: eventTransform.k
|
|
||||||
});
|
|
||||||
|
|
||||||
const ZoomPane = defineComponent({
|
|
||||||
props: {
|
props: {
|
||||||
onMove: {
|
|
||||||
type: Function as unknown as PropType<ZoomPaneProps['onMove']>,
|
|
||||||
required: false,
|
|
||||||
default: () => {}
|
|
||||||
},
|
|
||||||
onMoveStart: {
|
|
||||||
type: Function as unknown as PropType<ZoomPaneProps['onMoveStart']>,
|
|
||||||
required: false,
|
|
||||||
default: () => {}
|
|
||||||
},
|
|
||||||
onMoveEnd: {
|
|
||||||
type: Function as unknown as PropType<ZoomPaneProps['onMoveEnd']>,
|
|
||||||
required: false,
|
|
||||||
default: () => {}
|
|
||||||
},
|
|
||||||
zoomOnScroll: {
|
zoomOnScroll: {
|
||||||
type: Boolean as PropType<ZoomPaneProps['zoomOnScroll']>,
|
type: Boolean as PropType<ZoomPaneProps['zoomOnScroll']>,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -85,12 +60,12 @@ const ZoomPane = defineComponent({
|
|||||||
selectionKeyPressed: {
|
selectionKeyPressed: {
|
||||||
type: Boolean as PropType<ZoomPaneProps['selectionKeyPressed']>,
|
type: Boolean as PropType<ZoomPaneProps['selectionKeyPressed']>,
|
||||||
required: false,
|
required: false,
|
||||||
default: undefined
|
default: false
|
||||||
},
|
},
|
||||||
elementsSelectable: {
|
elementsSelectable: {
|
||||||
type: Boolean as PropType<ZoomPaneProps['elementsSelectable']>,
|
type: Boolean as PropType<ZoomPaneProps['elementsSelectable']>,
|
||||||
required: false,
|
required: false,
|
||||||
default: undefined
|
default: true
|
||||||
},
|
},
|
||||||
paneMoveable: {
|
paneMoveable: {
|
||||||
type: Boolean as PropType<ZoomPaneProps['paneMoveable']>,
|
type: Boolean as PropType<ZoomPaneProps['paneMoveable']>,
|
||||||
@@ -120,16 +95,23 @@ const ZoomPane = defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props, { slots }) {
|
setup(props, { slots }) {
|
||||||
const store = inject<RevueFlowStore>('store')!;
|
const store = inject<RevueFlowStore>('store')!;
|
||||||
const zoomPane = ref<HTMLDivElement | null>(null);
|
const hooks = inject<RevueFlowHooks>('hooks')!;
|
||||||
|
const zoomPane = templateRef<HTMLDivElement>('zoom-pane', null);
|
||||||
const prevTransform = ref<FlowTransform>({ x: 0, y: 0, zoom: 0 });
|
const prevTransform = ref<FlowTransform>({ x: 0, y: 0, zoom: 0 });
|
||||||
const zoomActivationKeyPressed = useKeyPress(props.zoomActivationKeyCode);
|
const zoomActivationKeyPressed = useKeyPress(props.zoomActivationKeyCode);
|
||||||
|
|
||||||
watchEffect(() => {
|
const viewChanged = (prevTransform: FlowTransform, eventTransform: ZoomTransform): boolean =>
|
||||||
if (zoomPane.value) useResizeHandler(zoomPane.value, store.updateSize);
|
prevTransform.x !== eventTransform.x || prevTransform.y !== eventTransform.y || prevTransform.zoom !== eventTransform.k;
|
||||||
|
|
||||||
|
const eventToFlowTransform = (eventTransform: ZoomTransform): FlowTransform => ({
|
||||||
|
x: eventTransform.x,
|
||||||
|
y: eventTransform.y,
|
||||||
|
zoom: eventTransform.k
|
||||||
});
|
});
|
||||||
|
|
||||||
watchEffect(() => {
|
const { onReady } = useResizeHandler(zoomPane, store.updateSize);
|
||||||
if (zoomPane.value && props.defaultPosition && props.defaultZoom) {
|
onReady(() => {
|
||||||
|
if (props.defaultPosition && props.defaultZoom) {
|
||||||
const currentTranslateExtent =
|
const currentTranslateExtent =
|
||||||
typeof props.translateExtent !== 'undefined' ? props.translateExtent : store.translateExtent;
|
typeof props.translateExtent !== 'undefined' ? props.translateExtent : store.translateExtent;
|
||||||
const d3ZoomInstance = zoom().scaleExtent([store.minZoom, store.maxZoom]).translateExtent(currentTranslateExtent);
|
const d3ZoomInstance = zoom().scaleExtent([store.minZoom, store.maxZoom]).translateExtent(currentTranslateExtent);
|
||||||
@@ -152,43 +134,41 @@ const ZoomPane = defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watchEffect(() => {
|
whenever(zoomActivationKeyPressed, () => {
|
||||||
if (store.d3Selection && store.d3Zoom) {
|
if (props.panOnScroll) {
|
||||||
if (props.panOnScroll && zoomActivationKeyPressed) {
|
store.d3Selection
|
||||||
store.d3Selection
|
?.on('wheel', (event: WheelEvent) => {
|
||||||
.on('wheel', (event: any) => {
|
event.preventDefault();
|
||||||
event.preventDefault();
|
event.stopImmediatePropagation();
|
||||||
event.stopImmediatePropagation();
|
|
||||||
|
|
||||||
const currentZoom = store.d3Selection?.property('__zoom').k || 1;
|
const currentZoom = store.d3Selection?.property('__zoom').k || 1;
|
||||||
|
|
||||||
if (event.ctrlKey && props.zoomOnPinch) {
|
if (event.ctrlKey && props.zoomOnPinch) {
|
||||||
const point = pointer(event);
|
const point = pointer(event);
|
||||||
// taken from https://github.com/d3/d3-zoom/blob/master/src/zoom.js
|
// taken from https://github.com/d3/d3-zoom/blob/master/src/zoom.js
|
||||||
const pinchDelta = -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * 10;
|
const pinchDelta = -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * 10;
|
||||||
const zoom = currentZoom * Math.pow(2, pinchDelta);
|
const zoom = currentZoom * Math.pow(2, pinchDelta);
|
||||||
if (store.d3Selection) store.d3Zoom?.scaleTo(store.d3Selection, zoom, point);
|
if (store.d3Selection) store.d3Zoom?.scaleTo(store.d3Selection, zoom, point);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// increase scroll speed in firefox
|
// increase scroll speed in firefox
|
||||||
// firefox: deltaMode === 1; chrome: deltaMode === 0
|
// firefox: deltaMode === 1; chrome: deltaMode === 0
|
||||||
const deltaNormalize = event.deltaMode === 1 ? 20 : 1;
|
const deltaNormalize = event.deltaMode === 1 ? 20 : 1;
|
||||||
const deltaX = props.panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize;
|
const deltaX = props.panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize;
|
||||||
const deltaY = props.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize;
|
const deltaY = props.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize;
|
||||||
|
|
||||||
if (store.d3Selection && props.panOnScrollSpeed)
|
if (store.d3Selection && props.panOnScrollSpeed)
|
||||||
store.d3Zoom?.translateBy(
|
store.d3Zoom?.translateBy(
|
||||||
store.d3Selection,
|
store.d3Selection,
|
||||||
-(deltaX / currentZoom) * props.panOnScrollSpeed,
|
-(deltaX / currentZoom) * props.panOnScrollSpeed,
|
||||||
-(deltaY / currentZoom) * props.panOnScrollSpeed
|
-(deltaY / currentZoom) * props.panOnScrollSpeed
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.on('wheel.zoom', null);
|
.on('wheel.zoom', null);
|
||||||
} else if (typeof store.d3ZoomHandler !== 'undefined') {
|
} else if (typeof store.d3ZoomHandler !== 'undefined') {
|
||||||
store.d3Selection.on('wheel', null).on('wheel.zoom', store.d3ZoomHandler);
|
store.d3Selection?.on('wheel', null).on('wheel.zoom', store.d3ZoomHandler);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -197,13 +177,9 @@ const ZoomPane = defineComponent({
|
|||||||
if (props.selectionKeyPressed) {
|
if (props.selectionKeyPressed) {
|
||||||
store.d3Zoom.on('zoom', null);
|
store.d3Zoom.on('zoom', null);
|
||||||
} else {
|
} else {
|
||||||
store.d3Zoom.on('zoom', (event: any) => {
|
store.d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
||||||
store.transform = [event.transform.x, event.transform.y, event.transform.k];
|
store.transform = [event.transform.x, event.transform.y, event.transform.k];
|
||||||
|
hooks.move.trigger(eventToFlowTransform(event.transform));
|
||||||
if (props.onMove) {
|
|
||||||
const flowTransform = eventToFlowTransform(event.transform);
|
|
||||||
props.onMove(flowTransform);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,41 +187,33 @@ const ZoomPane = defineComponent({
|
|||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (store.d3Zoom) {
|
if (store.d3Zoom) {
|
||||||
if (props.onMoveStart) {
|
store.d3Zoom.on('start', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
||||||
store.d3Zoom.on('start', (event: any) => {
|
if (viewChanged(prevTransform.value, event.transform)) {
|
||||||
if (viewChanged(prevTransform.value, event.transform)) {
|
const flowTransform = eventToFlowTransform(event.transform);
|
||||||
const flowTransform = eventToFlowTransform(event.transform);
|
prevTransform.value = flowTransform;
|
||||||
prevTransform.value = flowTransform;
|
|
||||||
|
|
||||||
if (props.onMoveStart) props.onMoveStart(flowTransform);
|
hooks.moveStart.trigger(flowTransform);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
store.d3Zoom.on('start', null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (store.d3Zoom) {
|
if (store.d3Zoom) {
|
||||||
if (props.onMoveEnd) {
|
store.d3Zoom.on('end', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
||||||
store.d3Zoom.on('end', (event: any) => {
|
if (viewChanged(prevTransform.value, event.transform)) {
|
||||||
if (viewChanged(prevTransform.value, event.transform)) {
|
const flowTransform = eventToFlowTransform(event.transform);
|
||||||
const flowTransform = eventToFlowTransform(event.transform);
|
prevTransform.value = flowTransform;
|
||||||
prevTransform.value = flowTransform;
|
|
||||||
|
|
||||||
if (props.onMoveEnd) props.onMoveEnd(flowTransform);
|
hooks.moveEnd.trigger(flowTransform);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
store.d3Zoom.on('end', null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (store.d3Zoom) {
|
if (store.d3Zoom) {
|
||||||
store.d3Zoom.filter((event: any) => {
|
store.d3Zoom.filter((event: MouseEvent) => {
|
||||||
const zoomScroll = props.zoomOnScroll;
|
const zoomScroll = props.zoomOnScroll;
|
||||||
const pinchZoom = props.zoomOnPinch && event.ctrlKey;
|
const pinchZoom = props.zoomOnPinch && event.ctrlKey;
|
||||||
|
|
||||||
@@ -264,20 +232,20 @@ const ZoomPane = defineComponent({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.target.closest('.nowheel') && event.type === 'wheel') {
|
if ((event.target as Element).closest('.nowheel') && event.type === 'wheel') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// when the target element is a node, we still allow zooming
|
// when the target element is a node, we still allow zooming
|
||||||
if (
|
if (
|
||||||
(event.target.closest('.revue-flow__node') || event.target.closest('.revue-flow__edge')) &&
|
((event.target as Element).closest('.revue-flow__node') || (event.target as Element).closest('.revue-flow__edge')) &&
|
||||||
event.type !== 'wheel'
|
event.type !== 'wheel'
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// when the target element is a node selection, we still allow zooming
|
// when the target element is a node selection, we still allow zooming
|
||||||
if (event.target.closest('.revue-flow__nodesselection') && event.type !== 'wheel') {
|
if ((event.target as Element).closest('.revue-flow__nodesselection') && event.type !== 'wheel') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,11 +270,9 @@ const ZoomPane = defineComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<div class="revue-flow__renderer revue-flow__zoompane" ref={zoomPane}>
|
<div class="revue-flow__renderer revue-flow__zoompane" ref="zoom-pane">
|
||||||
{slots.default ? slots.default() : ''}
|
{slots.default ? slots.default() : ''}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ZoomPane;
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export interface RevueFlowHooks {
|
|||||||
edgeUpdateEnd: EventHook<{ event: MouseEvent; edge: Edge }>;
|
edgeUpdateEnd: EventHook<{ event: MouseEvent; edge: Edge }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useRevueFlow = (): RevueFlowHooks & { bind: (emit: (event: string, ...args: any[]) => void) => void } => {
|
export const useRevueFlow = (): RevueFlowHooks & { bind: (emit: (event: string, ...args: any[]) => void) => RevueFlowHooks } => {
|
||||||
const eventHooks: RevueFlowHooks = {
|
const eventHooks: RevueFlowHooks = {
|
||||||
elementClick: createEventHook<{ event: MouseEvent; element: Node | Edge }>(),
|
elementClick: createEventHook<{ event: MouseEvent; element: Node | Edge }>(),
|
||||||
elementsRemove: createEventHook<Elements>(),
|
elementsRemove: createEventHook<Elements>(),
|
||||||
@@ -93,6 +93,8 @@ export const useRevueFlow = (): RevueFlowHooks & { bind: (emit: (event: string,
|
|||||||
emit(key, data);
|
emit(key, data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return eventHooks;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
import { createEventHook, MaybeRef, unrefElement, until, useEventListener, useResizeObserver } from '@vueuse/core';
|
||||||
import { getDimensions } from '../utils';
|
import { getDimensions } from '../utils';
|
||||||
import { Dimensions } from '../types';
|
import { Dimensions } from '../types';
|
||||||
import { MaybeRef, unrefElement, until, useEventListener, useResizeObserver } from '@vueuse/core';
|
|
||||||
|
|
||||||
export default (rendererNode: MaybeRef<HTMLDivElement>, updateSize: (size: Dimensions) => any) => {
|
export default (rendererNode: MaybeRef<HTMLDivElement>, updateSize: (size: Dimensions) => any) => {
|
||||||
|
const resizeHandlerHook = createEventHook();
|
||||||
const updateDimensions = () => {
|
const updateDimensions = () => {
|
||||||
if (!rendererNode) {
|
if (!unrefElement(rendererNode)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,5 +24,10 @@ export default (rendererNode: MaybeRef<HTMLDivElement>, updateSize: (size: Dimen
|
|||||||
updateDimensions();
|
updateDimensions();
|
||||||
useEventListener(window, 'resize', updateDimensions);
|
useEventListener(window, 'resize', updateDimensions);
|
||||||
useResizeObserver(unrefElement(rendererNode), () => updateDimensions());
|
useResizeObserver(unrefElement(rendererNode), () => updateDimensions());
|
||||||
|
resizeHandlerHook.trigger(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
onReady: resizeHandlerHook.on
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -65,8 +65,7 @@ const useZoomPanHelper = (): { onReady: EventHookOn<ZoomPanHelperFunctions> } =>
|
|||||||
project: (position: XYPosition) => {
|
project: (position: XYPosition) => {
|
||||||
const { transform, snapToGrid, snapGrid } = store;
|
const { transform, snapToGrid, snapGrid } = store;
|
||||||
return pointToRendererPoint(position, transform, snapToGrid, snapGrid);
|
return pointToRendererPoint(position, transform, snapToGrid, snapGrid);
|
||||||
},
|
}
|
||||||
initialized: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
until(d3Zoom)
|
until(d3Zoom)
|
||||||
|
|||||||
Vendored
+15
@@ -0,0 +1,15 @@
|
|||||||
|
import Vue, { VNode } from 'vue';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace JSX {
|
||||||
|
// tslint:disable no-empty-interface
|
||||||
|
interface Element extends VNode {}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
interface ElementClass extends Vue {}
|
||||||
|
|
||||||
|
interface IntrinsicElements {
|
||||||
|
[elem: string]: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -666,7 +666,6 @@ export interface ZoomPanHelperFunctions {
|
|||||||
setCenter: (x: number, y: number, zoom?: number) => void;
|
setCenter: (x: number, y: number, zoom?: number) => void;
|
||||||
fitBounds: (bounds: Rect, padding?: number) => void;
|
fitBounds: (bounds: Rect, padding?: number) => void;
|
||||||
project: (position: XYPosition) => XYPosition;
|
project: (position: XYPosition) => XYPosition;
|
||||||
initialized: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OnEdgeUpdateFunc<T = any> = (oldEdge: Edge<T>, newConnection: Connection) => void;
|
export type OnEdgeUpdateFunc<T = any> = (oldEdge: Edge<T>, newConnection: Connection) => void;
|
||||||
|
|||||||
@@ -262,10 +262,10 @@
|
|||||||
"@babel/helper-validator-identifier" "^7.14.5"
|
"@babel/helper-validator-identifier" "^7.14.5"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
"@braks/revue-draggable@^0.2.4":
|
"@braks/revue-draggable@^0.2.5":
|
||||||
version "0.2.4"
|
version "0.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/@braks/revue-draggable/-/revue-draggable-0.2.4.tgz#a3af0c27f32a1845f4182ae579ce80cc88fd9019"
|
resolved "https://registry.yarnpkg.com/@braks/revue-draggable/-/revue-draggable-0.2.5.tgz#28a94ee4633aab25bcee28877511e0c9ad262f59"
|
||||||
integrity sha512-8hDMuO7fl5KCshmg87aK3bQt371f3jPOZ35ip4gdcalP2fEnusVv8+AjWP1DHR2lObDTjovn30LzCK4Ily08LA==
|
integrity sha512-iKGLFXaurfEqoyPbkANv8ZuO1229krVvUQDDYN9le5WlVssFWcQtA3hNvBD2TPuwil226qXv9SqXGp5kgDhAsA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vueuse/core" "^5.1.3"
|
"@vueuse/core" "^5.1.3"
|
||||||
vue-demi latest
|
vue-demi latest
|
||||||
|
|||||||
Reference in New Issue
Block a user