update(script-setup): Refactor additional-components
* Remove jsx files fix: ConnectionLine.vue not recalculating properly
This commit is contained in:
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user