EditorLocalActions.vue 20 KB

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