瀏覽代碼

feat: add "on conflict" option in export data block

Ahmad Kholid 3 年之前
父節點
當前提交
b51dadf021

+ 1 - 0
.eslintrc.js

@@ -34,6 +34,7 @@ module.exports = {
     'no-console': ['warn', { allow: ['warn', 'error'] }],
     'no-underscore-dangle': 'off',
     'func-names': 'off',
+    'import/no-named-default': 'off',
     'import/extensions': [
       'error',
       'always',

+ 16 - 0
src/background/index.js

@@ -342,6 +342,22 @@ chrome.runtime.onStartup.addListener(async () => {
 if (chrome.downloads) {
   const getFileExtension = (str) => /(?:\.([^.]+))?$/.exec(str)[1];
   chrome.downloads.onDeterminingFilename.addListener((item, suggest) => {
+    if (item.byExtensionId === chrome.runtime.id) {
+      const filesname =
+        JSON.parse(sessionStorage.getItem('export-filesname')) || {};
+      const blobId = item.url.replace('blob:chrome-extension://', '');
+      const suggestion = filesname[blobId];
+
+      if (suggestion) {
+        delete filesname[blobId];
+
+        suggest(suggestion);
+        sessionStorage.setItem('export-filesname', JSON.stringify(filesname));
+      }
+
+      return;
+    }
+
     const filesname =
       JSON.parse(sessionStorage.getItem('rename-downloaded-files')) || {};
     const suggestion = filesname[item.id];

+ 41 - 8
src/background/workflow-engine/blocks-handler/handler-export-data.js

@@ -1,8 +1,11 @@
+import browser from 'webextension-polyfill';
 import { getBlockConnection } from '../helper';
-import dataExporter from '@/utils/data-exporter';
+import { default as dataExporter, files } from '@/utils/data-exporter';
 
-function exportData({ data, outputs }) {
-  return new Promise((resolve) => {
+async function exportData({ data, outputs }) {
+  const nextBlockId = getBlockConnection({ outputs });
+
+  try {
     const dataToExport = data.dataToExport || 'data-columns';
     let payload = this.referenceData.table;
 
@@ -10,13 +13,43 @@ function exportData({ data, outputs }) {
       payload = this.referenceData.googleSheets[data.refKey] || [];
     }
 
-    dataExporter(payload, data);
+    const hasDownloadAccess = await browser.permissions.contains({
+      permissions: ['downloads'],
+    });
+    const blobUrl = dataExporter(payload, {
+      ...data,
+      returnUrl: hasDownloadAccess,
+    });
+
+    if (hasDownloadAccess) {
+      const filename = `${data.name}${files[data.type].ext}`;
+      const blobId = blobUrl.replace('blob:chrome-extension://', '');
+      const filesname =
+        JSON.parse(sessionStorage.getItem('export-filesname')) || {};
+
+      const options = {
+        filename,
+        conflictAction: data.onConflict || 'uniquify',
+      };
 
-    resolve({
+      filesname[blobId] = options;
+      sessionStorage.setItem('export-filesname', JSON.stringify(filesname));
+
+      await browser.downloads.download({
+        ...options,
+        url: blobUrl,
+      });
+    }
+
+    return {
       data: '',
-      nextBlockId: getBlockConnection({ outputs }),
-    });
-  });
+      nextBlockId,
+    };
+  } catch (error) {
+    error.nextBlockId = nextBlockId;
+
+    throw error;
+  }
 }
 
 export default exportData;

+ 4 - 17
src/components/newtab/workflow/edit/EditClipboard.vue

@@ -6,7 +6,7 @@
       :placeholder="t('common.description')"
       @change="updateData({ description: $event })"
     />
-    <template v-if="hasPermission">
+    <template v-if="permission.has.clipboardRead">
       <p class="mt-4">
         {{ t('workflow.blocks.clipboard.data') }}
       </p>
@@ -16,17 +16,16 @@
       <p class="mt-4">
         {{ t('workflow.blocks.clipboard.noPermission') }}
       </p>
-      <ui-button variant="accent" class="mt-2" @click="requestPermission">
+      <ui-button variant="accent" class="mt-2" @click="permission.request">
         {{ t('workflow.blocks.clipboard.grantPermission') }}
       </ui-button>
     </template>
   </div>
 </template>
 <script setup>
-import { ref, onMounted } from 'vue';
 import { useI18n } from 'vue-i18n';
-import browser from 'webextension-polyfill';
 import InsertWorkflowData from './InsertWorkflowData.vue';
+import { useHasPermissions } from '@/composable/hasPermissions';
 
 const props = defineProps({
   data: {
@@ -36,22 +35,10 @@ const props = defineProps({
 });
 const emit = defineEmits(['update:data']);
 
-const permission = { permissions: ['clipboardRead'] };
 const { t } = useI18n();
+const permission = useHasPermissions(['clipboardRead']);
 
-const hasPermission = ref(false);
-
-function handlePermission(status) {
-  hasPermission.value = status;
-}
-function requestPermission() {
-  browser.permissions.request(permission).then(handlePermission);
-}
 function updateData(value) {
   emit('update:data', { ...props.data, ...value });
 }
-
-onMounted(() => {
-  browser.permissions.contains(permission).then(handlePermission);
-});
 </script>

+ 16 - 1
src/components/newtab/workflow/edit/EditExportData.vue

@@ -23,6 +23,17 @@
       placeholder="unnamed"
       @change="updateData({ name: $event })"
     />
+    <ui-select
+      v-if="permission.has.downloads"
+      :model-value="data.onConflict"
+      :label="t('workflow.blocks.handle-download.onConflict')"
+      class="mt-2 w-full"
+      @change="updateData({ onConflict: $event })"
+    >
+      <option v-for="item in onConflict" :key="item" :value="item">
+        {{ t(`workflow.blocks.base.downloads.onConflict.${item}`) }}
+      </option>
+    </ui-select>
     <ui-input
       v-if="data.dataToExport === 'google-sheets'"
       :model-value="data.refKey"
@@ -54,6 +65,7 @@
 <script setup>
 import { useI18n } from 'vue-i18n';
 import { dataExportTypes } from '@/utils/shared';
+import { useHasPermissions } from '@/composable/hasPermissions';
 
 const props = defineProps({
   data: {
@@ -63,8 +75,11 @@ const props = defineProps({
 });
 const emit = defineEmits(['update:data']);
 
-const { t } = useI18n();
 const dataToExport = ['data-columns', 'google-sheets'];
+const onConflict = ['uniquify', 'overwrite', 'prompt'];
+
+const { t } = useI18n();
+const permission = useHasPermissions(['downloads']);
 
 function updateData(value) {
   emit('update:data', { ...props.data, ...value });

+ 4 - 19
src/components/newtab/workflow/edit/EditHandleDownload.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <template v-if="hasPermission">
+    <template v-if="permission.has.downloads">
       <ui-textarea
         :model-value="data.description"
         class="w-full"
@@ -50,16 +50,15 @@
       <p class="mt-4">
         {{ t('workflow.blocks.handle-download.noPermission') }}
       </p>
-      <ui-button variant="accent" class="mt-2" @click="requestPermission">
+      <ui-button variant="accent" class="mt-2" @click="permission.request">
         {{ t('workflow.blocks.clipboard.grantPermission') }}
       </ui-button>
     </template>
   </div>
 </template>
 <script setup>
-import { ref, onMounted } from 'vue';
 import { useI18n } from 'vue-i18n';
-import browser from 'webextension-polyfill';
+import { useHasPermissions } from '@/composable/hasPermissions';
 import InsertWorkflowData from './InsertWorkflowData.vue';
 
 const props = defineProps({
@@ -70,26 +69,12 @@ const props = defineProps({
 });
 const emit = defineEmits(['update:data']);
 
-const permission = { permissions: ['downloads'] };
+const permission = useHasPermissions(['downloads']);
 const onConflict = ['uniquify', 'overwrite', 'prompt'];
 
 const { t } = useI18n();
 
-const hasPermission = ref(false);
-
-function handlePermission(status) {
-  hasPermission.value = status;
-}
-function requestPermission() {
-  browser.permissions.request(permission).then((isGranted) => {
-    if (isGranted) chrome.runtime.reload();
-  });
-}
 function updateData(value) {
   emit('update:data', { ...props.data, ...value });
 }
-
-onMounted(() => {
-  browser.permissions.contains(permission).then(handlePermission);
-});
 </script>

+ 40 - 0
src/composable/hasPermissions.js

@@ -0,0 +1,40 @@
+import { onMounted, shallowReactive } from 'vue';
+import browser from 'webextension-polyfill';
+
+export function useHasPermissions(permissions) {
+  const hasPermissions = shallowReactive({});
+
+  function handlePermission(name, status) {
+    hasPermissions[name] = status;
+  }
+  function request() {
+    const reqPermissions = permissions.map(
+      (permission) => !hasPermissions[permission]
+    );
+
+    browser.permissions
+      .request({ permissions: reqPermissions })
+      .then((status) => {
+        if (!status) return;
+
+        reqPermissions.forEach((permission) => {
+          handlePermission(permission, true);
+        });
+      });
+  }
+
+  onMounted(() => {
+    permissions.forEach((permission) => {
+      browser.permissions
+        .contains({ permissions: [permission] })
+        .then((status) => {
+          handlePermission(permission, status);
+        });
+    });
+  });
+
+  return {
+    request,
+    has: hasPermissions,
+  };
+}

+ 10 - 6
src/utils/data-exporter.js

@@ -1,7 +1,7 @@
 import Papa from 'papaparse';
 import { fileSaver } from './helper';
 
-const files = {
+export const files = {
   'plain-text': {
     mime: 'text/plain',
     ext: '.txt',
@@ -36,7 +36,11 @@ export function generateJSON(keys, data) {
   return result;
 }
 
-export default function (data, { name, type, addBOMHeader }, converted) {
+export default function (
+  data,
+  { name, type, addBOMHeader, returnUrl },
+  converted
+) {
   let result = data;
 
   if (type === 'csv' || type === 'json') {
@@ -61,9 +65,9 @@ export default function (data, { name, type, addBOMHeader }, converted) {
   }
 
   const { mime, ext } = files[type];
-  const blob = new Blob(payload, {
-    type: mime,
-  });
+  const blobUrl = URL.createObjectURL(new Blob(payload, { type: mime }));
+
+  if (!returnUrl) fileSaver(`${name || 'unnamed'}${ext}`, blobUrl);
 
-  fileSaver(`${name || 'unnamed'}${ext}`, URL.createObjectURL(blob));
+  return blobUrl;
 }

+ 2 - 2
src/utils/helper.js

@@ -130,9 +130,9 @@ export function openFilePicker(acceptedFileTypes = [], attrs = {}) {
   });
 }
 
-export function fileSaver(fileName, data) {
+export function fileSaver(filename, data) {
   const anchor = document.createElement('a');
-  anchor.download = fileName;
+  anchor.download = filename;
   anchor.href = data;
 
   anchor.dispatchEvent(new MouseEvent('click'));

+ 1 - 0
src/utils/shared.js

@@ -321,6 +321,7 @@ export const tasks = {
       type: 'json',
       description: '',
       addBOMHeader: false,
+      onConflict: 'uniquify',
       dataToExport: 'data-columns',
     },
   },