mirror of
https://gitee.com/dolphinscheduler/DolphinScheduler.git
synced 2024-11-30 19:27:38 +08:00
[Feature][UI Next] Add dag menu (#8481)
* add dag menu * add dag menu click event * workflow online edit not allowed
This commit is contained in:
parent
2a844dcc67
commit
66241fd5c2
@ -684,7 +684,11 @@ const project = {
|
||||
sql_input_placeholder: 'Please enter non-query sql.',
|
||||
sql_empty_tips: 'The sql can not be empty.',
|
||||
procedure_method: 'SQL Statement',
|
||||
procedure_method_tips: 'Please enter the procedure script'
|
||||
procedure_method_tips: 'Please enter the procedure script',
|
||||
start: 'Start',
|
||||
edit: 'Edit',
|
||||
copy: 'Copy',
|
||||
delete: 'Delete'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -676,7 +676,11 @@ const project = {
|
||||
sql_input_placeholder: '请输入非查询SQL语句',
|
||||
sql_empty_tips: '语句不能为空',
|
||||
procedure_method: 'SQL语句',
|
||||
procedure_method_tips: '请输入存储脚本'
|
||||
procedure_method_tips: '请输入存储脚本',
|
||||
start: '运行',
|
||||
edit: '编辑',
|
||||
copy: '复制节点',
|
||||
delete: '删除'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -314,3 +314,20 @@ export const tasksState = (t: any): ITaskState => ({
|
||||
isSpin: false
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* A simple uuid generator, support prefix and template pattern.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* uuid('v-') // -> v-xxx
|
||||
* uuid('v-ani-%{s}-translate') // -> v-ani-xxx
|
||||
*/
|
||||
export function uuid(prefix: string) {
|
||||
const id = Math.floor(Math.random() * 10000).toString(36)
|
||||
return prefix
|
||||
? ~prefix.indexOf('%{s}')
|
||||
? prefix.replace(/%\{s\}/g, id)
|
||||
: prefix + id
|
||||
: id
|
||||
}
|
||||
|
@ -200,7 +200,12 @@ export const NODE = {
|
||||
group: X6_PORT_OUT_NAME
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
tools: [
|
||||
{
|
||||
name: 'contextmenu'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const NODE_HOVER = {
|
||||
|
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { genTaskCodeList } from '@/service/modules/task-definition'
|
||||
import type { Cell } from '@antv/x6'
|
||||
import {
|
||||
defineComponent,
|
||||
onMounted,
|
||||
PropType,
|
||||
inject,
|
||||
ref,
|
||||
computed
|
||||
} from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
import styles from './menu.module.scss'
|
||||
import { uuid } from '@/utils/common'
|
||||
|
||||
const props = {
|
||||
cell: {
|
||||
type: Object as PropType<Cell>,
|
||||
require: true
|
||||
},
|
||||
visible: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true
|
||||
},
|
||||
left: {
|
||||
type: Number as PropType<number>,
|
||||
default: 0
|
||||
},
|
||||
top: {
|
||||
type: Number as PropType<number>,
|
||||
default: 0
|
||||
},
|
||||
releaseState: {
|
||||
type: String as PropType<string>,
|
||||
default: 'OFFLINE'
|
||||
}
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'dag-context-menu',
|
||||
props,
|
||||
emits: ['hide', 'start', 'edit', 'copyTask', 'removeTasks'],
|
||||
setup(props, ctx) {
|
||||
const graph = inject('graph', ref())
|
||||
const route = useRoute()
|
||||
const projectCode = Number(route.params.projectCode)
|
||||
|
||||
const startAvailable = computed(
|
||||
() =>
|
||||
route.name === 'workflow-definition-detail' &&
|
||||
props.releaseState !== 'NOT_RELEASE'
|
||||
)
|
||||
|
||||
const hide = () => {
|
||||
ctx.emit('hide', false)
|
||||
}
|
||||
|
||||
const startRunning = () => {
|
||||
ctx.emit('start')
|
||||
}
|
||||
|
||||
const handleEdit = () => {
|
||||
ctx.emit('edit', Number(props.cell?.id))
|
||||
}
|
||||
|
||||
const handleCopy = () => {
|
||||
const genNums = 1
|
||||
const type = props.cell?.data.taskType
|
||||
const taskName = uuid(props.cell?.data.taskName + '_')
|
||||
const targetCode = Number(props.cell?.id)
|
||||
|
||||
genTaskCodeList(genNums, projectCode).then((res) => {
|
||||
const [code] = res
|
||||
ctx.emit('copyTask', taskName, code, targetCode, type, {
|
||||
x: props.left + 100,
|
||||
y: props.top + 100
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = () => {
|
||||
graph.value?.removeCell(props.cell)
|
||||
ctx.emit('removeTasks', [Number(props.cell?.id)])
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', () => {
|
||||
hide()
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
startAvailable,
|
||||
startRunning,
|
||||
handleEdit,
|
||||
handleCopy,
|
||||
handleDelete
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { t } = useI18n()
|
||||
|
||||
return (
|
||||
this.visible && (
|
||||
<div
|
||||
class={styles['dag-context-menu']}
|
||||
style={{ left: `${this.left}px`, top: `${this.top}px` }}
|
||||
>
|
||||
<div
|
||||
class={`${styles['menu-item']} ${
|
||||
!this.startAvailable ? styles['disabled'] : ''
|
||||
} `}
|
||||
onClick={this.startRunning}
|
||||
>
|
||||
{t('project.node.start')}
|
||||
</div>
|
||||
<div
|
||||
class={`${styles['menu-item']} ${
|
||||
this.releaseState === 'ONLINE' ? styles['disabled'] : ''
|
||||
} `}
|
||||
onClick={this.handleEdit}
|
||||
>
|
||||
{t('project.node.edit')}
|
||||
</div>
|
||||
<div
|
||||
class={`${styles['menu-item']} ${
|
||||
this.releaseState === 'ONLINE' ? styles['disabled'] : ''
|
||||
} `}
|
||||
onClick={this.handleCopy}
|
||||
>
|
||||
{t('project.node.copy')}
|
||||
</div>
|
||||
<div
|
||||
class={`${styles['menu-item']} ${
|
||||
this.releaseState === 'ONLINE' ? styles['disabled'] : ''
|
||||
} `}
|
||||
onClick={this.handleDelete}
|
||||
>
|
||||
{t('project.node.delete')}
|
||||
</div>
|
||||
{/* TODO: view log */}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
@ -26,6 +26,7 @@ import { useCustomCellBuilder } from './use-custom-cell-builder'
|
||||
import { useGraphBackfill } from './use-graph-backfill'
|
||||
import { useDagDragAndDrop } from './use-dag-drag-drop'
|
||||
import { useTaskEdit } from './use-task-edit'
|
||||
import { useNodeMenu } from './use-node-menu'
|
||||
|
||||
export {
|
||||
useCanvasInit,
|
||||
@ -38,5 +39,6 @@ export {
|
||||
useGraphBackfill,
|
||||
useCellUpdate,
|
||||
useDagDragAndDrop,
|
||||
useTaskEdit
|
||||
useTaskEdit,
|
||||
useNodeMenu
|
||||
}
|
||||
|
@ -27,13 +27,16 @@ import {
|
||||
useGraphBackfill,
|
||||
useDagDragAndDrop,
|
||||
useTaskEdit,
|
||||
useBusinessMapper
|
||||
useBusinessMapper,
|
||||
useNodeMenu
|
||||
} from './dag-hooks'
|
||||
import { useThemeStore } from '@/store/theme/theme'
|
||||
import VersionModal from '../../definition/components/version-modal'
|
||||
import { WorkflowDefinition } from './types'
|
||||
import DagSaveModal from './dag-save-modal'
|
||||
import TaskModal from '@/views/projects/task/components/node/detail-modal'
|
||||
import StartModal from '@/views/projects/workflow/definition/components/start-modal'
|
||||
import ContextMenuItem from './dag-context-menu'
|
||||
import './x6-style.scss'
|
||||
|
||||
const props = {
|
||||
@ -82,10 +85,25 @@ export default defineComponent({
|
||||
currTask,
|
||||
taskCancel,
|
||||
appendTask,
|
||||
editTask,
|
||||
copyTask,
|
||||
taskDefinitions,
|
||||
removeTasks
|
||||
} = useTaskEdit({ graph, definition: toRef(props, 'definition') })
|
||||
|
||||
// Right click cell
|
||||
const {
|
||||
menuCell,
|
||||
pageX,
|
||||
pageY,
|
||||
menuVisible,
|
||||
startModalShow,
|
||||
menuHide,
|
||||
menuStart
|
||||
} = useNodeMenu({
|
||||
graph
|
||||
})
|
||||
|
||||
const { onDragStart, onDrop } = useDagDragAndDrop({
|
||||
graph,
|
||||
readonly: toRef(props, 'readonly'),
|
||||
@ -177,6 +195,24 @@ export default defineComponent({
|
||||
onSubmit={taskConfirm}
|
||||
onCancel={taskCancel}
|
||||
/>
|
||||
<ContextMenuItem
|
||||
cell={menuCell.value}
|
||||
visible={menuVisible.value}
|
||||
left={pageX.value}
|
||||
top={pageY.value}
|
||||
releaseState={props.definition?.processDefinition.releaseState}
|
||||
onHide={menuHide}
|
||||
onStart={menuStart}
|
||||
onEdit={editTask}
|
||||
onCopyTask={copyTask}
|
||||
onRemoveTasks={removeTasks}
|
||||
/>
|
||||
{!!props.definition && (
|
||||
<StartModal
|
||||
v-model:row={props.definition.processDefinition}
|
||||
v-model:show={startModalShow.value}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.dag-context-menu{
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.12);
|
||||
|
||||
.menu-item{
|
||||
padding: 5px 10px;
|
||||
border-bottom: solid 1px #f2f3f7;
|
||||
cursor: pointer;
|
||||
color: rgb(89, 89, 89);
|
||||
font-size: 12px;
|
||||
|
||||
&:hover:not(.disabled){
|
||||
color: #262626;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
&.disabled{
|
||||
cursor: not-allowed;
|
||||
color: rgba(89, 89, 89, .4);
|
||||
}
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ import { Graph } from '@antv/x6'
|
||||
import { NODE, EDGE, X6_NODE_NAME, X6_EDGE_NAME } from './dag-config'
|
||||
import { debounce } from 'lodash'
|
||||
import { useResizeObserver } from '@vueuse/core'
|
||||
import ContextMenuTool from './dag-context-menu'
|
||||
|
||||
interface Options {
|
||||
readonly: Ref<boolean>
|
||||
@ -45,6 +46,8 @@ export function useCanvasInit(options: Options) {
|
||||
* Graph Init, bind graph to the dom
|
||||
*/
|
||||
function graphInit() {
|
||||
Graph.registerNodeTool('contextmenu', ContextMenuTool, true)
|
||||
|
||||
return new Graph({
|
||||
container: paper.value,
|
||||
selecting: {
|
||||
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Ref } from 'vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import type { Graph, Cell } from '@antv/x6'
|
||||
|
||||
interface Options {
|
||||
graph: Ref<Graph | undefined>
|
||||
}
|
||||
|
||||
/**
|
||||
* Get position of the right-clicked Cell.
|
||||
*/
|
||||
export function useNodeMenu(options: Options) {
|
||||
const { graph } = options
|
||||
const startModalShow = ref(false)
|
||||
const menuVisible = ref(false)
|
||||
const pageX = ref()
|
||||
const pageY = ref()
|
||||
const menuCell = ref<Cell>()
|
||||
|
||||
const menuHide = () => {
|
||||
menuVisible.value = false
|
||||
|
||||
// unlock scroller
|
||||
graph.value?.unlockScroller()
|
||||
}
|
||||
|
||||
const menuStart = () => {
|
||||
startModalShow.value = true
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (graph.value) {
|
||||
// contextmenu
|
||||
graph.value.on('node:contextmenu', ({ cell, x, y }) => {
|
||||
menuCell.value = cell
|
||||
const data = graph.value?.localToPage(x, y)
|
||||
pageX.value = data?.x
|
||||
pageY.value = data?.y
|
||||
|
||||
// show menu
|
||||
menuVisible.value = true
|
||||
|
||||
// lock scroller
|
||||
graph.value?.lockScroller()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
pageX,
|
||||
pageY,
|
||||
startModalShow,
|
||||
menuVisible,
|
||||
menuCell,
|
||||
menuHide,
|
||||
menuStart
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash'
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import type { Graph } from '@antv/x6'
|
||||
@ -60,6 +61,28 @@ export function useTaskEdit(options: Options) {
|
||||
openTaskModal({ code, taskType: type, name: '' })
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a task
|
||||
*/
|
||||
function copyTask(
|
||||
name: string,
|
||||
code: number,
|
||||
targetCode: number,
|
||||
type: TaskType,
|
||||
coordinate: Coordinate
|
||||
) {
|
||||
addNode(code + '', type, name, coordinate)
|
||||
const definition = taskDefinitions.value.find((t) => t.code === targetCode)
|
||||
|
||||
const newDefinition = {
|
||||
...definition,
|
||||
code,
|
||||
name
|
||||
} as NodeData
|
||||
|
||||
taskDefinitions.value.push(newDefinition)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove task
|
||||
* @param {number} code
|
||||
@ -75,6 +98,18 @@ export function useTaskEdit(options: Options) {
|
||||
taskModalVisible.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit task
|
||||
* @param {number} code
|
||||
*/
|
||||
function editTask(code: number) {
|
||||
const definition = taskDefinitions.value.find((t) => t.code === code)
|
||||
if (definition) {
|
||||
currTask.value = definition
|
||||
}
|
||||
taskModalVisible.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* The confirm event in task config modal
|
||||
* @param formRef
|
||||
@ -108,11 +143,7 @@ export function useTaskEdit(options: Options) {
|
||||
if (graph.value) {
|
||||
graph.value.on('cell:dblclick', ({ cell }) => {
|
||||
const code = Number(cell.id)
|
||||
const definition = taskDefinitions.value.find((t) => t.code === code)
|
||||
if (definition) {
|
||||
currTask.value = definition
|
||||
}
|
||||
taskModalVisible.value = true
|
||||
editTask(code)
|
||||
})
|
||||
}
|
||||
})
|
||||
@ -127,6 +158,8 @@ export function useTaskEdit(options: Options) {
|
||||
taskConfirm,
|
||||
taskCancel,
|
||||
appendTask,
|
||||
editTask,
|
||||
copyTask,
|
||||
taskDefinitions,
|
||||
removeTasks
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user