From 6b385792e4669bb41095ec080997b70088342674 Mon Sep 17 00:00:00 2001
From: MiaoMint <44718819+MiaoMint@users.noreply.github.com>
Date: Sun, 18 Jan 2026 00:34:25 +0800
Subject: [PATCH 1/2] feat: implement asset file download functionality
---
binding/app/service.go | 3 ++
binding/database/service.go | 52 +++++++++++++++++++++++
frontend/src/components/asset-library.tsx | 28 ++++++++++--
frontend/wailsjs/go/database/Service.d.ts | 2 +
frontend/wailsjs/go/database/Service.js | 4 ++
main.go | 10 ++---
6 files changed, 90 insertions(+), 9 deletions(-)
diff --git a/binding/app/service.go b/binding/app/service.go
index f2a458a..33dfb07 100644
--- a/binding/app/service.go
+++ b/binding/app/service.go
@@ -1,6 +1,7 @@
package app
import (
+ "context"
"encoding/json"
"fmt"
"io"
@@ -10,6 +11,8 @@ import (
"visionflow/database"
)
+var WailsContext *context.Context
+
type Service struct {
WailsJSON string
}
diff --git a/binding/database/service.go b/binding/database/service.go
index c0b7abf..54319d0 100644
--- a/binding/database/service.go
+++ b/binding/database/service.go
@@ -4,12 +4,16 @@ import (
"crypto/md5"
"encoding/hex"
"fmt"
+ "io"
"os"
"path/filepath"
"time"
+ "visionflow/binding/app"
db "visionflow/database"
"visionflow/service/fileserver"
"visionflow/storage"
+
+ "github.com/wailsapp/wails/v2/pkg/runtime"
)
type Service struct {
@@ -142,3 +146,51 @@ func (s *Service) CreateAssetFromFile(name string, data []byte) (*db.Asset, erro
createdAsset.URL = fileserver.GetFileUrl(createdAsset.Path)
return createdAsset, nil
}
+
+func (s *Service) DownloadAssetFile(filename string) error {
+ // 1. Resolve source path
+ assetsDir, err := storage.GetAssetsDir()
+ if err != nil {
+ return fmt.Errorf("failed to get assets directory: %w", err)
+ }
+ sourcePath := filepath.Join(assetsDir, filename)
+
+ // Check if source file exists
+ if _, err := os.Stat(sourcePath); os.IsNotExist(err) {
+ return fmt.Errorf("source file does not exist: %s", filename)
+ }
+
+ // 2. Open Save Dialog
+ destPath, err := runtime.SaveFileDialog(*app.WailsContext, runtime.SaveDialogOptions{
+ DefaultFilename: filename,
+ Title: "Save Asset File",
+ })
+ if err != nil {
+ return err
+ }
+
+ // User cancelled
+ if destPath == "" {
+ return nil
+ }
+
+ // 3. Copy File efficiently
+ source, err := os.Open(sourcePath)
+ if err != nil {
+ return fmt.Errorf("failed to open source file: %w", err)
+ }
+ defer source.Close()
+
+ destination, err := os.Create(destPath)
+ if err != nil {
+ return fmt.Errorf("failed to create destination file: %w", err)
+ }
+ defer destination.Close()
+
+ _, err = io.Copy(destination, source)
+ if err != nil {
+ return fmt.Errorf("failed to save file: %w", err)
+ }
+
+ return nil
+}
diff --git a/frontend/src/components/asset-library.tsx b/frontend/src/components/asset-library.tsx
index 7816ffb..5011b2e 100644
--- a/frontend/src/components/asset-library.tsx
+++ b/frontend/src/components/asset-library.tsx
@@ -1,9 +1,9 @@
import { useState, useEffect, useRef } from "react";
import { database } from "../../wailsjs/go/models";
-import { ListAssets, DeleteAsset } from "../../wailsjs/go/database/Service";
+import { ListAssets, DeleteAsset, DownloadAssetFile } from "../../wailsjs/go/database/Service";
import { Card, CardContent, CardFooter } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
-import { Trash2, FileImage, FileVideo, FileAudio } from "lucide-react";
+import { Trash2, FileImage, FileVideo, FileAudio, Download } from "lucide-react";
import { toast } from "sonner";
import { msg } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
@@ -53,7 +53,16 @@ export function AssetLibrary() {
}
};
-
+ const handleSave = async (e: React.MouseEvent, asset: database.Asset) => {
+ e.stopPropagation();
+ try {
+ await DownloadAssetFile(asset.path);
+ toast.success(_(msg`Asset saved`));
+ } catch (err) {
+ console.error("Failed to save asset:", err);
+ toast.error(_(msg`Failed to save`));
+ }
+ };
return (
@@ -89,6 +98,14 @@ export function AssetLibrary() {
+