feat: script setup style

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