アクションフローの不具合改修

This commit is contained in:
2024-02-26 12:20:31 +09:00
parent d1ec123c8b
commit 72608a8ffd
5 changed files with 1344 additions and 60 deletions

1137
backend/log/api.log.1 Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="row justify-center" :style="{ marginLeft: node.inputPoint !== '' ? '240px' : '' }" > <div class="row justify-center no-wrap" >
<div class="row"> <div class="row">
<q-card class="action-node" :class="nodeStyle" :square="false" @click="onNodeClick" > <q-card class="action-node" :class="nodeStyle" :square="false" @click="onNodeClick" >
<q-toolbar class="col" > <q-toolbar class="col" >
@@ -29,7 +29,7 @@
</q-btn> </q-btn>
</q-toolbar> </q-toolbar>
<q-separator /> <q-separator />
<q-card-section> <q-card-section class="action-title">
<div class="row"> <div class="row">
<span class="text-h7">{{ node.title }}</span> <span class="text-h7">{{ node.title }}</span>
<q-space></q-space> <q-space></q-space>
@@ -48,23 +48,34 @@
</div> </div>
</div> </div>
<template v-if="hasBranch"> <template v-if="hasBranch">
<div class="row justify-center" :style="{ marginLeft: node.inputPoint !== '' ? '240px' : '' }"> <node-line :action-node="node" @addNode="addNode" :left-columns="leftColumns" :right-columns="rightColumns"></node-line>
<div v-for="(point, index) in node.outputPoints" :key="index"> <div class="row justify-center no-wrap" >
<node-line :action-node="node" :mode="getMode(point)" @addNode="addNode" :input-point="point"></node-line> <div v-for="(point, index) in node.outputPoints" :key="index" class="column" style="min-width: 300px;">
<div class="justify-center" >
<node-item v-if="nextNode(point)!==undefined" :key="nextNode(point).id" :isSelected="nextNode(point) === store.activeNode"
:actionNode="nextNode(point)" @addNode="addNodeFromItem" @nodeSelected="onNodeSelected" @nodeEdit="onNodeEdit"
@deleteNode="onDeleteNodeFromItem" @deleteAllNextNodes="onDeleteAllNextNodes" ></node-item>
</div>
</div> </div>
</div> </div>
</template> </template>
<template v-if="!hasBranch"> <template v-if="!hasBranch">
<div class="row justify-center" :style="{ marginLeft: node.inputPoint !== '' ? '240px' : '' }"> <div class="row justify-center no-wrap" >
<node-line :action-node="node" :mode="getMode('')" @addNode="addNode" input-point=""></node-line> <node-line :action-node="node" @addNode="addNode" ></node-line>
</div>
<div>
<node-item v-if="nextNode('')!==undefined" :key="nextNode('').id" :isSelected="nextNode('') === store.activeNode"
:actionNode="nextNode('')" @addNode="addNodeFromItem" @nodeSelected="onNodeSelected" @nodeEdit="onNodeEdit"
@deleteNode="onDeleteNodeFromItem" @deleteAllNextNodes="onDeleteAllNextNodes" ></node-item>
</div> </div>
</template> </template>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, computed, ref } from 'vue'; import { defineComponent, computed, ref } from 'vue';
import { IActionNode } from '../../types/ActionTypes'; import { IActionNode, IActionProperty } from '../../types/ActionTypes';
import NodeLine, { Direction } from '../main/NodeLine.vue'; import NodeLine, { Direction } from '../main/NodeLine.vue';
import { useFlowEditorStore } from 'stores/flowEditor';
export default defineComponent({ export default defineComponent({
name: 'NodeItem', name: 'NodeItem',
components: { components: {
@@ -88,6 +99,7 @@ export default defineComponent({
"copyFlow" "copyFlow"
], ],
setup(props, context) { setup(props, context) {
const store = useFlowEditorStore();
const hasBranch = computed(() => props.actionNode.outputPoints.length > 0); const hasBranch = computed(() => props.actionNode.outputPoints.length > 0);
const nodeStyle = computed(() => { const nodeStyle = computed(() => {
return { return {
@@ -96,23 +108,11 @@ export default defineComponent({
'selected': props.isSelected && !props.actionNode.isRoot 'selected': props.isSelected && !props.actionNode.isRoot
}; };
}); });
const getMode = (point: string) => {
if (point === '' || props.actionNode.outputPoints.length === 0) { const nextNode=(point:string)=>{
return Direction.Default; const nextId= props.actionNode.nextNodeIds.get(point);
} if(!nextId) return undefined;
if (point === props.actionNode.outputPoints[0]) { return store.currentFlow?.findNodeById(nextId);
if (props.actionNode.nextNodeIds.get(point)) {
return Direction.Left;
} else {
return Direction.LeftNotNext;
}
} else {
if (props.actionNode.nextNodeIds.get(point)) {
return Direction.Right;
} else {
return Direction.RightNotNext;
}
}
} }
/** /**
* アクションノード追加イベントを * アクションノード追加イベントを
@@ -121,6 +121,38 @@ export default defineComponent({
const addNode = (point: string) => { const addNode = (point: string) => {
context.emit('addNode', props.actionNode, point); context.emit('addNode', props.actionNode, point);
} }
/**
* アクションノード追加イベントを
* @param point 入力ポイント
*/
const addNodeFromItem = (node:IActionNode,point: string) => {
context.emit('addNode', node, point);
}
const leftColumns=computed(()=>{
if(!props.actionNode.outputPoints || props.actionNode.outputPoints.length<2){
return 1;
}
const leftNode = nextNode(props.actionNode.outputPoints[0]);
if(leftNode){
return store.currentFlow?.getColumns(leftNode);
}else{
return 1;
}
});
const rightColumns=computed(()=>{
if(!props.actionNode.outputPoints || props.actionNode.outputPoints.length<2){
return 1;
}
const rightNode = nextNode(props.actionNode.outputPoints[1]);
if(rightNode){
return store.currentFlow?.getColumns(rightNode);
}else{
return 1;
}
});
/** /**
* ノード選択状態 * ノード選択状態
*/ */
@@ -128,9 +160,20 @@ export default defineComponent({
context.emit('nodeSelected', props.actionNode); context.emit('nodeSelected', props.actionNode);
} }
const onNodeSelected = (node: IActionNode) => {
context.emit('nodeSelected', node);
}
const onEditNode=()=>{ const onEditNode=()=>{
context.emit('nodeEdit', props.actionNode); context.emit('nodeEdit', props.actionNode);
} }
const onNodeEdit=(node:IActionNode)=>{
context.emit('nodeEdit', node);
}
/** /**
* ノードを削除する * ノードを削除する
*/ */
@@ -138,12 +181,25 @@ export default defineComponent({
context.emit('deleteNode', props.actionNode); context.emit('deleteNode', props.actionNode);
} }
/**
* ノードを削除する
*/
const onDeleteNodeFromItem=(node:IActionNode)=>{
context.emit('deleteNode', node);
}
/** /**
* ノードの以下すべて削除する * ノードの以下すべて削除する
*/ */
const onDeleteAllNode=()=>{ const onDeleteAllNode=()=>{
context.emit('deleteAllNextNodes', props.actionNode); context.emit('deleteAllNextNodes', props.actionNode);
}; };
/**
* ノードの以下すべて削除する
*/
const onDeleteAllNextNodes=(node:IActionNode)=>{
context.emit('deleteAllNextNodes', node);
};
/** /**
* 変数名取得 * 変数名取得
*/ */
@@ -155,25 +211,38 @@ export default defineComponent({
context.emit('copyFlow', props.actionNode); context.emit('copyFlow', props.actionNode);
} }
return { return {
store,
node: props.actionNode, node: props.actionNode,
nextNode,
isRoot: props.actionNode.isRoot, isRoot: props.actionNode.isRoot,
hasBranch, hasBranch,
nodeStyle, nodeStyle,
getMode, // getMode,
addNode, addNode,
addNodeFromItem,
onNodeClick, onNodeClick,
onNodeSelected,
onEditNode, onEditNode,
onNodeEdit,
onDeleteNode, onDeleteNode,
onDeleteNodeFromItem,
onDeleteAllNode, onDeleteAllNode,
onDeleteAllNextNodes,
copyFlow, copyFlow,
varName varName,
leftColumns,
rightColumns
} }
} }
}); });
</script> </script>
<style lang="scss"> <style lang="scss">
.action-node { .action-node {
min-width: 300px !important; min-width: 280px !important;
}
.action-title{
max-width: 280px !important;
overflow-wrap: anywhere;
} }
.line { .line {

View File

@@ -1,11 +1,28 @@
<template> <template>
<div> <div class="row justify-center">
<svg class="node-line"> <svg class="node-line" style="width:100%" :viewBox="viewBox()">
<polyline :points="points.linePoints" class="line" ></polyline> <template v-if="!node.outputPoints || node.outputPoints.length===0" >
<text class="add-icon" @click="addNode(node)" :x="points.iconPoint.x" :y="points.iconPoint.y" font-family="Arial" font-size="25" <polyline :points="points(getMode('')).linePoints" class="line" ></polyline>
text-anchor="middle" dy=".3em" style="cursor: pointer;" > <text class="add-icon"
@click="addNode(node,'')"
</text> :x="points(getMode('')).iconPoint.x"
:y="points(getMode('')).iconPoint.y"
font-family="Arial" font-size="25"
text-anchor="middle" dy=".3em" style="cursor: pointer;" >
</text>
</template>
<template v-for="(point, index) in node.outputPoints" :key="index" >
<polyline :points="points(getMode(point)).linePoints" class="line" ></polyline>
<text class="add-icon"
@click="addNode(node,point)"
:x="points(getMode(point)).iconPoint.x"
:y="points(getMode(point)).iconPoint.y"
font-family="Arial" font-size="25"
text-anchor="middle" dy=".3em" style="cursor: pointer;" >
</text>
</template>
</svg> </svg>
</div> </div>
</template> </template>
@@ -27,55 +44,97 @@ export default defineComponent({
type: Object as PropType<IActionNode>, type: Object as PropType<IActionNode>,
required: true required: true
}, },
mode: { leftColumns:{
type: String as PropType<Direction>, type:Number,
required: true required:false
}, },
inputPoint:{ rightColumns:{
type:String type:Number,
required:false
} }
}, },
emits: ['addNode'], emits: ['addNode'],
setup(props,context) { setup(props,context) {
const hasBranch = computed(() => props.actionNode.outputPoints.length > 0); const hasBranch = computed(() => props.actionNode.outputPoints.length > 0);
const points = computed(() => { const getMode = (point: string):Direction => {
switch (props.mode) { if (point === '' || props.actionNode.outputPoints.length === 0) {
case Direction.Left: return Direction.Default;
}
if (point === props.actionNode.outputPoints[0]) {
if (props.actionNode.nextNodeIds.get(point)) {
return Direction.Left;
} else {
return Direction.LeftNotNext;
}
} else {
if (props.actionNode.nextNodeIds.get(point)) {
return Direction.Right;
} else {
return Direction.RightNotNext;
}
}
}
const points = (mode:Direction) => {
let startX ,endX;
const leftColumn=props.leftColumns?props.leftColumns:1;
const rightColumn=props.rightColumns?props.rightColumns:1;
switch (mode) {
case Direction.Left:
startX = leftColumn*300/2.0;
endX = ((leftColumn+rightColumn)/2.0 - 0.25)*300;
return { return {
linePoints: '180, 0, 180, 40, 120, 40, 120, 60', linePoints: `${startX}, 60, ${startX}, 40, ${endX}, 40, ${endX}, 0`,
iconPoint: { x: 180, y: 20 } iconPoint: { x: endX, y: 20 }
}; };
case Direction.Right: case Direction.Right:
startX = ((leftColumn+rightColumn)/2.0 + 0.25)*300;
endX = (leftColumn+(rightColumn/2.0))*300;
return { return {
linePoints: '60, 0, 60, 40, 120, 40, 120, 60', linePoints: `${startX}, 0, ${startX}, 40, ${endX}, 40, ${endX}, 60`,
iconPoint: { x: 60, y: 20 } iconPoint: { x: startX, y: 20 }
}; };
case Direction.LeftNotNext: case Direction.LeftNotNext:
startX = ((leftColumn+rightColumn)/2.0 - 0.25)*300;
return { return {
linePoints: '180, 0, 180, 40', linePoints: `${startX}, 0, ${startX}, 40`,
iconPoint: { x: 180, y: 20 } iconPoint: { x: startX, y: 20 }
}; };
case Direction.RightNotNext: case Direction.RightNotNext:
startX = ((leftColumn+rightColumn)/2.0 + 0.25)*300;
return { return {
linePoints: '60, 0, 60, 40', linePoints: `${startX}, 0, ${startX}, 40`,
iconPoint: { x: 60, y: 30 } iconPoint: { x: startX, y: 20 }
}; };
default: default:
return { return {
linePoints: '120, 0, 120, 60', linePoints: '150, 0, 150, 60',
iconPoint: { x: 120, y: 30 } iconPoint: { x: 150, y: 30 }
}; };
} }
}); };
const addNode=(prveNode:IActionNode)=>{
context.emit('addNode',props.inputPoint); const addNode=(prveNode:IActionNode,point:string)=>{
context.emit('addNode',point);
} }
const viewBox=()=>{
let columns=0;
if(props.leftColumns!==undefined) columns+=props.leftColumns;
if(props.rightColumns!==undefined) columns+=props.rightColumns;
if(columns===0) columns=1;
const width= columns*300;
return `0 0 ${width} 60`;
};
return { return {
node: props.actionNode, node: props.actionNode,
getMode,
hasBranch, hasBranch,
points, points,
addNode addNode,
viewBox
} }
} }
}); });

