diff --git a/app.go b/app.go index 7f9f213..9745cbb 100644 --- a/app.go +++ b/app.go @@ -14,6 +14,7 @@ import ( "u-desk/internal/common" "u-desk/internal/database" "u-desk/internal/filesystem" + "u-desk/internal/service" "u-desk/internal/storage" "u-desk/internal/system" @@ -59,7 +60,11 @@ func (a *App) Startup(ctx context.Context) { // 2.5. 迁移旧配置 _ = a.configAPI.MigrateTabConfig() - // 3. 读取配置,获取可见的 Tabs + // 3. 初始化版本号(提前触发缓存,避免后续重复计算) + version := service.GetCurrentVersion() + fmt.Printf("[启动] 当前版本: %s\n", version) + + // 4. 读取配置,获取可见的 Tabs visibleTabs := a.getVisibleTabs() fmt.Printf("[启动] 可用的模块: %v\n", visibleTabs) @@ -173,28 +178,31 @@ func (a *App) startFileServer() { return } - // 创建一个占位服务器用于保持引用(实际服务器由 StartLocalFileServer 管理) - a.fileServer = &http.Server{ - Addr: "localhost:18765", - } - fmt.Println("[文件服务器] 启动在 http://localhost:18765") } // Shutdown 应用关闭时调用 func (a *App) Shutdown(ctx context.Context) { - // 关闭文件系统服务(优雅关闭,释放资源) + // 创建带超时的上下文(5秒超时) + shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + // 1. 关闭文件系统服务(优雅关闭,释放资源) if a.filesystem != nil { fmt.Println("[文件系统服务] 正在关闭...") - if err := a.filesystem.Close(ctx); err != nil { + if err := a.filesystem.Close(shutdownCtx); err != nil { fmt.Printf("[文件系统服务] 关闭失败: %v\n", err) + } else { + fmt.Println("[文件系统服务] 已关闭") } } - // 停止文件服务器 - if a.fileServer != nil { - fmt.Println("[文件服务器] 正在关闭...") - a.fileServer.Shutdown(ctx) + // 2. 停止文件服务器(使用全局服务器的关闭方法) + fmt.Println("[文件服务器] 正在关闭...") + if err := filesystem.ShutdownLocalFileServer(); err != nil { + fmt.Printf("[文件服务器] 关闭失败: %v\n", err) + } else { + fmt.Println("[文件服务器] 已关闭") } } diff --git a/internal/api/update_api.go b/internal/api/update_api.go index 4109a1f..b5de1b6 100644 --- a/internal/api/update_api.go +++ b/internal/api/update_api.go @@ -50,13 +50,6 @@ func (api *UpdateAPI) CheckUpdate() (map[string]interface{}, error) { // GetCurrentVersion 获取当前版本号 func (api *UpdateAPI) GetCurrentVersion() (map[string]interface{}, error) { version := service.GetCurrentVersion() - - // 同步配置中的版本号 - if config, err := service.LoadUpdateConfig(); err == nil && config.CurrentVersion != version { - config.CurrentVersion = version - service.SaveUpdateConfig(config) - } - return successResponse(map[string]interface{}{ "version": version, }), nil @@ -69,13 +62,6 @@ func (api *UpdateAPI) GetUpdateConfig() (map[string]interface{}, error) { return errorResponse(err.Error()), nil } - // 同步最新版本号 - latestVersion := service.GetCurrentVersion() - if config.CurrentVersion != latestVersion { - config.CurrentVersion = latestVersion - service.SaveUpdateConfig(config) - } - return successResponse(map[string]interface{}{ "current_version": config.CurrentVersion, "last_check_time": config.LastCheckTime.Format("2006-01-02 15:04:05"), diff --git a/internal/filesystem/asset_handler.go b/internal/filesystem/asset_handler.go index 8d25527..911900a 100644 --- a/internal/filesystem/asset_handler.go +++ b/internal/filesystem/asset_handler.go @@ -1,6 +1,7 @@ package filesystem import ( + "context" "encoding/base64" "fmt" "log" @@ -10,12 +11,14 @@ import ( "path/filepath" "strings" "sync" + "time" ) // LocalFileServer 本地文件服务器(独立的 HTTP 服务器) type LocalFileServer struct { server *http.Server addr string + mu sync.RWMutex } var ( @@ -258,3 +261,35 @@ func HandleLocalFile(w http.ResponseWriter, r *http.Request) { func isAllowedFileType(ext string) bool { return defaultFileTypeManager.IsAllowed(ext) } + +// Shutdown 优雅关闭文件服务器 +func (lfs *LocalFileServer) Shutdown() error { + if lfs == nil || lfs.server == nil { + return nil + } + + lfs.mu.Lock() + defer lfs.mu.Unlock() + + // 创建带超时的上下文 + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + log.Printf("[LocalFileServer] 正在关闭...") + + if err := lfs.server.Shutdown(ctx); err != nil { + log.Printf("[LocalFileServer] 关闭失败: %v", err) + return err + } + + log.Printf("[LocalFileServer] 已关闭") + return nil +} + +// ShutdownLocalFileServer 关闭全局文件服务器 +func ShutdownLocalFileServer() error { + if localFileServer != nil { + return localFileServer.Shutdown() + } + return nil +} diff --git a/internal/filesystem/service.go b/internal/filesystem/service.go index 7b2a50f..1b2fb05 100644 --- a/internal/filesystem/service.go +++ b/internal/filesystem/service.go @@ -262,7 +262,7 @@ func (s *FileSystemService) DeletePathWithContext(ctx context.Context, path stri // 返回被删除的文件信息,用于前端更新 return &FileOperationResult{ - Path: path, + Path: filepath.ToSlash(path), // 统一使用正斜杠 Name: info.Name(), Size: info.Size(), SizeStr: formatBytes(info.Size()), @@ -297,7 +297,7 @@ func (s *FileSystemService) ListDir(path string) ([]map[string]interface{}, erro fullPath := filepath.Join(path, entry.Name()) result = append(result, map[string]interface{}{ "name": entry.Name(), - "path": fullPath, + "path": filepath.ToSlash(fullPath), // 统一使用正斜杠 "is_dir": entry.IsDir(), "size": info.Size(), "mod_time": info.ModTime().Format("2006-01-02 15:04:05"), @@ -338,14 +338,14 @@ func (s *FileSystemService) CreateDir(path string) (*FileOperationResult, error) if err != nil { // 创建成功但获取信息失败,返回基本信息 return &FileOperationResult{ - Path: path, + Path: filepath.ToSlash(path), // 统一使用正斜杠 Name: filepath.Base(path), IsDir: true, }, nil } return &FileOperationResult{ - Path: path, + Path: filepath.ToSlash(path), // 统一使用正斜杠 Name: info.Name(), Size: info.Size(), SizeStr: formatBytes(info.Size()), @@ -385,7 +385,7 @@ func (s *FileSystemService) CreateFile(path string) (*FileOperationResult, error if err != nil { // 创建成功但获取信息失败,返回基本信息 return &FileOperationResult{ - Path: path, + Path: filepath.ToSlash(path), // 统一使用正斜杠 Name: filepath.Base(path), IsDir: false, Size: 0, @@ -393,7 +393,7 @@ func (s *FileSystemService) CreateFile(path string) (*FileOperationResult, error } return &FileOperationResult{ - Path: path, + Path: filepath.ToSlash(path), // 统一使用正斜杠 Name: info.Name(), Size: info.Size(), SizeStr: formatBytes(info.Size()), @@ -424,7 +424,7 @@ func (s *FileSystemService) GetFileInfo(path string) (map[string]interface{}, er return map[string]interface{}{ "name": info.Name(), - "path": path, + "path": filepath.ToSlash(path), // 统一使用正斜杠 "size": info.Size(), "size_str": formatBytes(info.Size()), "is_dir": info.IsDir(), @@ -472,21 +472,21 @@ func (s *FileSystemService) RenamePath(oldPath, newPath string) (*FileOperationR if err != nil { // 重命名成功但获取信息失败,返回基本信息 return &FileOperationResult{ - Path: newPath, + Path: filepath.ToSlash(newPath), // 统一使用正斜杠 Name: filepath.Base(newPath), - OldPath: oldPath, + OldPath: filepath.ToSlash(oldPath), }, nil } return &FileOperationResult{ - Path: newPath, + Path: filepath.ToSlash(newPath), // 统一使用正斜杠 Name: info.Name(), Size: info.Size(), SizeStr: formatBytes(info.Size()), IsDir: info.IsDir(), ModTime: info.ModTime().Format("2006-01-02 15:04:05"), Mode: info.Mode().String(), - OldPath: oldPath, + OldPath: filepath.ToSlash(oldPath), }, nil } diff --git a/internal/filesystem/zip.go b/internal/filesystem/zip.go index cbdbc40..7fe8368 100644 --- a/internal/filesystem/zip.go +++ b/internal/filesystem/zip.go @@ -181,7 +181,7 @@ func ListZipContents(zipPath string) ([]map[string]interface{}, error) { entry := map[string]interface{}{ "name": name, - "path": file.Name, // zip 中的完整路径 + "path": file.Name, // zip 中的完整路径(已使用 /) "is_dir": isDir, "size": file.UncompressedSize64, "compressed": file.CompressedSize64, diff --git a/internal/filesystem/zip_helper.go b/internal/filesystem/zip_helper.go index ebda4c7..a82e460 100644 --- a/internal/filesystem/zip_helper.go +++ b/internal/filesystem/zip_helper.go @@ -103,7 +103,7 @@ func getCompressionMethodString(method uint16) string { func createFileInfoMap(file *zip.File, includeExtra ...bool) map[string]interface{} { info := map[string]interface{}{ "name": filepath.Base(file.Name), - "path": file.Name, + "path": file.Name, // zip 中的路径(已使用 /) "is_dir": file.Mode().IsDir(), "size": file.UncompressedSize64, "compressed": file.CompressedSize64, diff --git a/internal/service/update.go b/internal/service/update.go index 5d3e7cd..6ecb75e 100644 --- a/internal/service/update.go +++ b/internal/service/update.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "io" - "log" "net/http" "os" "os/exec" @@ -62,20 +61,13 @@ func NewUpdateService(checkURL string) *UpdateService { // CheckUpdate 检查更新 func (s *UpdateService) CheckUpdate() (*UpdateCheckResult, error) { - log.Printf("[更新检查] 开始检查更新,检查地址: %s", s.checkURL) - config, err := LoadUpdateConfig() if err != nil { return nil, fmt.Errorf("加载配置失败: %v", err) } - // 同步版本号 - currentVersionStr, err := s.syncConfigVersion(config) - if err != nil { - return nil, err - } - - currentVersion, err := ParseVersion(currentVersionStr) + // 获取当前版本(使用缓存) + currentVersion, err := ParseVersion(GetCurrentVersion()) if err != nil { return nil, fmt.Errorf("解析当前版本失败: %v", err) } @@ -86,14 +78,6 @@ func (s *UpdateService) CheckUpdate() (*UpdateCheckResult, error) { return nil, fmt.Errorf("获取远程版本信息失败: %v", err) } - log.Printf("[更新检查] 远程版本信息: 版本=%s, 下载地址=%s, 强制更新=%v, 更新日志长度=%d", - remoteInfo.Version, remoteInfo.DownloadURL, remoteInfo.ForceUpdate, len(remoteInfo.Changelog)) - if remoteInfo.Changelog != "" { - log.Printf("[更新检查] 更新日志内容: %s", remoteInfo.Changelog) - } else { - log.Printf("[更新检查] 警告: 远程接口未返回更新日志") - } - // 解析远程版本号 remoteVersion, err := ParseVersion(remoteInfo.Version) if err != nil { @@ -102,55 +86,30 @@ func (s *UpdateService) CheckUpdate() (*UpdateCheckResult, error) { // 比较版本 hasUpdate := remoteVersion.IsNewerThan(currentVersion) - log.Printf("[更新检查] 版本比较: 当前=%s, 远程=%s, 有更新=%v", - currentVersion.String(), remoteVersion.String(), hasUpdate) // 更新最后检查时间 config.UpdateLastCheckTime() - result := &UpdateCheckResult{ + return &UpdateCheckResult{ HasUpdate: hasUpdate, - CurrentVersion: currentVersionStr, + CurrentVersion: GetCurrentVersion(), LatestVersion: remoteInfo.Version, DownloadURL: remoteInfo.DownloadURL, Changelog: remoteInfo.Changelog, ForceUpdate: remoteInfo.ForceUpdate, ReleaseDate: remoteInfo.ReleaseDate, FileSize: remoteInfo.FileSize, - } - - log.Printf("[更新检查] 检查完成: 有更新=%v", hasUpdate) - return result, nil -} - -// syncConfigVersion 同步配置中的版本号 -func (s *UpdateService) syncConfigVersion(config *UpdateConfig) (string, error) { - currentVersionStr := GetCurrentVersion() - if currentVersionStr == "" { - currentVersionStr = config.CurrentVersion - log.Printf("[更新检查] 使用配置中的版本号: %s", currentVersionStr) - } else if config.CurrentVersion != currentVersionStr { - log.Printf("[更新检查] 配置中的版本号 (%s) 与当前版本号 (%s) 不一致,更新配置", - config.CurrentVersion, currentVersionStr) - config.CurrentVersion = currentVersionStr - if err := SaveUpdateConfig(config); err != nil { - log.Printf("[更新检查] 更新配置失败: %v", err) - } - } - return currentVersionStr, nil + }, nil } // fetchRemoteVersionInfo 获取远程版本信息 func (s *UpdateService) fetchRemoteVersionInfo() (*RemoteVersionInfo, error) { if s.checkURL == "" { - log.Printf("[远程版本] 版本检查 URL 未配置") return nil, fmt.Errorf("版本检查 URL 未配置,请先设置检查地址") } - log.Printf("[远程版本] 请求远程版本信息: %s", s.checkURL) - // 添加时间戳参数防止缓存 - timestamp := time.Now().UnixMilli() // 使用毫秒级时间戳 + timestamp := time.Now().UnixMilli() var requestURL string if strings.Contains(s.checkURL, "?") { requestURL = fmt.Sprintf("%s&t=%d", s.checkURL, timestamp) @@ -158,8 +117,6 @@ func (s *UpdateService) fetchRemoteVersionInfo() (*RemoteVersionInfo, error) { requestURL = fmt.Sprintf("%s?t=%d", s.checkURL, timestamp) } - log.Printf("[远程版本] 实际请求URL: %s", requestURL) - // 创建 HTTP 客户端,设置超时 client := &http.Client{ Timeout: 10 * time.Second, @@ -168,12 +125,10 @@ func (s *UpdateService) fetchRemoteVersionInfo() (*RemoteVersionInfo, error) { // 发送请求 resp, err := client.Get(requestURL) if err != nil { - log.Printf("[远程版本] 网络请求失败: %v", err) return nil, fmt.Errorf("网络请求失败: %v", err) } defer resp.Body.Close() - log.Printf("[远程版本] HTTP 响应状态码: %d", resp.StatusCode) if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("服务器返回错误状态码: %d", resp.StatusCode) } @@ -181,25 +136,19 @@ func (s *UpdateService) fetchRemoteVersionInfo() (*RemoteVersionInfo, error) { // 读取响应 body, err := io.ReadAll(resp.Body) if err != nil { - log.Printf("[远程版本] 读取响应失败: %v", err) return nil, fmt.Errorf("读取响应失败: %v", err) } - log.Printf("[远程版本] 响应内容长度: %d 字节", len(body)) - // 解析 JSON var remoteInfo RemoteVersionInfo if err := json.Unmarshal(body, &remoteInfo); err != nil { - log.Printf("[远程版本] 解析 JSON 失败: %v, 响应内容: %s", err, string(body)) return nil, fmt.Errorf("解析响应失败: %v", err) } if remoteInfo.Version == "" { - log.Printf("[远程版本] 远程版本信息不完整,响应内容: %s", string(body)) return nil, fmt.Errorf("远程版本信息不完整") } - log.Printf("[远程版本] 成功获取远程版本信息: %+v", remoteInfo) return &remoteInfo, nil } diff --git a/internal/service/update_config.go b/internal/service/update_config.go index c5405ea..5692172 100644 --- a/internal/service/update_config.go +++ b/internal/service/update_config.go @@ -3,7 +3,6 @@ package service import ( "encoding/json" "fmt" - "log" "os" "path/filepath" "time" @@ -71,20 +70,16 @@ func LoadUpdateConfig() (*UpdateConfig, error) { } } - // 同步最新版本号 - latestVersion := GetCurrentVersion() - if config.CurrentVersion == "" || config.CurrentVersion != latestVersion { - if config.CurrentVersion != "" { - log.Printf("[配置] 配置中的版本号 (%s) 与最新版本号 (%s) 不一致", config.CurrentVersion, latestVersion) - } - config.CurrentVersion = latestVersion - } - // 使用默认检查地址 if config.CheckURL == "" { config.CheckURL = "https://img.1216.top/u-desk/last-version.json" } + // 确保版本号不为空(使用缓存的版本号) + if config.CurrentVersion == "" { + config.CurrentVersion = GetCurrentVersion() + } + return &config, nil } diff --git a/internal/service/version.go b/internal/service/version.go index 7b9af7e..370f3aa 100644 --- a/internal/service/version.go +++ b/internal/service/version.go @@ -8,6 +8,7 @@ import ( "path/filepath" "strconv" "strings" + "sync" ) // ==================== 常量定义 ==================== @@ -15,6 +16,12 @@ import ( // AppVersion 应用版本号(发布时直接修改此处) const AppVersion = "0.3.0" +// 版本号缓存 +var ( + cachedVersion string + versionOnce sync.Once +) + // ==================== 类型定义 ==================== // Version 版本号结构 @@ -100,22 +107,25 @@ func (v *Version) IsOlderThan(other *Version) bool { // ==================== 版本号获取 ==================== -// GetCurrentVersion 获取当前版本号 +// GetCurrentVersion 获取当前版本号(带缓存) // 优先级:硬编码版本号 > wails.json(开发模式)> 默认值 func GetCurrentVersion() string { - if AppVersion != "" { - log.Printf("[版本] 使用硬编码版本号: %s", AppVersion) - return AppVersion - } + versionOnce.Do(func() { + if AppVersion != "" { + cachedVersion = AppVersion + return + } - version := getVersionFromWailsJSON() - if version != "" { - log.Printf("[版本] 从 wails.json 获取版本号: %s", version) - return version - } + version := getVersionFromWailsJSON() + if version != "" { + cachedVersion = version + return + } - log.Printf("[版本] 使用默认版本号: 0.0.1") - return "0.0.1" + cachedVersion = "0.0.1" + }) + + return cachedVersion } // ==================== 配置文件读取 ==================== diff --git a/web/package-lock.json b/web/package-lock.json index 70bfa8f..a4f8211 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -34,10 +34,13 @@ "highlight.js": "^11.11.1", "marked": "^17.0.1", "mermaid": "^11.12.2", + "pinia": "^3.0.4", "vue": "^3.5.26" }, "devDependencies": { "@vitejs/plugin-vue": "^6.0.3", + "unplugin-auto-import": "^0.18.3", + "unplugin-vue-components": "^0.27.4", "vite": "^7.3.0" } }, @@ -54,6 +57,16 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@antfu/utils": { + "version": "0.7.10", + "resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@arco-design/color": { "version": "0.4.0", "license": "MIT", @@ -1157,6 +1170,44 @@ "langium": "3.3.1" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.53", "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", @@ -1164,6 +1215,29 @@ "dev": true, "license": "MIT" }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/rollup-win32-x64-gnu": { "version": "4.54.0", "cpu": [ @@ -1530,6 +1604,39 @@ "@vue/shared": "3.5.26" } }, + "node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, "node_modules/@vue/reactivity": { "version": "3.5.26", "license": "MIT", @@ -1582,6 +1689,33 @@ "node": ">=0.4.0" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/b-tween": { "version": "0.3.3", "license": "MIT" @@ -1590,6 +1724,58 @@ "version": "1.5.3", "license": "MIT" }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/chevrotain": { "version": "11.0.3", "resolved": "https://registry.npmmirror.com/chevrotain/-/chevrotain-11.0.3.tgz", @@ -1622,6 +1808,31 @@ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", "license": "MIT" }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/color": { "version": "3.2.1", "license": "MIT", @@ -1668,6 +1879,21 @@ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", "license": "MIT" }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/cose-base": { "version": "1.0.3", "resolved": "https://registry.npmmirror.com/cose-base/-/cose-base-1.0.3.tgz", @@ -2192,6 +2418,24 @@ "version": "1.11.19", "license": "MIT" }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/delaunator": { "version": "5.0.1", "resolved": "https://registry.npmmirror.com/delaunator/-/delaunator-5.0.1.tgz", @@ -2262,10 +2506,57 @@ "@esbuild/win32-x64": "0.27.2" } }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/estree-walker": { "version": "2.0.2", "license": "MIT" }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", @@ -2284,6 +2575,19 @@ } } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", @@ -2299,6 +2603,19 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/hachure-fill": { "version": "0.5.2", "resolved": "https://registry.npmmirror.com/hachure-fill/-/hachure-fill-0.5.2.tgz", @@ -2314,6 +2631,12 @@ "node": ">=12.0.0" } }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -2339,6 +2662,71 @@ "version": "0.3.4", "license": "MIT" }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/katex": { "version": "0.16.28", "resolved": "https://registry.npmmirror.com/katex/-/katex-0.16.28.tgz", @@ -2391,6 +2779,23 @@ "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", "license": "MIT" }, + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/lodash-es": { "version": "4.17.23", "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.23.tgz", @@ -2416,6 +2821,16 @@ "node": ">= 20" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/mermaid": { "version": "11.12.2", "resolved": "https://registry.npmmirror.com/mermaid/-/mermaid-11.12.2.tgz", @@ -2456,6 +2871,55 @@ "node": ">= 20" } }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, "node_modules/mlly": { "version": "1.8.0", "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.0.tgz", @@ -2468,6 +2932,13 @@ "ufo": "^1.6.1" } }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.11", "funding": [ @@ -2484,6 +2955,16 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/number-precision": { "version": "1.6.0", "license": "MIT" @@ -2506,6 +2987,12 @@ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "license": "MIT" }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "license": "ISC" @@ -2516,7 +3003,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2524,6 +3010,27 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pinia": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz", + "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.7" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.5.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/pkg-types": { "version": "1.3.1", "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", @@ -2577,10 +3084,91 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/resize-observer-polyfill": { "version": "1.5.1", "license": "MIT" }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, "node_modules/robust-predicates": { "version": "3.0.2", "resolved": "https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.2.tgz", @@ -2591,6 +3179,7 @@ "version": "4.54.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -2639,6 +3228,30 @@ "points-on-path": "^0.2.1" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz", @@ -2658,6 +3271,13 @@ "compute-scroll-into-view": "^1.0.20" } }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "dev": true, + "license": "MIT" + }, "node_modules/simple-swizzle": { "version": "0.2.4", "license": "MIT", @@ -2672,6 +3292,28 @@ "node": ">=0.10.0" } }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/style-mod": { "version": "4.1.3", "resolved": "https://registry.npmmirror.com/style-mod/-/style-mod-4.1.3.tgz", @@ -2684,6 +3326,18 @@ "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", "license": "MIT" }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/tinyexec": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.0.2.tgz", @@ -2710,6 +3364,19 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/ts-dedent": { "version": "2.2.0", "resolved": "https://registry.npmmirror.com/ts-dedent/-/ts-dedent-2.2.0.tgz", @@ -2725,6 +3392,163 @@ "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", "license": "MIT" }, + "node_modules/unimport": { + "version": "3.14.6", + "resolved": "https://registry.npmmirror.com/unimport/-/unimport-3.14.6.tgz", + "integrity": "sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.4", + "acorn": "^8.14.0", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "fast-glob": "^3.3.3", + "local-pkg": "^1.0.0", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "pathe": "^2.0.1", + "picomatch": "^4.0.2", + "pkg-types": "^1.3.0", + "scule": "^1.3.0", + "strip-literal": "^2.1.1", + "unplugin": "^1.16.1" + } + }, + "node_modules/unimport/node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unimport/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/unimport/node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unimport/node_modules/local-pkg/node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/unplugin": { + "version": "1.16.1", + "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-1.16.1.tgz", + "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/unplugin-auto-import": { + "version": "0.18.6", + "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-0.18.6.tgz", + "integrity": "sha512-LMFzX5DtkTj/3wZuyG5bgKBoJ7WSgzqSGJ8ppDRdlvPh45mx6t6w3OcbExQi53n3xF5MYkNGPNR/HYOL95KL2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.3", + "fast-glob": "^3.3.2", + "local-pkg": "^0.5.1", + "magic-string": "^0.30.14", + "minimatch": "^9.0.5", + "unimport": "^3.13.4", + "unplugin": "^1.16.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@nuxt/kit": "^3.2.2", + "@vueuse/core": "*" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@vueuse/core": { + "optional": true + } + } + }, + "node_modules/unplugin-vue-components": { + "version": "0.27.5", + "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-0.27.5.tgz", + "integrity": "sha512-m9j4goBeNwXyNN8oZHHxvIIYiG8FQ9UfmKWeNllpDvhU7btKNNELGPt+o3mckQKuPwrE7e0PvCsx+IWuDSD9Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.3", + "chokidar": "^3.6.0", + "debug": "^4.3.7", + "fast-glob": "^3.3.2", + "local-pkg": "^0.5.1", + "magic-string": "^0.30.14", + "minimatch": "^9.0.5", + "mlly": "^1.7.3", + "unplugin": "^1.16.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, "node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmmirror.com/uuid/-/uuid-11.1.0.tgz", @@ -2888,6 +3712,13 @@ "resolved": "https://registry.npmmirror.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", "license": "MIT" + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" } } } diff --git a/web/package.json b/web/package.json index 881b550..820b9d1 100644 --- a/web/package.json +++ b/web/package.json @@ -34,10 +34,13 @@ "highlight.js": "^11.11.1", "marked": "^17.0.1", "mermaid": "^11.12.2", + "pinia": "^3.0.4", "vue": "^3.5.26" }, "devDependencies": { "@vitejs/plugin-vue": "^6.0.3", + "unplugin-vue-components": "^0.27.4", + "unplugin-auto-import": "^0.18.3", "vite": "^7.3.0" } } diff --git a/web/src/App.vue b/web/src/App.vue index 5ca6eca..1c676ec 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -58,29 +58,30 @@ diff --git a/web/src/components/DeviceTest.vue b/web/src/components/DeviceTest.vue index 9df4dfb..253b410 100644 --- a/web/src/components/DeviceTest.vue +++ b/web/src/components/DeviceTest.vue @@ -235,7 +235,7 @@ const { deleteFile, } = useFileOperations({ onSuccess: (operation, data) => { - console.log(`[DeviceTest] ${operation} 成功:`, data) + // 成功回调 }, onError: (operation, error) => { console.error(`[DeviceTest] ${operation} 失败:`, error) @@ -271,7 +271,6 @@ const { storedValue: pathHistory } = useLocalStorage( try { const oldContent = localStorage.getItem(STORAGE_KEYS.DEVICE_TEST.FILE_CONTENT) if (oldContent) { - console.log('[DeviceTest] 清理旧的文件内容缓存') localStorage.removeItem(STORAGE_KEYS.DEVICE_TEST.FILE_CONTENT) } } catch (error) { diff --git a/web/src/components/FileSystem/components/DropdownItem.vue b/web/src/components/FileSystem/components/DropdownItem.vue new file mode 100644 index 0000000..be96892 --- /dev/null +++ b/web/src/components/FileSystem/components/DropdownItem.vue @@ -0,0 +1,272 @@ + + + + + diff --git a/web/src/components/FileSystem/components/FileEditorPanel.vue b/web/src/components/FileSystem/components/FileEditorPanel.vue index f1f5bc0..187000c 100644 --- a/web/src/components/FileSystem/components/FileEditorPanel.vue +++ b/web/src/components/FileSystem/components/FileEditorPanel.vue @@ -129,7 +129,7 @@
-
-
- + + diff --git a/web/src/components/FileSystem/components/Sidebar.vue b/web/src/components/FileSystem/components/Sidebar.vue index 1ac27f1..d74e070 100644 --- a/web/src/components/FileSystem/components/Sidebar.vue +++ b/web/src/components/FileSystem/components/Sidebar.vue @@ -27,7 +27,7 @@ @drop="handleDrop($event, index)" @dragend="handleDragEnd" > - {{ fav.is_dir ? '📁' : '📄' }} + {{ fav.isDir ? '📁' : '📄' }} {{ fav.name }}
- - - - + +
+ + +
+ +
+
+
@@ -119,6 +113,7 @@ import { computed } from 'vue' import { IconForward, IconHistory, IconRefresh, IconMenu, IconClose, IconRight, IconCopy } from '@arco-design/web-vue/es/icon' import type { ToolbarConfig } from '@/types/file-system' +import PathBreadcrumb from './PathBreadcrumb.vue' // Props interface Props { @@ -134,21 +129,13 @@ interface Emits { (e: 'refresh'): void (e: 'exitZip'): void (e: 'goToPath', path: string): void + (e: 'openFile', path: string): void (e: 'navigateToZipDirectory', path: string): void (e: 'showMessage', message: string, type: 'success' | 'error' | 'warning' | 'info'): void } const emit = defineEmits() -// 将反斜杠转换为正斜杠显示 -const normalizedPath = computed(() => { - return props.config.filePath?.replace(/\\/g, '/') || '' -}) - -const normalizedPathHistory = computed(() => { - return props.config.pathHistory.map(path => path.replace(/\\/g, '/')) -}) - // 事件处理 const handlePathUpdate = (path: string) => { emit('update:filePath', path) @@ -162,6 +149,10 @@ const handleGoToPath = (path: string) => { emit('goToPath', path) } +const handleOpenFile = (path: string) => { + emit('openFile', path) +} + const handleRefresh = () => { emit('refresh') } @@ -235,22 +226,34 @@ const handleCopyPath = async () => { width: 100%; } -/* 覆盖 Arco 输入框 append 的默认 padding */ -.path-input-wrapper :deep(.arco-input-append) { - padding: 0 !important; +.path-breadcrumb-wrapper { + display: flex; + align-items: center; + flex: 1; + min-width: 200px; + gap: 8px; + padding: 4px 8px; + background: var(--color-fill-1); + border-radius: 4px; + border: 1px solid var(--color-border); + transition: border-color 0.2s; +} + +.path-breadcrumb-wrapper:hover { + border-color: var(--color-border-2); } .copy-icon-wrapper { display: flex; align-items: center; justify-content: center; - padding: 0 8px; - width: 100%; - height: 100%; + padding: 4px 8px; cursor: pointer; color: var(--color-text-3); font-size: 14px; transition: all 0.2s; + border-radius: 4px; + flex-shrink: 0; } .copy-icon-wrapper:hover { diff --git a/web/src/components/FileSystem/composables/useFavorites.ts b/web/src/components/FileSystem/composables/useFavorites.ts index 4a6d85b..53721b8 100644 --- a/web/src/components/FileSystem/composables/useFavorites.ts +++ b/web/src/components/FileSystem/composables/useFavorites.ts @@ -25,7 +25,13 @@ export function useFavorites() { try { const stored = localStorage.getItem(STORAGE_KEYS.FAVORITE_FILES) if (stored) { - favorites.value = JSON.parse(stored) + const loaded = JSON.parse(stored) as FavoriteFile[] + + // 数据迁移:将旧字段 is_dir 转换为 isDir + favorites.value = loaded.map(fav => ({ + ...fav, + isDir: fav.isDir ?? (fav as any).is_dir ?? false + })) } } catch (error) { console.error('加载收藏列表失败:', error) @@ -62,10 +68,10 @@ export function useFavorites() { } /** - * 标准化路径用于比较(处理正斜杠/反斜杠不一致) + * 标准化路径用于比较(后端已统一为 /,直接转小写) */ const normalizePath = (path: string): string => { - return path.replace(/\\/g, '/').toLowerCase() + return path.toLowerCase() } /** diff --git a/web/src/components/FileSystem/composables/usePathNavigation.ts b/web/src/components/FileSystem/composables/usePathNavigation.ts index ee14aee..5f64341 100644 --- a/web/src/components/FileSystem/composables/usePathNavigation.ts +++ b/web/src/components/FileSystem/composables/usePathNavigation.ts @@ -5,6 +5,7 @@ import { ref, watch, computed } from 'vue' import { STORAGE_KEYS } from '@/utils/constants' +import { normalizePathSeparators } from '@/utils/pathHelpers' import type { PathHistory } from '@/types/file-system' export interface UsePathNavigationOptions { @@ -18,6 +19,10 @@ export interface UsePathNavigationOptions { const restoreLastPath = (): string | null => { try { const lastPath = localStorage.getItem(STORAGE_KEYS.FILESYSTEM.FILE_PATH) + if (lastPath) { + // 规范化旧路径(可能包含反斜杠) + return normalizePathSeparators(lastPath) + } return lastPath } catch (error) { console.error('恢复路径失败:', error) @@ -56,8 +61,8 @@ export function usePathNavigation(options: UsePathNavigationOptions = {}) { if (!path || path === filePath.value) return try { - // 路径规范化 - const normalizedPath = normalizePath(path) + // 路径规范化(处理反斜杠并统一为正斜杠) + const normalizedPath = normalizePathSeparators(path) filePath.value = normalizedPath // 添加到历史记录 @@ -177,11 +182,10 @@ export function usePathNavigation(options: UsePathNavigationOptions = {}) { } /** - * 路径规范化(统一分隔符) + * 路径规范化(统一为正斜杠) */ const normalizePath = (path: string): string => { - if (!path) return '' - return path.replace(/\\/g, '/') + return normalizePathSeparators(path) } /** diff --git a/web/src/components/FileSystem/index-simple.vue b/web/src/components/FileSystem/index-simple.vue deleted file mode 100644 index 059992b..0000000 --- a/web/src/components/FileSystem/index-simple.vue +++ /dev/null @@ -1,200 +0,0 @@ - - - - - diff --git a/web/src/components/FileSystem/index.vue b/web/src/components/FileSystem/index.vue index 88a91e9..b93d425 100644 --- a/web/src/components/FileSystem/index.vue +++ b/web/src/components/FileSystem/index.vue @@ -8,6 +8,7 @@ @refresh="handleRefresh" @exit-zip="handleExitZip" @go-to-path="handleGoToPath" + @open-file="handleOpenFile" @navigate-to-zip-directory="handleNavigateToZipDirectory" @show-message="handleShowMessage" /> @@ -118,7 +119,9 @@ import { useCommonPaths } from './composables/useCommonPaths' // 导入工具函数 import { getFileName, sortFileList } from '@/utils/fileUtils' +import { getParentPath } from '@/utils/pathHelpers' import { isImageFile, isVideoFile, isAudioFile, isPdfFile, isHtmlFile, isMarkdownFile } from '@/utils/fileTypeHelpers' +import { listDir } from '@/api/system' import { STORAGE_KEYS, DEFAULTS, UI_TEXT, VALIDATION_RULES, FILE_EXTENSIONS } from '@/utils/constants' // 导入类型 @@ -345,6 +348,35 @@ const handleGoToPath = async (path: string) => { await navigate(path) } +const handleOpenFile = async (path: string) => { + // 检查是文件还是目录 + try { + const parentPath = getParentPath(path) + const files = await listDir(parentPath) + + // 后端已统一返回 / 路径,直接比较 + const targetFile = files.find(f => f.path === path) + + if (targetFile) { + if (targetFile.isDir) { + // 是目录,导航进入 + await navigate(path) + } else { + // 是文件,选中并加载 + selectedFileItem.value = targetFile + await loadFileContent(path) + } + } else { + // 未找到,尝试直接导航(可能是目录) + await navigate(path) + } + } catch (error) { + console.error('打开文件失败:', error) + // 如果出错,尝试直接导航 + await navigate(path) + } +} + const handleNavigateToZipDirectory = async (path: string) => { // 暂时不处理 ZIP } @@ -359,7 +391,7 @@ const handleShowMessage = (message: string, type: 'success' | 'error' | 'warning // 侧边栏事件 const handleOpenFavorite = async (file: FavoriteFile) => { - if (file.is_dir) { + if (file.isDir) { await navigate(file.path) } else { await selectFile(file.path) @@ -416,7 +448,7 @@ const handleFileClick = async (file: FileItem) => { */ // 正常文件系统浏览 - if (file.is_dir) { + if (file.isDir) { // 目录:使用 navigate 函数,确保历史记录正确更新 await navigate(file.path) } else { @@ -427,7 +459,7 @@ const handleFileClick = async (file: FileItem) => { } const handleFileDoubleClick = async (file: FileItem) => { - if (file.is_dir) { + if (file.isDir) { await navigate(file.path) } else { // 检查是否为 ZIP 文件 - 暂时禁用 @@ -535,7 +567,7 @@ const handleSaveEditing = async (oldPath: string, newName: string) => { // 如果重命名的是当前打开的文件,先关闭编辑器和预览 if (selectedFileItem.value?.path === oldPath) { // 如果是文件(不是文件夹),才需要关闭编辑器 - if (!selectedFileItem.value.is_dir) { + if (!selectedFileItem.value.isDir) { // 清空编辑器内容 await clearContent() @@ -580,7 +612,7 @@ const handleSaveEditing = async (oldPath: string, newName: string) => { errorMsg.includes('being used by another process') || errorMsg.includes('被另一个进程占用')) { errorMsg = '文件正在被其他程序使用,请先关闭该文件后重试' - if (selectedFileItem.value?.is_dir) { + if (selectedFileItem.value?.isDir) { errorMsg = '文件夹正在被其他程序使用(如文件管理器、终端等),请先关闭后重试' } } else if (errorMsg.includes('access is denied') || @@ -728,7 +760,7 @@ const handleCreateFile = async () => { } // 检查是否已存在同名文件 - const existingFile = fileList.value.find(f => f.name === fileName && !f.is_dir) + const existingFile = fileList.value.find(f => f.name === fileName && !f.isDir) if (existingFile) { Message.error(`文件 "${fileName}" 已存在`) // 重新显示对话框 @@ -784,7 +816,7 @@ const handleCreateDir = async () => { } // 检查是否已存在同名文件夹 - const existingFolder = fileList.value.find(f => f.name === folderName && f.is_dir) + const existingFolder = fileList.value.find(f => f.name === folderName && f.isDir) if (existingFolder) { Message.error(`文件夹 "${folderName}" 已存在`) // 重新显示对话框 @@ -822,7 +854,7 @@ const validateFileName = (name: string): boolean => { */ const handleDeleteFile = async (file: FileItem) => { const targetPath = file.path - const isDirectory = file.is_dir + const isDirectory = file.isDir const fileName = file.name || targetPath // 根据类型显示不同的确认信息 @@ -919,12 +951,12 @@ const isMediaPreviewable = (filename: string): boolean => { } const selectFile = async (path: string) => { - // 标准化路径进行比较(处理正斜杠/反斜杠不一致的问题) - const normalizedPath = path.replace(/\\/g, '/').toLowerCase() + // 后端已统一返回 / 路径,直接比较 + const normalizedPath = path.toLowerCase() // 尝试在当前文件列表中查找 const file = fileList.value.find(f => { - const normalizedFilePath = f.path.replace(/\\/g, '/').toLowerCase() + const normalizedFilePath = f.path.toLowerCase() return normalizedFilePath === normalizedPath }) @@ -938,7 +970,7 @@ const selectFile = async (path: string) => { selectedFileItem.value = { path, name: fileName, - is_dir: false, + isDir: false, size: 0, mod_time: '', is_favorite: isFavorite(path) @@ -1055,7 +1087,7 @@ const loadZipDirectoryContents = async (zipPath: string, currentDir: string): Pr const result = filtered.map((f: any) => ({ name: f.name, path: f.path, - is_dir: f.is_dir, + isDir: f.isDir, size: f.size || 0, mod_time: f.mod_time || '', is_favorite: false diff --git a/web/src/components/ThemeToggle.vue b/web/src/components/ThemeToggle.vue index 1933030..02a4734 100644 --- a/web/src/components/ThemeToggle.vue +++ b/web/src/components/ThemeToggle.vue @@ -1,26 +1,21 @@ diff --git a/web/src/components/UpdateNotification.vue b/web/src/components/UpdateNotification.vue index 898cf54..516b5de 100644 --- a/web/src/components/UpdateNotification.vue +++ b/web/src/components/UpdateNotification.vue @@ -3,8 +3,9 @@ diff --git a/web/src/composables/useTheme.ts b/web/src/composables/useTheme.ts deleted file mode 100644 index 2c41339..0000000 --- a/web/src/composables/useTheme.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { ref, computed } from 'vue' - -type Theme = 'light' | 'dark' - -const THEME_STORAGE_KEY = 'app-theme' - -// 单例模式:全局共享主题状态 -const theme = ref('light') -let systemThemeListener: (() => void) | null = null - -// 应用主题到 DOM -const applyTheme = (newTheme: Theme) => { - theme.value = newTheme - if (newTheme === 'dark') { - document.body.setAttribute('arco-theme', 'dark') - } else { - document.body.removeAttribute('arco-theme') - } - localStorage.setItem(THEME_STORAGE_KEY, newTheme) -} - -// 初始化主题(只调用一次) -const initTheme = () => { - const savedTheme = localStorage.getItem(THEME_STORAGE_KEY) as Theme - if (savedTheme && (savedTheme === 'light' || savedTheme === 'dark')) { - applyTheme(savedTheme) - } else { - // 检测系统偏好 - if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { - applyTheme('dark') - } else { - applyTheme('light') - } - } - - // 监听系统主题变化 - if (window.matchMedia) { - const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)') - const handleChange = (e: MediaQueryListEvent) => { - // 如果用户没有手动设置过主题,则跟随系统 - if (!localStorage.getItem(THEME_STORAGE_KEY)) { - applyTheme(e.matches ? 'dark' : 'light') - } - } - mediaQuery.addEventListener('change', handleChange) - systemThemeListener = () => mediaQuery.removeEventListener('change', handleChange) - } -} - -export function useTheme() { - // 切换主题 - const toggleTheme = () => { - const newTheme: Theme = theme.value === 'light' ? 'dark' : 'light' - applyTheme(newTheme) - } - - // 设置为亮色主题 - const setLightTheme = () => { - applyTheme('light') - } - - // 设置为暗色主题 - const setDarkTheme = () => { - applyTheme('dark') - } - - return { - theme: computed(() => theme.value), - isDark: computed(() => theme.value === 'dark'), - toggleTheme, - setLightTheme, - setDarkTheme, - initTheme - } -} - -// 导出初始化函数(在 main.js 中使用) -export { initTheme } diff --git a/web/src/composables/useTimeout.ts b/web/src/composables/useTimeout.ts new file mode 100644 index 0000000..6ef902a --- /dev/null +++ b/web/src/composables/useTimeout.ts @@ -0,0 +1,117 @@ +/** + * 定时器管理 Hook + * 自动管理定时器生命周期,防止内存泄漏 + * + * @module composables/useTimeout + * @description 提供类型安全的定时器管理,组件卸载时自动清理所有定时器 + */ + +import { ref, onUnmounted } from 'vue' + +export interface TimeoutOptions { + /** + * 是否在组件卸载时自动清理所有定时器 + * @default true + */ + autoCleanup?: boolean +} + +/** + * 定时器管理 Hook + * + * @param options - 配置选项 + * @returns 定时器管理方法 + * + * @example + * ```typescript + * const { setTimeout, clearTimeout, clearAll } = useTimeout() + * + * // 设置延迟执行 + * const timer = setTimeout(() => { + * console.log('延迟执行') + * }, 1000) + * + * // 清除特定定时器 + * clearTimeout(timer) + * + * // 清除所有定时器 + * clearAll() + * ``` + */ +export function useTimeout(options: TimeoutOptions = {}) { + const { autoCleanup = true } = options + + // 使用 Set 存储所有定时器 ID + const timers = ref>(new Set()) + + /** + * 设置定时器(自动管理生命周期) + * @param callback - 要执行的回调函数 + * @param delay - 延迟时间(毫秒) + * @returns 定时器 ID + */ + const setTimeout = ( + callback: () => T, + delay: number + ): NodeJS.Timeout => { + const timer = window.setTimeout(() => { + try { + callback() + } finally { + // 执行完成后自动从集合中移除 + timers.value.delete(timer) + } + }, delay) + + // 添加到集合中 + timers.value.add(timer) + + return timer + } + + /** + * 清除特定定时器 + * @param timer - 要清除的定时器 ID + */ + const clearTimeout = (timer: NodeJS.Timeout) => { + window.clearTimeout(timer) + timers.value.delete(timer) + } + + /** + * 清除所有定时器 + */ + const clearAll = () => { + timers.value.forEach((timer) => { + window.clearTimeout(timer) + }) + timers.value.clear() + } + + /** + * 获取当前活跃的定时器数量 + */ + const getActiveCount = () => timers.value.size + + // 组件卸载时自动清理 + if (autoCleanup) { + onUnmounted(() => { + clearAll() + }) + } + + return { + setTimeout, + clearTimeout, + clearAll, + getActiveCount + } +} + +/** + * 定时器管理 Hook 的别名 + * 便于语义化使用(如延迟执行、防抖等场景) + */ +export const useDelay = useTimeout + +export default useTimeout diff --git a/web/src/main.js b/web/src/main.js index 6e8e8df..c914ea6 100644 --- a/web/src/main.js +++ b/web/src/main.js @@ -1,15 +1,19 @@ -import {createApp} from 'vue' -import ArcoVue from '@arco-design/web-vue' +import { createApp } from 'vue' +import { createPinia } from 'pinia' +// Arco Design 样式(组件按需自动引入) import '@arco-design/web-vue/dist/arco.css' import './style.css' import App from './App.vue' -import {initTheme} from './composables/useTheme' +import { useThemeStore } from './stores/theme' const app = createApp(App) -app.use(ArcoVue) +const pinia = createPinia() -// 在应用挂载前初始化主题 -initTheme() +app.use(pinia) + +// 在应用挂载前初始化主题(需要先初始化 Pinia) +const themeStore = useThemeStore() +themeStore.initTheme() app.mount('#app') diff --git a/web/src/stores/config.ts b/web/src/stores/config.ts new file mode 100644 index 0000000..5ce8613 --- /dev/null +++ b/web/src/stores/config.ts @@ -0,0 +1,187 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import { Message } from '@arco-design/web-vue' + +/** + * Tab 配置类型 + */ +interface TabConfig { + key: string + title: string + visible: boolean + enabled: boolean +} + +/** + * 应用配置类型 + */ +interface AppConfig { + tabs: TabConfig[] + visibleTabs: string[] + defaultTab: string +} + +/** + * 应用配置管理 Store + * 统一管理应用配置(标签页、默认页等) + */ +export const useConfigStore = defineStore('config', () => { + // ==================== 状态 ==================== + const appConfig = ref({ + tabs: [], + visibleTabs: [], + defaultTab: 'file-system' + }) + + const loading = ref(false) + + // ==================== 计算属性 ==================== + /** + * 可见 Tabs(根据配置动态生成) + */ + const visibleTabs = computed(() => { + const tabs = appConfig.value.tabs + + if (!tabs?.length) { + return [ + { key: 'file-system', title: '文件管理' }, + { key: 'db-cli', title: '数据库' } + ] + } + + const { visibleTabs: order } = appConfig.value + return tabs + .filter(tab => tab.visible) + .sort((a, b) => order.indexOf(a.key) - order.indexOf(b.key)) + }) + + /** + * 所有可用 Tabs + */ + const allTabs = computed(() => appConfig.value.tabs) + + /** + * 默认 Tab + */ + const defaultTab = computed(() => appConfig.value.defaultTab) + + // ==================== 核心方法 ==================== + /** + * 加载配置 + */ + const loadConfig = async () => { + if (!window.go?.main?.App) { + console.warn('Wails 绑定未准备好,1秒后重试') + setTimeout(loadConfig, 1000) + return + } + + loading.value = true + + try { + const result = await window.go.main.App.GetAppConfig() + if (!result.success) throw new Error(result.message) + + const { tabs = [], visibleTabs = [], defaultTab = 'file-system' } = result.data + + appConfig.value = { + tabs: tabs.map(tab => ({ ...tab, visible: visibleTabs.includes(tab.key) })), + visibleTabs, + defaultTab + } + } catch (error) { + console.error('加载配置失败:', error) + useDefaultConfig() + } finally { + loading.value = false + } + } + + /** + * 使用默认配置 + */ + const useDefaultConfig = () => { + appConfig.value = { + tabs: [ + { key: 'file-system', title: '文件管理', visible: true, enabled: true }, + { key: 'db-cli', title: '数据库', visible: true, enabled: true } + ], + visibleTabs: ['file-system', 'db-cli'], + defaultTab: 'file-system' + } + } + + /** + * 保存配置 + */ + const saveConfig = async (config: AppConfig) => { + if (!window.go?.main?.App) { + Message.error('Wails 绑定未准备好') + throw new Error('Wails binding not ready') + } + + loading.value = true + + try { + const result = await window.go.main.App.SaveAppConfig({ + tabs: config.tabs, + visibleTabs: config.visibleTabs, + defaultTab: config.defaultTab + }) + + if (!result.success) { + Message.error(result.message || '保存配置失败') + throw new Error(result.message) + } + + // 更新本地配置 + appConfig.value = { + tabs: [...config.tabs], + visibleTabs: [...config.visibleTabs], + defaultTab: config.defaultTab + } + + Message.success('配置保存成功') + return true + } catch (error) { + console.error('保存配置失败:', error) + const message = error instanceof Error ? error.message : '保存配置失败' + Message.error('保存配置失败:' + message) + throw error + } finally { + loading.value = false + } + } + + /** + * 检查 Tab 是否可见 + */ + const isTabVisible = (tabKey: string) => { + return appConfig.value.visibleTabs.includes(tabKey) + } + + /** + * 获取 Tab 配置 + */ + const getTab = (tabKey: string) => { + return appConfig.value.tabs.find(tab => tab.key === tabKey) + } + + // ==================== 返回 ==================== + return { + // 状态 + appConfig, + loading, + + // 计算属性 + visibleTabs, + allTabs, + defaultTab, + + // 方法 + loadConfig, + saveConfig, + isTabVisible, + getTab + } +}) diff --git a/web/src/stores/theme.ts b/web/src/stores/theme.ts new file mode 100644 index 0000000..f11b9d2 --- /dev/null +++ b/web/src/stores/theme.ts @@ -0,0 +1,117 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' + +type Theme = 'light' | 'dark' + +const THEME_STORAGE_KEY = 'app-theme' + +/** + * 主题管理 Store + * 统一管理应用主题(亮色/暗色)及相关逻辑 + */ +export const useThemeStore = defineStore('theme', () => { + // ==================== 状态 ==================== + const theme = ref('light') + let systemThemeListener: (() => void) | null = null + + // ==================== 计算属性 ==================== + const isDark = computed(() => theme.value === 'dark') + const isLight = computed(() => theme.value === 'light') + const tooltipText = computed(() => + isDark.value ? '切换到亮色主题' : '切换到夜间主题' + ) + + // ==================== 核心方法 ==================== + /** + * 应用主题到 DOM + */ + const applyTheme = (newTheme: Theme) => { + theme.value = newTheme + + // 更新 DOM 属性 + const method = newTheme === 'dark' ? 'setAttribute' : 'removeAttribute' + document.body[method]('arco-theme', 'dark') + + // 持久化 + localStorage.setItem(THEME_STORAGE_KEY, newTheme) + } + + /** + * 切换主题 + */ + const toggleTheme = () => { + const newTheme: Theme = theme.value === 'light' ? 'dark' : 'light' + applyTheme(newTheme) + } + + /** + * 设置为亮色主题 + */ + const setLightTheme = () => { + applyTheme('light') + } + + /** + * 设置为暗色主题 + */ + const setDarkTheme = () => { + applyTheme('dark') + } + + /** + * 初始化主题(应用启动时调用) + */ + const initTheme = () => { + // 加载保存的主题或使用系统偏好 + const savedTheme = localStorage.getItem(THEME_STORAGE_KEY) as Theme + const isValidTheme = savedTheme === 'light' || savedTheme === 'dark' + + if (isValidTheme) { + applyTheme(savedTheme) + } else { + const prefersDark = window.matchMedia?.('(prefers-color-scheme: dark)').matches + applyTheme(prefersDark ? 'dark' : 'light') + } + + // 监听系统主题变化(仅在未手动设置时) + if (!window.matchMedia) return + + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)') + const handleChange = (e: MediaQueryListEvent) => { + if (!localStorage.getItem(THEME_STORAGE_KEY)) { + applyTheme(e.matches ? 'dark' : 'light') + } + } + + mediaQuery.addEventListener('change', handleChange) + systemThemeListener = () => mediaQuery.removeEventListener('change', handleChange) + } + + /** + * 清理系统主题监听器 + */ + const removeSystemThemeListener = () => { + if (systemThemeListener) { + systemThemeListener() + systemThemeListener = null + } + } + + // ==================== 返回 ==================== + return { + // 状态 + theme, + + // 计算属性 + isDark, + isLight, + tooltipText, + + // 方法 + toggleTheme, + setLightTheme, + setDarkTheme, + initTheme, + removeSystemThemeListener + } +}) diff --git a/web/src/stores/update.ts b/web/src/stores/update.ts new file mode 100644 index 0000000..abcd006 --- /dev/null +++ b/web/src/stores/update.ts @@ -0,0 +1,288 @@ +import { defineStore } from 'pinia' +import { ref, reactive } from 'vue' +import { Message } from '@arco-design/web-vue' + +/** + * 更新管理 Store + * 统一管理版本检查、下载、安装等更新相关逻辑 + */ +export const useUpdateStore = defineStore('update', () => { + // ==================== 状态 ==================== + const updateInfo = ref(null) + const showUpdate = ref(false) + const checking = ref(false) + const downloading = ref(false) + const installing = ref(false) + const downloadProgress = ref(0) + const downloadStatus = ref<'active' | 'exception' | 'success'>('active') + const progressInfo = ref({ + speed: 0, + downloaded: 0, + total: 0 + }) + + // 节流:防止过度更新 + let lastUpdateTime = 0 + const UPDATE_THROTTLE = 100 // 100ms 最小更新间隔 + + // 最小显示时间:确保进度条至少显示 5 秒 + let downloadStartTime = 0 + const MIN_DISPLAY_TIME = 5000 // 5 秒最小显示时间 + + // ==================== 工具函数 ==================== + const parseEventData = (event: unknown) => { + try { + return typeof event === 'string' ? JSON.parse(event) : (event as Record) + } catch { + return {} + } + } + + const formatFileSize = (bytes: number): string => { + if (!bytes || bytes < 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i] + } + + const formatSpeed = (bytesPerSecond: number): string => { + return formatFileSize(bytesPerSecond) + '/s' + } + + // ==================== 核心方法 ==================== + /** + * 检查更新 + * @param silent 是否静默模式(不显示消息) + */ + const checkForUpdates = async (silent = false) => { + if (checking.value || !window.go?.main?.App) return + + checking.value = true + + try { + const configResult = await window.go.main.App.GetUpdateConfig() + if (!configResult.success) return + + const { auto_check_enabled } = configResult.data || {} + if (!auto_check_enabled) return + + const result = await window.go.main.App.CheckUpdate() + if (result.success && result.data?.has_update) { + updateInfo.value = result.data + showUpdate.value = true + + if (!silent) { + Message.success('发现新版本!') + } + } else if (!silent) { + Message.success('已是最新版本') + } + } catch (error) { + if (!silent) { + console.error('检查更新失败:', error) + Message.error('检查更新失败:' + (error as Error).message) + } + } finally { + checking.value = false + } + } + + /** + * 下载更新 + */ + const downloadUpdate = async () => { + const url = updateInfo.value?.download_url + if (!url) { + Message.warning('下载地址不存在') + return + } + + // 记录开始时间 + downloadStartTime = Date.now() + + // 重置下载状态 + downloading.value = true + downloadProgress.value = 1 // 设置为 1 而不是 0,确保进度条显示 + downloadStatus.value = 'active' + progressInfo.value = { speed: 0, downloaded: 0, total: 0 } + + try { + const result = await window.go.main.App.DownloadUpdate(url) + if (!result.success) { + downloadStatus.value = 'exception' + downloading.value = false + Message.error(result.message || '下载启动失败') + return + } + Message.success('下载请求已发送,等待后端发送进度事件...') + } catch (error) { + console.error('下载失败:', error) + downloadStatus.value = 'exception' + downloading.value = false + Message.error('下载失败:' + (error as Error).message) + } + } + + /** + * 安装更新 + */ + const installUpdate = async (filePath: string) => { + if (!filePath) { + Message.warning('请先下载更新包') + return + } + + installing.value = true + + try { + const result = await window.go.main.App.InstallUpdate(filePath, true) + if (result.success) { + Message.success({ + content: '安装成功!应用将在几秒后重启...', + duration: 3000 + }) + return + } + Message.error(result.message || '安装失败') + } catch (error) { + Message.error('安装失败:' + (error as Error).message) + } finally { + installing.value = false + } + } + + /** + * 下载进度处理 + */ + const onDownloadProgress = (event: unknown) => { + const now = Date.now() + if (now - lastUpdateTime < UPDATE_THROTTLE) { + return + } + + lastUpdateTime = now + const data = parseEventData(event) + + progressInfo.value = { + speed: (data.speed as number) || 0, + downloaded: (data.downloaded as number) || 0, + total: (data.total as number) || 0 + } + + const rawProgress = Number(data.progress) || 0 + const safeProgress = Math.min(100, Math.max(0, Math.round(rawProgress))) + downloadProgress.value = safeProgress + } + + /** + * 下载完成处理 + */ + const onDownloadComplete = (event: unknown) => { + const data = parseEventData(event) + + // 错误处理 + if (data.error) { + console.error('下载失败:', data.error) + downloadStatus.value = 'exception' + downloading.value = false + Message.error('下载失败:' + data.error) + return + } + + // 数据验证 + if (!data.success || !data.file_path) { + console.error('下载数据不完整:', data) + downloadStatus.value = 'exception' + downloading.value = false + Message.error('下载完成但数据不完整') + return + } + + // 完成下载 + downloadProgress.value = 100 + downloadStatus.value = 'success' + const fileSize = (data.file_size as number) || 0 + progressInfo.value = { + speed: 0, + downloaded: fileSize, + total: fileSize + } + + // 计算已经显示的时间 + const elapsed = Date.now() - downloadStartTime + const remainingTime = Math.max(0, MIN_DISPLAY_TIME - elapsed) + + // 确保进度条至少显示 3 秒 + setTimeout(() => { + downloading.value = false // 安装前才关闭下载状态 + installUpdate(data.file_path as string) + }, remainingTime) + } + + /** + * 设置事件监听 + */ + const setupEventListeners = () => { + if (!window.runtime?.EventsOn) { + return + } + + window.runtime.EventsOn('download-progress', onDownloadProgress) + window.runtime.EventsOn('download-complete', onDownloadComplete) + } + + /** + * 移除事件监听 + */ + const removeEventListeners = () => { + if (!window.runtime?.EventsOff) { + return + } + + window.runtime.EventsOff('download-progress') + window.runtime.EventsOff('download-complete') + } + + /** + * 关闭更新提示 + */ + const closeUpdateNotification = () => { + showUpdate.value = false + } + + // ==================== 返回 ==================== + return { + // 状态 + updateInfo, + showUpdate, + checking, + downloading, + installing, + downloadProgress, + downloadStatus, + progressInfo, + + // 方法 + checkForUpdates, + downloadUpdate, + installUpdate, + setupEventListeners, + removeEventListeners, + closeUpdateNotification, + formatFileSize, + formatSpeed + } +}) + +// ==================== 类型定义 ==================== +interface UpdateInfo { + has_update: boolean + current_version: string + latest_version: string + download_url: string + changelog: string + force_update: boolean + release_date: string + file_size: number +} diff --git a/web/src/style.css b/web/src/style.css index 69b384a..834672e 100644 --- a/web/src/style.css +++ b/web/src/style.css @@ -48,4 +48,39 @@ body { * { scrollbar-width: thin; scrollbar-color: var(--color-border-2, rgba(0, 0, 0, 0.1)) transparent; +} + +/* Markdown 标题锚点链接样式 */ +.heading { + position: relative; + scroll-margin-top: 20px; /* 锚点跳转时的顶部偏移 */ +} + +.heading-anchor { + opacity: 0; + margin-left: 8px; + color: rgb(var(--primary-6)); + text-decoration: none; + font-size: 0.8em; + font-weight: normal; + transition: opacity 0.2s ease; + cursor: pointer; +} + +.heading:hover .heading-anchor { + opacity: 1; +} + +.heading-anchor:hover { + text-decoration: none; +} + +.heading-anchor:focus { + opacity: 1; + outline: none; +} + +/* 平滑滚动 */ +html { + scroll-behavior: smooth; } \ No newline at end of file diff --git a/web/src/types/file-system.ts b/web/src/types/file-system.ts index e30efeb..c89b2e2 100644 --- a/web/src/types/file-system.ts +++ b/web/src/types/file-system.ts @@ -14,7 +14,7 @@ export interface FileItem { /** 文件大小(字节) */ size: number /** 是否为目录 */ - is_dir: boolean + isDir: boolean /** 修改时间 */ modified_time?: string /** 是否被收藏(运行时属性) */ diff --git a/web/src/utils/codeMirrorLoader.js b/web/src/utils/codeMirrorLoader.js new file mode 100644 index 0000000..94121f5 --- /dev/null +++ b/web/src/utils/codeMirrorLoader.js @@ -0,0 +1,138 @@ +/** + * CodeMirror 语言包动态加载器 + * 按需加载语言支持,减少初始包体积和构建时间 + */ + +const languageCache = new Map() + +/** + * 动态加载 CodeMirror 语言扩展 + * @param {string} language - 语言名称 + * @returns {Promise} CodeMirror 语言扩展 + */ +export async function loadLanguageExtension(language) { + if (languageCache.has(language)) { + return languageCache.get(language) + } + + try { + let extension + + // 现代语言包(直接返回扩展) + const modernLangs = { + javascript: ['@codemirror/lang-javascript', 'javascript', { jsx: true }], + typescript: ['@codemirror/lang-javascript', 'javascript', { jsx: true }], + json: ['@codemirror/lang-json', 'json'], + yaml: ['@codemirror/lang-yaml', 'yaml'], + html: ['@codemirror/lang-html', 'html'], + css: ['@codemirror/lang-css', 'css'], + cpp: ['@codemirror/lang-cpp', 'cpp'], + c: ['@codemirror/lang-cpp', 'cpp'], + rust: ['@codemirror/lang-rust', 'rust'], + go: ['@codemirror/lang-go', 'go'], + python: ['@codemirror/lang-python', 'python'], + php: ['@codemirror/lang-php', 'php'], + sql: ['@codemirror/lang-sql', 'sql'], + markdown: ['@codemirror/lang-markdown', 'markdown'], + java: ['@codemirror/lang-java', 'java'] + } + + if (modernLangs[language]) { + const [path, method, ...args] = modernLangs[language] + const mod = await import(path) + extension = mod[method](...args) + } else { + // Legacy 语言包(需要 StreamLanguage 包装) + const legacyLangs = { + ruby: ['@codemirror/legacy-modes/mode/ruby', 'ruby'], + shell: ['@codemirror/legacy-modes/mode/shell', 'shell'], + bash: ['@codemirror/legacy-modes/mode/shell', 'shell'], + kotlin: ['@codemirror/legacy-modes/mode/clike', 'kotlin'], + csharp: ['@codemirror/legacy-modes/mode/clike', 'csharp'], + swift: ['@codemirror/legacy-modes/mode/swift', 'swift'], + r: ['@codemirror/legacy-modes/mode/r', 'r'], + perl: ['@codemirror/legacy-modes/mode/perl', 'perl'], + latex: ['@codemirror/legacy-modes/mode/stex', 'stex'], + tex: ['@codemirror/legacy-modes/mode/stex', 'stex'], + xml: ['@codemirror/legacy-modes/mode/xml', 'xml'], + svg: ['@codemirror/legacy-modes/mode/xml', 'xml'], + properties: ['@codemirror/legacy-modes/mode/properties', 'properties'], + ini: ['@codemirror/legacy-modes/mode/properties', 'properties'], + cfg: ['@codemirror/legacy-modes/mode/properties', 'properties'], + conf: ['@codemirror/legacy-modes/mode/properties', 'properties'], + dockerfile: ['@codemirror/legacy-modes/mode/dockerfile', 'dockerFile'], + matlab: ['@codemirror/legacy-modes/mode/octave', 'octave'], + octave: ['@codemirror/legacy-modes/mode/octave', 'octave'] + } + + if (legacyLangs[language]) { + const [path, method] = legacyLangs[language] + const [modeMod, { StreamLanguage }] = await Promise.all([ + import(path), + import('@codemirror/language') + ]) + extension = StreamLanguage.define(modeMod[method]) + } + } + + if (extension) { + languageCache.set(language, extension) + } + return extension + } catch (error) { + console.error(`[CodeMirror] 加载语言包失败: ${language}`, error) + return null + } +} + +/** + * 根据文件扩展名获取语言名称 + * @param {string} extension - 文件扩展名 + * @returns {string} 语言名称 + */ +export function getLanguageFromExtension(extension) { + const ext = extension.toLowerCase() + + const langMap = { + js: 'javascript', jsx: 'javascript', mjs: 'javascript', cjs: 'javascript', + ts: 'typescript', tsx: 'typescript', + json: 'json', + yaml: 'yaml', yml: 'yaml', + xml: 'xml', xhtml: 'xml', svg: 'svg', + html: 'html', htm: 'html', + css: 'css', scss: 'css', sass: 'css', less: 'css', + cpp: 'cpp', c: 'c', cc: 'cpp', cxx: 'cpp', h: 'cpp', hpp: 'cpp', hxx: 'cpp', + rust: 'rust', rs: 'rust', + go: 'go', + python: 'python', py: 'python', pyw: 'python', + php: 'php', + ruby: 'ruby', rb: 'ruby', + perl: 'perl', pl: 'perl', pm: 'perl', + shell: 'shell', sh: 'shell', bash: 'shell', zsh: 'shell', + bat: 'shell', cmd: 'shell', ps1: 'shell', + sql: 'sql', + java: 'java', + kotlin: 'kotlin', kt: 'kotlin', kts: 'kotlin', + csharp: 'csharp', cs: 'csharp', csx: 'csharp', + swift: 'swift', + markdown: 'markdown', md: 'markdown', + r: 'r', + matlab: 'matlab', m: 'matlab', + latex: 'latex', tex: 'latex', + dockerfile: 'dockerfile', + makefile: 'makefile', mk: 'makefile', gnumakefile: 'makefile', + ini: 'ini', cfg: 'ini', conf: 'ini', properties: 'properties', + gitignore: 'gitignore', + txt: 'text', text: 'text', log: 'text', csv: 'text' + } + + return langMap[ext] || 'text' +} + +/** + * 预加载常用语言包 + * 用于在应用启动时预热缓存 + */ +export async function preloadCommonLanguages() { + await Promise.all(['javascript', 'json', 'markdown', 'python', 'sql'].map(loadLanguageExtension)) +} diff --git a/web/src/utils/fileUtils.js b/web/src/utils/fileUtils.js index f8b5379..ab5e2e2 100644 --- a/web/src/utils/fileUtils.js +++ b/web/src/utils/fileUtils.js @@ -5,6 +5,7 @@ * @description 提供文件相关的通用工具函数,避免代码重复 */ +import { normalizePathSeparators } from './pathHelpers.js' import { FILE_ICON_MAP, FILE_ICONS, BYTE_UNITS, FILE_SIZE_FORMAT, FILE_EXTENSIONS } from './constants' /** @@ -46,11 +47,8 @@ export function formatBytes(bytes) { export function getFileName(path) { if (!path) return '' - // 统一分隔符为正斜杠 - const normalizedPath = path.replace(/\\/g, '/') - - // 分割路径并取最后一部分 - const parts = normalizedPath.split('/') + // 后端已统一返回 / 路径,直接分割 + const parts = path.split('/') return parts[parts.length - 1] || path } @@ -157,7 +155,7 @@ export function isPdfFile(path) { */ export function normalizeFilePath(path, encode = false) { if (!path) return '' - const normalized = path.replace(/\\/g, '/') + const normalized = normalizePathSeparators(path) // 如果需要编码,则使用 encodeURIComponent if (encode) { @@ -327,18 +325,17 @@ export function sanitizeFileName(filename, replacement = '_') { * @returns {Array} 排序后的文件列表 * * @example - * sortFileList([{name: 'b.txt', is_dir: false}, {name: 'a', is_dir: true}]) - * // [{name: 'a', is_dir: true}, {name: 'b.txt', is_dir: false}] + * sortFileList([{name: 'b.txt', isDir: false}, {name: 'a', isDir: true}]) + * // [{name: 'a', isDir: true}, {name: 'b.txt', isDir: false}] */ export function sortFileList(fileList) { if (!Array.isArray(fileList)) return fileList return fileList.sort((a, b) => { - // 如果都是目录或都是文件,按名称排序 - if (a.is_dir === b.is_dir) { + // API 层已转换,直接使用 isDir + if (a.isDir === b.isDir) { return a.name.localeCompare(b.name) } - // 目录优先 - return a.is_dir ? -1 : 1 + return a.isDir ? -1 : 1 }) } diff --git a/web/src/utils/markedExtensions.ts b/web/src/utils/markedExtensions.ts index 737c5b9..ae9473d 100644 --- a/web/src/utils/markedExtensions.ts +++ b/web/src/utils/markedExtensions.ts @@ -1,35 +1,67 @@ import { marked } from 'marked' import hljs from 'highlight.js' -import mermaid from 'mermaid' - -// 导入 highlight.js 核心和两种主题样式 import 'highlight.js/lib/common' import 'highlight.js/styles/github-dark.css' import 'highlight.js/styles/github.css' -// Mermaid 初始化 -mermaid.initialize({ startOnLoad: false, theme: 'default', securityLevel: 'loose' }) +let mermaidInstance: typeof import('mermaid').default | null = null + +async function loadMermaid() { + if (mermaidInstance) return mermaidInstance + + try { + const mermaid = await import('mermaid') + mermaid.default.initialize({ + startOnLoad: false, + theme: 'default', + securityLevel: 'loose' + }) + mermaidInstance = mermaid.default + return mermaidInstance + } catch { + return null + } +} -// 自定义 renderer const renderer = new marked.Renderer() renderer.code = function(token: any) { - // Mermaid 代码块 if (token.lang === 'mermaid') { return `
${token.text}
` } - // 普通代码块 - 使用 highlight.js 高亮 const lang = hljs.getLanguage(token.lang) ? token.lang : 'plaintext' const highlighted = hljs.highlight(token.text, { language: lang }).value return `
${highlighted}
` } +renderer.heading = function(token: any) { + const raw = token.raw || '' + const depth = token.depth || 1 + const text = token.text || '' + + const id = raw + .toLowerCase() + .replace(/[^\u4e00-\u9fa5a-z0-9\s-]/g, '') + .trim() + .replace(/\s+/g, '-') + .replace(/-+/g, '-') + .replace(/^-+|-+$/g, '') || `heading-${Math.random().toString(36).slice(2, 11)}` + + return ` + ${text} + ` +} + marked.use({ renderer, breaks: true, gfm: true }) export { marked } + export async function renderMermaidDiagrams() { - await mermaid.run() + const mermaid = await loadMermaid() + if (mermaid) { + await mermaid.run() + } } diff --git a/web/src/utils/pathHelpers.js b/web/src/utils/pathHelpers.js index e36d997..9e4f115 100644 --- a/web/src/utils/pathHelpers.js +++ b/web/src/utils/pathHelpers.js @@ -50,18 +50,33 @@ export const getFileName = (path) => { * @example * getParentPath('C:\\Users\\file.txt') // 'C:\\Users' * getParentPath('/home/user/file.txt') // '/home/user' + * getParentPath('E:/file.txt') // 'E:/' */ export const getParentPath = (path) => { if (!path) return '' - // 查找最后一个分隔符的位置 - const lastSep = Math.max( - path.lastIndexOf('/'), - path.lastIndexOf('\\') - ) + // 规范化路径分隔符 + const normalizedPath = path.replace(/\\/g, '/') - if (lastSep <= 0) return path - return path.substring(0, lastSep) + // 查找最后一个分隔符的位置 + const lastSep = normalizedPath.lastIndexOf('/') + + if (lastSep <= 0) { + // 没有分隔符或分隔符在开头,返回根目录(对于盘符情况) + if (/^[A-Za-z]:$/.test(normalizedPath)) { + return normalizedPath + '/' // E: 转换为 E:/ + } + return normalizedPath + } + + const parentPath = normalizedPath.substring(0, lastSep) + + // 特殊处理:如果是盘符根目录下的文件(E:/file.txt -> E:/) + if (/^[A-Za-z]:$/.test(parentPath)) { + return parentPath + '/' // 确保根目录带斜杠 + } + + return parentPath || '/' } /** diff --git a/web/src/views/db-cli/components/ResultPanel.vue b/web/src/views/db-cli/components/ResultPanel.vue index fa92795..1f79451 100644 --- a/web/src/views/db-cli/components/ResultPanel.vue +++ b/web/src/views/db-cli/components/ResultPanel.vue @@ -747,14 +747,6 @@ const updateResultTableHeight = () => { const maxHeight = availableHeight > 0 ? availableHeight : 400 tableScrollHeight.value = Math.max(minHeight, maxHeight) - - console.log('表格高度计算:', { - containerHeight, - paginationHeight, - tableHeaderHeight, - availableHeight, - final: tableScrollHeight.value - }) }, 150) } diff --git a/web/src/views/db-cli/composables/useStructureState.ts b/web/src/views/db-cli/composables/useStructureState.ts index bd9963a..d9a6e01 100644 --- a/web/src/views/db-cli/composables/useStructureState.ts +++ b/web/src/views/db-cli/composables/useStructureState.ts @@ -34,11 +34,8 @@ export function useStructureState() { dbType: 'mysql' | 'mongo' | 'redis', nodeType: string ) => { - console.log('🟢 loadStructure 开始:', { connectionId, database, tableName, dbType, nodeType }) - // 对于连接和数据库节点,不需要加载结构 if (nodeType === 'connection' || nodeType === 'database') { - console.log('🟡 跳过:节点类型为连接或数据库') structureInfo.value = { connectionId, database, @@ -52,7 +49,6 @@ export function useStructureState() { // 如果没有表名,不加载(但保留 structureInfo 用于显示提示) if (!tableName) { - console.log('🟡 跳过:表名为空') structureInfo.value = { connectionId, database, @@ -78,14 +74,8 @@ export function useStructureState() { tableName ) - console.log('表结构加载成功:', { connectionId, database, tableName, result }) - console.log('返回数据类型:', typeof result) - console.log('返回数据 keys:', result ? Object.keys(result) : 'null') - console.log('返回数据 type 字段:', result?.type) - console.log('返回数据 columns 字段:', result?.columns) - structureData.value = result - + // 确保 structureInfo 也设置了 structureInfo.value = { connectionId, @@ -94,20 +84,6 @@ export function useStructureState() { dbType, nodeType } - - // 确保 structureInfo 也设置了 - structureInfo.value = { - connectionId, - database, - tableName, - dbType, - nodeType - } - - console.log('✅ 设置完成 - structureData:', structureData.value) - console.log('✅ 设置完成 - structureInfo:', structureInfo.value) - console.log('✅ structureData 是否为 null:', structureData.value === null) - console.log('✅ structureInfo 是否为 null:', structureInfo.value === null) } catch (error: unknown) { console.error('加载表结构失败:', error) const errorMessage = error instanceof Error ? error.message : '加载表结构失败' diff --git a/web/src/wailsjs/wailsjs/go/main/App.d.ts b/web/src/wailsjs/wailsjs/go/main/App.d.ts new file mode 100644 index 0000000..1d8fdfa --- /dev/null +++ b/web/src/wailsjs/wailsjs/go/main/App.d.ts @@ -0,0 +1,131 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT +import {filesystem} from '../models'; +import {main} from '../models'; +import {api} from '../models'; + +export function CheckUpdate():Promise>; + +export function ClearCache():Promise; + +export function CreateDir(arg1:string):Promise; + +export function CreateFile(arg1:string):Promise; + +export function DeleteDbConnection(arg1:number):Promise; + +export function DeletePath(arg1:string):Promise; + +export function DeletePermanently(arg1:string):Promise; + +export function DeleteResultHistory(arg1:number):Promise; + +export function DetectFileTypeByContent(arg1:string):Promise>; + +export function DownloadUpdate(arg1:string):Promise>; + +export function EmptyRecycleBin():Promise; + +export function ExecuteSQL(arg1:number,arg2:string,arg3:string):Promise>; + +export function ExtractFileFromZip(arg1:string,arg2:string):Promise; + +export function ExtractFileFromZipToTemp(arg1:string,arg2:string):Promise; + +export function GetAppConfig():Promise>; + +export function GetAuditLogs(arg1:number):Promise>>; + +export function GetCPUInfo():Promise>; + +export function GetCommonPaths():Promise>; + +export function GetCurrentVersion():Promise>; + +export function GetDatabases(arg1:number):Promise>; + +export function GetDiskInfo():Promise>>; + +export function GetEnvVars():Promise>; + +export function GetFileInfo(arg1:string):Promise>; + +export function GetFileServerURL():Promise; + +export function GetIndexes(arg1:number,arg2:string,arg3:string):Promise>>; + +export function GetMemoryInfo():Promise>; + +export function GetRecycleBinEntries():Promise>>; + +export function GetResultHistory(arg1:any,arg2:string,arg3:number,arg4:number):Promise>; + +export function GetResultHistoryByID(arg1:number):Promise>; + +export function GetSystemInfo():Promise>; + +export function GetTableStructure(arg1:number,arg2:string,arg3:string):Promise>; + +export function GetTables(arg1:number,arg2:string):Promise>; + +export function GetUpdateConfig():Promise>; + +export function GetZipFileInfo(arg1:string,arg2:string):Promise>; + +export function Greet(arg1:string):Promise; + +export function InstallUpdate(arg1:string,arg2:boolean):Promise>; + +export function InstallUpdateWithHash(arg1:string,arg2:boolean,arg3:string,arg4:string):Promise>; + +export function ListDbConnections():Promise>>; + +export function ListDir(arg1:string):Promise>>; + +export function ListSqlTabs():Promise>>; + +export function ListZipContents(arg1:string):Promise>>; + +export function OpenPath(arg1:string):Promise; + +export function PreviewTableStructure(arg1:number,arg2:string,arg3:string,arg4:Record):Promise>; + +export function QueryUsers(arg1:string,arg2:number,arg3:number,arg4:number,arg5:number,arg6:number,arg7:string,arg8:string):Promise>; + +export function ReadFile(arg1:string):Promise; + +export function Reload():Promise; + +export function RenamePath(arg1:main.RenamePathRequest):Promise; + +export function ResolveShortcut(arg1:string):Promise>; + +export function RestoreFromRecycleBin(arg1:string):Promise; + +export function SaveAppConfig(arg1:main.SaveAppConfigRequest):Promise>; + +export function SaveDbConnection(arg1:api.SaveConnectionRequest):Promise; + +export function SaveResult(arg1:number,arg2:string,arg3:string,arg4:string,arg5:any,arg6:Array,arg7:number,arg8:number):Promise>; + +export function SaveSqlTabs(arg1:Array>):Promise; + +export function SetUpdateConfig(arg1:boolean,arg2:number,arg3:string):Promise>; + +export function TestDbConnection(arg1:number):Promise; + +export function TestDbConnectionWithParams(arg1:api.TestConnectionRequest):Promise; + +export function UpdateTableStructure(arg1:number,arg2:string,arg3:string,arg4:Record):Promise>; + +export function VerifyUpdateFile(arg1:string,arg2:string,arg3:string):Promise>; + +export function WindowClose():Promise; + +export function WindowIsMaximized():Promise; + +export function WindowMaximize():Promise; + +export function WindowMinimize():Promise; + +export function WriteFile(arg1:main.WriteFileRequest):Promise; diff --git a/web/src/wailsjs/wailsjs/go/main/App.js b/web/src/wailsjs/wailsjs/go/main/App.js new file mode 100644 index 0000000..bd6137b --- /dev/null +++ b/web/src/wailsjs/wailsjs/go/main/App.js @@ -0,0 +1,255 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export function CheckUpdate() { + return window['go']['main']['App']['CheckUpdate'](); +} + +export function ClearCache() { + return window['go']['main']['App']['ClearCache'](); +} + +export function CreateDir(arg1) { + return window['go']['main']['App']['CreateDir'](arg1); +} + +export function CreateFile(arg1) { + return window['go']['main']['App']['CreateFile'](arg1); +} + +export function DeleteDbConnection(arg1) { + return window['go']['main']['App']['DeleteDbConnection'](arg1); +} + +export function DeletePath(arg1) { + return window['go']['main']['App']['DeletePath'](arg1); +} + +export function DeletePermanently(arg1) { + return window['go']['main']['App']['DeletePermanently'](arg1); +} + +export function DeleteResultHistory(arg1) { + return window['go']['main']['App']['DeleteResultHistory'](arg1); +} + +export function DetectFileTypeByContent(arg1) { + return window['go']['main']['App']['DetectFileTypeByContent'](arg1); +} + +export function DownloadUpdate(arg1) { + return window['go']['main']['App']['DownloadUpdate'](arg1); +} + +export function EmptyRecycleBin() { + return window['go']['main']['App']['EmptyRecycleBin'](); +} + +export function ExecuteSQL(arg1, arg2, arg3) { + return window['go']['main']['App']['ExecuteSQL'](arg1, arg2, arg3); +} + +export function ExtractFileFromZip(arg1, arg2) { + return window['go']['main']['App']['ExtractFileFromZip'](arg1, arg2); +} + +export function ExtractFileFromZipToTemp(arg1, arg2) { + return window['go']['main']['App']['ExtractFileFromZipToTemp'](arg1, arg2); +} + +export function GetAppConfig() { + return window['go']['main']['App']['GetAppConfig'](); +} + +export function GetAuditLogs(arg1) { + return window['go']['main']['App']['GetAuditLogs'](arg1); +} + +export function GetCPUInfo() { + return window['go']['main']['App']['GetCPUInfo'](); +} + +export function GetCommonPaths() { + return window['go']['main']['App']['GetCommonPaths'](); +} + +export function GetCurrentVersion() { + return window['go']['main']['App']['GetCurrentVersion'](); +} + +export function GetDatabases(arg1) { + return window['go']['main']['App']['GetDatabases'](arg1); +} + +export function GetDiskInfo() { + return window['go']['main']['App']['GetDiskInfo'](); +} + +export function GetEnvVars() { + return window['go']['main']['App']['GetEnvVars'](); +} + +export function GetFileInfo(arg1) { + return window['go']['main']['App']['GetFileInfo'](arg1); +} + +export function GetFileServerURL() { + return window['go']['main']['App']['GetFileServerURL'](); +} + +export function GetIndexes(arg1, arg2, arg3) { + return window['go']['main']['App']['GetIndexes'](arg1, arg2, arg3); +} + +export function GetMemoryInfo() { + return window['go']['main']['App']['GetMemoryInfo'](); +} + +export function GetRecycleBinEntries() { + return window['go']['main']['App']['GetRecycleBinEntries'](); +} + +export function GetResultHistory(arg1, arg2, arg3, arg4) { + return window['go']['main']['App']['GetResultHistory'](arg1, arg2, arg3, arg4); +} + +export function GetResultHistoryByID(arg1) { + return window['go']['main']['App']['GetResultHistoryByID'](arg1); +} + +export function GetSystemInfo() { + return window['go']['main']['App']['GetSystemInfo'](); +} + +export function GetTableStructure(arg1, arg2, arg3) { + return window['go']['main']['App']['GetTableStructure'](arg1, arg2, arg3); +} + +export function GetTables(arg1, arg2) { + return window['go']['main']['App']['GetTables'](arg1, arg2); +} + +export function GetUpdateConfig() { + return window['go']['main']['App']['GetUpdateConfig'](); +} + +export function GetZipFileInfo(arg1, arg2) { + return window['go']['main']['App']['GetZipFileInfo'](arg1, arg2); +} + +export function Greet(arg1) { + return window['go']['main']['App']['Greet'](arg1); +} + +export function InstallUpdate(arg1, arg2) { + return window['go']['main']['App']['InstallUpdate'](arg1, arg2); +} + +export function InstallUpdateWithHash(arg1, arg2, arg3, arg4) { + return window['go']['main']['App']['InstallUpdateWithHash'](arg1, arg2, arg3, arg4); +} + +export function ListDbConnections() { + return window['go']['main']['App']['ListDbConnections'](); +} + +export function ListDir(arg1) { + return window['go']['main']['App']['ListDir'](arg1); +} + +export function ListSqlTabs() { + return window['go']['main']['App']['ListSqlTabs'](); +} + +export function ListZipContents(arg1) { + return window['go']['main']['App']['ListZipContents'](arg1); +} + +export function OpenPath(arg1) { + return window['go']['main']['App']['OpenPath'](arg1); +} + +export function PreviewTableStructure(arg1, arg2, arg3, arg4) { + return window['go']['main']['App']['PreviewTableStructure'](arg1, arg2, arg3, arg4); +} + +export function QueryUsers(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) { + return window['go']['main']['App']['QueryUsers'](arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); +} + +export function ReadFile(arg1) { + return window['go']['main']['App']['ReadFile'](arg1); +} + +export function Reload() { + return window['go']['main']['App']['Reload'](); +} + +export function RenamePath(arg1) { + return window['go']['main']['App']['RenamePath'](arg1); +} + +export function ResolveShortcut(arg1) { + return window['go']['main']['App']['ResolveShortcut'](arg1); +} + +export function RestoreFromRecycleBin(arg1) { + return window['go']['main']['App']['RestoreFromRecycleBin'](arg1); +} + +export function SaveAppConfig(arg1) { + return window['go']['main']['App']['SaveAppConfig'](arg1); +} + +export function SaveDbConnection(arg1) { + return window['go']['main']['App']['SaveDbConnection'](arg1); +} + +export function SaveResult(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) { + return window['go']['main']['App']['SaveResult'](arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); +} + +export function SaveSqlTabs(arg1) { + return window['go']['main']['App']['SaveSqlTabs'](arg1); +} + +export function SetUpdateConfig(arg1, arg2, arg3) { + return window['go']['main']['App']['SetUpdateConfig'](arg1, arg2, arg3); +} + +export function TestDbConnection(arg1) { + return window['go']['main']['App']['TestDbConnection'](arg1); +} + +export function TestDbConnectionWithParams(arg1) { + return window['go']['main']['App']['TestDbConnectionWithParams'](arg1); +} + +export function UpdateTableStructure(arg1, arg2, arg3, arg4) { + return window['go']['main']['App']['UpdateTableStructure'](arg1, arg2, arg3, arg4); +} + +export function VerifyUpdateFile(arg1, arg2, arg3) { + return window['go']['main']['App']['VerifyUpdateFile'](arg1, arg2, arg3); +} + +export function WindowClose() { + return window['go']['main']['App']['WindowClose'](); +} + +export function WindowIsMaximized() { + return window['go']['main']['App']['WindowIsMaximized'](); +} + +export function WindowMaximize() { + return window['go']['main']['App']['WindowMaximize'](); +} + +export function WindowMinimize() { + return window['go']['main']['App']['WindowMinimize'](); +} + +export function WriteFile(arg1) { + return window['go']['main']['App']['WriteFile'](arg1); +} diff --git a/web/src/wailsjs/wailsjs/go/models.ts b/web/src/wailsjs/wailsjs/go/models.ts new file mode 100644 index 0000000..a136a0b --- /dev/null +++ b/web/src/wailsjs/wailsjs/go/models.ts @@ -0,0 +1,177 @@ +export namespace api { + + export class AppTabDefinition { + key: string; + title: string; + visible: boolean; + enabled: boolean; + + static createFrom(source: any = {}) { + return new AppTabDefinition(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.key = source["key"]; + this.title = source["title"]; + this.visible = source["visible"]; + this.enabled = source["enabled"]; + } + } + export class SaveConnectionRequest { + id: number; + name: string; + type: string; + host: string; + port: number; + username: string; + password: string; + database: string; + options: string; + + static createFrom(source: any = {}) { + return new SaveConnectionRequest(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.id = source["id"]; + this.name = source["name"]; + this.type = source["type"]; + this.host = source["host"]; + this.port = source["port"]; + this.username = source["username"]; + this.password = source["password"]; + this.database = source["database"]; + this.options = source["options"]; + } + } + export class TestConnectionRequest { + id: number; + type: string; + host: string; + port: number; + username: string; + password: string; + database: string; + options: string; + + static createFrom(source: any = {}) { + return new TestConnectionRequest(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.id = source["id"]; + this.type = source["type"]; + this.host = source["host"]; + this.port = source["port"]; + this.username = source["username"]; + this.password = source["password"]; + this.database = source["database"]; + this.options = source["options"]; + } + } + +} + +export namespace filesystem { + + export class FileOperationResult { + path: string; + name: string; + size: number; + size_str?: string; + is_dir: boolean; + mod_time?: string; + mode?: string; + old_path?: string; + deleted?: boolean; + + static createFrom(source: any = {}) { + return new FileOperationResult(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.path = source["path"]; + this.name = source["name"]; + this.size = source["size"]; + this.size_str = source["size_str"]; + this.is_dir = source["is_dir"]; + this.mod_time = source["mod_time"]; + this.mode = source["mode"]; + this.old_path = source["old_path"]; + this.deleted = source["deleted"]; + } + } + +} + +export namespace main { + + export class RenamePathRequest { + oldPath: string; + newPath: string; + + static createFrom(source: any = {}) { + return new RenamePathRequest(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.oldPath = source["oldPath"]; + this.newPath = source["newPath"]; + } + } + export class SaveAppConfigRequest { + tabs: api.AppTabDefinition[]; + visibleTabs: string[]; + defaultTab: string; + + static createFrom(source: any = {}) { + return new SaveAppConfigRequest(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.tabs = this.convertValues(source["tabs"], api.AppTabDefinition); + this.visibleTabs = source["visibleTabs"]; + this.defaultTab = source["defaultTab"]; + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice && a.map) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } + } + export class WriteFileRequest { + path: string; + content: string; + + static createFrom(source: any = {}) { + return new WriteFileRequest(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.path = source["path"]; + this.content = source["content"]; + } + } + +} + diff --git a/web/src/wailsjs/wailsjs/runtime/package.json b/web/src/wailsjs/wailsjs/runtime/package.json new file mode 100644 index 0000000..1e7c8a5 --- /dev/null +++ b/web/src/wailsjs/wailsjs/runtime/package.json @@ -0,0 +1,24 @@ +{ + "name": "@wailsapp/runtime", + "version": "2.0.0", + "description": "Wails Javascript runtime library", + "main": "runtime.js", + "types": "runtime.d.ts", + "scripts": { + }, + "repository": { + "type": "git", + "url": "git+https://github.com/wailsapp/wails.git" + }, + "keywords": [ + "Wails", + "Javascript", + "Go" + ], + "author": "Lea Anthony ", + "license": "MIT", + "bugs": { + "url": "https://github.com/wailsapp/wails/issues" + }, + "homepage": "https://github.com/wailsapp/wails#readme" +} diff --git a/web/src/wailsjs/wailsjs/runtime/runtime.d.ts b/web/src/wailsjs/wailsjs/runtime/runtime.d.ts new file mode 100644 index 0000000..4445dac --- /dev/null +++ b/web/src/wailsjs/wailsjs/runtime/runtime.d.ts @@ -0,0 +1,249 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +export interface Position { + x: number; + y: number; +} + +export interface Size { + w: number; + h: number; +} + +export interface Screen { + isCurrent: boolean; + isPrimary: boolean; + width : number + height : number +} + +// Environment information such as platform, buildtype, ... +export interface EnvironmentInfo { + buildType: string; + platform: string; + arch: string; +} + +// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit) +// emits the given event. Optional data may be passed with the event. +// This will trigger any event listeners. +export function EventsEmit(eventName: string, ...data: any): void; + +// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name. +export function EventsOn(eventName: string, callback: (...data: any) => void): () => void; + +// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple) +// sets up a listener for the given event name, but will only trigger a given number times. +export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void; + +// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce) +// sets up a listener for the given event name, but will only trigger once. +export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void; + +// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff) +// unregisters the listener for the given event name. +export function EventsOff(eventName: string, ...additionalEventNames: string[]): void; + +// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) +// unregisters all listeners. +export function EventsOffAll(): void; + +// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) +// logs the given message as a raw message +export function LogPrint(message: string): void; + +// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace) +// logs the given message at the `trace` log level. +export function LogTrace(message: string): void; + +// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug) +// logs the given message at the `debug` log level. +export function LogDebug(message: string): void; + +// [LogError](https://wails.io/docs/reference/runtime/log#logerror) +// logs the given message at the `error` log level. +export function LogError(message: string): void; + +// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal) +// logs the given message at the `fatal` log level. +// The application will quit after calling this method. +export function LogFatal(message: string): void; + +// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo) +// logs the given message at the `info` log level. +export function LogInfo(message: string): void; + +// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning) +// logs the given message at the `warning` log level. +export function LogWarning(message: string): void; + +// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload) +// Forces a reload by the main application as well as connected browsers. +export function WindowReload(): void; + +// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp) +// Reloads the application frontend. +export function WindowReloadApp(): void; + +// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop) +// Sets the window AlwaysOnTop or not on top. +export function WindowSetAlwaysOnTop(b: boolean): void; + +// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme) +// *Windows only* +// Sets window theme to system default (dark/light). +export function WindowSetSystemDefaultTheme(): void; + +// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme) +// *Windows only* +// Sets window to light theme. +export function WindowSetLightTheme(): void; + +// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme) +// *Windows only* +// Sets window to dark theme. +export function WindowSetDarkTheme(): void; + +// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter) +// Centers the window on the monitor the window is currently on. +export function WindowCenter(): void; + +// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle) +// Sets the text in the window title bar. +export function WindowSetTitle(title: string): void; + +// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen) +// Makes the window full screen. +export function WindowFullscreen(): void; + +// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen) +// Restores the previous window dimensions and position prior to full screen. +export function WindowUnfullscreen(): void; + +// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen) +// Returns the state of the window, i.e. whether the window is in full screen mode or not. +export function WindowIsFullscreen(): Promise; + +// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) +// Sets the width and height of the window. +export function WindowSetSize(width: number, height: number): void; + +// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) +// Gets the width and height of the window. +export function WindowGetSize(): Promise; + +// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize) +// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions. +// Setting a size of 0,0 will disable this constraint. +export function WindowSetMaxSize(width: number, height: number): void; + +// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize) +// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions. +// Setting a size of 0,0 will disable this constraint. +export function WindowSetMinSize(width: number, height: number): void; + +// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition) +// Sets the window position relative to the monitor the window is currently on. +export function WindowSetPosition(x: number, y: number): void; + +// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition) +// Gets the window position relative to the monitor the window is currently on. +export function WindowGetPosition(): Promise; + +// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide) +// Hides the window. +export function WindowHide(): void; + +// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow) +// Shows the window, if it is currently hidden. +export function WindowShow(): void; + +// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise) +// Maximises the window to fill the screen. +export function WindowMaximise(): void; + +// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise) +// Toggles between Maximised and UnMaximised. +export function WindowToggleMaximise(): void; + +// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise) +// Restores the window to the dimensions and position prior to maximising. +export function WindowUnmaximise(): void; + +// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised) +// Returns the state of the window, i.e. whether the window is maximised or not. +export function WindowIsMaximised(): Promise; + +// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise) +// Minimises the window. +export function WindowMinimise(): void; + +// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise) +// Restores the window to the dimensions and position prior to minimising. +export function WindowUnminimise(): void; + +// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised) +// Returns the state of the window, i.e. whether the window is minimised or not. +export function WindowIsMinimised(): Promise; + +// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal) +// Returns the state of the window, i.e. whether the window is normal or not. +export function WindowIsNormal(): Promise; + +// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour) +// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels. +export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void; + +// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall) +// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system. +export function ScreenGetAll(): Promise; + +// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl) +// Opens the given URL in the system browser. +export function BrowserOpenURL(url: string): void; + +// [Environment](https://wails.io/docs/reference/runtime/intro#environment) +// Returns information about the environment +export function Environment(): Promise; + +// [Quit](https://wails.io/docs/reference/runtime/intro#quit) +// Quits the application. +export function Quit(): void; + +// [Hide](https://wails.io/docs/reference/runtime/intro#hide) +// Hides the application. +export function Hide(): void; + +// [Show](https://wails.io/docs/reference/runtime/intro#show) +// Shows the application. +export function Show(): void; + +// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext) +// Returns the current text stored on clipboard +export function ClipboardGetText(): Promise; + +// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext) +// Sets a text on the clipboard +export function ClipboardSetText(text: string): Promise; + +// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop) +// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. +export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void + +// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff) +// OnFileDropOff removes the drag and drop listeners and handlers. +export function OnFileDropOff() :void + +// Check if the file path resolver is available +export function CanResolveFilePaths(): boolean; + +// Resolves file paths for an array of files +export function ResolveFilePaths(files: File[]): void \ No newline at end of file diff --git a/web/src/wailsjs/wailsjs/runtime/runtime.js b/web/src/wailsjs/wailsjs/runtime/runtime.js new file mode 100644 index 0000000..7cb89d7 --- /dev/null +++ b/web/src/wailsjs/wailsjs/runtime/runtime.js @@ -0,0 +1,242 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +export function LogPrint(message) { + window.runtime.LogPrint(message); +} + +export function LogTrace(message) { + window.runtime.LogTrace(message); +} + +export function LogDebug(message) { + window.runtime.LogDebug(message); +} + +export function LogInfo(message) { + window.runtime.LogInfo(message); +} + +export function LogWarning(message) { + window.runtime.LogWarning(message); +} + +export function LogError(message) { + window.runtime.LogError(message); +} + +export function LogFatal(message) { + window.runtime.LogFatal(message); +} + +export function EventsOnMultiple(eventName, callback, maxCallbacks) { + return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks); +} + +export function EventsOn(eventName, callback) { + return EventsOnMultiple(eventName, callback, -1); +} + +export function EventsOff(eventName, ...additionalEventNames) { + return window.runtime.EventsOff(eventName, ...additionalEventNames); +} + +export function EventsOffAll() { + return window.runtime.EventsOffAll(); +} + +export function EventsOnce(eventName, callback) { + return EventsOnMultiple(eventName, callback, 1); +} + +export function EventsEmit(eventName) { + let args = [eventName].slice.call(arguments); + return window.runtime.EventsEmit.apply(null, args); +} + +export function WindowReload() { + window.runtime.WindowReload(); +} + +export function WindowReloadApp() { + window.runtime.WindowReloadApp(); +} + +export function WindowSetAlwaysOnTop(b) { + window.runtime.WindowSetAlwaysOnTop(b); +} + +export function WindowSetSystemDefaultTheme() { + window.runtime.WindowSetSystemDefaultTheme(); +} + +export function WindowSetLightTheme() { + window.runtime.WindowSetLightTheme(); +} + +export function WindowSetDarkTheme() { + window.runtime.WindowSetDarkTheme(); +} + +export function WindowCenter() { + window.runtime.WindowCenter(); +} + +export function WindowSetTitle(title) { + window.runtime.WindowSetTitle(title); +} + +export function WindowFullscreen() { + window.runtime.WindowFullscreen(); +} + +export function WindowUnfullscreen() { + window.runtime.WindowUnfullscreen(); +} + +export function WindowIsFullscreen() { + return window.runtime.WindowIsFullscreen(); +} + +export function WindowGetSize() { + return window.runtime.WindowGetSize(); +} + +export function WindowSetSize(width, height) { + window.runtime.WindowSetSize(width, height); +} + +export function WindowSetMaxSize(width, height) { + window.runtime.WindowSetMaxSize(width, height); +} + +export function WindowSetMinSize(width, height) { + window.runtime.WindowSetMinSize(width, height); +} + +export function WindowSetPosition(x, y) { + window.runtime.WindowSetPosition(x, y); +} + +export function WindowGetPosition() { + return window.runtime.WindowGetPosition(); +} + +export function WindowHide() { + window.runtime.WindowHide(); +} + +export function WindowShow() { + window.runtime.WindowShow(); +} + +export function WindowMaximise() { + window.runtime.WindowMaximise(); +} + +export function WindowToggleMaximise() { + window.runtime.WindowToggleMaximise(); +} + +export function WindowUnmaximise() { + window.runtime.WindowUnmaximise(); +} + +export function WindowIsMaximised() { + return window.runtime.WindowIsMaximised(); +} + +export function WindowMinimise() { + window.runtime.WindowMinimise(); +} + +export function WindowUnminimise() { + window.runtime.WindowUnminimise(); +} + +export function WindowSetBackgroundColour(R, G, B, A) { + window.runtime.WindowSetBackgroundColour(R, G, B, A); +} + +export function ScreenGetAll() { + return window.runtime.ScreenGetAll(); +} + +export function WindowIsMinimised() { + return window.runtime.WindowIsMinimised(); +} + +export function WindowIsNormal() { + return window.runtime.WindowIsNormal(); +} + +export function BrowserOpenURL(url) { + window.runtime.BrowserOpenURL(url); +} + +export function Environment() { + return window.runtime.Environment(); +} + +export function Quit() { + window.runtime.Quit(); +} + +export function Hide() { + window.runtime.Hide(); +} + +export function Show() { + window.runtime.Show(); +} + +export function ClipboardGetText() { + return window.runtime.ClipboardGetText(); +} + +export function ClipboardSetText(text) { + return window.runtime.ClipboardSetText(text); +} + +/** + * Callback for OnFileDrop returns a slice of file path strings when a drop is finished. + * + * @export + * @callback OnFileDropCallback + * @param {number} x - x coordinate of the drop + * @param {number} y - y coordinate of the drop + * @param {string[]} paths - A list of file paths. + */ + +/** + * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. + * + * @export + * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished. + * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target) + */ +export function OnFileDrop(callback, useDropTarget) { + return window.runtime.OnFileDrop(callback, useDropTarget); +} + +/** + * OnFileDropOff removes the drag and drop listeners and handlers. + */ +export function OnFileDropOff() { + return window.runtime.OnFileDropOff(); +} + +export function CanResolveFilePaths() { + return window.runtime.CanResolveFilePaths(); +} + +export function ResolveFilePaths(files) { + return window.runtime.ResolveFilePaths(files); +} \ No newline at end of file diff --git a/web/vite.config.js b/web/vite.config.js index 1c5a46c..b811c58 100644 --- a/web/vite.config.js +++ b/web/vite.config.js @@ -1,60 +1,74 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { resolve } from 'path' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { ArcoResolver } from 'unplugin-vue-components/resolvers' export default defineConfig({ - plugins: [vue()], + plugins: [ + vue(), + AutoImport({ + imports: ['vue', 'vue-router'], + dts: 'src/auto-imports.d.ts', + }), + Components({ + resolvers: [ArcoResolver({ sideEffect: true })], + dts: 'src/components.d.ts', + }) + ], resolve: { - alias: { - '@': resolve(__dirname, 'src') - } + alias: { '@': resolve(__dirname, 'src') } }, build: { outDir: 'dist', emptyOutDir: true, - sourcemap: false, // 生产环境禁用 source map,减小打包体积 + sourcemap: false, + minify: 'esbuild', + cssCodeSplit: true, + chunkSizeWarningLimit: 1000, + esbuild: { + target: 'es2020', + drop: ['console', 'debugger'] + }, rollupOptions: { output: { - manualChunks: { - 'codemirror': [ - '@codemirror/view', - '@codemirror/state', - '@codemirror/language', - '@codemirror/commands', - '@codemirror/lang-javascript', - '@codemirror/lang-java', - '@codemirror/lang-python', - '@codemirror/lang-html', - '@codemirror/lang-css', - '@codemirror/lang-markdown', - '@codemirror/lang-sql' - ] - } + manualChunks: (id) => { + if (!id.includes('node_modules')) return + + if (id.includes('@codemirror')) { + if (id.includes('lang-') || id.includes('legacy-modes')) { + return 'vendor-codemirror-langs' + } + return 'vendor-codemirror-core' + } + + if (id.includes('@arco-design')) return 'vendor-arco' + if (id.includes('mermaid')) return 'vendor-mermaid' + if (id.includes('marked') || id.includes('highlight.js')) return 'vendor-markdown' + if (id.includes('vue') || id.includes('pinia')) return 'vendor-vue' + + return 'vendor' + }, + chunkFileNames: 'assets/js/[name]-[hash].js', + entryFileNames: 'assets/js/[name]-[hash].js', + assetFileNames: 'assets/[ext]/[name]-[hash].[ext]' } } }, optimizeDeps: { include: [ - '@codemirror/view', - '@codemirror/state', - '@codemirror/language', - '@codemirror/commands', - '@codemirror/lang-javascript', - '@codemirror/lang-java', - '@codemirror/lang-python', - '@codemirror/lang-html', - '@codemirror/lang-css', - '@codemirror/lang-markdown', - '@codemirror/lang-sql', - '@codemirror/legacy-modes/mode/go', - '@codemirror/legacy-modes/mode/clike', - '@codemirror/legacy-modes/mode/ruby', - '@codemirror/legacy-modes/mode/rust', - '@codemirror/legacy-modes/mode/shell', - '@codemirror/legacy-modes/mode/yaml', - '@codemirror/legacy-modes/mode/xml' + 'vue', 'pinia', '@arco-design/web-vue', 'marked', 'highlight.js', + '@codemirror/view', '@codemirror/state', '@codemirror/language', '@codemirror/commands', + '@codemirror/lang-javascript', '@codemirror/lang-json', '@codemirror/lang-yaml', + '@codemirror/lang-html', '@codemirror/lang-css', '@codemirror/lang-markdown', + '@codemirror/lang-sql', '@codemirror/lang-java', '@codemirror/lang-python', + '@codemirror/lang-php', '@codemirror/lang-rust', '@codemirror/lang-go', '@codemirror/lang-cpp', + '@codemirror/legacy-modes/mode/clike', '@codemirror/legacy-modes/mode/ruby', + '@codemirror/legacy-modes/mode/shell', '@codemirror/legacy-modes/mode/xml' ] - } + }, + cacheDir: 'node_modules/.vite' })