WorkflowDetailsCard.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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('http') ? 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
  43. class="leading-tight cursor-pointer"
  44. :class="descriptionCollapsed ? 'line-clamp' : 'whitespace-pre-wrap'"
  45. @click="descriptionCollapsed = !descriptionCollapsed"
  46. >
  47. <!-- description here -->
  48. {{ workflow.description }}
  49. </p>
  50. </div>
  51. </div>
  52. <ui-input
  53. id="search-input"
  54. v-model="query"
  55. :placeholder="`${t('common.search')}... (${
  56. shortcut['action:search'].readable
  57. })`"
  58. prepend-icon="riSearch2Line"
  59. class="px-4 mt-4 mb-2"
  60. />
  61. <div class="scroll bg-scroll px-4 flex-1 relative overflow-auto">
  62. <workflow-block-list
  63. v-if="pinnedBlocksList.length > 0"
  64. :model-value="true"
  65. :blocks="pinnedBlocksList"
  66. :category="pinnedCategory"
  67. :pinned="pinnedBlocks"
  68. @pin="pinBlock"
  69. />
  70. <workflow-block-list
  71. v-for="(items, catId) in blocks"
  72. :key="catId"
  73. :model-value="true"
  74. :blocks="items"
  75. :category="categories[catId]"
  76. :pinned="pinnedBlocks"
  77. @pin="pinBlock"
  78. />
  79. </div>
  80. </template>
  81. <script setup>
  82. import { computed, ref, onMounted, watch, toRaw } from 'vue';
  83. import { useI18n } from 'vue-i18n';
  84. import browser from 'webextension-polyfill';
  85. import { useShortcut } from '@/composable/shortcut';
  86. import { tasks, categories } from '@/utils/shared';
  87. import customBlocks from '@business/blocks';
  88. import WorkflowBlockList from './WorkflowBlockList.vue';
  89. defineProps({
  90. workflow: {
  91. type: Object,
  92. default: () => ({}),
  93. },
  94. dataChanged: {
  95. type: Boolean,
  96. default: false,
  97. },
  98. });
  99. const emit = defineEmits(['update']);
  100. const { t, te } = useI18n();
  101. const shortcut = useShortcut('action:search', () => {
  102. const searchInput = document.querySelector('#search-input input');
  103. searchInput?.focus();
  104. });
  105. const pinnedCategory = {
  106. name: 'Pinned blocks',
  107. color: 'bg-accent',
  108. };
  109. const icons = [
  110. 'mdiPackageVariantClosed',
  111. 'riGlobalLine',
  112. 'riFileTextLine',
  113. 'riEqualizerLine',
  114. 'riTimerLine',
  115. 'riCalendarLine',
  116. 'riFlashlightLine',
  117. 'riLightbulbFlashLine',
  118. 'riDatabase2Line',
  119. 'riWindowLine',
  120. 'riCursorLine',
  121. 'riDownloadLine',
  122. 'riCommandLine',
  123. ];
  124. const copyTasks = { ...tasks };
  125. delete copyTasks['block-package'];
  126. const blocksArr = Object.entries({ ...copyTasks, ...customBlocks }).map(
  127. ([key, block]) => {
  128. const localeKey = `workflow.blocks.${key}.name`;
  129. return {
  130. ...block,
  131. id: key,
  132. name: te(localeKey) ? t(localeKey) : block.name,
  133. };
  134. }
  135. );
  136. const descriptionCollapsed = ref(true);
  137. const query = ref('');
  138. const pinnedBlocks = ref([]);
  139. const blocks = computed(() =>
  140. blocksArr.reduce((arr, block) => {
  141. if (
  142. block.name.toLocaleLowerCase().includes(query.value.toLocaleLowerCase())
  143. ) {
  144. (arr[block.category] = arr[block.category] || []).push(block);
  145. }
  146. return arr;
  147. }, {})
  148. );
  149. const pinnedBlocksList = computed(() =>
  150. pinnedBlocks.value
  151. .map((id) => {
  152. const namePath = `workflow.blocks.${id}.name`;
  153. return {
  154. ...tasks[id],
  155. id,
  156. name: te(namePath) ? t(namePath) : tasks[id].name,
  157. };
  158. })
  159. .filter(({ name }) =>
  160. name.toLocaleLowerCase().includes(query.value.toLocaleLowerCase())
  161. )
  162. );
  163. function updateWorkflowIcon(value) {
  164. if (!value.startsWith('http')) return;
  165. const iconUrl = value.slice(0, 1024);
  166. emit('update', { icon: iconUrl });
  167. }
  168. function pinBlock({ id }) {
  169. const index = pinnedBlocks.value.indexOf(id);
  170. if (index !== -1) pinnedBlocks.value.splice(index, 1);
  171. else pinnedBlocks.value.push(id);
  172. }
  173. watch(
  174. pinnedBlocks,
  175. () => {
  176. browser.storage.local.set({
  177. pinnedBlocks: toRaw(pinnedBlocks.value),
  178. });
  179. },
  180. { deep: true }
  181. );
  182. onMounted(() => {
  183. browser.storage.local.get('pinnedBlocks').then((item) => {
  184. pinnedBlocks.value = item.pinnedBlocks || [];
  185. });
  186. });
  187. </script>