@xuda.io/xuda-widget-plugin-xuda-drive 1.0.134 → 1.0.136

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xuda.io/xuda-widget-plugin-xuda-drive",
3
- "version": "1.0.134",
3
+ "version": "1.0.136",
4
4
  "description": "Xuda Drive widget plugin",
5
5
  "scripts": {
6
6
  "pub": "npm version patch --force && npm publish --access public",
package/src/runtime.mjs CHANGED
@@ -3,7 +3,6 @@ import 'filepond/dist/filepond.min.css';
3
3
 
4
4
  window.FilePond = FilePond;
5
5
 
6
-
7
6
  import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type';
8
7
 
9
8
  import * as pintura from './vendors/pintura/pintura/pintura.js';
@@ -163,7 +162,7 @@ export async function upload(fields, e) {
163
162
  allowProcess: true,
164
163
  allowRemove: true,
165
164
  allowRevert: true,
166
- maxParallelUploads: 25,
165
+ maxParallelUploads: 50,
167
166
 
168
167
  imageCropAspectRatio: 1,
169
168
  imageResizeTargetWidth: 200,
@@ -634,7 +633,7 @@ export async function viewer(fields, e) {
634
633
 
635
634
  <div>
636
635
  <button
637
- @click="uploadModal = true"
636
+ @click="$refs.fileInput.click()"
638
637
  type="button"
639
638
  class="inline-flex items-center justify-center px-4 py-2 text-sm font-medium tracking-wide text-white transition-colors duration-200 rounded-md bg-neutral-950 hover:bg-neutral-900 focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900 focus:shadow-outline focus:outline-none"
640
639
  >
@@ -752,129 +751,91 @@ export async function viewer(fields, e) {
752
751
  </div>
753
752
  </div>
754
753
 
755
- <teleport to="body">
756
- <div @keydown.escape.window="uploadModal = false" v-show="uploadModal" class="fixed top-0 left-0 z-[99] w-screen h-screen" x-cloak>
757
- <div v-show="uploadModal" @click="uploadModal=false" class="absolute inset-0 w-full h-full bg-white backdrop-blur-sm bg-opacity-70"></div>
754
+ <!-- Hidden native file input -->
755
+ <input type="file" ref="fileInput" :multiple="options.file_upload_allow_multiple_files" :accept="acceptTypes" class="hidden" @change="onFilesSelected" />
758
756
 
757
+ <!-- Consent override modal -->
758
+ <teleport to="body">
759
+ <div @keydown.escape.window="showExistModal = false" v-show="showExistModal" class="fixed top-0 left-0 z-[99] w-screen h-screen" x-cloak>
760
+ <div v-show="showExistModal" @click="showExistModal=false" class="absolute inset-0 w-full h-full bg-white backdrop-blur-sm bg-opacity-70"></div>
759
761
  <transition key="wrapper" enter-from-class="opacity-0" enter-active-class="ease-out duration-300" enter-to-class="opacity-100" leave-from-class="opacity-100" leave-active-class="ease-in duration-300" leave-to-class="opacity-0">
760
- <div class="flex items-center justify-center size-full" v-show="uploadModal">
761
- <transition
762
- key="inner"
763
- enter-active-class="ease-out duration-300"
764
- enter-from-class="opacity-0 -translate-y-2 sm:scale-95"
765
- enter-to-class="opacity-100 translate-y-0 sm:scale-100"
766
- leave-active-class="ease-in duration-200"
767
- leave-from-class="opacity-100 translate-y-0 sm:scale-100"
768
- leave-to-class="opacity-0 -translate-y-2 sm:scale-95"
769
- >
770
- <div v-show="uploadModal" class="relative w-full py-6 bg-white border shadow-lg px-7 border-neutral-200 sm:max-w-lg sm:rounded-lg overflow-hidden">
762
+ <div class="flex items-center justify-center size-full" v-show="showExistModal">
763
+ <transition key="inner" enter-active-class="ease-out duration-300" enter-from-class="opacity-0 -translate-y-2 sm:scale-95" enter-to-class="opacity-100 translate-y-0 sm:scale-100" leave-active-class="ease-in duration-200" leave-from-class="opacity-100 translate-y-0 sm:scale-100" leave-to-class="opacity-0 -translate-y-2 sm:scale-95">
764
+ <div v-show="showExistModal" class="relative w-full py-6 bg-white border shadow-lg px-7 border-neutral-200 sm:max-w-lg sm:rounded-lg overflow-hidden">
771
765
  <div class="flex items-center justify-between pb-3">
772
- <h3 class="text-lg font-semibold" v-text="'Upload file' + (options.file_upload_allow_multiple_files ? 's' : '')"></h3>
773
- <button @click="uploadModal=false" class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 text-gray-600 rounded-full hover:text-gray-800 hover:bg-gray-50">
766
+ <h3 class="text-lg font-semibold">Upload options</h3>
767
+ <button @click="showExistModal=false" class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 text-gray-600 rounded-full hover:text-gray-800 hover:bg-gray-50">
774
768
  <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>
775
769
  </button>
776
770
  </div>
777
- <div id="imageUploaderComponent" class="relative w-auto pb-8 h-48 [&>div]:h-full [&>div>div]:h-full"></div>
778
- <div class="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2">
779
- <button @click="uploadModal=false" type="button" class="inline-flex gap-2 items-center justify-center h-10 px-4 py-2 text-sm font-medium transition-colors border rounded-md focus:outline-none focus:ring-2 focus:ring-neutral-100 focus:ring-offset-2">
771
+ <div class="flex-1">
772
+ <p class="text-gray-800">One or more items already exists in this location. Do you want to replace the existing items with a new version or keep both items? Replacing the items won't change sharing settings.</p>
773
+ <fieldset>
774
+ <div class="mt-6 space-y-5 pl-2">
775
+ <div v-for="option in replaceOptions" :key="option.value">
776
+ <label :for="option.value" class="flex items-center cursor-pointer">
777
+ <input :id="option.value" name="notification-method" type="radio" :value="option.value" v-model="overrideAction" :checked="option.value === overrideAction" class="option-radio peer" />
778
+ <div class="ml-3 block font-medium peer-checked:text-black text-light-700 cursor-pointer">{{ option.label }}</div>
779
+ </label>
780
+ </div>
781
+ </div>
782
+ </fieldset>
783
+ </div>
784
+ <div class="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 mt-6">
785
+ <button @click="showExistModal=false" type="button" class="inline-flex gap-2 items-center justify-center h-10 px-4 py-2 text-sm font-medium transition-colors border rounded-md focus:outline-none focus:ring-2 focus:ring-neutral-100 focus:ring-offset-2">
780
786
  <div>Dismiss</div>
781
787
  </button>
782
- <button
783
- @click="uploadFiles(false)"
784
- :disabled="uploaderLoading || checkingFiles"
785
- type="button"
786
- class="inline-flex items-center gap-2 justify-center h-10 px-4 py-2 text-sm font-medium text-white transition-colors border border-transparent rounded-md focus:outline-none focus:ring-2 focus:ring-neutral-900 focus:ring-offset-2 bg-neutral-950 hover:bg-neutral-900 disabled:opacity-50 disabled:cursor-not-allowed"
787
- >
788
- <template v-if="checkingFiles">
789
- <sepcial-loader class="size-5" />
790
- Checking files...
791
- </template>
792
-
793
- <template v-else-if="uploaderLoading">
794
- <sepcial-loader class="size-5" />
795
- Uploading
796
- </template>
797
-
798
- <template v-else>Upload</template>
788
+ <button @click="startUpload(true)" type="button" class="inline-flex items-center gap-2 justify-center h-10 px-4 py-2 text-sm font-medium text-white transition-colors border border-transparent rounded-md focus:outline-none focus:ring-2 focus:ring-neutral-900 focus:ring-offset-2 bg-neutral-950 hover:bg-neutral-900">
789
+ Upload
799
790
  </button>
800
791
  </div>
801
-
802
- <div v-show="showExistModal" class="absolute inset-0 bg-white py-6 px-7 flex flex-col">
803
- <div class="flex items-center justify-between pb-3">
804
- <h3 class="text-lg font-semibold">Upload options</h3>
805
- <button @click="showExistModal=false" class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 text-gray-600 rounded-full hover:text-gray-800 hover:bg-gray-50">
806
- <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>
807
- </button>
808
- </div>
809
-
810
- <div class="flex-1">
811
- <p class="text-gray-800">One or more items already exists in this location. Do you want to replace the existing items with a new version or keep both items? Replacing the items won't change sharing settings.</p>
812
-
813
- <fieldset>
814
- <div class="mt-6 space-y-5 pl-2">
815
- <div v-for="option in replaceOptions" :key="option.value">
816
- <label :for="option.value" class="flex items-center cursor-pointer">
817
- <input :id="option.value" name="notification-method" type="radio" :value="option.value" v-model="overrideAction" :checked="option.value === overrideAction" class="option-radio peer" />
818
- <div class="ml-3 block font-medium peer-checked:text-black text-light-700 cursor-pointer">{{ option.label }}</div>
819
- </label>
820
- </div>
821
- </div>
822
- </fieldset>
823
- </div>
824
-
825
- <div class="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2">
826
- <button @click="showExistModal=false" type="button" class="inline-flex gap-2 items-center justify-center h-10 px-4 py-2 text-sm font-medium transition-colors border rounded-md focus:outline-none focus:ring-2 focus:ring-neutral-100 focus:ring-offset-2">
827
- <div>Dismiss</div>
828
- </button>
829
- <button
830
- @click="uploadFiles(true)"
831
- :disabled="uploaderLoading"
832
- type="button"
833
- class="inline-flex items-center gap-2 justify-center h-10 px-4 py-2 text-sm font-medium text-white transition-colors border border-transparent rounded-md focus:outline-none focus:ring-2 focus:ring-neutral-900 focus:ring-offset-2 bg-neutral-950 hover:bg-neutral-900 disabled:opacity-50 disabled:cursor-not-allowed"
834
- >
835
- Upload
836
- </button>
837
- </div>
838
- </div>
839
-
840
- <div v-show="checkerFailedModal" class="absolute inset-0 bg-white py-6 px-7 flex flex-col">
841
- <div class="flex items-center justify-between pb-3">
842
- <h3 class="text-lg font-semibold">Checker failed</h3>
843
- <button @click="checkerFailedModal=false" class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 text-gray-600 rounded-full hover:text-gray-800 hover:bg-gray-50">
844
- <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>
845
- </button>
846
- </div>
847
-
848
- <div class="flex-1">
849
- <p class="text-gray-800">We couldn't complete your file upload due to a network error. This might be due to a temporary connection issue.
850
-
851
- </p>
852
-
853
-
854
- </div>
855
-
856
- <div class="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2">
857
- <button @click="checkerFailedModal=false" type="button" class="inline-flex gap-2 items-center justify-center h-10 px-4 py-2 text-sm font-medium transition-colors border rounded-md focus:outline-none focus:ring-2 focus:ring-neutral-100 focus:ring-offset-2">
858
- <div>Dismiss</div>
859
- </button>
860
- <button
861
- @click="uploadFiles(false), (checkerFailedModal = false)"
862
- :disabled="uploaderLoading"
863
- type="button"
864
- class="inline-flex items-center gap-2 justify-center h-10 px-4 py-2 text-sm font-medium text-white transition-colors border border-transparent rounded-md focus:outline-none focus:ring-2 focus:ring-neutral-900 focus:ring-offset-2 bg-neutral-950 hover:bg-neutral-900 disabled:opacity-50 disabled:cursor-not-allowed"
865
- >
866
- Retry
867
- </button>
868
- </div>
869
- </div>
870
-
871
-
872
-
873
792
  </div>
874
793
  </transition>
875
794
  </div>
876
795
  </transition>
877
796
  </div>
797
+
798
+ <!-- Upload job window (Google Drive style) -->
799
+ <div v-if="uploadJobs.length" class="fixed bottom-4 right-4 z-[100] w-96 bg-white rounded-lg shadow-2xl border border-neutral-200 overflow-hidden" style="font-family: system-ui, -apple-system, sans-serif;">
800
+ <!-- Header -->
801
+ <div class="flex items-center justify-between px-4 py-3 bg-neutral-900 text-white cursor-pointer" @click="jobWindowCollapsed = !jobWindowCollapsed">
802
+ <div class="font-medium text-sm">
803
+ <template v-if="uploadsInProgress > 0">Uploading {{ uploadsInProgress }} item{{ uploadsInProgress > 1 ? 's' : '' }}</template>
804
+ <template v-else>{{ uploadJobs.length }} upload{{ uploadJobs.length > 1 ? 's' : '' }} complete</template>
805
+ </div>
806
+ <div class="flex items-center gap-2">
807
+ <button @click.stop="jobWindowCollapsed = !jobWindowCollapsed" class="text-white/70 hover:text-white">
808
+ <svg :class="{ 'rotate-180': jobWindowCollapsed }" class="w-5 h-5 transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" /></svg>
809
+ </button>
810
+ <button v-if="uploadsInProgress === 0" @click.stop="uploadJobs = []" class="text-white/70 hover:text-white">
811
+ <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>
812
+ </button>
813
+ </div>
814
+ </div>
815
+ <!-- File list -->
816
+ <div v-show="!jobWindowCollapsed" class="max-h-64 overflow-y-auto divide-y divide-neutral-100">
817
+ <div v-for="job in uploadJobs" :key="job.id" class="flex items-center gap-3 px-4 py-2.5">
818
+ <div class="shrink-0">
819
+ <!-- Upload icon -->
820
+ <svg v-if="job.status === 'uploading'" class="w-5 h-5 text-neutral-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" /></svg>
821
+ <!-- Success icon -->
822
+ <svg v-else-if="job.status === 'done'" class="w-5 h-5 text-green-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm13.36-1.814a.75.75 0 10-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 00-1.06 1.06l2.25 2.25a.75.75 0 001.14-.094l3.75-5.25z" clip-rule="evenodd" /></svg>
823
+ <!-- Error icon -->
824
+ <svg v-else-if="job.status === 'error'" class="w-5 h-5 text-red-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path fill-rule="evenodd" d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25zm-1.72 6.97a.75.75 0 10-1.06 1.06L10.94 12l-1.72 1.72a.75.75 0 101.06 1.06L12 13.06l1.72 1.72a.75.75 0 101.06-1.06L13.06 12l1.72-1.72a.75.75 0 10-1.06-1.06L12 10.94l-1.72-1.72z" clip-rule="evenodd" /></svg>
825
+ </div>
826
+ <div class="flex-1 min-w-0">
827
+ <div class="text-sm truncate" v-text="job.name"></div>
828
+ <!-- Progress bar -->
829
+ <div v-if="job.status === 'uploading'" class="mt-1 h-1 bg-neutral-100 rounded-full overflow-hidden">
830
+ <div class="h-full bg-blue-500 rounded-full transition-all duration-300" :style="{ width: job.progress + '%' }"></div>
831
+ </div>
832
+ </div>
833
+ <div class="shrink-0 text-xs text-neutral-400">
834
+ <span v-if="job.status === 'uploading'">{{ Math.round(job.progress) }}%</span>
835
+ </div>
836
+ </div>
837
+ </div>
838
+ </div>
878
839
  </teleport>
879
840
  </div>
880
841
 
@@ -993,18 +954,18 @@ export async function viewer(fields, e) {
993
954
  folders: [],
994
955
  files: [],
995
956
  view: fields.default_view_type || 'gallery',
996
- uploadModal: false,
997
- uploaderLoading: false,
998
- checkingFiles: false,
999
957
  loadingSearch: false,
1000
958
  fetcher,
1001
959
  formatBytes,
1002
- initUploader: upload,
1003
960
  swiper: null,
1004
961
  existingTags: '',
1005
962
  showSearchPopper: false,
1006
963
  isOverDropZone: false,
1007
964
  removeChipCount: 0,
965
+ // Upload job window state
966
+ uploadJobs: [],
967
+ jobWindowCollapsed: false,
968
+ pendingFiles: [],
1008
969
  searchOptions: [
1009
970
  {
1010
971
  label: 'Filename',
@@ -1036,10 +997,7 @@ export async function viewer(fields, e) {
1036
997
  existingFiles: {},
1037
998
  allLoading: false,
1038
999
  showExistModal: false,
1039
- checkerFailedModal: false,
1040
1000
  overrideAction: 'replace',
1041
- pond: null,
1042
- processFiles: null,
1043
1001
  };
1044
1002
  },
1045
1003
  methods: {
@@ -1086,67 +1044,139 @@ export async function viewer(fields, e) {
1086
1044
  refreshFsLightbox();
1087
1045
  });
1088
1046
  },
1089
- uploadFiles(is_after_confirm) {
1090
- if (is_after_confirm) {
1047
+ onFilesSelected(event) {
1048
+ const files = Array.from(event.target.files);
1049
+ if (!files.length) return;
1050
+ this.pendingFiles = files;
1051
+ // Reset the input so re-selecting the same files works
1052
+ event.target.value = '';
1053
+ this.checkAndUpload();
1054
+ },
1055
+ checkAndUpload() {
1056
+ this.existingFiles = {};
1057
+ var file_paths = this.pendingFiles.map((f) => '/' + f.name);
1058
+
1059
+ this.fetcher('check_drive_files_workspace', {
1060
+ file_paths,
1061
+ type: 'file',
1062
+ })
1063
+ .then(() => {
1064
+ // No conflicts — upload all
1065
+ this.startUpload(false);
1066
+ })
1067
+ .catch((err) => {
1068
+ if (err.code === -764) {
1069
+ (err.files || []).forEach((file, index) => {
1070
+ if (file.code === -764) {
1071
+ this.existingFiles[this.pendingFiles[index].name] = file.duplicates;
1072
+ }
1073
+ });
1074
+
1075
+ if (Object.keys(this.existingFiles).length) {
1076
+ this.showExistModal = true;
1077
+ } else {
1078
+ this.startUpload(false);
1079
+ }
1080
+ } else {
1081
+ // Network error — upload anyway, server will handle it
1082
+ this.startUpload(false);
1083
+ }
1084
+ });
1085
+ },
1086
+ startUpload(isAfterConsent) {
1087
+ if (isAfterConsent) {
1091
1088
  this.showExistModal = false;
1092
- this.uploaderLoading = true;
1093
- // this.processCb();
1094
- this.processFiles(null, {
1095
- overrideAction: this.overrideAction,
1096
- existingFiles: this.existingFiles,
1089
+ }
1090
+
1091
+ const filesToUpload = this.pendingFiles;
1092
+ this.pendingFiles = [];
1093
+ this.jobWindowCollapsed = false;
1094
+
1095
+ filesToUpload.forEach((file) => {
1096
+ const jobId = crypto.randomUUID();
1097
+ this.uploadJobs.push({
1098
+ id: jobId,
1099
+ name: file.name,
1100
+ progress: 0,
1101
+ status: 'uploading',
1097
1102
  });
1103
+ this.uploadSingleFile(file, jobId);
1104
+ });
1105
+ },
1106
+ uploadSingleFile(file, jobId) {
1107
+ let formData = new FormData();
1108
+ formData.append('file', file, file.name);
1109
+
1110
+ const { app_id: appId, gtp_token, app_token, api_key } = e._session;
1111
+ formData.append('app_id_reference', APP_OBJ?.[appId]?.app_id_reference);
1112
+ formData.append('app_id', appId);
1113
+ formData.append('gtp_token', gtp_token);
1114
+ formData.append('app_token', app_token);
1115
+ formData.append('drive_type', 'workspace');
1116
+ if (api_key) formData.append('api_key', api_key);
1117
+
1118
+ if (fields.file_upload_folder) formData.append('folder', fields.file_upload_folder);
1119
+ if (fields.public_file) formData.append('make_public', fields.public_file);
1120
+
1121
+ let tags = fields.assign_file_tags || [];
1122
+ let file_tags = [];
1123
+ if (fields.auto_tag_generator) {
1124
+ let identifier = fields.auto_tag_identifier || ' ';
1125
+ const filename = file.name.split('.').slice(0, -1).join('.');
1126
+ file_tags = filename.split(identifier);
1127
+ }
1128
+ formData.append('tags', [...tags, ...file_tags]);
1129
+
1130
+ const request = new XMLHttpRequest();
1098
1131
 
1099
- // this.pond.on("processfile", (e) => {
1100
- // this.uploaderLoading = false;
1101
- // this.uploadModal = false;
1102
- // this.pond.removeFiles();
1132
+ let _domain = e._session.domain;
1133
+ if (e._session.is_deployment) {
1134
+ _domain = e._session.opt.regional_server === 'dev.xuda.ai' ? 'dev.xuda.ai' : 'xuda.ai';
1135
+ }
1103
1136
 
1104
- // this.refreshDirectory();
1105
- // });
1137
+ if (this.overrideAction === 'replace' && file.name in this.existingFiles) {
1138
+ formData.append('file_path', '/' + file.name);
1139
+ formData.append('file_name', file.name);
1140
+ request.open('POST', 'https://' + _domain + '/cpi/update_drive_file_workspace');
1106
1141
  } else {
1107
- this.existingFiles = {};
1108
- this.checkingFiles = true;
1142
+ request.open('POST', 'https://' + _domain + '/cpi/upload_drive_file_workspace');
1143
+ }
1109
1144
 
1110
- var filesMapped = this.pond.getFiles().map((e) => ({ id: e.id, filename: e.file.name }));
1111
- var file_paths = filesMapped.map(({ filename }) => '/' + filename);
1145
+ request.setRequestHeader('xu-gtp-token', gtp_token);
1146
+ request.setRequestHeader('xu-app-token', app_token);
1112
1147
 
1113
- this.fetcher(`check_drive_files_workspace`, {
1114
- file_paths,
1115
- type: 'file',
1116
- })
1117
- .then(() => {
1118
- this.checkingFiles = false;
1119
- this.uploaderLoading = true;
1148
+ request.upload.onprogress = (ev) => {
1149
+ if (ev.lengthComputable) {
1150
+ const job = this.uploadJobs.find((j) => j.id === jobId);
1151
+ if (job) job.progress = (ev.loaded / ev.total) * 100;
1152
+ }
1153
+ };
1120
1154
 
1121
- // No conflicts process all files
1122
- filesMapped.forEach((e) => {
1123
- this.processFiles(e.id);
1124
- });
1125
- })
1126
- .catch((err) => {
1127
- this.checkingFiles = false;
1128
-
1129
- if (err.code === -764) {
1130
- (err.files || []).forEach((file, index) => {
1131
- if (file.code === -764) {
1132
- this.existingFiles[filesMapped[index].filename] = file.duplicates;
1133
- }
1134
- });
1135
-
1136
- if (Object.keys(this.existingFiles).length) {
1137
- // Wait for user consent before uploading anything
1138
- this.showExistModal = true;
1139
- } else {
1140
- this.uploaderLoading = true;
1141
- filesMapped.forEach((e) => {
1142
- this.processFiles(e.id);
1143
- });
1144
- }
1145
- } else {
1146
- this.checkerFailedModal = true;
1147
- }
1148
- });
1149
- }
1155
+ request.onload = () => {
1156
+ const job = this.uploadJobs.find((j) => j.id === jobId);
1157
+ try {
1158
+ const res = JSON.parse(request.responseText);
1159
+ if (request.status >= 200 && request.status < 300 && res.code > 0) {
1160
+ if (job) { job.status = 'done'; job.progress = 100; }
1161
+ } else {
1162
+ if (job) job.status = 'error';
1163
+ }
1164
+ } catch (_e) {
1165
+ if (job) job.status = 'error';
1166
+ }
1167
+
1168
+ // If all jobs done, refresh directory
1169
+ if (this.uploadsInProgress === 0) {
1170
+ this.refreshDirectory(false, true);
1171
+ }
1172
+ };
1173
+
1174
+ request.onerror = () => {
1175
+ const job = this.uploadJobs.find((j) => j.id === jobId);
1176
+ if (job) job.status = 'error';
1177
+ };
1178
+
1179
+ request.send(formData);
1150
1180
  },
1151
1181
 
1152
1182
  removeLastSearch($event) {
@@ -1178,10 +1208,10 @@ export async function viewer(fields, e) {
1178
1208
  moment,
1179
1209
  },
1180
1210
  mounted() {
1181
- var onDrop = (files) => {
1211
+ var onDrop = (droppedFiles) => {
1182
1212
  if (this.view === 'slider') return;
1183
- this.uploadModal = true;
1184
- this.pond.addFiles(files);
1213
+ this.pendingFiles = Array.from(droppedFiles);
1214
+ this.checkAndUpload();
1185
1215
  };
1186
1216
 
1187
1217
  const { isOverDropZone } = useDropZone(this.$refs.dropZoneRef, {
@@ -1191,6 +1221,21 @@ export async function viewer(fields, e) {
1191
1221
  this.isOverDropZone = isOverDropZone;
1192
1222
  },
1193
1223
  computed: {
1224
+ uploadsInProgress() {
1225
+ return this.uploadJobs.filter((j) => j.status === 'uploading').length;
1226
+ },
1227
+ acceptTypes() {
1228
+ if (fields.file_upload_mime_type_preset) {
1229
+ switch (fields.file_upload_mime_type_preset) {
1230
+ case 'image': return 'image/*';
1231
+ case 'video': return 'video/*';
1232
+ case 'audio': return 'audio/*';
1233
+ default: return 'image/*';
1234
+ }
1235
+ }
1236
+ if (fields.file_upload_custom_mime_types) return fields.file_upload_custom_mime_types;
1237
+ return null;
1238
+ },
1194
1239
  searchTags() {
1195
1240
  return this.opts.search_string.split(' ').filter((e) => e.includes(':'));
1196
1241
  },
@@ -1269,34 +1314,6 @@ export async function viewer(fields, e) {
1269
1314
  async created() {
1270
1315
  this.refreshDirectory();
1271
1316
 
1272
- this.$nextTick(async () => {
1273
- // debugger;
1274
- var { pond, processFiles } = await this.initUploader({ ...fields, instant_file_upload: 'N' }, { ...e, $containerP: $('#imageUploaderComponent') });
1275
-
1276
- this.pond = pond;
1277
- this.processFiles = processFiles;
1278
-
1279
- // Avoid per-file DOM removal — let processfiles handle cleanup in bulk
1280
- this.pond.on('processfile', (e, { id }) => {
1281
- // if (!e?.type !== 'error') {
1282
- // this.pond.removeFiles(id);
1283
- // }
1284
-
1285
- // if (!this.pond.getFiles().length) {
1286
- // this.uploaderLoading = false;
1287
- // this.uploadModal = false;
1288
- // this.pond.removeFiles();
1289
- // }
1290
- });
1291
-
1292
- this.pond.on('processfiles', () => {
1293
- this.uploaderLoading = false;
1294
- this.uploadModal = false;
1295
- this.pond.removeFiles();
1296
- this.refreshDirectory(false, true);
1297
- });
1298
- });
1299
-
1300
1317
  this.$watch('opts', (value) => {
1301
1318
  this.refreshDirectory();
1302
1319
  });
package/tests/index.html CHANGED
@@ -1,57 +1,53 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="en">
3
-
4
- <head>
5
- <meta charset="UTF-8" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>AG Grid Plugin Tester</title>
8
-
9
- <script src="https://master.xuda.io/dist/runtime/js/xuda-runtime-slim.js"></script>
10
-
11
- <style>
12
- html,
13
- body {
14
- height: 100%;
15
- width: 100%;
16
- }
17
- </style>
18
-
19
- <link rel="stylesheet" href="./style.css" />
20
- </head>
21
-
22
- <body>
23
- <div id="tester"></div>
24
-
25
- <script type="module">
26
- import * as plugin from '../src/runtime.mjs';
27
-
28
- var xu = new xuda($('#tester')[0], { domain: 'master.xuda.io' });
29
-
30
- setTimeout(() => {
31
- const SESSION_ID = Object.keys(SESSION_OBJ)[0];
32
- var _session = {
33
- ...SESSION_OBJ[SESSION_ID],
34
- prog_id: '5b1_cmp_189d0a5c99e2',
35
- api_key: 'pk_e3c62dd6084d61dde054eb9ddb21bd21',
36
- DS_GLB: {
37
- 1: { prog_id: '5b1_cmp_189d0a5c99e2', rows_found: 0, data_feed: { rows: [] } },
38
- },
39
- };
40
-
41
- const e = {
42
- SESSION_ID,
43
- $containerP: $('#embed_' + SESSION_ID),
44
- dsP: 1,
45
- plugin_name: '@xuda.io/xuda-widget-plugin-xuda-drive',
46
- _session,
47
- SESSION_OBJ,
48
- };
49
-
50
-
51
-
52
- plugin.viewer({}, e);
53
- }, 2000);
54
- </script>
55
- </body>
56
-
57
- </html>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>AG Grid Plugin Tester</title>
7
+
8
+ <script src="https://master.xuda.ai/dist/runtime/js/xuda-runtime-slim.js"></script>
9
+
10
+ <style>
11
+ html,
12
+ body {
13
+ height: 100%;
14
+ width: 100%;
15
+ }
16
+ </style>
17
+
18
+ <link rel="stylesheet" href="./style.css" />
19
+ </head>
20
+
21
+ <body>
22
+ <div id="tester"></div>
23
+
24
+ <script type="module">
25
+ import * as plugin from '../src/runtime.mjs';
26
+
27
+ var xu = new xuda($('#tester')[0], { domain: 'master.xuda.io' });
28
+
29
+ setTimeout(() => {
30
+ const SESSION_ID = Object.keys(SESSION_OBJ)[0];
31
+ var _session = {
32
+ ...SESSION_OBJ[SESSION_ID],
33
+ prog_id: '5b1_cmp_189d0a5c99e2',
34
+ api_key: 'pk_e3c62dd6084d61dde054eb9ddb21bd21',
35
+ DS_GLB: {
36
+ 1: { prog_id: '5b1_cmp_189d0a5c99e2', rows_found: 0, data_feed: { rows: [] } },
37
+ },
38
+ };
39
+
40
+ const e = {
41
+ SESSION_ID,
42
+ $containerP: $('#embed_' + SESSION_ID),
43
+ dsP: 1,
44
+ plugin_name: '@xuda.io/xuda-widget-plugin-xuda-drive',
45
+ _session,
46
+ SESSION_OBJ,
47
+ };
48
+
49
+ plugin.viewer({}, e);
50
+ }, 2000);
51
+ </script>
52
+ </body>
53
+ </html>