diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..80327f6 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,201 @@ +name: Build + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +permissions: + contents: write + +jobs: + build-windows: + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Get version + id: version + shell: bash + run: | + if [ "${{ github.ref_type }}" = "tag" ]; then + echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT + else + echo "version=dev-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + fi + + - name: Install Wails + run: go install github.com/wailsapp/wails/v2/cmd/wails@latest + + - name: Build + run: | + wails build ` + -platform windows/amd64 ` + -ldflags "-X webhelper/global.Version=${{ steps.version.outputs.version }}" ` + -o webhelper-windows-amd64.exe + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: webhelper-windows-amd64 + path: build/bin/webhelper-windows-amd64.exe + + build-linux: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Get version + id: version + run: | + if [ "${{ github.ref_type }}" = "tag" ]; then + echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT + else + echo "version=dev-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + fi + + - name: Install Linux dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libgtk-3-dev \ + libwebkit2gtk-4.0-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev \ + patchelf + + - name: Install Wails + run: go install github.com/wailsapp/wails/v2/cmd/wails@latest + + - name: Build amd64 + env: + CGO_ENABLED: 1 + GOOS: linux + GOARCH: amd64 + run: | + wails build \ + -platform linux/amd64 \ + -ldflags "-X webhelper/global.Version=${{ steps.version.outputs.version }}" \ + -o webhelper-linux-amd64 + + - name: Build arm64 + env: + CGO_ENABLED: 0 + GOOS: linux + GOARCH: arm64 + run: | + wails build \ + -platform linux/arm64 \ + -ldflags "-X webhelper/global.Version=${{ steps.version.outputs.version }}" \ + -o webhelper-linux-arm64 + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + path: | + build/bin/webhelper-linux-amd64 + build/bin/webhelper-linux-arm64 + + build-macos: + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Get version + id: version + run: | + if [ "${{ github.ref_type }}" = "tag" ]; then + echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT + else + echo "version=dev-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + fi + + - name: Install Wails + run: go install github.com/wailsapp/wails/v2/cmd/wails@latest + + - name: Build amd64 + env: + CGO_ENABLED: 1 + GOOS: darwin + GOARCH: amd64 + run: | + wails build \ + -platform darwin/amd64 \ + -ldflags "-X webhelper/global.Version=${{ steps.version.outputs.version }}" \ + -o webhelper-darwin-amd64 + + - name: Build arm64 + env: + CGO_ENABLED: 1 + GOOS: darwin + GOARCH: arm64 + run: | + wails build \ + -platform darwin/arm64 \ + -ldflags "-X webhelper/global.Version=${{ steps.version.outputs.version }}" \ + -o webhelper-darwin-arm64 + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + path: | + build/bin/webhelper-darwin-amd64 + build/bin/webhelper-darwin-arm64 + + release: + needs: [build-windows, build-linux, build-macos] + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true + + - name: Create release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true + files: artifacts/* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e04fcd --- /dev/null +++ b/.gitignore @@ -0,0 +1,63 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Code coverage profiles and other test artifacts +*.out +coverage.* +*.coverprofile +profile.cov + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# env file +.env + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo + +.workbuddy/ +.codebuddy/ + diff --git a/README.md b/README.md new file mode 100644 index 0000000..e7e666d --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +# WebHelper + +基于 Wails v2 + Vue 3 + Element Plus 的网页调试助手桌面应用。 + +## 功能 + +- **网页调试** — 发送 HTTP 请求,支持所有方法、请求头、Cookie、代理、文件上传、请求日志 +- **JS 调试** — 内置 JavaScript 执行引擎,支持 console.log 捕获和返回值 +- **JSON 解析** — 树形/表格/原始三种视图,支持从网页调试一键发送 +- **Socket 调试** — WebSocket/TCP 连接、收发消息、实时日志 +- **多语言** — 内置简体中文、English、日本語、한국어,运行时切换 + +## 技术栈 + +| 层级 | 技术 | +|------|------| +| 后端 | Go + Wails v2 + goja (JS引擎) + gorilla/websocket | +| 前端 | Vue 3 + Element Plus + Vite | +| 国际化 | 自定义 i18n,Go 后端加载,`go:embed` 打包 | + +## 环境要求 + +- [Go](https://go.dev/dl/) >= 1.23 +- [Node.js](https://nodejs.org/) >= 18 +- [Wails CLI](https://wails.io/docs/gettingstarted/installation) v2 + +```bash +go install github.com/wailsapp/wails/v2/cmd/wails@latest +``` + +## 运行 + +```bash +# 安装依赖 +cd frontend && npm install && cd .. + +# 开发模式 +wails dev -tags debug + +# 构建生产版本 +wails build +``` + +## 构建指定版本 + +```bash +wails build -ldflags "-X webhelper/global.Version=v1.0.0" +``` + +## 项目结构 + +``` +├── api/ # API 层 (HTTP/JS/Socket/Log) +├── global/ # 全局配置、日志、语言包、版本号 +├── lang/ # 语言包 (go:embed 打包) +│ ├── default/ # 简体中文 +│ ├── en/ # English +│ ├── ja/ # 日本語 +│ └── ko/ # 한국어 +├── logger/ # 日志系统 +├── service/ # Wails 绑定方法 +├── frontend/ # Vue 3 前端 +│ └── src/components/ # 5 个功能页面 +├── .github/workflows/ # CI/CD 自动构建 +├── main.go # 程序入口 +└── wails.json +``` + +## 添加语言 + +在 `lang/` 下新建目录,包含 `info.json` 和 `textmap.json`: + +```json +// lang/ja/info.json +{ + "language_name": "日本語", + "language_code": "ja-JP", + "textmap_path": "textmap.json", + "translation_progress": "100%", + "translator": "", + "last_updated": "", + "version": "1.0.0" +} +``` + +重启后自动出现在语言切换列表中。 + +## CI/CD + +推送 `v*` 标签自动构建多平台版本并发布 Release: + +```bash +git tag v1.0.0 +git push origin v1.0.0 +``` + +构建目标:Windows (amd64)、Linux (amd64/arm64)、macOS (amd64/arm64) + +## License + +GPL-3.0 diff --git a/api/http.go b/api/http.go new file mode 100644 index 0000000..ee8ca6c --- /dev/null +++ b/api/http.go @@ -0,0 +1,495 @@ +package api + +import ( + "bytes" + "crypto/tls" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/http/cookiejar" + "net/url" + "os" + "strings" + "time" +) + +// HttpRequest 请求结构体 +type HttpRequest struct { + Method string `json:"method"` // HTTP 方法 + URL string `json:"url"` // 请求地址 + Headers map[string]string `json:"headers"` // 请求头 + Cookies map[string]string `json:"cookies"` // Cookie + Proxy string `json:"proxy"` // 代理地址 + BodyType string `json:"bodyType"` // 数据类型: text, file, form, json + Body string `json:"body"` // 文本数据 + FilePath string `json:"filePath"` // 文件路径 + ContentType string `json:"contentType"` // Content-Type + Timeout int `json:"timeout"` // 超时时间(秒) + Insecure bool `json:"insecure"` // 忽略证书验证 +} + +// HttpResponse 响应结构体 +type HttpResponse struct { + StatusCode int `json:"statusCode"` // 状态码 + Status string `json:"status"` // 状态描述 + Headers map[string]string `json:"headers"` // 响应头 + Cookies []CookieInfo `json:"cookies"` // 响应 Cookie + Body string `json:"body"` // 响应正文 + ContentType string `json:"contentType"` // Content-Type + ContentLength int64 `json:"contentLength"` // 内容长度 + Time string `json:"time"` // 请求耗时 + Size int64 `json:"size"` // 响应大小 + Error string `json:"error"` // 错误信息 +} + +// CookieInfo Cookie 信息 +type CookieInfo struct { + Name string `json:"name"` + Value string `json:"value"` + Path string `json:"path"` + Domain string `json:"domain"` + Expires string `json:"expires"` + MaxAge int `json:"maxAge"` + Secure bool `json:"secure"` + HttpOnly bool `json:"httpOnly"` + SameSite string `json:"sameSite"` +} + +// FileData 文件数据 +type FileData struct { + FieldName string `json:"fieldName"` // 字段名 + FileName string `json:"fileName"` // 文件名 + ContentType string `json:"contentType"` // 文件类型 + Path string `json:"path"` // 文件路径 +} + +// SendRequest 发送 HTTP 请求 +func SendRequest(req *HttpRequest) *HttpResponse { + resp := &HttpResponse{ + Headers: make(map[string]string), + } + + if req.URL == "" { + resp.Error = "请求地址不能为空" + return resp + } + + // 补全协议 + if !strings.HasPrefix(req.URL, "http://") && !strings.HasPrefix(req.URL, "https://") { + req.URL = "https://" + req.URL + } + + // 设置超时 + timeout := time.Duration(req.Timeout) * time.Second + if timeout == 0 { + timeout = 30 * time.Second + } + + // 创建 HTTP 客户端 + client := &http.Client{ + Timeout: timeout, + } + + // 配置代理 + if req.Proxy != "" { + proxyURL, err := url.Parse(req.Proxy) + if err != nil { + resp.Error = fmt.Sprintf("代理地址格式错误: %v", err) + return resp + } + client.Transport = &http.Transport{ + Proxy: http.ProxyURL(proxyURL), + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: req.Insecure, + }, + } + } else if req.Insecure { + client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + } + + // 创建请求体 + var bodyReader io.Reader + contentType := req.ContentType + + if req.Method != http.MethodGet && req.Method != http.MethodHead { + switch req.BodyType { + case "file": + // 文件上传 + if req.FilePath != "" { + file, err := os.Open(req.FilePath) + if err != nil { + resp.Error = fmt.Sprintf("打开文件失败: %v", err) + return resp + } + defer file.Close() + + // 获取文件信息 + fileInfo, err := file.Stat() + if err != nil { + resp.Error = fmt.Sprintf("获取文件信息失败: %v", err) + return resp + } + + // 使用 multipart 上传 + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("file", fileInfo.Name()) + if err != nil { + resp.Error = fmt.Sprintf("创建表单文件失败: %v", err) + return resp + } + if _, err := io.Copy(part, file); err != nil { + resp.Error = fmt.Sprintf("复制文件内容失败: %v", err) + return resp + } + writer.Close() + bodyReader = body + contentType = writer.FormDataContentType() + } + + case "form": + // 表单数据 + if req.Body != "" { + bodyReader = strings.NewReader(req.Body) + if contentType == "" { + contentType = "application/x-www-form-urlencoded" + } + } + + case "json": + // JSON 数据 + if req.Body != "" { + bodyReader = strings.NewReader(req.Body) + if contentType == "" { + contentType = "application/json" + } + } + + default: + // 文本数据 + if req.Body != "" { + bodyReader = strings.NewReader(req.Body) + if contentType == "" { + contentType = "text/plain" + } + } + } + } + + // 创建请求 + httpReq, err := http.NewRequest(req.Method, req.URL, bodyReader) + if err != nil { + resp.Error = fmt.Sprintf("创建请求失败: %v", err) + return resp + } + + // 设置 Content-Type + if contentType != "" { + httpReq.Header.Set("Content-Type", contentType) + } + + // 设置自定义请求头(需要单独处理 host) + for key, value := range req.Headers { + if strings.ToLower(key) == "host" { + httpReq.Host = value + } else { + httpReq.Header.Set(key, value) + } + } + + // 设置 Cookie + if len(req.Cookies) > 0 { + cookieJar, _ := cookiejar.New(nil) + var cookieList []*http.Cookie + for name, value := range req.Cookies { + cookieList = append(cookieList, &http.Cookie{ + Name: name, + Value: value, + }) + } + parsedURL, _ := url.Parse(req.URL) + cookieJar.SetCookies(parsedURL, cookieList) + client.Jar = cookieJar + } + + // 记录开始时间 + start := time.Now() + + // 发送请求 + httpResp, err := client.Do(httpReq) + if err != nil { + resp.Error = fmt.Sprintf("请求失败: %v", err) + return resp + } + defer httpResp.Body.Close() + + // 记录耗时 + resp.Time = time.Since(start).String() + + // 读取响应体 + body, err := io.ReadAll(httpResp.Body) + if err != nil { + resp.Error = fmt.Sprintf("读取响应失败: %v", err) + return resp + } + + // 设置响应 + resp.StatusCode = httpResp.StatusCode + resp.Status = httpResp.Status + resp.Body = string(body) + resp.ContentLength = httpResp.ContentLength + resp.Size = int64(len(body)) + + // 获取 Content-Type + resp.ContentType = httpResp.Header.Get("Content-Type") + + // 获取响应头 + for key, values := range httpResp.Header { + resp.Headers[key] = strings.Join(values, "; ") + } + + // 获取响应 Cookie + resp.Cookies = convertCookies(httpResp.Cookies()) + + return resp +} + +// SendRequestWithFiles 带文件的请求 +func SendRequestWithFiles(req *HttpRequest, files []FileData) *HttpResponse { + resp := &HttpResponse{ + Headers: make(map[string]string), + } + + if req.URL == "" { + resp.Error = "请求地址不能为空" + return resp + } + + // 补全协议 + if !strings.HasPrefix(req.URL, "http://") && !strings.HasPrefix(req.URL, "https://") { + req.URL = "https://" + req.URL + } + + // 设置超时 + timeout := time.Duration(req.Timeout) * time.Second + if timeout == 0 { + timeout = 30 * time.Second + } + + // 创建 HTTP 客户端 + client := &http.Client{ + Timeout: timeout, + } + + // 配置代理 + if req.Proxy != "" { + proxyURL, err := url.Parse(req.Proxy) + if err != nil { + resp.Error = fmt.Sprintf("代理地址格式错误: %v", err) + return resp + } + client.Transport = &http.Transport{ + Proxy: http.ProxyURL(proxyURL), + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: req.Insecure, + }, + } + } else if req.Insecure { + client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + } + + // 创建 multipart 请求 + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + // 添加文本字段 + if req.Body != "" { + _ = writer.WriteField("text", req.Body) + } + + // 添加文件 + for _, file := range files { + f, err := os.Open(file.Path) + if err != nil { + resp.Error = fmt.Sprintf("打开文件 %s 失败: %v", file.Path, err) + return resp + } + defer f.Close() + + part, err := writer.CreateFormFile(file.FieldName, file.FileName) + if err != nil { + resp.Error = fmt.Sprintf("创建文件字段失败: %v", err) + return resp + } + + if _, err := io.Copy(part, f); err != nil { + resp.Error = fmt.Sprintf("复制文件内容失败: %v", err) + return resp + } + } + + writer.Close() + + // 创建请求 + httpReq, err := http.NewRequest(req.Method, req.URL, body) + if err != nil { + resp.Error = fmt.Sprintf("创建请求失败: %v", err) + return resp + } + + // 设置 Content-Type + httpReq.Header.Set("Content-Type", writer.FormDataContentType()) + + // 设置自定义请求头(需要单独处理 host) + for key, value := range req.Headers { + if strings.ToLower(key) == "host" { + httpReq.Host = value + } else { + httpReq.Header.Set(key, value) + } + } + + // 设置 Cookie + if len(req.Cookies) > 0 { + cookieJar, _ := cookiejar.New(nil) + var cookieList []*http.Cookie + for name, value := range req.Cookies { + cookieList = append(cookieList, &http.Cookie{ + Name: name, + Value: value, + }) + } + parsedURL, _ := url.Parse(req.URL) + cookieJar.SetCookies(parsedURL, cookieList) + client.Jar = cookieJar + } + + // 记录开始时间 + start := time.Now() + + // 发送请求 + httpResp, err := client.Do(httpReq) + if err != nil { + resp.Error = fmt.Sprintf("请求失败: %v", err) + return resp + } + defer httpResp.Body.Close() + + // 记录耗时 + resp.Time = time.Since(start).String() + + // 读取响应体 + respBody, err := io.ReadAll(httpResp.Body) + if err != nil { + resp.Error = fmt.Sprintf("读取响应失败: %v", err) + return resp + } + + // 设置响应 + resp.StatusCode = httpResp.StatusCode + resp.Status = httpResp.Status + resp.Body = string(respBody) + resp.ContentLength = httpResp.ContentLength + resp.Size = int64(len(respBody)) + + // 获取 Content-Type + resp.ContentType = httpResp.Header.Get("Content-Type") + + // 获取响应头 + for key, values := range httpResp.Header { + resp.Headers[key] = strings.Join(values, "; ") + } + + // 获取响应 Cookie + resp.Cookies = convertCookies(httpResp.Cookies()) + + return resp +} + +// ParseHeaders 解析请求头字符串 +func ParseHeaders(headerStr string) map[string]string { + headers := make(map[string]string) + if headerStr == "" { + return headers + } + + lines := strings.Split(headerStr, "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + + parts := strings.SplitN(line, ":", 2) + if len(parts) == 2 { + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + headers[key] = value + } + } + + return headers +} + +// ParseCookies 解析 Cookie 字符串 +func ParseCookies(cookieStr string) map[string]string { + cookies := make(map[string]string) + if cookieStr == "" { + return cookies + } + + pairs := strings.Split(cookieStr, ";") + for _, pair := range pairs { + pair = strings.TrimSpace(pair) + if pair == "" { + continue + } + + parts := strings.SplitN(pair, "=", 2) + if len(parts) == 2 { + name := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + cookies[name] = value + } + } + + return cookies +} + +// convertCookies 将 http.Cookie 转换为 CookieInfo +func convertCookies(httpCookies []*http.Cookie) []CookieInfo { + var cookies []CookieInfo + for _, c := range httpCookies { + sameSite := "" + switch c.SameSite { + case http.SameSiteStrictMode: + sameSite = "Strict" + case http.SameSiteLaxMode: + sameSite = "Lax" + case http.SameSiteNoneMode: + sameSite = "None" + } + + cookies = append(cookies, CookieInfo{ + Name: c.Name, + Value: c.Value, + Path: c.Path, + Domain: c.Domain, + Expires: c.Expires.Format(time.RFC1123), + MaxAge: c.MaxAge, + Secure: c.Secure, + HttpOnly: c.HttpOnly, + SameSite: sameSite, + }) + } + return cookies +} diff --git a/api/js.go b/api/js.go new file mode 100644 index 0000000..b91834a --- /dev/null +++ b/api/js.go @@ -0,0 +1,176 @@ +package api + +import ( + "fmt" + "strings" + "time" + + "github.com/dop251/goja" +) + +// JsExecResult JS 执行结果 +type JsExecResult struct { + Output string `json:"output"` // 控制台输出 + Result string `json:"result"` // 返回值 + Error string `json:"error"` // 错误信息 + Duration string `json:"duration"` // 执行耗时 + Success bool `json:"success"` // 是否成功 +} + +// consoleLog 存储 console.log 输出 +type consoleLogger struct { + logs []string +} + +func (c *consoleLogger) log(args ...interface{}) { + c.logs = append(c.logs, fmt.Sprint(args...)) +} + +func (c *consoleLogger) logFn(call goja.FunctionCall) goja.Value { + var args []interface{} + for _, arg := range call.Arguments { + args = append(args, arg.Export()) + } + c.logs = append(c.logs, fmt.Sprint(args...)) + return goja.Undefined() +} + +// ExecuteJs 执行 JavaScript 代码 +func ExecuteJs(code string) *JsExecResult { + result := &JsExecResult{ + Success: false, + } + + if strings.TrimSpace(code) == "" { + result.Error = "代码不能为空" + return result + } + + start := time.Now() + + // 创建 JS 虚拟机 + vm := goja.New() + + // 创建日志记录器 + logger := &consoleLogger{ + logs: make([]string, 0), + } + + // 注入 console 对象 + consoleObj := vm.NewObject() + consoleObj.Set("log", logger.logFn) + consoleObj.Set("info", logger.logFn) + consoleObj.Set("warn", logger.logFn) + consoleObj.Set("error", logger.logFn) + vm.Set("console", consoleObj) + + // 注入常用全局函数 + vm.Set("parseInt", parseInt) + vm.Set("parseFloat", parseFloat) + vm.Set("isNaN", isNaN) + vm.Set("isFinite", isFinite) + vm.Set("String", vm.Get("String")) + vm.Set("Number", vm.Get("Number")) + vm.Set("Boolean", vm.Get("Boolean")) + vm.Set("Array", vm.Get("Array")) + vm.Set("Object", vm.Get("Object")) + vm.Set("JSON", vm.Get("JSON")) + vm.Set("Math", vm.Get("Math")) + vm.Set("Date", vm.Get("Date")) + vm.Set("RegExp", vm.Get("RegExp")) + + // 执行代码 + val, err := vm.RunString(code) + if err != nil { + result.Error = err.Error() + result.Duration = time.Since(start).String() + result.Output = strings.Join(logger.logs, "\n") + return result + } + + // 获取返回值 + resultVal := "" + if val != nil && !goja.IsUndefined(val) && !goja.IsNull(val) { + resultVal = fmt.Sprintf("%v", val.Export()) + } + + result.Output = strings.Join(logger.logs, "\n") + result.Result = resultVal + result.Duration = time.Since(start).String() + result.Success = true + + return result +} + +// parseInt 解析整数 +func parseInt(call goja.FunctionCall) goja.Value { + if len(call.Arguments) < 1 { + return goja.NaN() + } + + arg := call.Arguments[0].String() + var radix int64 = 10 + if len(call.Arguments) > 1 { + radix = call.Arguments[1].ToInteger() + } + + var result int64 + var err error + + switch radix { + case 16: + _, err = fmt.Sscanf(arg, "%x", &result) + case 8: + _, err = fmt.Sscanf(arg, "%o", &result) + default: + _, err = fmt.Sscanf(arg, "%d", &result) + } + + if err != nil { + return goja.NaN() + } + + return goja.New().ToValue(result) +} + +// parseFloat 解析浮点数 +func parseFloat(call goja.FunctionCall) goja.Value { + if len(call.Arguments) < 1 { + return goja.NaN() + } + + arg := call.Arguments[0].String() + var result float64 + _, err := fmt.Sscanf(arg, "%f", &result) + if err != nil { + return goja.NaN() + } + + return goja.New().ToValue(result) +} + +// isNaN 检查是否为 NaN +func isNaN(call goja.FunctionCall) goja.Value { + if len(call.Arguments) < 1 { + return goja.New().ToValue(true) + } + + val := call.Arguments[0].ToNumber() + if goja.IsNaN(val) { + return goja.New().ToValue(true) + } + return goja.New().ToValue(false) +} + +// isFinite 检查是否为有限数 +func isFinite(call goja.FunctionCall) goja.Value { + if len(call.Arguments) < 1 { + return goja.New().ToValue(false) + } + + val := call.Arguments[0].ToNumber() + if goja.IsNaN(val) || goja.IsInfinity(val) { + return goja.New().ToValue(false) + } + return goja.New().ToValue(true) +} diff --git a/api/lang.go b/api/lang.go new file mode 100644 index 0000000..143ce77 --- /dev/null +++ b/api/lang.go @@ -0,0 +1,13 @@ +package api + +import ( + "webhelper/global" +) + +func GetLang() (*global.LanguagePack, error) { + return global.GetLangPack() +} + +func GetALLLang() []global.LanguageInfo { + return global.GetLangInfoList() +} \ No newline at end of file diff --git a/api/log.go b/api/log.go new file mode 100644 index 0000000..04927c9 --- /dev/null +++ b/api/log.go @@ -0,0 +1,233 @@ +package api + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "time" +) + +// RequestLog 请求日志 +type RequestLog struct { + ID int `json:"id"` // 序号 + Method string `json:"method"` // 请求方法 + URL string `json:"url"` // 请求地址 + Host string `json:"host"` // 主机地址 + Path string `json:"path"` // 请求路径 + Headers map[string]string `json:"headers"` // 请求头 + Cookies map[string]string `json:"cookies"` // Cookie + BodyType string `json:"bodyType"` // 数据类型 + Body string `json:"body"` // 请求体 + StatusCode int `json:"statusCode"` // 状态码 + Status string `json:"status"` // 状态描述 + RespHeaders map[string]string `json:"respHeaders"` // 响应头 + RespBody string `json:"respBody"` // 响应正文 + RespSize int64 `json:"respSize"` // 响应大小 + Time string `json:"time"` // 请求耗时 + CreatedAt string `json:"createdAt"` // 创建时间 +} + +// LogManager 日志管理器 +type LogManager struct { + logDir string + logs []*RequestLog + maxLogs int + fileName string +} + +var logManager *LogManager + +// InitLogManager 初始化日志管理器 +func InitLogManager(logDir string) { + logManager = &LogManager{ + logDir: logDir, + logs: make([]*RequestLog, 0), + maxLogs: 1000, + fileName: "http_requests.json", + } + logManager.loadLogs() +} + +// GetLogManager 获取日志管理器 +func GetLogManager() *LogManager { + if logManager == nil { + InitLogManager("logs") + } + return logManager +} + +// loadLogs 从文件加载日志 +func (m *LogManager) loadLogs() { + filePath := filepath.Join(m.logDir, m.fileName) + data, err := os.ReadFile(filePath) + if err != nil { + if os.IsNotExist(err) { + m.logs = make([]*RequestLog, 0) + return + } + return + } + + var logs []*RequestLog + if err := json.Unmarshal(data, &logs); err != nil { + m.logs = make([]*RequestLog, 0) + return + } + + m.logs = logs +} + +// saveLogs 保存日志到文件 +func (m *LogManager) saveLogs() error { + if err := os.MkdirAll(m.logDir, 0755); err != nil { + return err + } + + filePath := filepath.Join(m.logDir, m.fileName) + data, err := json.MarshalIndent(m.logs, "", " ") + if err != nil { + return err + } + + return os.WriteFile(filePath, data, 0644) +} + +// AddLog 添加日志 +func (m *LogManager) AddLog(log *RequestLog) error { + // 生成序号 + if len(m.logs) > 0 { + log.ID = m.logs[0].ID + 1 + } else { + log.ID = 1 + } + + log.CreatedAt = time.Now().Format(time.DateTime) + + // 插入到开头(最新的在前面) + m.logs = append([]*RequestLog{log}, m.logs...) + + // 限制最大日志数量 + if len(m.logs) > m.maxLogs { + m.logs = m.logs[:m.maxLogs] + } + + return m.saveLogs() +} + +// GetLogs 获取所有日志 +func (m *LogManager) GetLogs() []*RequestLog { + return m.logs +} + +// GetLog 获取单条日志 +func (m *LogManager) GetLog(id int) *RequestLog { + for _, log := range m.logs { + if log.ID == id { + return log + } + } + return nil +} + +// DeleteLog 删除日志 +func (m *LogManager) DeleteLog(id int) error { + for i, log := range m.logs { + if log.ID == id { + m.logs = append(m.logs[:i], m.logs[i+1:]...) + return m.saveLogs() + } + } + return nil +} + +// ClearLogs 清空日志 +func (m *LogManager) ClearLogs() error { + m.logs = make([]*RequestLog, 0) + return m.saveLogs() +} + +// SearchLogs 搜索日志 +func (m *LogManager) SearchLogs(keyword string) []*RequestLog { + var result []*RequestLog + keyword = strings.ToLower(keyword) + + for _, log := range m.logs { + if strings.Contains(strings.ToLower(log.URL), keyword) || + strings.Contains(strings.ToLower(log.Method), keyword) || + strings.Contains(strings.ToLower(log.Host), keyword) || + strings.Contains(strings.ToLower(log.Path), keyword) { + result = append(result, log) + } + } + + return result +} + +// CreateLogFromResponse 从响应创建日志 +func CreateLogFromResponse(req *HttpRequest, resp *HttpResponse) *RequestLog { + // 解析 URL + host := "" + path := req.URL + if strings.HasPrefix(req.URL, "http://") { + parts := strings.SplitN(strings.TrimPrefix(req.URL, "http://"), "/", 2) + host = parts[0] + if len(parts) > 1 { + path = "/" + parts[1] + } + } else if strings.HasPrefix(req.URL, "https://") { + parts := strings.SplitN(strings.TrimPrefix(req.URL, "https://"), "/", 2) + host = parts[0] + if len(parts) > 1 { + path = "/" + parts[1] + } + } + + return &RequestLog{ + Method: req.Method, + URL: req.URL, + Host: host, + Path: path, + Headers: req.Headers, + Cookies: req.Cookies, + BodyType: req.BodyType, + Body: req.Body, + StatusCode: resp.StatusCode, + Status: resp.Status, + RespHeaders: resp.Headers, + RespBody: resp.Body, + RespSize: resp.Size, + Time: resp.Time, + } +} + +// FormatFileSize 格式化文件大小 +func FormatFileSize(size int64) string { + if size < 1024 { + return fmt.Sprintf("%d B", size) + } else if size < 1024*1024 { + return fmt.Sprintf("%.2f KB", float64(size)/1024) + } else if size < 1024*1024*1024 { + return fmt.Sprintf("%.2f MB", float64(size)/(1024*1024)) + } + return fmt.Sprintf("%.2f GB", float64(size)/(1024*1024*1024)) +} + +// SortLogsByTime 按时间排序日志 +func SortLogsByTime(logs []*RequestLog, desc bool) []*RequestLog { + sorted := make([]*RequestLog, len(logs)) + copy(sorted, logs) + + sort.Slice(sorted, func(i, j int) bool { + timeI, _ := time.Parse(time.DateTime, sorted[i].CreatedAt) + timeJ, _ := time.Parse(time.DateTime, sorted[j].CreatedAt) + if desc { + return timeI.After(timeJ) + } + return timeI.Before(timeJ) + }) + + return sorted +} diff --git a/api/logger.go b/api/logger.go new file mode 100644 index 0000000..97acdaf --- /dev/null +++ b/api/logger.go @@ -0,0 +1,21 @@ +package api + +import ( + "webhelper/global" +) + +func LogInfo(msg string) { + global.Log.Info(msg) +} + +func LogWarn(msg string) { + global.Log.Warn(msg) +} + +func LogError(msg string) { + global.Log.Error(msg) +} + +func LogDebug(msg string) { + global.Log.Debug(msg) +} \ No newline at end of file diff --git a/api/socket.go b/api/socket.go new file mode 100644 index 0000000..6d5c991 --- /dev/null +++ b/api/socket.go @@ -0,0 +1,333 @@ +package api + +import ( + "fmt" + "net" + "net/http" + "net/url" + "sync" + "time" + + "webhelper/global" + + "github.com/gorilla/websocket" +) + +// WebSocketClient WebSocket 客户端 +type WebSocketClient struct { + conn *websocket.Conn + isConnected bool + mu sync.RWMutex + onMessage func(message string) + onError func(err error) + onClose func() +} + +// WebSocketConfig WebSocket 配置 +type WebSocketConfig struct { + URL string `json:"url"` + Headers map[string]string `json:"headers"` + Insecure bool `json:"insecure"` +} + +// WebSocketMessage WebSocket 消息 +type WebSocketMessage struct { + Type string `json:"type"` // text, binary, ping, pong, error, system + Content string `json:"content"` + Time string `json:"time"` +} + +var wsClient *WebSocketClient + +// NewWebSocketClient 创建 WebSocket 客户端 +func NewWebSocketClient() *WebSocketClient { + return &WebSocketClient{ + isConnected: false, + } +} + +// Connect 连接 WebSocket +func (c *WebSocketClient) Connect(config *WebSocketConfig, onMessage func(string), onError func(error), onClose func()) error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.isConnected { + return fmt.Errorf("已连接") + } + + // 解析 URL + u, err := url.Parse(config.URL) + if err != nil { + return fmt.Errorf("URL 格式错误: %v", err) + } + + // 创建 HTTP Header(只添加用户自定义头) + header := http.Header{} + for key, value := range config.Headers { + header.Set(key, value) + } + + // 创建 Dialer(最简配置) + dialer := &websocket.Dialer{ + HandshakeTimeout: 10 * time.Second, + } + + // 连接 + global.Log.Infof("正在连接 WebSocket: %s", u.String()) + conn, _, err := dialer.Dial(u.String(), header) + if err != nil { + return fmt.Errorf("连接失败: %v", err) + } + + c.conn = conn + c.isConnected = true + c.onMessage = onMessage + c.onError = onError + c.onClose = onClose + + global.Log.Infof("WebSocket 连接成功: %s", u.String()) + + // 启动消息监听 + go c.readMessages() + + return nil +} + +// readMessages 读取消息 +func (c *WebSocketClient) readMessages() { + defer func() { + c.mu.Lock() + c.isConnected = false + c.mu.Unlock() + if c.onClose != nil { + c.onClose() + } + }() + + for { + _, message, err := c.conn.ReadMessage() + if err != nil { + if c.onError != nil { + c.onError(err) + } + return + } + if c.onMessage != nil { + c.onMessage(string(message)) + } + } +} + +// Send 发送消息 +func (c *WebSocketClient) Send(message string) error { + c.mu.RLock() + defer c.mu.RUnlock() + + if !c.isConnected || c.conn == nil { + return fmt.Errorf("未连接") + } + + return c.conn.WriteMessage(websocket.TextMessage, []byte(message)) +} + +// Close 关闭连接 +func (c *WebSocketClient) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.conn != nil { + err := c.conn.Close() + c.isConnected = false + return err + } + return nil +} + +// IsConnected 是否已连接 +func (c *WebSocketClient) IsConnected() bool { + c.mu.RLock() + defer c.mu.RUnlock() + return c.isConnected +} + +// GetWebSocketClient 获取 WebSocket 客户端 +func GetWebSocketClient() *WebSocketClient { + if wsClient == nil { + wsClient = NewWebSocketClient() + } + return wsClient +} + +// ConnectWebSocket 连接 WebSocket +func ConnectWebSocket(config *WebSocketConfig, onMessage func(string), onError func(error), onClose func()) error { + client := GetWebSocketClient() + return client.Connect(config, onMessage, onError, onClose) +} + +// SendWebSocketMessage 发送 WebSocket 消息 +func SendWebSocketMessage(message string) error { + client := GetWebSocketClient() + return client.Send(message) +} + +// CloseWebSocket 关闭 WebSocket 连接 +func CloseWebSocket() error { + client := GetWebSocketClient() + return client.Close() +} + +// IsWebSocketConnected WebSocket 是否已连接 +func IsWebSocketConnected() bool { + client := GetWebSocketClient() + return client.IsConnected() +} + +// TCPClient TCP 客户端 +type TCPClient struct { + conn net.Conn + isConnected bool + mu sync.RWMutex + onMessage func(message string) + onError func(err error) + onClose func() +} + +// TCPConfig TCP 配置 +type TCPConfig struct { + Host string `json:"host"` + Port int `json:"port"` +} + +// TCPMessage TCP 消息 +type TCPMessage struct { + Type string `json:"type"` + Content string `json:"content"` + Time string `json:"time"` +} + +var tcpClient *TCPClient + +// NewTCPClient 创建 TCP 客户端 +func NewTCPClient() *TCPClient { + return &TCPClient{ + isConnected: false, + } +} + +// Connect 连接 TCP +func (c *TCPClient) Connect(config *TCPConfig, onMessage func(string), onError func(error), onClose func()) error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.isConnected { + return fmt.Errorf("已连接") + } + + addr := fmt.Sprintf("%s:%d", config.Host, config.Port) + conn, err := net.DialTimeout("tcp", addr, 10*time.Second) + if err != nil { + return fmt.Errorf("连接失败: %v", err) + } + + c.conn = conn + c.isConnected = true + c.onMessage = onMessage + c.onError = onError + c.onClose = onClose + + // 启动消息监听 + go c.readMessages() + + return nil +} + +// readMessages 读取消息 +func (c *TCPClient) readMessages() { + defer func() { + c.mu.Lock() + c.isConnected = false + c.mu.Unlock() + if c.onClose != nil { + c.onClose() + } + }() + + buf := make([]byte, 4096) + for { + n, err := c.conn.Read(buf) + if err != nil { + if c.onError != nil { + c.onError(err) + } + return + } + if c.onMessage != nil { + c.onMessage(string(buf[:n])) + } + } +} + +// Send 发送消息 +func (c *TCPClient) Send(message string) error { + c.mu.RLock() + defer c.mu.RUnlock() + + if !c.isConnected || c.conn == nil { + return fmt.Errorf("未连接") + } + + _, err := c.conn.Write([]byte(message)) + return err +} + +// Close 关闭连接 +func (c *TCPClient) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.conn != nil { + err := c.conn.Close() + c.isConnected = false + return err + } + return nil +} + +// IsConnected 是否已连接 +func (c *TCPClient) IsConnected() bool { + c.mu.RLock() + defer c.mu.RUnlock() + return c.isConnected +} + +// GetTCPClient 获取 TCP 客户端 +func GetTCPClient() *TCPClient { + if tcpClient == nil { + tcpClient = NewTCPClient() + } + return tcpClient +} + +// ConnectTCP 连接 TCP +func ConnectTCP(config *TCPConfig, onMessage func(string), onError func(error), onClose func()) error { + client := GetTCPClient() + return client.Connect(config, onMessage, onError, onClose) +} + +// SendTCPMessage 发送 TCP 消息 +func SendTCPMessage(message string) error { + client := GetTCPClient() + return client.Send(message) +} + +// CloseTCP 关闭 TCP 连接 +func CloseTCP() error { + client := GetTCPClient() + return client.Close() +} + +// IsTCPConnected TCP 是否已连接 +func IsTCPConnected() bool { + client := GetTCPClient() + return client.isConnected +} diff --git a/build/appicon.png b/build/appicon.png new file mode 100644 index 0000000..6a76e08 Binary files /dev/null and b/build/appicon.png differ diff --git a/build/darwin/Info.dev.plist b/build/darwin/Info.dev.plist new file mode 100644 index 0000000..14121ef --- /dev/null +++ b/build/darwin/Info.dev.plist @@ -0,0 +1,68 @@ + + + + CFBundlePackageType + APPL + CFBundleName + {{.Info.ProductName}} + CFBundleExecutable + {{.OutputFilename}} + CFBundleIdentifier + com.wails.{{.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}} + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + diff --git a/build/darwin/Info.plist b/build/darwin/Info.plist new file mode 100644 index 0000000..d17a747 --- /dev/null +++ b/build/darwin/Info.plist @@ -0,0 +1,63 @@ + + + + CFBundlePackageType + APPL + CFBundleName + {{.Info.ProductName}} + CFBundleExecutable + {{.OutputFilename}} + CFBundleIdentifier + com.wails.{{.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/windows/icon.ico b/build/windows/icon.ico new file mode 100644 index 0000000..cf46c2c 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/build/windows/installer/project.nsi b/build/windows/installer/project.nsi new file mode 100644 index 0000000..654ae2e --- /dev/null +++ b/build/windows/installer/project.nsi @@ -0,0 +1,114 @@ +Unicode true + +#### +## Please note: Template replacements don't work in this file. They are provided with default defines like +## mentioned underneath. +## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo. +## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually +## from outside of Wails for debugging and development of the installer. +## +## For development first make a wails nsis build to populate the "wails_tools.nsh": +## > wails build --target windows/amd64 --nsis +## Then you can call makensis on this file with specifying the path to your binary: +## For a AMD64 only installer: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe +## For a ARM64 only installer: +## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe +## For a installer with both architectures: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe +#### +## The following information is taken from the ProjectInfo file, but they can be overwritten here. +#### +## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}" +## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}" +## !define INFO_PRODUCTNAME "MyProduct" # Default "{{.Info.ProductName}}" +## !define INFO_PRODUCTVERSION "1.0.0" # Default "{{.Info.ProductVersion}}" +## !define INFO_COPYRIGHT "Copyright" # Default "{{.Info.Copyright}}" +### +## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" +## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +#### +## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html +#### +## Include the wails tools +#### +!include "wails_tools.nsh" + +# The version information for this two must consist of 4 parts +VIProductVersion "${INFO_PRODUCTVERSION}.0" +VIFileVersion "${INFO_PRODUCTVERSION}.0" + +VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" +VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" +VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" +VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" + +# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware +ManifestDPIAware true + +!include "MUI.nsh" + +!define MUI_ICON "..\icon.ico" +!define MUI_UNICON "..\icon.ico" +# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 +!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps +!define MUI_ABORTWARNING # This will warn the user if they exit from the installer. + +!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. +# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer +!insertmacro MUI_PAGE_DIRECTORY # In which folder install page. +!insertmacro MUI_PAGE_INSTFILES # Installing page. +!insertmacro MUI_PAGE_FINISH # Finished installation page. + +!insertmacro MUI_UNPAGE_INSTFILES # Uinstalling page + +!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer + +## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 +#!uninstfinalize 'signtool --file "%1"' +#!finalize 'signtool --file "%1"' + +Name "${INFO_PRODUCTNAME}" +OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. +InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). +ShowInstDetails show # This will always show the installation details. + +Function .onInit + !insertmacro wails.checkArchitecture +FunctionEnd + +Section + !insertmacro wails.setShellContext + + !insertmacro wails.webview2runtime + + SetOutPath $INSTDIR + + !insertmacro wails.files + + CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + + !insertmacro wails.associateFiles + !insertmacro wails.associateCustomProtocols + + !insertmacro wails.writeUninstaller +SectionEnd + +Section "uninstall" + !insertmacro wails.setShellContext + + RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath + + RMDir /r $INSTDIR + + Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" + Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" + + !insertmacro wails.unassociateFiles + !insertmacro wails.unassociateCustomProtocols + + !insertmacro wails.deleteUninstaller +SectionEnd diff --git a/build/windows/installer/wails_tools.nsh b/build/windows/installer/wails_tools.nsh new file mode 100644 index 0000000..f9c0f88 --- /dev/null +++ b/build/windows/installer/wails_tools.nsh @@ -0,0 +1,249 @@ +# DO NOT EDIT - Generated automatically by `wails build` + +!include "x64.nsh" +!include "WinVer.nsh" +!include "FileFunc.nsh" + +!ifndef INFO_PROJECTNAME + !define INFO_PROJECTNAME "{{.Name}}" +!endif +!ifndef INFO_COMPANYNAME + !define INFO_COMPANYNAME "{{.Info.CompanyName}}" +!endif +!ifndef INFO_PRODUCTNAME + !define INFO_PRODUCTNAME "{{.Info.ProductName}}" +!endif +!ifndef INFO_PRODUCTVERSION + !define INFO_PRODUCTVERSION "{{.Info.ProductVersion}}" +!endif +!ifndef INFO_COPYRIGHT + !define INFO_COPYRIGHT "{{.Info.Copyright}}" +!endif +!ifndef PRODUCT_EXECUTABLE + !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" +!endif +!ifndef UNINST_KEY_NAME + !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +!endif +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" + +!ifndef REQUEST_EXECUTION_LEVEL + !define REQUEST_EXECUTION_LEVEL "admin" +!endif + +RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" + +!ifdef ARG_WAILS_AMD64_BINARY + !define SUPPORTS_AMD64 +!endif + +!ifdef ARG_WAILS_ARM64_BINARY + !define SUPPORTS_ARM64 +!endif + +!ifdef SUPPORTS_AMD64 + !ifdef SUPPORTS_ARM64 + !define ARCH "amd64_arm64" + !else + !define ARCH "amd64" + !endif +!else + !ifdef SUPPORTS_ARM64 + !define ARCH "arm64" + !else + !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" + !endif +!endif + +!macro wails.checkArchitecture + !ifndef WAILS_WIN10_REQUIRED + !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." + !endif + + !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED + !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" + !endif + + ${If} ${AtLeastWin10} + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + Goto ok + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + Goto ok + ${EndIf} + !endif + + IfSilent silentArch notSilentArch + silentArch: + SetErrorLevel 65 + Abort + notSilentArch: + MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" + Quit + ${else} + IfSilent silentWin notSilentWin + silentWin: + SetErrorLevel 64 + Abort + notSilentWin: + MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" + Quit + ${EndIf} + + ok: +!macroend + +!macro wails.files + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" + ${EndIf} + !endif +!macroend + +!macro wails.writeUninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + SetRegView 64 + WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" + WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" +!macroend + +!macro wails.deleteUninstaller + Delete "$INSTDIR\uninstall.exe" + + SetRegView 64 + DeleteRegKey HKLM "${UNINST_KEY}" +!macroend + +!macro wails.setShellContext + ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" + SetShellVarContext all + ${else} + SetShellVarContext current + ${EndIf} +!macroend + +# Install webview2 by launching the bootstrapper +# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment +!macro wails.webview2runtime + !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT + !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" + !endif + + SetRegView 64 + # If the admin key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + + ${If} ${REQUEST_EXECUTION_LEVEL} == "user" + # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + ${EndIf} + + SetDetailsPrint both + DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" + SetDetailsPrint listonly + + InitPluginsDir + CreateDirectory "$pluginsdir\webview2bootstrapper" + SetOutPath "$pluginsdir\webview2bootstrapper" + File "tmp\MicrosoftEdgeWebview2Setup.exe" + ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' + + SetDetailsPrint both + ok: +!macroend + +# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b +!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" + + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` +!macroend + +!macro APP_UNASSOCIATE EXT FILECLASS + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" + + DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` +!macroend + +!macro wails.associateFiles + ; Create file associations + {{range .Info.FileAssociations}} + !insertmacro APP_ASSOCIATE "{{.Ext}}" "{{.Name}}" "{{.Description}}" "$INSTDIR\{{.IconName}}.ico" "Open with ${INFO_PRODUCTNAME}" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" + + File "..\{{.IconName}}.ico" + {{end}} +!macroend + +!macro wails.unassociateFiles + ; Delete app associations + {{range .Info.FileAssociations}} + !insertmacro APP_UNASSOCIATE "{{.Ext}}" "{{.Name}}" + + Delete "$INSTDIR\{{.IconName}}.ico" + {{end}} +!macroend + +!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND + DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}" +!macroend + +!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL + DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" +!macroend + +!macro wails.associateCustomProtocols + ; Create custom protocols associations + {{range .Info.Protocols}} + !insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" + + {{end}} +!macroend + +!macro wails.unassociateCustomProtocols + ; Delete app custom protocol associations + {{range .Info.Protocols}} + !insertmacro CUSTOM_PROTOCOL_UNASSOCIATE "{{.Scheme}}" + {{end}} +!macroend diff --git a/build/windows/wails.exe.manifest b/build/windows/wails.exe.manifest new file mode 100644 index 0000000..17e1a23 --- /dev/null +++ b/build/windows/wails.exe.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..614ed27 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + App + + +
+ + + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..bcc5ee6 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1705 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@wailsio/runtime": "^3.0.0-alpha.66", + "element-plus": "^2.9.10", + "vue": "^3.2.37" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^3.0.3", + "unplugin-auto-import": "^19.2.0", + "unplugin-vue-components": "^28.5.0", + "vite": "^3.0.7" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.2", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz", + "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.0.tgz", + "integrity": "sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.0.tgz", + "integrity": "sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.7", + "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", + "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.17", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.17.tgz", + "integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-3.2.0.tgz", + "integrity": "sha512-E0tnaL4fr+qkdCNxJ+Xd0yM31UwMkQje76fsDVBBUCoGOUPexu2VDUYHL8P4CwV+zMvWw6nlRw19OnRKmYAJpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^3.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.14", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.14.tgz", + "integrity": "sha512-k7qMHMbKvoCXIxPhquKQVw3Twid3Kg4s7+oYURxLGRd56LiuHJVrvFKI4fm2AM3c8apqODPfVJGoh8nePbXMRA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.2", + "@vue/shared": "3.5.14", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.14", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.14.tgz", + "integrity": "sha512-1aOCSqxGOea5I80U2hQJvXYpPm/aXo95xL/m/mMhgyPUsKe9jhjwWpziNAw7tYRnbz1I61rd9Mld4W9KmmRoug==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.14", + "@vue/shared": "3.5.14" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.14", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.14.tgz", + "integrity": "sha512-9T6m/9mMr81Lj58JpzsiSIjBgv2LiVoWjIVa7kuXHICUi8LiDSIotMpPRXYJsXKqyARrzjT24NAwttrMnMaCXA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.2", + "@vue/compiler-core": "3.5.14", + "@vue/compiler-dom": "3.5.14", + "@vue/compiler-ssr": "3.5.14", + "@vue/shared": "3.5.14", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.17", + "postcss": "^8.5.3", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.14", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.14.tgz", + "integrity": "sha512-Y0G7PcBxr1yllnHuS/NxNCSPWnRGH4Ogrp0tsLA5QemDZuJLs99YjAKQ7KqkHE0vCg4QTKlQzXLKCMF7WPSl7Q==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.14", + "@vue/shared": "3.5.14" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.14", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.14.tgz", + "integrity": "sha512-7cK1Hp343Fu/SUCCO52vCabjvsYu7ZkOqyYu7bXV9P2yyfjUMUXHZafEbq244sP7gf+EZEz+77QixBTuEqkQQw==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.14" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.14", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.14.tgz", + "integrity": "sha512-w9JWEANwHXNgieAhxPpEpJa+0V5G0hz3NmjAZwlOebtfKyp2hKxKF0+qSh0Xs6/PhfGihuSdqMprMVcQU/E6ag==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.14", + "@vue/shared": "3.5.14" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.14", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.14.tgz", + "integrity": "sha512-lCfR++IakeI35TVR80QgOelsUIdcKjd65rWAMfdSlCYnaEY5t3hYwru7vvcWaqmrK+LpI7ZDDYiGU5V3xjMacw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.14", + "@vue/runtime-core": "3.5.14", + "@vue/shared": "3.5.14", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.14", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.14.tgz", + "integrity": "sha512-Rf/ISLqokIvcySIYnv3tNWq40PLpNLDLSJwwVWzG6MNtyIhfbcrAxo5ZL9nARJhqjZyWWa40oRb2IDuejeuv6w==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.14", + "@vue/shared": "3.5.14" + }, + "peerDependencies": { + "vue": "3.5.14" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.14", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.14.tgz", + "integrity": "sha512-oXTwNxVfc9EtP1zzXAlSlgARLXNC84frFYkS0HHz0h3E4WZSP9sywqjqzGCP9Y34M8ipNmd380pVgmMuwELDyQ==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz", + "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.13.0", + "@vueuse/shared": "9.13.0", + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz", + "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz", + "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "license": "MIT", + "dependencies": { + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@wailsio/runtime": { + "version": "3.0.0-alpha.66", + "resolved": "https://registry.npmmirror.com/@wailsio/runtime/-/runtime-3.0.0-alpha.66.tgz", + "integrity": "sha512-ENLu8rn1griL1gFHJqkq1i+BVxrrA0JPJHYneUJYuf/s54kjuQViW0RKDEe/WTDo56ABpfykrd/T8OYpPUyXUw==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/element-plus": { + "version": "2.9.10", + "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.9.10.tgz", + "integrity": "sha512-W2v9jWnm1kl/zm4bSvCh8aFCVlxvhG3fmqiDZwyd6WQiWGE595J/mpjcCggEr+49QDgIymhXrpPMOPPSARUbng==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.1", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.14.182", + "@types/lodash-es": "^4.17.6", + "@vueuse/core": "^9.1.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.13", + "escape-html": "^1.0.3", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.2", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", + "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", + "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", + "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", + "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", + "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", + "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", + "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", + "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", + "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", + "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", + "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", + "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", + "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", + "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", + "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", + "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", + "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", + "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmmirror.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", + "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "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/exsolve": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.5.tgz", + "integrity": "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "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/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.1.tgz", + "integrity": "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.0.1", + "quansync": "^0.2.8" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/mlly": { + "version": "1.7.4", + "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.7.4.tgz", + "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "pathe": "^2.0.1", + "pkg-types": "^1.3.0", + "ufo": "^1.5.4" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "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.2", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.1.0.tgz", + "integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.1", + "exsolve": "^1.0.1", + "pathe": "^2.0.3" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "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.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/quansync": { + "version": "0.2.10", + "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.10.tgz", + "integrity": "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup": { + "version": "2.79.2", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/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/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unimport": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/unimport/-/unimport-4.2.0.tgz", + "integrity": "sha512-mYVtA0nmzrysnYnyb3ALMbByJ+Maosee2+WyE0puXl+Xm2bUwPorPaaeZt0ETfuroPOtG8jj1g/qeFZ6buFnag==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.1", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "local-pkg": "^1.1.1", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "pkg-types": "^2.1.0", + "scule": "^1.3.0", + "strip-literal": "^3.0.0", + "tinyglobby": "^0.2.12", + "unplugin": "^2.2.2", + "unplugin-utils": "^0.2.4" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unimport/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/unplugin": { + "version": "2.3.4", + "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.4.tgz", + "integrity": "sha512-m4PjxTurwpWfpMomp8AptjD5yj8qEZN5uQjjGM3TAs9MWWD2tXSSNNj6jGR2FoVGod4293ytyV6SwBbertfyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.1", + "picomatch": "^4.0.2", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin-auto-import": { + "version": "19.2.0", + "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-19.2.0.tgz", + "integrity": "sha512-DGRHg86nUDKEYpny1p2kFZjeLg7kHQmknsPQ8krAshvpeypps7dFxNBsAqhBaxYINjetbgQilF8wbjuZxpdomg==", + "dev": true, + "license": "MIT", + "dependencies": { + "local-pkg": "^1.1.1", + "magic-string": "^0.30.17", + "picomatch": "^4.0.2", + "unimport": "^4.2.0", + "unplugin": "^2.3.2", + "unplugin-utils": "^0.2.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@nuxt/kit": "^3.2.2", + "@vueuse/core": "*" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@vueuse/core": { + "optional": true + } + } + }, + "node_modules/unplugin-utils": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.2.4.tgz", + "integrity": "sha512-8U/MtpkPkkk3Atewj1+RcKIjb5WBimZ/WSLhhR3w6SsIj8XJuKTacSP8g+2JhfSGw0Cb125Y+2zA/IzJZDVbhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/unplugin-vue-components": { + "version": "28.5.0", + "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-28.5.0.tgz", + "integrity": "sha512-o7fMKU/uI8NiP+E0W62zoduuguWqB0obTfHFtbr1AP2uo2lhUPnPttWUE92yesdiYfo9/0hxIrj38FMc1eaySg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.6.0", + "debug": "^4.4.0", + "local-pkg": "^1.1.1", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "tinyglobby": "^0.2.12", + "unplugin": "^2.3.2", + "unplugin-utils": "^0.2.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite": { + "version": "3.2.11", + "resolved": "https://registry.npmmirror.com/vite/-/vite-3.2.11.tgz", + "integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.15.9", + "postcss": "^8.4.18", + "resolve": "^1.22.1", + "rollup": "^2.79.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.14", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.14.tgz", + "integrity": "sha512-LbOm50/vZFG6Mhy6KscQYXZMQ0LMCC/y40HDJPPvGFQ+i/lUH+PJHR6C3assgOQiXdl6tAfsXHbXYVBZZu65ew==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.14", + "@vue/compiler-sfc": "3.5.14", + "@vue/runtime-dom": "3.5.14", + "@vue/server-renderer": "3.5.14", + "@vue/shared": "3.5.14" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..f194bd7 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,22 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@wailsio/runtime": "^3.0.0-alpha.66", + "element-plus": "^2.9.10", + "vue": "^3.2.37" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^3.0.3", + "unplugin-auto-import": "^19.2.0", + "unplugin-vue-components": "^28.5.0", + "vite": "^3.0.7" + } +} diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 new file mode 100644 index 0000000..0c1b4c1 --- /dev/null +++ b/frontend/package.json.md5 @@ -0,0 +1 @@ +4e89b15208d82473f2aacb5ee7a39659 \ No newline at end of file diff --git a/frontend/public/appicon.png b/frontend/public/appicon.png new file mode 100644 index 0000000..6a76e08 Binary files /dev/null and b/frontend/public/appicon.png differ diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..2d67793 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,102 @@ + + + + + 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/components/HeaderBar.vue b/frontend/src/components/HeaderBar.vue new file mode 100644 index 0000000..43fb28d --- /dev/null +++ b/frontend/src/components/HeaderBar.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/frontend/src/components/JsDebug.vue b/frontend/src/components/JsDebug.vue new file mode 100644 index 0000000..fcdf57f --- /dev/null +++ b/frontend/src/components/JsDebug.vue @@ -0,0 +1,188 @@ + + + + + diff --git a/frontend/src/components/JsonParser.vue b/frontend/src/components/JsonParser.vue new file mode 100644 index 0000000..c8a7fa0 --- /dev/null +++ b/frontend/src/components/JsonParser.vue @@ -0,0 +1,526 @@ + + + + + diff --git a/frontend/src/components/LangSelector.vue b/frontend/src/components/LangSelector.vue new file mode 100644 index 0000000..3f25b25 --- /dev/null +++ b/frontend/src/components/LangSelector.vue @@ -0,0 +1,52 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/Settings.vue b/frontend/src/components/Settings.vue new file mode 100644 index 0000000..9a14111 --- /dev/null +++ b/frontend/src/components/Settings.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/frontend/src/components/SocketDebug.vue b/frontend/src/components/SocketDebug.vue new file mode 100644 index 0000000..79ee05e --- /dev/null +++ b/frontend/src/components/SocketDebug.vue @@ -0,0 +1,462 @@ + + + + + diff --git a/frontend/src/components/Test.vue b/frontend/src/components/Test.vue new file mode 100644 index 0000000..1b3ec47 --- /dev/null +++ b/frontend/src/components/Test.vue @@ -0,0 +1,751 @@ + + + + + diff --git a/frontend/src/components/WebDebug.vue b/frontend/src/components/WebDebug.vue new file mode 100644 index 0000000..6a887e0 --- /dev/null +++ b/frontend/src/components/WebDebug.vue @@ -0,0 +1,771 @@ + + + + + diff --git a/frontend/src/composables/useI18n.js b/frontend/src/composables/useI18n.js new file mode 100644 index 0000000..2aee6ae --- /dev/null +++ b/frontend/src/composables/useI18n.js @@ -0,0 +1,37 @@ +import { ref, onMounted } from 'vue' +import { GetLangTextMap } from '../../wailsjs/go/service/App' + +// 全局单例状态 +const textMap = ref({}) +const isLoaded = ref(false) + +export function useI18n() { + const loadTextMap = async () => { + try { + const map = await GetLangTextMap() + textMap.value = map || {} + isLoaded.value = true + } catch (error) { + console.error('加载语言包失败:', error) + textMap.value = {} + isLoaded.value = true + } + } + + const t = (key, defaultValue = '') => { + return textMap.value[key] || defaultValue || key + } + + onMounted(() => { + if (!isLoaded.value) { + loadTextMap() + } + }) + + return { + t, + textMap, + isLoaded, + loadTextMap + } +} \ No newline at end of file diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..dd70ff8 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,11 @@ +import { createApp } from 'vue' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import App from './App.vue' + +const app = createApp(App) + +app.use(ElementPlus) +app.mount('#app') + +document.body.style.overflow = 'hidden'; \ No newline at end of file diff --git a/frontend/src/style.css b/frontend/src/style.css new file mode 100644 index 0000000..7225f6e --- /dev/null +++ b/frontend/src/style.css @@ -0,0 +1,31 @@ +/* 基础样式 */ +html, body { + margin: 0; + padding: 0; + height: 100%; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; +} + +#app { + height: 100vh; +} + +/* 隐藏滚动条 - 通用方法 */ +.hide-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; +} + +.hide-scrollbar::-webkit-scrollbar { + display: none; +} + +/* Element Plus textarea 内部滚动条隐藏 */ +.el-textarea__inner { + -ms-overflow-style: none; + scrollbar-width: none; +} + +.el-textarea__inner::-webkit-scrollbar { + display: none; +} diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..58d3450 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,20 @@ +import {defineConfig} from 'vite' +import vue from '@vitejs/plugin-vue' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + AutoImport({ + resolvers: [ElementPlusResolver()], + }), + Components({ + resolvers: [ElementPlusResolver()], + }), + ], +}) + + diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts new file mode 100644 index 0000000..f4bc077 --- /dev/null +++ b/frontend/wailsjs/go/models.ts @@ -0,0 +1,227 @@ +export namespace api { + + export class CookieInfo { + name: string; + value: string; + path: string; + domain: string; + expires: string; + maxAge: number; + secure: boolean; + httpOnly: boolean; + sameSite: string; + + static createFrom(source: any = {}) { + return new CookieInfo(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.name = source["name"]; + this.value = source["value"]; + this.path = source["path"]; + this.domain = source["domain"]; + this.expires = source["expires"]; + this.maxAge = source["maxAge"]; + this.secure = source["secure"]; + this.httpOnly = source["httpOnly"]; + this.sameSite = source["sameSite"]; + } + } + export class HttpResponse { + statusCode: number; + status: string; + headers: Record; + cookies: CookieInfo[]; + body: string; + contentType: string; + contentLength: number; + time: string; + size: number; + error: string; + + static createFrom(source: any = {}) { + return new HttpResponse(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.statusCode = source["statusCode"]; + this.status = source["status"]; + this.headers = source["headers"]; + this.cookies = this.convertValues(source["cookies"], CookieInfo); + this.body = source["body"]; + this.contentType = source["contentType"]; + this.contentLength = source["contentLength"]; + this.time = source["time"]; + this.size = source["size"]; + this.error = source["error"]; + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice && a.map) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } + } + export class JsExecResult { + output: string; + result: string; + error: string; + duration: string; + success: boolean; + + static createFrom(source: any = {}) { + return new JsExecResult(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.output = source["output"]; + this.result = source["result"]; + this.error = source["error"]; + this.duration = source["duration"]; + this.success = source["success"]; + } + } + export class RequestLog { + id: number; + method: string; + url: string; + host: string; + path: string; + headers: Record; + cookies: Record; + bodyType: string; + body: string; + statusCode: number; + status: string; + respHeaders: Record; + respBody: string; + respSize: number; + time: string; + createdAt: string; + + static createFrom(source: any = {}) { + return new RequestLog(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.id = source["id"]; + this.method = source["method"]; + this.url = source["url"]; + this.host = source["host"]; + this.path = source["path"]; + this.headers = source["headers"]; + this.cookies = source["cookies"]; + this.bodyType = source["bodyType"]; + this.body = source["body"]; + this.statusCode = source["statusCode"]; + this.status = source["status"]; + this.respHeaders = source["respHeaders"]; + this.respBody = source["respBody"]; + this.respSize = source["respSize"]; + this.time = source["time"]; + this.createdAt = source["createdAt"]; + } + } + +} + +export namespace global { + + export class LanguageInfo { + language_name: string; + language_code: string; + textmap_path: string; + translation_progress: string; + translator: string; + last_updated: string; + version: string; + + static createFrom(source: any = {}) { + return new LanguageInfo(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.language_name = source["language_name"]; + this.language_code = source["language_code"]; + this.textmap_path = source["textmap_path"]; + this.translation_progress = source["translation_progress"]; + this.translator = source["translator"]; + this.last_updated = source["last_updated"]; + this.version = source["version"]; + } + } + export class LanguagePack { + language_name: string; + language_code: string; + textmap_path: string; + translation_progress: string; + translator: string; + last_updated: string; + version: string; + textmap: Record; + + static createFrom(source: any = {}) { + return new LanguagePack(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.language_name = source["language_name"]; + this.language_code = source["language_code"]; + this.textmap_path = source["textmap_path"]; + this.translation_progress = source["translation_progress"]; + this.translator = source["translator"]; + this.last_updated = source["last_updated"]; + this.version = source["version"]; + this.textmap = source["textmap"]; + } + } + +} + +export namespace service { + + export class SystemInfo { + os: string; + arch: string; + num_cpu: number; + hostname: string; + go_ver: string; + time: string; + process_name: string; + + static createFrom(source: any = {}) { + return new SystemInfo(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.os = source["os"]; + this.arch = source["arch"]; + this.num_cpu = source["num_cpu"]; + this.hostname = source["hostname"]; + this.go_ver = source["go_ver"]; + this.time = source["time"]; + this.process_name = source["process_name"]; + } + } + +} + diff --git a/frontend/wailsjs/go/service/App.d.ts b/frontend/wailsjs/go/service/App.d.ts new file mode 100644 index 0000000..60835d6 --- /dev/null +++ b/frontend/wailsjs/go/service/App.d.ts @@ -0,0 +1,73 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT +import {api} from '../models'; +import {global} from '../models'; +import {service} from '../models'; + +export function ClearRequestLogs():Promise; + +export function ConnectSocket(arg1:string,arg2:string,arg3:number,arg4:string,arg5:string):Promise; + +export function DeleteRequestLog(arg1:number):Promise; + +export function DisconnectSocket(arg1:string):Promise; + +export function ExecuteJs(arg1:string):Promise; + +export function Flashtime():Promise; + +export function GetALLLang():Promise>; + +export function GetCurrentLang():Promise; + +export function GetLangPack():Promise; + +export function GetLangTextMap():Promise>; + +export function GetLogFileContent(arg1:string):Promise; + +export function GetLogFiles():Promise>; + +export function GetLogLevel():Promise; + +export function GetProcessName():Promise; + +export function GetRequestLog(arg1:number):Promise; + +export function GetRequestLogs():Promise>; + +export function GetSystemInfo():Promise; + +export function Gettestjson():Promise; + +export function Greet(arg1:string):Promise; + +export function IsSocketConnected(arg1:string):Promise; + +export function Notify(arg1:string,arg2:string):Promise; + +export function OpenFileSelect():Promise; + +export function OpenFolderSelect():Promise; + +export function ReadFileContent(arg1:string):Promise; + +export function SaveFileSelect():Promise; + +export function SearchRequestLogs(arg1:string):Promise>; + +export function SendHttpRequest(arg1:string,arg2:string,arg3:string,arg4:string,arg5:string,arg6:string,arg7:string,arg8:string,arg9:string,arg10:number,arg11:boolean,arg12:boolean):Promise; + +export function SendSocketMessage(arg1:string,arg2:string):Promise; + +export function SetLanguage(arg1:string):Promise; + +export function SetLogLevel(arg1:string):Promise; + +export function WindowClose():Promise; + +export function WindowMinimise():Promise; + +export function WindowToggleMaximise():Promise; + +export function WriteFileContent(arg1:string,arg2:string):Promise; diff --git a/frontend/wailsjs/go/service/App.js b/frontend/wailsjs/go/service/App.js new file mode 100644 index 0000000..bec1632 --- /dev/null +++ b/frontend/wailsjs/go/service/App.js @@ -0,0 +1,139 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export function ClearRequestLogs() { + return window['go']['service']['App']['ClearRequestLogs'](); +} + +export function ConnectSocket(arg1, arg2, arg3, arg4, arg5) { + return window['go']['service']['App']['ConnectSocket'](arg1, arg2, arg3, arg4, arg5); +} + +export function DeleteRequestLog(arg1) { + return window['go']['service']['App']['DeleteRequestLog'](arg1); +} + +export function DisconnectSocket(arg1) { + return window['go']['service']['App']['DisconnectSocket'](arg1); +} + +export function ExecuteJs(arg1) { + return window['go']['service']['App']['ExecuteJs'](arg1); +} + +export function Flashtime() { + return window['go']['service']['App']['Flashtime'](); +} + +export function GetALLLang() { + return window['go']['service']['App']['GetALLLang'](); +} + +export function GetCurrentLang() { + return window['go']['service']['App']['GetCurrentLang'](); +} + +export function GetLangPack() { + return window['go']['service']['App']['GetLangPack'](); +} + +export function GetLangTextMap() { + return window['go']['service']['App']['GetLangTextMap'](); +} + +export function GetLogFileContent(arg1) { + return window['go']['service']['App']['GetLogFileContent'](arg1); +} + +export function GetLogFiles() { + return window['go']['service']['App']['GetLogFiles'](); +} + +export function GetLogLevel() { + return window['go']['service']['App']['GetLogLevel'](); +} + +export function GetProcessName() { + return window['go']['service']['App']['GetProcessName'](); +} + +export function GetRequestLog(arg1) { + return window['go']['service']['App']['GetRequestLog'](arg1); +} + +export function GetRequestLogs() { + return window['go']['service']['App']['GetRequestLogs'](); +} + +export function GetSystemInfo() { + return window['go']['service']['App']['GetSystemInfo'](); +} + +export function Gettestjson() { + return window['go']['service']['App']['Gettestjson'](); +} + +export function Greet(arg1) { + return window['go']['service']['App']['Greet'](arg1); +} + +export function IsSocketConnected(arg1) { + return window['go']['service']['App']['IsSocketConnected'](arg1); +} + +export function Notify(arg1, arg2) { + return window['go']['service']['App']['Notify'](arg1, arg2); +} + +export function OpenFileSelect() { + return window['go']['service']['App']['OpenFileSelect'](); +} + +export function OpenFolderSelect() { + return window['go']['service']['App']['OpenFolderSelect'](); +} + +export function ReadFileContent(arg1) { + return window['go']['service']['App']['ReadFileContent'](arg1); +} + +export function SaveFileSelect() { + return window['go']['service']['App']['SaveFileSelect'](); +} + +export function SearchRequestLogs(arg1) { + return window['go']['service']['App']['SearchRequestLogs'](arg1); +} + +export function SendHttpRequest(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12) { + return window['go']['service']['App']['SendHttpRequest'](arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); +} + +export function SendSocketMessage(arg1, arg2) { + return window['go']['service']['App']['SendSocketMessage'](arg1, arg2); +} + +export function SetLanguage(arg1) { + return window['go']['service']['App']['SetLanguage'](arg1); +} + +export function SetLogLevel(arg1) { + return window['go']['service']['App']['SetLogLevel'](arg1); +} + +export function WindowClose() { + return window['go']['service']['App']['WindowClose'](); +} + +export function WindowMinimise() { + return window['go']['service']['App']['WindowMinimise'](); +} + +export function WindowToggleMaximise() { + return window['go']['service']['App']['WindowToggleMaximise'](); +} + +export function WriteFileContent(arg1, arg2) { + return window['go']['service']['App']['WriteFileContent'](arg1, arg2); +} 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/global/build_debug.go b/global/build_debug.go new file mode 100644 index 0000000..67649e9 --- /dev/null +++ b/global/build_debug.go @@ -0,0 +1,7 @@ +//go:build debug + +package global + +// IsDebug 标记是否为调试模式 +// 仅在使用 -tags debug 编译时为 true +var IsDebug = true diff --git a/global/build_release.go b/global/build_release.go new file mode 100644 index 0000000..db4336b --- /dev/null +++ b/global/build_release.go @@ -0,0 +1,7 @@ +//go:build !debug + +package global + +// IsDebug 标记是否为调试模式 +// 编译后为 false,不允许使用 debug 模式 +var IsDebug = false diff --git a/global/config.go b/global/config.go new file mode 100644 index 0000000..441a8bd --- /dev/null +++ b/global/config.go @@ -0,0 +1,11 @@ +package global + +type GConfig struct { + Language string `json:"language"` + LogDir string `json:"log_dir"` +} + +var GlobalConfig = &GConfig{ + Language: "zh-CN", + LogDir: "logs", +} \ No newline at end of file diff --git a/global/init.go b/global/init.go new file mode 100644 index 0000000..84958d5 --- /dev/null +++ b/global/init.go @@ -0,0 +1,124 @@ +package global + +import ( + "encoding/json" + "fmt" + "io/fs" + "os" + Pa "path" +) + +func Init() { + // 1. 创建必要目录 + dirs := []string{ + "lang", + GlobalConfig.LogDir, + } + for _, dir := range dirs { + if _, err := os.Stat(dir); os.IsNotExist(err) { + os.MkdirAll(dir, 0755) + } + } + + InitLogger() + Log.Info("日志系统初始化完成") + + InitLang() + Log.Infof("语言系统初始化完成,当前语言: %s", useLangPath) +} + +func GetLangTextMap() map[string]string { + langPack, err := GetLangPack() + if err != nil { + Log.Warnf("获取语言包失败: %v", err) + return make(map[string]string) + } + return langPack.Textmap +} + +func GetLangPack() (*LanguagePack, error) { + langPath := Pa.Join(pathLang, useLangPath) + + if cached, ok := langPackCache[langPath]; ok { + return cached, nil + } + + if _, err := os.Stat(langPath); err == nil { + pack, err := tryLoadLangPack(langPath) + if err == nil { + langPackCache[langPath] = pack + } + return pack, err + } + + embedPath := Pa.Join("lang", useLangPath) + pack, err := tryLoadLangPackFromEmbed(embedPath) + if err == nil { + langPackCache[langPath] = pack + } + return pack, err +} + +func tryLoadLangPack(langPath string) (*LanguagePack, error) { + infoPath := Pa.Join(langPath, "info.json") + infoData, err := os.ReadFile(infoPath) + if err != nil { + return nil, err + } + + var langInfo LanguageInfo + if err := json.Unmarshal(infoData, &langInfo); err != nil { + return nil, err + } + + textmapPath := Pa.Join(langPath, "textmap.json") + textmapData, err := os.ReadFile(textmapPath) + if err != nil { + return nil, fmt.Errorf("读取 textmap.json 失败: %v", err) + } + + var textmap map[string]string + if err := json.Unmarshal(textmapData, &textmap); err != nil { + return nil, fmt.Errorf("解析 textmap.json 失败: %v", err) + } + + return &LanguagePack{ + LanguageInfo: langInfo, + Textmap: textmap, + }, nil +} + +func tryLoadLangPackFromEmbed(langPath string) (*LanguagePack, error) { + if LangFS == nil { + return nil, fmt.Errorf("嵌入的文件系统未初始化") + } + + infoPath := Pa.Join(langPath, "info.json") + infoData, err := fs.ReadFile(LangFS, infoPath) + if err != nil { + Log.Warnf("读取嵌入 info.json 失败: %v", err) + return nil, err + } + + var langInfo LanguageInfo + if err := json.Unmarshal(infoData, &langInfo); err != nil { + Log.Warnf("解析嵌入 info.json 失败: %v", err) + return nil, err + } + + textmapPath := Pa.Join(langPath, "textmap.json") + textmapData, err := fs.ReadFile(LangFS, textmapPath) + if err != nil { + return nil, fmt.Errorf("读取嵌入 textmap.json 失败: %v", err) + } + + var textmap map[string]string + if err := json.Unmarshal(textmapData, &textmap); err != nil { + return nil, fmt.Errorf("解析嵌入 textmap.json 失败: %v", err) + } + + return &LanguagePack{ + LanguageInfo: langInfo, + Textmap: textmap, + }, nil +} diff --git a/global/lang.go b/global/lang.go new file mode 100644 index 0000000..29adf61 --- /dev/null +++ b/global/lang.go @@ -0,0 +1,154 @@ +package global + +import ( + "encoding/json" + "io/fs" + "os" + Pa "path" + "path/filepath" +) + +var ( + pathLang = "lang/" + useLangPath = "default" + allLangInfo = []LanguageInfo{} + LangFS fs.FS + langPackCache = map[string]*LanguagePack{} + langCodeToDir = map[string]string{} +) + +type LanguageInfo struct { + LanguageName string `json:"language_name"` + LanguageCode string `json:"language_code"` + TextmapPath string `json:"textmap_path"` + TranslationProgress string `json:"translation_progress"` + Translator string `json:"translator"` + LastUpdated string `json:"last_updated"` + Version string `json:"version"` +} + +type LanguagePack struct { + LanguageInfo + Textmap map[string]string `json:"textmap"` +} + +func InitLang() { + useLang := GlobalConfig.Language + allLangInfo = []LanguageInfo{} + langCodeToDir = map[string]string{} + + // 扫描文件系统语言目录 + scanFileSystemLangs(langCodeToDir) + // 扫描嵌入文件系统语言 + scanEmbeddedLangs(langCodeToDir) + + // 设置当前语言路径 + if dir, ok := langCodeToDir[useLang]; ok { + Log.Infof("找到匹配语言: %s -> %s", useLang, dir) + useLangPath = dir + } else { + Log.Infof("未找到匹配语言 %s,使用默认语言", useLang) + useLangPath = "default" + } +} + +func scanFileSystemLangs(langDirMap map[string]string) { + err := filepath.WalkDir(pathLang, func(path string, d fs.DirEntry, err error) error { + if err != nil { + Log.Warnf("读取语言文件夹失败: %v", err) + return nil + } + if !d.IsDir() || path == pathLang { + return nil + } + return loadLangInfoFromFile(path, langDirMap, "[文件系统]") + }) + if err != nil { + Log.Warnf("遍历文件系统 lang 目录出错: %v", err) + } +} + +func scanEmbeddedLangs(langDirMap map[string]string) { + if LangFS == nil { + Log.Warnf("读取语言文件夹失败: %v", "LangFS 为空") + return + } + + entries, err := fs.ReadDir(LangFS, "lang") + if err != nil { + Log.Warnf("读取语言文件夹失败: %v", err) + return + } + for _, entry := range entries { + if !entry.IsDir() { + continue + } + loadLangInfoFromEmbed(entry.Name(), langDirMap, "[嵌入FS]") + } +} + +func loadLangInfoFromFile(path string, langDirMap map[string]string, source string) error { + infoPath := Pa.Join(path, "info.json") + data, err := os.ReadFile(infoPath) + if err != nil { + Log.Warnf("读取语言文件夹失败: %v", err) + return nil + } + return parseAndAddLangInfo(data, filepath.Base(path), langDirMap, source) +} + +func loadLangInfoFromEmbed(path string, langDirMap map[string]string, source string) { + infoPath := Pa.Join("lang", path, "info.json") + data, err := fs.ReadFile(LangFS, infoPath) + if err != nil { + Log.Warnf("读取语言文件夹失败: %v", err) + return + } + parseAndAddLangInfo(data, filepath.Base(path), langDirMap, source) +} + +func parseAndAddLangInfo(data []byte, dirName string, langDirMap map[string]string, source string) error { + var info LanguageInfo + if err := json.Unmarshal(data, &info); err != nil { + Log.Warnf("解析语言文件夹失败: %v", err) + return nil + } + if info.LanguageCode == "" || info.TextmapPath == "" { + Log.Warnf("语言信息不完整: %s", dirName) + return nil + } + if !containsLang(allLangInfo, info.LanguageCode) { + allLangInfo = append(allLangInfo, info) + langDirMap[info.LanguageCode] = dirName + Log.Debugf("%s 识别到语言: %s (%s) -> %s", source, info.LanguageName, info.LanguageCode, dirName) + } + return nil +} + +func containsLang(slice []LanguageInfo, code string) bool { + for _, s := range slice { + if s.LanguageCode == code { + return true + } + } + return false +} + +func ClearLangCache() { + langPackCache = map[string]*LanguagePack{} +} + +func GetLangInfoList() []LanguageInfo { + return allLangInfo +} + +func UpdateCurrentLangPath() { + useLang := GlobalConfig.Language + if dir, ok := langCodeToDir[useLang]; ok { + Log.Infof("更新语言路径: %s -> %s", useLang, dir) + useLangPath = dir + } else { + Log.Infof("未找到匹配语言 %s,使用默认语言", useLang) + useLangPath = "default" + } +} diff --git a/global/logger.go b/global/logger.go new file mode 100644 index 0000000..cf7e3d3 --- /dev/null +++ b/global/logger.go @@ -0,0 +1,190 @@ +package global + +import ( + "bytes" + "fmt" + "os" + "path" + "path/filepath" + "runtime" + "sort" + "strings" + "time" + + "github.com/sirupsen/logrus" +) + +var Log *logrus.Logger + +const ( + red = 31 + yellow = 33 + blue = 36 + gray = 37 +) + +// LogFormatter 控制台日志格式化器(带颜色) +type LogFormatter struct{} + +func (f *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) { + var levelColor int + switch entry.Level { + case logrus.DebugLevel, logrus.TraceLevel: + levelColor = gray + case logrus.WarnLevel: + levelColor = yellow + case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel: + levelColor = red + default: + levelColor = blue + } + + var b *bytes.Buffer + if entry.Buffer != nil { + b = entry.Buffer + } else { + b = &bytes.Buffer{} + } + + timestamp := entry.Time.Format("2006/01/02 15:04:05") + levelStr := strings.ToUpper(entry.Level.String()) + + if IsDebug && entry.HasCaller() { + funcVal := entry.Caller.Function + fileVal := fmt.Sprintf("%s:%d", path.Base(entry.Caller.File), entry.Caller.Line) + fmt.Fprintf(b, "\033[%dm[%s]\033[0m [%s] \033[%dm[%s]\033[0m %s %s: %s\n", + colorGreen, processName, timestamp, levelColor, levelStr, funcVal, fileVal, entry.Message) + } else { + fmt.Fprintf(b, "\033[%dm[%s]\033[0m [%s] \033[%dm[%s]\033[0m %s\n", + colorGreen, processName, timestamp, levelColor, levelStr, entry.Message) + } + return b.Bytes(), nil +} + +// FileFormatter 文件日志格式化器(不包含 ANSI 颜色代码) +type FileFormatter struct{} + +func (f *FileFormatter) Format(entry *logrus.Entry) ([]byte, error) { + var b *bytes.Buffer + if entry.Buffer != nil { + b = entry.Buffer + } else { + b = &bytes.Buffer{} + } + + timestamp := entry.Time.Format("2006/01/02 15:04:05") + levelStr := strings.ToUpper(entry.Level.String()) + + if IsDebug && entry.HasCaller() { + funcVal := entry.Caller.Function + fileVal := fmt.Sprintf("%s:%d", path.Base(entry.Caller.File), entry.Caller.Line) + fmt.Fprintf(b, "[%s] [%s] [%s] %s %s: %s\n", + processName, timestamp, levelStr, funcVal, fileVal, entry.Message) + } else { + fmt.Fprintf(b, "[%s] [%s] [%s] %s\n", + processName, timestamp, levelStr, entry.Message) + } + return b.Bytes(), nil +} + +var processName string + +func init() { + processName = filepath.Base(os.Args[0]) + processName = strings.TrimSuffix(processName, filepath.Ext(processName)) +} + +func GetProcessName() string { + return processName +} + +// SetLogLevel 动态设置日志等级 +func SetLogLevel(level logrus.Level) { + if Log != nil { + Log.SetLevel(level) + } +} + +// InitLogger 初始化日志系统,每次运行时按启动时间保存日志文件,自动清理旧日志 +func InitLogger() { + Log = logrus.New() + Log.SetFormatter(&LogFormatter{}) + Log.SetLevel(logrus.InfoLevel) + + if IsDebug { + Log.SetReportCaller(true) + } else { + Log.SetReportCaller(false) + } + + logDir := GlobalConfig.LogDir + if err := os.MkdirAll(logDir, 0755); err != nil { + Log.Warnf("无法创建日志目录: %v", err) + } + + cleanOldLogs(logDir, 10) + + startTime := time.Now().Format("2006-01-02_15-04-05") + logFile := filepath.Join(logDir, startTime+".log") + file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + Log.Warnf("无法打开日志文件: %v", err) + } else { + Log.SetOutput(os.Stdout) + Log.AddHook(&FileHook{file: file}) + } +} + +const colorGreen = 32 + +// FileHook 将日志同时写入文件的 Hook +type FileHook struct { + file *os.File +} + +func (h *FileHook) Levels() []logrus.Level { + return logrus.AllLevels +} + +func (h *FileHook) Fire(entry *logrus.Entry) error { + formatter := &FileFormatter{} + line, err := formatter.Format(entry) + if err != nil { + return err + } + _, err = h.file.Write(line) + return err +} + +// cleanOldLogs 清理旧日志文件,保留最新的 maxKeep 个日志 +func cleanOldLogs(logDir string, maxKeep int) { + entries, err := os.ReadDir(logDir) + if err != nil { + return + } + + var logFiles []os.DirEntry + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".log") { + logFiles = append(logFiles, entry) + } + } + + if len(logFiles) <= maxKeep { + return + } + + // 文件名包含时间戳,按文件名排序即按时间排序 + sort.Slice(logFiles, func(i, j int) bool { + return logFiles[i].Name() < logFiles[j].Name() + }) + + filesToDelete := logFiles[:len(logFiles)-maxKeep] + for _, file := range filesToDelete { + os.Remove(filepath.Join(logDir, file.Name())) + } +} + +func GetRuntimeInfo() string { + return fmt.Sprintf("Go %s, OS %s, Arch %s", runtime.Version(), runtime.GOOS, runtime.GOARCH) +} diff --git a/global/version.go b/global/version.go new file mode 100644 index 0000000..f9c2603 --- /dev/null +++ b/global/version.go @@ -0,0 +1,6 @@ +package global + +// Version 版本号,通过 ldflags 注入 +// 编译时: wails build -ldflags "-X webhelper/global.Version=v1.2.3" +// 开发时默认为 "dev" +var Version = "dev" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a739697 --- /dev/null +++ b/go.mod @@ -0,0 +1,45 @@ +module webhelper + +go 1.23 + +require ( + github.com/dop251/goja v0.0.0-20260311135729-065cd970411c + github.com/gorilla/websocket v1.5.3 + github.com/sirupsen/logrus v1.9.3 + github.com/wailsapp/wails/v2 v2.12.0 +) + +require ( + git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect + github.com/bep/debounce v1.2.1 // indirect + github.com/dlclark/regexp2 v1.11.4 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect + github.com/google/uuid v1.6.0 // 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.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect +) + +// replace github.com/wailsapp/wails/v2 v2.10.1 => C:\Users\mogum\go\pkg\mod diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3e03409 --- /dev/null +++ b/go.sum @@ -0,0 +1,104 @@ +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/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= +github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dop251/goja v0.0.0-20260311135729-065cd970411c h1:OcLmPfx1T1RmZVHHFwWMPaZDdRf0DBMZOFMVWJa7Pdk= +github.com/dop251/goja v0.0.0-20260311135729-065cd970411c/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4= +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/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +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/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U= +github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= +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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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.12.0 h1:BHO/kLNWFHYjCzucxbzAYZWUjub1Tvb4cSguQozHn5c= +github.com/wailsapp/wails/v2 v2.12.0/go.mod h1:mo1bzK1DEJrobt7YrBjgxvb5Sihb1mhAY09hppbibQg= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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/lang/default/info.json b/lang/default/info.json new file mode 100644 index 0000000..6a87d07 --- /dev/null +++ b/lang/default/info.json @@ -0,0 +1,9 @@ +{ + "language_name": "简体中文", + "language_code": "zh-CN", + "textmap_path": "textmap.json", + "translation_progress": "100%", + "translator": "MoGuQAQ", + "last_updated": "2025-07-26", + "version": "1.0.0" +} \ No newline at end of file diff --git a/lang/default/textmap.json b/lang/default/textmap.json new file mode 100644 index 0000000..6e70559 --- /dev/null +++ b/lang/default/textmap.json @@ -0,0 +1,160 @@ +{ + "app.tab.webdebug": "网页调试", + "app.tab.jsdebug": "JS调试", + "app.tab.jsonparser": "JSON解析", + "app.tab.socketdebug": "Socket调试", + "app.tab.settings": "程序设置", + + "header.app_name": "WebHelper", + + "webdebug.url_placeholder": "输入请求地址", + "webdebug.btn_send": "发送", + "webdebug.tab.headers": "请求头", + "webdebug.tab.body": "请求体", + "webdebug.tab.cookie": "Cookie", + "webdebug.tab.advanced": "高级设置", + "webdebug.header.key_placeholder": "Key", + "webdebug.header.value_placeholder": "Value", + "webdebug.header.add": "添加请求头", + "webdebug.body.type_text": "文本", + "webdebug.body.type_json": "JSON", + "webdebug.body.type_form": "表单", + "webdebug.body.type_file": "文件", + "webdebug.body.file_placeholder": "选择文件", + "webdebug.body.file_btn": "选择文件", + "webdebug.body.json_placeholder": "输入 JSON 数据", + "webdebug.body.text_placeholder": "输入请求体内容", + "webdebug.body.cookie_placeholder": "Cookie 字符串 (格式: name1=value1; name2=value2)", + "webdebug.advanced.proxy_label": "代理地址", + "webdebug.advanced.proxy_placeholder": "http://127.0.0.1:7890", + "webdebug.advanced.timeout_label": "超时时间", + "webdebug.advanced.timeout_unit": "秒", + "webdebug.advanced.content_type_label": "Content-Type", + "webdebug.advanced.content_type_placeholder": "自动检测", + "webdebug.advanced.insecure_label": "忽略证书", + "webdebug.advanced.log_divider": "日志设置", + "webdebug.advanced.save_log_label": "记录日志", + "webdebug.advanced.log_manage_label": "日志管理", + "webdebug.advanced.view_logs_btn": "查看日志记录", + "webdebug.response.title": "响应结果", + "webdebug.response.tab.body": "响应正文", + "webdebug.response.tab.headers": "响应头", + "webdebug.response.tab.more": "更多信息", + "webdebug.response.body_placeholder": "响应内容将显示在这里", + "webdebug.response.send_to_json": "发送到 JSON 解析器", + "webdebug.response.headers_placeholder": "响应头将显示在这里", + "webdebug.response.status_code_label": "状态码", + "webdebug.response.status_label": "状态", + "webdebug.response.content_type_label": "Content-Type", + "webdebug.response.content_length_label": "内容长度", + "webdebug.response.request_time_label": "请求耗时", + "webdebug.log.title": "请求日志", + "webdebug.log.search_placeholder": "搜索日志...", + "webdebug.log.clear": "清空", + "webdebug.log.empty": "暂无日志", + "webdebug.log.tab.request_info": "请求信息", + "webdebug.log.tab.response_info": "响应信息", + "webdebug.log.request.method_label": "请求方法", + "webdebug.log.request.url_label": "请求地址", + "webdebug.log.request.host_label": "主机", + "webdebug.log.request.path_label": "路径", + "webdebug.log.request.body_label": "请求体", + "webdebug.log.response.status_code_label": "状态码", + "webdebug.log.response.status_label": "状态", + "webdebug.log.response.size_label": "响应大小", + "webdebug.log.response.time_label": "耗时", + "webdebug.log.response.body_label": "响应正文", + "webdebug.log.load_to_debug": "加载到调试页面", + "webdebug.log.select_hint": "选择一条日志查看详情", + "webdebug.msg.url_required": "请输入请求地址", + "webdebug.msg.request_failed": "请求失败: ", + "webdebug.msg.load_logs_failed": "加载日志失败: ", + "webdebug.msg.delete_failed": "删除失败: ", + "webdebug.msg.clear_failed": "清空失败: ", + + "jsdebug.title": "JavaScript 调试器", + "jsdebug.tag.duration": "耗时: ", + "jsdebug.tag.success": "成功", + "jsdebug.tag.failed": "失败", + "jsdebug.btn.run": "运行", + "jsdebug.btn.clear": "清空", + "jsdebug.section.code": "代码", + "jsdebug.section.output": "输出", + "jsdebug.btn.copy": "复制", + "jsdebug.section.return_value": "返回值", + "jsdebug.code_placeholder": "输入 JavaScript 代码", + "jsdebug.output_placeholder": "console.log 输出将显示在这里", + "jsdebug.return_placeholder": "函数返回值将显示在这里", + "jsdebug.msg.code_required": "请输入代码", + "jsdebug.msg.execute_failed": "执行失败: ", + "jsdebug.msg.copy_prefix": "返回值: ", + "jsdebug.msg.copied": "已复制到剪贴板", + "jsdebug.msg.copy_failed": "复制失败", + + "jsonparser.title": "JSON 解析器", + "jsonparser.stats.objects": "对象: ", + "jsonparser.stats.arrays": "数组: ", + "jsonparser.stats.strings": "字符串: ", + "jsonparser.stats.numbers": "数字: ", + "jsonparser.view.tree": "树形", + "jsonparser.view.table": "表格", + "jsonparser.view.raw": "原始", + "jsonparser.btn.copy": "复制", + "jsonparser.btn.clear": "清空", + "jsonparser.section.input": "输入", + "jsonparser.section.result": "解析结果", + "jsonparser.input_placeholder": "粘贴或输入 JSON 内容", + "jsonparser.raw_placeholder": "格式化后的 JSON 将显示在这里", + "jsonparser.empty": "请输入 JSON 数据", + "jsonparser.table.column.key": "Key", + "jsonparser.table.column.index": "Index", + "jsonparser.table.column.value": "Value", + "jsonparser.table.column.action": "操作", + "jsonparser.msg.parse_error": "JSON 解析错误: ", + "jsonparser.msg.copied": "已复制到剪贴板", + "jsonparser.msg.copy_failed": "复制失败", + "jsonparser.msg.received": "已接收 JSON 数据", + + "socket.connection.title": "连接配置", + "socket.connection.status.connected": "已连接", + "socket.connection.status.disconnected": "未连接", + "socket.connection.btn.connect": "连接", + "socket.connection.btn.disconnect": "断开", + "socket.connection.label.protocol": "协议", + "socket.connection.label.address": "地址", + "socket.connection.label.port": "端口", + "socket.connection.label.path": "路径", + "socket.connection.label.headers": "请求头", + "socket.connection.placeholder.host": "主机地址", + "socket.connection.placeholder.port": "8080", + "socket.connection.placeholder.path": "/", + "socket.connection.placeholder.headers": "可选,格式: Key: Value", + "socket.message.title": "消息记录", + "socket.message.auto_scroll": "自动滚动", + "socket.message.btn.clear": "清空", + "socket.message.tag.sent": "发送", + "socket.message.tag.received": "接收", + "socket.message.tag.error": "错误", + "socket.message.tag.system": "系统", + "socket.message.empty": "暂无消息", + "socket.message.placeholder": "输入消息内容", + "socket.message.btn.send": "发送", + "socket.msg.host_required": "请输入主机地址", + "socket.msg.port_invalid": "端口格式错误", + "socket.msg.connect_failed": "连接失败: ", + "socket.msg.disconnect_failed": "断开连接失败: ", + "socket.msg.not_connected": "请先连接", + "socket.msg.send_failed": "发送失败: ", + + "settings.title": "程序设置", + "settings.form.language_label": "语言", + "settings.form.proxy_divider": "代理", + "settings.form.enable_proxy_label": "启用代理", + "settings.form.proxy_address_label": "代理地址", + "settings.form.proxy_address_placeholder": "127.0.0.1", + "settings.form.proxy_port_label": "代理端口", + "settings.form.btn.save": "保存设置", + "settings.form.btn.reset": "恢复默认", + "settings.msg.saved": "设置已保存", + "settings.msg.reset": "设置已重置" +} diff --git a/lang/en/info.json b/lang/en/info.json new file mode 100644 index 0000000..94254d1 --- /dev/null +++ b/lang/en/info.json @@ -0,0 +1,9 @@ +{ + "language_name": "English", + "language_code": "en-US", + "textmap_path": "textmap.json", + "translation_progress": "100%", + "translator": "MoGuQAQ", + "last_updated": "2025-07-26", + "version": "1.0.0" +} diff --git a/lang/en/textmap.json b/lang/en/textmap.json new file mode 100644 index 0000000..b7fd43c --- /dev/null +++ b/lang/en/textmap.json @@ -0,0 +1,160 @@ +{ + "app.tab.webdebug": "Web Debug", + "app.tab.jsdebug": "JS Debug", + "app.tab.jsonparser": "JSON Parser", + "app.tab.socketdebug": "Socket Debug", + "app.tab.settings": "Settings", + + "header.app_name": "WebHelper", + + "webdebug.url_placeholder": "Enter request URL", + "webdebug.btn_send": "Send", + "webdebug.tab.headers": "Headers", + "webdebug.tab.body": "Body", + "webdebug.tab.cookie": "Cookie", + "webdebug.tab.advanced": "Advanced", + "webdebug.header.key_placeholder": "Key", + "webdebug.header.value_placeholder": "Value", + "webdebug.header.add": "Add Header", + "webdebug.body.type_text": "Text", + "webdebug.body.type_json": "JSON", + "webdebug.body.type_form": "Form", + "webdebug.body.type_file": "File", + "webdebug.body.file_placeholder": "Select file", + "webdebug.body.file_btn": "Select File", + "webdebug.body.json_placeholder": "Enter JSON data", + "webdebug.body.text_placeholder": "Enter request body", + "webdebug.body.cookie_placeholder": "Cookie string (format: name1=value1; name2=value2)", + "webdebug.advanced.proxy_label": "Proxy", + "webdebug.advanced.proxy_placeholder": "http://127.0.0.1:7890", + "webdebug.advanced.timeout_label": "Timeout", + "webdebug.advanced.timeout_unit": "sec", + "webdebug.advanced.content_type_label": "Content-Type", + "webdebug.advanced.content_type_placeholder": "Auto detect", + "webdebug.advanced.insecure_label": "Ignore Certificate", + "webdebug.advanced.log_divider": "Log Settings", + "webdebug.advanced.save_log_label": "Save Log", + "webdebug.advanced.log_manage_label": "Log Management", + "webdebug.advanced.view_logs_btn": "View Logs", + "webdebug.response.title": "Response", + "webdebug.response.tab.body": "Body", + "webdebug.response.tab.headers": "Headers", + "webdebug.response.tab.more": "More Info", + "webdebug.response.body_placeholder": "Response body will appear here", + "webdebug.response.send_to_json": "Send to JSON Parser", + "webdebug.response.headers_placeholder": "Response headers will appear here", + "webdebug.response.status_code_label": "Status Code", + "webdebug.response.status_label": "Status", + "webdebug.response.content_type_label": "Content-Type", + "webdebug.response.content_length_label": "Content Length", + "webdebug.response.request_time_label": "Time", + "webdebug.log.title": "Request Logs", + "webdebug.log.search_placeholder": "Search logs...", + "webdebug.log.clear": "Clear", + "webdebug.log.empty": "No logs", + "webdebug.log.tab.request_info": "Request Info", + "webdebug.log.tab.response_info": "Response Info", + "webdebug.log.request.method_label": "Method", + "webdebug.log.request.url_label": "URL", + "webdebug.log.request.host_label": "Host", + "webdebug.log.request.path_label": "Path", + "webdebug.log.request.body_label": "Body", + "webdebug.log.response.status_code_label": "Status Code", + "webdebug.log.response.status_label": "Status", + "webdebug.log.response.size_label": "Size", + "webdebug.log.response.time_label": "Time", + "webdebug.log.response.body_label": "Response Body", + "webdebug.log.load_to_debug": "Load to Debugger", + "webdebug.log.select_hint": "Select a log to view details", + "webdebug.msg.url_required": "Please enter a URL", + "webdebug.msg.request_failed": "Request failed: ", + "webdebug.msg.load_logs_failed": "Failed to load logs: ", + "webdebug.msg.delete_failed": "Delete failed: ", + "webdebug.msg.clear_failed": "Clear failed: ", + + "jsdebug.title": "JavaScript Debugger", + "jsdebug.tag.duration": "Duration: ", + "jsdebug.tag.success": "Success", + "jsdebug.tag.failed": "Failed", + "jsdebug.btn.run": "Run", + "jsdebug.btn.clear": "Clear", + "jsdebug.section.code": "Code", + "jsdebug.section.output": "Output", + "jsdebug.btn.copy": "Copy", + "jsdebug.section.return_value": "Return Value", + "jsdebug.code_placeholder": "Enter JavaScript code", + "jsdebug.output_placeholder": "console.log output will appear here", + "jsdebug.return_placeholder": "Function return value will appear here", + "jsdebug.msg.code_required": "Please enter code", + "jsdebug.msg.execute_failed": "Execution failed: ", + "jsdebug.msg.copy_prefix": "Return value: ", + "jsdebug.msg.copied": "Copied to clipboard", + "jsdebug.msg.copy_failed": "Copy failed", + + "jsonparser.title": "JSON Parser", + "jsonparser.stats.objects": "Objects: ", + "jsonparser.stats.arrays": "Arrays: ", + "jsonparser.stats.strings": "Strings: ", + "jsonparser.stats.numbers": "Numbers: ", + "jsonparser.view.tree": "Tree", + "jsonparser.view.table": "Table", + "jsonparser.view.raw": "Raw", + "jsonparser.btn.copy": "Copy", + "jsonparser.btn.clear": "Clear", + "jsonparser.section.input": "Input", + "jsonparser.section.result": "Result", + "jsonparser.input_placeholder": "Paste or enter JSON content", + "jsonparser.raw_placeholder": "Formatted JSON will appear here", + "jsonparser.empty": "Enter JSON data to parse", + "jsonparser.table.column.key": "Key", + "jsonparser.table.column.index": "Index", + "jsonparser.table.column.value": "Value", + "jsonparser.table.column.action": "Action", + "jsonparser.msg.parse_error": "JSON parse error: ", + "jsonparser.msg.copied": "Copied to clipboard", + "jsonparser.msg.copy_failed": "Copy failed", + "jsonparser.msg.received": "JSON data received", + + "socket.connection.title": "Connection", + "socket.connection.status.connected": "Connected", + "socket.connection.status.disconnected": "Disconnected", + "socket.connection.btn.connect": "Connect", + "socket.connection.btn.disconnect": "Disconnect", + "socket.connection.label.protocol": "Protocol", + "socket.connection.label.address": "Address", + "socket.connection.label.port": "Port", + "socket.connection.label.path": "Path", + "socket.connection.label.headers": "Headers", + "socket.connection.placeholder.host": "Host address", + "socket.connection.placeholder.port": "8080", + "socket.connection.placeholder.path": "/", + "socket.connection.placeholder.headers": "Optional, format: Key: Value", + "socket.message.title": "Messages", + "socket.message.auto_scroll": "Auto Scroll", + "socket.message.btn.clear": "Clear", + "socket.message.tag.sent": "Sent", + "socket.message.tag.received": "Received", + "socket.message.tag.error": "Error", + "socket.message.tag.system": "System", + "socket.message.empty": "No messages", + "socket.message.placeholder": "Enter message", + "socket.message.btn.send": "Send", + "socket.msg.host_required": "Please enter host address", + "socket.msg.port_invalid": "Invalid port", + "socket.msg.connect_failed": "Connection failed: ", + "socket.msg.disconnect_failed": "Disconnect failed: ", + "socket.msg.not_connected": "Not connected", + "socket.msg.send_failed": "Send failed: ", + + "settings.title": "Settings", + "settings.form.language_label": "Language", + "settings.form.proxy_divider": "Proxy", + "settings.form.enable_proxy_label": "Enable Proxy", + "settings.form.proxy_address_label": "Proxy Address", + "settings.form.proxy_address_placeholder": "127.0.0.1", + "settings.form.proxy_port_label": "Proxy Port", + "settings.form.btn.save": "Save Settings", + "settings.form.btn.reset": "Reset to Default", + "settings.msg.saved": "Settings saved", + "settings.msg.reset": "Settings reset" +} diff --git a/lang/ja/info.json b/lang/ja/info.json new file mode 100644 index 0000000..0fbd19f --- /dev/null +++ b/lang/ja/info.json @@ -0,0 +1,9 @@ +{ + "language_name": "日本語", + "language_code": "ja-JP", + "textmap_path": "textmap.json", + "translation_progress": "100%", + "translator": "MoGuQAQ", + "last_updated": "2025-07-26", + "version": "1.0.0" +} diff --git a/lang/ja/textmap.json b/lang/ja/textmap.json new file mode 100644 index 0000000..9164821 --- /dev/null +++ b/lang/ja/textmap.json @@ -0,0 +1,160 @@ +{ + "app.tab.webdebug": "ウェブデバッグ", + "app.tab.jsdebug": "JSデバッグ", + "app.tab.jsonparser": "JSONパーサー", + "app.tab.socketdebug": "Socketデバッグ", + "app.tab.settings": "設定", + + "header.app_name": "WebHelper", + + "webdebug.url_placeholder": "リクエストURLを入力", + "webdebug.btn_send": "送信", + "webdebug.tab.headers": "ヘッダー", + "webdebug.tab.body": "ボディ", + "webdebug.tab.cookie": "Cookie", + "webdebug.tab.advanced": "詳細設定", + "webdebug.header.key_placeholder": "キー", + "webdebug.header.value_placeholder": "値", + "webdebug.header.add": "ヘッダーを追加", + "webdebug.body.type_text": "テキスト", + "webdebug.body.type_json": "JSON", + "webdebug.body.type_form": "フォーム", + "webdebug.body.type_file": "ファイル", + "webdebug.body.file_placeholder": "ファイルを選択", + "webdebug.body.file_btn": "ファイルを選択", + "webdebug.body.json_placeholder": "JSONデータを入力", + "webdebug.body.text_placeholder": "リクエストボディを入力", + "webdebug.body.cookie_placeholder": "Cookie文字列(形式: name1=value1; name2=value2)", + "webdebug.advanced.proxy_label": "プロキシ", + "webdebug.advanced.proxy_placeholder": "http://127.0.0.1:7890", + "webdebug.advanced.timeout_label": "タイムアウト", + "webdebug.advanced.timeout_unit": "秒", + "webdebug.advanced.content_type_label": "Content-Type", + "webdebug.advanced.content_type_placeholder": "自動検出", + "webdebug.advanced.insecure_label": "証明書を無視", + "webdebug.advanced.log_divider": "ログ設定", + "webdebug.advanced.save_log_label": "ログを保存", + "webdebug.advanced.log_manage_label": "ログ管理", + "webdebug.advanced.view_logs_btn": "ログを表示", + "webdebug.response.title": "レスポンス", + "webdebug.response.tab.body": "本文", + "webdebug.response.tab.headers": "ヘッダー", + "webdebug.response.tab.more": "詳細情報", + "webdebug.response.body_placeholder": "レスポンス本文がここに表示されます", + "webdebug.response.send_to_json": "JSONパーサーに送信", + "webdebug.response.headers_placeholder": "レスポンスヘッダーがここに表示されます", + "webdebug.response.status_code_label": "ステータスコード", + "webdebug.response.status_label": "ステータス", + "webdebug.response.content_type_label": "Content-Type", + "webdebug.response.content_length_label": "コンテンツ長", + "webdebug.response.request_time_label": "所要時間", + "webdebug.log.title": "リクエストログ", + "webdebug.log.search_placeholder": "ログを検索...", + "webdebug.log.clear": "クリア", + "webdebug.log.empty": "ログなし", + "webdebug.log.tab.request_info": "リクエスト情報", + "webdebug.log.tab.response_info": "レスポンス情報", + "webdebug.log.request.method_label": "メソッド", + "webdebug.log.request.url_label": "URL", + "webdebug.log.request.host_label": "ホスト", + "webdebug.log.request.path_label": "パス", + "webdebug.log.request.body_label": "ボディ", + "webdebug.log.response.status_code_label": "ステータスコード", + "webdebug.log.response.status_label": "ステータス", + "webdebug.log.response.size_label": "サイズ", + "webdebug.log.response.time_label": "所要時間", + "webdebug.log.response.body_label": "レスポンス本文", + "webdebug.log.load_to_debug": "デバッガーに読み込む", + "webdebug.log.select_hint": "ログを選択して詳細を表示", + "webdebug.msg.url_required": "URLを入力してください", + "webdebug.msg.request_failed": "リクエスト失敗: ", + "webdebug.msg.load_logs_failed": "ログの読み込みに失敗: ", + "webdebug.msg.delete_failed": "削除に失敗: ", + "webdebug.msg.clear_failed": "クリアに失敗: ", + + "jsdebug.title": "JavaScriptデバッガー", + "jsdebug.tag.duration": "所要時間: ", + "jsdebug.tag.success": "成功", + "jsdebug.tag.failed": "失敗", + "jsdebug.btn.run": "実行", + "jsdebug.btn.clear": "クリア", + "jsdebug.section.code": "コード", + "jsdebug.section.output": "出力", + "jsdebug.btn.copy": "コピー", + "jsdebug.section.return_value": "戻り値", + "jsdebug.code_placeholder": "JavaScriptコードを入力", + "jsdebug.output_placeholder": "console.log出力がここに表示されます", + "jsdebug.return_placeholder": "関数の戻り値がここに表示されます", + "jsdebug.msg.code_required": "コードを入力してください", + "jsdebug.msg.execute_failed": "実行失敗: ", + "jsdebug.msg.copy_prefix": "戻り値: ", + "jsdebug.msg.copied": "クリップボードにコピーしました", + "jsdebug.msg.copy_failed": "コピーに失敗しました", + + "jsonparser.title": "JSONパーサー", + "jsonparser.stats.objects": "オブジェクト: ", + "jsonparser.stats.arrays": "配列: ", + "jsonparser.stats.strings": "文字列: ", + "jsonparser.stats.numbers": "数値: ", + "jsonparser.view.tree": "ツリー", + "jsonparser.view.table": "テーブル", + "jsonparser.view.raw": "生データ", + "jsonparser.btn.copy": "コピー", + "jsonparser.btn.clear": "クリア", + "jsonparser.section.input": "入力", + "jsonparser.section.result": "解析結果", + "jsonparser.input_placeholder": "JSONコンテンツを貼り付けまたは入力", + "jsonparser.raw_placeholder": "フォーマットされたJSONがここに表示されます", + "jsonparser.empty": "JSONデータを入力してください", + "jsonparser.table.column.key": "キー", + "jsonparser.table.column.index": "インデックス", + "jsonparser.table.column.value": "値", + "jsonparser.table.column.action": "操作", + "jsonparser.msg.parse_error": "JSON解析エラー: ", + "jsonparser.msg.copied": "クリップボードにコピーしました", + "jsonparser.msg.copy_failed": "コピーに失敗しました", + "jsonparser.msg.received": "JSONデータを受信しました", + + "socket.connection.title": "接続設定", + "socket.connection.status.connected": "接続済み", + "socket.connection.status.disconnected": "未接続", + "socket.connection.btn.connect": "接続", + "socket.connection.btn.disconnect": "切断", + "socket.connection.label.protocol": "プロトコル", + "socket.connection.label.address": "アドレス", + "socket.connection.label.port": "ポート", + "socket.connection.label.path": "パス", + "socket.connection.label.headers": "ヘッダー", + "socket.connection.placeholder.host": "ホストアドレス", + "socket.connection.placeholder.port": "8080", + "socket.connection.placeholder.path": "/", + "socket.connection.placeholder.headers": "オプション、形式: Key: Value", + "socket.message.title": "メッセージ履歴", + "socket.message.auto_scroll": "自動スクロール", + "socket.message.btn.clear": "クリア", + "socket.message.tag.sent": "送信", + "socket.message.tag.received": "受信", + "socket.message.tag.error": "エラー", + "socket.message.tag.system": "システム", + "socket.message.empty": "メッセージなし", + "socket.message.placeholder": "メッセージを入力", + "socket.message.btn.send": "送信", + "socket.msg.host_required": "ホストアドレスを入力してください", + "socket.msg.port_invalid": "ポート番号が無効です", + "socket.msg.connect_failed": "接続失敗: ", + "socket.msg.disconnect_failed": "切断失敗: ", + "socket.msg.not_connected": "未接続です", + "socket.msg.send_failed": "送信失敗: ", + + "settings.title": "設定", + "settings.form.language_label": "言語", + "settings.form.proxy_divider": "プロキシ", + "settings.form.enable_proxy_label": "プロキシを有効にする", + "settings.form.proxy_address_label": "プロキシアドレス", + "settings.form.proxy_address_placeholder": "127.0.0.1", + "settings.form.proxy_port_label": "プロキシポート", + "settings.form.btn.save": "設定を保存", + "settings.form.btn.reset": "デフォルトに戻す", + "settings.msg.saved": "設定を保存しました", + "settings.msg.reset": "設定をリセットしました" +} diff --git a/lang/ko/info.json b/lang/ko/info.json new file mode 100644 index 0000000..a70bc38 --- /dev/null +++ b/lang/ko/info.json @@ -0,0 +1,9 @@ +{ + "language_name": "한국어", + "language_code": "ko-KR", + "textmap_path": "textmap.json", + "translation_progress": "100%", + "translator": "MoGuQAQ", + "last_updated": "2025-07-26", + "version": "1.0.0" +} diff --git a/lang/ko/textmap.json b/lang/ko/textmap.json new file mode 100644 index 0000000..62734fa --- /dev/null +++ b/lang/ko/textmap.json @@ -0,0 +1,160 @@ +{ + "app.tab.webdebug": "웹 디버그", + "app.tab.jsdebug": "JS 디버그", + "app.tab.jsonparser": "JSON 파서", + "app.tab.socketdebug": "Socket 디버그", + "app.tab.settings": "설정", + + "header.app_name": "WebHelper", + + "webdebug.url_placeholder": "요청 URL 입력", + "webdebug.btn_send": "보내기", + "webdebug.tab.headers": "헤더", + "webdebug.tab.body": "본문", + "webdebug.tab.cookie": "Cookie", + "webdebug.tab.advanced": "고급 설정", + "webdebug.header.key_placeholder": "키", + "webdebug.header.value_placeholder": "값", + "webdebug.header.add": "헤더 추가", + "webdebug.body.type_text": "텍스트", + "webdebug.body.type_json": "JSON", + "webdebug.body.type_form": "폼", + "webdebug.body.type_file": "파일", + "webdebug.body.file_placeholder": "파일 선택", + "webdebug.body.file_btn": "파일 선택", + "webdebug.body.json_placeholder": "JSON 데이터 입력", + "webdebug.body.text_placeholder": "요청 본문 입력", + "webdebug.body.cookie_placeholder": "Cookie 문자열 (형식: name1=value1; name2=value2)", + "webdebug.advanced.proxy_label": "프록시", + "webdebug.advanced.proxy_placeholder": "http://127.0.0.1:7890", + "webdebug.advanced.timeout_label": "타임아웃", + "webdebug.advanced.timeout_unit": "초", + "webdebug.advanced.content_type_label": "Content-Type", + "webdebug.advanced.content_type_placeholder": "자동 감지", + "webdebug.advanced.insecure_label": "인증서 무시", + "webdebug.advanced.log_divider": "로그 설정", + "webdebug.advanced.save_log_label": "로그 저장", + "webdebug.advanced.log_manage_label": "로그 관리", + "webdebug.advanced.view_logs_btn": "로그 보기", + "webdebug.response.title": "응답", + "webdebug.response.tab.body": "본문", + "webdebug.response.tab.headers": "헤더", + "webdebug.response.tab.more": "추가 정보", + "webdebug.response.body_placeholder": "응답 본문이 여기에 표시됩니다", + "webdebug.response.send_to_json": "JSON 파서로 보내기", + "webdebug.response.headers_placeholder": "응답 헤더가 여기에 표시됩니다", + "webdebug.response.status_code_label": "상태 코드", + "webdebug.response.status_label": "상태", + "webdebug.response.content_type_label": "Content-Type", + "webdebug.response.content_length_label": "콘텐츠 길이", + "webdebug.response.request_time_label": "소요 시간", + "webdebug.log.title": "요청 로그", + "webdebug.log.search_placeholder": "로그 검색...", + "webdebug.log.clear": "지우기", + "webdebug.log.empty": "로그 없음", + "webdebug.log.tab.request_info": "요청 정보", + "webdebug.log.tab.response_info": "응답 정보", + "webdebug.log.request.method_label": "메서드", + "webdebug.log.request.url_label": "URL", + "webdebug.log.request.host_label": "호스트", + "webdebug.log.request.path_label": "경로", + "webdebug.log.request.body_label": "본문", + "webdebug.log.response.status_code_label": "상태 코드", + "webdebug.log.response.status_label": "상태", + "webdebug.log.response.size_label": "크기", + "webdebug.log.response.time_label": "소요 시간", + "webdebug.log.response.body_label": "응답 본문", + "webdebug.log.load_to_debug": "디버거에 로드", + "webdebug.log.select_hint": "로그를 선택하여 상세 정보 보기", + "webdebug.msg.url_required": "URL을 입력하세요", + "webdebug.msg.request_failed": "요청 실패: ", + "webdebug.msg.load_logs_failed": "로그 로드 실패: ", + "webdebug.msg.delete_failed": "삭제 실패: ", + "webdebug.msg.clear_failed": "지우기 실패: ", + + "jsdebug.title": "JavaScript 디버거", + "jsdebug.tag.duration": "소요 시간: ", + "jsdebug.tag.success": "성공", + "jsdebug.tag.failed": "실패", + "jsdebug.btn.run": "실행", + "jsdebug.btn.clear": "지우기", + "jsdebug.section.code": "코드", + "jsdebug.section.output": "출력", + "jsdebug.btn.copy": "복사", + "jsdebug.section.return_value": "반환값", + "jsdebug.code_placeholder": "JavaScript 코드 입력", + "jsdebug.output_placeholder": "console.log 출력이 여기에 표시됩니다", + "jsdebug.return_placeholder": "함수 반환값이 여기에 표시됩니다", + "jsdebug.msg.code_required": "코드를 입력하세요", + "jsdebug.msg.execute_failed": "실행 실패: ", + "jsdebug.msg.copy_prefix": "반환값: ", + "jsdebug.msg.copied": "클립보드에 복사됨", + "jsdebug.msg.copy_failed": "복사 실패", + + "jsonparser.title": "JSON 파서", + "jsonparser.stats.objects": "객체: ", + "jsonparser.stats.arrays": "배열: ", + "jsonparser.stats.strings": "문자열: ", + "jsonparser.stats.numbers": "숫자: ", + "jsonparser.view.tree": "트리", + "jsonparser.view.table": "테이블", + "jsonparser.view.raw": "원본", + "jsonparser.btn.copy": "복사", + "jsonparser.btn.clear": "지우기", + "jsonparser.section.input": "입력", + "jsonparser.section.result": "파싱 결과", + "jsonparser.input_placeholder": "JSON 콘텐츠를 붙여넣거나 입력", + "jsonparser.raw_placeholder": "포맷된 JSON이 여기에 표시됩니다", + "jsonparser.empty": "JSON 데이터를 입력하세요", + "jsonparser.table.column.key": "키", + "jsonparser.table.column.index": "인덱스", + "jsonparser.table.column.value": "값", + "jsonparser.table.column.action": "작업", + "jsonparser.msg.parse_error": "JSON 파싱 오류: ", + "jsonparser.msg.copied": "클립보드에 복사됨", + "jsonparser.msg.copy_failed": "복사 실패", + "jsonparser.msg.received": "JSON 데이터 수신됨", + + "socket.connection.title": "연결 설정", + "socket.connection.status.connected": "연결됨", + "socket.connection.status.disconnected": "미연결", + "socket.connection.btn.connect": "연결", + "socket.connection.btn.disconnect": "해제", + "socket.connection.label.protocol": "프로토콜", + "socket.connection.label.address": "주소", + "socket.connection.label.port": "포트", + "socket.connection.label.path": "경로", + "socket.connection.label.headers": "헤더", + "socket.connection.placeholder.host": "호스트 주소", + "socket.connection.placeholder.port": "8080", + "socket.connection.placeholder.path": "/", + "socket.connection.placeholder.headers": "선택, 형식: Key: Value", + "socket.message.title": "메시지 기록", + "socket.message.auto_scroll": "자동 스크롤", + "socket.message.btn.clear": "지우기", + "socket.message.tag.sent": "보냄", + "socket.message.tag.received": "수신", + "socket.message.tag.error": "오류", + "socket.message.tag.system": "시스템", + "socket.message.empty": "메시지 없음", + "socket.message.placeholder": "메시지 입력", + "socket.message.btn.send": "보내기", + "socket.msg.host_required": "호스트 주소를 입력하세요", + "socket.msg.port_invalid": "잘못된 포트", + "socket.msg.connect_failed": "연결 실패: ", + "socket.msg.disconnect_failed": "해제 실패: ", + "socket.msg.not_connected": "연결되지 않음", + "socket.msg.send_failed": "전송 실패: ", + + "settings.title": "설정", + "settings.form.language_label": "언어", + "settings.form.proxy_divider": "프록시", + "settings.form.enable_proxy_label": "프록시 활성화", + "settings.form.proxy_address_label": "프록시 주소", + "settings.form.proxy_address_placeholder": "127.0.0.1", + "settings.form.proxy_port_label": "프록시 포트", + "settings.form.btn.save": "설정 저장", + "settings.form.btn.reset": "기본값으로 복원", + "settings.msg.saved": "설정이 저장되었습니다", + "settings.msg.reset": "설정이 초기화되었습니다" +} diff --git a/logger/logger.go b/logger/logger.go new file mode 100644 index 0000000..5bc398e --- /dev/null +++ b/logger/logger.go @@ -0,0 +1,168 @@ +package logger + +import ( + "bytes" + "fmt" + "os" + "path" + "path/filepath" + "sort" + "strings" + "time" + + "github.com/sirupsen/logrus" +) + +var Log *logrus.Logger + +// ANSI 颜色代码 +const ( + red = 31 + yellow = 33 + blue = 36 + gray = 37 +) + +// LogFormatter 自定义日志格式化器 +type LogFormatter struct{} + +func (f *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) { + var levelColor int + switch entry.Level { + case logrus.DebugLevel, logrus.TraceLevel: + levelColor = gray + case logrus.WarnLevel: + levelColor = yellow + case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel: + levelColor = red + default: + levelColor = blue + } + + var b *bytes.Buffer + if entry.Buffer != nil { + b = entry.Buffer + } else { + b = &bytes.Buffer{} + } + + timestamp := entry.Time.Format("2006/01/02 15:04:05") + if entry.HasCaller() { + funcVal := entry.Caller.Function + fileVal := fmt.Sprintf("%s:%d", path.Base(entry.Caller.File), entry.Caller.Line) + fmt.Fprintf(b, "%s[%s] \033[%dm[%s]\033[0m %s %s: %s\n", + "[Logger]", timestamp, levelColor, entry.Level, funcVal, fileVal, entry.Message) + } else { + fmt.Fprintf(b, "%s[%s] \033[%dm[%s]\033[0m %s\n", + "[Logger]", timestamp, levelColor, entry.Level, entry.Message) + } + return b.Bytes(), nil +} + +// FileFormatter 文件日志格式化器(不包含 ANSI 颜色代码) +type FileFormatter struct{} + +func (f *FileFormatter) Format(entry *logrus.Entry) ([]byte, error) { + var b *bytes.Buffer + if entry.Buffer != nil { + b = entry.Buffer + } else { + b = &bytes.Buffer{} + } + + timestamp := entry.Time.Format("2006/01/02 15:04:05") + if entry.HasCaller() { + funcVal := entry.Caller.Function + fileVal := fmt.Sprintf("%s:%d", path.Base(entry.Caller.File), entry.Caller.Line) + fmt.Fprintf(b, "%s[%s] [%s] %s %s: %s\n", + "[Logger]", timestamp, entry.Level, funcVal, fileVal, entry.Message) + } else { + fmt.Fprintf(b, "%s[%s] [%s] %s\n", + "[Logger]", timestamp, entry.Level, entry.Message) + } + return b.Bytes(), nil +} + +// InitLogger 初始化日志系统 +// 每次运行时按照启动时间保存日志文件,并自动删除最新十个日志之前的日志 +func InitLogger() { + Log = logrus.New() + Log.SetReportCaller(true) + Log.SetFormatter(&LogFormatter{}) + Log.SetLevel(logrus.DebugLevel) + + // 确保日志目录存在 + logDir := "logs" + if err := os.MkdirAll(logDir, 0755); err != nil { + Log.Warnf("无法创建日志目录: %v", err) + } + + // 清理旧日志(保留最新10个) + cleanOldLogs(logDir, 10) + + // 创建按启动时间命名的日志文件 + startTime := time.Now().Format("2006-01-02_15-04-05") + logFile := filepath.Join(logDir, startTime+".log") + file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + Log.Warnf("无法打开日志文件: %v", err) + } else { + // 控制台输出使用带颜色的格式 + Log.SetOutput(os.Stdout) + + // 文件输出使用无颜色的格式,通过 Hook 实现 + Log.AddHook(&FileHook{file: file}) + } +} + +// FileHook 将日志同时写入文件的 Hook +type FileHook struct { + file *os.File +} + +func (h *FileHook) Levels() []logrus.Level { + return logrus.AllLevels +} + +func (h *FileHook) Fire(entry *logrus.Entry) error { + formatter := &FileFormatter{} + line, err := formatter.Format(entry) + if err != nil { + return err + } + _, err = h.file.Write(line) + return err +} + +// cleanOldLogs 清理旧日志文件,保留最新的 maxKeep 个日志 +func cleanOldLogs(logDir string, maxKeep int) { + entries, err := os.ReadDir(logDir) + if err != nil { + return + } + + // 收集所有 .log 文件 + var logFiles []os.DirEntry + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".log") { + logFiles = append(logFiles, entry) + } + } + + // 如果日志文件数量未超过限制,无需清理 + if len(logFiles) <= maxKeep { + return + } + + // 按文件名排序(文件名包含时间戳,排序即按时间排序) + sort.Slice(logFiles, func(i, j int) bool { + return logFiles[i].Name() < logFiles[j].Name() + }) + + // 删除超出限制的旧日志 + filesToDelete := logFiles[:len(logFiles)-maxKeep] + for _, file := range filesToDelete { + filePath := filepath.Join(logDir, file.Name()) + os.Remove(filePath) + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..8a547f4 --- /dev/null +++ b/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "embed" + "webhelper/global" + "webhelper/service" + + "github.com/wailsapp/wails/v2" + "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" +) + +//go:embed all:frontend/dist +var assets embed.FS + +//go:embed lang/* +var langFS embed.FS + +func main() { + global.LangFS = langFS + global.Init() + + appName := global.GetProcessName() + App := service.NewApp() + + err := wails.Run(&options.App{ + Title: appName, + Width: 1024, + Height: 768, + MinWidth: 800, + MinHeight: 600, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, + OnStartup: App.Startup, + Frameless: true, + Bind: []interface{}{ + App, + }, + }) + + if err != nil { + println("Error:", err.Error()) + } +} diff --git a/service/router.go b/service/router.go new file mode 100644 index 0000000..f290db1 --- /dev/null +++ b/service/router.go @@ -0,0 +1,517 @@ +package service + +import ( + "context" + "fmt" + "os" + "path/filepath" + goruntime "runtime" + "sort" + "strings" + "time" + + "webhelper/api" + "webhelper/global" + + "github.com/sirupsen/logrus" + "github.com/wailsapp/wails/v2/pkg/runtime" +) + +type App struct { + ctx context.Context +} + +func NewApp() *App { + return &App{} +} + +func (a *App) Startup(ctx context.Context) { + a.ctx = ctx +} + +func (a *App) Greet(name string) string { + return greet(name) +} + +func (a *App) Flashtime() { + flashtime(a) +} + +func (a *App) Gettestjson() string { + return getJSONString() +} + +func (a *App) GetLangTextMap() map[string]string { + return global.GetLangTextMap() +} + +func (a *App) GetLangPack() *global.LanguagePack { + langPack, err := global.GetLangPack() + if err != nil { + global.Log.Warnf("获取语言包失败: %v", err) + return nil + } + return langPack +} + +func (a *App) GetALLLang() []global.LanguageInfo { + return global.GetLangInfoList() +} + +func (a *App) SetLanguage(langCode string) bool { + global.GlobalConfig.Language = langCode + global.ClearLangCache() + global.UpdateCurrentLangPath() + return true +} + +func (a *App) GetCurrentLang() string { + return global.GlobalConfig.Language +} + +func (a *App) GetLogFiles() []string { + logDir := global.GlobalConfig.LogDir + entries, err := os.ReadDir(logDir) + if err != nil { + global.Log.Warnf("读取日志目录失败: %v", err) + return []string{} + } + + var logFiles []string + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".log") { + logFiles = append(logFiles, entry.Name()) + } + } + + sort.Sort(sort.Reverse(sort.StringSlice(logFiles))) + return logFiles +} + +func (a *App) GetLogFileContent(filename string) string { + // 安全检查:防止路径遍历 + if strings.Contains(filename, "..") || strings.Contains(filename, "/") || strings.Contains(filename, "\\") { + global.Log.Warnf("非法的日志文件名: %s", filename) + return "" + } + + logPath := filepath.Join(global.GlobalConfig.LogDir, filename) + data, err := os.ReadFile(logPath) + if err != nil { + global.Log.Warnf("读取日志文件失败: %v", err) + return "" + } + return string(data) +} + +func (a *App) SetLogLevel(level string) bool { + switch strings.ToLower(level) { + case "debug": + global.SetLogLevel(logrus.DebugLevel) + global.Log.Debug("日志等级已切换为 Debug") + case "info": + global.SetLogLevel(logrus.InfoLevel) + global.Log.Info("日志等级已切换为 Info") + case "warn": + global.SetLogLevel(logrus.WarnLevel) + global.Log.Warn("日志等级已切换为 Warn") + case "error": + global.SetLogLevel(logrus.ErrorLevel) + global.Log.Error("日志等级已切换为 Error") + default: + global.Log.Warnf("未知的日志等级: %s", level) + return false + } + return true +} + +func (a *App) GetLogLevel() string { + if global.Log == nil { + return "info" + } + return strings.ToUpper(global.Log.GetLevel().String()) +} + +func (a *App) WindowMinimise() { + runtime.WindowMinimise(a.ctx) +} + +func (a *App) WindowToggleMaximise() { + runtime.WindowToggleMaximise(a.ctx) +} + +func (a *App) WindowClose() { + runtime.Quit(a.ctx) +} + +func (a *App) GetSystemInfo() SystemInfo { + hostname, _ := os.Hostname() + + return SystemInfo{ + OS: goruntime.GOOS, + Arch: goruntime.GOARCH, + NumCPU: goruntime.NumCPU(), + Hostname: hostname, + GoVer: goruntime.Version(), + Time: time.Now().Format(time.DateTime), + ProcessName: global.GetProcessName(), + } +} + +func (a *App) GetProcessName() string { + return global.GetProcessName() +} + +func (a *App) GetVersion() string { + return global.Version +} + +func (a *App) OpenFileSelect() string { + file, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{ + Title: "选择文件", + Filters: []runtime.FileFilter{ + {DisplayName: "所有文件", Pattern: "*.*"}, + {DisplayName: "文本文件", Pattern: "*.txt"}, + {DisplayName: "JSON 文件", Pattern: "*.json"}, + }, + }) + if err != nil { + global.Log.Warnf("打开文件对话框失败: %v", err) + return "" + } + return file +} + +func (a *App) OpenFolderSelect() string { + folder, err := runtime.OpenDirectoryDialog(a.ctx, runtime.OpenDialogOptions{ + Title: "选择目录", + }) + if err != nil { + global.Log.Warnf("打开目录对话框失败: %v", err) + return "" + } + return folder +} + +func (a *App) SaveFileSelect() string { + file, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{ + Title: "保存文件", + Filters: []runtime.FileFilter{ + {DisplayName: "文本文件", Pattern: "*.txt"}, + {DisplayName: "JSON 文件", Pattern: "*.json"}, + }, + }) + if err != nil { + global.Log.Warnf("打开保存对话框失败: %v", err) + return "" + } + return file +} + +func (a *App) ReadFileContent(path string) string { + if path == "" { + return "" + } + data, err := os.ReadFile(path) + if err != nil { + global.Log.Warnf("读取文件失败: %v", err) + return fmt.Sprintf("读取失败: %v", err) + } + return string(data) +} + +func (a *App) WriteFileContent(path string, content string) bool { + if path == "" { + return false + } + err := os.WriteFile(path, []byte(content), 0644) + if err != nil { + global.Log.Warnf("写入文件失败: %v", err) + return false + } + global.Log.Infof("文件写入成功: %s", path) + return true +} + +func (a *App) Notify(title string, message string) { + runtime.EventsEmit(a.ctx, "notification", map[string]string{ + "title": title, + "message": message, + }) +} + +// SendHttpRequest 发送 HTTP 请求 +func (a *App) SendHttpRequest(method, url, headers, cookies, proxy, bodyType, body, filePath, contentType string, timeout int, insecure bool, saveLog bool) *api.HttpResponse { + // 解析请求头 + headerMap := api.ParseHeaders(headers) + + // 解析 Cookie + cookieMap := api.ParseCookies(cookies) + + req := &api.HttpRequest{ + Method: method, + URL: url, + Headers: headerMap, + Cookies: cookieMap, + Proxy: proxy, + BodyType: bodyType, + Body: body, + FilePath: filePath, + ContentType: contentType, + Timeout: timeout, + Insecure: insecure, + } + + global.Log.Infof("发送 HTTP 请求: %s %s", method, url) + resp := api.SendRequest(req) + + if resp.Error != "" { + global.Log.Warnf("HTTP 请求失败: %s", resp.Error) + } else { + global.Log.Infof("HTTP 请求完成: %d %s (%v)", resp.StatusCode, resp.Status, resp.Time) + } + + // 保存日志 + if saveLog { + logManager := api.GetLogManager() + requestLog := api.CreateLogFromResponse(req, resp) + if err := logManager.AddLog(requestLog); err != nil { + global.Log.Warnf("保存请求日志失败: %v", err) + } + } + + return resp +} + +// GetRequestLogs 获取请求日志列表 +func (a *App) GetRequestLogs() []*api.RequestLog { + logManager := api.GetLogManager() + return logManager.GetLogs() +} + +// GetRequestLog 获取单条请求日志 +func (a *App) GetRequestLog(id int) *api.RequestLog { + logManager := api.GetLogManager() + return logManager.GetLog(id) +} + +// DeleteRequestLog 删除请求日志 +func (a *App) DeleteRequestLog(id int) bool { + logManager := api.GetLogManager() + if err := logManager.DeleteLog(id); err != nil { + global.Log.Warnf("删除请求日志失败: %v", err) + return false + } + return true +} + +// ClearRequestLogs 清空请求日志 +func (a *App) ClearRequestLogs() bool { + logManager := api.GetLogManager() + if err := logManager.ClearLogs(); err != nil { + global.Log.Warnf("清空请求日志失败: %v", err) + return false + } + return true +} + +// SearchRequestLogs 搜索请求日志 +func (a *App) SearchRequestLogs(keyword string) []*api.RequestLog { + logManager := api.GetLogManager() + return logManager.SearchLogs(keyword) +} + +// ExecuteJs 执行 JavaScript 代码 +func (a *App) ExecuteJs(code string) *api.JsExecResult { + global.Log.Infof("执行 JavaScript 代码,长度: %d", len(code)) + result := api.ExecuteJs(code) + + if result.Success { + global.Log.Infof("JavaScript 执行成功,耗时: %s", result.Duration) + } else { + global.Log.Warnf("JavaScript 执行失败: %s", result.Error) + } + + return result +} + +// ConnectSocket 连接 WebSocket 或 TCP +func (a *App) ConnectSocket(protocol, host string, port int, path string, headers string) bool { + // 确保 path 以 / 开头 + if path == "" { + path = "/" + } else if path[0] != '/' { + path = "/" + path + } + + var url string + if protocol == "tcp" { + url = fmt.Sprintf("%s:%d", host, port) + } else { + scheme := "ws" + if protocol == "wss" { + scheme = "wss" + } + url = fmt.Sprintf("%s://%s:%d%s", scheme, host, port, path) + } + + global.Log.Infof("连接 %s: %s", protocol, url) + + // 发送连接中事件 + runtime.EventsEmit(a.ctx, "socket-status", map[string]string{ + "type": "connecting", + "content": "正在连接...", + }) + + if protocol == "tcp" { + // TCP 连接 + config := &api.TCPConfig{ + Host: host, + Port: port, + } + + err := api.ConnectTCP(config, + // onMessage + func(message string) { + runtime.EventsEmit(a.ctx, "socket-message", map[string]string{ + "type": "received", + "content": message, + }) + }, + // onError + func(err error) { + // 连接失败时的错误回调 + runtime.EventsEmit(a.ctx, "socket-message", map[string]string{ + "type": "error", + "content": err.Error(), + }) + }, + // onClose + func() { + runtime.EventsEmit(a.ctx, "socket-status", map[string]string{ + "type": "disconnected", + "content": "连接已断开", + }) + }, + ) + + if err != nil { + global.Log.Warnf("TCP 连接失败: %v", err) + runtime.EventsEmit(a.ctx, "socket-message", map[string]string{ + "type": "error", + "content": "连接失败: " + err.Error(), + }) + return false + } + } else { + // WebSocket 连接 + headerMap := api.ParseHeaders(headers) + config := &api.WebSocketConfig{ + URL: url, + Headers: headerMap, + } + + err := api.ConnectWebSocket(config, + // onMessage + func(message string) { + runtime.EventsEmit(a.ctx, "socket-message", map[string]string{ + "type": "received", + "content": message, + }) + }, + // onError + func(err error) { + // 连接失败时的错误回调 + runtime.EventsEmit(a.ctx, "socket-message", map[string]string{ + "type": "error", + "content": err.Error(), + }) + }, + // onClose + func() { + runtime.EventsEmit(a.ctx, "socket-status", map[string]string{ + "type": "disconnected", + "content": "连接已断开", + }) + }, + ) + + if err != nil { + global.Log.Warnf("WebSocket 连接失败: %v", err) + runtime.EventsEmit(a.ctx, "socket-message", map[string]string{ + "type": "error", + "content": "连接失败: " + err.Error(), + }) + return false + } + } + + global.Log.Infof("连接成功: %s", url) + runtime.EventsEmit(a.ctx, "socket-status", map[string]string{ + "type": "connected", + "content": "连接成功", + }) + + return true +} + +// SendSocketMessage 发送 Socket 消息 +func (a *App) SendSocketMessage(protocol, message string) bool { + global.Log.Infof("发送 %s 消息: %s", protocol, message) + + var err error + if protocol == "tcp" { + err = api.SendTCPMessage(message) + } else { + err = api.SendWebSocketMessage(message) + } + + if err != nil { + global.Log.Warnf("发送消息失败: %v", err) + runtime.EventsEmit(a.ctx, "socket-message", map[string]string{ + "type": "error", + "content": "发送失败: " + err.Error(), + }) + return false + } + + // 发送成功事件 + runtime.EventsEmit(a.ctx, "socket-sent", map[string]string{ + "type": "sent", + "content": message, + }) + + return true +} + +// DisconnectSocket 断开 Socket 连接 +func (a *App) DisconnectSocket(protocol string) bool { + global.Log.Infof("断开 %s 连接", protocol) + + var err error + if protocol == "tcp" { + err = api.CloseTCP() + } else { + err = api.CloseWebSocket() + } + + if err != nil { + global.Log.Warnf("断开连接失败: %v", err) + return false + } + + runtime.EventsEmit(a.ctx, "socket-status", map[string]string{ + "type": "disconnected", + "content": "已断开连接", + }) + + return true +} + +// IsSocketConnected 检查 Socket 是否已连接 +func (a *App) IsSocketConnected(protocol string) bool { + if protocol == "tcp" { + return api.IsTCPConnected() + } + return api.IsWebSocketConnected() +} diff --git a/service/temp.go b/service/temp.go new file mode 100644 index 0000000..723538c --- /dev/null +++ b/service/temp.go @@ -0,0 +1,67 @@ +package service + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/wailsapp/wails/v2/pkg/runtime" +) + +func greet(name string) string { + return fmt.Sprintf("Hello %s, It's show time!", name) +} + +func flashtime(a *App) { + go func() { + for { + now := time.Now().Format(time.DateTime) + runtime.EventsEmit(a.ctx, "time", now) + time.Sleep(time.Second) + } + }() +} + +func getJSONString() string { + datas := map[string]interface{}{ + "code": 200, + "message": "ok", + "data": []map[string]interface{}{ + { + "id": 1, + "name": "apps", + "isdir": true, + "size": 0, + }, + { + "id": 2, + "name": "docs", + "isdir": true, + "size": 0, + }, + { + "id": 3, + "name": "readme.txt", + "isdir": false, + "size": 2048, + }, + }, + } + + jsonBytes, err := json.Marshal(datas) + if err != nil { + return "{}" + } + + return string(jsonBytes) +} + +type SystemInfo struct { + OS string `json:"os"` + Arch string `json:"arch"` + NumCPU int `json:"num_cpu"` + Hostname string `json:"hostname"` + GoVer string `json:"go_ver"` + Time string `json:"time"` + ProcessName string `json:"process_name"` +} diff --git a/wails.json b/wails.json new file mode 100644 index 0000000..664fecd --- /dev/null +++ b/wails.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://wails.io/schemas/config.v2.json", + "name": "webhelper", + "outputfilename": "webhelper", + "frontend:install": "npm install", + "frontend:build": "npm run build", + "frontend:dev:watcher": "npm run dev", + "frontend:dev:serverUrl": "auto", + "author": { + "name": "MoGuQAQ" + }, + "info": { + "companyName": "MoGuQAQ", + "productName": "WebHelper", + "productVersion": "1.0.0", + "copyright": "Copyright © 2026 MoGuQAQ", + "comments": "MIT license" + } +}