update(deps): Add unplugin-components

fix: Excessive rerender of edge texts
update(script-setup): Update some examples

* Remove more jsx files
This commit is contained in:
Braks
2021-10-20 22:39:54 +02:00
parent 1ff3a8dc9c
commit a5ae6a4e48
38 changed files with 377 additions and 1513 deletions
@@ -1,5 +1,5 @@
<script lang="ts" setup>
export type ControlButtonProps = HTMLButtonElement
export interface ControlButtonProps extends HTMLButtonElement {}
const props = defineProps<ControlButtonProps>()
</script>
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { HTMLAttributes } from 'vue'
import MiniMapNodeDepr from './MiniMapNodeDepr'
import MiniMapNode from './MiniMapNode.vue'
import { getRectOfNodes, getBoundsofRects } from '~/utils/graph'
import { Node, Rect, RevueFlowStore } from '~/types'
@@ -67,7 +67,7 @@ const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crisp
<template>
<svg :width="elementWidth" :height="elementHeight" :viewBox="`${x} ${y} ${width} ${height}`" class="revue-flow__minimap">
<template v-for="(node, i) of store.nodes" :key="`mini-map-node-${i}`">
<MiniMapNodeDepr
<MiniMapNode
v-if="!node.isHidden"
:x="node.__rf.position.x"
:y="node.__rf.position.y"
+26
View File
@@ -0,0 +1,26 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/vue-next/pull/3399
declare module 'vue' {
export interface GlobalComponents {
BezierEdge: typeof import('./components/Edges/BezierEdge.vue')['default']
ConnectionLine: typeof import('./components/ConnectionLine/ConnectionLine.vue')['default']
DefaultNode: typeof import('./components/Nodes/DefaultNode.vue')['default']
Edge: typeof import('./components/Edges/Edge.vue')['default']
EdgeAnchor: typeof import('./components/Edges/EdgeAnchor.vue')['default']
EdgeText: typeof import('./components/Edges/EdgeText.vue')['default']
Handle: typeof import('./components/Handle/Handle.vue')['default']
InputNode: typeof import('./components/Nodes/InputNode.vue')['default']
Node: typeof import('./components/Nodes/Node.vue')['default']
NodesSelection: typeof import('./components/NodesSelection/NodesSelection.vue')['default']
OutputNode: typeof import('./components/Nodes/OutputNode.vue')['default']
SelectionRect: typeof import('./components/UserSelection/SelectionRect.vue')['default']
SmoothStepEdge: typeof import('./components/Edges/SmoothStepEdge.vue')['default']
StepEdge: typeof import('./components/Edges/StepEdge.vue')['default']
StraightEdge: typeof import('./components/Edges/StraightEdge.vue')['default']
UserSelection: typeof import('./components/UserSelection/UserSelection.vue')['default']
}
}
export { }
+2
View File
@@ -180,6 +180,8 @@ const visible = computed(() => !props.edge.isHidden && isVisible(edgePos.value))
data: props.edge.data,
style: props.edge.style,
arrowHeadType: props.edge.arrowHeadType,
sourcePosition,
targetPosition,
sourceX: edgePos.sourceX,
sourceY: edgePos.sourceY,
targetX: edgePos.targetX,
+10 -24
View File
@@ -1,6 +1,5 @@
<script lang="ts" setup>
import { HTMLAttributes, ref, VNode, watchEffect } from 'vue'
import { Rect } from '~/types'
import { HTMLAttributes, VNode } from 'vue'
interface EdgeTextProps extends HTMLAttributes {
x: number
@@ -22,30 +21,14 @@ const props = withDefaults(defineProps<EdgeTextProps>(), {
})
const edgeRef = templateRef<SVGTextElement>('edge-text', null)
const edgeTextBox = ref<Rect>({ x: 0, y: 0, width: 0, height: 0 })
watchEffect(() => {
const textBbox = edgeRef.value?.getBBox()
if (textBbox) {
edgeTextBox.value = {
x: textBbox.x,
y: textBbox.y,
width: textBbox.width,
height: textBbox.height,
}
}
})
const { width = 0, height = 0, x = 0, y = 0 } = useElementBounding(edgeRef)
</script>
<template>
<g
:transform="`translate(${props.x - edgeTextBox.width / 2} ${props.y - edgeTextBox.height / 2})`"
class="revue-flow__edge-textwrapper"
>
<g :transform="`translate(${props.x - width / 2} ${props.y - height / 2})`" class="revue-flow__edge-textwrapper">
<rect
v-if="props.labelShowBg"
:width="edgeTextBox.width + 2 * props.labelBgPadding[0] + 'px'"
:height="edgeTextBox.height + 2 * props.labelBgPadding[1] + 'px'"
:width="width + 2 * props.labelBgPadding[0] + 'px'"
:height="height + 2 * props.labelBgPadding[1] + 'px'"
:x="-props.labelBgPadding[0]"
:y="-props.labelBgPadding[1]"
class="revue-flow__edge-textbg"
@@ -53,8 +36,11 @@ watchEffect(() => {
:rx="props.labelBgBorderRadius"
:ry="props.labelBgBorderRadius"
/>
<text ref="edge-text" class="revue-flow__edge-text" :y="edgeTextBox.height / 2" dy="0.3em" :style="props.labelStyle">
{{ props.label }}
<text ref="edge-text" class="revue-flow__edge-text" :y="height / 2" dy="0.3em" :style="props.labelStyle">
<component :is="props.label" v-if="typeof props.label !== 'string'" />
<template v-else>
{{ props.label }}
</template>
</text>
<slot></slot>
</g>
+4 -1
View File
@@ -19,7 +19,10 @@ const props = withDefaults(defineProps<DefaultNodeProps>(), {
<template>
<div class="revue-flow__node-default">
<Handle type="target" :position="props.targetPosition" :is-connectable="props.connectable" />
{{ props.data?.label }}
<component :is="props.data?.label" v-if="typeof props.data?.label !== 'string'" />
<template v-else>
{{ props.data?.label }}
</template>
<Handle type="source" :position="props.sourcePosition" :is-connectable="props.connectable" />
</div>
</template>
+4 -1
View File
@@ -16,7 +16,10 @@ const props = withDefaults(defineProps<InputNodeProps>(), {
</script>
<template>
<div class="revue-flow__node-input">
{{ props.data?.label }}
<component :is="props.data?.label" v-if="typeof props.data?.label !== 'string'" />
<template v-else>
{{ props.data?.label }}
</template>
<Handle type="source" :position="props.sourcePosition" :connectable="props.connectable" />
</div>
</template>
+4 -1
View File
@@ -16,7 +16,10 @@ const props = withDefaults(defineProps<OutputNodeProps>(), {
</script>
<template>
<div class="revue-flow__node-output">
{{ props.data?.label }}
<component :is="props.data?.label" v-if="typeof props.data?.label !== 'string'" />
<template v-else>
{{ props.data?.label }}
</template>
<Handle type="source" :position="props.targetPosition" :is-connectable="props.connectable" />
</div>
</template>
@@ -1,6 +1,7 @@
<script lang="ts" setup>
import { CSSProperties } from 'vue'
import { getSourceTargetNodes } from '~/container/EdgeRenderer/utils'
import { getSourceTargetNodes } from './utils'
import MarkerDefinitions from './MarkerDefinitions.vue'
import Edge from '~/components/Edges/Edge.vue'
import ConnectionLine from '~/components/ConnectionLine/ConnectionLine.vue'
import {
@@ -12,7 +13,6 @@ import {
RevueFlowStore,
Transform,
} from '~/types'
import MarkerDefinitions from '~/container/MarkerDefinitions.vue'
interface EdgeRendererProps {
edgeTypes: Record<string, EdgeType>
-30
View File
@@ -1,30 +0,0 @@
import { defineComponent } from 'vue'
interface MarkerProps {
id: string
}
export default defineComponent({
props: {
id: {
type: String,
required: true
}
},
setup(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>
)
}
})
@@ -1,43 +0,0 @@
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: false,
default: ''
}
},
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>
)
}
})
-99
View File
@@ -1,99 +0,0 @@
import { CSSProperties, defineComponent, inject, PropType } from 'vue'
import { ConnectionLineType, ConnectionLineComponent, ConnectionMode, RevueFlowStore } from '../../types'
import ConnectionLine from '../../components/ConnectionLine'
import Edge from '../../components/Edges/EdgeDepr'
import MarkerDefinitions from './MarkerDefinitions'
interface EdgeRendererProps {
edgeTypes: any
connectionLineType: ConnectionLineType
connectionLineStyle?: CSSProperties
connectionLineComponent?: ConnectionLineComponent
connectionMode?: ConnectionMode
arrowHeadColor: string
markerEndId?: string
onlyRenderVisibleElements: boolean
edgeUpdaterRadius?: number
}
export default defineComponent({
name: 'EdgeRenderer',
components: {
Edge,
ConnectionLine,
MarkerDefinitions,
},
props: {
edgeTypes: {
type: Object,
required: true,
},
connectionLineType: {
type: String as PropType<EdgeRendererProps['connectionLineType']>,
required: false,
default: undefined,
},
connectionLineStyle: {
type: Object as PropType<EdgeRendererProps['connectionLineStyle']>,
required: false,
default: undefined,
},
connectionLineComponent: {
type: Object as PropType<EdgeRendererProps['connectionLineComponent']>,
required: false,
default: undefined,
},
connectionMode: {
type: String as PropType<EdgeRendererProps['connectionMode']>,
required: false,
default: undefined,
},
arrowHeadColor: {
type: String as PropType<EdgeRendererProps['arrowHeadColor']>,
required: false,
default: undefined,
},
markerEndId: {
type: String as PropType<EdgeRendererProps['markerEndId']>,
required: false,
default: undefined,
},
onlyRenderVisibleElements: {
type: Boolean as PropType<EdgeRendererProps['onlyRenderVisibleElements']>,
required: false,
default: undefined,
},
edgeUpdaterRadius: {
type: Number as PropType<EdgeRendererProps['edgeUpdaterRadius']>,
required: false,
default: undefined,
},
},
setup(props) {
const store = inject<RevueFlowStore>('store')!
return () => (
<svg width={store.width} height={store.height} className="revue-flow__edges">
<MarkerDefinitions color={props.arrowHeadColor} />
<g transform={`translate(${store.transform[0]},${store.transform[1]}) scale(${store.transform[2]})`}>
{store.edges.map((edge, i) => (
<Edge
edge={edge}
type={props.edgeTypes[edge.type || 'default']}
onlyRenderVisibleElements={props.onlyRenderVisibleElements}
markerEndId={props.markerEndId}
key={edge.id + i}
/>
))}
{store.connectionNodeId && store.connectionHandleType && (
<ConnectionLine
connectionLineStyle={props.connectionLineStyle}
connectionLineType={props.connectionLineType}
customConnectionLine={props.connectionLineComponent}
/>
)}
</g>
</svg>
)
},
})
@@ -2,10 +2,10 @@
import '~/style.css'
import '~/theme-default.css'
import { CSSProperties, onBeforeUnmount } 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 ZoomPane from '~/container/ZoomPane/ZoomPane.vue'
import SelectionPane from '~/container/SelectionPane/SelectionPane.vue'
import NodeRenderer from '~/container/NodeRenderer/NodeRenderer.vue'
import EdgeRenderer from '~/container/EdgeRenderer/EdgeRenderer.vue'
import {
ConnectionLineType,
ConnectionMode,
@@ -22,10 +22,10 @@ import {
import { RevueFlowHooks, useRevueFlow } from '~/hooks/RevueFlowHooks'
import configureStore from '~/store/configure-store'
import { initialState } from '~/store'
import { createNodeTypes } from '~/container/NodeRenderer/utils'
import { DefaultNode, InputNode, OutputNode } from '~/components/Nodes'
import { BezierEdge, SmoothStepEdge, StepEdge, StraightEdge } from '~/components/Edges'
import { createEdgeTypes } from '~/container/EdgeRenderer/utils'
import { createNodeTypes } from '~/container/NodeRenderer/utils'
interface RevueFlowProps {
elements: Elements
-150
View File
@@ -1,150 +0,0 @@
import { computed, defineComponent, inject, PropType } from 'vue'
import ZoomPane from '../ZoomPane'
import UserSelection from '../../components/UserSelection'
import NodesSelection from '../../components/NodesSelection'
import { RevueFlowStore, GraphViewProps } from '../../types'
import useGlobalKeyHandler from '../../hooks/useGlobalKeyHandler'
import useKeyPress from '../../hooks/useKeyPress'
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'
type FlowRendererProps = Omit<
GraphViewProps,
| 'elements'
| 'snapToGrid'
| 'nodeTypes'
| 'edgeTypes'
| 'snapGrid'
| 'connectionLineType'
| 'arrowHeadColor'
| 'onlyRenderVisibleElements'
| 'selectNodesOnDrag'
>
export default defineComponent({
name: 'FlowRenderer',
components: { UserSelection, ZoomPane, NodesSelection },
props: {
deleteKeyCode: {
type: [Number, String] as PropType<FlowRendererProps['deleteKeyCode']>,
required: false,
default: undefined
},
selectionKeyCode: {
type: [Number, String] as PropType<FlowRendererProps['selectionKeyCode']>,
required: false,
default: undefined
},
multiSelectionKeyCode: {
type: [Number, String] as PropType<FlowRendererProps['multiSelectionKeyCode']>,
required: false,
default: undefined
},
zoomActivationKeyCode: {
type: [Number, String] as PropType<FlowRendererProps['zoomActivationKeyCode']>,
required: false,
default: undefined
},
elementsSelectable: {
type: Boolean as PropType<FlowRendererProps['elementsSelectable']>,
required: false,
default: false
},
zoomOnScroll: {
type: Boolean as PropType<FlowRendererProps['zoomOnScroll']>,
required: false,
default: false
},
zoomOnPinch: {
type: Boolean as PropType<FlowRendererProps['zoomOnPinch']>,
required: false,
default: false
},
panOnScroll: {
type: Boolean as PropType<FlowRendererProps['panOnScroll']>,
required: false,
default: false
},
panOnScrollSpeed: {
type: Number as PropType<FlowRendererProps['panOnScrollSpeed']>,
required: false,
default: undefined
},
panOnScrollMode: {
type: String as PropType<FlowRendererProps['panOnScrollMode']>,
required: false,
default: undefined
},
zoomOnDoubleClick: {
type: Boolean as PropType<FlowRendererProps['zoomOnDoubleClick']>,
required: false,
default: false
},
paneMoveable: {
type: Boolean as PropType<FlowRendererProps['paneMoveable']>,
required: false,
default: false
},
defaultPosition: {
type: Array as unknown as PropType<FlowRendererProps['defaultPosition']>,
required: false,
default: undefined
},
defaultZoom: {
type: Number as PropType<FlowRendererProps['defaultZoom']>,
required: false,
default: undefined
},
translateExtent: {
type: Array as unknown as PropType<FlowRendererProps['translateExtent']>,
required: false,
default: undefined
}
},
setup(props, { slots }) {
const store = inject<RevueFlowStore>('store')!
const hooks = inject<RevueFlowHooks>('hooks')!
const keyPressed = useKeyPress(props.selectionKeyCode)
useGlobalKeyHandler({
store,
onElementsRemove: hooks.elementsRemove.trigger,
deleteKeyCode: props.deleteKeyCode || '',
multiSelectionKeyCode: props.multiSelectionKeyCode || ''
})
const onClick = (event: MouseEvent) => {
hooks.paneClick.trigger(event)
store.unsetNodesSelection()
store.resetSelectedElements()
}
const onContextMenu = (event: MouseEvent) => hooks.paneContextMenu.trigger(event)
const onWheel = (event: WheelEvent) => hooks.paneScroll.trigger(event)
const selectionActive = computed(() => store.nodesSelectionActive)
return () => (
<ZoomPane
elementsSelectable={props.elementsSelectable}
zoomOnScroll={props.zoomOnScroll}
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}
selectionKeyCode={props.selectionKeyCode}
>
{slots.default ? slots.default() : ''}
{keyPressed.value ? <UserSelection /> : ''}
{selectionActive.value ? <NodesSelection /> : ''}
<div class="revue-flow__pane" onClick={onClick} onContextmenu={onContextMenu} onWheel={onWheel} />
</ZoomPane>
)
}
})
-280
View File
@@ -1,280 +0,0 @@
import { defineComponent, inject, PropType } from 'vue'
import FlowRenderer from '../FlowRenderer'
import NodeRenderer from '../NodeRenderer'
import EdgeRenderer from '../EdgeRenderer'
import { onLoadProject, onLoadGetElements, onLoadToObject } from '../../utils/graph'
import { GraphViewProps, RevueFlowStore } from '../../types'
import useZoomPanHelper from '../../hooks/useZoomPanHelper'
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'
import { tryOnMounted } from '@vueuse/core'
export default defineComponent({
name: 'GraphView',
components: { NodeRenderer, EdgeRenderer, FlowRenderer },
props: {
nodeTypes: {
type: Object as PropType<GraphViewProps['nodeTypes']>,
required: true
},
edgeTypes: {
type: Object as PropType<GraphViewProps['edgeTypes']>,
required: true
},
connectionMode: {
type: String as PropType<GraphViewProps['connectionMode']>,
required: false,
default: undefined
},
connectionLineType: {
type: String as PropType<GraphViewProps['connectionLineType']>,
required: false,
default: undefined
},
connectionLineStyle: {
type: Object as PropType<GraphViewProps['connectionLineStyle']>,
required: false,
default: () => {}
},
connectionLineComponent: {
type: Object as PropType<GraphViewProps['connectionLineComponent']>,
required: false,
default: undefined
},
selectionKeyCode: {
type: [Number, String] as PropType<GraphViewProps['selectionKeyCode']>,
required: false,
default: undefined
},
multiSelectionKeyCode: {
type: [Number, String] as PropType<GraphViewProps['multiSelectionKeyCode']>,
required: false,
default: undefined
},
zoomActivationKeyCode: {
type: [Number, String] as PropType<GraphViewProps['zoomActivationKeyCode']>,
required: false,
default: undefined
},
deleteKeyCode: {
type: [Number, String] as PropType<GraphViewProps['deleteKeyCode']>,
required: false,
default: undefined
},
snapToGrid: {
type: Boolean as PropType<GraphViewProps['snapToGrid']>,
required: false,
default: undefined
},
snapGrid: {
type: Array as unknown as PropType<GraphViewProps['snapGrid']>,
required: false,
default: undefined
},
onlyRenderVisibleElements: {
type: Boolean as PropType<GraphViewProps['onlyRenderVisibleElements']>,
required: false,
default: undefined
},
nodesDraggable: {
type: Boolean as PropType<GraphViewProps['nodesDraggable']>,
required: false,
default: undefined
},
nodesConnectable: {
type: Boolean as PropType<GraphViewProps['nodesConnectable']>,
required: false,
default: undefined
},
elementsSelectable: {
type: Boolean as PropType<GraphViewProps['elementsSelectable']>,
required: false,
default: undefined
},
selectNodesOnDrag: {
type: Boolean as PropType<GraphViewProps['selectNodesOnDrag']>,
required: false,
default: undefined
},
minZoom: {
type: Number as PropType<GraphViewProps['minZoom']>,
required: false,
default: undefined
},
maxZoom: {
type: Number as PropType<GraphViewProps['maxZoom']>,
required: false,
default: undefined
},
defaultZoom: {
type: Number as PropType<GraphViewProps['defaultZoom']>,
required: false,
default: undefined
},
defaultPosition: {
type: Array as unknown as PropType<GraphViewProps['defaultPosition']>,
required: false,
default: undefined
},
translateExtent: {
type: Array as unknown as PropType<GraphViewProps['translateExtent']>,
required: false,
default: undefined
},
nodeExtent: {
type: Array as unknown as PropType<GraphViewProps['nodeExtent']>,
required: false,
default: undefined
},
arrowHeadColor: {
type: String as PropType<GraphViewProps['arrowHeadColor']>,
required: false,
default: undefined
},
markerEndId: {
type: String as PropType<GraphViewProps['markerEndId']>,
required: false,
default: undefined
},
zoomOnScroll: {
type: Boolean as PropType<GraphViewProps['zoomOnScroll']>,
required: false,
default: undefined
},
zoomOnPinch: {
type: Boolean as PropType<GraphViewProps['zoomOnPinch']>,
required: false,
default: undefined
},
panOnScroll: {
type: Boolean as PropType<GraphViewProps['panOnScroll']>,
required: false,
default: undefined
},
panOnScrollSpeed: {
type: Number as PropType<GraphViewProps['panOnScrollSpeed']>,
required: false,
default: undefined
},
panOnScrollMode: {
type: String as PropType<GraphViewProps['panOnScrollMode']>,
required: false,
default: undefined
},
zoomOnDoubleClick: {
type: Boolean as PropType<GraphViewProps['zoomOnDoubleClick']>,
required: false,
default: undefined
},
paneMoveable: {
type: Boolean as PropType<GraphViewProps['paneMoveable']>,
required: false,
default: undefined
},
edgeUpdaterRadius: {
type: Number as PropType<GraphViewProps['edgeUpdaterRadius']>,
required: false,
default: undefined
}
},
setup(props) {
const store = inject<RevueFlowStore>('store')!
const hooks = inject<RevueFlowHooks>('hooks')!
const { onReady } = useZoomPanHelper()
onReady(({ zoomIn, zoomOut, zoomTo, transform, fitView }) => {
hooks.load.trigger({
fitView: (params = { padding: 0.1 }) => fitView(params),
zoomIn,
zoomOut,
zoomTo,
setTransform: transform,
project: onLoadProject(store),
getElements: onLoadGetElements(store),
toObject: onLoadToObject(store)
})
})
const init = () => {
if (typeof props.snapToGrid !== 'undefined') {
store.snapToGrid = props.snapToGrid
}
if (typeof props.snapGrid !== 'undefined') {
store.snapGrid = props.snapGrid
}
if (typeof props.minZoom !== 'undefined') {
store.setMinZoom(props.minZoom)
}
if (typeof props.maxZoom !== 'undefined') {
store.setMaxZoom(props.maxZoom)
}
if (typeof props.translateExtent !== 'undefined') {
store.setTranslateExtent(props.translateExtent)
}
if (typeof props.nodeExtent !== 'undefined') {
store.setNodeExtent(props.nodeExtent)
}
if (typeof props.nodesConnectable !== 'undefined') {
store.nodesConnectable = !!props.nodesConnectable
}
if (typeof props.elementsSelectable !== 'undefined') {
store.elementsSelectable = !!props.elementsSelectable
}
if (typeof props.nodesDraggable !== 'undefined') {
store.nodesDraggable = !!props.nodesDraggable
}
if (typeof props.connectionMode !== 'undefined') {
store.connectionMode = props.connectionMode
}
}
tryOnMounted(() => {
init()
})
return () => (
<FlowRenderer
deleteKeyCode={props.deleteKeyCode}
selectionKeyCode={props.selectionKeyCode}
multiSelectionKeyCode={props.multiSelectionKeyCode}
zoomActivationKeyCode={props.zoomActivationKeyCode}
elementsSelectable={props.elementsSelectable}
zoomOnScroll={props.zoomOnScroll}
zoomOnPinch={props.zoomOnPinch}
zoomOnDoubleClick={props.zoomOnDoubleClick}
panOnScroll={props.panOnScroll}
panOnScrollSpeed={props.panOnScrollSpeed}
panOnScrollMode={props.panOnScrollMode}
paneMoveable={props.paneMoveable}
defaultPosition={props.defaultPosition}
defaultZoom={props.defaultZoom}
translateExtent={props.translateExtent}
>
<NodeRenderer
nodeTypes={props.nodeTypes}
selectNodesOnDrag={props.selectNodesOnDrag}
snapToGrid={props.snapToGrid}
snapGrid={props.snapGrid}
onlyRenderVisibleElements={props.onlyRenderVisibleElements}
/>
<EdgeRenderer
edgeTypes={props.edgeTypes}
connectionLineType={props.connectionLineType}
connectionLineStyle={props.connectionLineStyle}
connectionLineComponent={props.connectionLineComponent}
connectionMode={props.connectionMode}
arrowHeadColor={props.arrowHeadColor}
markerEndId={props.markerEndId}
onlyRenderVisibleElements={props.onlyRenderVisibleElements}
edgeUpdaterRadius={props.edgeUpdaterRadius}
/>
</FlowRenderer>
)
}
})
-94
View File
@@ -1,94 +0,0 @@
import { defineComponent, inject, PropType } from 'vue'
import Node from '../../components/Nodes/NodeDepr'
import { getNodesInside } from '../../utils/graph'
import { NodeType, RevueFlowStore } from '../../types'
interface NodeRendererProps {
nodeTypes: Record<string, NodeType>
selectNodesOnDrag: boolean
snapToGrid: boolean
snapGrid: [number, number]
onlyRenderVisibleElements: boolean
}
export default defineComponent({
name: 'NodeRenderer',
components: { Node },
props: {
nodeTypes: {
type: Object as PropType<NodeRendererProps['nodeTypes']>,
required: false,
default: undefined,
},
selectNodesOnDrag: {
type: Boolean as PropType<NodeRendererProps['selectNodesOnDrag']>,
required: false,
default: undefined,
},
snapToGrid: {
type: Boolean as PropType<NodeRendererProps['snapToGrid']>,
required: false,
default: undefined,
},
snapGrid: {
type: Array as unknown as PropType<NodeRendererProps['snapGrid']>,
required: false,
default: undefined,
},
onlyRenderVisibleElements: {
type: Boolean as PropType<NodeRendererProps['onlyRenderVisibleElements']>,
required: false,
default: undefined,
},
},
setup(props) {
const store = inject<RevueFlowStore>('store')!
const getNodes = () => {
return props.onlyRenderVisibleElements
? store.nodes &&
getNodesInside(
store.nodes,
{
x: 0,
y: 0,
width: store?.width,
height: store?.height,
},
store.transform,
true,
)
: store.nodes
}
return () => (
<div
className="revue-flow__nodes"
style={{
transform: `translate(${store.transform[0]}px,${store.transform[1]}px) scale(${store.transform[2]})`,
}}
>
{getNodes().map((node, i) => {
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}
key={node.id + i}
/>
)
}
})}
</div>
)
},
})
-283
View File
@@ -1,283 +0,0 @@
import { PropType } from 'vue'
import '../../style.css'
import '../../theme-default.css'
import GraphView from '../GraphView'
import DefaultNode from '../../components/Nodes/DefaultNodeDepr'
import InputNode from '../../components/Nodes/InputNodeDepr'
import OutputNode from '../../components/Nodes/OutputNodeDepr'
import { createNodeTypes } from '../NodeRenderer/utils'
import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges'
import { createEdgeTypes } from '../EdgeRenderer/utils'
import { ConnectionMode, ConnectionLineType, PanOnScrollMode, RevueFlowStore, RevueFlowProps } from '../../types'
import { initialState } from '../../store'
import configureStore from '../../store/configure-store'
import { RevueFlowHooks, useRevueFlow } from '../../hooks/RevueFlowHooks'
const defaultNodeTypes = {
input: InputNode,
default: DefaultNode,
output: OutputNode,
}
const defaultEdgeTypes = {
default: BezierEdge,
straight: StraightEdge,
step: StepEdge,
smoothstep: SmoothStepEdge,
}
export default defineComponent({
name: 'RevueFlow',
components: { GraphView },
props: {
modelValue: {
type: Array as PropType<RevueFlowProps['modelValue']>,
required: false,
default: () => [],
},
nodeTypesId: {
type: String as PropType<RevueFlowProps['nodeTypesId']>,
required: false,
default: '1',
},
edgeTypesId: {
type: String as PropType<RevueFlowProps['edgeTypesId']>,
required: false,
default: '1',
},
nodeTypes: {
type: Object as PropType<RevueFlowProps['nodeTypes']>,
required: false,
default: () => defaultNodeTypes,
},
edgeTypes: {
type: Object as PropType<RevueFlowProps['edgeTypes']>,
required: false,
default: () => defaultEdgeTypes,
},
connectionMode: {
type: String as PropType<RevueFlowProps['connectionMode']>,
required: false,
default: ConnectionMode.Strict,
},
connectionLineType: {
type: String as PropType<RevueFlowProps['connectionLineType']>,
required: false,
default: ConnectionLineType.Bezier,
},
connectionLineStyle: {
type: Object as PropType<RevueFlowProps['connectionLineStyle']>,
required: false,
default: undefined,
},
connectionLineComponent: {
type: Object as PropType<RevueFlowProps['connectionLineComponent']>,
required: false,
default: undefined,
},
selectionKeyCode: {
type: [Number, String] as PropType<RevueFlowProps['selectionKeyCode']>,
required: false,
default: 'Shift',
},
multiSelectionKeyCode: {
type: [Number, String] as PropType<RevueFlowProps['multiSelectionKeyCode']>,
required: false,
default: 'Meta',
},
zoomActivationKeyCode: {
type: [Number, String] as PropType<RevueFlowProps['zoomActivationKeyCode']>,
required: false,
default: 'Meta',
},
deleteKeyCode: {
type: [Number, String] as PropType<RevueFlowProps['deleteKeyCode']>,
required: false,
default: 'Backspace',
},
snapToGrid: {
type: Boolean as PropType<RevueFlowProps['snapToGrid']>,
required: false,
default: false,
},
snapGrid: {
type: Array as unknown as PropType<RevueFlowProps['snapGrid']>,
required: false,
default: () => [15, 15],
},
onlyRenderVisibleElements: {
type: Boolean as PropType<RevueFlowProps['onlyRenderVisibleElements']>,
required: false,
default: false,
},
nodesDraggable: {
type: Boolean as PropType<RevueFlowProps['nodesDraggable']>,
required: false,
default: undefined,
},
nodesConnectable: {
type: Boolean as PropType<RevueFlowProps['nodesConnectable']>,
required: false,
default: true,
},
elementsSelectable: {
type: Boolean as PropType<RevueFlowProps['elementsSelectable']>,
required: false,
default: true,
},
selectNodesOnDrag: {
type: Boolean as PropType<RevueFlowProps['selectNodesOnDrag']>,
required: false,
default: true,
},
minZoom: {
type: Number as PropType<RevueFlowProps['minZoom']>,
required: false,
default: 0.5,
},
maxZoom: {
type: Number as PropType<RevueFlowProps['maxZoom']>,
required: false,
default: 2,
},
defaultZoom: {
type: Number as PropType<RevueFlowProps['defaultZoom']>,
required: false,
default: 1,
},
defaultPosition: {
type: Array as unknown as PropType<RevueFlowProps['defaultPosition']>,
required: false,
default: () => [0, 0],
},
translateExtent: {
type: Array as unknown as PropType<RevueFlowProps['translateExtent']>,
required: false,
default: () => [
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
],
},
nodeExtent: {
type: Array as unknown as PropType<RevueFlowProps['nodeExtent']>,
required: false,
default: () => [
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY],
],
},
arrowHeadColor: {
type: String as PropType<RevueFlowProps['arrowHeadColor']>,
required: false,
default: '#b1b1b7',
},
markerEndId: {
type: String as PropType<RevueFlowProps['markerEndId']>,
required: false,
default: undefined,
},
zoomOnScroll: {
type: Boolean as PropType<RevueFlowProps['zoomOnScroll']>,
required: false,
default: true,
},
zoomOnPinch: {
type: Boolean as PropType<RevueFlowProps['zoomOnPinch']>,
required: false,
default: true,
},
panOnScroll: {
type: Boolean as PropType<RevueFlowProps['panOnScroll']>,
required: false,
default: false,
},
panOnScrollSpeed: {
type: Number as PropType<RevueFlowProps['panOnScrollSpeed']>,
required: false,
default: 0.5,
},
panOnScrollMode: {
type: String as PropType<RevueFlowProps['panOnScrollMode']>,
required: false,
default: PanOnScrollMode.Free,
},
zoomOnDoubleClick: {
type: Boolean as PropType<RevueFlowProps['zoomOnDoubleClick']>,
required: false,
default: true,
},
paneMoveable: {
type: Boolean as PropType<RevueFlowProps['paneMoveable']>,
required: false,
default: true,
},
edgeUpdaterRadius: {
type: Number as PropType<RevueFlowProps['edgeUpdaterRadius']>,
required: false,
default: 10,
},
},
emits: Object.keys(useRevueFlow()),
setup(props, { emit, slots }) {
const store = configureStore(initialState)()
provide<RevueFlowStore>('store', store)
const hooks = useRevueFlow().bind(emit)
provide<RevueFlowHooks>('hooks', hooks)
const elements = useVModel(props, 'modelValue', emit)
store.setElements(elements.value)
watch(elements.value, () => {
store.setElements(elements.value)
})
onUpdated(() => {
store.setElements(elements.value)
})
onBeforeUnmount(() => {
store.$reset()
store.setElements([])
})
return () => (
<div className="revue-flow">
<GraphView
nodeTypes={createNodeTypes({ ...defaultNodeTypes, ...props.nodeTypes })}
edgeTypes={createEdgeTypes({ ...defaultEdgeTypes, ...props.edgeTypes })}
connectionMode={props.connectionMode}
connectionLineType={props.connectionLineType}
connectionLineStyle={props.connectionLineStyle}
connectionLineComponent={props.connectionLineComponent}
selectionKeyCode={props.selectionKeyCode}
deleteKeyCode={props.deleteKeyCode}
multiSelectionKeyCode={props.multiSelectionKeyCode}
zoomActivationKeyCode={props.zoomActivationKeyCode}
snapToGrid={props.snapToGrid}
snapGrid={props.snapGrid}
onlyRenderVisibleElements={props.onlyRenderVisibleElements}
nodesDraggable={props.nodesDraggable}
nodesConnectable={props.nodesConnectable}
elementsSelectable={props.elementsSelectable}
selectNodesOnDrag={props.selectNodesOnDrag}
minZoom={props.minZoom}
maxZoom={props.maxZoom}
defaultZoom={props.defaultZoom}
defaultPosition={props.defaultPosition}
translateExtent={props.translateExtent}
nodeExtent={props.nodeExtent}
arrowHeadColor={props.arrowHeadColor}
markerEndId={props.markerEndId}
zoomOnScroll={props.zoomOnScroll}
zoomOnPinch={props.zoomOnPinch}
zoomOnDoubleClick={props.zoomOnDoubleClick}
panOnScroll={props.panOnScroll}
panOnScrollSpeed={props.panOnScrollSpeed}
panOnScrollMode={props.panOnScrollMode}
paneMoveable={props.paneMoveable}
edgeUpdaterRadius={props.edgeUpdaterRadius}
/>
{slots.default ? slots.default() : ''}
</div>
)
},
})
-277
View File
@@ -1,277 +0,0 @@
import { D3ZoomEvent, zoom, zoomIdentity, ZoomTransform } from 'd3-zoom'
import { select, pointer } from 'd3-selection'
import { clamp } from '../../utils'
import { FlowTransform, TranslateExtent, PanOnScrollMode, KeyCode, RevueFlowStore } from '../../types'
import { defineComponent, inject, PropType, ref, watchPostEffect } from 'vue'
import useKeyPress from '../../hooks/useKeyPress'
import useResizeHandler from '../../hooks/useResizeHandler'
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'
import { templateRef } from '@vueuse/core'
import { ZoomBehavior } from 'd3'
interface ZoomPaneProps {
elementsSelectable?: boolean
zoomOnScroll?: boolean
zoomOnPinch?: boolean
panOnScroll?: boolean
panOnScrollSpeed?: number
panOnScrollMode?: PanOnScrollMode
zoomOnDoubleClick?: boolean
paneMoveable?: boolean
defaultPosition?: [number, number]
defaultZoom?: number
translateExtent?: TranslateExtent
zoomActivationKeyCode?: KeyCode
selectionKeyCode?: KeyCode
}
export default defineComponent({
name: 'ZoomPane',
props: {
zoomOnScroll: {
type: Boolean as PropType<ZoomPaneProps['zoomOnScroll']>,
required: false,
default: true
},
zoomOnPinch: {
type: Boolean as PropType<ZoomPaneProps['zoomOnPinch']>,
required: false,
default: true
},
panOnScroll: {
type: Boolean as PropType<ZoomPaneProps['panOnScroll']>,
required: false,
default: false
},
panOnScrollSpeed: {
type: Number as PropType<ZoomPaneProps['panOnScrollSpeed']>,
required: false,
default: 0.5
},
panOnScrollMode: {
type: String as PropType<ZoomPaneProps['panOnScrollMode']>,
required: false,
default: PanOnScrollMode.Free
},
zoomOnDoubleClick: {
type: Boolean as PropType<ZoomPaneProps['zoomOnDoubleClick']>,
required: false,
default: true
},
selectionKeyCode: {
type: [Number, String] as PropType<ZoomPaneProps['selectionKeyCode']>,
required: false,
default: undefined
},
elementsSelectable: {
type: Boolean as PropType<ZoomPaneProps['elementsSelectable']>,
required: false,
default: true
},
paneMoveable: {
type: Boolean as PropType<ZoomPaneProps['paneMoveable']>,
required: false,
default: true
},
defaultPosition: {
type: Array as unknown as PropType<ZoomPaneProps['defaultPosition']>,
required: false,
default: () => [0, 0]
},
defaultZoom: {
type: Number as PropType<ZoomPaneProps['defaultZoom']>,
required: false,
default: 1
},
translateExtent: {
type: Array as unknown as PropType<ZoomPaneProps['translateExtent']>,
required: false,
default: undefined
},
zoomActivationKeyCode: {
type: [Number, String] as PropType<ZoomPaneProps['zoomActivationKeyCode']>,
required: false,
default: undefined
}
},
setup(props, { slots }) {
const store = inject<RevueFlowStore>('store')!
const hooks = inject<RevueFlowHooks>('hooks')!
const zoomPane = templateRef<HTMLDivElement>('zoom-pane', null)
const prevTransform = ref<FlowTransform>({ x: 0, y: 0, zoom: 0 })
const viewChanged = (prevTransform: FlowTransform, eventTransform: ZoomTransform): boolean =>
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
})
const init = () => {
const { onReady } = useResizeHandler(zoomPane, store.updateSize)
onReady(() => {
if (props.defaultPosition && props.defaultZoom) {
const currentTranslateExtent =
typeof props.translateExtent !== 'undefined' ? props.translateExtent : store.translateExtent
const d3ZoomInstance = zoom().scaleExtent([store.minZoom, store.maxZoom]).translateExtent(currentTranslateExtent)
const selection = select(zoomPane.value as Element).call(d3ZoomInstance)
const clampedX = clamp(props.defaultPosition[0], currentTranslateExtent[0][0], currentTranslateExtent[1][0])
const clampedY = clamp(props.defaultPosition[1], currentTranslateExtent[0][1], currentTranslateExtent[1][1])
const clampedZoom = clamp(props.defaultZoom, store.minZoom, store.maxZoom)
const updatedTransform = zoomIdentity.translate(clampedX, clampedY).scale(clampedZoom)
d3ZoomInstance.transform(selection, updatedTransform)
store.initD3Zoom({
d3Zoom: d3ZoomInstance,
d3Selection: selection,
d3ZoomHandler: selection.on('wheel.zoom'),
// we need to pass transform because zoom handler is not registered when we set the initial transform
transform: [clampedX, clampedY, clampedZoom]
})
applyZoomHandlers(d3ZoomInstance)
applyZoomFilter(d3ZoomInstance)
}
})
const applyZoomHandlers = (d3Zoom: ZoomBehavior<Element, unknown>) => {
d3Zoom.on('start', (event: D3ZoomEvent<HTMLDivElement, any>) => {
if (viewChanged(prevTransform.value, event.transform)) {
const flowTransform = eventToFlowTransform(event.transform)
prevTransform.value = flowTransform
hooks.moveStart.trigger(flowTransform)
}
})
d3Zoom.on('end', (event: D3ZoomEvent<HTMLDivElement, any>) => {
if (viewChanged(prevTransform.value, event.transform)) {
const flowTransform = eventToFlowTransform(event.transform)
prevTransform.value = flowTransform
hooks.moveEnd.trigger(flowTransform)
}
})
useKeyPress(props.selectionKeyCode, (keyPress) => {
if (keyPress) {
d3Zoom.on('zoom', null)
} else {
d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
store.transform = [event.transform.x, event.transform.y, event.transform.k]
hooks.move.trigger(eventToFlowTransform(event.transform))
})
}
})
useKeyPress(props.zoomActivationKeyCode, (keyPress) => {
if (props.panOnScroll && keyPress) {
store.d3Selection
?.on('wheel', (event: WheelEvent) => {
event.preventDefault()
event.stopImmediatePropagation()
const currentZoom = store.d3Selection?.property('__zoom').k || 1
if (event.ctrlKey && props.zoomOnPinch) {
const point = pointer(event)
// 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 zoom = currentZoom * Math.pow(2, pinchDelta)
if (store.d3Selection) store.d3Zoom?.scaleTo(store.d3Selection, zoom, point)
return
}
// increase scroll speed in firefox
// firefox: deltaMode === 1; chrome: deltaMode === 0
const deltaNormalize = event.deltaMode === 1 ? 20 : 1
const deltaX = props.panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize
const deltaY = props.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize
if (store.d3Selection && props.panOnScrollSpeed)
store.d3Zoom?.translateBy(
store.d3Selection,
-(deltaX / currentZoom) * props.panOnScrollSpeed,
-(deltaY / currentZoom) * props.panOnScrollSpeed
)
})
.on('wheel.zoom', null)
} else if (typeof store.d3ZoomHandler !== 'undefined') {
store.d3Selection?.on('wheel', null).on('wheel.zoom', store.d3ZoomHandler)
}
})
}
watchPostEffect(() => store.d3Zoom && applyZoomFilter(store.d3Zoom))
const applyZoomFilter = (d3Zoom: ZoomBehavior<Element, unknown>) => {
const keyPress = useKeyPress(props.selectionKeyCode)
d3Zoom.filter((event: MouseEvent) => {
const zoomScroll = props.zoomOnScroll
const pinchZoom = props.zoomOnPinch && event.ctrlKey
// if all interactions are disabled, we prevent all zoom events
if (!props.paneMoveable && !zoomScroll && !props.panOnScroll && !props.zoomOnDoubleClick && !props.zoomOnPinch) {
return false
}
// during a selection we prevent all other interactions
if (keyPress.value) {
return false
}
// if zoom on double click is disabled, we prevent the double click event
if (!props.zoomOnDoubleClick && event.type === 'dblclick') {
return false
}
if ((event.target as Element).closest('.nowheel') && event.type === 'wheel') {
return false
}
// when the target element is a node, we still allow zooming
if (
((event.target as Element).closest('.revue-flow__node') || (event.target as Element).closest('.revue-flow__edge')) &&
event.type !== 'wheel'
) {
return false
}
// when the target element is a node selection, we still allow zooming
if ((event.target as Element).closest('.revue-flow__nodesselection') && event.type !== 'wheel') {
return false
}
if (!props.zoomOnPinch && event.ctrlKey && event.type === 'wheel') {
return false
}
// when there is no scroll handling enabled, we prevent all wheel events
if (!zoomScroll && !props.panOnScroll && !pinchZoom && event.type === 'wheel') {
return false
}
// if the pane is not movable, we prevent dragging it with mousestart or touchstart
if (!props.paneMoveable && (event.type === 'mousedown' || event.type === 'touchstart')) {
return false
}
// default filter for d3-zoom
return (!event.ctrlKey || event.type === 'wheel') && !event.button
})
}
}
init()
return () => (
<div ref="zoom-pane" class="revue-flow__renderer revue-flow__zoompane">
{slots.default ? slots.default() : ''}
</div>
)
}
})
+7 -7
View File
@@ -1,7 +1,7 @@
export { default as Flow } from './Flow.vue'
export { default as EdgeRenderer } from './EdgeRenderer.vue'
export { default as NodeRenderer } from './NodeRenderer.vue'
export { default as Marker } from './Marker.vue'
export { default as MarkerDefinitions } from './MarkerDefinitions.vue'
export { default as ZoomPane } from './ZoomPane.vue'
export { default as SelectionPane } from './SelectionPane.vue'
export { default as Flow } from './Flow/Flow.vue'
export { default as EdgeRenderer } from './EdgeRenderer/EdgeRenderer.vue'
export { default as Marker } from './EdgeRenderer/Marker.vue'
export { default as MarkerDefinitions } from './EdgeRenderer/MarkerDefinitions.vue'
export { default as NodeRenderer } from './NodeRenderer/NodeRenderer.vue'
export { default as ZoomPane } from './ZoomPane/ZoomPane.vue'
export { default as SelectionPane } from './SelectionPane/SelectionPane.vue'
+1 -1
View File
@@ -1,4 +1,4 @@
export { default } from './container/Flow.vue'
export { default } from './container/Flow/Flow.vue'
export { default as Handle } from './components/Handle/Handle.vue'
export { default as EdgeText } from './components/Edges/EdgeText.vue'
export { getBezierPath, getSmoothStepPath, getMarkerEnd, getCenter as getEdgeCenter } from './components/Edges/utils'