feat: script setup style
* Replace jsx with script setup syntax
* Simplify logic and make it more "composable"
* Move Zoom functions to useZoom composable
* Update eslint config & tsconfig
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
<script lang="ts" setup>
|
||||
import { CSSProperties } from 'vue'
|
||||
import Edge from '~/components/Edges/Edge'
|
||||
import { ConnectionLineComponent, ConnectionLineType, ConnectionMode, Dimensions, Edge as TEdge, Transform } from '~/types'
|
||||
import MarkerDefinitions from '~/container/MarkerDefinitions.vue'
|
||||
|
||||
interface EdgeRendererProps {
|
||||
edges: TEdge[]
|
||||
edgeTypes: any
|
||||
transform: Transform
|
||||
connectionLineType: ConnectionLineType
|
||||
connectionLineStyle?: CSSProperties
|
||||
connectionLineComponent?: ConnectionLineComponent
|
||||
connectionMode?: ConnectionMode
|
||||
arrowHeadColor: string
|
||||
markerEndId?: string
|
||||
onlyRenderVisibleElements: boolean
|
||||
edgeUpdaterRadius?: number
|
||||
dimensions: Dimensions
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<EdgeRendererProps>(), {
|
||||
transform: () => [0, 0, 1],
|
||||
edges: () => [],
|
||||
arrowHeadColor: '#fff',
|
||||
dimensions: () => ({ width: 0, height: 0 }),
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<svg :width="props.dimensions.width" :height="props.dimensions.height" class="revue-flow__edges">
|
||||
<MarkerDefinitions :color="props.arrowHeadColor" />
|
||||
<g
|
||||
:transform="
|
||||
props.transform &&
|
||||
props.transform.length &&
|
||||
`translate(${props.transform[0]},${props.transform[1]}) scale(${props.transform[2]})`
|
||||
"
|
||||
>
|
||||
<template v-for="(edge, i) of props.edges" :key="`edge-${i}`">
|
||||
<Edge
|
||||
:edge="edge"
|
||||
:type="props.edgeTypes[edge.type || 'default']"
|
||||
:only-render-visible-elements="props.onlyRenderVisibleElements"
|
||||
:marker-end-id="props.markerEndId"
|
||||
/>
|
||||
</template>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -1,13 +1,13 @@
|
||||
import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges'
|
||||
import { rectToBox } from '../../utils/graph'
|
||||
import { Position, Node, XYPosition, ElementId, HandleElement, Transform, Edge, EdgeType } from '../../types'
|
||||
import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '~/components/Edges'
|
||||
import { rectToBox } from '~/utils/graph'
|
||||
import { Position, Node, XYPosition, ElementId, HandleElement, Transform, Edge, EdgeType } from '~/types'
|
||||
|
||||
export function createEdgeTypes(edgeTypes: Record<string, EdgeType>): Record<string, EdgeType> {
|
||||
const standardTypes: Record<string, EdgeType> = {
|
||||
default: edgeTypes.default || BezierEdge,
|
||||
straight: edgeTypes.bezier || StraightEdge,
|
||||
step: edgeTypes.step || StepEdge,
|
||||
smoothstep: edgeTypes.step || SmoothStepEdge
|
||||
smoothstep: edgeTypes.step || SmoothStepEdge,
|
||||
}
|
||||
|
||||
const wrappedTypes = {} as Record<string, EdgeType>
|
||||
@@ -21,7 +21,7 @@ export function createEdgeTypes(edgeTypes: Record<string, EdgeType>): Record<str
|
||||
|
||||
return {
|
||||
...standardTypes,
|
||||
...specialTypes
|
||||
...specialTypes,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,22 +35,22 @@ export function getHandlePosition(position: Position, node: Node, handle: any |
|
||||
case Position.Top:
|
||||
return {
|
||||
x: x + width / 2,
|
||||
y
|
||||
y,
|
||||
}
|
||||
case Position.Right:
|
||||
return {
|
||||
x: x + width,
|
||||
y: y + height / 2
|
||||
y: y + height / 2,
|
||||
}
|
||||
case Position.Bottom:
|
||||
return {
|
||||
x: x + width / 2,
|
||||
y: y + height
|
||||
y: y + height,
|
||||
}
|
||||
case Position.Left:
|
||||
return {
|
||||
x,
|
||||
y: y + height / 2
|
||||
y: y + height / 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,7 +85,7 @@ export const getEdgePositions = (
|
||||
sourcePosition: Position,
|
||||
targetNode: Node,
|
||||
targetHandle: HandleElement | unknown,
|
||||
targetPosition: Position
|
||||
targetPosition: Position,
|
||||
): EdgePositions => {
|
||||
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNode, sourceHandle)
|
||||
const targetHandlePos = getHandlePosition(targetPosition, targetNode, targetHandle)
|
||||
@@ -94,7 +94,7 @@ export const getEdgePositions = (
|
||||
sourceX: sourceHandlePos.x,
|
||||
sourceY: sourceHandlePos.y,
|
||||
targetX: targetHandlePos.x,
|
||||
targetY: targetHandlePos.y
|
||||
targetY: targetHandlePos.y,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ export function isEdgeVisible({ sourcePos, targetPos, width, height, transform }
|
||||
x: Math.min(sourcePos.x, targetPos.x),
|
||||
y: Math.min(sourcePos.y, targetPos.y),
|
||||
x2: Math.max(sourcePos.x, targetPos.x),
|
||||
y2: Math.max(sourcePos.y, targetPos.y)
|
||||
y2: Math.max(sourcePos.y, targetPos.y),
|
||||
}
|
||||
|
||||
if (edgeBox.x === edgeBox.x2) {
|
||||
@@ -126,7 +126,7 @@ export function isEdgeVisible({ sourcePos, targetPos, width, height, transform }
|
||||
x: (0 - transform[0]) / transform[2],
|
||||
y: (0 - transform[1]) / transform[2],
|
||||
width: width / transform[2],
|
||||
height: height / transform[2]
|
||||
height: height / transform[2],
|
||||
})
|
||||
|
||||
const xOverlap = Math.max(0, Math.min(viewBox.x2, edgeBox.x2) - Math.max(viewBox.x, edgeBox.x))
|
||||
@@ -152,6 +152,6 @@ export const getSourceTargetNodes = (edge: Edge, nodes: Node[]): SourceTargetNod
|
||||
}
|
||||
return res
|
||||
},
|
||||
{ sourceNode: null, targetNode: null } as SourceTargetNode
|
||||
{ sourceNode: null, targetNode: null } as SourceTargetNode,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
<script lang="ts" setup>
|
||||
import '~/style.css'
|
||||
import '~/theme-default.css'
|
||||
import { CSSProperties } from 'vue'
|
||||
import ZoomPane from './ZoomPane.vue'
|
||||
import SelectionPane from './SelectionPane.vue'
|
||||
import NodeRenderer from '~/container/NodeRenderer.vue'
|
||||
import EdgeRenderer from '~/container/EdgeRenderer.vue'
|
||||
import {
|
||||
ConnectionLineType,
|
||||
ConnectionMode,
|
||||
Elements,
|
||||
PanOnScrollMode,
|
||||
RevueFlowStore,
|
||||
Node,
|
||||
Edge,
|
||||
NodeType,
|
||||
EdgeType,
|
||||
ConnectionLineComponent,
|
||||
KeyCode,
|
||||
TranslateExtent,
|
||||
NodeExtent,
|
||||
} from '~/types'
|
||||
import { RevueFlowHooks, useRevueFlow } from '~/hooks/RevueFlowHooks'
|
||||
import configureStore from '~/store/configure-store'
|
||||
import { initialState } from '~/store'
|
||||
import { createNodeTypes } from '~/container/NodeRenderer/utils'
|
||||
import InputNode from '~/components/Nodes/InputNode'
|
||||
import DefaultNode from '~/components/Nodes/DefaultNode'
|
||||
import OutputNode from '~/components/Nodes/OutputNode'
|
||||
import { BezierEdge, SmoothStepEdge, StepEdge, StraightEdge } from '~/components/Edges'
|
||||
import { createEdgeTypes } from '~/container/EdgeRenderer/utils'
|
||||
import { isEdge, isNode, parseEdge, parseNode } from '~/utils/graph'
|
||||
|
||||
interface RevueFlowProps {
|
||||
modelValue: Elements
|
||||
nodeTypes?: Record<string, NodeType>
|
||||
edgeTypes?: Record<string, EdgeType>
|
||||
connectionMode?: ConnectionMode
|
||||
connectionLineType?: ConnectionLineType
|
||||
connectionLineStyle?: CSSProperties
|
||||
connectionLineComponent?: ConnectionLineComponent
|
||||
deleteKeyCode?: KeyCode
|
||||
selectionKeyCode?: KeyCode
|
||||
multiSelectionKeyCode?: KeyCode
|
||||
zoomActivationKeyCode?: KeyCode
|
||||
snapToGrid?: boolean
|
||||
snapGrid?: [number, number]
|
||||
onlyRenderVisibleElements?: boolean
|
||||
nodesDraggable?: boolean
|
||||
nodesConnectable?: boolean
|
||||
elementsSelectable?: boolean
|
||||
selectNodesOnDrag?: boolean
|
||||
paneMoveable?: boolean
|
||||
minZoom?: number
|
||||
maxZoom?: number
|
||||
defaultZoom?: number
|
||||
defaultPosition?: [number, number]
|
||||
translateExtent?: TranslateExtent
|
||||
nodeExtent?: NodeExtent
|
||||
arrowHeadColor?: string
|
||||
markerEndId?: string
|
||||
zoomOnScroll?: boolean
|
||||
zoomOnPinch?: boolean
|
||||
panOnScroll?: boolean
|
||||
panOnScrollSpeed?: number
|
||||
panOnScrollMode?: PanOnScrollMode
|
||||
zoomOnDoubleClick?: boolean
|
||||
edgeUpdaterRadius?: number
|
||||
}
|
||||
|
||||
type NextElements = {
|
||||
nextNodes: Node[]
|
||||
nextEdges: Edge[]
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<RevueFlowProps>(), {
|
||||
modelValue: () => [] as Elements,
|
||||
connectionMode: ConnectionMode.Strict,
|
||||
connectionLineType: ConnectionLineType.Bezier,
|
||||
selectionKeyCode: 'Shift',
|
||||
multiSelectionKeyCode: 'Meta',
|
||||
zoomActivationKeyCode: 'Meta',
|
||||
deleteKeyCode: 'Backspace',
|
||||
snapToGrid: false,
|
||||
snapGrid: () => [15, 15],
|
||||
onlyRenderVisibleElements: false,
|
||||
nodesConnectable: true,
|
||||
elementsSelectable: true,
|
||||
selectNodesOnDrag: true,
|
||||
minZoom: 0.5,
|
||||
maxZoom: 2,
|
||||
defaultZoom: 1,
|
||||
defaultPosition: () => [0, 0],
|
||||
translateExtent: () => [
|
||||
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
|
||||
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
|
||||
],
|
||||
nodeExtent: () => [
|
||||
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
|
||||
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
|
||||
],
|
||||
arrowHeadColor: '#b1b1b7',
|
||||
zoomOnScroll: true,
|
||||
zoomOnPinch: true,
|
||||
zoomOnDoubleClick: true,
|
||||
panOnScroll: false,
|
||||
panOnScrollSpeed: 0.5,
|
||||
panOnScrollMode: PanOnScrollMode.Free,
|
||||
paneMoveable: true,
|
||||
edgeUpdaterRadius: 10,
|
||||
})
|
||||
const emit = defineEmits(Object.keys(useRevueFlow()))
|
||||
|
||||
const store = configureStore(initialState)()
|
||||
provide<RevueFlowStore>('store', store)
|
||||
|
||||
const hooks = useRevueFlow().bind(emit)
|
||||
provide<RevueFlowHooks>('hooks', hooks)
|
||||
|
||||
const elements = useVModel(props, 'modelValue', emit)
|
||||
|
||||
const defaultNodeTypes = {
|
||||
input: InputNode,
|
||||
default: DefaultNode,
|
||||
output: OutputNode,
|
||||
}
|
||||
|
||||
const defaultEdgeTypes = {
|
||||
default: BezierEdge,
|
||||
straight: StraightEdge,
|
||||
step: StepEdge,
|
||||
smoothstep: SmoothStepEdge,
|
||||
}
|
||||
|
||||
const nodeTypes = createNodeTypes({ ...defaultNodeTypes, ...props.nodeTypes })
|
||||
const edgeTypes = createEdgeTypes({ ...defaultEdgeTypes, ...props.edgeTypes })
|
||||
const nodes = ref<Node[]>([])
|
||||
const edges = ref<Edge[]>([])
|
||||
|
||||
const setElements = (elements: Elements) => {
|
||||
const propElements = elements
|
||||
const nextElements: NextElements = {
|
||||
nextNodes: [],
|
||||
nextEdges: [],
|
||||
}
|
||||
const { nextNodes, nextEdges } = propElements.reduce((res, propElement): NextElements => {
|
||||
if (isNode(propElement)) {
|
||||
const storeNode = nodes.value.find((node) => node.id === propElement.id)
|
||||
|
||||
if (storeNode) {
|
||||
const updatedNode: Node = {
|
||||
...storeNode,
|
||||
...propElement,
|
||||
}
|
||||
|
||||
if (storeNode.position.x !== propElement.position.x || storeNode.position.y !== propElement.position.y) {
|
||||
updatedNode.__rf.position = propElement.position
|
||||
}
|
||||
|
||||
if (typeof propElement.type !== 'undefined' && propElement.type !== storeNode.type) {
|
||||
// we reset the elements dimensions here in order to force a re-calculation of the bounds.
|
||||
// When the type of a node changes it is possible that the number or positions of handles changes too.
|
||||
updatedNode.__rf.width = null
|
||||
}
|
||||
|
||||
res.nextNodes.push(updatedNode)
|
||||
} else {
|
||||
res.nextNodes.push(parseNode(propElement, props.nodeExtent))
|
||||
}
|
||||
} else if (isEdge(propElement)) {
|
||||
const storeEdge = edges.value.find((se) => se.id === propElement.id)
|
||||
|
||||
if (storeEdge) {
|
||||
res.nextEdges.push({
|
||||
...storeEdge,
|
||||
...propElement,
|
||||
})
|
||||
} else {
|
||||
res.nextEdges.push(parseEdge(propElement))
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}, nextElements)
|
||||
|
||||
nodes.value = nextNodes
|
||||
edges.value = nextEdges
|
||||
}
|
||||
|
||||
setElements(elements.value)
|
||||
</script>
|
||||
<template>
|
||||
<div class="revue-flow">
|
||||
<ZoomPane v-bind="props">
|
||||
<template #default="{ transform, dimensions }">
|
||||
<SelectionPane
|
||||
:delete-key-code="props.deleteKeyCode"
|
||||
:multi-selection-key-code="props.multiSelectionKeyCode"
|
||||
:selection-key-code="props.selectionKeyCode"
|
||||
>
|
||||
<NodeRenderer v-bind="props" :node-types="nodeTypes" :nodes="nodes" :transform="transform" :dimensions="dimensions" />
|
||||
<EdgeRenderer
|
||||
v-if="false"
|
||||
v-bind="props"
|
||||
:edge-types="edgeTypes"
|
||||
:edges="edges"
|
||||
:transform="transform"
|
||||
:dimensions="dimensions"
|
||||
/>
|
||||
</SelectionPane>
|
||||
</template>
|
||||
</ZoomPane>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script lang="ts" setup>
|
||||
interface MarkerProps {
|
||||
id: string
|
||||
}
|
||||
|
||||
const props = defineProps<MarkerProps>()
|
||||
</script>
|
||||
<template>
|
||||
<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"
|
||||
>
|
||||
<slot></slot>
|
||||
</marker>
|
||||
</template>
|
||||
@@ -0,0 +1,35 @@
|
||||
<script lang="ts" setup>
|
||||
import Marker from './Marker.vue'
|
||||
|
||||
interface MarkerDefinitionsProps {
|
||||
color: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<MarkerDefinitionsProps>(), {
|
||||
color: '',
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<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>
|
||||
</template>
|
||||
@@ -0,0 +1,74 @@
|
||||
<script lang="ts" setup>
|
||||
import Node from '~/components/Nodes/Node'
|
||||
import { NodeType, Node as TNode, Transform, Dimensions } from '~/types'
|
||||
import { getNodesInside } from '~/utils/graph'
|
||||
|
||||
interface NodeRendererProps {
|
||||
nodes: TNode[]
|
||||
transform: Transform
|
||||
nodeTypes: Record<string, NodeType>
|
||||
selectNodesOnDrag: boolean
|
||||
snapToGrid: boolean
|
||||
snapGrid: [number, number]
|
||||
onlyRenderVisibleElements: boolean
|
||||
dimensions: Dimensions
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<NodeRendererProps>(), {
|
||||
selectNodesOnDrag: true,
|
||||
snapGrid: () => [15, 15],
|
||||
snapToGrid: false,
|
||||
onlyRenderVisibleElements: false,
|
||||
transform: () => [0, 0, 1],
|
||||
nodes: () => [],
|
||||
dimensions: () => ({ width: 0, height: 0 }),
|
||||
})
|
||||
|
||||
const getNodes = () =>
|
||||
props.onlyRenderVisibleElements
|
||||
? props.nodes &&
|
||||
getNodesInside(
|
||||
props.nodes,
|
||||
{
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: props.dimensions.width,
|
||||
height: props.dimensions.height,
|
||||
},
|
||||
props.transform,
|
||||
true,
|
||||
)
|
||||
: props.nodes
|
||||
|
||||
const cNodes = computed(() => getNodes())
|
||||
|
||||
const type = (node: TNode) => {
|
||||
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 type
|
||||
}
|
||||
return false
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="revue-flow__nodes"
|
||||
:style="{ transform: `translate(${props.transform[0]}px,${props.transform[1]}px) scale(${props.transform[2]})` }"
|
||||
>
|
||||
<template v-for="(node, i) of cNodes" :key="`node-${i}`">
|
||||
<div>Node</div>
|
||||
<Node
|
||||
v-if="type(node)"
|
||||
:node="node"
|
||||
:snap-grid="props.snapGrid"
|
||||
:snap-to-grid="props.snapToGrid"
|
||||
:select-nodes-on-drag="props.selectNodesOnDrag"
|
||||
:type="type(node)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,13 +1,13 @@
|
||||
import DefaultNode from '../../components/Nodes/DefaultNode'
|
||||
import InputNode from '../../components/Nodes/InputNode'
|
||||
import OutputNode from '../../components/Nodes/OutputNode'
|
||||
import { NodeType } from '../../types'
|
||||
import DefaultNode from '~/components/Nodes/DefaultNode'
|
||||
import InputNode from '~/components/Nodes/InputNode'
|
||||
import OutputNode from '~/components/Nodes/OutputNode'
|
||||
import { NodeType } from '~/types'
|
||||
|
||||
export function createNodeTypes(nodeTypes: Record<string, NodeType>): Record<string, NodeType> {
|
||||
const standardTypes: Record<string, NodeType> = {
|
||||
input: nodeTypes.input || InputNode,
|
||||
default: nodeTypes.default || DefaultNode,
|
||||
output: nodeTypes.output || OutputNode
|
||||
output: nodeTypes.output || OutputNode,
|
||||
}
|
||||
|
||||
const wrappedTypes = {} as NodeType
|
||||
@@ -21,6 +21,6 @@ export function createNodeTypes(nodeTypes: Record<string, NodeType>): Record<str
|
||||
|
||||
return {
|
||||
...standardTypes,
|
||||
...specialTypes
|
||||
...specialTypes,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<script lang="ts" setup>
|
||||
import { inject } from 'vue'
|
||||
import { RevueFlowHooks } from '~/hooks/RevueFlowHooks'
|
||||
import useKeyPress from '~/hooks/useKeyPress'
|
||||
import { KeyCode } from '~/types'
|
||||
import useGlobalKeyHandler from '~/hooks/useGlobalKeyHandler'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
selectionKeyCode: KeyCode
|
||||
deleteKeyCode: KeyCode
|
||||
multiSelectionKeyCode: KeyCode
|
||||
}>(),
|
||||
{
|
||||
selectionKeyCode: 'Shift',
|
||||
deleteKeyCode: 'Backspace',
|
||||
multiSelectionKeyCode: 'Meta',
|
||||
},
|
||||
)
|
||||
const hooks = inject<RevueFlowHooks>('hooks')!
|
||||
const keyPressed = useKeyPress(props.selectionKeyCode)
|
||||
const nodeSelectionActive = ref(false)
|
||||
const selectedElements = ref([])
|
||||
|
||||
const onClick = (event: MouseEvent) => {
|
||||
hooks.paneClick.trigger(event)
|
||||
nodeSelectionActive.value = false
|
||||
selectedElements.value = []
|
||||
}
|
||||
|
||||
const onContextMenu = (event: MouseEvent) => hooks.paneContextMenu.trigger(event)
|
||||
|
||||
const onWheel = (event: WheelEvent) => hooks.paneScroll.trigger(event)
|
||||
|
||||
useGlobalKeyHandler({
|
||||
onElementsRemove: hooks.elementsRemove.trigger,
|
||||
deleteKeyCode: props.deleteKeyCode,
|
||||
multiSelectionKeyCode: props.multiSelectionKeyCode,
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<slot></slot>
|
||||
<div v-if="keyPressed" id="user-selection">User Selection</div>
|
||||
<div v-if="nodeSelectionActive" id="nodes-selection">Nodes Selection</div>
|
||||
<div class="revue-flow__pane" @click="onClick" @contextmenu="onContextMenu" @wheel="onWheel" />
|
||||
</template>
|
||||
@@ -0,0 +1,54 @@
|
||||
<script lang="ts" setup>
|
||||
import { RevueFlowOptions, ConnectionLineType, ConnectionMode, Elements, PanOnScrollMode } from '~/types'
|
||||
import useZoom from '~/composables/useZoom'
|
||||
import useResizeHandler from '~/hooks/useResizeHandler'
|
||||
|
||||
interface RevueFlowProps extends RevueFlowOptions {
|
||||
modelValue: Elements
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<RevueFlowProps>(), {
|
||||
modelValue: () => [] as Elements,
|
||||
connectionMode: ConnectionMode.Strict,
|
||||
connectionLineType: ConnectionLineType.Bezier,
|
||||
selectionKeyCode: 'Shift',
|
||||
multiSelectionKeyCode: 'Meta',
|
||||
zoomActivationKeyCode: 'Meta',
|
||||
deleteKeyCode: 'Backspace',
|
||||
snapToGrid: false,
|
||||
snapGrid: () => [15, 15],
|
||||
onlyRenderVisibleElements: false,
|
||||
nodesConnectable: true,
|
||||
elementsSelectable: true,
|
||||
selectNodesOnDrag: true,
|
||||
minZoom: 0.5,
|
||||
maxZoom: 2,
|
||||
defaultZoom: 1,
|
||||
defaultPosition: () => [0, 0],
|
||||
translateExtent: () => [
|
||||
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
|
||||
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
|
||||
],
|
||||
nodeExtent: () => [
|
||||
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
|
||||
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
|
||||
],
|
||||
arrowHeadColor: '#b1b1b7',
|
||||
zoomOnScroll: true,
|
||||
zoomOnPinch: true,
|
||||
zoomOnDoubleClick: true,
|
||||
panOnScroll: false,
|
||||
panOnScrollSpeed: 0.5,
|
||||
panOnScrollMode: PanOnScrollMode.Free,
|
||||
paneMoveable: true,
|
||||
edgeUpdaterRadius: 10,
|
||||
})
|
||||
const zoomPane = templateRef<HTMLDivElement>('zoom-pane', null)
|
||||
const transform = useZoom(zoomPane, props)
|
||||
const dimensions = useResizeHandler(zoomPane)
|
||||
</script>
|
||||
<template>
|
||||
<div ref="zoom-pane" class="revue-flow__renderer revue-flow__zoompane">
|
||||
<slot :transform="transform" :dimensions="dimensions"></slot>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user