View File

@@ -21,9 +21,9 @@
</q-drawer> </q-drawer>
</div> </div>
<div class="q-pa-md q-gutter-sm"> <div class="q-pa-md q-gutter-sm">
<div class="flowchart" v-if="store.currentFlow"> <div class="flowchart" v-if="store.currentFlow" style="padding-left: 300px;">
<node-item v-for="(node,) in store.currentFlow.actionNodes" :key="node.id" :isSelected="node === store.activeNode" <node-item v-if="rootNode!==undefined" :key="rootNode.id" :isSelected="rootNode === store.activeNode"
:actionNode="node" @addNode="addNode" @nodeSelected="onNodeSelected" @nodeEdit="onNodeEdit" :actionNode="rootNode" @addNode="addNode" @nodeSelected="onNodeSelected" @nodeEdit="onNodeEdit"
@deleteNode="onDeleteNode" @deleteAllNextNodes="onDeleteAllNextNodes" @copyFlow="onCopyFlow"></node-item> @deleteNode="onDeleteNode" @deleteAllNextNodes="onDeleteAllNextNodes" @copyFlow="onCopyFlow"></node-item>
</div> </div>
</div> </div>
@@ -71,6 +71,9 @@ const addActionNode = (action: IActionNode) => {
// refFlow.value?.actionNodes.push(action); // refFlow.value?.actionNodes.push(action);
store.currentFlow?.actionNodes.push(action); store.currentFlow?.actionNodes.push(action);
} }
const rootNode = computed(()=>{
return store.currentFlow?.getRoot();
})
const addNode = (node: IActionNode, inputPoint: string) => { const addNode = (node: IActionNode, inputPoint: string) => {
if (drawerRight.value) { if (drawerRight.value) {

View File

@@ -81,6 +81,7 @@ export interface IActionFlow {
toJSON(): any; toJSON(): any;
getRoot(): IActionNode | undefined; getRoot(): IActionNode | undefined;
createNewId(): string; createNewId(): string;
getColumns(node:IActionNode):number;
} }
/** /**
@@ -473,6 +474,21 @@ export class ActionFlow implements IActionFlow {
}; };
} }
getColumns(node:IActionNode):number{
let result= 1;
if(node.outputPoints && node.outputPoints.length>1){
result += node.outputPoints.length -1;
}
let nextNode;
for (const [key, id] of node.nextNodeIds) {
nextNode=this.findNodeById(id);
if(nextNode){
result +=this.getColumns(nextNode)-1;
}
}
return result;
}
getRoot(): IActionNode | undefined { getRoot(): IActionNode | undefined {
return this.actionNodes.find(node => node.isRoot) return this.actionNodes.find(node => node.isRoot)
} }