commit 218ee31adb184aef457b9f8ceda5ed8030dfb22a Author: Jack Date: Mon Sep 14 15:06:14 2026 +0800 chore: initial commit - Wails v2 + Go 1.21+ + Vue 3 dark UI - 3 buttons: Check env (auto-install dsh) / Start dsh web / Stop - 升级 / 检查版本 一键按钮 - 启动后窗口自动最小化到 Dock - 顶部 logo 鼠标 hover 显示大图预览 - scripts/: build-mac.sh, build-windows.bat, install-app.sh - ui-design/: 视觉设计稿 (HTML + tokens) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b112c18 --- /dev/null +++ b/.gitignore @@ -0,0 +1,65 @@ +# ─── Go ─────────────────────────────────────────────── +# 编译产物(.app/.exe 整个目录) +build/bin/ +# go build 出来的裸 binary(--selftest 等调试命令) +/dsh-launcher +# macOS 开发期构建的 Info.plist(带 dev id),不入仓;release 用 Info.plist 入仓 +build/darwin/Info.dev.plist +# Windows 打包临时 +build/windows/installer/ +build/windows/wails.exe.manifest +# Go 覆盖率/测试产物 +*.test +*.out +coverage.* + +# ─── Node / Vite / Wails 前端 ──────────────────────── +node_modules/ +frontend/dist/ +# frontend/wailsjs/ 是 wails build 自动生成的 JS 绑定,建议入仓(clone 后开箱即用) +# 顶部 banner 图片是 build 临时文件,不入仓 +frontend/src/assets/images/banner.png + +# ─── Wails 框架生成的本地开发配置 ───────────────────── +# wails.json 是项目配置,应该 commit +# 但 build/ 下生成的 Info.plist / icon.icns 已经包含在 build/bin/,无需额外忽略 + +# ─── macOS ─────────────────────────────────────────── +.DS_Store +.AppleDouble +.LSOverride +Icon? +._* +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# ─── Windows ───────────────────────────────────────── +Thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ + +# ─── IDE ───────────────────────────────────────────── +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# ─── 日志 / 临时文件 ────────────────────────────────── +*.log +.DS_Store +.tmp/ +*.tmp +.env +.env.local +.env.*.local + +# ─── 应用运行产生的缓存 ────────────────────────────── +# Wails macOS app 运行时会在 ~/Library/{Caches,Logs} 下产数据,不入仓 +# (路径在 $HOME 下,不会进项目) diff --git a/README.md b/README.md new file mode 100644 index 0000000..d27aaee --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# README + +## About + +This is the official Wails Vue template. + +You can configure the project by editing `wails.json`. More information about the project settings can be found +here: https://wails.io/docs/reference/project-config + +## Live Development + +To run in live development mode, run `wails dev` in the project directory. This will run a Vite development +server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser +and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect +to this in your browser, and you can call your Go code from devtools. + +## Building + +To build a redistributable, production mode package, use `wails build`. diff --git a/app.go b/app.go new file mode 100644 index 0000000..0db3563 --- /dev/null +++ b/app.go @@ -0,0 +1,810 @@ +package main + +import ( + "bufio" + "bytes" + "context" + "fmt" + "io" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "runtime" + "runtime/debug" + "strings" + "sync" + "time" + + wailsruntime "github.com/wailsapp/wails/v2/pkg/runtime" +) + +// App is the Wails application backend. +type App struct { + ctx context.Context + + mu sync.Mutex + dshCmd *exec.Cmd + dshURL string + startedAt time.Time + running bool +} + +// NewApp creates a new App application struct. +func NewApp() *App { + initCrashLog() + // 在进程启动时预先初始化 npm prefix 缓存,后续 buildChildEnv 直接读缓存。 + // 这步可能 exec npm 子命令(限 2s 超时),但不会和 buildChildEnv 互相递归。 + initNpmPrefix() + return &App{} +} + +// initNpmPrefix triggers the cached npm prefix lookup exactly once. +// 必须在启动早期(且不依赖 buildChildEnv)调用,避免 buildChildEnv → cachedNpmPrefix +// 第一次执行时栈还未充分展开导致后续路径无法解析。 +func initNpmPrefix() { + _, _ = cachedNpmPrefix() +} + +// initCrashLog wires a panic logger that writes to ~/Library/Logs/dsh-launcher.log. +// macOS GUI 进程 stderr 没终端,panic 信息看不到,落盘最稳。 +var crashLogOnce sync.Once + +func initCrashLog() { + crashLogOnce.Do(func() { + home, _ := os.UserHomeDir() + logPath := filepath.Join(home, "Library", "Logs", "dsh-launcher.log") + _ = os.MkdirAll(filepath.Dir(logPath), 0o755) + f, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) + if err != nil { + return + } + log.SetOutput(f) + log.SetFlags(log.LstdFlags | log.Lmicroseconds | log.Lshortfile) + }) +} + +// safeGo wraps a goroutine with recover so a panic in any background +// task can't take down the whole Wails app. +func safeGo(name string, fn func()) { + go func() { + defer func() { + if r := recover(); r != nil { + log.Printf("[panic:%s] %v\n%s", name, r, debugStack()) + } + }() + fn() + }() +} + +func debugStack() string { + return string(debug.Stack()) +} + +// startup is called when the app starts. +func (a *App) startup(ctx context.Context) { + a.ctx = ctx +} + +// shutdown is called when the app is closing. Ensure dsh is killed +// so we never leak a child process. +func (a *App) shutdown(ctx context.Context) { + a.stopDSH() +} + +// ---- Types exposed to the frontend ---- + +// CheckResult is returned by CheckEnv. +type CheckResult struct { + NodePath string `json:"nodePath"` + NodeVersion string `json:"nodeVersion"` + NodeOK bool `json:"nodeOK"` + DshPath string `json:"dshPath"` + DshVersion string `json:"dshVersion"` + CredFile string `json:"credFile"` + CredExists bool `json:"credExists"` + HomeDshDir string `json:"homeDshDir"` + PortBusy bool `json:"portBusy"` + PortBusyBy string `json:"portBusyBy"` + Issues []string `json:"issues"` // 始终非 nil,前端可放心访问 +} + +// StartResult is returned by Start. +type StartResult struct { + OK bool `json:"ok"` + URL string `json:"url"` + Error string `json:"error"` + Output string `json:"output"` + Hidden bool `json:"hidden"` // 是否已自动隐藏窗口 +} + +// StatusResult is returned by GetStatus. +type StatusResult struct { + Running bool `json:"running"` + URL string `json:"url"` + StartedAt string `json:"startedAt"` + Pid int `json:"pid"` +} + +// ---- 3 core methods ---- + +// CheckEnv inspects Node, dsh, ~/.dsh credentials and port 3080. +// 所有外部命令都套超时 + panic recover,任何一个失败不能让整个 app 闪退。 +func (a *App) CheckEnv() (res CheckResult) { + defer func() { + if r := recover(); r != nil { + log.Printf("[panic:CheckEnv] %v\n%s", r, debugStack()) + res.Issues = append(res.Issues, fmt.Sprintf("CheckEnv 内部错误: %v", r)) + } + }() + + home := os.Getenv("HOME") + if home == "" { + home = "/tmp" + } + res = CheckResult{ + HomeDshDir: filepath.Join(home, ".dsh"), + CredFile: filepath.Join(home, ".dsh", ".credentials.yaml"), + PortBusy: false, + Issues: []string{}, // 确保 JSON 序列化为 [] 而非 null + } + + // node + res.NodePath = lookPath("node") + if res.NodePath != "" { + if v, err := runQuiet(res.NodePath, "-v"); err == nil { + res.NodeVersion = strings.TrimSpace(v) + if nodeMajor(res.NodeVersion) >= 18 { + res.NodeOK = true + } + } else { + log.Printf("CheckEnv node -v err: %v", err) + } + } + if !res.NodeOK { + res.Issues = append(res.Issues, "Node >= 18 未安装或不可用") + } + + // dsh + res.DshPath = lookPath("dsh") + if res.DshPath == "" { + // fallback to npm prefix bin + if npm, err := runQuiet("npm", "prefix", "-g"); err == nil { + prefix := strings.TrimSpace(npm) + candidate := filepath.Join(prefix, "bin", "dsh") + if _, statErr := os.Stat(candidate); statErr == nil { + res.DshPath = candidate + } + } else { + log.Printf("CheckEnv npm prefix err: %v", err) + } + } + if res.DshPath != "" { + if v, err := runQuiet(res.DshPath, "--version"); err == nil { + res.DshVersion = strings.TrimSpace(v) + } else { + log.Printf("CheckEnv dsh --version err: %v", err) + } + } else { + res.Issues = append(res.Issues, "dsh 命令未找到(请 npm i -g @deepseek-ai/dsh)") + } + + // credentials + if _, err := os.Stat(res.CredFile); err == nil { + res.CredExists = true + } else { + res.Issues = append(res.Issues, "缺少 ~/.dsh/.credentials.yaml,请先 dsh login") + } + + // port 3080 — lsof 在受限进程下可能挂死,超时保护 + if pids, err := lsofPort(3080); err == nil && len(pids) > 0 { + res.PortBusy = true + res.PortBusyBy = strings.Join(pids, ", ") + } + + return res +} + +// Start launches dsh web, captures the token URL and opens the browser. +func (a *App) Start() StartResult { + a.mu.Lock() + if a.running { + a.mu.Unlock() + return StartResult{OK: false, URL: a.dshURL, Error: "已在运行中"} + } + a.mu.Unlock() + + dshBin := lookPath("dsh") + if dshBin == "" { + if npm, err := runQuiet("npm", "prefix", "-g"); err == nil { + candidate := filepath.Join(strings.TrimSpace(npm), "bin", "dsh") + if _, statErr := os.Stat(candidate); statErr == nil { + dshBin = candidate + } + } + } + if dshBin == "" { + return StartResult{OK: false, Error: "找不到 dsh 可执行文件"} + } + + // --no-open 让 dsh 只打印 URL,我们自己用系统 open 打开 + cmd := exec.Command(dshBin, "--profile", "web", "--no-open") + cmd.Env = buildChildEnv() + + stdout, err := cmd.StdoutPipe() + if err != nil { + return StartResult{OK: false, Error: "无法创建 stdout 管道: " + err.Error()} + } + stderr, err := cmd.StderrPipe() + if err != nil { + return StartResult{OK: false, Error: "无法创建 stderr 管道: " + err.Error()} + } + + if err := cmd.Start(); err != nil { + return StartResult{OK: false, Error: "启动 dsh 失败: " + err.Error()} + } + + a.mu.Lock() + a.dshCmd = cmd + a.running = true + a.startedAt = time.Now() + a.dshURL = "" + a.mu.Unlock() + + // 异步读 stdout/stderr,抓 URL + urlCh := make(chan string, 1) + errCh := make(chan string, 1) + safeGo("scanForURL", func() { scanForURL(stdout, urlCh) }) + safeGo("drainStderr", func() { drainStderr(stderr, errCh) }) + + // 监听子进程退出 + safeGo("dshWait", func() { + _ = cmd.Wait() + a.mu.Lock() + a.running = false + a.mu.Unlock() + }) + + // 等 URL(最多 30 秒) + select { + case url := <-urlCh: + a.mu.Lock() + a.dshURL = url + a.mu.Unlock() + // 用系统 open 唤起浏览器 + if openErr := openBrowser(url); openErr != nil { + return StartResult{OK: true, URL: url, Error: "已启动,但自动开浏览器失败: " + openErr.Error()} + } + // 启动成功 + 已打开浏览器 → 最小化到 Dock(用 WindowMinimise 不用 WindowHide, + // 这样点 Dock 图标系统会自动 unminimise 恢复窗口,而不是再也打不开)。 + if a.ctx != nil { + wailsruntime.WindowMinimise(a.ctx) + } + return StartResult{OK: true, URL: url, Hidden: true} + case errMsg := <-errCh: + return StartResult{OK: false, Error: "dsh 输出错误: " + errMsg} + case <-time.After(30 * time.Second): + return StartResult{OK: false, Error: "等待 dsh URL 超时(30s)"} + } +} + +// Stop kills the dsh process and verifies the port is released. +func (a *App) Stop() StartResult { + a.mu.Lock() + cmd := a.dshCmd + a.mu.Unlock() + + if cmd == nil || cmd.Process == nil { + // 没记录,直接 pkill 兜底 + if pkill := lookPath("pkill"); pkill != "" { + _ = exec.Command(pkill, "-f", "dsh/lib/bin.js").Run() + } + } else { + _ = cmd.Process.Kill() + _, _ = cmd.Process.Wait() + } + + // 兜底 pkill,确保所有 dsh 子进程都死 + if pkill := lookPath("pkill"); pkill != "" { + _ = exec.Command(pkill, "-f", "dsh/lib/bin.js").Run() + } + time.Sleep(500 * time.Millisecond) + + a.mu.Lock() + a.dshCmd = nil + a.dshURL = "" + a.running = false + a.mu.Unlock() + + if pids, err := lsofPort(3080); err == nil && len(pids) > 0 { + return StartResult{OK: false, Error: "端口 3080 仍被占用: " + strings.Join(pids, ", ")} + } + return StartResult{OK: true} +} + +// ShowWindow brings the application window to the front. +func (a *App) ShowWindow() { + if a.ctx != nil { + wailsruntime.WindowShow(a.ctx) + } +} + +// HideWindow minimises the application window to Dock. +// 用 WindowMinimise 而不是 WindowHide,这样 macOS 上点 Dock 图标系统会 +// 自动 unminimise 恢复窗口(WindowHide 之后点 Dock 不会自动恢复)。 +func (a *App) HideWindow() { + if a.ctx != nil { + wailsruntime.WindowMinimise(a.ctx) + } +} + +// GetStatus returns the current dsh state. +func (a *App) GetStatus() StatusResult { + a.mu.Lock() + defer a.mu.Unlock() + res := StatusResult{Running: a.running, URL: a.dshURL} + if a.dshCmd != nil && a.dshCmd.Process != nil { + res.Pid = a.dshCmd.Process.Pid + } + if !a.startedAt.IsZero() { + res.StartedAt = a.startedAt.Format(time.RFC3339) + } + return res +} + +// ---- internal helpers ---- + +func (a *App) stopDSH() { + a.mu.Lock() + cmd := a.dshCmd + a.running = false + a.mu.Unlock() + if cmd != nil && cmd.Process != nil { + _ = cmd.Process.Kill() + } + if pkill := lookPath("pkill"); pkill != "" { + _ = exec.Command(pkill, "-f", "dsh/lib/bin.js").Run() + } +} + +// buildChildEnv 手动把 homebrew 路径塞进子进程 PATH。 +// macOS GUI 启动的进程不读 ~/.zshrc,直接拿环境变量 PATH 经常缺 /opt/homebrew/bin。 +// 注意:本函数不能调任何 exec 子命令(包括 npm),否则会触发栈溢出。 +// npm 全局 bin 路径由 cachedNpmPrefix() 在 initNpmPrefix() 中预先算好。 +func buildChildEnv() []string { + env := os.Environ() + home := os.Getenv("HOME") + extra := []string{ + "/opt/homebrew/bin", + "/usr/local/bin", + "/usr/bin", + "/bin", + "/usr/sbin", + "/sbin", + } + // 加上缓存好的 npm 全局 bin(如果有) + if prefix, ok := cachedNpmPrefix(); ok { + extra = append(extra, filepath.Join(prefix, "bin")) + } + + hasPath := false + for i, e := range env { + if strings.HasPrefix(e, "PATH=") { + hasPath = true + cur := strings.TrimPrefix(e, "PATH=") + merged := mergePath(cur, extra) + env[i] = "PATH=" + merged + } + } + if !hasPath { + env = append(env, "PATH="+strings.Join(extra, ":")) + } + if home == "" { + env = append(env, "HOME=/tmp") + } + return env +} + +// mergePath 拼接 PATH。绝对不能调 runQuiet / 任何 exec 子命令, +// 否则会触发 buildChildEnv → mergePath 的无限递归 → 栈溢出。 +// npm 全局 bin 用 sync.Once 懒加载 + 超时兜底,在 initEnvOnce 里完成。 +func mergePath(cur string, extra []string) string { + seen := map[string]bool{} + out := []string{} + add := func(p string) { + if p == "" || seen[p] { + return + } + seen[p] = true + out = append(out, p) + } + for _, p := range strings.Split(cur, ":") { + add(p) + } + for _, p := range extra { + add(p) + } + return strings.Join(out, ":") +} + +// npmPrefixCache 用 sync.Once 缓存 `npm prefix -g` 的输出, +// 避免每次 buildChildEnv 都 exec npm。 +var ( + npmPrefixOnce sync.Once + npmPrefixValue string + npmPrefixReady bool +) + +func cachedNpmPrefix() (string, bool) { + npmPrefixOnce.Do(func() { + // 只在确实有 npm 时才跑 + bin := lookPath("npm") + if bin == "" { + return + } + // 硬超时 2s,失败就算了,不致命。 + // 注意:这里直接 exec 用 os.Environ(),不走 buildChildEnv, + // 否则初始化期间可能再次触发 buildChildEnv 形成循环。 + cmd := exec.Command(bin, "prefix", "-g") + cmd.Env = os.Environ() + var buf bytes.Buffer + cmd.Stdout = &buf + cmd.Stderr = &buf + if err := cmd.Start(); err != nil { + return + } + done := make(chan error, 1) + go func() { done <- cmd.Wait() }() + select { + case <-done: + npmPrefixValue = strings.TrimSpace(buf.String()) + npmPrefixReady = npmPrefixValue != "" + case <-time.After(2 * time.Second): + _ = cmd.Process.Kill() + <-done + } + }) + return npmPrefixValue, npmPrefixReady +} + +func scanForURL(r io.Reader, urlCh chan<- string) { + scanner := bufio.NewScanner(r) + scanner.Buffer(make([]byte, 64*1024), 1024*1024) + // dsh web: http://127.0.0.1:3080/?token=xxx + // 兼容多种格式 + re := regexp.MustCompile(`https?://[^\s)]+token=[A-Za-z0-9._\-]+`) + for scanner.Scan() { + line := scanner.Text() + if m := re.FindString(line); m != "" { + urlCh <- m + return + } + // 兜底:任何 http URL 也接受 + if strings.Contains(line, "http://") || strings.Contains(line, "https://") { + if m := regexp.MustCompile(`https?://[^\s]+`).FindString(line); m != "" { + urlCh <- strings.TrimRight(m, ".,;)") + return + } + } + } +} + +func drainStderr(r io.Reader, errCh chan<- string) { + scanner := bufio.NewScanner(r) + scanner.Buffer(make([]byte, 64*1024), 1024*1024) + for scanner.Scan() { + // 把 stderr 行发给 errCh,Start 收到后会返回 + select { + case errCh <- scanner.Text(): + default: + } + } +} + +func openBrowser(url string) error { + switch runtime.GOOS { + case "darwin": + bin := lookPath("open") + if bin == "" { + bin = "/usr/bin/open" + } + return exec.Command(bin, url).Start() + case "windows": + return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() + default: + bin := lookPath("xdg-open") + if bin == "" { + return fmt.Errorf("xdg-open not found") + } + return exec.Command(bin, url).Start() + } +} + +func lookPath(name string) string { + // 先用 PATH + if p, err := exec.LookPath(name); err == nil { + return p + } + // macOS GUI 进程常缺 PATH,兜底几个常见位置 + for _, dir := range []string{"/opt/homebrew/bin", "/usr/local/bin", "/usr/bin"} { + candidate := filepath.Join(dir, name) + if _, err := os.Stat(candidate); err == nil { + return candidate + } + } + return "" +} + +func runQuiet(name string, args ...string) (string, error) { + return runQuietTimeout(name, 3*time.Second, args...) +} + +// runQuietTimeout runs a command with a hard timeout. If it doesn't finish in time, +// the child is killed and an error is yielded. macOS 上某些 fork-exec 失败场景, +// 没有超时保护会一直 hang,进而拖垮调用方。 +func runQuietTimeout(name string, timeout time.Duration, args ...string) (string, error) { + if name == "" { + return "", fmt.Errorf("empty command") + } + // 用我们自己的 lookPath(GUI 进程 PATH 缺失时也能找到 /opt/homebrew/bin 等), + // 不能用 exec.LookPath —— 它只读系统 PATH,看不到我们手动塞的路径。 + bin := lookPath(name) + if bin == "" { + return "", fmt.Errorf("not found: %s (也没在 /opt/homebrew/bin 找到)", name) + } + cmd := exec.Command(bin, args...) + cmd.Env = buildChildEnv() + // 输出大小限制,防止 npm 输出爆 buffer 卡死 + var buf bytes.Buffer + cmd.Stdout = &buf + cmd.Stderr = &buf + + if err := cmd.Start(); err != nil { + return "", fmt.Errorf("start %s: %w", bin, err) + } + + done := make(chan error, 1) + go func() { done <- cmd.Wait() }() + + select { + case err := <-done: + return buf.String(), err + case <-time.After(timeout): + _ = cmd.Process.Kill() + <-done + return "", fmt.Errorf("timeout after %s: %s %s", timeout, bin, strings.Join(args, " ")) + } +} + +func nodeMajor(v string) int { + v = strings.TrimPrefix(v, "v") + parts := strings.SplitN(v, ".", 2) + if len(parts) == 0 { + return 0 + } + n := 0 + for _, ch := range parts[0] { + if ch < '0' || ch > '9' { + break + } + n = n*10 + int(ch-'0') + } + return n +} + +// lsofPort 返回占用该端口的 PID 列表(macOS)。带超时 + panic recover。 +func lsofPort(port int) (res []string, err error) { + defer func() { + if r := recover(); r != nil { + log.Printf("[panic:lsofPort] %v\n%s", r, debugStack()) + err = fmt.Errorf("lsof panic: %v", r) + } + }() + + lsofBin := lookPath("lsof") + if lsofBin == "" { + return nil, nil // 没 lsof,跳过 + } + cmd := exec.Command(lsofBin, "-nP", fmt.Sprintf("-iTCP:%d", port), "-sTCP:LISTEN", "-t") + cmd.Env = buildChildEnv() + var buf bytes.Buffer + cmd.Stdout = &buf + cmd.Stderr = &buf + + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("lsof start: %w", err) + } + + done := make(chan error, 1) + go func() { done <- cmd.Wait() }() + + select { + case err := <-done: + out := buf.Bytes() + if err != nil && len(out) == 0 { + // lsof 无命中时退出码非 0 + 空输出 = 端口空闲 + return nil, nil + } + for _, l := range strings.Split(strings.TrimSpace(string(out)), "\n") { + l = strings.TrimSpace(l) + if l != "" { + res = append(res, l) + } + } + return res, err + case <-time.After(3 * time.Second): + _ = cmd.Process.Kill() + <-done + return nil, fmt.Errorf("lsof timeout") + } +} + +// ---- Install / Update ---- + +const dshPkg = "@deepseek-ai/dsh" + +// UpdateInfo describes the available update for dsh. +type UpdateInfo struct { + Installed string `json:"installed"` + Latest string `json:"latest"` + HasUpdate bool `json:"hasUpdate"` + Raw string `json:"raw"` +} + +// InstallResult is returned by InstallDsh / UpdateDsh. +type InstallResult struct { + OK bool `json:"ok"` + Error string `json:"error"` + Output string `json:"output"` +} + +// CheckUpdate runs `npm view version` to get the latest version on the registry. +// 不依赖 outdated — outdated 需要本地有 installed record,registry-only 查不到。 +func (a *App) CheckUpdate() UpdateInfo { + installed := "" + if p := lookPath("dsh"); p != "" { + if v, err := runQuietTimeout(p, 5*time.Second, "--version"); err == nil { + installed = strings.TrimSpace(v) + } else { + log.Printf("CheckUpdate dsh --version err: %v", err) + } + } + + latest, raw := latestVersionFromNPM(dshPkg) + + info := UpdateInfo{ + Installed: installed, + Latest: latest, + Raw: raw, + } + if installed != "" && latest != "" && installed != latest { + info.HasUpdate = true + } + return info +} + +// latestVersionFromNPM 用 `npm view version` 取最新版。 +// 带 10 秒超时,带 stderr 捕获以便诊断。 +func latestVersionFromNPM(pkg string) (string, string) { + npmBin := lookPath("npm") + if npmBin == "" { + return "", "npm not found in PATH" + } + cmd := exec.Command(npmBin, "view", pkg, "version", "--registry=https://registry.npmjs.org/") + cmd.Env = buildChildEnv() + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + if err := cmd.Start(); err != nil { + return "", "start npm: " + err.Error() + } + + done := make(chan error, 1) + go func() { done <- cmd.Wait() }() + + select { + case err := <-done: + out := strings.TrimSpace(stdout.String()) + errMsg := strings.TrimSpace(stderr.String()) + if err != nil { + raw := errMsg + if raw == "" { + raw = err.Error() + } + return "", raw + } + // npm 输出可能带换行 / 警告,只取第一行 token + first := out + if i := strings.IndexAny(out, "\n\r"); i >= 0 { + first = strings.TrimSpace(out[:i]) + } + // 清理 npm 有时会附加的 @ 标签 + first = strings.TrimPrefix(first, "@") + return first, out + case <-time.After(15 * time.Second): + _ = cmd.Process.Kill() + <-done + return "", "npm view 超时(15s),请检查网络" + } +} + +// InstallDsh runs `npm install -g @deepseek-ai/dsh` and streams output via Wails events. +// Returns InstallResult once the command exits. +func (a *App) InstallDsh() InstallResult { + return a.runNpmEvent("install", []string{"install", "-g", dshPkg}, "dsh:install:done") +} + +// UpdateDsh runs `npm install -g @deepseek-ai/dsh@latest` and streams output. +func (a *App) UpdateDsh() InstallResult { + return a.runNpmEvent("update", []string{"install", "-g", dshPkg + "@latest"}, "dsh:update:done") +} + +func (a *App) runNpmEvent(phase string, args []string, doneEvent string) InstallResult { + npmBin := lookPath("npm") + if npmBin == "" { + errMsg := "找不到 npm(系统 PATH 和 /opt/homebrew/bin 都没找到)" + if a.ctx != nil { + wailsruntime.EventsEmit(a.ctx, "dsh:"+phase+":log", "✗ "+errMsg+"\n") + wailsruntime.EventsEmit(a.ctx, doneEvent, InstallResult{Error: errMsg}) + } + return InstallResult{Error: errMsg} + } + if a.ctx != nil { + wailsruntime.EventsEmit(a.ctx, "dsh:"+phase+":log", "▶ "+npmBin+" "+strings.Join(args, " ")+"\n") + } + + cmd := exec.Command(npmBin, args...) + cmd.Env = buildChildEnv() + stdout, err := cmd.StdoutPipe() + if err != nil { + return InstallResult{Error: err.Error()} + } + stderr, err := cmd.StderrPipe() + if err != nil { + return InstallResult{Error: err.Error()} + } + if err := cmd.Start(); err != nil { + return InstallResult{Error: "启动 npm 失败: " + err.Error()} + } + + var buf strings.Builder + var mu sync.Mutex + collect := func(line string) { + mu.Lock() + buf.WriteString(line) + buf.WriteString("\n") + mu.Unlock() + if a.ctx != nil { + wailsruntime.EventsEmit(a.ctx, "dsh:"+phase+":log", line+"\n") + } + } + safeGo("scanStdout", func() { scanLines(stdout, collect) }) + safeGo("scanStderr", func() { scanLines(stderr, collect) }) + + waitErr := cmd.Wait() + mu.Lock() + out := buf.String() + mu.Unlock() + + res := InstallResult{Output: out} + if waitErr != nil { + res.Error = waitErr.Error() + } else { + res.OK = true + } + if a.ctx != nil { + wailsruntime.EventsEmit(a.ctx, doneEvent, res) + } + return res +} + +func scanLines(r io.Reader, fn func(string)) { + scanner := bufio.NewScanner(r) + scanner.Buffer(make([]byte, 64*1024), 1024*1024) + for scanner.Scan() { + fn(scanner.Text()) + } +} + diff --git a/build/README.md b/build/README.md new file mode 100644 index 0000000..1ae2f67 --- /dev/null +++ b/build/README.md @@ -0,0 +1,35 @@ +# Build Directory + +The build directory is used to house all the build files and assets for your application. + +The structure is: + +* bin - Output directory +* darwin - macOS specific files +* windows - Windows specific files + +## Mac + +The `darwin` directory holds files specific to Mac builds. +These may be customised and used as part of the build. To return these files to the default state, simply delete them +and +build with `wails build`. + +The directory contains the following files: + +- `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`. +- `Info.dev.plist` - same as the main plist file but used when building using `wails dev`. + +## Windows + +The `windows` directory contains the manifest and rc files used when building with `wails build`. +These may be customised for your application. To return these files to the default state, simply delete them and +build with `wails build`. + +- `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to + use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file + will be created using the `appicon.png` file in the build directory. +- `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`. +- `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer, + as well as the application itself (right click the exe -> properties -> details) +- `wails.exe.manifest` - The main application manifest file. \ No newline at end of file diff --git a/build/appicon.png b/build/appicon.png new file mode 100644 index 0000000..71fa5f4 Binary files /dev/null and b/build/appicon.png differ diff --git a/build/darwin/Info.plist b/build/darwin/Info.plist new file mode 100644 index 0000000..4de2887 --- /dev/null +++ b/build/darwin/Info.plist @@ -0,0 +1,63 @@ + + + + CFBundlePackageType + APPL + CFBundleName + {{.Info.ProductName}} + CFBundleExecutable + {{.OutputFilename}} + CFBundleIdentifier + com.wails.{{safeBundleID .Name}} + CFBundleVersion + {{.Info.ProductVersion}} + CFBundleGetInfoString + {{.Info.Comments}} + CFBundleShortVersionString + {{.Info.ProductVersion}} + CFBundleIconFile + iconfile + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + {{.Info.Copyright}} + {{if .Info.FileAssociations}} + CFBundleDocumentTypes + + {{range .Info.FileAssociations}} + + CFBundleTypeExtensions + + {{.Ext}} + + CFBundleTypeName + {{.Name}} + CFBundleTypeRole + {{.Role}} + CFBundleTypeIconFile + {{.IconName}} + + {{end}} + + {{end}} + {{if .Info.Protocols}} + CFBundleURLTypes + + {{range .Info.Protocols}} + + CFBundleURLName + com.wails.{{.Scheme}} + CFBundleURLSchemes + + {{.Scheme}} + + CFBundleTypeRole + {{.Role}} + + {{end}} + + {{end}} + + diff --git a/build/iconfile.icns b/build/iconfile.icns new file mode 100644 index 0000000..77563fa Binary files /dev/null and b/build/iconfile.icns differ diff --git a/build/windows/icon.ico b/build/windows/icon.ico new file mode 100644 index 0000000..f334798 Binary files /dev/null and b/build/windows/icon.ico differ diff --git a/build/windows/info.json b/build/windows/info.json new file mode 100644 index 0000000..9727946 --- /dev/null +++ b/build/windows/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "{{.Info.ProductVersion}}" + }, + "info": { + "0000": { + "ProductVersion": "{{.Info.ProductVersion}}", + "CompanyName": "{{.Info.CompanyName}}", + "FileDescription": "{{.Info.ProductName}}", + "LegalCopyright": "{{.Info.Copyright}}", + "ProductName": "{{.Info.ProductName}}", + "Comments": "{{.Info.Comments}}" + } + } +} \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..b4719be --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,8 @@ +# Vue 3 + Vite + +This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` + + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..236a8bc --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1392 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "vue": "^3.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.0", + "vite": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.2.tgz", + "integrity": "sha512-Xa6RDoWa+hNiX6PgsljlH6W75RaONx3y6PVlbLhkEWW+GaPQ3dP5gwbL/erAzQHWwkvW5UxdD5l87Qx2FAQ/4A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.2.tgz", + "integrity": "sha512-vNASxsghMfQ5s+v3PrpnJd+ryL/26lxCCaGI+sDJ7VzmHiYXIrrVltsDhaawxLM1WcoMU2oYlbPHLaYQtBzhcg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.2.tgz", + "integrity": "sha512-0dWDjmlrpZAgjPD/aPzUDhBW8APLRjAni5bOrM76wiiZm+E+KTMVKNhAzaTBohz8UyO2fKNAl0+fygbe2HZXOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.2.tgz", + "integrity": "sha512-N58uktcwzk3+qT4KHEuNdIxX1N01RWrkfVoml69EAbSaNDL+sbNVLx2RMl4Qd23lpA0fgPvyh5hHb4weD5WKmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.2.tgz", + "integrity": "sha512-HWF2zH8EAp2scWRpt2PGe6iUGz7zi04waXsdRr3zb4DWCk2ImIo5FZu0jjmD53nP/DGSvnW0e7/1ToCNZs2lZw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.2.tgz", + "integrity": "sha512-MkvcwHMnzPSMOQEwB6wHnLzmc+hT8BGc5bW/Mhmjjgx3wbj6VBnlc47XsK74kD0K9MikFfXpQqyz4NUXaUW62A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.2.tgz", + "integrity": "sha512-xe1bCKPJaKsD0tfd7Rb6bGfUogJTpKbTEEthsfdb7hTfTRNJVQTdirabQx0o6ERVba/smkM720soMY+0QnrlSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.2.tgz", + "integrity": "sha512-yOM7LdK0p6gk6+Q773OEwtlsikT1TL3yMmYsTtRlDRPha5vV2DC5x7LqRWDr6f3cSYNMKVqxzffXv8ivxNBIFQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.2.tgz", + "integrity": "sha512-qiWuJJV3DybA2IfzvRimeKXGrGuVPv1zobSY/26KnP3HbV0VcNb3ECzgvtbvF3xjSMkcooou6HASXZuLdjnhpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.2.tgz", + "integrity": "sha512-akcZquRzCY/KpUoZAMBhGf7oi4LmXq1BzRA5CPAC3rkUf28Y/sAYV3jSL+JKd7cwEyFvR5G0XVZ0gaMedP+60A==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.2.tgz", + "integrity": "sha512-fNwYHrPyYyxauPzX/cpYw8Z7LQpp+DGA0KCoswA0aVFBpmdMil9XgjB8V3Ny64Ihu797+GKcuJqnsOKEmor7fA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.2.tgz", + "integrity": "sha512-XfvsgzR7DZqREdst7K1Mj3ilSUM5xLAHJcIMDFPKdxTs9q5VHOT8aMA+a683fqBu7DQl8+Sd9HCsQYL8EMY9qA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.2.tgz", + "integrity": "sha512-Pp7gVZggEFlbcuztay+/U0gVG9S1XAh8i7I1Re/htbAzo43P5wHZHw6pTyzotISqlKohoh9RpIfnOz3RbemK1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.2.tgz", + "integrity": "sha512-zkgL2xff6i7u5hau/m6FGeS8gRkLEdgLw522WGmdWWlLd9btmNl3S80mcEjtGq+kvgUekQ3+BOYLLLcPlS2LIA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.2.tgz", + "integrity": "sha512-qOheJomrkVCbbHFJ7L3J97cnhfogKqguAQphv26+3ZsAQIF1L19b+dArl//s8rjJHJLz9byykyM8NBP4nmSa1g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.2.tgz", + "integrity": "sha512-XlxLD54wQhH3FciCgMofxBw27NzUe818gJH410qWvc41UT0ZFcgxVjyX5/EK8MPTupjeVWqN5oy+9pCA9mqfCA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.2.tgz", + "integrity": "sha512-vdryWeRb2bLJZf0Fv/W8se6nvsHe2PkTCxV0meheK3nQE+G90VCJcke51Miy1yQRsfm2uqIyjXOu4wmUzbTtkQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.2.tgz", + "integrity": "sha512-bcq2h2pkKmH2po4cZV8VWzO4lL40STyu/nLoFpYMQp9C2tCVNTdcVv86MwSsn3D5s1FBe2Ty1atqvVAUTMimNg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.2.tgz", + "integrity": "sha512-EGoo5DMVMRkTId8fuTDaoxVlR5ZTsKULUezRjd9gCw5eeY+DjCvDpZAOlNUvKPGX+7rS1RWx6j+yOpNPx0cUgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.2.tgz", + "integrity": "sha512-MErl12k7BFHZG1TI9QF/3lSSZARzq9KgNy/FjnqFMCkv+N4RSSzoUCA5h2mqHX4Mox3WaTVKblyzhQ1zRb2ZuQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.2.tgz", + "integrity": "sha512-ILs8k07Wh4p0PsNY4wYLEaXZKMOpVhrG5QDB0yHhGhuzOfDlnyHN6sflL4El/MpUP1y8uY2lUZrv4oBS6pTT3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.2.tgz", + "integrity": "sha512-hKgB3nz/TKD3Wv78XEsyXzQsNjvhOHmwKQTvXADGOyU/cIClZDO7DsoggbdmJDPGp5V80tA3Vfv61PaKTLH3LA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.2.tgz", + "integrity": "sha512-T4wf1mudIDxN8Q/CWIBJC1u5gQUc+r5mPvlwoSbIvNkyVTP2TAFeobEmst5AQ4gMyAz4sSByVdoTDfvTmGK/8g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.2.tgz", + "integrity": "sha512-tC3IY7qoaD9Ll3/8WJQn49j5V2f/NuI9S41NOE2iM5MPs3sPIvOkVToLcz/7Bz4pyF7PSvrtwu8I/pUrGOSecQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.2.tgz", + "integrity": "sha512-6NHnk/K3eq2ZFYcU1X8g67s9qIJRCOTT92gwLMVBp08dB2uuuwI1/Q/empzL2Bfr2f2WRLJVwpp90RmacQyFkw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.8", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.8.tgz", + "integrity": "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.42", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.42.tgz", + "integrity": "sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/shared": "3.5.42", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.42", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.42.tgz", + "integrity": "sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.42", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.42.tgz", + "integrity": "sha512-fkCAFB4okcAANGMThboWnScp/gzWjU0ZSkVnjTIiplmMDq2uq0tIB3j+xVu4rhv5rvOgBySCysudmbMd6xRRqw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/compiler-core": "3.5.42", + "@vue/compiler-dom": "3.5.42", + "@vue/compiler-ssr": "3.5.42", + "@vue/shared": "3.5.42", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.42", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.42.tgz", + "integrity": "sha512-xmLk3wLkbizPAiLyomjgFFosf2ys9b5Ghb+oh/k2tnvipNz8OFrQOiTcWCzyK7MpBp9KkyGtfvgfLUivbmuGYA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.42", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.42.tgz", + "integrity": "sha512-TzNNfKpb7hDxbQltwAut8VDQA5YP+BuRlxntHUuRjyKwlMvmAPbs3unhCvieijifY6vFfVBwsS7wG/C7uq+bEQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.42", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.42.tgz", + "integrity": "sha512-9uACtuHs7vJGkm5Bp3xu4xRDLFTIYy5DgxpToVjqGIAhAEKwQfsaLvKINhM6nFVp6bZPRFGdDqd1g52MqKsotA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.42", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.42.tgz", + "integrity": "sha512-rsCmhiWLaRxGltLwhlCWyYkFn7WAbKRh0q17eZ1A6Dq6eqc2ACQ61IIryxz0LrsvCzHSilLA9JHovVwM8CNE2g==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.42", + "@vue/runtime-core": "3.5.42", + "@vue/shared": "3.5.42", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.42", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.42.tgz", + "integrity": "sha512-2++5dUyYS4gvo7xQXSECUDhB7TS0aOl5SeVfC5qSq1Jgfhjvegw1zqhwTIR3imZ+QYPJQw9gfcFvXGAjGZ7ajQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.42", + "@vue/runtime-dom": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.42", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.42.tgz", + "integrity": "sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.63.2", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.63.2.tgz", + "integrity": "sha512-l5eyksV4tPBj6lJyEa37YzIOCSOV7lkZzEHUdpjWZbtD7wTcFYmEYXSgm5bT4vV+dZLb9rBG1W9GROOG4NS4Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.2", + "@rollup/rollup-android-arm64": "4.63.2", + "@rollup/rollup-darwin-arm64": "4.63.2", + "@rollup/rollup-darwin-x64": "4.63.2", + "@rollup/rollup-freebsd-arm64": "4.63.2", + "@rollup/rollup-freebsd-x64": "4.63.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.2", + "@rollup/rollup-linux-arm-musleabihf": "4.63.2", + "@rollup/rollup-linux-arm64-gnu": "4.63.2", + "@rollup/rollup-linux-arm64-musl": "4.63.2", + "@rollup/rollup-linux-loong64-gnu": "4.63.2", + "@rollup/rollup-linux-loong64-musl": "4.63.2", + "@rollup/rollup-linux-ppc64-gnu": "4.63.2", + "@rollup/rollup-linux-ppc64-musl": "4.63.2", + "@rollup/rollup-linux-riscv64-gnu": "4.63.2", + "@rollup/rollup-linux-riscv64-musl": "4.63.2", + "@rollup/rollup-linux-s390x-gnu": "4.63.2", + "@rollup/rollup-linux-x64-gnu": "4.63.2", + "@rollup/rollup-linux-x64-musl": "4.63.2", + "@rollup/rollup-openbsd-x64": "4.63.2", + "@rollup/rollup-openharmony-arm64": "4.63.2", + "@rollup/rollup-win32-arm64-msvc": "4.63.2", + "@rollup/rollup-win32-ia32-msvc": "4.63.2", + "@rollup/rollup-win32-x64-gnu": "4.63.2", + "@rollup/rollup-win32-x64-msvc": "4.63.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmmirror.com/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.42", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.42.tgz", + "integrity": "sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.42", + "@vue/compiler-sfc": "3.5.42", + "@vue/runtime-dom": "3.5.42", + "@vue/server-renderer": "3.5.42", + "@vue/shared": "3.5.42" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..a34ac1f --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,18 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.0", + "vite": "^7.0.0" + } +} \ No newline at end of file diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 new file mode 100755 index 0000000..96e6058 --- /dev/null +++ b/frontend/package.json.md5 @@ -0,0 +1 @@ +71e741a4044d4a0f11cff668ef3f7ad4 \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..91546c4 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,1063 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/assets/fonts/OFL.txt b/frontend/src/assets/fonts/OFL.txt new file mode 100644 index 0000000..9cac04c --- /dev/null +++ b/frontend/src/assets/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com), + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 b/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 new file mode 100644 index 0000000..2f9cc59 Binary files /dev/null and b/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 differ diff --git a/frontend/src/assets/images/appicon.png b/frontend/src/assets/images/appicon.png new file mode 100644 index 0000000..411779f Binary files /dev/null and b/frontend/src/assets/images/appicon.png differ diff --git a/frontend/src/assets/images/logo-universal.png b/frontend/src/assets/images/logo-universal.png new file mode 100644 index 0000000..e9913c1 Binary files /dev/null and b/frontend/src/assets/images/logo-universal.png differ diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..f9754fe --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,5 @@ +import {createApp} from 'vue' +import App from './App.vue' +import './style.css'; + +createApp(App).mount('#app') diff --git a/frontend/src/style.css b/frontend/src/style.css new file mode 100644 index 0000000..9dd6d9a --- /dev/null +++ b/frontend/src/style.css @@ -0,0 +1 @@ +/* 全局样式交给 App.vue 设计稿 token;此处不引入冲突样式 */ \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..a30c338 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,7 @@ +import {defineConfig} from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()] +}) diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts new file mode 100755 index 0000000..d78b55b --- /dev/null +++ b/frontend/wailsjs/go/main/App.d.ts @@ -0,0 +1,21 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT +import {main} from '../models'; + +export function CheckEnv():Promise; + +export function CheckUpdate():Promise; + +export function GetStatus():Promise; + +export function HideWindow():Promise; + +export function InstallDsh():Promise; + +export function ShowWindow():Promise; + +export function Start():Promise; + +export function Stop():Promise; + +export function UpdateDsh():Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js new file mode 100755 index 0000000..86debee --- /dev/null +++ b/frontend/wailsjs/go/main/App.js @@ -0,0 +1,39 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export function CheckEnv() { + return window['go']['main']['App']['CheckEnv'](); +} + +export function CheckUpdate() { + return window['go']['main']['App']['CheckUpdate'](); +} + +export function GetStatus() { + return window['go']['main']['App']['GetStatus'](); +} + +export function HideWindow() { + return window['go']['main']['App']['HideWindow'](); +} + +export function InstallDsh() { + return window['go']['main']['App']['InstallDsh'](); +} + +export function ShowWindow() { + return window['go']['main']['App']['ShowWindow'](); +} + +export function Start() { + return window['go']['main']['App']['Start'](); +} + +export function Stop() { + return window['go']['main']['App']['Stop'](); +} + +export function UpdateDsh() { + return window['go']['main']['App']['UpdateDsh'](); +} diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts new file mode 100755 index 0000000..557989c --- /dev/null +++ b/frontend/wailsjs/go/models.ts @@ -0,0 +1,109 @@ +export namespace main { + + export class CheckResult { + nodePath: string; + nodeVersion: string; + nodeOK: boolean; + dshPath: string; + dshVersion: string; + credFile: string; + credExists: boolean; + homeDshDir: string; + portBusy: boolean; + portBusyBy: string; + issues: string[]; + + static createFrom(source: any = {}) { + return new CheckResult(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.nodePath = source["nodePath"]; + this.nodeVersion = source["nodeVersion"]; + this.nodeOK = source["nodeOK"]; + this.dshPath = source["dshPath"]; + this.dshVersion = source["dshVersion"]; + this.credFile = source["credFile"]; + this.credExists = source["credExists"]; + this.homeDshDir = source["homeDshDir"]; + this.portBusy = source["portBusy"]; + this.portBusyBy = source["portBusyBy"]; + this.issues = source["issues"]; + } + } + export class InstallResult { + ok: boolean; + error: string; + output: string; + + static createFrom(source: any = {}) { + return new InstallResult(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.ok = source["ok"]; + this.error = source["error"]; + this.output = source["output"]; + } + } + export class StartResult { + ok: boolean; + url: string; + error: string; + output: string; + hidden: boolean; + + static createFrom(source: any = {}) { + return new StartResult(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.ok = source["ok"]; + this.url = source["url"]; + this.error = source["error"]; + this.output = source["output"]; + this.hidden = source["hidden"]; + } + } + export class StatusResult { + running: boolean; + url: string; + startedAt: string; + pid: number; + + static createFrom(source: any = {}) { + return new StatusResult(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.running = source["running"]; + this.url = source["url"]; + this.startedAt = source["startedAt"]; + this.pid = source["pid"]; + } + } + export class UpdateInfo { + installed: string; + latest: string; + hasUpdate: boolean; + raw: string; + + static createFrom(source: any = {}) { + return new UpdateInfo(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.installed = source["installed"]; + this.latest = source["latest"]; + this.hasUpdate = source["hasUpdate"]; + this.raw = source["raw"]; + } + } + +} + diff --git a/frontend/wailsjs/runtime/package.json b/frontend/wailsjs/runtime/package.json new file mode 100644 index 0000000..1e7c8a5 --- /dev/null +++ b/frontend/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/frontend/wailsjs/runtime/runtime.d.ts b/frontend/wailsjs/runtime/runtime.d.ts new file mode 100644 index 0000000..3bbea84 --- /dev/null +++ b/frontend/wailsjs/runtime/runtime.d.ts @@ -0,0 +1,330 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +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 + +// Notification types +export interface NotificationOptions { + id: string; + title: string; + subtitle?: string; // macOS and Linux only + body?: string; + categoryId?: string; + data?: { [key: string]: any }; +} + +export interface NotificationAction { + id?: string; + title?: string; + destructive?: boolean; // macOS-specific +} + +export interface NotificationCategory { + id?: string; + actions?: NotificationAction[]; + hasReplyField?: boolean; + replyPlaceholder?: string; + replyButtonTitle?: string; +} + +// [InitializeNotifications](https://wails.io/docs/reference/runtime/notification#initializenotifications) +// Initializes the notification service for the application. +// This must be called before sending any notifications. +export function InitializeNotifications(): Promise; + +// [CleanupNotifications](https://wails.io/docs/reference/runtime/notification#cleanupnotifications) +// Cleans up notification resources and releases any held connections. +export function CleanupNotifications(): Promise; + +// [IsNotificationAvailable](https://wails.io/docs/reference/runtime/notification#isnotificationavailable) +// Checks if notifications are available on the current platform. +export function IsNotificationAvailable(): Promise; + +// [RequestNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#requestnotificationauthorization) +// Requests notification authorization from the user (macOS only). +export function RequestNotificationAuthorization(): Promise; + +// [CheckNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#checknotificationauthorization) +// Checks the current notification authorization status (macOS only). +export function CheckNotificationAuthorization(): Promise; + +// [SendNotification](https://wails.io/docs/reference/runtime/notification#sendnotification) +// Sends a basic notification with the given options. +export function SendNotification(options: NotificationOptions): Promise; + +// [SendNotificationWithActions](https://wails.io/docs/reference/runtime/notification#sendnotificationwithactions) +// Sends a notification with action buttons. Requires a registered category. +export function SendNotificationWithActions(options: NotificationOptions): Promise; + +// [RegisterNotificationCategory](https://wails.io/docs/reference/runtime/notification#registernotificationcategory) +// Registers a notification category that can be used with SendNotificationWithActions. +export function RegisterNotificationCategory(category: NotificationCategory): Promise; + +// [RemoveNotificationCategory](https://wails.io/docs/reference/runtime/notification#removenotificationcategory) +// Removes a previously registered notification category. +export function RemoveNotificationCategory(categoryId: string): Promise; + +// [RemoveAllPendingNotifications](https://wails.io/docs/reference/runtime/notification#removeallpendingnotifications) +// Removes all pending notifications from the notification center. +export function RemoveAllPendingNotifications(): Promise; + +// [RemovePendingNotification](https://wails.io/docs/reference/runtime/notification#removependingnotification) +// Removes a specific pending notification by its identifier. +export function RemovePendingNotification(identifier: string): Promise; + +// [RemoveAllDeliveredNotifications](https://wails.io/docs/reference/runtime/notification#removealldeliverednotifications) +// Removes all delivered notifications from the notification center. +export function RemoveAllDeliveredNotifications(): Promise; + +// [RemoveDeliveredNotification](https://wails.io/docs/reference/runtime/notification#removedeliverednotification) +// Removes a specific delivered notification by its identifier. +export function RemoveDeliveredNotification(identifier: string): Promise; + +// [RemoveNotification](https://wails.io/docs/reference/runtime/notification#removenotification) +// Removes a notification by its identifier (cross-platform convenience function). +export function RemoveNotification(identifier: string): Promise; \ No newline at end of file diff --git a/frontend/wailsjs/runtime/runtime.js b/frontend/wailsjs/runtime/runtime.js new file mode 100644 index 0000000..556621e --- /dev/null +++ b/frontend/wailsjs/runtime/runtime.js @@ -0,0 +1,298 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +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); +} + +export function InitializeNotifications() { + return window.runtime.InitializeNotifications(); +} + +export function CleanupNotifications() { + return window.runtime.CleanupNotifications(); +} + +export function IsNotificationAvailable() { + return window.runtime.IsNotificationAvailable(); +} + +export function RequestNotificationAuthorization() { + return window.runtime.RequestNotificationAuthorization(); +} + +export function CheckNotificationAuthorization() { + return window.runtime.CheckNotificationAuthorization(); +} + +export function SendNotification(options) { + return window.runtime.SendNotification(options); +} + +export function SendNotificationWithActions(options) { + return window.runtime.SendNotificationWithActions(options); +} + +export function RegisterNotificationCategory(category) { + return window.runtime.RegisterNotificationCategory(category); +} + +export function RemoveNotificationCategory(categoryId) { + return window.runtime.RemoveNotificationCategory(categoryId); +} + +export function RemoveAllPendingNotifications() { + return window.runtime.RemoveAllPendingNotifications(); +} + +export function RemovePendingNotification(identifier) { + return window.runtime.RemovePendingNotification(identifier); +} + +export function RemoveAllDeliveredNotifications() { + return window.runtime.RemoveAllDeliveredNotifications(); +} + +export function RemoveDeliveredNotification(identifier) { + return window.runtime.RemoveDeliveredNotification(identifier); +} + +export function RemoveNotification(identifier) { + return window.runtime.RemoveNotification(identifier); +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d8edf16 --- /dev/null +++ b/go.mod @@ -0,0 +1,38 @@ +module dsh-launcher + +go 1.25.0 + +require github.com/wailsapp/wails/v2 v2.13.0 + +require ( + git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect + github.com/labstack/echo/v4 v4.13.3 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/gosod v1.0.4 // indirect + github.com/leaanthony/slicer v1.6.0 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.49.1 // indirect + github.com/tkrajina/go-reflector v0.5.8 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/wailsapp/go-webview2 v1.0.22 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + golang.org/x/crypto v0.51.0 // indirect + golang.org/x/net v0.54.0 // indirect + golang.org/x/sys v0.44.0 // indirect + golang.org/x/text v0.37.0 // indirect +) + +// replace github.com/wailsapp/wails/v2 v2.13.0 => /Users/jack/go/pkg/mod diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b664a33 --- /dev/null +++ b/go.sum @@ -0,0 +1,83 @@ +git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA= +git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= +github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= +github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI= +github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw= +github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js= +github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ= +github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58= +github.com/wailsapp/go-webview2 v1.0.22/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/wailsapp/wails/v2 v2.13.0 h1:S7OgXWpj72V91unF8iDWJKbcS9ZpwCT3R0QVru4v2Mg= +github.com/wailsapp/wails/v2 v2.13.0/go.mod h1:nVr/wSIEZ7xxKPkzK65mjpKpaOPQI2k4pvLwGR/i4kc= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..5b64281 --- /dev/null +++ b/main.go @@ -0,0 +1,68 @@ +package main + +import ( + "embed" + "fmt" + "os" + + "github.com/wailsapp/wails/v2" + "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" + "github.com/wailsapp/wails/v2/pkg/options/mac" +) + +//go:embed all:frontend/dist +var assets embed.FS + +func main() { + // 调试模式:不启 GUI,直接调 CheckEnv 走完流程,看是不是会崩。 + if len(os.Args) > 1 && os.Args[1] == "--selftest" { + app := NewApp() + fmt.Println("=== CheckEnv ===") + r := app.CheckEnv() + fmt.Printf("nodeOK=%v dshPath=%q dshVer=%q cred=%v portBusy=%v\n", + r.NodeOK, r.DshPath, r.DshVersion, r.CredExists, r.PortBusy) + fmt.Printf("issues=%v\n", r.Issues) + fmt.Println("=== CheckUpdate ===") + u := app.CheckUpdate() + fmt.Printf("installed=%q latest=%q hasUpdate=%v\n", + u.Installed, u.Latest, u.HasUpdate) + fmt.Println("=== done, exiting ===") + return + } + + app := NewApp() + + err := wails.Run(&options.App{ + Title: "dsh-launcher", + Width: 1040, + Height: 1280, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + BackgroundColour: &options.RGBA{R: 17, G: 24, B: 39, A: 1}, + OnStartup: app.startup, + OnShutdown: app.shutdown, + Bind: []interface{}{ + app, + }, + Mac: &mac.Options{ + TitleBar: &mac.TitleBar{ + TitlebarAppearsTransparent: true, + HideTitle: false, + HideTitleBar: false, + FullSizeContent: false, + }, + Appearance: mac.NSAppearanceNameDarkAqua, + WebviewIsTransparent: false, + About: &mac.AboutInfo{ + Title: "dsh-launcher", + Message: "本地按需启动 dsh web UI。", + }, + }, + }) + + if err != nil { + println("Error:", err.Error()) + } +} \ No newline at end of file diff --git a/scripts/build-mac.sh b/scripts/build-mac.sh new file mode 100755 index 0000000..2810336 --- /dev/null +++ b/scripts/build-mac.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Build dsh-launcher.app for macOS (universal arm64/x86_64). +# +# 用法: ./scripts/build-mac.sh +# +# 产物: build/bin/dsh-launcher.app +# 安装: ./scripts/install-app.sh (拷贝到 /Applications/ + 刷新图标缓存) +# +# 注意: Wails v2 跨平台 build 需要在目标平台编译。 +# 想出 Windows 版,必须在 Windows 上跑 scripts/build-windows.bat。 + +set -euo pipefail + +cd "$(dirname "$0")/.." + +echo "▶ dsh-launcher · macOS build" +echo "" + +# 1. 基础工具检查 +command -v go >/dev/null 2>&1 || { echo "✗ go not installed"; exit 1; } +command -v node >/dev/null 2>&1 || { echo "✗ node not installed"; exit 1; } +command -v npm >/dev/null 2>&1 || { echo "✗ npm not installed"; exit 1; } +command -v wails >/dev/null 2>&1 || { echo "✗ wails not installed (go install github.com/wailsapp/wails/v2/cmd/wails@latest)"; exit 1; } + +GOOS=$(go env GOOS) +if [[ "${GOOS}" != "darwin" ]]; then + echo "✗ must run on macOS (current: ${GOOS})" + exit 1 +fi + +# 2. 同步前端依赖(版本变更后才需要,但每次跑更稳) +echo "▶ npm install" +(cd frontend && npm install) + +# 3. 编译 +# -clean: 清掉 build/bin,确保 icns / Info.plist 跟当前源码一致 +# 不带 -platform: 当前平台 + 当前架构 +echo "" +echo "▶ wails build" +wails build -clean + +# 4. 校验产物 +APP="build/bin/dsh-launcher.app" +if [[ ! -d "${APP}" ]]; then + echo "✗ ${APP} not found" + exit 1 +fi +if [[ ! -f "${APP}/Contents/Resources/iconfile.icns" ]]; then + echo "✗ iconfile.icns missing" + exit 1 +fi + +ICON_SIZE=$(stat -f%z "${APP}/Contents/Resources/iconfile.icns") +EXE_SIZE=$(stat -f%z "${APP}/Contents/MacOS/dsh-launcher") +echo "" +echo "✓ app: ${APP}" +echo " binary: ${EXE_SIZE} bytes" +echo " icon: ${ICON_SIZE} bytes" +echo "" +echo " open: open ${APP}" +echo " install: ./scripts/install-app.sh" \ No newline at end of file diff --git a/scripts/build-windows.bat b/scripts/build-windows.bat new file mode 100644 index 0000000..4afbbbb --- /dev/null +++ b/scripts/build-windows.bat @@ -0,0 +1,87 @@ +@echo off +REM Build dsh-launcher.exe for Windows (amd64). +REM +REM 用法: scripts\build-windows.bat +REM +REM 前置依赖(Windows 上装好后): +REM - Go 1.21+ https://go.dev/dl/ +REM - Node.js 18+ https://nodejs.org/ +REM - Wails CLI go install github.com/wailsapp/wails/v2/cmd/wails@latest +REM - WebView2 Runtime Win10 1803+ 自带,否则 https://aka.ms/webview2 +REM - MSVC build tools https://visualstudio.microsoft.com/downloads/ (Build Tools for Visual Studio, 勾选 "Desktop development with C++") +REM +REM 注意: Wails v2 的 Windows build 需要 MSVC toolchain (cgo 编译 webview2 loader)。 +REM 不能在 macOS / Linux 上 cross-compile 出 .exe。 +REM +REM 产物: build\bin\dsh-launcher.exe + +setlocal enabledelayedexpansion + +cd /d "%~dp0\.." + +echo ^>^> dsh-launcher · Windows build +echo. + +REM 1. 基础工具检查 +where go >nul 2>nul +if errorlevel 1 ( + echo [X] go not installed + exit /b 1 +) +where node >nul 2>nul +if errorlevel 1 ( + echo [X] node not installed + exit /b 1 +) +where npm >nul 2>nul +if errorlevel 1 ( + echo [X] npm not installed + exit /b 1 +) +where wails >nul 2>nul +if errorlevel 1 ( + echo [X] wails CLI not installed - run: go install github.com/wailsapp/wails/v2/cmd/wails@latest + exit /b 1 +) + +REM 2. 检查 WINDOWS 环境 +if not "%OS%"=="Windows_NT" ( + echo [X] must run on Windows, current: %OS% + exit /b 1 +) + +REM 3. 同步前端依赖 +echo ^>^> npm install +cd frontend +call npm install +if errorlevel 1 ( + echo [X] npm install failed + exit /b 1 +) +cd .. + +REM 4. 编译 +echo. +echo ^>^> wails build +wails build -clean -platform windows/amd64 +if errorlevel 1 ( + echo [X] wails build failed + exit /b 1 +) + +REM 5. 校验产物 +set EXE=build\bin\dsh-launcher.exe +if not exist "%EXE%" ( + echo [X] %EXE% not found + exit /b 1 +) + +echo. +echo [OK] exe: %EXE% +echo size: %~z1 bytes +echo. +echo run: %EXE% +echo build dir: build\bin\ +echo. + +endlocal \ No newline at end of file diff --git a/scripts/install-app.sh b/scripts/install-app.sh new file mode 100755 index 0000000..db22905 --- /dev/null +++ b/scripts/install-app.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# 装 dsh-launcher.app 到 /Applications/,并强制刷新 macOS 图标。 +# +# 背景: macOS LaunchServices 会把 com.wails.dsh-launcher 的图标缓存成 +# 当前 app 文件的快照。每次 wails build 之后 .app 内容重建,缓存可能不刷新, +# 导致 Dock 显示默认 .app 占位图而不是真实 icns。 +# +# 解决: 把 .app 复制到一个稳定路径(/Applications/),重建时先 rm 再 cp, +# lsregister -f 强制重新注册。 +# +# 用法: ./scripts/install-app.sh + +set -euo pipefail + +APP_NAME="dsh-launcher.app" +SRC="build/bin/${APP_NAME}" +DEST="/Applications/${APP_NAME}" + +if [[ ! -d "${SRC}" ]]; then + echo "✗ ${SRC} not found, run 'wails build' first" + exit 1 +fi + +# 1. 验证 icns 文件是新图 +if [[ ! -f "${SRC}/Contents/Resources/iconfile.icns" ]]; then + echo "✗ iconfile.icns missing — icon wasn't packaged" + exit 1 +fi +ICON_SIZE=$(stat -f%z "${SRC}/Contents/Resources/iconfile.icns") +if [[ ${ICON_SIZE} -lt 100000 ]]; then + echo "⚠ iconfile.icns suspiciously small (${ICON_SIZE} bytes)" +fi +echo "✓ iconfile.icns: ${ICON_SIZE} bytes" + +# 2. 删除旧版(让 lsregister 看到全新路径) +if [[ -d "${DEST}" ]]; then + rm -rf "${DEST}" +fi + +# 3. 复制到 /Applications +cp -R "${SRC}" "${DEST}" +echo "✓ copied to ${DEST}" + +# 4. 强制重新注册到 LaunchServices +LSREGISTER="/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister" +"${LSREGISTER}" -f "${DEST}" + +# 5. touch 一下让文件 mtime 更新 +/usr/bin/touch -c "${DEST}/Contents/Info.plist" +/usr/bin/touch -c "${DEST}/Contents/Resources/iconfile.icns" + +# 6. 清 Dock iconcache +DOCK_CACHE=$(find /var/folders -name "com.apple.dock.iconcache" 2>/dev/null | head -1) +if [[ -n "${DOCK_CACHE}" ]]; then + rm -f "${DOCK_CACHE}" + echo "✓ cleared dock iconcache" +fi + +# 7. 重启 Dock 强制重读 +killall Dock 2>/dev/null || true +sleep 1 + +echo "" +echo "✓ ${APP_NAME} installed at ${DEST}" +echo " open it via Finder / Spotlight / Dock (re-add to Dock if needed)" \ No newline at end of file diff --git a/ui-design/DESIGN-HANDOFF.md b/ui-design/DESIGN-HANDOFF.md new file mode 100644 index 0000000..b6ad9b2 --- /dev/null +++ b/ui-design/DESIGN-HANDOFF.md @@ -0,0 +1,87 @@ +# bfd990a6-69af-455b-a8f4-ba717ca190b9 implementation handoff + +This archive is the source of truth for turning the design into production code. Start from `dsh-launcher-ui.html`, then preserve the visual system, responsive behavior, and interactions found in the exported files. + +## Implementation target +- Build production UI from the exported design, not a loose reinterpretation. +- Preserve typography scale, spacing rhythm, color tokens, border radii, shadows, motion timing, and component states. +- Replace static placeholders only when the target app has real data or functional equivalents. +- Keep generated product UI free of OpenDesign chrome, preview labels, or design-process annotations. +- Treat this handoff as a visual contract: if implementation choices conflict, match the exported pixels and behavior first, then refactor internals. + +## Source map +- Primary entry: `dsh-launcher-ui.html` +- HTML screens detected: 1 +- Stylesheets detected: 0 +- Script/component files detected: 0 +- Supporting assets detected: 1 + +## Responsive contract +Validate the implementation across this 2025–2026 viewport matrix: +- Mobile compact: 360×800 +- Mobile standard: 390×844 +- Mobile large: 430×932 +- Foldable / small tablet: 600×960 +- Tablet portrait: 820×1180 +- Tablet landscape: 1024×768 +- Laptop: 1366×768 +- Desktop: 1440×900 +- Wide desktop: 1920×1080 + +For responsive web exports, treat these as a modern breakpoint system for one adaptive web experience, not three fixed screenshots. Do not split responsive web into unrelated native app screens unless the project explicitly includes native targets. Use semantic layout thresholds, fluid `clamp()` type/spacing, and container queries where component width matters more than viewport width. Preserve any CSS media queries, container queries, fluid `clamp()` scales, and layout changes already present in the exported files. + +## Design fidelity contract +- Extract reusable tokens before writing components: background, surface, foreground, muted text, border, accent, radius, shadow, spacing, type scale, and motion duration/easing. +- Map product screens, in-app modules/components, optional landing page, and optional OS widget surfaces before coding. Keep these surfaces separate in the target architecture. +- Match layout geometry: max-widths, gutters, grid columns, card proportions, sticky/fixed elements, and viewport-specific navigation. +- Preserve real copy, labels, and data shown in the export. Do not replace specific text with generic marketing filler. +- Preserve interactive affordances: hover, focus, pressed, disabled, loading, validation, copy/share, tab/accordion, modal/sheet, and keyboard states where present. +- Preserve accessibility semantics when converting: headings stay hierarchical, controls remain buttons/links/inputs, focus states stay visible. +- Do not keep prototype-only annotations, frame labels, or OpenDesign chrome in the production UI. + +## CJX-ready UX contract +- Use `DESIGN-MANIFEST.json` as the machine-readable map for screens, app modules, OS widgets, landing pages, tokens, interactions, and viewport checks. +- Screen-file-first: when multiple user-facing surfaces exist, implement each HTML screen as its own route/file. Treat `index.html` as a launcher/overview when the manifest marks it that way, not as a combined final UI. +- If `landing.html`, app screens, platform screens, or OS widget files exist, preserve those boundaries in the target app instead of merging them into one page. +- A single self-contained `dsh-launcher-ui.html` is acceptable only when the export truly contains one user-facing screen and its CSS/JS are structured enough to extract tokens, components, states, and behavior. +- If separate `css/` or `js/` files exist, treat them as source of truth for token/component/interactions before porting to React, Vue, SwiftUI, Compose, or another target stack. +- In-app modules/components are product UI blocks inside the app. OS widgets are home-screen/lock-screen/quick-access surfaces outside the app. Do not merge those concepts. + +## Color and brand contract +- Use the exported design tokens and product/domain context as the color source of truth. +- Do not introduce warm beige / cream / peach / pink / orange-brown background washes unless they are already explicit brand/reference colors in the export. +- No obvious token stylesheet was detected; sample colors from the entry file and convert them into named tokens before coding. + +## Implementation sequence for AI coding tools +1. Open `dsh-launcher-ui.html` and `DESIGN-MANIFEST.json`; identify every screen file, launcher/overview file, app module, and interaction before coding. +2. If multiple HTML screens exist, map them to separate routes/surfaces first; do not merge `landing.html`, product app screens, platform screens, or OS widgets into one route. +3. Extract a token table from CSS/root styles and inline styles before building framework components. +4. Build product screens and domain-specific in-app modules from largest layout regions down to controls; avoid starting with isolated atoms that lose spatial intent. +5. Port responsive behavior across the modern viewport matrix and test each semantic breakpoint before cleanup. +6. Port interactions and states, then replace static placeholders only with real app data or functional equivalents. +7. Keep optional landing page and OS widget surfaces as separate surfaces if present. +8. Compare final screenshots against the export at 360×800, 390×844, 430×932, 820×1180, 1024×768, 1366×768, 1440×900, and 1920×1080 before declaring done. + +## Entry points +- `dsh-launcher-ui.html` + +## Styles +- None detected + +## Scripts/components +- None detected + +## Assets and supporting files +- `assets/appicon.png` + +## Coding checklist for AI tools +1. Inspect `dsh-launcher-ui.html` and `DESIGN-MANIFEST.json` first and identify reusable components before coding. +2. Implement each user-facing screen file as its own route/surface; keep launcher, landing, app, platform, and OS widget files separate. +3. Extract design tokens into the target stack: colors, type scale, spacing, radius, shadows, and motion. +4. Implement layout with real 2025–2026 responsive breakpoints, fluid type/spacing, and container-query-aware component behavior; test with no horizontal overflow. +5. Preserve interactive controls, hover/focus/pressed states, form behavior, validation, and copy actions where present. +6. Implement domain-specific in-app modules with real states; do not flatten them into generic cards. +7. Keep landing page, product screens, and OS widget/quick-access surfaces separate when present. +8. Confirm the production result visually matches the exported design before refactoring internals. +9. Reject implementation shortcuts that flatten the design into generic cards, generic gradients, placeholder stats, or framework-default typography. +10. If a detail is ambiguous, keep the exported HTML/CSS/JS behavior rather than inventing a new pattern. diff --git a/ui-design/DESIGN-MANIFEST.json b/ui-design/DESIGN-MANIFEST.json new file mode 100644 index 0000000..de1436e --- /dev/null +++ b/ui-design/DESIGN-MANIFEST.json @@ -0,0 +1,171 @@ +{ + "schema": "open-design.design-manifest.v1", + "title": "bfd990a6-69af-455b-a8f4-ba717ca190b9", + "entryFile": "dsh-launcher-ui.html", + "sourceFiles": { + "all": [ + "assets/appicon.png", + "dsh-launcher-ui.html" + ], + "html": [ + "dsh-launcher-ui.html" + ], + "css": [], + "scriptsAndComponents": [], + "assets": [ + "assets/appicon.png" + ] + }, + "screens": [ + { + "file": "dsh-launcher-ui.html", + "role": "screen", + "implementationNote": "Preserve visual hierarchy, responsive behavior, and interactive states from this screen." + } + ], + "screenFilePolicy": { + "mode": "screen-file-first", + "entryFileRole": "primary-screen", + "rules": [ + "Each distinct user-facing screen or surface must be delivered and implemented as its own file/route.", + "If a landing page is present or requested, keep it in landing.html and do not merge it into the product app screen.", + "When multiple HTML screens exist, index.html is a launcher/overview only; it must not be treated as the combined final UI.", + "Keep product app screens, landing pages, platform screens, and OS widget surfaces separate in production code." + ] + }, + "appModules": [ + "Identify domain-specific in-app modules from the exported UI; do not reduce them to generic cards.", + "For each major module, implement purpose, default/loading/empty/error/success states, and responsive behavior.", + "Keep app modules separate from OS home-screen widgets in the production component model." + ], + "osWidgets": [ + "If the export includes home-screen, lock-screen, Live Activity, tablet glance, or Android widget surfaces, implement them as platform quick-access surfaces outside the app UI.", + "If none are present, do not invent OS widgets unless the product requirements request them." + ], + "landingPage": { + "detection": "Inspect files and screen names for a marketing/landing page surface. If present, keep it separate from product app screens.", + "requiredSections": [ + "hero", + "value props", + "product proof/screenshots", + "feature proof", + "CTA" + ] + }, + "tokens": { + "source": [ + "dsh-launcher-ui.html" + ], + "required": [ + "background", + "surface", + "foreground", + "muted text", + "border", + "accent", + "radius", + "shadow", + "spacing", + "type scale", + "motion" + ], + "note": "Extract/freeze tokens before framework implementation so coding tools do not substitute default theme colors or typography." + }, + "interactions": { + "source": [ + "dsh-launcher-ui.html" + ], + "requiredStates": [ + "default", + "hover", + "focus", + "active", + "disabled", + "loading", + "empty", + "error", + "success" + ], + "requiredBehaviors": [ + "forms/validation where present", + "tabs/filters where present", + "dialogs/sheets/drawers where present", + "copy/generate/share actions where present", + "player or quick controls where present" + ], + "note": "If the prototype is static, derive missing behavior from visible controls and document it before coding." + }, + "responsiveViewports": [ + { + "name": "mobile-compact", + "width": 360, + "height": 800, + "category": "mobile", + "mustAvoidHorizontalScroll": true + }, + { + "name": "mobile-standard", + "width": 390, + "height": 844, + "category": "mobile", + "mustAvoidHorizontalScroll": true + }, + { + "name": "mobile-large", + "width": 430, + "height": 932, + "category": "mobile", + "mustAvoidHorizontalScroll": true + }, + { + "name": "foldable-small-tablet", + "width": 600, + "height": 960, + "category": "foldable-tablet", + "mustAvoidHorizontalScroll": true + }, + { + "name": "tablet-portrait", + "width": 820, + "height": 1180, + "category": "tablet", + "mustAvoidHorizontalScroll": true + }, + { + "name": "tablet-landscape", + "width": 1024, + "height": 768, + "category": "tablet", + "mustAvoidHorizontalScroll": true + }, + { + "name": "laptop", + "width": 1366, + "height": 768, + "category": "desktop", + "mustAvoidHorizontalScroll": true + }, + { + "name": "desktop", + "width": 1440, + "height": 900, + "category": "desktop", + "mustAvoidHorizontalScroll": true + }, + { + "name": "wide", + "width": 1920, + "height": 1080, + "category": "wide", + "mustAvoidHorizontalScroll": true + } + ], + "implementationChecklist": [ + "Open entryFile first and map screens, modules, tokens, and interactions.", + "Extract tokens before writing framework components.", + "Implement app-specific modules with real states instead of generic card grids.", + "Preserve or rebuild JS interactions for meaningful UX actions.", + "Validate screenshots at desktop/tablet/mobile viewports with no horizontal overflow.", + "Keep landing pages, in-app modules, and OS widgets as separate implementation surfaces." + ] +} \ No newline at end of file diff --git a/ui-design/assets/appicon.png b/ui-design/assets/appicon.png new file mode 100644 index 0000000..411779f Binary files /dev/null and b/ui-design/assets/appicon.png differ diff --git a/ui-design/dsh-launcher-ui.html b/ui-design/dsh-launcher-ui.html new file mode 100644 index 0000000..3857eff --- /dev/null +++ b/ui-design/dsh-launcher-ui.html @@ -0,0 +1,707 @@ + + + + + + + dsh-launcher + + + +
+ +
+ dsh-launcher +
+ +
+
+ + +
+ + +
+ +
+ 本地启动器 +

