@xuda.io/xuda-widget-plugin-xuda-drive 1.0.135 → 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.135",
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
@@ -633,7 +633,7 @@ export async function viewer(fields, e) {
633
633
 
634
634
  <div>
635
635
  <button
636
- @click="uploadModal = true"
636
+ @click="$refs.fileInput.click()"
637
637
  type="button"
638
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"
639
639
  >
@@ -751,129 +751,91 @@ export async function viewer(fields, e) {
751
751
  </div>
752
752
  </div>
753
753
 
754
- <teleport to="body">
755
- <div @keydown.escape.window="uploadModal = false" v-show="uploadModal" class="fixed top-0 left-0 z-[99] w-screen h-screen" x-cloak>
756
- <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" />
757
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>
758
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">
759
- <div class="flex items-center justify-center size-full" v-show="uploadModal">
760
- <transition
761
- key="inner"
762
- enter-active-class="ease-out duration-300"
763
- enter-from-class="opacity-0 -translate-y-2 sm:scale-95"
764
- enter-to-class="opacity-100 translate-y-0 sm:scale-100"
765
- leave-active-class="ease-in duration-200"
766
- leave-from-class="opacity-100 translate-y-0 sm:scale-100"
767
- leave-to-class="opacity-0 -translate-y-2 sm:scale-95"
768
- >
769
- <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">
770
765
  <div class="flex items-center justify-between pb-3">
771
- <h3 class="text-lg font-semibold" v-text="'Upload file' + (options.file_upload_allow_multiple_files ? 's' : '')"></h3>
772
- <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">
773
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>
774
769
  </button>
775
770
  </div>
776
- <div id="imageUploaderComponent" class="relative w-auto pb-8 h-48 [&>div]:h-full [&>div>div]:h-full"></div>
777
- <div class="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2">
778
- <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">
779
786
  <div>Dismiss</div>
780
787
  </button>
781
- <button
782
- @click="uploadFiles(false)"
783
- :disabled="uploaderLoading || checkingFiles"
784
- type="button"
785
- 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"
786
- >
787
- <template v-if="checkingFiles">
788
- <sepcial-loader class="size-5" />
789
- Checking files...
790
- </template>
791
-
792
- <template v-else-if="uploaderLoading">
793
- <sepcial-loader class="size-5" />
794
- Uploading
795
- </template>
796
-
797
- <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
798
790
  </button>
799
791
  </div>
800
-
801
- <div v-show="showExistModal" class="absolute inset-0 bg-white py-6 px-7 flex flex-col">
802
- <div class="flex items-center justify-between pb-3">
803
- <h3 class="text-lg font-semibold">Upload options</h3>
804
- <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">
805
- <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>
806
- </button>
807
- </div>
808
-
809
- <div class="flex-1">
810
- <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>
811
-
812
- <fieldset>
813
- <div class="mt-6 space-y-5 pl-2">
814
- <div v-for="option in replaceOptions" :key="option.value">
815
- <label :for="option.value" class="flex items-center cursor-pointer">
816
- <input :id="option.value" name="notification-method" type="radio" :value="option.value" v-model="overrideAction" :checked="option.value === overrideAction" class="option-radio peer" />
817
- <div class="ml-3 block font-medium peer-checked:text-black text-light-700 cursor-pointer">{{ option.label }}</div>
818
- </label>
819
- </div>
820
- </div>
821
- </fieldset>
822
- </div>
823
-
824
- <div class="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2">
825
- <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">
826
- <div>Dismiss</div>
827
- </button>
828
- <button
829
- @click="uploadFiles(true)"
830
- :disabled="uploaderLoading"
831
- type="button"
832
- 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"
833
- >
834
- Upload
835
- </button>
836
- </div>
837
- </div>
838
-
839
- <div v-show="checkerFailedModal" class="absolute inset-0 bg-white py-6 px-7 flex flex-col">
840
- <div class="flex items-center justify-between pb-3">
841
- <h3 class="text-lg font-semibold">Checker failed</h3>
842
- <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">
843
- <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>
844
- </button>
845
- </div>
846
-
847
- <div class="flex-1">
848
- <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.
849
-
850
- </p>
851
-
852
-
853
- </div>
854
-
855
- <div class="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2">
856
- <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">
857
- <div>Dismiss</div>
858
- </button>
859
- <button
860
- @click="uploadFiles(false), (checkerFailedModal = false)"
861
- :disabled="uploaderLoading"
862
- type="button"
863
- 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"
864
- >
865
- Retry
866
- </button>
867
- </div>
868
- </div>
869
-
870
-
871
-
872
792
  </div>
873
793
  </transition>
874
794
  </div>
875
795
  </transition>
876
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>
877
839
  </teleport>
878
840
  </div>
879
841
 
@@ -992,18 +954,18 @@ export async function viewer(fields, e) {
992
954
  folders: [],
993
955
  files: [],
994
956
  view: fields.default_view_type || 'gallery',
995
- uploadModal: false,
996
- uploaderLoading: false,
997
- checkingFiles: false,
998
957
  loadingSearch: false,
999
958
  fetcher,
1000
959
  formatBytes,
1001
- initUploader: upload,
1002
960
  swiper: null,
1003
961
  existingTags: '',
1004
962
  showSearchPopper: false,
1005
963
  isOverDropZone: false,
1006
964
  removeChipCount: 0,
965
+ // Upload job window state
966
+ uploadJobs: [],
967
+ jobWindowCollapsed: false,
968
+ pendingFiles: [],
1007
969
  searchOptions: [
1008
970
  {
1009
971
  label: 'Filename',
@@ -1035,10 +997,7 @@ export async function viewer(fields, e) {
1035
997
  existingFiles: {},
1036
998
  allLoading: false,
1037
999
  showExistModal: false,
1038
- checkerFailedModal: false,
1039
1000
  overrideAction: 'replace',
1040
- pond: null,
1041
- processFiles: null,
1042
1001
  };
1043
1002
  },
1044
1003
  methods: {
@@ -1085,67 +1044,139 @@ export async function viewer(fields, e) {
1085
1044
  refreshFsLightbox();
1086
1045
  });
1087
1046
  },
1088
- uploadFiles(is_after_confirm) {
1089
- 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) {
1090
1088
  this.showExistModal = false;
1091
- this.uploaderLoading = true;
1092
- // this.processCb();
1093
- this.processFiles(null, {
1094
- overrideAction: this.overrideAction,
1095
- 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',
1096
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();
1097
1131
 
1098
- // this.pond.on("processfile", (e) => {
1099
- // this.uploaderLoading = false;
1100
- // this.uploadModal = false;
1101
- // 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
+ }
1102
1136
 
1103
- // this.refreshDirectory();
1104
- // });
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');
1105
1141
  } else {
1106
- this.existingFiles = {};
1107
- this.checkingFiles = true;
1142
+ request.open('POST', 'https://' + _domain + '/cpi/upload_drive_file_workspace');
1143
+ }
1108
1144
 
1109
- var filesMapped = this.pond.getFiles().map((e) => ({ id: e.id, filename: e.file.name }));
1110
- var file_paths = filesMapped.map(({ filename }) => '/' + filename);
1145
+ request.setRequestHeader('xu-gtp-token', gtp_token);
1146
+ request.setRequestHeader('xu-app-token', app_token);
1111
1147
 
1112
- this.fetcher(`check_drive_files_workspace`, {
1113
- file_paths,
1114
- type: 'file',
1115
- })
1116
- .then(() => {
1117
- this.checkingFiles = false;
1118
- 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
+ };
1119
1154
 
1120
- // No conflicts process all files
1121
- filesMapped.forEach((e) => {
1122
- this.processFiles(e.id);
1123
- });
1124
- })
1125
- .catch((err) => {
1126
- this.checkingFiles = false;
1127
-
1128
- if (err.code === -764) {
1129
- (err.files || []).forEach((file, index) => {
1130
- if (file.code === -764) {
1131
- this.existingFiles[filesMapped[index].filename] = file.duplicates;
1132
- }
1133
- });
1134
-
1135
- if (Object.keys(this.existingFiles).length) {
1136
- // Wait for user consent before uploading anything
1137
- this.showExistModal = true;
1138
- } else {
1139
- this.uploaderLoading = true;
1140
- filesMapped.forEach((e) => {
1141
- this.processFiles(e.id);
1142
- });
1143
- }
1144
- } else {
1145
- this.checkerFailedModal = true;
1146
- }
1147
- });
1148
- }
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);
1149
1180
  },
