Compare commits

..

7 Commits

Author SHA1 Message Date
Mouriya
c3b560dbc9 条件付きコンポーネントは'source'でappidを受け取ることができる。 2024-05-25 04:15:09 +09:00
53aadfcaaa feat:データ集計処理作成 2024-05-24 09:20:19 +09:00
Mouriya
7fb3d08ccb 細部の問題の修正 2024-05-20 03:38:27 +09:00
Mouriya
cf4209333d データ処理書き込み完了 2024-05-17 23:32:14 +09:00
Mouriya
61ac281134 verNameのラッピング・オブジェクト 2024-05-17 14:41:15 +09:00
Mouriya
64d2cadd82 2つのデータ計算コンポーネントを追加する 2024-05-13 06:56:44 +09:00
Mouriya
371e2ee073 add vueuse dependencies 2024-05-13 06:56:03 +09:00
23 changed files with 803 additions and 673 deletions

View File

@@ -2,7 +2,6 @@
<html lang="ja-jp"> <html lang="ja-jp">
<head> <head>
<title><%= productName %></title> <title><%= productName %></title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="description" content="<%= productDescription %>"> <meta name="description" content="<%= productDescription %>">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">

View File

@@ -17,8 +17,9 @@
}, },
"dependencies": { "dependencies": {
"@quasar/extras": "^1.16.4", "@quasar/extras": "^1.16.4",
"@vueuse/core": "^10.9.0",
"axios": "^1.4.0", "axios": "^1.4.0",
"pinia": "^2.1.6", "pinia": "^2.1.7",
"quasar": "^2.6.0", "quasar": "^2.6.0",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"vue": "^3.0.0", "vue": "^3.0.0",

View File

@@ -4,6 +4,7 @@ import { Router } from 'vue-router';
import { App } from 'vue'; import { App } from 'vue';
export default boot(({ app, router }: { app: App<Element>; router: Router }) => { export default boot(({ app, router }: { app: App<Element>; router: Router }) => {
document.documentElement.lang="ja-JP";
app.config.errorHandler = (err: any, instance: any, info: string) => { app.config.errorHandler = (err: any, instance: any, info: string) => {
if (err.response && err.response.status === 401) { if (err.response && err.response.status === 401) {
// 認証エラーの場合再ログインする // 認証エラーの場合再ログインする

View File

@@ -1,5 +1,5 @@
<template> <template>
<show-dialog v-model:visible="showflg" name="条件エディタ" @close="closeDg" min-width="60vw" min-height="60vh"> <show-dialog v-model:visible="showflg" name="条件エディタ" @close="closeDg" min-width="50vw" min-height="60vh">
<template v-slot:toolbar> <template v-slot:toolbar>
<q-btn flat round dense icon="more_vert" > <q-btn flat round dense icon="more_vert" >
<q-menu auto-close anchor="bottom start"> <q-menu auto-close anchor="bottom start">

View File

@@ -6,14 +6,14 @@
{{ selectedObject.name }} {{ selectedObject.name }}
</q-chip> </q-chip>
<q-chip color="info" text-color="white" v-if="isSelected && selectedObject.objectType==='variable'" :dense="true" class="selected-obj"> <q-chip color="info" text-color="white" v-if="isSelected && selectedObject.objectType==='variable'" :dense="true" class="selected-obj">
{{ selectedObject.name }} {{ selectedObject.name.name }}
</q-chip> </q-chip>
</template> </template>
<template v-slot:append> <template v-slot:append>
<q-icon name="search" class="cursor-pointer" @click="showDg"/> <q-icon name="search" class="cursor-pointer" @click="showDg"/>
</template> </template>
</q-field> </q-field>
<show-dialog v-model:visible="show" name="条件設定項目一覧" @close="closeDg" width="600px"> <show-dialog v-model:visible="show" name="設定項目一覧" @close="closeDg" min-width="400px">
<template v-slot:toolbar> <template v-slot:toolbar>
<q-input dense debounce="200" v-model="filter" placeholder="検索" clearable> <q-input dense debounce="200" v-model="filter" placeholder="検索" clearable>
<template v-slot:before> <template v-slot:before>
@@ -88,9 +88,9 @@
.condition-object{ .condition-object{
min-width: 200px; min-width: 200px;
max-height: 40px; max-height: 40px;
padding: 2px; margin: 0 2px;
} }
.selected-obj{ .selected-obj{
margin: 0px; margin: 0 2px;
} }
</style> </style>

View File

@@ -66,7 +66,7 @@
</div> </div>
<!-- condition --> <!-- condition -->
<div @click.stop @keypress.stop v-else > <div @click.stop @keypress.stop v-else >
<div class="row no-wrap items-center"> <div class="row no-wrap items-center q-my-xs">
<ConditionObject v-bind="prop.node" v-model="prop.node.object" class="col-4"></ConditionObject> <ConditionObject v-bind="prop.node" v-model="prop.node.object" class="col-4"></ConditionObject>
<q-select v-model="prop.node.operator" :options="operators" class="operator" :outlined="true" :dense="true"></q-select> <q-select v-model="prop.node.operator" :options="operators" class="operator" :outlined="true" :dense="true"></q-select>
<q-input v-if="!prop.node.object || !('options' in prop.node.object)" <q-input v-if="!prop.node.object || !('options' in prop.node.object)"
@@ -257,12 +257,12 @@ export default defineComponent( {
.condition-value{ .condition-value{
min-width: 200px; min-width: 200px;
max-height: 40px; max-height: 40px;
padding: 2px; margin: 0 2px;
} }
.operator{ .operator{
min-width: 150px; min-width: 150px;
max-height: 40px; max-height: 40px;
padding: 2px; margin: 0 2px;
text-align: center; text-align: center;
font-size: 12pt; font-size: 12pt;
} }

View File

@@ -19,7 +19,7 @@
<q-tab-panels v-model="tab" animated> <q-tab-panels v-model="tab" animated>
<q-tab-panel name="fields"> <q-tab-panel name="fields">
<field-list v-model="selected" type="single" :filter="filter" :appId="appId"></field-list> <field-list v-model="selected" type="single" :filter="filter" :appId="sourceApp ? sourceApp :appId " :fields="sourceFields"></field-list>
</q-tab-panel> </q-tab-panel>
<q-tab-panel name="vars" > <q-tab-panel name="vars" >
@@ -30,7 +30,7 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { ref, onMounted, reactive } from 'vue' import { ref, onMounted, reactive, inject } from 'vue'
import FieldList from './FieldList.vue'; import FieldList from './FieldList.vue';
import VariableList from './VariableList.vue'; import VariableList from './VariableList.vue';
@@ -48,10 +48,14 @@ export default {
filter:String filter:String
}, },
setup(props) { setup(props) {
const selected = ref([]);
console.log(selected);
return { return {
sourceFields : inject('sourceFields'),
sourceApp : inject('sourceApp'),
tab: ref('fields'), tab: ref('fields'),
selected: ref([]) selected
} }
}, },

View File

@@ -1,50 +1,50 @@
<template> <template>
<div class="q-pa-md"> <div class="q-pa-md">
<q-table flat bordered :loading="!isLoaded" row-key="name" :selection="type" <q-table flat bordered :loading="!isLoaded" row-key="name" :selection="type" :selected="modelValue"
:selected="modelValue" @update:selected="$emit('update:modelValue', $event)" :filter="filter" :columns="columns" :rows="rows" />
@update:selected="$emit('update:modelValue', $event)"
:filter="filter"
:columns="columns" :rows="rows" />
</div> </div>
</template> </template>
<script> <script lang="ts">
import { ref, onMounted, reactive } from 'vue' import { useAsyncState } from '@vueuse/core';
import { api } from 'boot/axios'; import { api } from 'boot/axios';
import { computed } from 'vue';
export default { export default {
name: 'FieldList', name: 'FieldList',
props: { props: {
fields: Array,
name: String, name: String,
type: String, type: String,
appId: Number, appId: Number,
modelValue:Array, modelValue: Array,
filter:String filter: String
}, },
emits:[ emits: [
'update:modelValue' 'update:modelValue'
], ],
setup(props) { setup(props) {
const isLoaded = ref(false); // const rows = ref([]);
// const isLoaded = ref(false);
const columns = [ const columns = [
{ name: 'name', required: true, label: 'フィールド名', align: 'left', field: row => row.name, sortable: true }, { name: 'name', required: true, label: 'フィールド名', align: 'left', field: 'name', sortable: true },
{ name: 'code', label: 'フィールドコード', align: 'left', field: 'code', sortable: true }, { name: 'code', label: 'フィールドコード', align: 'left', field: 'code', sortable: true },
{ name: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true } { name: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true }
] ]
const rows = reactive([]);
onMounted(async () => { const { state : rows, isReady: isLoaded, isLoading } = useAsyncState((args) => {
const res = await api.get('api/v1/appfields', { if (props.fields) {
return props.fields.map(f => ({ name: f.label, objectType: 'field', ...f }));
} else {
return api.get('api/v1/appfields', {
params: { params: {
app: props.appId app: props.appId
} }
}).then(res => {
console.log(res);
return Object.values(res.data.properties).map(f => ({ name: f.label, objectType: 'field', ...f }));
}); });
let fields = res.data.properties; }
console.log(fields); }, [{ name: '', objectType: '', type: '', code: '', label: '' }])
Object.keys(fields).forEach((key) => {
const fld = fields[key];
rows.push({ name: fld.label, objectType: 'field', ...fld });
});
isLoaded.value = true;
});
return { return {
columns, columns,

View File

@@ -1,7 +1,7 @@
<template> <template>
<!-- <div class="q-pa-md q-gutter-sm" > --> <!-- <div class="q-pa-md q-gutter-sm" > -->
<q-dialog :model-value="visible" persistent bordered > <q-dialog :model-value="visible" persistent bordered >
<q-card :style="cardStyle" style=" min-width: 40vw; max-width: 80vw; max-height: 95vh;"> <q-card style="min-width: 40vw; max-width: 80vw; max-height: 95vh;" :style="cardStyle">
<q-toolbar class="bg-grey-4"> <q-toolbar class="bg-grey-4">
<q-toolbar-title>{{ name }}</q-toolbar-title> <q-toolbar-title>{{ name }}</q-toolbar-title>
<q-space></q-space> <q-space></q-space>

View File

@@ -1,43 +1,59 @@
<template> <template>
<div class="q-pa-md"> <div class="q-pa-md">
<q-table flat bordered row-key="name" :selection="type" <q-table flat bordered row-key="id" :selection="type" :selected="modelValue"
:selected="modelValue" @update:selected="$emit('update:modelValue', $event)" :columns="columns" :rows="rows" />
@update:selected="$emit('update:modelValue', $event)"
:columns="columns" :rows="rows" />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { ref, reactive, PropType, compile } from 'vue'; import { PropType, reactive } from 'vue';
import {IActionNode,IActionVariable} from '../types/ActionTypes'; import { IActionVariable } from '../types/ActionTypes';
import { v4 as uuidv4 } from 'uuid';
export default { export default {
name: 'VariableList', name: 'VariableList',
props: { props: {
name: String, name: String,
type: String, type: String,
vars:{ vars: {
type:Array as PropType<IActionVariable[]>, type: Array as PropType<IActionVariable[]>,
reqired:true, reqired: true,
default:()=>[] default: () => []
}, },
modelValue:Array modelValue: Array
}, },
emits:[ emits: [
'update:modelValue' 'update:modelValue'
], ],
setup(props) { setup(props) {
const columns= [ const variableName = (field) => {
{ name: 'actionName', label: 'アクション名',align: 'left',field: 'actionName',sortable: true}, const name = field.name;
{ name: 'displayName', label: '変数表示名', align: 'left',field: 'displayName', sortable: true }, return name.name;
{ name: 'name', label: '変数名', align: 'left',field: 'name',required: true, sortable: true } }
const columns = [
{ name: 'actionName', label: 'アクション名', align: 'left', field: 'actionName', sortable: true },
{ name: 'displayName', label: '変数表示名', align: 'left', field: 'displayName', sortable: true },
{ name: 'name', label: '変数名', align: 'left', field: variableName, required: true, sortable: true }
]; ];
const rows= props.vars.map((v)=>{
return {objectType:'variable',...v}; const rows = props.vars.flatMap((v) => {
if (v.name.vars && v.name.vars.length > 0) {
return v.name.vars
.filter(o => o.vName && o.logicalOperator && o.field)
.map(o => ({
id: uuidv4(),
objectType: 'variable',
name: { name: `${v.name.name}.${o.vName}` },
actionName: v.name.actionName,
displayName: v.name.displayName
}));
} else {
return [{ objectType: 'variable', ...v }];
}
}); });
return { return {
columns, columns,
rows:reactive(rows) rows: reactive(rows)
} }
} }
} }

View File

@@ -1,55 +1,48 @@
<template> <template>
<!-- <div class="q-pa-md q-gutter-sm"> --> <!-- <div class="q-pa-md q-gutter-sm"> -->
<q-tree :nodes="store.eventTree.screens" node-key="eventId" children-key="events" no-connectors <q-tree
v-model:expanded="store.expandedScreen" :dense="true" :ref="tree"> :nodes="store.eventTree.screens"
node-key="eventId"
children-key="events"
no-connectors
v-model:expanded="store.expandedScreen"
:dense="true"
:ref="tree"
>
<template v-slot:header-EVENT="prop"> <template v-slot:header-EVENT="prop">
<div :ref="prop.node.eventId" class="row col items-center no-wrap event-node"> <div class="row col items-start no-wrap event-node" @click="onSelected(prop.node)">
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px" <q-icon v-if="prop.node.eventId"
class="q-mr-sm"> name="play_circle"
:color="prop.node.hasFlow?'green':'grey'"
size="16px" class="q-mr-sm">
</q-icon> </q-icon>
<div class="no-wrap" @click="onSelected(prop.node)" <div class="no-wrap" :class="selectedEvent && prop.node.eventId===selectedEvent.eventId?'selected-node':''">{{ prop.node.label }}</div>
:class="selectedEvent && prop.node.eventId === selectedEvent.eventId ? 'selected-node' : ''">{{
prop.node.label }}</div>
<q-space></q-space> <q-space></q-space>
<!-- <q-icon v-if="prop.node.hasFlow" name="delete" color="negative" size="16px" class="q-mr-sm"></q-icon> --> <!-- <q-icon v-if="prop.node.hasFlow" name="delete" color="negative" size="16px" class="q-mr-sm"></q-icon> -->
</div> </div>
</template> </template>
<template v-slot:header-CHANGE="prop"> <template v-slot:header-CHANGE="prop" >
<div class="row col items-center no-wrap event-node"> <div class="row col items-start no-wrap event-node" >
<div class="no-wrap">{{ prop.node.label }}</div> <div class="no-wrap">{{ prop.node.label }}</div>
<q-space></q-space> <q-space></q-space>
<q-icon name="add_circle" color="primary" size="16px" class="q-mr-sm" <q-icon name="add_circle" color="primary" size="16px" class="q-mr-sm" @click="addChangeEvent(prop.node)"></q-icon>
@click="addChangeEvent(prop.node)"></q-icon>
</div>
</template>
<template v-slot:header-DELETABLE="prop">
<div class="row col items-center event-node">
<div class="row col items-center" @click="onSelected(prop.node)">
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px"
class="q-mr-sm">
</q-icon>
<div>{{ prop.node.label }}</div>
</div>
<div>
<q-btn class="q-mr-sm delete-btn" flat fab-mini icon="delete_forever" padding="none" color="negative"
@click="deleteEvent(prop.node)"></q-btn>
</div>
</div> </div>
</template> </template>
</q-tree> </q-tree>
<show-dialog v-model:visible="showDialog" name="フィールド選択" @close="closeDg"> <show-dialog v-model:visible="showDialog" name="フィールド選択" @close="closeDg" widht="400px">
<field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select> <field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select>
</show-dialog> </show-dialog>
</template> </template>
<script lang="ts"> <script lang="ts">
import { QTree, useQuasar } from 'quasar'; import { defineComponent, computed, ref } from 'vue';
import { ActionFlow, RootAction } from 'src/types/ActionTypes'; import { IKintoneEvent ,IKintoneEventGroup, IKintoneEventNode, kintoneEvent} from '../../types/KintoneEvents';
import { storeToRefs } from 'pinia';
import { useFlowEditorStore } from 'stores/flowEditor'; import { useFlowEditorStore } from 'stores/flowEditor';
import { defineComponent, ref } from 'vue'; import { ActionFlow, ActionNode, RootAction } from 'src/types/ActionTypes';
import { IKintoneEvent, IKintoneEventGroup, IKintoneEventNode } from '../../types/KintoneEvents';
import FieldSelect from '../FieldSelect.vue';
import ShowDialog from '../ShowDialog.vue'; import ShowDialog from '../ShowDialog.vue';
import FieldSelect from '../FieldSelect.vue';
import { QTree } from 'quasar';
export default defineComponent({ export default defineComponent({
name: 'EventTree', name: 'EventTree',
components: { components: {
@@ -57,7 +50,6 @@ export default defineComponent({
FieldSelect, FieldSelect,
}, },
setup(props, context) { setup(props, context) {
const $q = useQuasar();
const appDg = ref(); const appDg = ref();
const store = useFlowEditorStore(); const store = useFlowEditorStore();
const showDialog = ref(false); const showDialog = ref(false);
@@ -66,78 +58,61 @@ export default defineComponent({
// const selectedFlow = store.currentFlow; // const selectedFlow = store.currentFlow;
// const expanded=ref(); // const expanded=ref();
const selectedEvent = ref<IKintoneEvent | null>(null); const selectedEvent = ref<IKintoneEvent|null>(null);
const selectedChangeEvent = ref<IKintoneEventGroup | null>(null); const selectedChangeEvent=ref<IKintoneEventGroup|null>(null);
const isFieldChange = (node: IKintoneEventNode) => { const isFieldChange = (node:IKintoneEventNode)=>{
return node.header == 'EVENT' && node.eventId.indexOf(".change.") > -1; return node.header=='EVENT' && node.eventId.indexOf(".change.")>-1;
} }
//フィールド値変更イベント追加 //フィールド値変更イベント追加
const closeDg = (val: string) => { const closeDg = (val:string) => {
if (val == 'OK') { if (val == 'OK') {
if (!selectedChangeEvent.value) { return; } if(!selectedChangeEvent.value){return;}
const field = appDg.value.selected[0]; const field = appDg.value.selected[0];
const eventid = `${selectedChangeEvent.value.eventId}.${field.code}`; const eventid = `${selectedChangeEvent.value.eventId}.${field.code}`;
if (store.eventTree.findEventById(eventid)) { if(store.eventTree.findEventById(eventid)){
return; return;
} }
selectedChangeEvent.value?.events.push({ selectedChangeEvent.value?.events.push(
eventId: eventid, new kintoneEvent(
label: field.name, field.label,
parentId: selectedChangeEvent.value.eventId, eventid,
header: 'DELETABLE' selectedChangeEvent.value.eventId)
}); );
tree.value?.expanded?.push(selectedChangeEvent.value.eventId); tree.value?.expanded?.push(selectedChangeEvent.value.eventId);
tree.value?.expandAll(); tree.value?.expandAll();
} }
}; };
const addChangeEvent = (node: IKintoneEventGroup) => { const addChangeEvent=(node:IKintoneEventGroup)=>{
if (store.appInfo === undefined) { if(store.appInfo===undefined){
return; return;
} }
selectedChangeEvent.value = node; selectedChangeEvent.value=node;
showDialog.value = true; showDialog.value=true;
} }
const onSelected=(node:IKintoneEvent)=>{
const deleteEvent = (node: IKintoneEvent) => { if(!node.eventId){
if (!node.eventId) {
return; return;
} }
store.deleteEvent(node); selectedEvent.value=node;
store.selectFlow(undefined) if(store.appInfo===undefined){
$q.notify({
type: 'positive',
caption: "通知",
message: `イベント ${node.label} 削除`
})
}
const onSelected = (node: IKintoneEvent) => {
if (!node.eventId) {
return;
}
selectedEvent.value = node;
if (store.appInfo === undefined) {
return; return;
} }
const screen = store.eventTree.findEventById(node.parentId); const screen = store.eventTree.findEventById(node.parentId);
let flow =store.findFlowByEventId(node.eventId);
let flow = store.findFlowByEventId(node.eventId); let screenName=screen!==null?screen.label:"";
let screenName = screen !== null ? screen.label : "";
let nodeLabel = node.label; let nodeLabel = node.label;
// if(isFieldChange(node)){ // if(isFieldChange(node)){
// screenName=nodeLabel; // screenName=nodeLabel;
// nodeLabel=`${node.label}の値を変更したとき`; // nodeLabel=`${node.label}の値を変更したとき`;
// } // }
if(flow!==undefined && flow!==null ){
if (flow !== undefined && flow !== null) {
store.selectFlow(flow); store.selectFlow(flow);
} else { }else{
const root = new RootAction(node.eventId, screenName, nodeLabel) const root = new RootAction(node.eventId,screenName,nodeLabel)
const flow = new ActionFlow(root); const flow =new ActionFlow(root);
store.flows?.push(flow); store.flows?.push(flow);
store.selectFlow(flow); store.selectFlow(flow);
selectedEvent.value.flowData = flow; selectedEvent.value.flowData=flow;
} }
}; };
return { return {
@@ -150,7 +125,6 @@ export default defineComponent({
onSelected, onSelected,
selectedEvent, selectedEvent,
addChangeEvent, addChangeEvent,
deleteEvent,
closeDg, closeDg,
store store
} }
@@ -158,25 +132,20 @@ export default defineComponent({
}); });
</script> </script>
<style lang="scss"> <style lang="scss">
.nowrap { .nowrap{
flex-wrap: nowarp; flex-wrap:nowarp;
text-wrap: nowarp; text-wrap:nowarp;
} }
.event-node{
.event-node { cursor:pointer;
cursor: pointer;
} }
.selected-node{
.selected-node {
color: $primary; color: $primary;
font-weight: bolder; font-weight: bolder;
} }
.event-node:hover{
.event-node:hover {
background-color: $light-blue-1; background-color: $light-blue-1;
} }
.delete-btn {
margin-right: 5px;
}
</style> </style>

View File

@@ -205,7 +205,7 @@ export default defineComponent({
*/ */
const varName =(node:IActionNode)=>{ const varName =(node:IActionNode)=>{
const prop = node.actionProps.find((prop) => prop.props.name === "verName"); const prop = node.actionProps.find((prop) => prop.props.name === "verName");
return prop?.props.modelValue; return prop?.props.modelValue.name;
}; };
const copyFlow=()=>{ const copyFlow=()=>{
context.emit('copyFlow', props.actionNode); context.emit('copyFlow', props.actionNode);

View File

@@ -128,7 +128,7 @@ interface IAppFields{
export default defineComponent({ export default defineComponent({
inheritAttrs:false, inheritAttrs:false,
name: 'FieldInput', name: 'AppFieldSelect',
components: { components: {
ShowDialog, ShowDialog,
FieldSelect, FieldSelect,

View File

@@ -1,12 +1,12 @@
<template> <template>
<div v-bind="$attrs"> <div v-bind="$attrs">
<q-field v-model="tree" :label="displayName" labelColor="primary" stack-label > <q-field v-model="tree" :label="displayName" labelColor="primary" stack-label>
<template v-slot:control > <template v-slot:control>
<q-card flat class="full-width"> <q-card flat class="full-width">
<q-card-actions vertical> <q-card-actions vertical>
<q-btn color="grey-3" text-color="black" @click="showDg()">クリックで設定{{ isSetted?'設定済み':'未設定' }}</q-btn> <q-btn color="grey-3" text-color="black" @click="showDg()">クリックで設定{{ isSetted ? '設定済み' : '未設定' }}</q-btn>
</q-card-actions> </q-card-actions>
<q-card-section class="text-caption" > <q-card-section class="text-caption">
<div v-if="!isSetted">{{ placeholder }}</div> <div v-if="!isSetted">{{ placeholder }}</div>
<div v-else>{{ conditionString }}</div> <div v-else>{{ conditionString }}</div>
</q-card-section> </q-card-section>
@@ -17,22 +17,43 @@
</div> </div>
</template> </template>
<script lang="ts">
import { defineComponent, ref ,watchEffect,computed,reactive} from 'vue';
import { ConditionTree,GroupNode,ConditionNode,LogicalOperator,Operator } from 'app/src/types/Conditions'; <script lang="ts">
import ConditionEditor from '../ConditionEditor/ConditionEditor.vue' import { ConditionNode, ConditionTree, Operator } from 'app/src/types/Conditions';
export default defineComponent({ import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
import ConditionEditor from '../ConditionEditor/ConditionEditor.vue';
type Props = {
props?: {
name: string;
modelValue?: {
fields: {
type: string;
label: string;
code: string;
}[]
}
}
};
export default defineComponent({
name: 'FieldInput', name: 'FieldInput',
inheritAttrs:false, inheritAttrs: false,
components: { components: {
ConditionEditor ConditionEditor
}, },
props: { props: {
displayName:{ context: {
type: Array<Props>,
default: '',
},
displayName: {
type: String, type: String,
default: '', default: '',
}, },
name:{ name: {
type: String, type: String,
default: '', default: '',
}, },
@@ -40,7 +61,7 @@
type: String, type: String,
default: '', default: '',
}, },
hint:{ hint: {
type: String, type: String,
default: '', default: '',
}, },
@@ -48,22 +69,37 @@
type: String, type: String,
default: null default: null
}, },
sourceType: {
type: String,
default: 'field'
}
}, },
setup(props, { emit }) { setup(props, { emit }) {
const source = props.context.find(element => element?.props?.name === 'sources')
if (source) {
if(props.sourceType === 'field'){
provide('sourceFields', computed( () => source.props?.modelValue?.fields ?? []));
} else if(props.sourceType === 'app'){
console.log('sourceApp', source.props?.modelValue);
provide('sourceApp', computed( () => source.props?.modelValue?.app?.id));
}
}
const appDg = ref(); const appDg = ref();
const show = ref(false); const show = ref(false);
const tree = reactive(new ConditionTree()); const tree = reactive(new ConditionTree());
if(props.modelValue && props.modelValue!==''){ if (props.modelValue && props.modelValue !== '') {
tree.fromJson(props.modelValue); tree.fromJson(props.modelValue);
}else{ } else {
const newNode = new ConditionNode({},Operator.Equal,'',tree.root); const newNode = new ConditionNode({}, Operator.Equal, '', tree.root);
tree.addNode(tree.root,newNode); tree.addNode(tree.root, newNode);
} }
const isSetted=ref(props.modelValue && props.modelValue!==''); const isSetted = ref(props.modelValue && props.modelValue !== '');
const conditionString = computed(()=>{ const conditionString = computed(() => {
return tree.buildConditionString(tree.root); return tree.buildConditionString(tree.root);
}); });
@@ -71,10 +107,10 @@
show.value = true; show.value = true;
}; };
const onClosed = (val:string) => { const onClosed = (val: string) => {
if (val == 'OK') { if (val == 'OK') {
const conditionJson = tree.toJson(); const conditionJson = tree.toJson();
isSetted.value=true; isSetted.value = true;
emit('update:modelValue', conditionJson); emit('update:modelValue', conditionJson);
} }
}; };
@@ -94,5 +130,5 @@
conditionString conditionString
}; };
} }
}); });
</script> </script>

View File

@@ -0,0 +1,229 @@
<template>
<div>
<q-field :label="displayName" labelColor="primary" stack-label>
<template v-slot:control>
<q-card flat class="full-width">
<q-card-actions vertical>
<q-btn color="grey-3" text-color="black" @click="() => { dgIsShow = true }">クリックで設定</q-btn>
</q-card-actions>
<q-card-section class="text-caption">
<div v-if="processingObjectsInputDisplay && processingObjectsInputDisplay.length>0">
<div v-for="(item) in processingObjectsInputDisplay" :key="item">{{ item }}</div>
</div>
<div v-else>{{ placeholder }}</div>
</q-card-section>
</q-card>
</template>
</q-field>
<show-dialog v-model:visible="dgIsShow" name="集計処理" @close="closeDg" min-width="50vw" min-height="60vh">
<div class="q-mx-md q-mb-md">
<q-input v-model="processingProps.name" type="text" label-color="primary" label="集計結果の変数名"
placeholder="集計結果を格納する変数名を入力してください" stack-label />
</div>
<div class="q-mx-md">
<div class="row q-col-gutter-x-xs flex-center">
<div class="col-5">
<div class="q-mx-xs">データソース</div>
</div>
<div class="col-2">
<div class="q-mx-xs">集計計算</div>
</div>
<div class="col-4">
<div class="q-mx-xs">集計結果変数名</div>
</div>
<div class="col-1"><q-btn flat round dense icon="add" size="sm" @click="addProcessingObject" />
</div>
</div>
<div class="q-my-sm" v-for="(item, index) in processingObjects" :key="item.id">
<div class="row q-col-gutter-x-xs flex-center">
<div class="col-5">
<ConditionObject v-model="item.field" />
</div>
<div class="col-2">
<q-select v-model="item.logicalOperator" :options="logicalOperators" outlined dense></q-select>
</div>
<div class="col-4">
<q-input v-model="item.vName" type="text" outlined dense />
</div>
<div class="col-1">
<q-btn flat round dense icon="delete" size="sm" @click="() => deleteProcessingObject(index)" />
</div>
</div>
</div>
</div>
</show-dialog>
</div>
</template>
<script lang="ts">
import { v4 as uuidv4 } from 'uuid';
import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
import ConditionObject from '../ConditionEditor/ConditionObject.vue';
import ShowDialog from '../ShowDialog.vue';
type Props = {
props?: {
name: string;
modelValue?: {
fields: {
type: string;
label: string;
code: string;
}[]
} | string
}
};
type ProcessingObjectType = {
field?: {
name: string | {
name: string;
};
objectType: string;
type: string;
code: string;
label: string;
noLabel: boolean;
};
logicalOperator?: string;
vName?: string;
id: string;
}
type ValueType = {
name: string;
actionName: string,
displayName: string,
vars: ProcessingObjectType[];
}
export default defineComponent({
name: 'DataProcessing',
inheritAttrs: false,
components: {
ShowDialog,
ConditionObject,
},
props: {
context: {
type: Array<Props>,
default: '',
},
displayName: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
modelValue: {
type: Object as () => ValueType,
},
placeholder: {
type: String,
default: '',
},
},
setup(props, { emit }) {
const source = props.context.find(element => element?.props?.name === 'sources')
if (source) {
provide('sourceFields', computed(() => {
const modelValue = source.props?.modelValue;
if (modelValue && typeof modelValue !== 'string') {
return modelValue.fields;
}
return null;
}));
}
const actionName = props.context.find(element => element?.props?.name === 'displayName')
const processingProps: ValueType = props.modelValue && props.modelValue.vars
? props.modelValue
: reactive({
name: '',
actionName: actionName?.props?.modelValue as string,
displayName: '結果(戻り値)',
vars: [{ id: uuidv4() }]
});
const closeDg = () => {
emit('update:modelValue', processingProps
);
}
const processingObjects = processingProps.vars;
const deleteProcessingObject = (index: number) => processingObjects.length === 1
? processingObjects.splice(0, processingObjects.length, { id: uuidv4() })
: processingObjects.splice(index, 1);
const processingObjectsInputDisplay = computed(() =>
processingObjects ?
processingObjects
.filter(item => item.field && item.logicalOperator && item.vName)
.map(item => {
const name = typeof item.field?.name === 'string'
? item.field.name
: item.field?.name.name;
return item.logicalOperator.operator!==''?
`${processingProps.name}.${item.vName} = ${item.logicalOperator.operator}(${name})`
:`${processingProps.name}.${item.vName} = ${name}`
})
: []
);
//集計処理方法
const logicalOperators = ref([
{
"operator": "",
"label": "なし"
},
{
"operator": "SUM",
"label": "合計"
},
{
"operator": "AVG",
"label": "平均"
},
{
"operator": "MAX",
"label": "最大値"
},
{
"operator": "MIN",
"label": "最小値"
},
{
"operator": "COUNT",
"label": "カウント"
},
{
"operator": "FIRST",
"label": "最初の値"
}
]);
watchEffect(() => {
emit('update:modelValue', processingProps);
});
return {
uuidv4,
dgIsShow: ref(false),
closeDg,
processingObjects,
processingProps,
addProcessingObject: () => processingObjects.push({ id: uuidv4() }),
deleteProcessingObject,
logicalOperators,
processingObjectsInputDisplay,
};
},
});
</script>
<style lang="scss"></style>

View File

@@ -1,10 +1,7 @@
<template> <template>
<div v-bind="$attrs"> <div v-bind="$attrs">
<q-input :label="displayName" v-model="inputValue" label-color="primary" <q-input :label="displayName" v-model="inputValue" label-color="primary" :placeholder="placeholder" stack-label
:placeholder="placeholder" stack-label :rules="rulesExp" :maxlength="maxLength">
:rules="rulesExp"
:maxlength="maxLength"
>
<template v-slot:append v-if="hint !== ''"> <template v-slot:append v-if="hint !== ''">
<q-icon name="help" size="22px" color="blue-8"> <q-icon name="help" size="22px" color="blue-8">
<q-tooltip class="bg-yellow-2 text-black shadow-4" anchor="bottom right"> <q-tooltip class="bg-yellow-2 text-black shadow-4" anchor="bottom right">
@@ -18,7 +15,7 @@
<script lang="ts"> <script lang="ts">
import { kMaxLength } from 'buffer'; import { kMaxLength } from 'buffer';
import { defineComponent, ref, watchEffect } from 'vue'; import { defineComponent, ref, watchEffect, computed } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'InputText', name: 'InputText',
@@ -40,27 +37,50 @@ export default defineComponent({
type: String, type: String,
default: '', default: '',
}, },
maxLength:{ maxLength: {
type: Number, type: Number,
default:undefined default: undefined
}, },
//例:[val=>!!val ||'入力してください'] //例:[val=>!!val ||'入力してください']
rules:{ rules: {
type:String, type: String,
default:undefined default: undefined
}, },
modelValue: { modelValue: {
type: String, // type: Any,
default: '', default: '',
}, },
}, },
setup(props, { emit }) { setup(props, { emit }) {
const inputValue = ref(props.modelValue); const inputValue = computed({
const rulesExp = props.rules===undefined?null : eval(props.rules); get: () => {
watchEffect(() => { if (props.modelValue !== null && typeof props.modelValue === 'object' && 'name' in props.modelValue) {
emit('update:modelValue', inputValue.value); return props.modelValue.name;
} else {
return props.modelValue;
}
},
set: (val) => {
if (props.name === 'verName') {
// return props.modelValue.name;
emit('update:modelValue', { name: val });
} else {
emit('update:modelValue', val);
}
},
}); });
// const inputValue = ref(props.modelValue);
const rulesExp = props.rules === undefined ? null : eval(props.rules);
// const finalValue = computed(() => {
// return props.name !== 'verName' ? inputValue.value : {
// name: inputValue.value,
// };
// });
// watchEffect(() => {
// emit('update:modelValue', finalValue);
// });
return { return {
inputValue, inputValue,

View File

@@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<div v-for="(item, index) in properties" :key="index" > <div v-for="(item, index) in properties" :key="index" >
<component :is="item.component" v-bind="item.props" :connectProps="connectProps(item.props)" v-model="item.props.modelValue"></component> <component :is="item.component" v-bind="item.props" :context="properties" :connectProps="connectProps(item.props)" v-model="item.props.modelValue"></component>
</div> </div>
</div> </div>
</template> </template>
@@ -21,6 +21,7 @@ import ConditionInput from '../right/ConditionInput.vue';
import EventSetter from '../right/EventSetter.vue'; import EventSetter from '../right/EventSetter.vue';
import ColorPicker from './ColorPicker.vue'; import ColorPicker from './ColorPicker.vue';
import NumInput from './NumInput.vue'; import NumInput from './NumInput.vue';
import DataProcessing from './DataProcessing.vue';
import { IActionNode,IActionProperty,IProp } from 'src/types/ActionTypes'; import { IActionNode,IActionProperty,IProp } from 'src/types/ActionTypes';
export default defineComponent({ export default defineComponent({
@@ -35,7 +36,8 @@ export default defineComponent({
ConditionInput, ConditionInput,
EventSetter, EventSetter,
ColorPicker, ColorPicker,
NumInput NumInput,
DataProcessing
}, },
props: { props: {
nodeProps: { nodeProps: {
@@ -50,7 +52,7 @@ export default defineComponent({
setup(props, context) { setup(props, context) {
const properties=ref(props.nodeProps); const properties=ref(props.nodeProps);
const connectProps=(props:IProp)=>{ const connectProps=(props:IProp)=>{
const connProps:any={}; const connProps:any={context:properties};
if(props && "connectProps" in props && props.connectProps!=undefined){ if(props && "connectProps" in props && props.connectProps!=undefined){
for(let connProp of props.connectProps){ for(let connProp of props.connectProps){
let targetProp = properties.value.find((prop)=>prop.props.name===connProp.propName); let targetProp = properties.value.find((prop)=>prop.props.name===connProp.propName);

View File

@@ -1,11 +1,12 @@
<template> <template>
<div v-bind="$attrs"> <div v-bind="$attrs">
<q-select v-model="selectedValue" :label="displayName" :options="options"/> <q-select v-model="selectedValue" :use-chips="multiple" :label="displayName" label-color="primary" :options="options" stack-label
:multiple="multiple"/>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent,ref,watchEffect } from 'vue'; import { defineComponent,ref,watchEffect,computed } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'SelectBox', name: 'SelectBox',
@@ -23,20 +24,27 @@ export default defineComponent({
type: Array, type: Array,
required: true, required: true,
}, },
selectType:{
type:String,
default:'',
},
modelValue: { modelValue: {
type: String, type: Object,
default: '', default: null,
}, },
}, },
setup(props, { emit }) { setup(props, { emit }) {
const selectedValue = ref(props.modelValue); const selectedValue = ref(props.modelValue);
const multiple = computed(()=>{
return props.selectType==='multiple'
});
watchEffect(() => { watchEffect(() => {
emit('update:modelValue', selectedValue.value); emit('update:modelValue', selectedValue.value);
}); });
return { return {
selectedValue selectedValue,
multiple
}; };
}, },
}); });

View File

@@ -1,29 +1,33 @@
import { api } from 'boot/axios'; import { api } from 'boot/axios';
import { ActionFlow } from 'src/types/ActionTypes'; import { ActionFlow } from 'src/types/ActionTypes';
export class FlowCtrl { export class FlowCtrl
async getFlows(appId: string): Promise<ActionFlow[]> { {
const flows: ActionFlow[] = [];
try { async getFlows(appId:string):Promise<ActionFlow[]>
{
const flows:ActionFlow[]=[];
try{
const result = await api.get(`api/flows/${appId}`); const result = await api.get(`api/flows/${appId}`);
//console.info(result.data); //console.info(result.data);
if (!result.data || !Array.isArray(result.data)) { if(!result.data || !Array.isArray(result.data)){
return []; return [];
} }
for (const flow of result.data) { for(const flow of result.data){
flows.push(ActionFlow.fromJSON(flow.content)); flows.push(ActionFlow.fromJSON(flow.content));
} }
return flows; return flows;
} catch (error) { }catch(error){
console.error(error); console.error(error);
return flows; return flows;
} }
} }
async SaveFlow(jsonData: any): Promise<boolean> { async SaveFlow(jsonData:any):Promise<boolean>
const result = await api.post('api/flow', jsonData); {
console.info(result.data); const result = await api.post('api/flow',jsonData);
console.info(result.data)
return true; return true;
} }
/** /**
@@ -31,19 +35,10 @@ export class FlowCtrl {
* @param jsonData * @param jsonData
* @returns * @returns
*/ */
async UpdateFlow(jsonData: any): Promise<boolean> { async UpdateFlow(jsonData:any):Promise<boolean>
const result = await api.put('api/flow/' + jsonData.flowid, jsonData); {
console.info(result.data); const result = await api.put('api/flow/' + jsonData.flowid,jsonData);
return true; console.info(result.data)
}
/**
* フローを消去する
* @param flowId
* @returns
*/
async DeleteFlow(flowId: string): Promise<boolean> {
const result = await api.delete('api/flow/' + flowId);
console.info(result.data);
return true; return true;
} }
/** /**
@@ -51,9 +46,12 @@ export class FlowCtrl {
* @param appid * @param appid
* @returns * @returns
*/ */
async depoly(appid: string): Promise<boolean> { async depoly(appid:string):Promise<boolean>
{
const result = await api.post(`api/v1/createjstokintone?app=${appid}`); const result = await api.post(`api/v1/createjstokintone?app=${appid}`);
console.info(result.data); console.info(result.data);
return true; return true;
} }
} }

View File

@@ -1,37 +1,37 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { AppInfo, IActionFlow, IActionNode } from 'src/types/ActionTypes'; import { AppInfo ,IActionFlow, IActionNode} from 'src/types/ActionTypes';
import { IKintoneEvent, KintoneEventManager } from 'src/types/KintoneEvents'; import { IKintoneEvent,KintoneEventManager } from 'src/types/KintoneEvents';
import { FlowCtrl } from '../control/flowctrl'; import {FlowCtrl } from '../control/flowctrl';
export interface FlowEditorState { export interface FlowEditorState{
flowNames1: string; flowNames1:string;
appInfo?: AppInfo; appInfo?:AppInfo;
flows?: IActionFlow[]; flows?:IActionFlow[];
selectedFlow?: IActionFlow | undefined; selectedFlow?:IActionFlow|undefined;
activeNode: IActionNode | undefined; activeNode:IActionNode|undefined;
eventTree: KintoneEventManager; eventTree:KintoneEventManager;
selectedEvent: IKintoneEvent | undefined; selectedEvent:IKintoneEvent|undefined;
expandedScreen: any[]; expandedScreen:any[];
} }
const flowCtrl = new FlowCtrl(); const flowCtrl=new FlowCtrl();
const eventTree = new KintoneEventManager(); const eventTree = new KintoneEventManager();
export const useFlowEditorStore = defineStore('flowEditor', { export const useFlowEditorStore = defineStore("flowEditor",{
state: (): FlowEditorState => ({ state: ():FlowEditorState => ({
flowNames1: '', flowNames1: '',
appInfo: undefined, appInfo:undefined,
flows: [], flows:[],
selectedFlow: undefined, selectedFlow:undefined,
activeNode: undefined, activeNode:undefined,
eventTree: eventTree, eventTree:eventTree,
selectedEvent: undefined, selectedEvent:undefined,
expandedScreen: [], expandedScreen:[]
}), }),
getters: { getters: {
/** /**
* *
* @returns 現在編集しているフロー * @returns 現在編集しているフロー
*/ */
currentFlow(): IActionFlow | undefined { currentFlow():IActionFlow|undefined{
return this.selectedFlow; return this.selectedFlow;
}, },
/** /**
@@ -39,104 +39,80 @@ export const useFlowEditorStore = defineStore('flowEditor', {
* @param state * @param state
* @returns * @returns
*/ */
findFlowByEventId(state) { findFlowByEventId(state){
return (eventId: string) => { return (eventId:string)=>{
return state.flows?.find((flow) => { return state.flows?.find((flow)=>{
const root = flow.getRoot(); const root=flow.getRoot();
return root?.name === eventId; return root?.name===eventId
}); });
}; }
}, }
findEventById(state) {
return (eventId: string) => {
return state.eventTree.findEventById(eventId);
};
},
}, },
actions: { actions: {
setFlows(flows: IActionFlow[]) { setFlows(flows:IActionFlow[]){
this.flows = flows; this.flows=flows;
}, },
selectFlow(flow: IActionFlow | undefined) { selectFlow(flow:IActionFlow){
this.selectedFlow = flow; this.selectedFlow=flow;
}, },
setActiveNode(node: IActionNode) { setActiveNode(node:IActionNode){
this.activeNode = node; this.activeNode=node;
}, },
setApp(app: AppInfo) { setApp(app:AppInfo){
this.appInfo = app; this.appInfo=app;
}, },
/** /**
* DBからフルーを保存する * DBからフルーを保存する
* @returns * @returns
*/ */
async loadFlow() { async loadFlow(){
if (this.appInfo === undefined) return; if(this.appInfo===undefined) return;
const actionFlows = await flowCtrl.getFlows(this.appInfo?.appId); const actionFlows = await flowCtrl.getFlows(this.appInfo?.appId);
//eventTreeにバンドする //eventTreeにバンドする
this.eventTree.bindFlows(actionFlows); this.eventTree.bindFlows(actionFlows);
if (actionFlows === undefined || actionFlows.length === 0) { if(actionFlows===undefined || actionFlows.length===0){
this.flows = []; this.flows=[];
this.selectedFlow = undefined; this.selectedFlow=undefined;
return; return;
} }
this.setFlows(actionFlows); this.setFlows(actionFlows);
if (actionFlows && actionFlows.length > 0) { if(actionFlows && actionFlows.length>0){
this.selectFlow(actionFlows[0]); this.selectFlow(actionFlows[0]);
} }
const expandNames = actionFlows.map((flow) => flow.getRoot()?.title); const expandNames = actionFlows.map(flow=>flow.getRoot()?.title);
// const expandName =actionFlows[0].getRoot()?.title; // const expandName =actionFlows[0].getRoot()?.title;
this.expandedScreen = expandNames; this.expandedScreen=expandNames;
}, },
/** /**
* フローをDBに保存及び更新する * フローをDBに保存及び更新する
*/ */
async saveFlow(flow: IActionFlow) { async saveFlow(flow:IActionFlow){
const root = flow.getRoot(); const root=flow.getRoot();
const isNew = flow.id === ''; const isNew = flow.id==='';
const jsonData = { const jsonData={
flowid: isNew ? flow.createNewId() : flow.id, flowid: isNew ? flow.createNewId():flow.id,
appid: this.appInfo?.appId, appid: this.appInfo?.appId,
eventid: root?.name, eventid: root?.name,
name: root?.subTitle, name: root?.subTitle,
content: JSON.stringify(flow), content: JSON.stringify(flow)
}; }
if (isNew) { if(isNew){
return await flowCtrl.SaveFlow(jsonData); return await flowCtrl.SaveFlow(jsonData);
} else { }else{
return await flowCtrl.UpdateFlow(jsonData); return await flowCtrl.UpdateFlow(jsonData);
} }
}, },
deleteEvent(event: IKintoneEvent) {
const store = useFlowEditorStore();
if (event.flowData) {
const flow = event.flowData;
if (flow.id === '') {
return;
}
flowCtrl.DeleteFlow(flow.id)
eventTree.deleteEvent(event, store);
if(this.flows){
this.flows = this.flows.filter((f) => f.id !== flow.id);
}
} else {
eventTree.deleteEvent(event, store);
}
},
/** /**
* デプロイする * デプロイする
*/ */
async deploy(): Promise<boolean> { async deploy():Promise<boolean>{
if (this.appInfo === undefined) { if(this.appInfo===undefined){
return false; return false;
} }
return await flowCtrl.depoly(this.appInfo?.appId); return await flowCtrl.depoly(this.appInfo?.appId);
}, }
},
}
}); });

View File

@@ -45,7 +45,16 @@ export interface IActionProperty {
export interface IActionVariable{ export interface IActionVariable{
actionName:string; actionName:string;
displayName:string; displayName:string;
name: {
name:string; name:string;
actionName:string;
displayName:string;
vars : {
vName:string;
logicalOperator:string;
field: object;
}[]
};
} }
/** /**
* アクションタイプ定義 * アクションタイプ定義
@@ -448,6 +457,12 @@ export class ActionFlow implements IActionFlow {
getPrevVarNames(prevNode:IActionNode):IActionVariable[]{ getPrevVarNames(prevNode:IActionNode):IActionVariable[]{
let varNames:IActionVariable[]=[]; let varNames:IActionVariable[]=[];
if(prevNode.varName!==undefined && prevNode.varName.modelValue){ if(prevNode.varName!==undefined && prevNode.varName.modelValue){
if(prevNode.varName.modelValue ==='object'){
console.log(prevNode);
}
varNames.unshift({ varNames.unshift({
actionName:prevNode.name, actionName:prevNode.name,
displayName:prevNode.varName.displayName, displayName:prevNode.varName.displayName,

View File

@@ -198,7 +198,7 @@ export class ConditionTree {
if(value && typeof value ==='object' && ('label' in value)){ if(value && typeof value ==='object' && ('label' in value)){
value =condNode.value.label; value =condNode.value.label;
} }
return `${condNode.object.name} ${condNode.operator} '${value}'`; return `${typeof condNode.object.name === 'object' ? condNode.object.name.name : condNode.object.name} ${condNode.operator} '${value}'`;
} else { } else {
return ''; return '';
} }

View File

@@ -1,10 +1,9 @@
import { useFlowEditorStore } from 'src/stores/flowEditor'; import {IActionFlow} from './ActionTypes';
import { IActionFlow } from './ActionTypes';
export interface IKintoneEventNode { export interface IKintoneEventNode {
label: string; label: string;
header: string; header:string;
eventId: string; eventId:string;
parentId: string; parentId:string;
} }
export interface IKintoneEvent extends IKintoneEventNode { export interface IKintoneEvent extends IKintoneEventNode {
@@ -16,64 +15,60 @@ export interface IKintoneEventGroup extends IKintoneEventNode {
events: IKintoneEventNode[]; events: IKintoneEventNode[];
} }
export class kintoneEvent implements IKintoneEvent {
export class kintoneEvent implements IKintoneEvent{
eventId: string; eventId: string;
parentId: string; parentId:string;
get hasFlow(): boolean { get hasFlow(): boolean{
return this.flowData !== undefined && this.flowData.actionNodes.length > 1; return this.flowData!==undefined && this.flowData.actionNodes.length>1
} };
flowData?: IActionFlow | undefined; flowData?: IActionFlow | undefined;
label: string; label: string;
header = 'EVENT'; get header():string{
constructor(label: string, eventId: string, parentId: string) { return "EVENT";
this.eventId = eventId; }
this.label = label; constructor(label:string,eventId:string,parentId:string){
this.parentId = parentId; this.eventId=eventId;
this.label=label;
this.parentId=parentId;
} }
} }
export class kintoneEventGroup implements IKintoneEventGroup { export class kintoneEventGroup implements IKintoneEventGroup{
eventId: string; eventId: string;
parentId: string; parentId:string;
label: string; label: string;
events: IKintoneEventNode[]; events: IKintoneEventNode[];
get header(): string { get header():string{
return 'EVENTGROUP'; return "EVENTGROUP";
} }
constructor( constructor(eventId:string,label:string,events:IKintoneEventNode[],parentId:string){
eventId: string, this.eventId=eventId;
label: string, this.label=label;
events: IKintoneEventNode[], this.events=events;
parentId: string this.parentId=parentId;
) {
this.eventId = eventId;
this.label = label;
this.events = events;
this.parentId = parentId;
} }
} }
export class kintoneEventForChange implements IKintoneEventGroup {
export class kintoneEventForChange implements IKintoneEventGroup{
eventId: string; eventId: string;
parentId: string; parentId:string;
label: string; label: string;
events: IKintoneEventNode[]; events: IKintoneEventNode[];
get header(): string { get header():string{
return 'CHANGE'; return "CHANGE";
} }
constructor( constructor(eventId:string,label:string,events:IKintoneEventNode[],parentId:string){
eventId: string, this.eventId=eventId;
label: string, this.label=label;
events: IKintoneEventNode[], this.events=events;
parentId: string this.parentId=parentId;
) {
this.eventId = eventId;
this.label = label;
this.events = events;
this.parentId = parentId;
} }
} }
export class KintoneEventManager { export class KintoneEventManager {
public screens: IKintoneEventGroup[]; public screens: IKintoneEventGroup[];
@@ -81,35 +76,28 @@ export class KintoneEventManager {
this.screens = this.getKintoneEvents(); this.screens = this.getKintoneEvents();
} }
public bindFlows(flows: IActionFlow[]) { public bindFlows(flows:IActionFlow[]){
this.screens = this.getKintoneEvents(); this.screens=this.getKintoneEvents();
for (const flow of flows) { for (const flow of flows){
const eventId = flow.getRoot()?.name; const eventId =flow.getRoot()?.name;
if (eventId !== undefined) { if(eventId!==undefined){
const eventNode = this.findEventById(eventId); const eventNode = this.findEventById(eventId);
if (eventNode !== null && eventNode.header === 'EVENT') { if(eventNode!==null && eventNode.header==="EVENT"){
const event = eventNode as kintoneEvent; const event =eventNode as kintoneEvent;
event.flowData = flow; event.flowData=flow;
} else { }else{
//EventGroupのIDを取得 //EventGroupのIDを取得
const lastIndex = eventId.lastIndexOf('.'); const lastIndex = eventId.lastIndexOf(".");
const groupId = eventId.substring(0, lastIndex); const groupId=eventId.substring(0,lastIndex);
const eventNode = this.findEventById(groupId); const eventNode = this.findEventById(groupId);
if ( if(eventNode && (eventNode.header==="EVENTGROUP" || eventNode.header==="CHANGE")){
eventNode && const groupEvent=eventNode as kintoneEventGroup;
(eventNode.header === 'EVENTGROUP' || eventNode.header === 'CHANGE') const newEvent =new kintoneEvent(
) { flow.getRoot()?.subTitle || "",
const groupEvent = eventNode as kintoneEventGroup; eventId,
groupEvent.parentId
const newEvent = { );
label: flow.getRoot()?.subTitle || '', newEvent.flowData=flow;
eventId: eventId,
parentId: groupId,
header: 'DELETABLE',
hasFlow: true,
flowData: flow,
};
groupEvent.events.push(newEvent); groupEvent.events.push(newEvent);
} }
} }
@@ -118,21 +106,19 @@ export class KintoneEventManager {
} }
public findEventById(eventId: string): IKintoneEventNode | null { public findEventById(eventId: string): IKintoneEventNode | null {
const screen = this.findScreen(eventId); const screen=this.findScreen(eventId);
if (screen) { if(screen) {return screen;}
return screen;
}
for (const screen of this.screens) { for (const screen of this.screens) {
for (const event of screen.events) { for (const event of screen.events) {
if (event.eventId === eventId) { if (event.eventId === eventId) {
return event; return event;
} }
if (event.header === 'EVENTGROUP' || event.header === 'CHANGE') { if(event.header==="EVENTGROUP"||event.header==="CHANGE"){
const eventGroup = event as IKintoneEventGroup; const eventGroup = event as IKintoneEventGroup;
const targetEvent = eventGroup.events.find((ev) => { const targetEvent = eventGroup.events.find((ev)=>{
return ev.eventId === eventId; return ev.eventId===eventId;
}); })
if (targetEvent) { if(targetEvent){
return targetEvent; return targetEvent;
} }
} }
@@ -141,170 +127,40 @@ export class KintoneEventManager {
return null; return null;
} }
public findScreen(eventId: string): IKintoneEventGroup | undefined { public findScreen(eventId:string):IKintoneEventGroup|undefined{
return this.screens.find((screen) => screen.eventId == eventId); return this.screens.find(screen=>screen.eventId==eventId);
} }
public deleteEvent( public getKintoneEvents():IKintoneEventGroup[]{
event: kintoneEvent,
store: ReturnType<typeof useFlowEditorStore>
) {
if (event.header !== 'DELETABLE') {
return;
}
const parent = store.findEventById(event.parentId);
if (parent?.header !== 'CHANGE' && parent?.header !== 'EVENTGROUP') {
return;
}
const realParent = parent as kintoneEventForChange;
const index = realParent.events.findIndex(
(e) => e.eventId === event.eventId
);
if (index !== -1) {
realParent.events.splice(index, 1);
}
}
public getKintoneEvents(): IKintoneEventGroup[] {
return [ return [
new kintoneEventGroup( new kintoneEventGroup("app.record.create","レコード追加画面",[
'app.record.create', new kintoneEvent('レコード追加画面を表示した後','app.record.create.show',"app.record.create"),
'レコード追加画面', new kintoneEvent('保存をクリックしたとき','app.record.create.submit',"app.record.create"),
[ new kintoneEvent('保存が成功したとき','app.record.create.submit.success',"app.record.create"),
new kintoneEvent( new kintoneEventForChange('app.record.create.change','フィールドの値を変更したとき',[],"app.record.create"),
'レコード追加画面を表示した', new kintoneEventGroup('app.record.create.show.customButtonClick','ボタンをクリックした',[],"app.record.create")
'app.record.create.show', ],""),
'app.record.create' new kintoneEventGroup("app.record.detail","レコード詳細画面",[
), new kintoneEvent('レコード詳細画面を表示した後','app.record.detail.show',"app.record.detail"),
new kintoneEvent( new kintoneEvent('レコードを削除するとき','app.record.detail.delete.submit',"app.record.detail"),
'保存をクリックしたとき', new kintoneEvent('プロセス管理のアクションを実行したとき','app.record.detail.process.proceed',"app.record.detail"),
'app.record.create.submit', new kintoneEventGroup('app.record.detail.show.customButtonClick','ボタンをクリックした時',[],"app.record.detail"),
'app.record.create' ],""),
), new kintoneEventGroup("app.record.edit","レコード編集画面",[
new kintoneEvent( new kintoneEvent('レコード編集画面を表示した後','app.record.edit.show',"app.record.edit"),
'保存が成功したとき', new kintoneEvent('保存をクリックしたとき','app.record.edit.submit',"app.record.edit"),
'app.record.create.submit.success', new kintoneEvent('保存が成功したとき','app.record.edit.submit.success',"app.record.edit"),
'app.record.create' new kintoneEventForChange('app.record.edit.change','フィールドの値を変更したとき',[],"app.record.edit"),
), new kintoneEventGroup('app.record.edit.show.customButtonClick','ボタンをクリックした時',[],"app.record.edit"),
new kintoneEventForChange( ],""),
'app.record.create.change', new kintoneEventGroup("app.record.index","レコード一覧画面",[
'フィールドの値を変更したとき', new kintoneEvent('一覧画面を表示した後', 'app.record.index.show',"app.record.index"),
[], new kintoneEvent('インライン編集を開始したとき','app.record.index.edit.show',"app.record.index"),
'app.record.create' new kintoneEvent('インライン編集の【保存】をクリックしたとき','app.record.index.edit.submit',"app.record.index"),
), new kintoneEvent('インライン編集の保存が成功したとき', 'app.record.index.edit.submit.success',"app.record.index"),
new kintoneEventGroup( new kintoneEventForChange('app.record.index.edit.change','インライン編集のフィールド値を変更したとき' ,[],"app.record.index"),
'app.record.create.show.customButtonClick', new kintoneEventGroup('app.record.detail.show.customButtonClick','ボタンをクリックした時',[],"app.record.index"),
'ボタンをクリックした時', ],"")
[],
'app.record.create'
),
],
''
),
new kintoneEventGroup(
'app.record.detail',
'レコード詳細画面',
[
new kintoneEvent(
'レコード詳細画面を表示した後',
'app.record.detail.show',
'app.record.detail'
),
new kintoneEvent(
'レコードを削除するとき',
'app.record.detail.delete.submit',
'app.record.detail'
),
new kintoneEvent(
'プロセス管理のアクションを実行したとき',
'app.record.detail.process.proceed',
'app.record.detail'
),
new kintoneEventGroup(
'app.record.detail.show.customButtonClick',
'ボタンをクリックした時',
[],
'app.record.detail'
),
],
''
),
new kintoneEventGroup(
'app.record.edit',
'レコード編集画面',
[
new kintoneEvent(
'レコード編集画面を表示した後',
'app.record.edit.show',
'app.record.edit'
),
new kintoneEvent(
'保存をクリックしたとき',
'app.record.edit.submit',
'app.record.edit'
),
new kintoneEvent(
'保存が成功したとき',
'app.record.edit.submit.success',
'app.record.edit'
),
new kintoneEventForChange(
'app.record.edit.change',
'フィールドの値を変更したとき',
[],
'app.record.edit'
),
new kintoneEventGroup(
'app.record.edit.show.customButtonClick',
'ボタンをクリックした時',
[],
'app.record.edit'
),
],
''
),
new kintoneEventGroup(
'app.record.index',
'レコード一覧画面',
[
new kintoneEvent(
'一覧画面を表示した後',
'app.record.index.show',
'app.record.index'
),
new kintoneEvent(
'インライン編集を開始したとき',
'app.record.index.edit.show',
'app.record.index'
),
new kintoneEvent(
'インライン編集の【保存】をクリックしたとき',
'app.record.index.edit.submit',
'app.record.index'
),
new kintoneEvent(
'インライン編集の保存が成功したとき',
'app.record.index.edit.submit.success',
'app.record.index'
),
new kintoneEventForChange(
'app.record.index.edit.change',
'インライン編集のフィールド値を変更したとき',
[],
'app.record.index'
),
new kintoneEventGroup(
'app.record.detail.show.customButtonClick',
'ボタンをクリックした時',
[],
'app.record.index'
),
],
''
),
]; ];
} }
} }