WorkflowDetailsCard.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. <template>
  2. <div class="px-4 flex items-start mb-2 mt-1">
  3. <ui-popover class="mr-2 h-8">
  4. <template #trigger>
  5. <span
  6. :title="t('workflow.sidebar.workflowIcon')"
  7. class="cursor-pointer inline-block h-full"
  8. >
  9. <ui-img
  10. v-if="workflow.icon.startsWith('http')"
  11. :src="workflow.icon"
  12. class="w-8 h-8"
  13. />
  14. <v-remixicon v-else :name="workflow.icon" size="26" class="mt-1" />
  15. </span>
  16. </template>
  17. <div class="w-56">
  18. <p class="mb-2">{{ t('workflow.sidebar.workflowIcon') }}</p>
  19. <div class="grid grid-cols-5 mb-2 gap-1">
  20. <span
  21. v-for="icon in icons"
  22. :key="icon"
  23. class="cursor-pointer rounded-lg inline-block text-center p-2 hoverable"
  24. @click="$emit('update', { icon })"
  25. >
  26. <v-remixicon :name="icon" />
  27. </span>
  28. </div>
  29. <ui-input
  30. :model-value="workflow.icon.startsWith('ri') ? '' : workflow.icon"
  31. type="url"
  32. placeholder="http://example.com/img.png"
  33. label="Icon URL"
  34. @change="updateWorkflowIcon"
  35. />
  36. </div>
  37. </ui-popover>
  38. <div class="flex-1 overflow-hidden">
  39. <p class="font-semibold mt-1 text-overflow text-lg leading-tight">
  40. {{ workflow.name }}
  41. </p>
  42. <p class="line-clamp leading-tight">
  43. {{ workflow.description }}
  44. </p>
  45. </div>
  46. </div>
  47. <ui-input
  48. id="search-input"
  49. v-model="query"
  50. :placeholder="`${t('common.search')}... (${
  51. shortcut['action:search'].readable
  52. })`"
  53. prepend-icon="riSearch2Line"
  54. class="px-4 mt-4 mb-2"
  55. />
  56. <div class="scroll bg-scroll px-4 flex-1 relative overflow-auto">
  57. <ui-expand
  58. v-for="(items, catId) in blocks"
  59. :key="catId"
  60. v-model="expandList[catId]"
  61. hide-header-icon
  62. header-class="flex items-center py-2 focus:ring-0 w-full text-left text-gray-600 dark:text-gray-200"
  63. >
  64. <template #header="{ show }">
  65. <span
  66. :class="categories[catId].color"
  67. class="h-3 w-3 rounded-full"
  68. ></span>
  69. <p class="capitalize flex-1 ml-2">
  70. {{ categories[catId].name }}
  71. </p>
  72. <v-remixicon :name="show ? 'riSubtractLine' : 'riAddLine'" size="20" />
  73. </template>
  74. <div class="grid grid-cols-2 gap-2 mb-4">
  75. <div
  76. v-for="block in items"
  77. :key="block.id"
  78. :title="getBlockTitle(block)"
  79. draggable="true"
  80. class="transform select-none cursor-move relative p-4 rounded-lg bg-input transition group"
  81. @dragstart="
  82. $event.dataTransfer.setData('block', JSON.stringify(block))
  83. "
  84. >
  85. <a
  86. :href="`https://docs.automa.site/blocks/${block.id}.html`"
  87. :title="t('common.docs')"
  88. target="_blank"
  89. rel="noopener"
  90. class="absolute top-px right-2 top-2 text-gray-600 dark:text-gray-300 invisible group-hover:visible"
  91. >
  92. <v-remixicon name="riInformationLine" size="18" />
  93. </a>
  94. <v-remixicon :name="block.icon" size="24" class="mb-2" />
  95. <p class="leading-tight text-overflow capitalize">
  96. {{ block.name }}
  97. </p>
  98. </div>
  99. </div>
  100. </ui-expand>
  101. </div>
  102. </template>
  103. <script setup>
  104. import { computed, ref } from 'vue';
  105. import { useI18n } from 'vue-i18n';
  106. import { useShortcut } from '@/composable/shortcut';
  107. import { tasks, categories } from '@/utils/shared';
  108. defineProps({
  109. workflow: {
  110. type: Object,
  111. default: () => ({}),
  112. },
  113. dataChanged: {
  114. type: Boolean,
  115. default: false,
  116. },
  117. });
  118. const emit = defineEmits(['update']);
  119. const { t } = useI18n();
  120. const shortcut = useShortcut('action:search', () => {
  121. const searchInput = document.querySelector('#search-input input');
  122. searchInput?.focus();
  123. });
  124. const icons = [
  125. 'riGlobalLine',
  126. 'riFileTextLine',
  127. 'riEqualizerLine',
  128. 'riTimerLine',
  129. 'riCalendarLine',
  130. 'riFlashlightLine',
  131. 'riLightbulbFlashLine',
  132. 'riDatabase2Line',
  133. 'riWindowLine',
  134. 'riCursorLine',
  135. 'riDownloadLine',
  136. 'riCommandLine',
  137. ];
  138. const blocksArr = Object.entries(tasks).map(([key, block]) => ({
  139. ...block,
  140. id: key,
  141. name: t(`workflow.blocks.${key}.name`),
  142. }));
  143. const categoriesExpand = Object.keys(categories).reduce((acc, key) => {
  144. acc[key] = true;
  145. return acc;
  146. }, {});
  147. const query = ref('');
  148. const expandList = ref(categoriesExpand);
  149. const blocks = computed(() =>
  150. blocksArr.reduce((arr, block) => {
  151. if (
  152. block.name.toLocaleLowerCase().includes(query.value.toLocaleLowerCase())
  153. ) {
  154. expandList.value[block.category] = true;
  155. (arr[block.category] = arr[block.category] || []).push(block);
  156. }
  157. return arr;
  158. }, {})
  159. );
  160. function updateWorkflowIcon(value) {
  161. if (!value.startsWith('http')) return;
  162. const iconUrl = value.slice(0, 1024);
  163. emit('update', { icon: iconUrl });
  164. }
  165. function getBlockTitle({ description, id }) {
  166. const blockPath = `workflow.blocks.${id}`;
  167. let blockDescription = t(
  168. `${blockPath}.${description ? 'description' : 'name'}`
  169. );
  170. if (description) {
  171. blockDescription = `[${t(`${blockPath}.name`)}]\n${blockDescription}`;
  172. }
  173. return blockDescription;
  174. }
  175. </script>