EditorLocalActions.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803
  1. <template>
  2. <span
  3. v-if="isTeam && workflow.tag"
  4. :class="tagColors[workflow.tag]"
  5. class="text-sm rounded-md text-black capitalize p-1 mr-2"
  6. >
  7. {{ workflow.tag }}
  8. </span>
  9. <ui-card
  10. v-if="!isTeam"
  11. padding="p-1"
  12. class="flex items-center pointer-events-auto ml-4"
  13. >
  14. <ui-popover>
  15. <template #trigger>
  16. <button
  17. v-tooltip.group="t('workflow.host.title')"
  18. class="hoverable p-2 rounded-lg"
  19. >
  20. <v-remixicon
  21. :class="{ 'text-primary': hosted }"
  22. name="riBaseStationLine"
  23. />
  24. </button>
  25. </template>
  26. <div :class="{ 'text-center': state.isUploadingHost }" class="w-64">
  27. <div class="flex items-center text-gray-600 dark:text-gray-200">
  28. <p>
  29. {{ t('workflow.host.set') }}
  30. </p>
  31. <a
  32. :title="t('common.docs')"
  33. href="https://docs.automa.site/workflow/sharing-workflow.html#host-workflow"
  34. target="_blank"
  35. class="ml-1"
  36. >
  37. <v-remixicon name="riInformationLine" size="20" />
  38. </a>
  39. <div class="flex-grow"></div>
  40. <ui-spinner v-if="state.isUploadingHost" color="text-accent" />
  41. <ui-switch
  42. v-else
  43. :model-value="Boolean(hosted)"
  44. @change="setAsHostWorkflow"
  45. />
  46. </div>
  47. <transition-expand>
  48. <ui-input
  49. v-if="hosted"
  50. v-tooltip:bottom="t('workflow.host.id')"
  51. :model-value="hosted.hostId"
  52. prepend-icon="riLinkM"
  53. readonly
  54. class="mt-4 block w-full"
  55. @click="$event.target.select()"
  56. />
  57. </transition-expand>
  58. </div>
  59. </ui-popover>
  60. <ui-popover :disabled="userDontHaveTeamsAccess">
  61. <template #trigger>
  62. <button
  63. v-tooltip.group="t('workflow.share.title')"
  64. :class="{ 'text-primary': shared }"
  65. class="hoverable p-2 rounded-lg"
  66. @click="shareWorkflow(!userDontHaveTeamsAccess)"
  67. >
  68. <v-remixicon name="riShareLine" />
  69. </button>
  70. </template>
  71. <p class="font-semibold">Share the workflow</p>
  72. <ui-list class="mt-2 space-y-1 w-56">
  73. <ui-list-item
  74. v-close-popover
  75. class="cursor-pointer"
  76. @click="shareWorkflowWithTeam"
  77. >
  78. <v-remixicon name="riTeamLine" class="-ml-1 mr-2" />
  79. With your team
  80. </ui-list-item>
  81. <ui-list-item
  82. v-close-popover
  83. class="cursor-pointer"
  84. @click="shareWorkflow()"
  85. >
  86. <v-remixicon name="riGroupLine" class="-ml-1 mr-2" />
  87. With the community
  88. </ui-list-item>
  89. </ui-list>
  90. </ui-popover>
  91. </ui-card>
  92. <ui-card
  93. v-if="canEdit"
  94. padding="p-1 ml-4 hidden md:block pointer-events-auto"
  95. >
  96. <button
  97. v-for="item in modalActions"
  98. :key="item.id"
  99. v-tooltip.group="item.name"
  100. class="hoverable p-2 rounded-lg"
  101. @click="$emit('modal', item.id)"
  102. >
  103. <v-remixicon :name="item.icon" />
  104. </button>
  105. </ui-card>
  106. <ui-card padding="p-1 ml-4 flex items-center pointer-events-auto">
  107. <ui-popover v-if="canEdit" class="md:hidden">
  108. <template #trigger>
  109. <button class="rounded-lg p-2 hoverable">
  110. <v-remixicon name="riMore2Line" />
  111. </button>
  112. </template>
  113. <ui-list class="space-y-1 cursor-pointer">
  114. <ui-list-item
  115. v-for="item in modalActions"
  116. :key="item.id"
  117. v-close-popover
  118. @click="$emit('modal', item.id)"
  119. >
  120. <v-remixicon :name="item.icon" class="mr-2 -ml-1" />
  121. {{ item.name }}
  122. </ui-list-item>
  123. </ui-list>
  124. </ui-popover>
  125. <button
  126. v-if="!workflow.isDisabled"
  127. v-tooltip.group="
  128. `${t('common.execute')} (${
  129. shortcuts['editor:execute-workflow'].readable
  130. })`
  131. "
  132. class="hoverable p-2 rounded-lg"
  133. @click="executeCurrWorkflow"
  134. >
  135. <v-remixicon name="riPlayLine" />
  136. </button>
  137. <button
  138. v-else
  139. v-tooltip="t('workflow.clickToEnable')"
  140. class="p-2"
  141. @click="updateWorkflow({ isDisabled: false })"
  142. >
  143. {{ t('common.disabled') }}
  144. </button>
  145. </ui-card>
  146. <ui-card padding="p-1 ml-4 space-x-1 pointer-events-auto flex items-center">
  147. <button
  148. v-if="!canEdit"
  149. v-tooltip.group="state.triggerText"
  150. class="p-2 hoverable rounded-lg"
  151. >
  152. <v-remixicon name="riFlashlightLine" />
  153. </button>
  154. <ui-popover>
  155. <template #trigger>
  156. <button class="rounded-lg p-2 hoverable">
  157. <v-remixicon name="riMore2Line" />
  158. </button>
  159. </template>
  160. <ui-list style="min-width: 9rem">
  161. <ui-list-item
  162. v-close-popover
  163. class="cursor-pointer"
  164. @click="copyWorkflowId"
  165. >
  166. <v-remixicon name="riFileCopyLine" class="mr-2 -ml-1" />
  167. Copy workflow Id
  168. </ui-list-item>
  169. <ui-list-item
  170. v-if="isTeam && canEdit"
  171. v-close-popover
  172. class="cursor-pointer"
  173. @click="syncWorkflow"
  174. >
  175. <v-remixicon name="riRefreshLine" class="mr-2 -ml-1" />
  176. <span>{{ t('workflow.host.sync.title') }}</span>
  177. </ui-list-item>
  178. <ui-list-item
  179. class="cursor-pointer"
  180. @click="updateWorkflow({ isDisabled: !workflow.isDisabled })"
  181. >
  182. <v-remixicon name="riToggleLine" class="mr-2 -ml-1" />
  183. {{ t(`common.${workflow.isDisabled ? 'enable' : 'disable'}`) }}
  184. </ui-list-item>
  185. <ui-list-item
  186. v-for="item in moreActions"
  187. :key="item.id"
  188. v-bind="item.attrs || {}"
  189. v-close-popover
  190. class="cursor-pointer"
  191. @click="item.action"
  192. >
  193. <v-remixicon :name="item.icon" class="mr-2 -ml-1" />
  194. {{ item.name }}
  195. </ui-list-item>
  196. <ui-list-item
  197. v-if="
  198. isTeam &&
  199. canEdit &&
  200. userStore.validateTeamAccess(teamId, ['owner', 'create'])
  201. "
  202. v-close-popover
  203. class="cursor-pointer text-red-400 dark:text-red-500"
  204. @click="deleteFromTeam"
  205. >
  206. <v-remixicon name="riDeleteBin7Line" class="mr-2 -ml-1" />
  207. <span>Delete from team</span>
  208. </ui-list-item>
  209. </ui-list>
  210. </ui-popover>
  211. <ui-button
  212. v-if="!isTeam"
  213. :title="shortcuts['editor:save'].readable"
  214. variant="accent"
  215. class="relative px-2 md:px-4"
  216. @click="saveWorkflow"
  217. >
  218. <span
  219. v-if="isDataChanged"
  220. class="flex h-3 w-3 absolute top-0 left-0 -ml-1 -mt-1"
  221. >
  222. <span
  223. class="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"
  224. ></span>
  225. <span
  226. class="relative inline-flex rounded-full h-3 w-3 bg-blue-600"
  227. ></span>
  228. </span>
  229. <v-remixicon name="riSaveLine" class="md:-ml-1 my-1" />
  230. <span class="hidden md:block ml-2">{{ t('common.save') }}</span>
  231. </ui-button>
  232. <ui-button
  233. v-else-if="!canEdit"
  234. v-tooltip.group="'Sync workflow'"
  235. :loading="state.loadingSync"
  236. variant="accent"
  237. @click="syncWorkflow"
  238. >
  239. <v-remixicon name="riRefreshLine" class="mr-2 -ml-1" />
  240. <span>
  241. {{ t('workflow.host.sync.title') }}
  242. </span>
  243. </ui-button>
  244. <template v-else>
  245. <ui-button
  246. v-tooltip="`Save workflow (${shortcuts['editor:save'].readable})`"
  247. class="mr-2"
  248. icon
  249. @click="saveWorkflow"
  250. >
  251. <span
  252. v-if="isDataChanged"
  253. class="flex h-3 w-3 absolute top-0 left-0 -ml-1 -mt-1"
  254. >
  255. <span
  256. class="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"
  257. ></span>
  258. <span
  259. class="relative inline-flex rounded-full h-3 w-3 bg-blue-600"
  260. ></span>
  261. </span>
  262. <v-remixicon name="riSaveLine" />
  263. </ui-button>
  264. <ui-button
  265. v-tooltip="'Publish workflow update'"
  266. :loading="state.isPublishing"
  267. variant="accent"
  268. @click="publishWorkflow"
  269. >
  270. Publish
  271. </ui-button>
  272. </template>
  273. </ui-card>
  274. <ui-modal v-model="state.showEditDescription" persist blur custom-content>
  275. <workflow-share-team
  276. :workflow="workflow"
  277. :is-update="true"
  278. @update="updateWorkflowDescription"
  279. @close="state.showEditDescription = false"
  280. />
  281. </ui-modal>
  282. <ui-modal v-model="renameState.showModal" title="Rename">
  283. <ui-input
  284. v-model="renameState.name"
  285. :placeholder="t('common.name')"
  286. autofocus
  287. class="w-full mb-4"
  288. @keyup.enter="renameWorkflow"
  289. />
  290. <ui-textarea
  291. v-model="renameState.description"
  292. :placeholder="t('common.description')"
  293. height="165px"
  294. class="w-full dark:text-gray-200"
  295. max="300"
  296. style="min-height: 140px"
  297. />
  298. <p class="mb-6 text-right text-gray-600 dark:text-gray-200">
  299. {{ renameState.description.length }}/300
  300. </p>
  301. <div class="space-x-2 flex">
  302. <ui-button class="w-full" @click="clearRenameModal">
  303. {{ t('common.cancel') }}
  304. </ui-button>
  305. <ui-button variant="accent" class="w-full" @click="renameWorkflow">
  306. {{ t('common.update') }}
  307. </ui-button>
  308. </div>
  309. </ui-modal>
  310. </template>
  311. <script setup>
  312. import { reactive, computed } from 'vue';
  313. import { useI18n } from 'vue-i18n';
  314. import { useRouter } from 'vue-router';
  315. import { useToast } from 'vue-toastification';
  316. import browser from 'webextension-polyfill';
  317. import { fetchApi } from '@/utils/api';
  318. import { useUserStore } from '@/stores/user';
  319. import { useWorkflowStore } from '@/stores/workflow';
  320. import { useTeamWorkflowStore } from '@/stores/teamWorkflow';
  321. import { useSharedWorkflowStore } from '@/stores/sharedWorkflow';
  322. import { usePackageStore } from '@/stores/package';
  323. import { useDialog } from '@/composable/dialog';
  324. import { useGroupTooltip } from '@/composable/groupTooltip';
  325. import { useShortcut, getShortcut } from '@/composable/shortcut';
  326. import { tagColors } from '@/utils/shared';
  327. import { parseJSON, findTriggerBlock } from '@/utils/helper';
  328. import { exportWorkflow, convertWorkflow } from '@/utils/workflowData';
  329. import { registerWorkflowTrigger } from '@/utils/workflowTrigger';
  330. import { executeWorkflow } from '@/newtab/workflowEngine';
  331. import getTriggerText from '@/utils/triggerText';
  332. import convertWorkflowData from '@/utils/convertWorkflowData';
  333. import WorkflowShareTeam from '@/components/newtab/workflow/WorkflowShareTeam.vue';
  334. const props = defineProps({
  335. isDataChanged: {
  336. type: Boolean,
  337. default: false,
  338. },
  339. workflow: {
  340. type: Object,
  341. default: () => ({}),
  342. },
  343. editor: {
  344. type: Object,
  345. default: () => ({}),
  346. },
  347. changedData: {
  348. type: Object,
  349. default: () => ({}),
  350. },
  351. canEdit: {
  352. type: Boolean,
  353. default: true,
  354. },
  355. isTeam: Boolean,
  356. isPackage: Boolean,
  357. });
  358. const emit = defineEmits(['modal', 'change', 'update', 'permission']);
  359. useGroupTooltip();
  360. const { t } = useI18n();
  361. const toast = useToast();
  362. const router = useRouter();
  363. const dialog = useDialog();
  364. const userStore = useUserStore();
  365. const packageStore = usePackageStore();
  366. const workflowStore = useWorkflowStore();
  367. const teamWorkflowStore = useTeamWorkflowStore();
  368. const sharedWorkflowStore = useSharedWorkflowStore();
  369. const shortcuts = useShortcut([
  370. /* eslint-disable-next-line */
  371. getShortcut('editor:save', saveWorkflow),
  372. /* eslint-disable-next-line */
  373. getShortcut('editor:execute-workflow', executeCurrWorkflow),
  374. ]);
  375. const { teamId } = router.currentRoute.value.params;
  376. const state = reactive({
  377. triggerText: '',
  378. loadingSync: false,
  379. isPublishing: false,
  380. isUploadingHost: false,
  381. showEditDescription: false,
  382. });
  383. const renameState = reactive({
  384. name: '',
  385. description: '',
  386. showModal: false,
  387. });
  388. const shared = computed(() => sharedWorkflowStore.getById(props.workflow.id));
  389. const hosted = computed(() => userStore.hostedWorkflows[props.workflow.id]);
  390. const userDontHaveTeamsAccess = computed(() => {
  391. if (props.isTeam || !userStore.user?.teams) return true;
  392. return !userStore.user.teams.some((team) =>
  393. team.access.some((item) => ['owner', 'create'].includes(item))
  394. );
  395. });
  396. function copyWorkflowId() {
  397. navigator.clipboard.writeText(props.workflow.id).catch((error) => {
  398. console.error(error);
  399. const textarea = document.createElement('textarea');
  400. textarea.value = props.workflow.id;
  401. textarea.select();
  402. document.execCommand('copy');
  403. textarea.blur();
  404. });
  405. }
  406. function updateWorkflow(data = {}, changedIndicator = false) {
  407. let store = null;
  408. if (props.isTeam) {
  409. store = teamWorkflowStore.update({
  410. data,
  411. teamId,
  412. id: props.workflow.id,
  413. });
  414. } else {
  415. store = workflowStore.update({
  416. data,
  417. id: props.workflow.id,
  418. });
  419. }
  420. return store.then((result) => {
  421. emit('update', { data, changedIndicator });
  422. return result;
  423. });
  424. }
  425. function updateWorkflowDescription(value) {
  426. const keys = ['description', 'category', 'content', 'tag', 'name'];
  427. const payload = {};
  428. keys.forEach((key) => {
  429. payload[key] = value[key];
  430. });
  431. updateWorkflow(payload);
  432. state.showEditDescription = false;
  433. }
  434. function executeCurrWorkflow() {
  435. executeWorkflow({
  436. ...props.workflow,
  437. isTesting: props.isDataChanged,
  438. });
  439. }
  440. async function setAsHostWorkflow(isHost) {
  441. if (!userStore.user) {
  442. dialog.custom('auth', {
  443. title: t('auth.title'),
  444. });
  445. return;
  446. }
  447. state.isUploadingHost = true;
  448. try {
  449. let url = '/me/workflows';
  450. let payload = {};
  451. if (isHost) {
  452. const workflowPaylod = convertWorkflow(props.workflow, ['id']);
  453. workflowPaylod.drawflow = parseJSON(
  454. props.workflow.drawflow,
  455. props.workflow.drawflow
  456. );
  457. delete workflowPaylod.extVersion;
  458. url += `/host`;
  459. payload = {
  460. method: 'POST',
  461. body: JSON.stringify({
  462. workflow: workflowPaylod,
  463. }),
  464. };
  465. } else {
  466. url += `?id=${props.workflow.id}&type=host`;
  467. payload.method = 'DELETE';
  468. }
  469. const response = await fetchApi(url, payload);
  470. const result = await response.json();
  471. if (!response.ok) {
  472. const error = new Error(result.message);
  473. error.data = result.data;
  474. throw error;
  475. }
  476. if (isHost) {
  477. userStore.hostedWorkflows[props.workflow.id] = result;
  478. } else {
  479. delete userStore.hostedWorkflows[props.workflow.id];
  480. }
  481. // Update cache
  482. const userWorkflows = parseJSON('user-workflows', {
  483. backup: [],
  484. hosted: {},
  485. });
  486. userWorkflows.hosted = userStore.hostedWorkflows;
  487. sessionStorage.setItem('user-workflows', JSON.stringify(userWorkflows));
  488. state.isUploadingHost = false;
  489. } catch (error) {
  490. console.error(error);
  491. state.isUploadingHost = false;
  492. toast.error(error.message);
  493. }
  494. }
  495. function shareWorkflowWithTeam() {
  496. emit('modal', 'workflow-share-team');
  497. }
  498. function shareWorkflow(disabled = false) {
  499. if (disabled) return;
  500. if (shared.value) {
  501. router.push(`/workflows/${props.workflow.id}/shared`);
  502. return;
  503. }
  504. if (userStore.user) {
  505. emit('modal', 'workflow-share');
  506. } else {
  507. dialog.custom('auth', {
  508. title: t('auth.title'),
  509. });
  510. }
  511. }
  512. function deleteFromTeam() {
  513. dialog.confirm({
  514. async: true,
  515. title: 'Delete workflow from team',
  516. okVariant: 'danger',
  517. body: `Are you sure want to delete the "${props.workflow.name}" workflow from this team?`,
  518. onConfirm: async () => {
  519. try {
  520. const response = await fetchApi(
  521. `/teams/${teamId}/workflows/${props.workflow.id}`,
  522. { method: 'DELETE' }
  523. );
  524. const result = await response.json();
  525. if (!response.ok && response.status !== 404)
  526. throw new Error(result.message);
  527. await teamWorkflowStore.delete(teamId, props.workflow.id);
  528. router.replace(`/workflows?active=team&teamId=${teamId}`);
  529. return true;
  530. } catch (error) {
  531. toast.error('Something went wrong');
  532. console.error(error);
  533. return false;
  534. }
  535. },
  536. });
  537. }
  538. function clearRenameModal() {
  539. Object.assign(renameState, {
  540. id: '',
  541. name: '',
  542. description: '',
  543. showModal: false,
  544. });
  545. }
  546. async function publishWorkflow() {
  547. if (!props.canEdit) return;
  548. const workflowPaylod = convertWorkflow(props.workflow, [
  549. 'id',
  550. 'tag',
  551. 'content',
  552. ]);
  553. workflowPaylod.drawflow = parseJSON(
  554. props.workflow.drawflow,
  555. props.workflow.drawflow
  556. );
  557. delete workflowPaylod.id;
  558. delete workflowPaylod.extVersion;
  559. state.isPublishing = true;
  560. try {
  561. const response = await fetchApi(
  562. `/teams/${teamId}/workflows/${props.workflow.id}`,
  563. {
  564. method: 'PATCH',
  565. body: JSON.stringify({ workflow: workflowPaylod }),
  566. }
  567. );
  568. const result = await response.json();
  569. if (!response.ok) {
  570. if (response.status === 404) {
  571. await teamWorkflowStore.delete(teamId, props.workflow.id);
  572. router.replace('/');
  573. return;
  574. }
  575. throw new Error(result.message);
  576. }
  577. } catch (error) {
  578. console.error(error);
  579. toast.error('Something went wrong');
  580. } finally {
  581. state.isPublishing = false;
  582. }
  583. }
  584. function initRenameWorkflow() {
  585. if (props.isTeam) {
  586. state.showEditDescription = true;
  587. return;
  588. }
  589. Object.assign(renameState, {
  590. showModal: true,
  591. name: `${props.workflow.name}`,
  592. description: `${props.workflow.description}`,
  593. });
  594. }
  595. function renameWorkflow() {
  596. updateWorkflow({
  597. name: renameState.name,
  598. description: renameState.description,
  599. });
  600. clearRenameModal();
  601. }
  602. function deleteWorkflow() {
  603. dialog.confirm({
  604. title: props.isPackage ? t('common.delete') : t('workflow.delete'),
  605. okVariant: 'danger',
  606. body: props.isPackage
  607. ? `Are you sure want to delete "${props.workflow.name}" package?`
  608. : t('message.delete', { name: props.workflow.name }),
  609. onConfirm: async () => {
  610. if (props.isPackage) {
  611. await packageStore.delete(props.workflow.id);
  612. } else if (props.isTeam) {
  613. await teamWorkflowStore.delete(teamId, props.workflow.id);
  614. } else {
  615. await workflowStore.delete(props.workflow.id);
  616. }
  617. router.replace(props.isPackage ? '/packages' : '/');
  618. },
  619. });
  620. }
  621. async function saveWorkflow() {
  622. try {
  623. const flow = props.editor.toObject();
  624. flow.edges = flow.edges.map((edge) => {
  625. delete edge.sourceNode;
  626. delete edge.targetNode;
  627. return edge;
  628. });
  629. const triggerBlock = flow.nodes.find((node) => node.label === 'trigger');
  630. if (!triggerBlock) {
  631. toast.error(t('message.noTriggerBlock'));
  632. return;
  633. }
  634. await updateWorkflow(
  635. {
  636. drawflow: flow,
  637. trigger: triggerBlock.data,
  638. version: browser.runtime.getManifest().version,
  639. },
  640. false
  641. );
  642. await registerWorkflowTrigger(props.workflow.id, triggerBlock);
  643. emit('change', { drawflow: flow });
  644. } catch (error) {
  645. console.error(error);
  646. }
  647. }
  648. async function retrieveTriggerText() {
  649. if (props.canEdit) return;
  650. const triggerBlock = findTriggerBlock(props.workflow.drawflow);
  651. if (!triggerBlock) return;
  652. state.triggerText = await getTriggerText(
  653. triggerBlock.data,
  654. t,
  655. router.currentRoute.value.params.id,
  656. true
  657. );
  658. }
  659. async function fetchSyncWorkflow() {
  660. try {
  661. const response = await fetchApi(
  662. `/teams/${teamId}/workflows/${props.workflow.id}`
  663. );
  664. const result = await response.json();
  665. if (response.status === 404) {
  666. await teamWorkflowStore.delete(teamId, props.workflow.id);
  667. router.replace(`/workflows?active=team&teamId=${teamId}`);
  668. return;
  669. }
  670. if (!response.ok) throw new Error(result.message);
  671. await teamWorkflowStore.update({
  672. teamId,
  673. data: result,
  674. id: props.workflow.id,
  675. });
  676. const convertedData = convertWorkflowData(result);
  677. props.editor.setNodes(convertedData.drawflow.nodes || []);
  678. props.editor.setEdges(convertedData.drawflow.edges || []);
  679. props.editor.fitView();
  680. await retrieveTriggerText();
  681. const triggerBlock = convertedData.drawflow.nodes.find(
  682. (node) => node.label === 'trigger'
  683. );
  684. registerWorkflowTrigger(props.workflow.id, triggerBlock);
  685. emit('permission');
  686. } catch (error) {
  687. toast.error(error.message);
  688. console.error(error);
  689. } finally {
  690. state.loadingSync = false;
  691. toast.dismiss('sync');
  692. }
  693. }
  694. async function syncWorkflow() {
  695. state.loadingSync = true;
  696. if (props.canEdit) {
  697. dialog.confirm({
  698. title: 'Sync workflow',
  699. okText: 'Sync',
  700. body: 'This action will overwrite the current workflow with the one that stored in cloud',
  701. onConfirm: () => {
  702. fetchSyncWorkflow();
  703. toast('Syncing workflow...', { timeout: false, id: 'sync' });
  704. },
  705. });
  706. } else {
  707. fetchSyncWorkflow();
  708. }
  709. }
  710. retrieveTriggerText();
  711. const modalActions = [
  712. {
  713. id: 'table',
  714. name: t('workflow.table.title'),
  715. icon: 'riTable2',
  716. },
  717. {
  718. id: 'global-data',
  719. name: t('common.globalData'),
  720. icon: 'riDatabase2Line',
  721. },
  722. {
  723. id: 'settings',
  724. name: t('common.settings'),
  725. icon: 'riSettings3Line',
  726. },
  727. ];
  728. const moreActions = [
  729. {
  730. id: 'export',
  731. icon: 'riDownloadLine',
  732. name: t('common.export'),
  733. action: () => exportWorkflow(props.workflow),
  734. hasAccess: props.isTeam ? props.canEdit : true,
  735. },
  736. {
  737. id: 'rename',
  738. icon: 'riPencilLine',
  739. hasAccess: props.isTeam ? props.canEdit : true,
  740. name: props.isTeam ? 'Edit detail' : t('common.rename'),
  741. action: initRenameWorkflow,
  742. },
  743. {
  744. id: 'delete',
  745. hasAccess: true,
  746. action: deleteWorkflow,
  747. name: t('common.delete'),
  748. icon: 'riDeleteBin7Line',
  749. attrs: {
  750. class: 'text-red-400 dark:text-red-500',
  751. },
  752. },
  753. ].filter((item) => item.hasAccess);
  754. </script>