Эх сурвалжийг харах

ux: improve model import visibility and add auto-import on paste

unknown 2 сар өмнө
parent
commit
ee71ae1118

+ 41 - 3
src/components/ModelUploadSection.vue

@@ -158,6 +158,7 @@
             </label>
             <div class="relative group">
               <input v-model="modelLink" type="url" :placeholder="t('upload.modelLinkPlaceholder')"
+                @paste="onLinkPaste"
                 class="w-full bg-secondary/50 border border-black/[0.03] rounded-2xl px-4 py-2.5 pr-12 focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary transition-all text-sm font-medium" />
               <button 
                 v-if="modelLink.includes('thingiverse.com') || modelLink.includes('printables.com')"
@@ -171,6 +172,25 @@
                 <LinkIcon v-else class="w-4 h-4" />
               </button>
             </div>
+            
+            <!-- Animated Import Prompt -->
+            <Transition enter-active-class="transition duration-300 ease-out" enter-from-class="transform -translate-y-2 opacity-0" enter-to-class="transform translate-y-0 opacity-100">
+              <div v-if="(modelLink.includes('thingiverse.com') || modelLink.includes('printables.com')) && !files.length && !isImporting" 
+                class="mt-3 p-4 bg-primary/10 border border-primary/20 rounded-2xl flex items-center justify-between gap-4 animate-pulse-subtle">
+                <div class="flex items-center gap-3">
+                  <div class="w-10 h-10 rounded-full bg-primary/20 flex items-center justify-center text-primary">
+                    <FileBox class="w-5 h-5" />
+                  </div>
+                  <div>
+                    <p class="text-xs font-bold text-primary uppercase tracking-tight">{{ t("common.import") }}?</p>
+                    <p class="text-[10px] text-primary/60 font-medium">{{ t("upload.importHint") || "We can automatically download files from this link!" }}</p>
+                  </div>
+                </div>
+                <Button @click="handleImportUrl" variant="hero" size="sm" class="h-9 px-4 rounded-xl shadow-lg">
+                  {{ t("common.import") }}
+                </Button>
+              </div>
+            </Transition>
           </div>
         </div>
 
@@ -502,16 +522,21 @@ async function handleImportUrl() {
   }
 
   isImporting.value = true;
+  const loadingToast = toast.loading(t("common.loading") + "...");
+  
   try {
     const res = await importFromUrl(modelLink.value);
     if (res.status === "success" && res.files) {
       for (const f of res.files) {
+        // Prevent duplicates
+        if (files.value.some(existing => existing.dbId === f.id)) continue;
+        
         files.value.push({
           id: Math.random().toString(36).substr(2, 9),
           dbId: f.id,
           name: f.filename,
           size: f.size,
-          type: "model/stl", // Assumption
+          type: "model/stl",
           quantity: 1,
           printTime: f.print_time,
           filamentG: f.filament_g,
@@ -519,16 +544,29 @@ async function handleImportUrl() {
           isUploading: false
         });
       }
-      toast.success(t("common.success"));
+      toast.success(t("common.success"), { id: loadingToast });
     }
   } catch (e: any) {
     console.error("Import failed:", e);
-    toast.error(e.message || "Failed to import models. Check URL or try again later.");
+    let msg = e.message || "Failed to import models";
+    if (modelLink.value.includes("printables.com")) {
+      msg = "Printables link failed. This site is often protected. Please download ZIP manually and upload here.";
+    }
+    toast.error(msg, { id: loadingToast });
   } finally {
     isImporting.value = false;
   }
 }
 
+function onLinkPaste() {
+  // Wait for next tick to let v-model update
+  setTimeout(() => {
+    if ((modelLink.value.includes('thingiverse.com') || modelLink.value.includes('printables.com')) && !files.length) {
+      handleImportUrl();
+    }
+  }, 100);
+}
+
 async function handleFileSelect(e: Event) { addFiles(Array.from((e.target as HTMLInputElement).files ?? [])); }
 function removeFile(id: string) { files.value = files.value.filter(f => f.id !== id); }
 function formatFileSize(bytes: number) {