update(script-setup): Refactor additional-components
* Remove jsx files fix: ConnectionLine.vue not recalculating properly
This commit is contained in:
59
src/additional-components/Background/Background.vue
Normal file
59
src/additional-components/Background/Background.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<script lang="ts" setup>
|
||||
import { HTMLAttributes } from 'vue'
|
||||
import { BackgroundVariant, RevueFlowStore } from '~/types'
|
||||
|
||||
export interface BackgroundProps extends HTMLAttributes {
|
||||
variant?: BackgroundVariant
|
||||
gap?: number
|
||||
color?: string
|
||||
size?: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<BackgroundProps>(), {
|
||||
variant: BackgroundVariant.Dots,
|
||||
gap: 10,
|
||||
size: 0.4,
|
||||
})
|
||||
|
||||
const defaultColors: Record<BackgroundVariant, string> = {
|
||||
[BackgroundVariant.Dots]: '#81818a',
|
||||
[BackgroundVariant.Lines]: '#eee',
|
||||
}
|
||||
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
const transform = computed(() => store.transform)
|
||||
// when there are multiple flows on a page we need to make sure that every background gets its own pattern.
|
||||
const patternId = `pattern-${Math.floor(Math.random() * 100000)}`
|
||||
|
||||
const bgClasses = ['revue-flow__background']
|
||||
const scaledGap = computed(() => props.gap && props.gap * transform.value[2])
|
||||
const xOffset = computed(() => scaledGap.value && transform.value[0] % scaledGap.value)
|
||||
const yOffset = computed(() => scaledGap.value && transform.value[1] % scaledGap.value)
|
||||
|
||||
const isLines = computed(() => props.variant === BackgroundVariant.Lines)
|
||||
const bgColor = computed(() => (props.color ? props.color : defaultColors[props.variant || BackgroundVariant.Dots]))
|
||||
const size = computed(() => props.size || 0.4 * transform.value[2])
|
||||
</script>
|
||||
<template>
|
||||
<svg
|
||||
:class="bgClasses"
|
||||
:style="{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}"
|
||||
>
|
||||
<pattern :id="patternId" :x="xOffset" :y="yOffset" :width="scaledGap" :height="scaledGap" patternUnits="userSpaceOnUse">
|
||||
<template v-if="isLines">
|
||||
<path
|
||||
:stroke="bgColor"
|
||||
:stroke-width="props.size"
|
||||
:d="`M${scaledGap / 2} 0 V${scaledGap} M0 ${scaledGap / 2} H${scaledGap}`"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<circle :cx="size" :cy="size" :r="size" :fill="bgColor" />
|
||||
</template>
|
||||
</pattern>
|
||||
<rect x="0" y="0" width="100%" height="100%" :fill="`url(#${patternId})`" />
|
||||
</svg>
|
||||
</template>
|
||||
@@ -1,84 +0,0 @@
|
||||
import { BackgroundVariant, RevueFlowStore } from '../../types'
|
||||
import { createGridDotsPath, createGridLinesPath } from './utils'
|
||||
import { computed, defineComponent, HTMLAttributes, inject, PropType } from 'vue'
|
||||
|
||||
export interface BackgroundProps extends HTMLAttributes {
|
||||
variant?: BackgroundVariant
|
||||
gap?: number
|
||||
color?: string
|
||||
size?: number
|
||||
}
|
||||
|
||||
const defaultColors = {
|
||||
[BackgroundVariant.Dots]: '#81818a',
|
||||
[BackgroundVariant.Lines]: '#eee'
|
||||
}
|
||||
|
||||
const Background = defineComponent({
|
||||
name: 'Background',
|
||||
props: {
|
||||
variant: {
|
||||
type: String as PropType<BackgroundProps['variant']>,
|
||||
required: false,
|
||||
default: BackgroundVariant.Dots
|
||||
},
|
||||
gap: {
|
||||
type: Number as PropType<BackgroundProps['gap']>,
|
||||
required: false,
|
||||
default: 10
|
||||
},
|
||||
color: {
|
||||
type: String as PropType<BackgroundProps['color']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
size: {
|
||||
type: Number as PropType<BackgroundProps['size']>,
|
||||
required: false,
|
||||
default: 0.4
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
const transform = computed(() => store.transform)
|
||||
// when there are multiple flows on a page we need to make sure that every background gets its own pattern.
|
||||
const patternId = `pattern-${Math.floor(Math.random() * 100000)}`
|
||||
|
||||
const bgClasses = ['revue-flow__background']
|
||||
const scaledGap = computed(() => props.gap && props.gap * transform.value[2])
|
||||
const xOffset = computed(() => scaledGap.value && transform.value[0] % scaledGap.value)
|
||||
const yOffset = computed(() => scaledGap.value && transform.value[1] % scaledGap.value)
|
||||
|
||||
const isLines = computed(() => props.variant === BackgroundVariant.Lines)
|
||||
const bgColor = computed(() => (props.color ? props.color : defaultColors[props.variant || BackgroundVariant.Dots]))
|
||||
const path = computed(() =>
|
||||
isLines.value
|
||||
? scaledGap.value && props.size && createGridLinesPath(scaledGap.value, props.size, bgColor.value)
|
||||
: createGridDotsPath(props.size || 0.4 * transform.value[2], bgColor.value)
|
||||
)
|
||||
|
||||
return () => (
|
||||
<svg
|
||||
class={bgClasses}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}}
|
||||
>
|
||||
<pattern
|
||||
id={patternId}
|
||||
x={xOffset.value}
|
||||
y={yOffset.value}
|
||||
width={scaledGap.value}
|
||||
height={scaledGap.value}
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
{path.value}
|
||||
</pattern>
|
||||
<rect x="0" y="0" width="100%" height="100%" fill={`url(#${patternId})`} />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default Background
|
||||
10
src/additional-components/Controls/ControlButton.vue
Normal file
10
src/additional-components/Controls/ControlButton.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
export type ControlButtonProps = HTMLButtonElement
|
||||
|
||||
const props = defineProps<ControlButtonProps>()
|
||||
</script>
|
||||
<template>
|
||||
<button :class="['revue-flow__controls-button']" v-bind="props">
|
||||
<slot></slot>
|
||||
</button>
|
||||
</template>
|
||||
76
src/additional-components/Controls/Controls.vue
Normal file
76
src/additional-components/Controls/Controls.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<script lang="ts" setup>
|
||||
import { HTMLAttributes } from 'vue'
|
||||
import PlusIcon from '../../../assets/icons/plus.svg'
|
||||
import MinusIcon from '../../../assets/icons/minus.svg'
|
||||
import Fitview from '../../../assets/icons/fitview.svg'
|
||||
import Lock from '../../../assets/icons/lock.svg'
|
||||
import Unlock from '../../../assets/icons/unlock.svg'
|
||||
import ControlButton from './ControlButton.vue'
|
||||
import { FitViewParams, RevueFlowStore, ZoomPanHelperFunctions } from '~/types'
|
||||
import useZoomPanHelper from '~/hooks/useZoomPanHelper'
|
||||
|
||||
export interface ControlProps extends HTMLAttributes {
|
||||
showZoom?: boolean
|
||||
showFitView?: boolean
|
||||
showInteractive?: boolean
|
||||
fitViewParams?: FitViewParams
|
||||
onZoomIn?: () => void
|
||||
onZoomOut?: () => void
|
||||
onFitView?: () => void
|
||||
onInteractiveChange?: (interactiveStatus: boolean) => void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<ControlProps>(), {
|
||||
showZoom: true,
|
||||
showFitView: true,
|
||||
showInteractive: true,
|
||||
})
|
||||
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
// const { onReady } = useZoomPanHelper()
|
||||
|
||||
const isInteractive = store.nodesDraggable && store.nodesConnectable && store.elementsSelectable
|
||||
const mapClasses = ['revue-flow__controls']
|
||||
|
||||
/*
|
||||
* const onZoomInHandler = () => {
|
||||
zoomHelper.value?.zoomIn?.()
|
||||
props.onZoomIn?.()
|
||||
}
|
||||
|
||||
const onZoomOutHandler = () => {
|
||||
zoomHelper.value?.zoomOut?.()
|
||||
props.onZoomOut?.()
|
||||
}
|
||||
|
||||
const onFitViewHandler = () => {
|
||||
zoomHelper.value?.fitView?.(props.fitViewParams)
|
||||
props.onFitView?.()
|
||||
}
|
||||
|
||||
const onInteractiveChangeHandler = () => {
|
||||
store.setInteractive?.(!isInteractive)
|
||||
props.onInteractiveChange?.(!isInteractive)
|
||||
}
|
||||
*/
|
||||
</script>
|
||||
<template>
|
||||
<div :class="mapClasses">
|
||||
<template v-if="props.showZoom">
|
||||
<ControlButton on-click="onZoomInHandler" class="revue-flow__controls-zoomin">
|
||||
<PlusIcon />
|
||||
</ControlButton>
|
||||
<ControlButton on-click="onZoomOutHandler" class="revue-flow__controls-zoomout">
|
||||
<MinusIcon />
|
||||
</ControlButton>
|
||||
</template>
|
||||
<ControlButton v-if="props.showFitView" class="revue-flow__controls-fitview" on-click="onFitViewHandler">
|
||||
<Fitview />
|
||||
</ControlButton>
|
||||
<ControlButton v-if="props.showInteractive" class="revue-flow__controls-interactive" on-click="onInteractiveChangeHandler">
|
||||
<Unlock v-if="isInteractive" />
|
||||
<Lock v-else />
|
||||
</ControlButton>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,154 +0,0 @@
|
||||
import { defineComponent, HTMLAttributes, inject, onMounted, PropType, ref } from 'vue'
|
||||
import useZoomPanHelper from '../../hooks/useZoomPanHelper'
|
||||
import { FitViewParams, RevueFlowStore, ZoomPanHelperFunctions } from '../../types'
|
||||
import PlusIcon from '../../../assets/icons/plus.svg'
|
||||
import MinusIcon from '../../../assets/icons/minus.svg'
|
||||
import Fitview from '../../../assets/icons/fitview.svg'
|
||||
import Lock from '../../../assets/icons/lock.svg'
|
||||
import Unlock from '../../../assets/icons/unlock.svg'
|
||||
|
||||
export interface ControlProps extends HTMLAttributes {
|
||||
showZoom?: boolean
|
||||
showFitView?: boolean
|
||||
showInteractive?: boolean
|
||||
fitViewParams?: FitViewParams
|
||||
onZoomIn?: () => void
|
||||
onZoomOut?: () => void
|
||||
onFitView?: () => void
|
||||
onInteractiveChange?: (interactiveStatus: boolean) => void
|
||||
}
|
||||
|
||||
export type ControlButtonProps = HTMLButtonElement
|
||||
|
||||
export const ControlButton = defineComponent({
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean as PropType<ControlButtonProps['disabled']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
onClick: {
|
||||
type: Function as PropType<() => any>,
|
||||
required: false,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
return () => (
|
||||
<button class={['revue-flow__controls-button']} {...props}>
|
||||
{slots.default ? slots.default() : ''}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const Controls = defineComponent({
|
||||
name: 'Controls',
|
||||
props: {
|
||||
showZoom: {
|
||||
type: Boolean as PropType<ControlProps['showZoom']>,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
showFitView: {
|
||||
type: Boolean as PropType<ControlProps['showFitView']>,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
showInteractive: {
|
||||
type: Boolean as PropType<ControlProps['showInteractive']>,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
fitViewParams: {
|
||||
type: Object as PropType<ControlProps['fitViewParams']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
onZoomIn: {
|
||||
type: Function() as PropType<ControlProps['onZoomIn']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
onZoomOut: {
|
||||
type: Function() as PropType<ControlProps['onZoomOut']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
onFitView: {
|
||||
type: Function() as PropType<ControlProps['onFitView']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
onInteractiveChange: {
|
||||
type: Function() as PropType<ControlProps['onInteractiveChange']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
const isVisible = ref<boolean>(false)
|
||||
const zoomHelper = ref<ZoomPanHelperFunctions>()
|
||||
const { onReady } = useZoomPanHelper()
|
||||
|
||||
onReady((helper) => {
|
||||
zoomHelper.value = helper
|
||||
})
|
||||
|
||||
const isInteractive = store.nodesDraggable && store.nodesConnectable && store.elementsSelectable
|
||||
const mapClasses = ['revue-flow__controls']
|
||||
|
||||
const onZoomInHandler = () => {
|
||||
zoomHelper.value?.zoomIn?.()
|
||||
props.onZoomIn?.()
|
||||
}
|
||||
|
||||
const onZoomOutHandler = () => {
|
||||
zoomHelper.value?.zoomOut?.()
|
||||
props.onZoomOut?.()
|
||||
}
|
||||
|
||||
const onFitViewHandler = () => {
|
||||
zoomHelper.value?.fitView?.(props.fitViewParams)
|
||||
props.onFitView?.()
|
||||
}
|
||||
|
||||
const onInteractiveChangeHandler = () => {
|
||||
store.setInteractive?.(!isInteractive)
|
||||
props.onInteractiveChange?.(!isInteractive)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
isVisible.value = true
|
||||
})
|
||||
|
||||
return () => (
|
||||
<div class={mapClasses}>
|
||||
{props.showZoom && (
|
||||
<>
|
||||
<ControlButton onClick={onZoomInHandler} class="revue-flow__controls-zoomin">
|
||||
<PlusIcon />
|
||||
</ControlButton>
|
||||
<ControlButton onClick={onZoomOutHandler} class="revue-flow__controls-zoomout">
|
||||
<MinusIcon />
|
||||
</ControlButton>
|
||||
</>
|
||||
)}
|
||||
{props.showFitView && (
|
||||
<ControlButton class="revue-flow__controls-fitview" onClick={onFitViewHandler}>
|
||||
<Fitview />
|
||||
</ControlButton>
|
||||
)}
|
||||
{props.showInteractive && (
|
||||
<ControlButton class="revue-flow__controls-interactive" onClick={onInteractiveChangeHandler}>
|
||||
{isInteractive ? <Unlock /> : <Lock />}
|
||||
</ControlButton>
|
||||
)}
|
||||
{slots.default ? slots.default() : ''}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default Controls
|
||||
93
src/additional-components/MiniMap/MiniMap.vue
Normal file
93
src/additional-components/MiniMap/MiniMap.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<script lang="ts" setup>
|
||||
import { HTMLAttributes } from 'vue'
|
||||
import MiniMapNodeDepr from './MiniMapNodeDepr'
|
||||
import { getRectOfNodes, getBoundsofRects } from '~/utils/graph'
|
||||
import { Node, Rect, RevueFlowStore } from '~/types'
|
||||
|
||||
type StringFunc = (node: Node) => string
|
||||
|
||||
export interface MiniMapProps extends HTMLAttributes {
|
||||
nodeColor?: string | StringFunc
|
||||
nodeStrokeColor?: string | StringFunc
|
||||
nodeClassName?: string | StringFunc
|
||||
nodeBorderRadius?: number
|
||||
nodeStrokeWidth?: number
|
||||
maskColor?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<MiniMapProps>(), {
|
||||
nodeStrokeColor: '#555',
|
||||
nodeColor: '#fff',
|
||||
nodeClassName: '',
|
||||
nodeBorderRadius: 5,
|
||||
nodeStrokeWidth: 2,
|
||||
maskColor: 'rgb(240, 242, 243, 0.7)',
|
||||
})
|
||||
const attrs: any = useAttrs()
|
||||
|
||||
declare const window: any
|
||||
|
||||
const defaultWidth = 200
|
||||
const defaultHeight = 150
|
||||
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
const transform = computed(() => store.transform)
|
||||
const elementWidth = computed(() => (attrs.style?.width ?? defaultWidth)! as number)
|
||||
const elementHeight = computed(() => (attrs.style?.height ?? defaultHeight)! as number)
|
||||
const nodeColorFunc = computed(
|
||||
() => (props.nodeColor instanceof Function ? props.nodeColor : () => props.nodeColor) as StringFunc,
|
||||
)
|
||||
const nodeStrokeColorFunc = computed(
|
||||
() => (props.nodeStrokeColor instanceof Function ? props.nodeStrokeColor : () => props.nodeStrokeColor) as StringFunc,
|
||||
)
|
||||
const nodeClassNameFunc = computed(
|
||||
() => (props.nodeClassName instanceof Function ? props.nodeClassName : () => props.nodeClassName) as StringFunc,
|
||||
)
|
||||
const hasNodes = computed(() => store.nodes && store.nodes.length)
|
||||
const bb = computed(() => getRectOfNodes(store.nodes))
|
||||
const viewBB = computed<Rect>(() => ({
|
||||
x: -transform.value[0] / transform.value[2],
|
||||
y: -transform.value[1] / transform.value[2],
|
||||
width: store.width / transform.value[2],
|
||||
height: store.height / transform.value[2],
|
||||
}))
|
||||
const boundingRect = computed(() => (hasNodes.value ? getBoundsofRects(bb.value, viewBB.value) : viewBB.value))
|
||||
const scaledWidth = computed(() => boundingRect.value.width / elementWidth.value)
|
||||
const scaledHeight = computed(() => boundingRect.value.height / elementHeight.value)
|
||||
const viewScale = computed(() => Math.max(scaledWidth.value, scaledHeight.value))
|
||||
const viewWidth = computed(() => viewScale.value * elementWidth.value)
|
||||
const viewHeight = computed(() => viewScale.value * elementHeight.value)
|
||||
const offset = computed(() => 5 * viewScale.value)
|
||||
const x = computed(() => boundingRect.value.x - (viewWidth.value - boundingRect.value.width) / 2 - offset.value)
|
||||
const y = computed(() => boundingRect.value.y - (viewHeight.value - boundingRect.value.height) / 2 - offset.value)
|
||||
const width = computed(() => viewWidth.value + offset.value * 2)
|
||||
const height = computed(() => viewHeight.value + offset.value * 2)
|
||||
const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'
|
||||
</script>
|
||||
<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
|
||||
v-if="!node.isHidden"
|
||||
:x="node.__rf.position.x"
|
||||
:y="node.__rf.position.y"
|
||||
:width="node.__rf.width || 0"
|
||||
:height="node.__rf.height || 0"
|
||||
:style="node.style"
|
||||
:class="nodeClassNameFunc(node)"
|
||||
:color="nodeColorFunc(node)"
|
||||
:border-radius="props.nodeBorderRadius"
|
||||
:stroke-color="nodeStrokeColorFunc(node)"
|
||||
:stroke-width="props.nodeStrokeWidth"
|
||||
:shape-rendering="shapeRendering"
|
||||
/>
|
||||
</template>
|
||||
<path
|
||||
class="revue-flow__minimap-mask"
|
||||
:d="`M${x - offset},${y - offset}h${width + offset * 2}v${height + offset * 2}h${-width - offset * 2}z
|
||||
M${viewBB.x},${viewBB.y}h${viewBB.width}v${viewBB.height}h${-viewBB.width}z`"
|
||||
:fill="props.maskColor"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -1,86 +0,0 @@
|
||||
import { computed, defineComponent, PropType } from 'vue'
|
||||
|
||||
interface MiniMapNodeProps {
|
||||
x: number
|
||||
y: number
|
||||
width: number
|
||||
height: number
|
||||
borderRadius: number
|
||||
color: string
|
||||
shapeRendering: string
|
||||
strokeColor: string
|
||||
strokeWidth: number
|
||||
}
|
||||
|
||||
const MiniMapNode = defineComponent({
|
||||
name: 'MiniMapNode',
|
||||
props: {
|
||||
x: {
|
||||
type: Number as PropType<MiniMapNodeProps['x']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
y: {
|
||||
type: Number as PropType<MiniMapNodeProps['y']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
width: {
|
||||
type: Number as PropType<MiniMapNodeProps['width']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
height: {
|
||||
type: Number as PropType<MiniMapNodeProps['height']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
borderRadius: {
|
||||
type: Number as PropType<MiniMapNodeProps['borderRadius']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
color: {
|
||||
type: String as PropType<MiniMapNodeProps['color']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
shapeRendering: {
|
||||
type: String as PropType<MiniMapNodeProps['shapeRendering']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
strokeColor: {
|
||||
type: String as PropType<MiniMapNodeProps['strokeColor']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
strokeWidth: {
|
||||
type: Number as PropType<MiniMapNodeProps['strokeWidth']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
setup(props, { attrs }: { attrs: Record<string, any> }) {
|
||||
const styles = attrs.style || {}
|
||||
const fill = computed(() => (props.color || styles.value.background || styles.value.backgroundColor) as string)
|
||||
|
||||
return () => (
|
||||
<rect
|
||||
class="revue-flow__minimap-node"
|
||||
x={props.x}
|
||||
y={props.y}
|
||||
rx={props.borderRadius}
|
||||
ry={props.borderRadius}
|
||||
width={props.width}
|
||||
height={props.height}
|
||||
fill={fill.value}
|
||||
stroke={props.strokeColor}
|
||||
stroke-width={props.strokeWidth}
|
||||
shape-rendering={props.shapeRendering}
|
||||
/>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default MiniMapNode
|
||||
35
src/additional-components/MiniMap/MiniMapNode.vue
Normal file
35
src/additional-components/MiniMap/MiniMapNode.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, CSSProperties } from 'vue'
|
||||
|
||||
interface MiniMapNodeProps {
|
||||
x?: number
|
||||
y?: number
|
||||
width?: number
|
||||
height?: number
|
||||
borderRadius?: number
|
||||
color?: string
|
||||
shapeRendering?: string
|
||||
strokeColor?: string
|
||||
strokeWidth?: number
|
||||
}
|
||||
const props = defineProps<MiniMapNodeProps>()
|
||||
const attrs = useAttrs()
|
||||
|
||||
const styles = (attrs.style ?? {}) as CSSProperties
|
||||
const fill = computed(() => (props.color || styles.background || styles.backgroundColor) as string)
|
||||
</script>
|
||||
<template>
|
||||
<rect
|
||||
class="revue-flow__minimap-node"
|
||||
:x="props.x"
|
||||
:y="props.y"
|
||||
:rx="props.borderRadius"
|
||||
:ry="props.borderRadius"
|
||||
:width="props.width"
|
||||
:height="props.height"
|
||||
:fill="fill"
|
||||
:stroke="props.strokeColor"
|
||||
:stroke-width="props.strokeWidth"
|
||||
:shape-rendering="props.shapeRendering"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,130 +0,0 @@
|
||||
import { getRectOfNodes, getBoundsofRects } from '../../utils/graph'
|
||||
import { Node, Rect, RevueFlowStore } from '../../types'
|
||||
import MiniMapNode from './MiniMapNode'
|
||||
import { computed, defineComponent, HTMLAttributes, inject, PropType } from 'vue'
|
||||
|
||||
type StringFunc = (node: Node) => string
|
||||
|
||||
export interface MiniMapProps extends HTMLAttributes {
|
||||
nodeColor?: string | StringFunc
|
||||
nodeStrokeColor?: string | StringFunc
|
||||
nodeClassName?: string | StringFunc
|
||||
nodeBorderRadius?: number
|
||||
nodeStrokeWidth?: number
|
||||
maskColor?: string
|
||||
}
|
||||
|
||||
declare const window: any
|
||||
|
||||
const defaultWidth = 200
|
||||
const defaultHeight = 150
|
||||
|
||||
const MiniMap = defineComponent({
|
||||
name: 'MiniMap',
|
||||
props: {
|
||||
nodeStrokeColor: {
|
||||
type: [String, Function] as PropType<MiniMapProps['nodeStrokeColor']>,
|
||||
required: false,
|
||||
default: '#555'
|
||||
},
|
||||
nodeColor: {
|
||||
type: [String, Function] as PropType<MiniMapProps['nodeColor']>,
|
||||
required: false,
|
||||
default: '#fff'
|
||||
},
|
||||
nodeClassName: {
|
||||
type: [String, Function] as PropType<MiniMapProps['nodeClassName']>,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
nodeBorderRadius: {
|
||||
type: Number as PropType<MiniMapProps['nodeBorderRadius']>,
|
||||
required: false,
|
||||
default: 5
|
||||
},
|
||||
nodeStrokeWidth: {
|
||||
type: Number as PropType<MiniMapProps['nodeStrokeWidth']>,
|
||||
required: false,
|
||||
default: 2
|
||||
},
|
||||
maskColor: {
|
||||
type: String as PropType<MiniMapProps['maskColor']>,
|
||||
required: false,
|
||||
default: 'rgb(240, 242, 243, 0.7)'
|
||||
}
|
||||
},
|
||||
setup(props, { attrs }: { attrs: Record<string, any> }) {
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
const transform = computed(() => store.transform)
|
||||
const elementWidth = computed(() => (attrs.style?.width || defaultWidth)! as number)
|
||||
const elementHeight = computed(() => (attrs.style?.height || defaultHeight)! as number)
|
||||
const nodeColorFunc = computed(
|
||||
() => (props.nodeColor instanceof Function ? props.nodeColor : () => props.nodeColor) as StringFunc
|
||||
)
|
||||
const nodeStrokeColorFunc = computed(
|
||||
() => (props.nodeStrokeColor instanceof Function ? props.nodeStrokeColor : () => props.nodeStrokeColor) as StringFunc
|
||||
)
|
||||
const nodeClassNameFunc = computed(
|
||||
() => (props.nodeClassName instanceof Function ? props.nodeClassName : () => props.nodeClassName) as StringFunc
|
||||
)
|
||||
const hasNodes = computed(() => store.nodes && store.nodes.length)
|
||||
const bb = computed(() => getRectOfNodes(store.nodes))
|
||||
const viewBB = computed<Rect>(() => ({
|
||||
x: -transform.value[0] / transform.value[2],
|
||||
y: -transform.value[1] / transform.value[2],
|
||||
width: store.width / transform.value[2],
|
||||
height: store.height / transform.value[2]
|
||||
}))
|
||||
const boundingRect = computed(() => (hasNodes.value ? getBoundsofRects(bb.value, viewBB.value) : viewBB.value))
|
||||
const scaledWidth = computed(() => boundingRect.value.width / elementWidth.value)
|
||||
const scaledHeight = computed(() => boundingRect.value.height / elementHeight.value)
|
||||
const viewScale = computed(() => Math.max(scaledWidth.value, scaledHeight.value))
|
||||
const viewWidth = computed(() => viewScale.value * elementWidth.value)
|
||||
const viewHeight = computed(() => viewScale.value * elementHeight.value)
|
||||
const offset = computed(() => 5 * viewScale.value)
|
||||
const x = computed(() => boundingRect.value.x - (viewWidth.value - boundingRect.value.width) / 2 - offset.value)
|
||||
const y = computed(() => boundingRect.value.y - (viewHeight.value - boundingRect.value.height) / 2 - offset.value)
|
||||
const width = computed(() => viewWidth.value + offset.value * 2)
|
||||
const height = computed(() => viewHeight.value + offset.value * 2)
|
||||
const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'
|
||||
|
||||
return () => (
|
||||
<svg
|
||||
width={elementWidth.value}
|
||||
height={elementHeight.value}
|
||||
viewBox={`${x.value} ${y.value} ${width.value} ${height.value}`}
|
||||
class="revue-flow__minimap"
|
||||
>
|
||||
{store.nodes
|
||||
.filter((node) => !node.isHidden)
|
||||
.map((node) => (
|
||||
<MiniMapNode
|
||||
key={node.id}
|
||||
x={node.__rf.position.x}
|
||||
y={node.__rf.position.y}
|
||||
width={node.__rf.width || 0}
|
||||
height={node.__rf.height || 0}
|
||||
style={node.style}
|
||||
class={nodeClassNameFunc.value(node)}
|
||||
color={nodeColorFunc.value(node)}
|
||||
borderRadius={props.nodeBorderRadius}
|
||||
strokeColor={nodeStrokeColorFunc.value(node)}
|
||||
strokeWidth={props.nodeStrokeWidth}
|
||||
shapeRendering={shapeRendering}
|
||||
/>
|
||||
))}
|
||||
<path
|
||||
class="revue-flow__minimap-mask"
|
||||
d={`M${x.value - offset.value},${y.value - offset.value}h${width.value + offset.value * 2}v${
|
||||
height.value + offset.value * 2
|
||||
}h${-width.value - offset.value * 2}z
|
||||
M${viewBB.value.x},${viewBB.value.y}h${viewBB.value.width}v${viewBB.value.height}h${-viewBB.value.width}z`}
|
||||
fill={props.maskColor}
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default MiniMap
|
||||
@@ -1,6 +1,6 @@
|
||||
// These components are not used by revue Flow directly
|
||||
// but the user can add them as children of a revue Flow component
|
||||
|
||||
export { default as MiniMap } from './MiniMap'
|
||||
export { default as Controls, ControlButton } from './Controls'
|
||||
export { default as Background } from './Background'
|
||||
export { default as MiniMap } from './MiniMap/MiniMap.vue'
|
||||
export { default as Controls } from './Controls/Controls.vue'
|
||||
export { default as Background } from './Background/Background.vue'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { CSSProperties } from 'vue'
|
||||
import { ConnectionLineType, CustomConnectionLine, HandleElement, Position, RevueFlowStore, Node } from '~/types'
|
||||
import { ConnectionLineType, CustomConnectionLine, HandleElement, Node, Position, RevueFlowStore } from '~/types'
|
||||
import { RevueFlowHooks } from '~/hooks/RevueFlowHooks'
|
||||
import { getBezierPath, getSmoothStepPath } from '~/components/Edges/utils'
|
||||
|
||||
@@ -16,76 +16,67 @@ const props = withDefaults(defineProps<ConnectionLineProps>(), {
|
||||
connectionLineStyle: () => ({}),
|
||||
})
|
||||
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
const hooks = inject<RevueFlowHooks>('hooks')!
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
|
||||
const sourceHandle = computed(() =>
|
||||
const sourceHandle =
|
||||
store.connectionHandleId && store.connectionHandleType
|
||||
? props.sourceNode.__rf.handleBounds[store.connectionHandleType].find((d: HandleElement) => d.id === store.connectionHandleId)
|
||||
: store.connectionHandleType && props.sourceNode.__rf.handleBounds[store.connectionHandleType][0],
|
||||
)
|
||||
const sourceHandleX = computed(() =>
|
||||
sourceHandle.value ? sourceHandle.value.x + sourceHandle.value.width / 2 : (props.sourceNode.__rf.width as number) / 2,
|
||||
)
|
||||
const sourceHandleY = computed(() =>
|
||||
sourceHandle.value ? sourceHandle.value.y + sourceHandle.value.height / 2 : props.sourceNode.__rf.height,
|
||||
)
|
||||
const sourceX = computed(() => props.sourceNode.__rf.position.x + sourceHandleX.value)
|
||||
const sourceY = computed(() => props.sourceNode.__rf.position.y + sourceHandleY.value)
|
||||
: store.connectionHandleType && props.sourceNode.__rf.handleBounds[store.connectionHandleType][0]
|
||||
const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : (props.sourceNode.__rf.width as number) / 2
|
||||
const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : props.sourceNode.__rf.height
|
||||
const sourceX = props.sourceNode.__rf.position.x + sourceHandleX
|
||||
const sourceY = props.sourceNode.__rf.position.y + sourceHandleY
|
||||
|
||||
const targetX = computed(() => (store.connectionPosition?.x - store.transform[0]) / store.transform[2])
|
||||
const targetY = computed(() => (store.connectionPosition?.y - store.transform[1]) / store.transform[2])
|
||||
const isRightOrLeft = sourceHandle.position === Position.Left || sourceHandle.position === Position.Right
|
||||
const targetPosition = isRightOrLeft ? Position.Left : Position.Top
|
||||
|
||||
const isRightOrLeft = computed(
|
||||
() => sourceHandle.value?.position === Position.Left || sourceHandle.value?.position === Position.Right,
|
||||
)
|
||||
const targetPosition = computed(() => (isRightOrLeft.value ? Position.Left : Position.Top))
|
||||
const targetX = computed(() => (store.connectionPosition.x - store.transform[0]) / store.transform[2])
|
||||
const targetY = computed(() => (store.connectionPosition.y - store.transform[1]) / store.transform[2])
|
||||
|
||||
let dAttr = computed(() => `M${sourceX.value},${sourceY.value} ${targetX.value},${targetY.value}`)
|
||||
|
||||
if (props.connectionLineType === ConnectionLineType.Bezier) {
|
||||
dAttr = computed(() =>
|
||||
getBezierPath({
|
||||
sourceX: sourceX.value,
|
||||
sourceY: sourceY.value,
|
||||
sourcePosition: sourceHandle.value?.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition: targetPosition.value,
|
||||
}),
|
||||
)
|
||||
} else if (props.connectionLineType === ConnectionLineType.Step) {
|
||||
dAttr = computed(() =>
|
||||
getSmoothStepPath({
|
||||
sourceX: sourceX.value,
|
||||
sourceY: sourceY.value,
|
||||
sourcePosition: sourceHandle.value?.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition: targetPosition.value,
|
||||
borderRadius: 0,
|
||||
}),
|
||||
)
|
||||
} else if (props.connectionLineType === ConnectionLineType.SmoothStep) {
|
||||
dAttr = computed(() =>
|
||||
getSmoothStepPath({
|
||||
sourceX: sourceX.value,
|
||||
sourceY: sourceY.value,
|
||||
sourcePosition: sourceHandle.value?.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition: targetPosition.value,
|
||||
}),
|
||||
)
|
||||
}
|
||||
watch(dAttr, () => console.log(dAttr.value))
|
||||
const dAttr = computed(() => {
|
||||
let path = `M${sourceX.value},${sourceY.value} ${targetX.value},${targetY.value}`
|
||||
switch (props.connectionLineType) {
|
||||
case ConnectionLineType.Bezier:
|
||||
path = getBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition: sourceHandle.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition,
|
||||
})
|
||||
break
|
||||
case ConnectionLineType.Step:
|
||||
path = getSmoothStepPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition: sourceHandle.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition,
|
||||
borderRadius: 0,
|
||||
})
|
||||
break
|
||||
case ConnectionLineType.SmoothStep:
|
||||
path = getSmoothStepPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition: sourceHandle.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition,
|
||||
})
|
||||
break
|
||||
}
|
||||
return path
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<g>
|
||||
<g class="revue-flow__connection">
|
||||
<component
|
||||
:is="props.customConnectionLine"
|
||||
v-if="props.customConnectionLine"
|
||||
class="revue-flow__connection"
|
||||
v-bind="{
|
||||
sourceX: sourceX,
|
||||
sourceY: sourceY,
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
import { ref, defineComponent, CSSProperties, PropType, computed, inject, h } from 'vue'
|
||||
|
||||
import { getBezierPath } from '../Edges/BezierEdgeDepr'
|
||||
import { getSmoothStepPath } from '../Edges/SmoothStepEdgeDepr'
|
||||
import { Node, HandleElement, Position, ConnectionLineType, ConnectionLineComponent, RevueFlowStore } from '../../types'
|
||||
|
||||
interface ConnectionLineProps {
|
||||
connectionLineType: ConnectionLineType
|
||||
connectionLineStyle?: CSSProperties
|
||||
customConnectionLine?: ConnectionLineComponent
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
connectionLineStyle: {
|
||||
type: Object as PropType<ConnectionLineProps['connectionLineStyle']>,
|
||||
required: false,
|
||||
default: () => {},
|
||||
},
|
||||
connectionLineType: {
|
||||
type: String as PropType<ConnectionLineProps['connectionLineType']>,
|
||||
required: false,
|
||||
default: ConnectionLineType.Bezier,
|
||||
},
|
||||
customConnectionLine: {
|
||||
type: Object as PropType<ConnectionLineProps['customConnectionLine']>,
|
||||
required: false,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
const sourceNode = ref<Node | null>(store.nodes.find((n) => n.id === store.connectionNodeId) || null)
|
||||
const nodesConnectable = computed(() => store.nodesConnectable)
|
||||
|
||||
const sourceHandle = computed(() =>
|
||||
store.connectionHandleId && store.connectionHandleType
|
||||
? sourceNode.value?.__rf.handleBounds[store.connectionHandleType].find(
|
||||
(d: HandleElement) => d.id === store.connectionHandleId,
|
||||
)
|
||||
: store.connectionHandleType && sourceNode.value?.__rf.handleBounds[store.connectionHandleType][0],
|
||||
)
|
||||
const sourceHandleX = computed(() =>
|
||||
sourceHandle.value ? sourceHandle.value.x + sourceHandle.value.width / 2 : (sourceNode.value?.__rf.width as number) / 2,
|
||||
)
|
||||
const sourceHandleY = computed(() =>
|
||||
sourceHandle.value ? sourceHandle.value.y + sourceHandle.value.height / 2 : sourceNode.value?.__rf.height,
|
||||
)
|
||||
const sourceX = computed(() => sourceNode.value?.__rf.position.x + sourceHandleX.value)
|
||||
const sourceY = computed(() => sourceNode.value?.__rf.position.y + sourceHandleY.value)
|
||||
|
||||
const targetX = computed(() => (store.connectionPosition.x - store.transform[0]) / store.transform[2])
|
||||
const targetY = computed(() => (store.connectionPosition.y - store.transform[1]) / store.transform[2])
|
||||
|
||||
const isRightOrLeft = computed(
|
||||
() => sourceHandle.value?.position === Position.Left || sourceHandle.value?.position === Position.Right,
|
||||
)
|
||||
const targetPosition = computed(() => (isRightOrLeft.value ? Position.Left : Position.Top))
|
||||
|
||||
let dAttr = computed(() => `M${sourceX.value},${sourceY.value} ${targetX.value},${targetY.value}`)
|
||||
|
||||
if (props.connectionLineType === ConnectionLineType.Bezier) {
|
||||
dAttr = computed(() =>
|
||||
getBezierPath({
|
||||
sourceX: sourceX.value,
|
||||
sourceY: sourceY.value,
|
||||
sourcePosition: sourceHandle.value?.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition: targetPosition.value,
|
||||
}),
|
||||
)
|
||||
} else if (props.connectionLineType === ConnectionLineType.Step) {
|
||||
dAttr = computed(() =>
|
||||
getSmoothStepPath({
|
||||
sourceX: sourceX.value,
|
||||
sourceY: sourceY.value,
|
||||
sourcePosition: sourceHandle.value?.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition: targetPosition.value,
|
||||
borderRadius: 0,
|
||||
}),
|
||||
)
|
||||
} else if (props.connectionLineType === ConnectionLineType.SmoothStep) {
|
||||
dAttr = computed(() =>
|
||||
getSmoothStepPath({
|
||||
sourceX: sourceX.value,
|
||||
sourceY: sourceY.value,
|
||||
sourcePosition: sourceHandle.value?.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition: targetPosition.value,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
if (props.customConnectionLine) {
|
||||
return () => (
|
||||
<g className="revue-flow__connection">
|
||||
{props.customConnectionLine &&
|
||||
h(props.customConnectionLine, {
|
||||
sourceX: sourceX.value,
|
||||
sourceY: sourceY.value,
|
||||
sourcePosition: sourceHandle.value?.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition: targetPosition.value,
|
||||
connectionLineType: props.connectionLineType,
|
||||
connectionLineStyle: props.connectionLineStyle,
|
||||
})}
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
return () =>
|
||||
nodesConnectable.value && sourceNode.value ? (
|
||||
<g className="revue-flow__connection">
|
||||
<path d={dAttr.value} className="revue-flow__connection-path" style={props.connectionLineStyle} />
|
||||
</g>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -4,7 +4,7 @@ import { RevueFlowHooks } from '~/hooks/RevueFlowHooks'
|
||||
import { getEdgePositions, getHandle, getSourceTargetNodes, isEdgeVisible } from '~/container/EdgeRenderer/utils'
|
||||
import { isEdge } from '~/utils/graph'
|
||||
import { ConnectionMode, Dimensions, Edge, EdgeType, Elements, Position, RevueFlowStore, Transform } from '~/types'
|
||||
import { onMouseDown } from '~/components/Handle/handler'
|
||||
import { onMouseDown } from '~/components/Handle/utils'
|
||||
|
||||
interface EdgeProps {
|
||||
type: EdgeType
|
||||
@@ -12,7 +12,7 @@ interface EdgeProps {
|
||||
nodes: ReturnType<typeof getSourceTargetNodes>
|
||||
dimensions: Dimensions
|
||||
transform: Transform
|
||||
selectedElements?: Elements | null
|
||||
selectedElements?: Elements
|
||||
elementsSelectable?: boolean
|
||||
onlyRenderVisibleElements?: boolean
|
||||
connectionMode?: ConnectionMode
|
||||
@@ -23,7 +23,6 @@ interface EdgeProps {
|
||||
const props = withDefaults(defineProps<EdgeProps>(), {
|
||||
elementsSelectable: true,
|
||||
onlyRenderVisibleElements: false,
|
||||
selectedElements: () => null,
|
||||
})
|
||||
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { ConnectionMode, ElementId, Position, RevueFlowStore } from '~/types'
|
||||
import { onMouseDown, ValidConnectionFunc } from '~/components/Handle/handler'
|
||||
import { ElementId, Position, RevueFlowStore } from '~/types'
|
||||
import { onMouseDown, ValidConnectionFunc } from '~/components/Handle/utils'
|
||||
import { RevueFlowHooks } from '~/hooks/RevueFlowHooks'
|
||||
|
||||
interface HandleProps {
|
||||
@@ -15,7 +15,6 @@ const props = withDefaults(defineProps<HandleProps>(), {
|
||||
id: '',
|
||||
type: 'source',
|
||||
position: Position.Top,
|
||||
isValidConnection: () => true,
|
||||
connectable: true,
|
||||
})
|
||||
|
||||
@@ -24,7 +23,18 @@ const hooks = inject<RevueFlowHooks>('hooks')!
|
||||
const nodeId = inject<ElementId>('NodeIdContext')!
|
||||
|
||||
const onMouseDownHandler = (event: MouseEvent) =>
|
||||
onMouseDown(event, store, hooks, props.id, nodeId, props.type === 'target', props.isValidConnection)
|
||||
onMouseDown(
|
||||
event,
|
||||
store,
|
||||
hooks,
|
||||
props.id,
|
||||
nodeId,
|
||||
props.type === 'target',
|
||||
props.isValidConnection ??
|
||||
function () {
|
||||
return true
|
||||
},
|
||||
)
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import { ElementId, Position, RevueFlowStore } from '../../types'
|
||||
import { onMouseDown, ValidConnectionFunc } from './handler'
|
||||
import { defineComponent, 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')!
|
||||
|
||||
const onMouseDownHandler = (event: MouseEvent) =>
|
||||
onMouseDown(event, store, hooks, props.id as string, nodeId, props.type === 'target', props.isValidConnection)
|
||||
|
||||
return () => (
|
||||
<div
|
||||
data-handleid={props.id}
|
||||
data-nodeid={nodeId}
|
||||
data-handlepos={props.position}
|
||||
class={[
|
||||
'revue-flow__handle',
|
||||
`revue-flow__handle-${props.position}`,
|
||||
'nodrag',
|
||||
{
|
||||
source: props.type !== 'target',
|
||||
target: props.type === 'target',
|
||||
connectable: props.isConnectable
|
||||
}
|
||||
]}
|
||||
onMousedown={onMouseDownHandler}
|
||||
>
|
||||
{slots.default ? slots.default() : ''}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getHostForElement } from '~/utils'
|
||||
import { ElementId, ConnectionMode, Connection, HandleType, XYPosition, RevueFlowStore } from '~/types'
|
||||
import { ElementId, ConnectionMode, Connection, HandleType, RevueFlowStore } from '~/types'
|
||||
import { RevueFlowHooks } from '~/hooks/RevueFlowHooks'
|
||||
|
||||
export type ValidConnectionFunc = (connection: Connection) => boolean
|
||||
@@ -82,7 +82,9 @@ export function onMouseDown(
|
||||
handleId: ElementId,
|
||||
nodeId: ElementId,
|
||||
isTarget: boolean,
|
||||
isValidConnection: ValidConnectionFunc = () => true,
|
||||
isValidConnection: ValidConnectionFunc = () => {
|
||||
return true
|
||||
},
|
||||
elementEdgeUpdaterType?: HandleType,
|
||||
): void {
|
||||
const revueFlowNode = (event.target as Element).closest('.revue-flow')
|
||||
@@ -105,12 +107,6 @@ export function onMouseDown(
|
||||
const containerBounds = revueFlowNode.getBoundingClientRect()
|
||||
let recentHoveredHandle: Element
|
||||
|
||||
const connectionPosition = ref<XYPosition>({
|
||||
x: event.clientX - containerBounds.left,
|
||||
y: event.clientY - containerBounds.top,
|
||||
})
|
||||
|
||||
if (!store.connectionPosition) store.connectionPosition = { x: 0, y: 0 }
|
||||
store.connectionPosition.x = event.clientX - containerBounds.left
|
||||
store.connectionPosition.y = event.clientY - containerBounds.top
|
||||
|
||||
@@ -122,8 +118,8 @@ export function onMouseDown(
|
||||
hooks.connectStart.trigger({ event, params: { nodeId, handleId, handleType } })
|
||||
|
||||
function onMouseMove(event: MouseEvent) {
|
||||
connectionPosition.value.x = event.clientX - containerBounds.left
|
||||
connectionPosition.value.y = event.clientY - containerBounds.top
|
||||
store.connectionPosition.x = event.clientX - containerBounds.left
|
||||
store.connectionPosition.y = event.clientY - containerBounds.top
|
||||
|
||||
const { connection, elementBelow, isValid, isHoveringHandle } = checkElementBelowIsValid(
|
||||
event,
|
||||
@@ -173,6 +169,7 @@ export function onMouseDown(
|
||||
|
||||
resetRecentHandle(recentHoveredHandle)
|
||||
store.setConnectionNodeId({ connectionNodeId: undefined, connectionHandleId: undefined, connectionHandleType: undefined })
|
||||
store.connectionPosition = { x: NaN, y: NaN }
|
||||
|
||||
doc.removeEventListener('mousemove', onMouseMove as EventListenerOrEventListenerObject)
|
||||
doc.removeEventListener('mouseup', onMouseUp as EventListenerOrEventListenerObject)
|
||||
@@ -1,40 +0,0 @@
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
import Handle from '../../components/Handle'
|
||||
import { NodeProps, NodeType, Position } from '../../types'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DefaultNode',
|
||||
components: { Handle },
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
data: {
|
||||
type: Object as PropType<NodeProps['data']>,
|
||||
required: false,
|
||||
default: undefined as any
|
||||
},
|
||||
isConnectable: {
|
||||
type: Boolean as PropType<NodeProps['isConnectable']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
targetPosition: {
|
||||
type: String as PropType<NodeProps['targetPosition']>,
|
||||
required: false,
|
||||
default: Position.Top
|
||||
},
|
||||
sourcePosition: {
|
||||
type: String as PropType<NodeProps['sourcePosition']>,
|
||||
required: false,
|
||||
default: Position.Bottom
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
return () => (
|
||||
<>
|
||||
<Handle type="target" position={props.targetPosition} isConnectable={props.isConnectable} />
|
||||
{props.data?.label}
|
||||
<Handle type="source" position={props.sourcePosition} isConnectable={props.isConnectable} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
}) as NodeType
|
||||
@@ -1,34 +0,0 @@
|
||||
import Handle from '../../components/Handle'
|
||||
import { NodeProps, NodeType, Position } from '../../types'
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'InputNode',
|
||||
components: { Handle },
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
data: {
|
||||
type: Object as PropType<NodeProps['data']>,
|
||||
required: false,
|
||||
default: undefined as any
|
||||
},
|
||||
isConnectable: {
|
||||
type: Boolean as PropType<NodeProps['isConnectable']>,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
sourcePosition: {
|
||||
type: String as PropType<NodeProps['sourcePosition']>,
|
||||
required: false,
|
||||
default: Position.Bottom
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
return () => (
|
||||
<>
|
||||
{props.data?.label}
|
||||
<Handle type="source" position={props.sourcePosition} isConnectable={props.isConnectable} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
}) as NodeType
|
||||
@@ -1,236 +0,0 @@
|
||||
import { computed, defineComponent, h, inject, onMounted, PropType, provide } from 'vue'
|
||||
import { templateRef, useResizeObserver } from '@vueuse/core'
|
||||
import { DraggableCore, DraggableEventListener } from '@braks/revue-draggable'
|
||||
import { Node, NodeDimensionUpdate, NodeType, RevueFlowStore } from '../../types'
|
||||
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'
|
||||
|
||||
interface NodeProps {
|
||||
nodeTypes: Record<string, NodeType>
|
||||
snapToGrid: boolean
|
||||
snapGrid: [number, number]
|
||||
selectNodesOnDrag: boolean
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
components: { DraggableCore },
|
||||
props: {
|
||||
node: {
|
||||
type: Object as PropType<Node>,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
snapToGrid: {
|
||||
type: Boolean as PropType<NodeProps['snapToGrid']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
snapGrid: {
|
||||
type: Array as unknown as PropType<NodeProps['snapGrid']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
selectNodesOnDrag: {
|
||||
type: Boolean as PropType<NodeProps['selectNodesOnDrag']>,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
const hooks = inject<RevueFlowHooks>('hooks')!
|
||||
provide('NodeIdContext', props.node.id)
|
||||
|
||||
const nodeElement = templateRef<HTMLDivElement>('nodeElement', null)
|
||||
|
||||
const selected = computed(() => store.selectedElements?.some(({ id }) => id === props.node.id) || false)
|
||||
const isDraggable = computed(
|
||||
() => props.node.draggable || (store.nodesDraggable && typeof props.node.draggable === 'undefined')
|
||||
)
|
||||
const isSelectable = computed(
|
||||
() => props.node.selectable || (store.elementsSelectable && typeof props.node.selectable === 'undefined')
|
||||
)
|
||||
const isConnectable = computed(
|
||||
() => props.node.connectable || (store.nodesConnectable && typeof props.node.connectable === 'undefined')
|
||||
)
|
||||
|
||||
const node = () =>
|
||||
({
|
||||
id: props.node.id,
|
||||
type: props.node.type,
|
||||
position: { x: props.node.__rf.position.x, y: props.node.__rf.position.y },
|
||||
data: props.node.data
|
||||
} as Node)
|
||||
|
||||
const onMouseEnterHandler = () => {
|
||||
if (props.node.__rf.isDragging) {
|
||||
return
|
||||
}
|
||||
|
||||
return (event: MouseEvent) => hooks.nodeMouseEnter.trigger({ event, node: node() })
|
||||
}
|
||||
|
||||
const onMouseMoveHandler = () => {
|
||||
if (props.node.__rf.isDragging) {
|
||||
return
|
||||
}
|
||||
|
||||
return (event: MouseEvent) => hooks.nodeMouseMove.trigger({ event, node: node() })
|
||||
}
|
||||
|
||||
const onMouseLeaveHandler = () => {
|
||||
if (props.node.__rf.isDragging) {
|
||||
return
|
||||
}
|
||||
|
||||
return (event: MouseEvent) => hooks.nodeMouseLeave.trigger({ event, node: node() })
|
||||
}
|
||||
|
||||
const onContextMenuHandler = () => {
|
||||
return (event: MouseEvent) => hooks.nodeContextMenu.trigger({ event, node: node() })
|
||||
}
|
||||
|
||||
const onSelectNodeHandler = (event: MouseEvent) => {
|
||||
if (!isDraggable.value) {
|
||||
const n = node()
|
||||
if (isSelectable.value) {
|
||||
store.unsetNodesSelection()
|
||||
|
||||
if (!selected.value) {
|
||||
store.addSelectedElements([n])
|
||||
}
|
||||
}
|
||||
|
||||
hooks.nodeClick.trigger({ event, node: n })
|
||||
}
|
||||
}
|
||||
|
||||
const onDragStart: DraggableEventListener = ({ event }) => {
|
||||
const n = node()
|
||||
hooks.nodeDragStart.trigger({ event, node: n })
|
||||
|
||||
if (props.selectNodesOnDrag && isSelectable.value) {
|
||||
store.unsetNodesSelection()
|
||||
|
||||
if (!selected.value) {
|
||||
store.addSelectedElements([n])
|
||||
}
|
||||
} else if (!props.selectNodesOnDrag && !selected.value && isSelectable.value) {
|
||||
store.unsetNodesSelection()
|
||||
store.addSelectedElements([])
|
||||
}
|
||||
}
|
||||
|
||||
const onDrag: DraggableEventListener = ({ event, data }) => {
|
||||
const n = node()
|
||||
n.position.x += data.deltaX
|
||||
n.position.y += data.deltaY
|
||||
hooks.nodeDrag.trigger({ event, node: n })
|
||||
|
||||
store?.updateNodePosDiff({
|
||||
id: props.node.id as string,
|
||||
diff: {
|
||||
x: data.deltaX,
|
||||
y: data.deltaY
|
||||
},
|
||||
isDragging: true
|
||||
})
|
||||
}
|
||||
|
||||
const onDragStop: DraggableEventListener = ({ event }) => {
|
||||
const n = node()
|
||||
// onDragStop also gets called when user just clicks on a node.
|
||||
// Because of that we set dragging to true inside the onDrag handler and handle the click here
|
||||
if (!props.node.__rf.isDragging) {
|
||||
if (isSelectable.value && !props.selectNodesOnDrag && !selected.value) {
|
||||
store.addSelectedElements([n])
|
||||
}
|
||||
|
||||
hooks.nodeClick.trigger({ event, node: n })
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
store.updateNodePosDiff({
|
||||
id: n.id || '',
|
||||
isDragging: false
|
||||
})
|
||||
|
||||
hooks.nodeDragStop.trigger({ event, node: n })
|
||||
}
|
||||
|
||||
useResizeObserver(nodeElement, (entries) => {
|
||||
const updates: NodeDimensionUpdate[] = entries.map((entry) => ({
|
||||
id: entry.target.getAttribute('data-id') || '',
|
||||
nodeElement: entry.target as HTMLDivElement
|
||||
}))
|
||||
|
||||
store.updateNodeDimensions(updates)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
store.updateNodeDimensions([
|
||||
{
|
||||
id: props.node.id || '',
|
||||
nodeElement: nodeElement.value,
|
||||
forceUpdate: true
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
return () =>
|
||||
props.node.isHidden ? (
|
||||
''
|
||||
) : (
|
||||
<DraggableCore
|
||||
cancel=".nodrag"
|
||||
scale={store.transform[2]}
|
||||
disabled={!isDraggable.value}
|
||||
grid={props.snapGrid}
|
||||
enableUserSelectHack={false}
|
||||
onStart={onDragStart}
|
||||
onMove={onDrag}
|
||||
onStop={onDragStop}
|
||||
>
|
||||
<div
|
||||
ref="nodeElement"
|
||||
class={[
|
||||
'revue-flow__node',
|
||||
`revue-flow__node-${props.node.type}`,
|
||||
{
|
||||
selected: selected.value,
|
||||
selectable: isSelectable.value
|
||||
}
|
||||
]}
|
||||
style={{
|
||||
zIndex: selected.value ? 10 : 3,
|
||||
transform: `translate(${props.node.__rf.position.x}px,${props.node.__rf.position.y}px)`,
|
||||
pointerEvents: isSelectable.value || isDraggable.value ? 'all' : 'none',
|
||||
opacity: props.node.__rf.width !== null && props.node.__rf.height !== null ? 1 : 0,
|
||||
...props.node.style
|
||||
}}
|
||||
data-id={props.node.id}
|
||||
onMouseenter={onMouseEnterHandler}
|
||||
onMousemove={onMouseMoveHandler}
|
||||
onMouseleave={onMouseLeaveHandler}
|
||||
onContextmenu={onContextMenuHandler}
|
||||
onClick={onSelectNodeHandler}
|
||||
>
|
||||
{h(props.type, {
|
||||
data: props.node.data,
|
||||
type: props.node.type,
|
||||
xPos: props.node.__rf.position.x,
|
||||
yPos: props.node.__rf.position.y,
|
||||
selected: selected.value,
|
||||
isConnectable: isConnectable.value,
|
||||
sourcePosition: props.node.sourcePosition,
|
||||
targetPosition: props.node.targetPosition,
|
||||
isDragging: props.node.__rf.isDragging
|
||||
})}
|
||||
</div>
|
||||
</DraggableCore>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,34 +0,0 @@
|
||||
import Handle from '../../components/Handle'
|
||||
import { NodeProps, NodeType, Position } from '../../types'
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'OutputNode',
|
||||
components: { Handle },
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
data: {
|
||||
type: Object as PropType<NodeProps['data']>,
|
||||
required: false,
|
||||
default: undefined as any
|
||||
},
|
||||
isConnectable: {
|
||||
type: Boolean as PropType<NodeProps['isConnectable']>,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
targetPosition: {
|
||||
type: String as PropType<NodeProps['targetPosition']>,
|
||||
required: false,
|
||||
default: Position.Top
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
return () => (
|
||||
<>
|
||||
{props.data?.label}
|
||||
<Handle type="source" position={props.targetPosition} isConnectable={props.isConnectable} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
}) as NodeType
|
||||
@@ -1,103 +0,0 @@
|
||||
// @ts-nocheck
|
||||
/**
|
||||
* The nodes selection rectangle gets displayed when a user
|
||||
* made a selectio with on or several nodes
|
||||
*/
|
||||
import { computed, defineComponent, inject } from 'vue'
|
||||
import { Draggable, DraggableEventListener } from '@braks/revue-draggable'
|
||||
import { isNode } from '../../utils/graph'
|
||||
import { Node, RevueFlowStore } from '../../types'
|
||||
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'
|
||||
|
||||
export interface NodesSelectionProps {
|
||||
onSelectionDragStart?: (event: MouseEvent, nodes: Node[]) => void
|
||||
onSelectionDrag?: (event: MouseEvent, nodes: Node[]) => void
|
||||
onSelectionDragStop?: (event: MouseEvent, nodes: Node[]) => void
|
||||
onSelectionContextMenu?: (event: MouseEvent, nodes: Node[]) => void
|
||||
}
|
||||
|
||||
const NodesSelection = defineComponent({
|
||||
setup() {
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
const hooks = inject<RevueFlowHooks>('hooks')!
|
||||
const grid = computed(() => (store.snapToGrid ? store.snapGrid : [1, 1])! as [number, number])
|
||||
const transform = computed(() => store.transform)
|
||||
|
||||
const selectedNodes = computed(() =>
|
||||
store.selectedElements
|
||||
? store.selectedElements.filter(isNode).map((selectedNode) => {
|
||||
const matchingNode = store.nodes.find((node) => node.id === selectedNode.id)
|
||||
|
||||
return {
|
||||
...matchingNode,
|
||||
position: matchingNode?.__rf.position
|
||||
} as Node
|
||||
})
|
||||
: []
|
||||
)
|
||||
|
||||
const style = computed(() => ({
|
||||
transform: `translate(${store.transform[0]}px,${store.transform[1]}px) scale(${store.transform[2]})`
|
||||
}))
|
||||
|
||||
const innerStyle = computed(() => ({
|
||||
width: `${store.selectedNodesBbox.width}px`,
|
||||
height: `${store.selectedNodesBbox.height}px`,
|
||||
top: `${store.selectedNodesBbox.y}px`,
|
||||
left: `${store.selectedNodesBbox.x}px`
|
||||
}))
|
||||
|
||||
const onStart: DraggableEventListener = ({ event }) => {
|
||||
hooks.selectionDragStart.trigger({ event, nodes: selectedNodes.value })
|
||||
}
|
||||
|
||||
const onDrag: DraggableEventListener = ({ event, data }) => {
|
||||
hooks.selectionDrag.trigger({ event, nodes: selectedNodes.value })
|
||||
|
||||
store.updateNodePosDiff({
|
||||
diff: {
|
||||
x: data.deltaX,
|
||||
y: data.deltaY
|
||||
},
|
||||
isDragging: true
|
||||
})
|
||||
}
|
||||
|
||||
const onStop: DraggableEventListener = ({ event }) => {
|
||||
store.updateNodePosDiff({
|
||||
isDragging: false
|
||||
})
|
||||
|
||||
hooks.selectionDragStop.trigger({ event, nodes: selectedNodes.value })
|
||||
}
|
||||
|
||||
const onContextMenu = (event: MouseEvent) => {
|
||||
const selectedNodes: Node[] = store.selectedElements
|
||||
? store.selectedElements.filter(isNode).map((selectedNode) => store.nodes.find((node) => node.id === selectedNode.id))
|
||||
: []
|
||||
|
||||
hooks.selectionContextMenu.trigger({ event, nodes: selectedNodes })
|
||||
}
|
||||
|
||||
return () => {
|
||||
return !store.selectedElements || store.selectionActive ? (
|
||||
''
|
||||
) : (
|
||||
<div class="revue-flow__nodesselection" style={style.value}>
|
||||
<Draggable
|
||||
onStart={onStart}
|
||||
onMove={onDrag}
|
||||
onStop={onStop}
|
||||
scale={transform.value[2]}
|
||||
grid={grid.value}
|
||||
enableUserSelectHack={false}
|
||||
>
|
||||
<div class="revue-flow__nodesselection-rect" onContextmenu={onContextMenu} style={innerStyle.value} />
|
||||
</Draggable>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default NodesSelection
|
||||
@@ -1,96 +0,0 @@
|
||||
/**
|
||||
* The user selection rectangle gets displayed when a user drags the mouse while pressing shift
|
||||
*/
|
||||
import { RevueFlowStore, XYPosition } from '../../types'
|
||||
import { computed, defineComponent, inject } from 'vue'
|
||||
import { templateRef, useEventListener } from '@vueuse/core'
|
||||
|
||||
function getMousePosition(event: MouseEvent): XYPosition | void {
|
||||
const revueFlowNode = (event.target as Element).closest('.revue-flow')
|
||||
if (!revueFlowNode) {
|
||||
return
|
||||
}
|
||||
|
||||
const containerBounds = revueFlowNode.getBoundingClientRect()
|
||||
|
||||
return {
|
||||
x: event.clientX - containerBounds.left,
|
||||
y: event.clientY - containerBounds.top
|
||||
}
|
||||
}
|
||||
|
||||
const SelectionRect = (props: { width: number; height: number; x: number; y: number }) => {
|
||||
return (
|
||||
<div
|
||||
class="revue-flow__selection"
|
||||
style={{
|
||||
width: `${props.width}px`,
|
||||
height: `${props.height}px`,
|
||||
transform: `translate(${props.x}px, ${props.y}px)`
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
components: { SelectionRect },
|
||||
setup() {
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
const el = templateRef('user-selection', null)
|
||||
const shouldRender = computed(() => store.selectionActive || store.elementsSelectable)
|
||||
|
||||
const onMouseDown = (event: MouseEvent) => {
|
||||
const mousePos = getMousePosition(event)
|
||||
if (!mousePos) {
|
||||
return
|
||||
}
|
||||
|
||||
store.setUserSelection(mousePos)
|
||||
}
|
||||
|
||||
const onMouseMove = (event: MouseEvent) => {
|
||||
if (!store.selectionActive) {
|
||||
return
|
||||
}
|
||||
const mousePos = getMousePosition(event)
|
||||
if (!mousePos) {
|
||||
return
|
||||
}
|
||||
|
||||
store.updateUserSelection(mousePos)
|
||||
}
|
||||
|
||||
const onMouseUp = () => {
|
||||
store.unsetUserSelection()
|
||||
}
|
||||
|
||||
const onMouseLeave = () => {
|
||||
store.unsetUserSelection()
|
||||
store.unsetNodesSelection()
|
||||
}
|
||||
|
||||
useEventListener(el, 'mousedown', onMouseDown)
|
||||
useEventListener(el, 'mousemove', onMouseMove)
|
||||
useEventListener(el, 'click', onMouseUp)
|
||||
useEventListener(el, 'mouseup', onMouseUp)
|
||||
useEventListener(el, 'mouseleave', onMouseLeave)
|
||||
|
||||
return () =>
|
||||
shouldRender.value ? (
|
||||
<div class="revue-flow__selectionpane" ref="user-selection">
|
||||
{store?.userSelectionRect.draw ? (
|
||||
<SelectionRect
|
||||
width={store.userSelectionRect.width}
|
||||
height={store.userSelectionRect.height}
|
||||
x={store.userSelectionRect.x}
|
||||
y={store.userSelectionRect.y}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
}
|
||||
})
|
||||
7
src/container/index.ts
Normal file
7
src/container/index.ts
Normal file
@@ -0,0 +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'
|
||||
@@ -17,7 +17,7 @@ export default function configureStore(
|
||||
setActivePinia(pinia)
|
||||
|
||||
return defineStore({
|
||||
id: `revue-flow-${Math.random()}`,
|
||||
id: `revue-flow-${Math.floor(Math.random() * 100)}`,
|
||||
state: () => ({
|
||||
...preloadedState,
|
||||
}),
|
||||
|
||||
@@ -41,7 +41,7 @@ export const initialState: RevueFlowState = {
|
||||
connectionNodeId: undefined,
|
||||
connectionHandleId: undefined,
|
||||
connectionHandleType: 'source',
|
||||
connectionPosition: { x: 0, y: 0 },
|
||||
connectionPosition: { x: NaN, y: NaN },
|
||||
connectionMode: ConnectionMode.Strict,
|
||||
|
||||
snapGrid: [15, 15],
|
||||
|
||||
@@ -31,7 +31,7 @@ export interface RevueFlowState {
|
||||
connectionNodeId?: ElementId
|
||||
connectionHandleId?: ElementId
|
||||
connectionHandleType?: HandleType
|
||||
connectionPosition?: XYPosition
|
||||
connectionPosition: XYPosition
|
||||
connectionMode: ConnectionMode
|
||||
|
||||
snapToGrid: boolean
|
||||
|
||||
Reference in New Issue
Block a user