dsh web UI

+

检查环境 · 启动本地服务
自动打开浏览器,启动后窗口隐藏到状态栏。

+ + + 未运行 + +
+
+ + +
+
+ + +
+
+ 环境检查 + +
+
+
+
+ + + + +
+
+ Node.js + v20.14.0 · /opt/homebrew/bin/node +
+
+
+
+ + + +
+
+ dsh CLI + 0.5.2 · /opt/homebrew/bin/dsh +
+
+
+
+ + + + +
+
+ 登录凭证 + ~/.dsh/.credentials.yaml +
+
+
+
+ + + + +
+
+ 端口 3080 + 空闲 +
+
+
+ + + +
+ + + + + +
+
+ 输出日志 +
+ +
+
+
+
+ 12:42:08 + 环境检查通过 · node v20.14.0 · dsh 0.5.2 · 端口 3080 空闲 +
+ +
+
+ + + +
+
+ + + + \ No newline at end of file diff --git a/wails.json b/wails.json new file mode 100644 index 0000000..6b4aa00 --- /dev/null +++ b/wails.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://wails.io/schemas/config.v2.json", + "name": "dsh-launcher", + "outputfilename": "dsh-launcher", + "frontend:install": "npm install", + "frontend:build": "npm run build", + "frontend:dev:watcher": "npm run dev", + "frontend:dev:serverUrl": "auto", + "author": { + "name": "Jack", + "email": "jack0210_@hotmail.com" + } +}