mirror of
https://github.com/Powerful-517/yys-editor.git
synced 2026-03-05 15:05:27 +00:00
feat: add flow capability levels and plugin injection API
This commit is contained in:
@@ -41,7 +41,6 @@ import { ref, computed, watch, onMounted, onBeforeUnmount, provide } from 'vue'
|
|||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import LogicFlow from '@logicflow/core'
|
import LogicFlow from '@logicflow/core'
|
||||||
import '@logicflow/core/lib/style/index.css'
|
import '@logicflow/core/lib/style/index.css'
|
||||||
import { Snapshot, MiniMap, Control } from '@logicflow/extension'
|
|
||||||
import '@logicflow/extension/lib/style/index.css'
|
import '@logicflow/extension/lib/style/index.css'
|
||||||
|
|
||||||
import FlowEditor from './components/flow/FlowEditor.vue'
|
import FlowEditor from './components/flow/FlowEditor.vue'
|
||||||
@@ -49,13 +48,13 @@ import Toolbar from './components/Toolbar.vue'
|
|||||||
import ComponentsPanel from './components/flow/ComponentsPanel.vue'
|
import ComponentsPanel from './components/flow/ComponentsPanel.vue'
|
||||||
import { useFilesStore } from '@/ts/useStore'
|
import { useFilesStore } from '@/ts/useStore'
|
||||||
import { setLogicFlowInstance, destroyLogicFlowInstance, getLogicFlowInstance } from '@/ts/useLogicFlow'
|
import { setLogicFlowInstance, destroyLogicFlowInstance, getLogicFlowInstance } from '@/ts/useLogicFlow'
|
||||||
import { register } from '@logicflow/vue-node-registry'
|
import {
|
||||||
import ImageNode from './components/flow/nodes/common/ImageNode.vue'
|
registerFlowNodes,
|
||||||
import AssetSelectorNode from './components/flow/nodes/common/AssetSelectorNode.vue'
|
resolveFlowPlugins,
|
||||||
import TextNode from './components/flow/nodes/common/TextNode.vue'
|
type FlowCapabilityLevel,
|
||||||
import TextNodeModel from './components/flow/nodes/common/TextNodeModel'
|
type FlowNodeRegistration,
|
||||||
import VectorNode from './components/flow/nodes/common/VectorNode.vue'
|
type FlowPlugin
|
||||||
import VectorNodeModel from './components/flow/nodes/common/VectorNodeModel'
|
} from './flowRuntime'
|
||||||
|
|
||||||
// 类型定义
|
// 类型定义
|
||||||
export interface GraphData {
|
export interface GraphData {
|
||||||
@@ -92,12 +91,15 @@ export interface EditorConfig {
|
|||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
data?: GraphData
|
data?: GraphData
|
||||||
mode?: 'preview' | 'edit'
|
mode?: 'preview' | 'edit'
|
||||||
|
capability?: FlowCapabilityLevel
|
||||||
width?: string | number
|
width?: string | number
|
||||||
height?: string | number
|
height?: string | number
|
||||||
showToolbar?: boolean
|
showToolbar?: boolean
|
||||||
showPropertyPanel?: boolean
|
showPropertyPanel?: boolean
|
||||||
showComponentPanel?: boolean
|
showComponentPanel?: boolean
|
||||||
config?: EditorConfig
|
config?: EditorConfig
|
||||||
|
plugins?: FlowPlugin[]
|
||||||
|
nodeRegistrations?: FlowNodeRegistration[]
|
||||||
}>(), {
|
}>(), {
|
||||||
mode: 'edit',
|
mode: 'edit',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -132,6 +134,13 @@ const previewContainerRef = ref<HTMLElement | null>(null)
|
|||||||
const previewLf = ref<LogicFlow | null>(null)
|
const previewLf = ref<LogicFlow | null>(null)
|
||||||
|
|
||||||
// Computed
|
// Computed
|
||||||
|
const effectiveCapability = computed<FlowCapabilityLevel>(() => {
|
||||||
|
if (props.capability) {
|
||||||
|
return props.capability
|
||||||
|
}
|
||||||
|
return props.mode === 'preview' ? 'render-only' : 'interactive'
|
||||||
|
})
|
||||||
|
|
||||||
const containerStyle = computed(() => ({
|
const containerStyle = computed(() => ({
|
||||||
width: typeof props.width === 'number' ? `${props.width}px` : props.width,
|
width: typeof props.width === 'number' ? `${props.width}px` : props.width,
|
||||||
height: typeof props.height === 'number' ? `${props.height}px` : props.height
|
height: typeof props.height === 'number' ? `${props.height}px` : props.height
|
||||||
@@ -150,10 +159,20 @@ const contentHeight = computed(() => {
|
|||||||
return containerHeight.value
|
return containerHeight.value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const destroyPreviewMode = () => {
|
||||||
|
if (previewLf.value) {
|
||||||
|
previewLf.value.destroy()
|
||||||
|
previewLf.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化预览模式的 LogicFlow
|
// 初始化预览模式的 LogicFlow
|
||||||
const initPreviewMode = () => {
|
const initPreviewMode = () => {
|
||||||
if (!previewContainerRef.value) return
|
if (!previewContainerRef.value) return
|
||||||
|
|
||||||
|
destroyPreviewMode()
|
||||||
|
const isRenderOnly = effectiveCapability.value === 'render-only'
|
||||||
|
|
||||||
// 创建 LogicFlow 实例(只读模式)
|
// 创建 LogicFlow 实例(只读模式)
|
||||||
previewLf.value = new LogicFlow({
|
previewLf.value = new LogicFlow({
|
||||||
container: previewContainerRef.value,
|
container: previewContainerRef.value,
|
||||||
@@ -161,36 +180,19 @@ const initPreviewMode = () => {
|
|||||||
height: previewContainerRef.value.offsetHeight,
|
height: previewContainerRef.value.offsetHeight,
|
||||||
grid: false,
|
grid: false,
|
||||||
keyboard: {
|
keyboard: {
|
||||||
enabled: false
|
enabled: !isRenderOnly
|
||||||
},
|
},
|
||||||
// 禁用所有交互
|
// render-only 模式禁用所有交互能力
|
||||||
isSilentMode: true,
|
isSilentMode: isRenderOnly,
|
||||||
stopScrollGraph: true,
|
stopScrollGraph: isRenderOnly,
|
||||||
stopZoomGraph: true,
|
stopZoomGraph: isRenderOnly,
|
||||||
stopMoveGraph: true,
|
stopMoveGraph: isRenderOnly,
|
||||||
adjustNodePosition: false,
|
adjustNodePosition: !isRenderOnly,
|
||||||
plugins: [Snapshot, MiniMap, Control]
|
plugins: resolveFlowPlugins(effectiveCapability.value, props.plugins)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 注册自定义节点(必须在 LogicFlow 实例创建后)
|
// 注册节点(支持外部注入)
|
||||||
register({
|
registerFlowNodes(previewLf.value, props.nodeRegistrations)
|
||||||
type: 'imageNode',
|
|
||||||
component: ImageNode
|
|
||||||
}, previewLf.value)
|
|
||||||
register({
|
|
||||||
type: 'assetSelector',
|
|
||||||
component: AssetSelectorNode
|
|
||||||
}, previewLf.value)
|
|
||||||
register({
|
|
||||||
type: 'textNode',
|
|
||||||
component: TextNode,
|
|
||||||
model: TextNodeModel
|
|
||||||
}, previewLf.value)
|
|
||||||
register({
|
|
||||||
type: 'vectorNode',
|
|
||||||
component: VectorNode,
|
|
||||||
model: VectorNodeModel
|
|
||||||
}, previewLf.value)
|
|
||||||
|
|
||||||
// 渲染数据
|
// 渲染数据
|
||||||
if (props.data) {
|
if (props.data) {
|
||||||
@@ -257,9 +259,23 @@ watch(() => props.mode, (newMode) => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
initPreviewMode()
|
initPreviewMode()
|
||||||
}, 100)
|
}, 100)
|
||||||
|
} else {
|
||||||
|
destroyPreviewMode()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[() => props.capability, () => props.plugins, () => props.nodeRegistrations],
|
||||||
|
() => {
|
||||||
|
if (props.mode === 'preview') {
|
||||||
|
setTimeout(() => {
|
||||||
|
initPreviewMode()
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.mode === 'preview') {
|
if (props.mode === 'preview') {
|
||||||
@@ -277,10 +293,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
// 清理
|
// 清理
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (previewLf.value) {
|
destroyPreviewMode()
|
||||||
previewLf.value.destroy()
|
|
||||||
previewLf.value = null
|
|
||||||
}
|
|
||||||
destroyLogicFlowInstance()
|
destroyLogicFlowInstance()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
63
src/flowRuntime.ts
Normal file
63
src/flowRuntime.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import type LogicFlow from '@logicflow/core'
|
||||||
|
import { Menu, Label, Snapshot, SelectionSelect, MiniMap, Control } from '@logicflow/extension'
|
||||||
|
import { register } from '@logicflow/vue-node-registry'
|
||||||
|
|
||||||
|
import ImageNode from './components/flow/nodes/common/ImageNode.vue'
|
||||||
|
import AssetSelectorNode from './components/flow/nodes/common/AssetSelectorNode.vue'
|
||||||
|
import TextNode from './components/flow/nodes/common/TextNode.vue'
|
||||||
|
import TextNodeModel from './components/flow/nodes/common/TextNodeModel'
|
||||||
|
import VectorNode from './components/flow/nodes/common/VectorNode.vue'
|
||||||
|
import VectorNodeModel from './components/flow/nodes/common/VectorNodeModel'
|
||||||
|
|
||||||
|
export type FlowCapabilityLevel = 'render-only' | 'interactive'
|
||||||
|
|
||||||
|
export interface FlowNodeRegistration {
|
||||||
|
type: string
|
||||||
|
component?: any
|
||||||
|
model?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FlowPlugin = any
|
||||||
|
|
||||||
|
const DEFAULT_FLOW_NODES: FlowNodeRegistration[] = [
|
||||||
|
{ type: 'imageNode', component: ImageNode },
|
||||||
|
{ type: 'assetSelector', component: AssetSelectorNode },
|
||||||
|
{ type: 'textNode', component: TextNode, model: TextNodeModel },
|
||||||
|
{ type: 'vectorNode', component: VectorNode, model: VectorNodeModel }
|
||||||
|
]
|
||||||
|
|
||||||
|
const FLOW_PLUGIN_PRESETS: Record<FlowCapabilityLevel, FlowPlugin[]> = {
|
||||||
|
'render-only': [Snapshot],
|
||||||
|
interactive: [Menu, Label, Snapshot, SelectionSelect, MiniMap, Control]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFlowPluginsByCapability(capability: FlowCapabilityLevel): FlowPlugin[] {
|
||||||
|
return [...FLOW_PLUGIN_PRESETS[capability]]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveFlowPlugins(
|
||||||
|
capability: FlowCapabilityLevel,
|
||||||
|
plugins?: FlowPlugin[]
|
||||||
|
): FlowPlugin[] {
|
||||||
|
if (Array.isArray(plugins) && plugins.length > 0) {
|
||||||
|
return plugins
|
||||||
|
}
|
||||||
|
return getFlowPluginsByCapability(capability)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDefaultFlowNodes(): FlowNodeRegistration[] {
|
||||||
|
return [...DEFAULT_FLOW_NODES]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveFlowNodes(nodes?: FlowNodeRegistration[]): FlowNodeRegistration[] {
|
||||||
|
if (Array.isArray(nodes) && nodes.length > 0) {
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
return getDefaultFlowNodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerFlowNodes(lfInstance: LogicFlow, nodes?: FlowNodeRegistration[]) {
|
||||||
|
const registrations = resolveFlowNodes(nodes)
|
||||||
|
registrations.forEach((registration) => register(registration, lfInstance))
|
||||||
|
}
|
||||||
|
|
||||||
@@ -10,3 +10,4 @@ export default YysEditorEmbed
|
|||||||
|
|
||||||
// 类型导出
|
// 类型导出
|
||||||
export * from './YysEditorEmbed.vue'
|
export * from './YysEditorEmbed.vue'
|
||||||
|
export * from './flowRuntime'
|
||||||
|
|||||||
Reference in New Issue
Block a user