293 lines
8.6 KiB
Vue
293 lines
8.6 KiB
Vue
<template>
|
|
<q-page>
|
|
<q-layout container class="absolute-full shadow-2 rounded-borders">
|
|
<div class="q-pa-sm q-gutter-sm ">
|
|
<q-drawer side="left" :overlay="true" bordered v-model="drawerLeft" :show-if-above="false" elevated>
|
|
<div class="flex-center fixed-top app-selector">
|
|
<AppSelector />
|
|
</div>
|
|
|
|
<div class="flex-center absolute-full" style="padding-top:65px;padding-left:15px;padding-right:15px;">
|
|
<q-scroll-area class="fit" :horizontal-thumb-style="{ opacity: '0' }">
|
|
<EventTree />
|
|
</q-scroll-area>
|
|
</div>
|
|
|
|
<div class="flex-center fixed-bottom bg-grey-3 q-pa-md row ">
|
|
<q-btn color="secondary" glossy label="デプロイ" @click="onDeploy" icon="sync" :loading="deployLoading" />
|
|
<q-space></q-space>
|
|
<q-btn color="primary" label="保存" @click="onSaveFlow" icon="save" :loading="saveLoading" />
|
|
</div>
|
|
</q-drawer>
|
|
</div>
|
|
<q-btn flat dense round :icon="drawerLeft ? 'keyboard_double_arrow_left' : 'keyboard_double_arrow_right'"
|
|
:style="[drawerLeft ? { 'left': '300px' } : { 'left': '0px' }]" @click="drawerLeft = !drawerLeft"
|
|
class="expand" />
|
|
<div class="q-pa-md q-gutter-sm" :style="{ minWidth: minPanelWidth }">
|
|
<div class="flowchart" v-if="store.currentFlow" :style="[drawerLeft ? { paddingLeft: '300px' } : {}]">
|
|
<node-item v-if="rootNode !== undefined" :key="rootNode.id" :isSelected="rootNode === store.activeNode"
|
|
:actionNode="rootNode" @addNode="addNode" @nodeSelected="onNodeSelected" @nodeEdit="onNodeEdit"
|
|
@deleteNode="onDeleteNode" @deleteAllNextNodes="onDeleteAllNextNodes" @copyFlow="onCopyFlow"></node-item>
|
|
</div>
|
|
</div>
|
|
<PropertyPanel :actionNode="store.activeNode" v-model:drawerRight="drawerRight"></PropertyPanel>
|
|
</q-layout>
|
|
<ShowDialog v-model:visible="showAddAction" name="アクション" @close="closeDg" min-width="500px" min-height="500px">
|
|
<template v-slot:toolbar>
|
|
<q-input dense debounce="200" v-model="filter" placeholder="検索" clearable>
|
|
<template v-slot:before>
|
|
<q-icon name="search" />
|
|
</template>
|
|
</q-input>
|
|
</template>
|
|
<action-select ref="appDg" name="model" :filter="filter" type="single"></action-select>
|
|
</ShowDialog>
|
|
|
|
</q-page>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive, computed, onMounted } from 'vue';
|
|
import { IActionNode, ActionNode, IActionFlow, ActionFlow, RootAction, IActionProperty } from 'src/types/ActionTypes';
|
|
import { storeToRefs } from 'pinia';
|
|
import { useFlowEditorStore } from 'stores/flowEditor';
|
|
import { useAuthStore } from 'stores/useAuthStore';
|
|
|
|
import NodeItem from 'src/components/main/NodeItem.vue';
|
|
import ShowDialog from 'components/ShowDialog.vue';
|
|
import ActionSelect from 'components/ActionSelect.vue';
|
|
import PropertyPanel from 'components/right/PropertyPanel.vue';
|
|
import AppSelector from 'components/left/AppSelector.vue';
|
|
import EventTree from 'components/left/EventTree.vue';
|
|
import { FlowCtrl } from '../control/flowctrl';
|
|
import { useQuasar } from 'quasar';
|
|
const deployLoading = ref(false);
|
|
const saveLoading = ref(false);
|
|
const drawerLeft = ref(false);
|
|
const $q = useQuasar();
|
|
const store = useFlowEditorStore();
|
|
const authStore = useAuthStore();
|
|
|
|
const appDg = ref();
|
|
const prevNodeIfo = ref({
|
|
prevNode: {} as IActionNode,
|
|
inputPoint: ""
|
|
});
|
|
// const refFlow = ref<ActionFlow|null>(null);
|
|
const showAddAction = ref(false);
|
|
const drawerRight = ref(false);
|
|
const filter = ref("");
|
|
const model = ref("");
|
|
const addActionNode = (action: IActionNode) => {
|
|
// refFlow.value?.actionNodes.push(action);
|
|
store.currentFlow?.actionNodes.push(action);
|
|
}
|
|
const rootNode = computed(() => {
|
|
return store.currentFlow?.getRoot();
|
|
});
|
|
const minPanelWidth = computed(() => {
|
|
const root = store.currentFlow?.getRoot();
|
|
if (store.currentFlow && root) {
|
|
return store.currentFlow?.getColumns(root) * 300 + 'px';
|
|
} else {
|
|
return "300px";
|
|
}
|
|
});
|
|
|
|
const addNode = (node: IActionNode, inputPoint: string) => {
|
|
if (drawerRight.value) {
|
|
drawerRight.value = false;
|
|
}
|
|
showAddAction.value = true;
|
|
prevNodeIfo.value.prevNode = node;
|
|
prevNodeIfo.value.inputPoint = inputPoint;
|
|
}
|
|
|
|
const onNodeSelected = (node: IActionNode) => {
|
|
//右パネルが開いている場合、自動閉じる
|
|
if (drawerRight.value && store.activeNode?.id !== node.id) {
|
|
drawerRight.value = false;
|
|
}
|
|
store.setActiveNode(node);
|
|
}
|
|
|
|
const onNodeEdit = (node: IActionNode) => {
|
|
store.setActiveNode(node);
|
|
drawerRight.value = true;
|
|
}
|
|
|
|
const onDeleteNode = (node: IActionNode) => {
|
|
if (!store.currentFlow) return;
|
|
//右パネルが開いている場合、自動閉じる
|
|
if (drawerRight.value && store.activeNode?.id === node.id) {
|
|
drawerRight.value = false;
|
|
}
|
|
store.currentFlow?.removeNode(node);
|
|
}
|
|
|
|
const onDeleteAllNextNodes = (node: IActionNode) => {
|
|
if (!store.currentFlow) return;
|
|
//右パネルが開いている場合、自動閉じる
|
|
if (drawerRight.value) {
|
|
drawerRight.value = false;
|
|
}
|
|
store.currentFlow?.removeAllNext(node.id);
|
|
}
|
|
const closeDg = (val: any) => {
|
|
console.log("Dialog closed->", val);
|
|
if (val == 'OK') {
|
|
const data = appDg.value.selected[0];
|
|
const actionProps = JSON.parse(data.property);
|
|
const outputPoint = JSON.parse(data.outputPoints);
|
|
const action = new ActionNode(data.name, data.desc, "", outputPoint, actionProps);
|
|
store.currentFlow?.addNode(action, prevNodeIfo.value.prevNode, prevNodeIfo.value.inputPoint);
|
|
}
|
|
}
|
|
/*
|
|
*フローのデータをコピーする
|
|
*/
|
|
const onCopyFlow = () => {
|
|
if (navigator.clipboard) {
|
|
const jsonData = JSON.stringify(store.currentFlow);
|
|
navigator.clipboard.writeText(jsonData).then(() => {
|
|
console.log('Text successfully copied to clipboard');
|
|
},
|
|
(err) => {
|
|
console.error('Error in copying text: ', err);
|
|
});
|
|
} else {
|
|
console.log('Clipboard API not available');
|
|
}
|
|
};
|
|
/**
|
|
* デプロイ
|
|
*/
|
|
const onDeploy = async () => {
|
|
if (store.appInfo === undefined || store.flows?.length === 0) {
|
|
$q.notify({
|
|
type: 'negative',
|
|
caption: "エラー",
|
|
message: `設定されたフローがありません。`
|
|
});
|
|
return;
|
|
}
|
|
try {
|
|
deployLoading.value = true;
|
|
await store.deploy();
|
|
deployLoading.value = false;
|
|
$q.notify({
|
|
type: 'positive',
|
|
caption: "通知",
|
|
message: `デプロイを成功しました。`
|
|
});
|
|
} catch (error) {
|
|
console.error(error);
|
|
deployLoading.value = false;
|
|
$q.notify({
|
|
type: 'negative',
|
|
caption: "エラー",
|
|
message: `デプロイが失敗しました。`
|
|
})
|
|
}
|
|
return;
|
|
}
|
|
|
|
const onSaveFlow = async () => {
|
|
const targetFlow = store.selectedFlow;
|
|
|
|
const deleteFlows = () => {
|
|
$q.notify({
|
|
type: 'positive',
|
|
caption: "通知",
|
|
message: `削除 ${store.deleteFlowIds.length} イベント`
|
|
});
|
|
store.deletebackendFlow();
|
|
}
|
|
|
|
if (targetFlow === undefined) {
|
|
if (store.deleteFlowIds.length > 0) {
|
|
deleteFlows();
|
|
return;
|
|
}
|
|
|
|
$q.notify({
|
|
type: 'negative',
|
|
caption: "エラー",
|
|
message: `編集中のフローがありません。`
|
|
});
|
|
return;
|
|
}
|
|
try {
|
|
if (store.deleteFlowIds.length > 0) {
|
|
deleteFlows();
|
|
}
|
|
saveLoading.value = true;
|
|
await store.saveFlow(targetFlow);
|
|
saveLoading.value = false;
|
|
$q.notify({
|
|
type: 'positive',
|
|
caption: "通知",
|
|
message: `${targetFlow.getRoot()?.subTitle}のフロー設定を保存しました。`
|
|
});
|
|
} catch (error) {
|
|
console.error(error);
|
|
saveLoading.value = false;
|
|
$q.notify({
|
|
type: 'negative',
|
|
caption: "エラー",
|
|
message: `${targetFlow.getRoot()?.subTitle}のフローの設定の保存が失敗しました。`
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
const fetchData = async () => {
|
|
drawerLeft.value = true;
|
|
if (store.appInfo === undefined) return;
|
|
const flowCtrl = new FlowCtrl();
|
|
const actionFlows = await flowCtrl.getFlows(store.appInfo?.appId);
|
|
if (actionFlows && actionFlows.length > 0) {
|
|
store.setFlows(actionFlows);
|
|
}
|
|
if (actionFlows && actionFlows.length == 1) {
|
|
store.selectFlow(actionFlows[0]);
|
|
}
|
|
const root = actionFlows[0].getRoot();
|
|
if (root) {
|
|
store.setActiveNode(root);
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
authStore.toggleLeftMenu();
|
|
fetchData();
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.app-selector {
|
|
padding: 15px;
|
|
z-index: 999;
|
|
}
|
|
|
|
.flowchart {
|
|
padding-top: 10px;
|
|
}
|
|
|
|
.flow-toolbar {
|
|
opacity: 50%;
|
|
}
|
|
|
|
.event-tree .q-drawer {
|
|
top: 50px;
|
|
z-index: 999;
|
|
}
|
|
|
|
.expand {
|
|
position: fixed;
|
|
left: 0px;
|
|
top: 50%;
|
|
z-index: 9999;
|
|
}
|
|
</style>
|