1150
1181
 
1151
1182
  removeLastSearch($event) {
@@ -1177,10 +1208,10 @@ export async function viewer(fields, e) {
1177
1208
  moment,
1178
1209
  },
1179
1210
  mounted() {
1180
- var onDrop = (files) => {
1211
+ var onDrop = (droppedFiles) => {
1181
1212
  if (this.view === 'slider') return;
1182
- this.uploadModal = true;
1183
- this.pond.addFiles(files);
1213
+ this.pendingFiles = Array.from(droppedFiles);
1214
+ this.checkAndUpload();
1184
1215
  };
1185
1216
 
1186
1217
  const { isOverDropZone } = useDropZone(this.$refs.dropZoneRef, {
@@ -1190,6 +1221,21 @@ export async function viewer(fields, e) {
1190
1221
  this.isOverDropZone = isOverDropZone;
1191
1222
  },
1192
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
+ },
1193
1239
  searchTags() {
1194
1240
  return this.opts.search_string.split(' ').filter((e) => e.includes(':'));
1195
1241
  },
@@ -1268,33 +1314,6 @@ export async function viewer(fields, e) {
1268
1314
  async created() {
1269
1315
  this.refreshDirectory();
1270
1316
 
1271
- this.$nextTick(async () => {
1272
- // debugger;
1273
- var { pond, processFiles } = await this.initUploader({ ...fields, instant_file_upload: 'N' }, { ...e, $containerP: $('#imageUploaderComponent') });
1274
-
1275
- this.pond = pond;
1276
- this.processFiles = processFiles;
1277
-
1278
- // Avoid per-file DOM removal — let processfiles handle cleanup in bulk
1279
- this.pond.on('processfile', (e, { id }) => {
1280
- // if (!e?.type !== 'error') {
1281
- // this.pond.removeFiles(id);
1282
- // }
1283
- // if (!this.pond.getFiles().length) {
1284
- // this.uploaderLoading = false;
1285
- // this.uploadModal = false;
1286
- // this.pond.removeFiles();
1287
- // }
1288
- });
1289
-
1290
- this.pond.on('processfiles', () => {
1291
- this.uploaderLoading = false;
1292
- this.uploadModal = false;
1293
- this.pond.removeFiles();
1294
- this.refreshDirectory(false, true);
1295
- });
1296
- });
1297
-
1298
1317
  this.$watch('opts', (value) => {
1299
1318
  this.refreshDirectory();
1300
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>