jiaxing.liao 1 settimana fa
parent
commit
ca6b92e920

+ 1 - 0
apps/web/package.json

@@ -16,6 +16,7 @@
     "vue-router": "4"
   },
   "devDependencies": {
+    "@repo/nodes": "workspace:*",
     "@repo/workflow": "workspace:*",
     "@vitejs/plugin-vue": "^6.0.1",
     "@vue/tsconfig": "^0.8.1",

+ 1 - 1
packages/ui/components/icon-button/IconButton.vue

@@ -9,7 +9,7 @@ import { ElButton } from 'element-plus'
 import Icon from '../icon/Icon.vue'
 
 withDefaults(
-	defineOptions<{
+	defineProps<{
 		icon: string
 		iconColor?: string
 		size?: 'small' | 'medium' | 'large'

+ 1 - 1
packages/workflow/src/Interface.ts

@@ -7,7 +7,7 @@ export type CanvasConnectionPort = {
 	required?: boolean
 	maxConnections?: number
 	label?: string
-    [key: string]: any
+	[key: string]: any
 }
 
 export interface CanvasElementPortWithRenderData extends CanvasConnectionPort {

+ 71 - 56
packages/workflow/src/components/elements/CanvasNode.vue

@@ -3,17 +3,17 @@ import { computed } from 'vue'
 import { Position } from '@vue-flow/core'
 import type { NodeProps } from '@vue-flow/core'
 import type {
-    IWorkflowNode,
-    CanvasConnectionPort,
-    CanvasElementPortWithRenderData
+	IWorkflowNode,
+	CanvasConnectionPort,
+	CanvasElementPortWithRenderData
 } from '../../Interface'
 
 import { Icon } from '@repo/ui'
 import CanvasHandle from './handles/CanvasHandle.vue'
 
 type Props = NodeProps<IWorkflowNode['data']> & {
-    readOnly?: boolean
-    hovered?: boolean
+	readOnly?: boolean
+	hovered?: boolean
 }
 
 const props = defineProps<Props>()
@@ -22,76 +22,91 @@ const props = defineProps<Props>()
  * 处理节点
  */
 const createEndpoint = (data: {
-    port: CanvasConnectionPort
-    index: number
-    count: number
-    offsetAxis: 'top' | 'left'
-    position: Position
+	port: CanvasConnectionPort
+	index: number
+	count: number
+	offsetAxis: 'top' | 'left'
+	position: Position
 }): CanvasElementPortWithRenderData => {
-    const { port, index, count, offsetAxis, position } = data
+	const { port, index, count, offsetAxis, position } = data
 
-    return {
-        ...port,
-        handleId: `${port.type}-${index}`,
-        position,
-        connectionsCount: count,
-        isConnecting: false,
-        offset: {
-            [offsetAxis]: `${(100 / (count + 1)) * (index + 1)}%`
-        }
-    }
+	return {
+		...port,
+		handleId: `${port.type}-${index}`,
+		position,
+		connectionsCount: count,
+		isConnecting: false,
+		offset: {
+			[offsetAxis]: `${(100 / (count + 1)) * (index + 1)}%`
+		}
+	}
 }
 
 /**
  * Inputs
  */
 const inputs = computed(() =>
-    (props.data.inputs || []).map((target, index) =>
-        createEndpoint({
-            port: target,
-            index,
-            count: props.data.inputs.length,
-            offsetAxis: 'top',
-            position: Position.Left
-        })
-    )
+	(props.data.inputs || []).map((target, index) =>
+		createEndpoint({
+			port: target,
+			index,
+			count: props.data.inputs.length,
+			offsetAxis: 'top',
+			position: Position.Left
+		})
+	)
 )
 
 /**
  * Outputs
  */
 const outputs = computed(() =>
-    (props.data.outputs || []).map((source, index) =>
-        createEndpoint({
-            port: source,
-            index,
-            count: props.data.outputs.length,
-            offsetAxis: 'top',
-            position: Position.Right
-        })
-    )
+	(props.data.outputs || []).map((source, index) =>
+		createEndpoint({
+			port: source,
+			index,
+			count: props.data.outputs.length,
+			offsetAxis: 'top',
+			position: Position.Right
+		})
+	)
 )
+
+const nodeClass = computed(() => {
+	let classes: string[] = []
+	if (props.selected) {
+		classes.push('ring-6px', 'ring-#e0e2e7')
+	}
+	if (inputs.value.length === 0) {
+		classes.push('rounded-l-36px')
+	}
+
+	return classes
+})
 </script>
 
 <template>
-    <div class="w-full h-full bg-#fff box-border border-2 border-solid border-#dcdcdc rounded-4px relative">
-        <div class="w-full h-full relative flex items-center justify-center">
-            <Icon :icon="data?.icon" height="40" width="40" :color="data?.iconColor" />
-        </div>
+	<div
+		class="w-full h-full bg-#fff box-border border-2 border-solid border-#dcdcdc rounded-8px relative"
+		:class="nodeClass"
+	>
+		<div className="w-full h-full relative flex items-center justify-center">
+			<Icon :icon="data?.icon" height="40" width="40" :color="data?.iconColor" />
+		</div>
 
-        <div class="absolute w-full bottom--22px text-12px text-center text-#222">
-            {{ data?.displayName }}
-        </div>
-        <div class="absolute w-full bottom--38px text-12px text-center text-#999 truncate">
-            {{ data.subtitle }}
-        </div>
+		<div className="absolute w-full bottom--24px text-12px text-center text-#222">
+			{{ data?.displayName }}
+		</div>
+		<div className="absolute w-full bottom--40px text-12px text-center text-#999 truncate">
+			{{ data.subtitle }}
+		</div>
 
-        <template v-for="target in inputs" :key="'handle-inputs-port' + target.index">
-            <CanvasHandle v-bind="target" type="target" />
-        </template>
+		<template v-for="target in inputs" :key="'handle-inputs-port' + target.index">
+			<CanvasHandle v-bind="target" type="target" />
+		</template>
 
-        <template v-for="source in outputs" :key="'handle-outputs-port' + source.index">
-            <CanvasHandle v-bind="source" type="source" />
-        </template>
-    </div>
+		<template v-for="source in outputs" :key="'handle-outputs-port' + source.index">
+			<CanvasHandle v-bind="source" type="source" />
+		</template>
+	</div>
 </template>

+ 24 - 6
packages/workflow/src/components/elements/handles/CanvasHandle.vue

@@ -1,17 +1,35 @@
 <script lang="ts" setup>
+import { computed } from 'vue'
 import { Handle, type Position, type ValidConnectionFunc } from '@vue-flow/core'
 import HandlePort from './HandlePort.vue'
 
-defineProps<{
+const props = defineProps<{
 	handleId: string
 	handleClasses?: string | string[]
-	handleType: 'source' | 'target'
 	position: Position
 	offset?: Record<string, string>
-	isConnectableStart?: boolean
-	isConnectableEnd?: boolean
 	isValidConnection?: ValidConnectionFunc
+	type: 'source' | 'target'
+	label?: string
+	maxConnections?: number
+	connectionsCount: number
 }>()
+
+const connectionsLimitReached = computed(() => {
+	return props.maxConnections && props.connectionsCount >= props.maxConnections
+})
+
+const isConnectableStart = computed(() => {
+	if (connectionsLimitReached.value) return false
+
+	return props.type === 'source'
+})
+
+const isConnectableEnd = computed(() => {
+	if (connectionsLimitReached.value) return false
+
+	return props.type === 'target'
+})
 </script>
 
 <template>
@@ -19,14 +37,14 @@ defineProps<{
 		v-bind="$attrs"
 		:id="handleId"
 		:class="$style.handle"
-		:type="handleType"
+		:type="type"
 		:position="position"
 		:style="offset"
 		:connectable-start="isConnectableStart"
 		:connectable-end="isConnectableEnd"
 		:is-valid-connection="isValidConnection"
 	>
-		<HandlePort :position="position" :type="handleType" />
+		<HandlePort :position="position" :type="type" />
 	</Handle>
 </template>