完成基本功能
This commit is contained in:
1
.task/checksum/common-install-frontend-deps
Normal file
1
.task/checksum/common-install-frontend-deps
Normal file
@@ -0,0 +1 @@
|
|||||||
|
c9e4d012d15abf06367f9dcb35fbde1a
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
c6a7af2915380768678ecf3032701af1
|
||||||
1
.task/checksum/windows-common-generate-icons
Normal file
1
.task/checksum/windows-common-generate-icons
Normal file
@@ -0,0 +1 @@
|
|||||||
|
9717dac68ebaaaeee7334acd18992d68
|
||||||
1
.task/checksum/windows-common-install-frontend-deps
Normal file
1
.task/checksum/windows-common-install-frontend-deps
Normal file
@@ -0,0 +1 @@
|
|||||||
|
c9e4d012d15abf06367f9dcb35fbde1a
|
||||||
31
Taskfile.yml
Normal file
31
Taskfile.yml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
common: ./build/Taskfile.yml
|
||||||
|
windows: ./build/windows/Taskfile.yml
|
||||||
|
|
||||||
|
vars:
|
||||||
|
APP_NAME: "gitpilot"
|
||||||
|
BIN_DIR: "bin"
|
||||||
|
VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
summary: Builds the application
|
||||||
|
cmds:
|
||||||
|
- task: "{{OS}}:build"
|
||||||
|
|
||||||
|
package:
|
||||||
|
summary: Packages a production build of the application
|
||||||
|
cmds:
|
||||||
|
- task: "{{OS}}:package"
|
||||||
|
|
||||||
|
run:
|
||||||
|
summary: Runs the application
|
||||||
|
cmds:
|
||||||
|
- task: "{{OS}}:run"
|
||||||
|
|
||||||
|
dev:
|
||||||
|
summary: Runs the application in development mode
|
||||||
|
cmds:
|
||||||
|
- wails3 dev -config ./build/config.yml -port {{.VITE_PORT}}
|
||||||
BIN
bin/gitpilot.exe
Normal file
BIN
bin/gitpilot.exe
Normal file
Binary file not shown.
74
build/Taskfile.yml
Normal file
74
build/Taskfile.yml
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
go:mod:tidy:
|
||||||
|
summary: Runs `go mod tidy`
|
||||||
|
internal: true
|
||||||
|
cmds:
|
||||||
|
- go mod tidy
|
||||||
|
|
||||||
|
install:frontend:deps:
|
||||||
|
summary: Install frontend dependencies
|
||||||
|
dir: frontend
|
||||||
|
sources:
|
||||||
|
- package.json
|
||||||
|
- package-lock.json
|
||||||
|
generates:
|
||||||
|
- node_modules/*
|
||||||
|
cmds:
|
||||||
|
- npm install
|
||||||
|
|
||||||
|
build:frontend:
|
||||||
|
label: build:frontend (PRODUCTION={{.PRODUCTION}})
|
||||||
|
summary: Build the frontend project
|
||||||
|
dir: frontend
|
||||||
|
deps:
|
||||||
|
- task: install:frontend:deps
|
||||||
|
- task: generate:bindings
|
||||||
|
vars:
|
||||||
|
BUILD_FLAGS:
|
||||||
|
ref: .BUILD_FLAGS
|
||||||
|
cmds:
|
||||||
|
- npm run {{.BUILD_COMMAND}} -q
|
||||||
|
env:
|
||||||
|
PRODUCTION: '{{.PRODUCTION | default "false"}}'
|
||||||
|
vars:
|
||||||
|
BUILD_COMMAND: '{{if eq .PRODUCTION "true"}}build{{else}}generate{{end}}'
|
||||||
|
|
||||||
|
generate:bindings:
|
||||||
|
label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}})
|
||||||
|
summary: Generates bindings for the frontend
|
||||||
|
deps:
|
||||||
|
- task: go:mod:tidy
|
||||||
|
sources:
|
||||||
|
- "**/*.go"
|
||||||
|
- go.mod
|
||||||
|
generates:
|
||||||
|
- frontend/bindings/**/*
|
||||||
|
cmds:
|
||||||
|
- wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=false
|
||||||
|
|
||||||
|
generate:icons:
|
||||||
|
summary: Generates Windows `.ico` and Mac `.icns` files from an image
|
||||||
|
dir: build
|
||||||
|
sources:
|
||||||
|
- "appicon.png"
|
||||||
|
generates:
|
||||||
|
- "darwin/icons.icns"
|
||||||
|
- "windows/icon.ico"
|
||||||
|
cmds:
|
||||||
|
- wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico
|
||||||
|
|
||||||
|
dev:frontend:
|
||||||
|
summary: Runs the frontend in development mode
|
||||||
|
dir: frontend
|
||||||
|
deps:
|
||||||
|
- task: install:frontend:deps
|
||||||
|
cmds:
|
||||||
|
- npm run dev -- --port {{.VITE_PORT}} --strictPort
|
||||||
|
|
||||||
|
update:build-assets:
|
||||||
|
summary: Updates the build assets
|
||||||
|
dir: build
|
||||||
|
cmds:
|
||||||
|
- wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir .
|
||||||
BIN
build/appicon.png
Normal file
BIN
build/appicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
43
build/config.yml
Normal file
43
build/config.yml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# This file contains the configuration for this project.
|
||||||
|
# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets.
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
info:
|
||||||
|
companyName: "GitPilot"
|
||||||
|
productName: "GitPilot"
|
||||||
|
productIdentifier: "com.gitpilot.app"
|
||||||
|
description: "Git Repository Management Tool"
|
||||||
|
copyright: "(c) 2025, GitPilot"
|
||||||
|
comments: "GitPilot - Git Repository Manager"
|
||||||
|
version: "0.1.0"
|
||||||
|
|
||||||
|
dev_mode:
|
||||||
|
root_path: .
|
||||||
|
log_level: warn
|
||||||
|
debounce: 300
|
||||||
|
ignore:
|
||||||
|
dir:
|
||||||
|
- .git
|
||||||
|
- node_modules
|
||||||
|
- frontend
|
||||||
|
- bin
|
||||||
|
file:
|
||||||
|
- .DS_Store
|
||||||
|
- .gitignore
|
||||||
|
- .gitkeep
|
||||||
|
watched_extension:
|
||||||
|
- "*.go"
|
||||||
|
git_ignore: true
|
||||||
|
executes:
|
||||||
|
- cmd: wails3 task common:dev:frontend
|
||||||
|
type: background
|
||||||
|
- cmd: powershell.exe -ExecutionPolicy Bypass -File ./build/wait.ps1 -Seconds 30
|
||||||
|
type: blocking
|
||||||
|
- cmd: wails3 task build
|
||||||
|
type: blocking
|
||||||
|
- cmd: wails3 task run
|
||||||
|
type: primary
|
||||||
|
|
||||||
|
fileAssociations:
|
||||||
|
|
||||||
|
other:
|
||||||
32
build/darwin/Info.dev.plist
Normal file
32
build/darwin/Info.dev.plist
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>Peter Agent</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>peter-agent</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.peteragent.app</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>0.1.0</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>AI Agent Application</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>0.1.0</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>icons</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.15.0</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<string>true</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>© 2025, Peter Agent</string>
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSAllowsLocalNetworking</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
27
build/darwin/Info.plist
Normal file
27
build/darwin/Info.plist
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>Peter Agent</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>peter-agent</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.peteragent.app</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>0.1.0</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>AI Agent Application</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>0.1.0</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>icons</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.15.0</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<string>true</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>© 2025, Peter Agent</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
81
build/darwin/Taskfile.yml
Normal file
81
build/darwin/Taskfile.yml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
common: ../Taskfile.yml
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
summary: Creates a production build of the application
|
||||||
|
deps:
|
||||||
|
- task: common:go:mod:tidy
|
||||||
|
- task: common:build:frontend
|
||||||
|
vars:
|
||||||
|
BUILD_FLAGS:
|
||||||
|
ref: .BUILD_FLAGS
|
||||||
|
PRODUCTION:
|
||||||
|
ref: .PRODUCTION
|
||||||
|
- task: common:generate:icons
|
||||||
|
cmds:
|
||||||
|
- go build {{.BUILD_FLAGS}} -o {{.OUTPUT}}
|
||||||
|
vars:
|
||||||
|
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags "production,disable_onnx" -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-tags "disable_onnx" -buildvcs=false -gcflags=all="-l"{{end}}'
|
||||||
|
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
|
||||||
|
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
|
||||||
|
env:
|
||||||
|
GOOS: darwin
|
||||||
|
CGO_ENABLED: 1
|
||||||
|
GOARCH: '{{.ARCH | default ARCH}}'
|
||||||
|
CGO_CFLAGS: "-mmacosx-version-min=10.15"
|
||||||
|
CGO_LDFLAGS: "-mmacosx-version-min=10.15"
|
||||||
|
MACOSX_DEPLOYMENT_TARGET: "10.15"
|
||||||
|
PRODUCTION: '{{.PRODUCTION | default "false"}}'
|
||||||
|
|
||||||
|
build:universal:
|
||||||
|
summary: Builds darwin universal binary (arm64 + amd64)
|
||||||
|
deps:
|
||||||
|
- task: build
|
||||||
|
vars:
|
||||||
|
ARCH: amd64
|
||||||
|
OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-amd64"
|
||||||
|
- task: build
|
||||||
|
vars:
|
||||||
|
ARCH: arm64
|
||||||
|
OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
|
||||||
|
cmds:
|
||||||
|
- lipo -create -output "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
|
||||||
|
- rm "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
|
||||||
|
|
||||||
|
package:
|
||||||
|
summary: Packages a production build of the application into a `.app` bundle
|
||||||
|
deps:
|
||||||
|
- task: build
|
||||||
|
vars:
|
||||||
|
PRODUCTION: "true"
|
||||||
|
cmds:
|
||||||
|
- task: create:app:bundle
|
||||||
|
|
||||||
|
package:universal:
|
||||||
|
summary: Packages darwin universal binary (arm64 + amd64)
|
||||||
|
deps:
|
||||||
|
- task: build:universal
|
||||||
|
cmds:
|
||||||
|
- task: create:app:bundle
|
||||||
|
|
||||||
|
|
||||||
|
create:app:bundle:
|
||||||
|
summary: Creates an `.app` bundle
|
||||||
|
cmds:
|
||||||
|
- mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/{MacOS,Resources}
|
||||||
|
- cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources
|
||||||
|
- cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS
|
||||||
|
- cp build/darwin/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents
|
||||||
|
- codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.app
|
||||||
|
|
||||||
|
run:
|
||||||
|
cmds:
|
||||||
|
- mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/{MacOS,Resources}
|
||||||
|
- cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources
|
||||||
|
- cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS
|
||||||
|
- cp build/darwin/Info.dev.plist {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist
|
||||||
|
- codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.dev.app
|
||||||
|
- '{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}'
|
||||||
BIN
build/darwin/icons.icns
Normal file
BIN
build/darwin/icons.icns
Normal file
Binary file not shown.
119
build/linux/Taskfile.yml
Normal file
119
build/linux/Taskfile.yml
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
common: ../Taskfile.yml
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
summary: Builds the application for Linux
|
||||||
|
deps:
|
||||||
|
- task: common:go:mod:tidy
|
||||||
|
- task: common:build:frontend
|
||||||
|
vars:
|
||||||
|
BUILD_FLAGS:
|
||||||
|
ref: .BUILD_FLAGS
|
||||||
|
PRODUCTION:
|
||||||
|
ref: .PRODUCTION
|
||||||
|
- task: common:generate:icons
|
||||||
|
cmds:
|
||||||
|
- go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}
|
||||||
|
vars:
|
||||||
|
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags "production,disable_onnx" -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-tags "disable_onnx" -buildvcs=false -gcflags=all="-l"{{end}}'
|
||||||
|
env:
|
||||||
|
GOOS: linux
|
||||||
|
CGO_ENABLED: 1
|
||||||
|
GOARCH: '{{.ARCH | default ARCH}}'
|
||||||
|
PRODUCTION: '{{.PRODUCTION | default "false"}}'
|
||||||
|
|
||||||
|
package:
|
||||||
|
summary: Packages a production build of the application for Linux
|
||||||
|
deps:
|
||||||
|
- task: build
|
||||||
|
vars:
|
||||||
|
PRODUCTION: "true"
|
||||||
|
cmds:
|
||||||
|
- task: create:appimage
|
||||||
|
- task: create:deb
|
||||||
|
- task: create:rpm
|
||||||
|
- task: create:aur
|
||||||
|
|
||||||
|
create:appimage:
|
||||||
|
summary: Creates an AppImage
|
||||||
|
dir: build/linux/appimage
|
||||||
|
deps:
|
||||||
|
- task: build
|
||||||
|
vars:
|
||||||
|
PRODUCTION: "true"
|
||||||
|
- task: generate:dotdesktop
|
||||||
|
cmds:
|
||||||
|
- cp {{.APP_BINARY}} {{.APP_NAME}}
|
||||||
|
- cp ../../appicon.png appicon.png
|
||||||
|
- wails3 generate appimage -binary {{.APP_NAME}} -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build
|
||||||
|
vars:
|
||||||
|
APP_NAME: '{{.APP_NAME}}'
|
||||||
|
APP_BINARY: '../../../bin/{{.APP_NAME}}'
|
||||||
|
ICON: '../../appicon.png'
|
||||||
|
DESKTOP_FILE: '../{{.APP_NAME}}.desktop'
|
||||||
|
OUTPUT_DIR: '../../../bin'
|
||||||
|
|
||||||
|
create:deb:
|
||||||
|
summary: Creates a deb package
|
||||||
|
deps:
|
||||||
|
- task: build
|
||||||
|
vars:
|
||||||
|
PRODUCTION: "true"
|
||||||
|
cmds:
|
||||||
|
- task: generate:dotdesktop
|
||||||
|
- task: generate:deb
|
||||||
|
|
||||||
|
create:rpm:
|
||||||
|
summary: Creates a rpm package
|
||||||
|
deps:
|
||||||
|
- task: build
|
||||||
|
vars:
|
||||||
|
PRODUCTION: "true"
|
||||||
|
cmds:
|
||||||
|
- task: generate:dotdesktop
|
||||||
|
- task: generate:rpm
|
||||||
|
|
||||||
|
create:aur:
|
||||||
|
summary: Creates a arch linux packager package
|
||||||
|
deps:
|
||||||
|
- task: build
|
||||||
|
vars:
|
||||||
|
PRODUCTION: "true"
|
||||||
|
cmds:
|
||||||
|
- task: generate:dotdesktop
|
||||||
|
- task: generate:aur
|
||||||
|
|
||||||
|
generate:deb:
|
||||||
|
summary: Creates a deb package
|
||||||
|
cmds:
|
||||||
|
- wails3 tool package -name {{.APP_NAME}} -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
|
||||||
|
|
||||||
|
generate:rpm:
|
||||||
|
summary: Creates a rpm package
|
||||||
|
cmds:
|
||||||
|
- wails3 tool package -name {{.APP_NAME}} -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
|
||||||
|
|
||||||
|
generate:aur:
|
||||||
|
summary: Creates a arch linux packager package
|
||||||
|
cmds:
|
||||||
|
- wails3 tool package -name {{.APP_NAME}} -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
|
||||||
|
|
||||||
|
generate:dotdesktop:
|
||||||
|
summary: Generates a `.desktop` file
|
||||||
|
dir: build
|
||||||
|
cmds:
|
||||||
|
- mkdir -p {{.ROOT_DIR}}/build/linux/appimage
|
||||||
|
- wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile {{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop -categories "{{.CATEGORIES}}"
|
||||||
|
vars:
|
||||||
|
APP_NAME: '{{.APP_NAME}}'
|
||||||
|
EXEC: '{{.APP_NAME}}'
|
||||||
|
ICON: '{{.APP_NAME}}'
|
||||||
|
CATEGORIES: 'Development;'
|
||||||
|
OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop'
|
||||||
|
|
||||||
|
run:
|
||||||
|
cmds:
|
||||||
|
- '{{.BIN_DIR}}/{{.APP_NAME}}'
|
||||||
35
build/linux/appimage/build.sh
Normal file
35
build/linux/appimage/build.sh
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Copyright (c) 2018-Present Lea Anthony
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
# Fail script on any error
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
# Define variables
|
||||||
|
APP_DIR="${APP_NAME}.AppDir"
|
||||||
|
|
||||||
|
# Create AppDir structure
|
||||||
|
mkdir -p "${APP_DIR}/usr/bin"
|
||||||
|
cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/"
|
||||||
|
cp "${ICON_PATH}" "${APP_DIR}/"
|
||||||
|
cp "${DESKTOP_FILE}" "${APP_DIR}/"
|
||||||
|
|
||||||
|
if [[ $(uname -m) == *x86_64* ]]; then
|
||||||
|
# Download linuxdeploy and make it executable
|
||||||
|
wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
|
||||||
|
chmod +x linuxdeploy-x86_64.AppImage
|
||||||
|
|
||||||
|
# Run linuxdeploy to bundle the application
|
||||||
|
./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage
|
||||||
|
else
|
||||||
|
# Download linuxdeploy and make it executable (arm64)
|
||||||
|
wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage
|
||||||
|
chmod +x linuxdeploy-aarch64.AppImage
|
||||||
|
|
||||||
|
# Run linuxdeploy to bundle the application (arm64)
|
||||||
|
./linuxdeploy-aarch64.AppImage --appdir "${APP_DIR}" --output appimage
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Rename the generated AppImage
|
||||||
|
mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage"
|
||||||
|
|
||||||
13
build/linux/desktop
Normal file
13
build/linux/desktop
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Version=1.0
|
||||||
|
Name=Peter Agent
|
||||||
|
Comment=AI Agent Application
|
||||||
|
# The Exec line includes %u to pass the URL to the application
|
||||||
|
Exec=/usr/local/bin/peter-agent %u
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Icon=peter-agent
|
||||||
|
Categories=Utility;
|
||||||
|
StartupWMClass=peter-agent
|
||||||
|
|
||||||
|
|
||||||
67
build/linux/nfpm/nfpm.yaml
Normal file
67
build/linux/nfpm/nfpm.yaml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# Feel free to remove those if you don't want/need to use them.
|
||||||
|
# Make sure to check the documentation at https://nfpm.goreleaser.com
|
||||||
|
#
|
||||||
|
# The lines below are called `modelines`. See `:help modeline`
|
||||||
|
|
||||||
|
name: "peter-agent"
|
||||||
|
arch: ${GOARCH}
|
||||||
|
platform: "linux"
|
||||||
|
version: "0.1.0"
|
||||||
|
section: "default"
|
||||||
|
priority: "extra"
|
||||||
|
maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}>
|
||||||
|
description: "AI Agent Application powered by Eino + Chroma"
|
||||||
|
vendor: "Peter Agent"
|
||||||
|
homepage: "https://github.com/yourusername/peter-agent"
|
||||||
|
license: "MIT"
|
||||||
|
release: "1"
|
||||||
|
|
||||||
|
contents:
|
||||||
|
- src: "./bin/peter-agent"
|
||||||
|
dst: "/usr/local/bin/peter-agent"
|
||||||
|
- src: "./build/appicon.png"
|
||||||
|
dst: "/usr/share/icons/hicolor/128x128/apps/peter-agent.png"
|
||||||
|
- src: "./build/linux/peter-agent.desktop"
|
||||||
|
dst: "/usr/share/applications/peter-agent.desktop"
|
||||||
|
|
||||||
|
# Default dependencies for Debian 12/Ubuntu 22.04+ with WebKit 4.1
|
||||||
|
depends:
|
||||||
|
- libgtk-3-0
|
||||||
|
- libwebkit2gtk-4.1-0
|
||||||
|
|
||||||
|
# Distribution-specific overrides for different package formats and WebKit versions
|
||||||
|
overrides:
|
||||||
|
# RPM packages for RHEL/CentOS/AlmaLinux/Rocky Linux (WebKit 4.0)
|
||||||
|
rpm:
|
||||||
|
depends:
|
||||||
|
- gtk3
|
||||||
|
- webkit2gtk4.1
|
||||||
|
|
||||||
|
# Arch Linux packages (WebKit 4.1)
|
||||||
|
archlinux:
|
||||||
|
depends:
|
||||||
|
- gtk3
|
||||||
|
- webkit2gtk-4.1
|
||||||
|
|
||||||
|
# scripts section to ensure desktop database is updated after install
|
||||||
|
scripts:
|
||||||
|
postinstall: "./build/linux/nfpm/scripts/postinstall.sh"
|
||||||
|
# You can also add preremove, postremove if needed
|
||||||
|
# preremove: "./build/linux/nfpm/scripts/preremove.sh"
|
||||||
|
# postremove: "./build/linux/nfpm/scripts/postremove.sh"
|
||||||
|
|
||||||
|
# replaces:
|
||||||
|
# - foobar
|
||||||
|
# provides:
|
||||||
|
# - bar
|
||||||
|
# depends:
|
||||||
|
# - gtk3
|
||||||
|
# - libwebkit2gtk
|
||||||
|
# recommends:
|
||||||
|
# - whatever
|
||||||
|
# suggests:
|
||||||
|
# - something-else
|
||||||
|
# conflicts:
|
||||||
|
# - not-foo
|
||||||
|
# - not-bar
|
||||||
|
# changelog: "changelog.yaml"
|
||||||
21
build/linux/nfpm/scripts/postinstall.sh
Normal file
21
build/linux/nfpm/scripts/postinstall.sh
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Update desktop database for .desktop file changes
|
||||||
|
# This makes the application appear in application menus and registers its capabilities.
|
||||||
|
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||||
|
echo "Updating desktop database..."
|
||||||
|
update-desktop-database -q /usr/share/applications
|
||||||
|
else
|
||||||
|
echo "Warning: update-desktop-database command not found. Desktop file may not be immediately recognized." >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update MIME database for custom URL schemes (x-scheme-handler)
|
||||||
|
# This ensures the system knows how to handle your custom protocols.
|
||||||
|
if command -v update-mime-database >/dev/null 2>&1; then
|
||||||
|
echo "Updating MIME database..."
|
||||||
|
update-mime-database -n /usr/share/mime
|
||||||
|
else
|
||||||
|
echo "Warning: update-mime-database command not found. Custom URL schemes may not be immediately recognized." >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
1
build/linux/nfpm/scripts/postremove.sh
Normal file
1
build/linux/nfpm/scripts/postremove.sh
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#!/bin/bash
|
||||||
1
build/linux/nfpm/scripts/preinstall.sh
Normal file
1
build/linux/nfpm/scripts/preinstall.sh
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#!/bin/bash
|
||||||
1
build/linux/nfpm/scripts/preremove.sh
Normal file
1
build/linux/nfpm/scripts/preremove.sh
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#!/bin/bash
|
||||||
28
build/wait.ps1
Normal file
28
build/wait.ps1
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Wait for specified seconds and detect frontend server
|
||||||
|
param(
|
||||||
|
[int]$Seconds = 5,
|
||||||
|
[string]$Url = "http://localhost:9245"
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Host "Waiting for frontend to start..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
$elapsed = 0
|
||||||
|
$interval = 1
|
||||||
|
|
||||||
|
while ($elapsed -lt $Seconds) {
|
||||||
|
try {
|
||||||
|
$response = Invoke-WebRequest -Uri $Url -Method Head -TimeoutSec 1 -ErrorAction SilentlyContinue
|
||||||
|
if ($response.StatusCode -eq 200) {
|
||||||
|
Write-Host "✓ Frontend server is ready ($elapsed seconds)" -ForegroundColor Green
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
# Continue waiting
|
||||||
|
}
|
||||||
|
|
||||||
|
Start-Sleep -Seconds $interval
|
||||||
|
$elapsed += $interval
|
||||||
|
Write-Host " Waiting... ($elapsed/$Seconds seconds)" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "✓ Wait completed (max $Seconds seconds)" -ForegroundColor Green
|
||||||
62
build/windows/Taskfile.yml
Normal file
62
build/windows/Taskfile.yml
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
includes:
|
||||||
|
common: ../Taskfile.yml
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
summary: Builds the application for Windows
|
||||||
|
deps:
|
||||||
|
- task: common:go:mod:tidy
|
||||||
|
- task: common:build:frontend
|
||||||
|
vars:
|
||||||
|
BUILD_FLAGS:
|
||||||
|
ref: .BUILD_FLAGS
|
||||||
|
PRODUCTION:
|
||||||
|
ref: .PRODUCTION
|
||||||
|
- task: common:generate:icons
|
||||||
|
cmds:
|
||||||
|
- task: generate:syso
|
||||||
|
- go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}.exe
|
||||||
|
- cmd: powershell Remove-item *.syso
|
||||||
|
platforms: [windows]
|
||||||
|
- cmd: rm -f *.syso
|
||||||
|
platforms: [linux, darwin]
|
||||||
|
vars:
|
||||||
|
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}'
|
||||||
|
env:
|
||||||
|
GOOS: windows
|
||||||
|
CGO_ENABLED: 1
|
||||||
|
GOARCH: '{{.ARCH | default ARCH}}'
|
||||||
|
PRODUCTION: '{{.PRODUCTION | default "false"}}'
|
||||||
|
|
||||||
|
package:
|
||||||
|
summary: Packages a production build of the application
|
||||||
|
cmds:
|
||||||
|
- task: create:nsis:installer
|
||||||
|
|
||||||
|
generate:syso:
|
||||||
|
summary: Generates Windows `.syso` file
|
||||||
|
dir: build
|
||||||
|
cmds:
|
||||||
|
- wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso
|
||||||
|
vars:
|
||||||
|
ARCH: '{{.ARCH | default ARCH}}'
|
||||||
|
|
||||||
|
create:nsis:installer:
|
||||||
|
summary: Creates an NSIS installer
|
||||||
|
dir: build/windows/nsis
|
||||||
|
deps:
|
||||||
|
- task: build
|
||||||
|
vars:
|
||||||
|
PRODUCTION: "true"
|
||||||
|
cmds:
|
||||||
|
- wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows/nsis"
|
||||||
|
- makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR | replace "/" "\\"}}\\{{.BIN_DIR}}\\{{.APP_NAME}}.exe" project.nsi
|
||||||
|
vars:
|
||||||
|
ARCH: '{{.ARCH | default ARCH}}'
|
||||||
|
ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}'
|
||||||
|
|
||||||
|
run:
|
||||||
|
cmds:
|
||||||
|
- '{{.BIN_DIR}}/{{.APP_NAME}}.exe'
|
||||||
BIN
build/windows/icon.ico
Normal file
BIN
build/windows/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
15
build/windows/info.json
Normal file
15
build/windows/info.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"fixed": {
|
||||||
|
"file_version": "0.1.0"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"0000": {
|
||||||
|
"ProductVersion": "0.1.0",
|
||||||
|
"CompanyName": "GitPilot",
|
||||||
|
"FileDescription": "Git Repository Management Tool",
|
||||||
|
"LegalCopyright": "© 2025, GitPilot",
|
||||||
|
"ProductName": "GitPilot",
|
||||||
|
"Comments": "GitPilot - Git Repository Manager"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
build/windows/wails.exe.manifest
Normal file
22
build/windows/wails.exe.manifest
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<assemblyIdentity type="win32" name="com.gitpilot.app" version="0.1.0" processorArchitecture="*"/>
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
<asmv3:application>
|
||||||
|
<asmv3:windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness>
|
||||||
|
</asmv3:windowsSettings>
|
||||||
|
</asmv3:application>
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges>
|
||||||
|
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
</assembly>
|
||||||
14
cmd/main.go
14
cmd/main.go
@@ -1,11 +1,19 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/zhuy1228/GitPilot/internal"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/zhuy1228/GitPilot/internal/git"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
proxyInfo, err := internal.GetCurrentProxy()
|
gitClient := git.NewGitClient()
|
||||||
|
changes, err := gitClient.StagedFiles("F:\\Project\\nakama")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
println(proxyInfo.String())
|
fmt.Printf("共 %d 个文件变更:\n", len(changes))
|
||||||
|
for _, c := range changes {
|
||||||
|
fmt.Println(c)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
frontend/.gitignore
vendored
Normal file
24
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Nuxt dev/build outputs
|
||||||
|
.output
|
||||||
|
.data
|
||||||
|
.nuxt
|
||||||
|
.nitro
|
||||||
|
.cache
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Node dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
.fleet
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Local env files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
10
frontend/app/app.vue
Normal file
10
frontend/app/app.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NuxtLayout>
|
||||||
|
<NuxtPage />
|
||||||
|
</NuxtLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style></style>
|
||||||
1513
frontend/app/components/ContentArea.vue
Normal file
1513
frontend/app/components/ContentArea.vue
Normal file
File diff suppressed because it is too large
Load Diff
220
frontend/app/components/FileTreeNode.vue
Normal file
220
frontend/app/components/FileTreeNode.vue
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
<script setup>
|
||||||
|
import {
|
||||||
|
FileOutlined,
|
||||||
|
FolderOutlined,
|
||||||
|
FolderOpenOutlined,
|
||||||
|
CaretRightOutlined,
|
||||||
|
CaretDownOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
MinusOutlined,
|
||||||
|
UndoOutlined,
|
||||||
|
} from '@ant-design/icons-vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
node: { type: Object, required: true },
|
||||||
|
collapsedDirs: { type: Set, default: () => new Set() },
|
||||||
|
selectedFile: { type: Object, default: null },
|
||||||
|
depth: { type: Number, default: 0 },
|
||||||
|
// 'unstaged' | 'staged'
|
||||||
|
mode: { type: String, default: 'unstaged' },
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits([
|
||||||
|
'select-file', 'toggle-dir',
|
||||||
|
'stage-file', 'unstage-file', 'discard-file',
|
||||||
|
'stage-dir', 'unstage-dir', 'discard-dir',
|
||||||
|
])
|
||||||
|
|
||||||
|
function getStatusColor(status) {
|
||||||
|
const colors = { 'M': 'orange', 'A': 'green', 'D': 'red', '?': 'default' }
|
||||||
|
return colors[status] || 'default'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- 目录节点 -->
|
||||||
|
<div v-if="node.type === 'dir'" class="tree-dir">
|
||||||
|
<div class="tree-dir-header" :style="{ paddingLeft: (8 + depth * 16) + 'px' }" @click="emit('toggle-dir', node.path)">
|
||||||
|
<CaretDownOutlined v-if="!collapsedDirs.has(node.path)" class="tree-arrow" />
|
||||||
|
<CaretRightOutlined v-else class="tree-arrow" />
|
||||||
|
<FolderOpenOutlined v-if="!collapsedDirs.has(node.path)" class="tree-icon dir-icon" />
|
||||||
|
<FolderOutlined v-else class="tree-icon dir-icon" />
|
||||||
|
<span class="tree-dir-name">{{ node.name }}</span>
|
||||||
|
<span class="tree-dir-count">{{ node.fileCount }}</span>
|
||||||
|
<!-- 目录级操作按钮 -->
|
||||||
|
<span class="tree-actions" @click.stop>
|
||||||
|
<template v-if="mode === 'unstaged'">
|
||||||
|
<span class="tree-action-btn" title="暂存目录下所有文件" @click="emit('stage-dir', node.path)"><PlusOutlined /></span>
|
||||||
|
<span class="tree-action-btn danger" title="丢弃目录下所有更改" @click="emit('discard-dir', node.path)"><UndoOutlined /></span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class="tree-action-btn" title="取消暂存目录下所有文件" @click="emit('unstage-dir', node.path)"><MinusOutlined /></span>
|
||||||
|
</template>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-show="!collapsedDirs.has(node.path)">
|
||||||
|
<FileTreeNode
|
||||||
|
v-for="child in node.children"
|
||||||
|
:key="child.type + '-' + (child.path || child.data?.filePath)"
|
||||||
|
:node="child"
|
||||||
|
:collapsed-dirs="collapsedDirs"
|
||||||
|
:selected-file="selectedFile"
|
||||||
|
:depth="depth + 1"
|
||||||
|
:mode="mode"
|
||||||
|
@select-file="emit('select-file', $event)"
|
||||||
|
@toggle-dir="emit('toggle-dir', $event)"
|
||||||
|
@stage-file="emit('stage-file', $event)"
|
||||||
|
@unstage-file="emit('unstage-file', $event)"
|
||||||
|
@discard-file="emit('discard-file', $event)"
|
||||||
|
@stage-dir="emit('stage-dir', $event)"
|
||||||
|
@unstage-dir="emit('unstage-dir', $event)"
|
||||||
|
@discard-dir="emit('discard-dir', $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 文件节点 -->
|
||||||
|
<div v-else class="tree-file-item"
|
||||||
|
:style="{ paddingLeft: (8 + depth * 16) + 'px' }"
|
||||||
|
:class="{ active: selectedFile?.filePath === node.data.filePath && selectedFile?.staged === node.data.staged }"
|
||||||
|
@click="emit('select-file', node.data)">
|
||||||
|
<FileOutlined class="tree-icon file-icon" />
|
||||||
|
<span class="tree-file-name">{{ node.name }}</span>
|
||||||
|
<a-tag :color="getStatusColor(node.data.status)" size="small" class="tree-status-tag">{{ node.data.statusText }}</a-tag>
|
||||||
|
<!-- 文件级操作按钮 -->
|
||||||
|
<span class="tree-actions" @click.stop>
|
||||||
|
<template v-if="mode === 'unstaged'">
|
||||||
|
<span class="tree-action-btn" title="暂存此文件" @click="emit('stage-file', node.data)"><PlusOutlined /></span>
|
||||||
|
<span class="tree-action-btn danger" title="丢弃更改" @click="emit('discard-file', node.data)"><UndoOutlined /></span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class="tree-action-btn" title="取消暂存" @click="emit('unstage-file', node.data)"><MinusOutlined /></span>
|
||||||
|
</template>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tree-dir-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 3px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-primary);
|
||||||
|
transition: background 0.12s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-dir-header:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-arrow {
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 14px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dir-icon {
|
||||||
|
color: var(--accent, #89b4fa);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-icon {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-dir-name {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-dir-count {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
background: var(--bg-hover, rgba(255,255,255,0.06));
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0 6px;
|
||||||
|
min-width: 18px;
|
||||||
|
text-align: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-file-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 3px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
transition: background 0.12s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-file-item:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-file-item.active {
|
||||||
|
background: var(--bg-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-file-name {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-status-tag {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 操作按钮 */
|
||||||
|
.tree-actions {
|
||||||
|
display: flex;
|
||||||
|
visibility: hidden;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-dir-header:hover .tree-actions,
|
||||||
|
.tree-file-item:hover .tree-actions {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-action-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.12s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-action-btn:hover {
|
||||||
|
background: var(--bg-active, rgba(255,255,255,0.12));
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-action-btn.danger:hover {
|
||||||
|
background: rgba(243, 139, 168, 0.2);
|
||||||
|
color: var(--danger, #f38ba8);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
620
frontend/app/components/Sidebar.vue
Normal file
620
frontend/app/components/Sidebar.vue
Normal file
@@ -0,0 +1,620 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, computed, h } from 'vue'
|
||||||
|
import {
|
||||||
|
FolderOutlined,
|
||||||
|
FolderOpenOutlined,
|
||||||
|
UserOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
} from '@ant-design/icons-vue'
|
||||||
|
import { AppService } from '../../bindings/github.com/zhuy1228/GitPilot/internal/app'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
selectedProject: { type: Object, default: null }
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['select-project', 'tree-updated'])
|
||||||
|
|
||||||
|
const tree = ref([])
|
||||||
|
const expandedKeys = ref([])
|
||||||
|
const selectedKeys = ref([])
|
||||||
|
|
||||||
|
// 添加项目弹窗
|
||||||
|
const showAddDialog = ref(false)
|
||||||
|
const addForm = ref({ platform: '', username: '', name: '', path: '' })
|
||||||
|
|
||||||
|
// 右键菜单
|
||||||
|
const contextMenu = ref({ visible: false, x: 0, y: 0, type: '', data: {} })
|
||||||
|
|
||||||
|
// 平台弹窗
|
||||||
|
const showPlatformDialog = ref(false)
|
||||||
|
const platformDialogMode = ref('add')
|
||||||
|
const platformForm = ref({ name: '', baseUrl: '', oldName: '' })
|
||||||
|
|
||||||
|
// 用户弹窗
|
||||||
|
const showUserDialog = ref(false)
|
||||||
|
const userDialogMode = ref('add')
|
||||||
|
const userForm = ref({ platform: '', username: '', token: '', oldUsername: '' })
|
||||||
|
|
||||||
|
// 构建 key → 原始节点 的映射,避免 a-tree event node 丢失自定义字段
|
||||||
|
const nodeMap = computed(() => {
|
||||||
|
const map = {}
|
||||||
|
;(tree.value || []).forEach(platform => {
|
||||||
|
platform.children?.forEach(user => {
|
||||||
|
user.children?.forEach(proj => {
|
||||||
|
map[proj.key] = proj
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return map
|
||||||
|
})
|
||||||
|
|
||||||
|
// 转换后端树数据为 a-tree 格式
|
||||||
|
const treeData = computed(() => {
|
||||||
|
return (tree.value || []).map(platform => ({
|
||||||
|
key: platform.key,
|
||||||
|
title: platform.label,
|
||||||
|
type: 'platform',
|
||||||
|
isLeaf: false,
|
||||||
|
children: (platform.children || []).map(user => ({
|
||||||
|
key: user.key,
|
||||||
|
title: user.label,
|
||||||
|
type: 'user',
|
||||||
|
platformKey: platform.key,
|
||||||
|
isLeaf: false,
|
||||||
|
children: (user.children || []).map(proj => ({
|
||||||
|
key: proj.key,
|
||||||
|
title: proj.label,
|
||||||
|
type: 'project',
|
||||||
|
path: proj.path,
|
||||||
|
platformKey: platform.key,
|
||||||
|
username: user.label,
|
||||||
|
isLeaf: true,
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
async function loadTree() {
|
||||||
|
try {
|
||||||
|
const result = await AppService.GetProjectTree()
|
||||||
|
tree.value = result || []
|
||||||
|
const keys = []
|
||||||
|
tree.value.forEach(node => {
|
||||||
|
keys.push(node.key)
|
||||||
|
if (node.children) {
|
||||||
|
node.children.forEach(child => {
|
||||||
|
keys.push(child.key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expandedKeys.value = keys
|
||||||
|
} catch (e) {
|
||||||
|
console.error('加载项目树失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTreeSelect(keys, { node }) {
|
||||||
|
if (node.type === 'project') {
|
||||||
|
selectedKeys.value = keys
|
||||||
|
// 从 nodeMap 中取 path,避免 a-tree 事件节点丢失自定义字段
|
||||||
|
const raw = nodeMap.value[node.key]
|
||||||
|
const path = raw?.path || node.path || ''
|
||||||
|
console.log('[Sidebar] select project:', node.key, 'path:', path)
|
||||||
|
emit('select-project', { key: node.key, label: node.title || raw?.label, path, type: 'project' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 右键菜单 ---
|
||||||
|
function onRightClick({ event, node }) {
|
||||||
|
event.preventDefault()
|
||||||
|
const type = node.type
|
||||||
|
const data = {}
|
||||||
|
if (type === 'platform') {
|
||||||
|
data.key = node.key
|
||||||
|
} else if (type === 'user') {
|
||||||
|
data.platformKey = node.platformKey
|
||||||
|
data.username = node.title
|
||||||
|
} else if (type === 'project') {
|
||||||
|
data.platformKey = node.platformKey
|
||||||
|
data.username = node.username
|
||||||
|
data.name = node.title
|
||||||
|
}
|
||||||
|
contextMenu.value = { visible: true, x: event.clientX, y: event.clientY, type, data }
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSidebarContextMenu(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
contextMenu.value = { visible: true, x: e.clientX, y: e.clientY, type: 'empty', data: {} }
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideContextMenu() {
|
||||||
|
contextMenu.value.visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function onContextMenuClick({ key: action }) {
|
||||||
|
const { type, data } = contextMenu.value
|
||||||
|
hideContextMenu()
|
||||||
|
switch (action) {
|
||||||
|
case 'add-platform': openAddPlatformDialog(); break
|
||||||
|
case 'add-user': openAddUserDialog(data.key || data.platformKey); break
|
||||||
|
case 'edit-platform': openEditPlatformDialog(data.key); break
|
||||||
|
case 'remove-platform': removePlatform(data.key); break
|
||||||
|
case 'add-project': openAddDialog(data.platformKey, data.username); break
|
||||||
|
case 'edit-user': openEditUserDialog(data.platformKey, data.username); break
|
||||||
|
case 'remove-user': removeUser(data.platformKey, data.username); break
|
||||||
|
case 'remove-project': removeProject(data.platformKey, data.username, data.name); break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextMenuItems = computed(() => {
|
||||||
|
const { type } = contextMenu.value
|
||||||
|
if (type === 'empty') {
|
||||||
|
return [{ key: 'add-platform', label: '添加平台', icon: h(PlusOutlined) }]
|
||||||
|
}
|
||||||
|
if (type === 'platform') {
|
||||||
|
return [
|
||||||
|
{ key: 'add-user', label: '添加用户', icon: h(UserOutlined) },
|
||||||
|
{ key: 'edit-platform', label: '编辑平台', icon: h(EditOutlined) },
|
||||||
|
{ type: 'divider' },
|
||||||
|
{ key: 'add-platform', label: '添加平台', icon: h(PlusOutlined) },
|
||||||
|
{ type: 'divider' },
|
||||||
|
{ key: 'remove-platform', label: '删除平台', danger: true, icon: h(DeleteOutlined) },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
if (type === 'user') {
|
||||||
|
return [
|
||||||
|
{ key: 'add-project', label: '添加项目', icon: h(FolderOutlined) },
|
||||||
|
{ key: 'edit-user', label: '编辑用户', icon: h(EditOutlined) },
|
||||||
|
{ type: 'divider' },
|
||||||
|
{ key: 'remove-user', label: '删除用户', danger: true, icon: h(DeleteOutlined) },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
if (type === 'project') {
|
||||||
|
return [
|
||||||
|
{ key: 'add-project', label: '添加项目', icon: h(FolderOutlined) },
|
||||||
|
{ type: 'divider' },
|
||||||
|
{ key: 'remove-project', label: '删除项目', danger: true, icon: h(DeleteOutlined) },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
|
||||||
|
// --- 平台操作 ---
|
||||||
|
function openAddPlatformDialog() {
|
||||||
|
platformDialogMode.value = 'add'
|
||||||
|
platformForm.value = { name: '', baseUrl: '', oldName: '' }
|
||||||
|
showPlatformDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openEditPlatformDialog(platformKey) {
|
||||||
|
try {
|
||||||
|
const info = await AppService.GetPlatformInfo(platformKey)
|
||||||
|
platformDialogMode.value = 'edit'
|
||||||
|
platformForm.value = { name: info.name, baseUrl: info.baseUrl || '', oldName: info.name }
|
||||||
|
showPlatformDialog.value = true
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取平台信息失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function savePlatform() {
|
||||||
|
if (!platformForm.value.name) return
|
||||||
|
try {
|
||||||
|
if (platformDialogMode.value === 'add') {
|
||||||
|
await AppService.AddPlatform(platformForm.value.name, platformForm.value.baseUrl)
|
||||||
|
} else {
|
||||||
|
await AppService.UpdatePlatform(platformForm.value.oldName, platformForm.value.baseUrl)
|
||||||
|
}
|
||||||
|
showPlatformDialog.value = false
|
||||||
|
await loadTree()
|
||||||
|
emit('tree-updated')
|
||||||
|
} catch (e) {
|
||||||
|
console.error('操作失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removePlatform(name) {
|
||||||
|
if (!confirm(`确定删除平台 "${name}" 及其所有用户和项目吗?`)) return
|
||||||
|
try {
|
||||||
|
await AppService.RemovePlatform(name)
|
||||||
|
await loadTree()
|
||||||
|
emit('tree-updated')
|
||||||
|
} catch (e) {
|
||||||
|
console.error('删除失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 用户操作 ---
|
||||||
|
function openAddUserDialog(platformKey) {
|
||||||
|
userDialogMode.value = 'add'
|
||||||
|
userForm.value = { platform: platformKey, username: '', token: '', oldUsername: '' }
|
||||||
|
showUserDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openEditUserDialog(platformKey, username) {
|
||||||
|
try {
|
||||||
|
const info = await AppService.GetUserInfo(platformKey, username)
|
||||||
|
userDialogMode.value = 'edit'
|
||||||
|
userForm.value = {
|
||||||
|
platform: platformKey,
|
||||||
|
username: info.username,
|
||||||
|
token: info.token || '',
|
||||||
|
oldUsername: info.username
|
||||||
|
}
|
||||||
|
showUserDialog.value = true
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取用户信息失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveUser() {
|
||||||
|
if (!userForm.value.username) return
|
||||||
|
try {
|
||||||
|
if (userDialogMode.value === 'add') {
|
||||||
|
await AppService.AddUser(userForm.value.platform, userForm.value.username, userForm.value.token)
|
||||||
|
} else {
|
||||||
|
await AppService.UpdateUser(
|
||||||
|
userForm.value.platform,
|
||||||
|
userForm.value.oldUsername,
|
||||||
|
userForm.value.username,
|
||||||
|
userForm.value.token
|
||||||
|
)
|
||||||
|
}
|
||||||
|
showUserDialog.value = false
|
||||||
|
await loadTree()
|
||||||
|
emit('tree-updated')
|
||||||
|
} catch (e) {
|
||||||
|
console.error('操作失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeUser(platform, username) {
|
||||||
|
if (!confirm(`确定删除用户 "${username}" 及其所有项目吗?`)) return
|
||||||
|
try {
|
||||||
|
await AppService.RemoveUser(platform, username)
|
||||||
|
await loadTree()
|
||||||
|
emit('tree-updated')
|
||||||
|
} catch (e) {
|
||||||
|
console.error('删除失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 项目操作 ---
|
||||||
|
function openAddDialog(platformKey, username) {
|
||||||
|
addForm.value = { platform: platformKey, username: username, name: '', path: '' }
|
||||||
|
showAddDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pickDirectory() {
|
||||||
|
try {
|
||||||
|
const path = await AppService.SelectDirectory()
|
||||||
|
if (path) {
|
||||||
|
addForm.value.path = path
|
||||||
|
// 如果项目名称为空,自动用文件夹名称填充
|
||||||
|
if (!addForm.value.name) {
|
||||||
|
const parts = path.replace(/\\/g, '/').split('/')
|
||||||
|
addForm.value.name = parts[parts.length - 1] || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('选择文件夹失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addProject() {
|
||||||
|
if (!addForm.value.name || !addForm.value.path) return
|
||||||
|
try {
|
||||||
|
await AppService.AddProject(
|
||||||
|
addForm.value.platform,
|
||||||
|
addForm.value.username,
|
||||||
|
addForm.value.name,
|
||||||
|
addForm.value.path
|
||||||
|
)
|
||||||
|
showAddDialog.value = false
|
||||||
|
await loadTree()
|
||||||
|
emit('tree-updated')
|
||||||
|
} catch (e) {
|
||||||
|
console.error('添加项目失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeProject(platform, username, name) {
|
||||||
|
if (!confirm(`确定删除项目 "${name}" 吗?`)) return
|
||||||
|
try {
|
||||||
|
await AppService.RemoveProject(platform, username, name)
|
||||||
|
await loadTree()
|
||||||
|
emit('tree-updated')
|
||||||
|
} catch (e) {
|
||||||
|
console.error('删除项目失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 平台 SVG Logo
|
||||||
|
const platformLogos = {
|
||||||
|
github: `<svg viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>`,
|
||||||
|
gitee: `<svg viewBox="0 0 1024 1024" fill="currentColor"><path d="M512 1024C229.222 1024 0 794.778 0 512S229.222 0 512 0s512 229.222 512 512-229.222 512-512 512z m259.149-568.883h-290.74a25.293 25.293 0 0 0-25.292 25.293l-0.026 63.206c0 13.952 11.315 25.293 25.267 25.293h177.024c13.978 0 25.293 11.315 25.293 25.267v12.646a75.853 75.853 0 0 1-75.853 75.853h-240.23a25.293 25.293 0 0 1-25.267-25.293V417.203a75.853 75.853 0 0 1 75.827-75.853h353.946a25.293 25.293 0 0 0 25.267-25.292l0.077-63.207a25.293 25.293 0 0 0-25.268-25.293H417.152a189.62 189.62 0 0 0-189.62 189.645V771.15c0 13.977 11.316 25.293 25.294 25.293h372.94a170.65 170.65 0 0 0 170.65-170.65V480.384a25.293 25.293 0 0 0-25.293-25.267z"/></svg>`,
|
||||||
|
gitea: `<svg viewBox="0 0 640 640" fill="currentColor"><path d="M395.022 297.778c-13.158 0-23.822 10.664-23.822 23.822s10.664 23.822 23.822 23.822 23.822-10.664 23.822-23.822-10.664-23.822-23.822-23.822zM243.2 297.778c-13.158 0-23.822 10.664-23.822 23.822s10.664 23.822 23.822 23.822 23.822-10.664 23.822-23.822-10.664-23.822-23.822-23.822zM319.111 22.756C158.578 22.756 28.444 152.889 28.444 313.422s130.133 290.667 290.667 290.667 290.667-130.133 290.667-290.667S479.644 22.756 319.111 22.756zm165.689 361.244c0 5.333-0.711 10.667-1.778 15.644-20.267 93.867-148.089 167.111-305.067 167.111a360.604 360.604 0 0 1-65.778-5.689c-21.333 18.133-56.889 37.689-101.333 49.422-7.822 2.133-16 3.911-24.889 5.333h-0.711c-4.267 0-7.822-3.556-8.889-7.822v-0.356c-1.067-4.622 2.133-7.467 4.978-10.667 17.067-18.844 36.622-34.844 48-71.467-51.911-36.267-83.911-85.067-83.911-139.733 0-106.311 107.378-192.356 239.644-192.356 118.4 0 220.089 68.267 238.578 160.356 1.778 6.4 3.556 14.578 3.556 22.4-0.356 2.489-0.356 5.333-0.356 7.822z"/></svg>`,
|
||||||
|
default: `<svg viewBox="0 0 16 16" fill="currentColor"><path d="M15 5.6c0-.3-.2-.6-.5-.7L8.3.2a.6.6 0 0 0-.6 0L1.5 4.9c-.3.1-.5.4-.5.7v5.8c0 .3.2.6.5.7l6.2 3.7c.2.1.4.1.6 0l6.2-3.7c.3-.1.5-.4.5-.7V5.6zM8 1.2l5.2 3.1L8 7.5 2.8 4.3 8 1.2zm-6 4.5l5.5 3.2v6.3L2 12V5.7zm7 9.5V9l5.5-3.2V12L9 15.2z"/></svg>`
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPlatformColor(name) {
|
||||||
|
const colors = { github: '#e6edf3', gitee: '#c71d23', gitea: '#609926' }
|
||||||
|
return colors[name] || 'var(--text-secondary)'
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadTree()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<aside class="sidebar">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<span class="logo">GitPilot</span>
|
||||||
|
<a-button type="text" size="small" @click="openAddPlatformDialog" title="添加平台">
|
||||||
|
<template #icon><PlusOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar-content" @contextmenu="onSidebarContextMenu">
|
||||||
|
<a-tree
|
||||||
|
v-if="treeData.length"
|
||||||
|
v-model:expandedKeys="expandedKeys"
|
||||||
|
v-model:selectedKeys="selectedKeys"
|
||||||
|
:tree-data="treeData"
|
||||||
|
block-node
|
||||||
|
:show-icon="true"
|
||||||
|
@select="onTreeSelect"
|
||||||
|
@rightClick="onRightClick"
|
||||||
|
@contextmenu.stop
|
||||||
|
>
|
||||||
|
<template #title="{ title, type, key }">
|
||||||
|
<div class="tree-node-title">
|
||||||
|
<span
|
||||||
|
v-if="type === 'platform'"
|
||||||
|
class="platform-logo"
|
||||||
|
:style="{ color: getPlatformColor(key) }"
|
||||||
|
v-html="platformLogos[key] || platformLogos.default"
|
||||||
|
></span>
|
||||||
|
<UserOutlined v-else-if="type === 'user'" class="node-icon" />
|
||||||
|
<FolderOutlined v-else class="node-icon" />
|
||||||
|
<span class="node-label">{{ title }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-tree>
|
||||||
|
<div v-else class="empty-tip">右键可添加平台</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右键菜单 -->
|
||||||
|
<Teleport to="body">
|
||||||
|
<div
|
||||||
|
v-if="contextMenu.visible"
|
||||||
|
class="context-menu-mask"
|
||||||
|
@click="hideContextMenu"
|
||||||
|
@contextmenu.prevent="hideContextMenu"
|
||||||
|
>
|
||||||
|
<div @click.stop @contextmenu.stop>
|
||||||
|
<a-menu
|
||||||
|
class="context-menu-popup"
|
||||||
|
:style="{ left: contextMenu.x + 'px', top: contextMenu.y + 'px' }"
|
||||||
|
:items="contextMenuItems"
|
||||||
|
@click="onContextMenuClick"
|
||||||
|
mode="vertical"
|
||||||
|
:selectable="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
|
||||||
|
<!-- 添加项目弹窗 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="showAddDialog"
|
||||||
|
title="添加项目"
|
||||||
|
@ok="addProject"
|
||||||
|
ok-text="添加"
|
||||||
|
cancel-text="取消"
|
||||||
|
:width="420"
|
||||||
|
>
|
||||||
|
<a-form layout="vertical" :style="{ marginTop: '16px' }">
|
||||||
|
<a-form-item label="平台">
|
||||||
|
<a-input :value="addForm.platform" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="用户">
|
||||||
|
<a-input :value="addForm.username" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="项目名称">
|
||||||
|
<a-input v-model:value="addForm.name" placeholder="my-project" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="本地路径">
|
||||||
|
<a-input v-model:value="addForm.path" placeholder="F:/Projects/xxx">
|
||||||
|
<template #suffix>
|
||||||
|
<FolderOpenOutlined
|
||||||
|
style="cursor: pointer; color: var(--accent, #89b4fa);"
|
||||||
|
title="选择文件夹"
|
||||||
|
@click="pickDirectory"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 平台弹窗 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="showPlatformDialog"
|
||||||
|
:title="platformDialogMode === 'add' ? '添加平台' : '编辑平台'"
|
||||||
|
@ok="savePlatform"
|
||||||
|
:ok-text="platformDialogMode === 'add' ? '添加' : '保存'"
|
||||||
|
cancel-text="取消"
|
||||||
|
:width="420"
|
||||||
|
>
|
||||||
|
<a-form layout="vertical" :style="{ marginTop: '16px' }">
|
||||||
|
<a-form-item label="平台名称">
|
||||||
|
<a-input
|
||||||
|
v-model:value="platformForm.name"
|
||||||
|
placeholder="github / gitee / gitea"
|
||||||
|
:disabled="platformDialogMode === 'edit'"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<template #label>
|
||||||
|
Base URL <span class="form-hint">(自建平台需要填写)</span>
|
||||||
|
</template>
|
||||||
|
<a-input v-model:value="platformForm.baseUrl" placeholder="http://192.168.1.10:3000" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 用户弹窗 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="showUserDialog"
|
||||||
|
:title="userDialogMode === 'add' ? '添加用户' : '编辑用户'"
|
||||||
|
@ok="saveUser"
|
||||||
|
:ok-text="userDialogMode === 'add' ? '添加' : '保存'"
|
||||||
|
cancel-text="取消"
|
||||||
|
:width="420"
|
||||||
|
>
|
||||||
|
<a-form layout="vertical" :style="{ marginTop: '16px' }">
|
||||||
|
<a-form-item label="平台">
|
||||||
|
<a-input :value="userForm.platform" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="用户名">
|
||||||
|
<a-input v-model:value="userForm.username" placeholder="your-username" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<template #label>
|
||||||
|
Token <span class="form-hint">(可选,用于 API 认证)</span>
|
||||||
|
</template>
|
||||||
|
<a-input-password v-model:value="userForm.token" placeholder="ghp_xxxx / Bearer token" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
</aside>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.sidebar {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-right: 1px solid var(--border-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header {
|
||||||
|
height: var(--header-height);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 16px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
--wails-draggable: drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-logo {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platform-logo :deep(svg) {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-label {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-tip {
|
||||||
|
padding: 24px 16px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-hint {
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右键菜单遮罩 */
|
||||||
|
.context-menu-mask {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-menu-popup {
|
||||||
|
position: fixed !important;
|
||||||
|
z-index: 2001;
|
||||||
|
border-radius: 6px !important;
|
||||||
|
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45) !important;
|
||||||
|
min-width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ant Design 暗色主题覆盖 */
|
||||||
|
:deep(.ant-tree) {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tree .ant-tree-node-content-wrapper) {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tree .ant-tree-node-content-wrapper:hover) {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tree .ant-tree-node-content-wrapper.ant-tree-node-selected) {
|
||||||
|
background: var(--bg-active);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tree .ant-tree-switcher) {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tree .ant-tree-treenode) {
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tree .ant-tree-icon__customize) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
156
frontend/app/layouts/default.vue
Normal file
156
frontend/app/layouts/default.vue
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<script setup>
|
||||||
|
import { theme } from 'ant-design-vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<a-config-provider
|
||||||
|
:theme="{
|
||||||
|
algorithm: theme.darkAlgorithm,
|
||||||
|
token: {
|
||||||
|
colorPrimary: '#89b4fa',
|
||||||
|
colorBgContainer: '#252536',
|
||||||
|
colorBgElevated: '#252536',
|
||||||
|
colorBgLayout: '#1e1e2e',
|
||||||
|
colorBgSpotlight: '#313244',
|
||||||
|
colorBorder: '#313244',
|
||||||
|
colorBorderSecondary: '#313244',
|
||||||
|
colorText: '#cdd6f4',
|
||||||
|
colorTextSecondary: '#a6adc8',
|
||||||
|
colorTextTertiary: '#6c7086',
|
||||||
|
colorTextQuaternary: '#6c7086',
|
||||||
|
colorFill: '#313244',
|
||||||
|
colorFillSecondary: '#45475a',
|
||||||
|
colorFillTertiary: '#313244',
|
||||||
|
colorFillQuaternary: '#252536',
|
||||||
|
colorSuccess: '#a6e3a1',
|
||||||
|
colorWarning: '#f9e2af',
|
||||||
|
colorError: '#f38ba8',
|
||||||
|
colorInfo: '#89b4fa',
|
||||||
|
borderRadius: 6,
|
||||||
|
fontFamily: `'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif`,
|
||||||
|
fontSize: 14,
|
||||||
|
colorBgMask: 'rgba(0, 0, 0, 0.6)',
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Tree: {
|
||||||
|
directoryNodeSelectedBg: '#45475a',
|
||||||
|
nodeSelectedBg: '#45475a',
|
||||||
|
nodeHoverBg: '#313244',
|
||||||
|
},
|
||||||
|
Menu: {
|
||||||
|
itemBg: '#252536',
|
||||||
|
itemHoverBg: '#313244',
|
||||||
|
itemSelectedBg: '#45475a',
|
||||||
|
darkItemBg: '#252536',
|
||||||
|
},
|
||||||
|
Modal: {
|
||||||
|
contentBg: '#252536',
|
||||||
|
headerBg: '#252536',
|
||||||
|
footerBg: '#252536',
|
||||||
|
},
|
||||||
|
Button: {
|
||||||
|
defaultBg: '#313244',
|
||||||
|
defaultBorderColor: '#313244',
|
||||||
|
},
|
||||||
|
Input: {
|
||||||
|
activeBg: '#1e1e2e',
|
||||||
|
hoverBg: '#1e1e2e',
|
||||||
|
colorBgContainer: '#1e1e2e',
|
||||||
|
},
|
||||||
|
Select: {
|
||||||
|
colorBgContainer: '#1e1e2e',
|
||||||
|
optionActiveBg: '#313244',
|
||||||
|
optionSelectedBg: '#45475a',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div id="app-root">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</a-config-provider>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body, #app-root, #__nuxt {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--sidebar-width: 280px;
|
||||||
|
--header-height: 40px;
|
||||||
|
--bg-primary: #1e1e2e;
|
||||||
|
--bg-secondary: #181825;
|
||||||
|
--bg-surface: #252536;
|
||||||
|
--bg-hover: #313244;
|
||||||
|
--bg-active: #45475a;
|
||||||
|
--text-primary: #cdd6f4;
|
||||||
|
--text-secondary: #a6adc8;
|
||||||
|
--text-muted: #6c7086;
|
||||||
|
--border-color: #313244;
|
||||||
|
--accent: #89b4fa;
|
||||||
|
--accent-hover: #74c7ec;
|
||||||
|
--danger: #f38ba8;
|
||||||
|
--success: #a6e3a1;
|
||||||
|
--warning: #f9e2af;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 14px;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--bg-active);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ant Design 全局暗色覆盖 */
|
||||||
|
.ant-modal-mask {
|
||||||
|
background: rgba(0, 0, 0, 0.6) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-btn-text {
|
||||||
|
color: var(--text-secondary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-btn-text:hover {
|
||||||
|
color: var(--text-primary) !important;
|
||||||
|
background: var(--bg-hover) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-segmented {
|
||||||
|
background: var(--bg-primary) !important;
|
||||||
|
color: var(--text-secondary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-segmented-item-selected {
|
||||||
|
background: var(--bg-active) !important;
|
||||||
|
color: var(--text-primary) !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
74
frontend/app/pages/index.vue
Normal file
74
frontend/app/pages/index.vue
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onBeforeUnmount } from 'vue'
|
||||||
|
|
||||||
|
const selectedProject = ref(null)
|
||||||
|
|
||||||
|
function onSelectProject(project) {
|
||||||
|
selectedProject.value = project
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 侧栏拖拽调整宽度 ----
|
||||||
|
const sidebarWidth = ref(280)
|
||||||
|
const MIN_SIDEBAR = 180
|
||||||
|
const MAX_SIDEBAR = 500
|
||||||
|
let draggingSidebar = false
|
||||||
|
|
||||||
|
function startSidebarResize(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
draggingSidebar = true
|
||||||
|
const startX = e.clientX
|
||||||
|
const startW = sidebarWidth.value
|
||||||
|
|
||||||
|
function onMove(ev) {
|
||||||
|
if (!draggingSidebar) return
|
||||||
|
const delta = ev.clientX - startX
|
||||||
|
sidebarWidth.value = Math.min(MAX_SIDEBAR, Math.max(MIN_SIDEBAR, startW + delta))
|
||||||
|
}
|
||||||
|
function onUp() {
|
||||||
|
draggingSidebar = false
|
||||||
|
document.removeEventListener('mousemove', onMove)
|
||||||
|
document.removeEventListener('mouseup', onUp)
|
||||||
|
document.body.style.cursor = ''
|
||||||
|
document.body.style.userSelect = ''
|
||||||
|
}
|
||||||
|
document.body.style.cursor = 'col-resize'
|
||||||
|
document.body.style.userSelect = 'none'
|
||||||
|
document.addEventListener('mousemove', onMove)
|
||||||
|
document.addEventListener('mouseup', onUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
draggingSidebar = false
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="main-layout">
|
||||||
|
<Sidebar :selected-project="selectedProject" :style="{ width: sidebarWidth + 'px' }" @select-project="onSelectProject" />
|
||||||
|
<div class="resize-handle" @mousedown="startSidebarResize"></div>
|
||||||
|
<ContentArea :project="selectedProject" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.main-layout {
|
||||||
|
display: flex;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle {
|
||||||
|
width: 4px;
|
||||||
|
cursor: col-resize;
|
||||||
|
background: transparent;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle:hover,
|
||||||
|
.resize-handle:active {
|
||||||
|
background: var(--accent, #89b4fa);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
6
frontend/app/plugins/antd.ts
Normal file
6
frontend/app/plugins/antd.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import Antd from 'ant-design-vue'
|
||||||
|
import 'ant-design-vue/dist/reset.css'
|
||||||
|
|
||||||
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
|
nuxtApp.vueApp.use(Antd)
|
||||||
|
})
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
//@ts-check
|
||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import { Create as $Create } from "@wailsio/runtime";
|
||||||
|
|
||||||
|
Object.freeze($Create.Events);
|
||||||
2
frontend/bindings/github.com/wailsapp/wails/v3/internal/eventdata.d.ts
vendored
Normal file
2
frontend/bindings/github.com/wailsapp/wails/v3/internal/eventdata.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// @ts-check
|
||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
export {
|
||||||
|
App,
|
||||||
|
BrowserManager,
|
||||||
|
ClipboardManager,
|
||||||
|
ContextMenuManager,
|
||||||
|
DialogManager,
|
||||||
|
EnvironmentManager,
|
||||||
|
EventManager,
|
||||||
|
KeyBindingManager,
|
||||||
|
MenuManager,
|
||||||
|
ScreenManager,
|
||||||
|
SystemTrayManager,
|
||||||
|
WindowManager
|
||||||
|
} from "./models.js";
|
||||||
@@ -0,0 +1,452 @@
|
|||||||
|
// @ts-check
|
||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import { Create as $Create } from "@wailsio/runtime";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as slog$0 from "../../../../../../log/slog/models.js";
|
||||||
|
|
||||||
|
export class App {
|
||||||
|
/**
|
||||||
|
* Creates a new App instance.
|
||||||
|
* @param {Partial<App>} [$$source = {}] - The source object to create the App.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
if (!("Window" in $$source)) {
|
||||||
|
/**
|
||||||
|
* Manager pattern for organized API
|
||||||
|
* @member
|
||||||
|
* @type {WindowManager | null}
|
||||||
|
*/
|
||||||
|
this["Window"] = null;
|
||||||
|
}
|
||||||
|
if (!("ContextMenu" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {ContextMenuManager | null}
|
||||||
|
*/
|
||||||
|
this["ContextMenu"] = null;
|
||||||
|
}
|
||||||
|
if (!("KeyBinding" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {KeyBindingManager | null}
|
||||||
|
*/
|
||||||
|
this["KeyBinding"] = null;
|
||||||
|
}
|
||||||
|
if (!("Browser" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {BrowserManager | null}
|
||||||
|
*/
|
||||||
|
this["Browser"] = null;
|
||||||
|
}
|
||||||
|
if (!("Env" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {EnvironmentManager | null}
|
||||||
|
*/
|
||||||
|
this["Env"] = null;
|
||||||
|
}
|
||||||
|
if (!("Dialog" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {DialogManager | null}
|
||||||
|
*/
|
||||||
|
this["Dialog"] = null;
|
||||||
|
}
|
||||||
|
if (!("Event" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {EventManager | null}
|
||||||
|
*/
|
||||||
|
this["Event"] = null;
|
||||||
|
}
|
||||||
|
if (!("Menu" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {MenuManager | null}
|
||||||
|
*/
|
||||||
|
this["Menu"] = null;
|
||||||
|
}
|
||||||
|
if (!("Screen" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {ScreenManager | null}
|
||||||
|
*/
|
||||||
|
this["Screen"] = null;
|
||||||
|
}
|
||||||
|
if (!("Clipboard" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {ClipboardManager | null}
|
||||||
|
*/
|
||||||
|
this["Clipboard"] = null;
|
||||||
|
}
|
||||||
|
if (!("SystemTray" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {SystemTrayManager | null}
|
||||||
|
*/
|
||||||
|
this["SystemTray"] = null;
|
||||||
|
}
|
||||||
|
if (!("Logger" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {slog$0.Logger | null}
|
||||||
|
*/
|
||||||
|
this["Logger"] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new App instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {App}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
const $$createField0_0 = $$createType1;
|
||||||
|
const $$createField1_0 = $$createType3;
|
||||||
|
const $$createField2_0 = $$createType5;
|
||||||
|
const $$createField3_0 = $$createType7;
|
||||||
|
const $$createField4_0 = $$createType9;
|
||||||
|
const $$createField5_0 = $$createType11;
|
||||||
|
const $$createField6_0 = $$createType13;
|
||||||
|
const $$createField7_0 = $$createType15;
|
||||||
|
const $$createField8_0 = $$createType17;
|
||||||
|
const $$createField9_0 = $$createType19;
|
||||||
|
const $$createField10_0 = $$createType21;
|
||||||
|
const $$createField11_0 = $$createType23;
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
if ("Window" in $$parsedSource) {
|
||||||
|
$$parsedSource["Window"] = $$createField0_0($$parsedSource["Window"]);
|
||||||
|
}
|
||||||
|
if ("ContextMenu" in $$parsedSource) {
|
||||||
|
$$parsedSource["ContextMenu"] = $$createField1_0($$parsedSource["ContextMenu"]);
|
||||||
|
}
|
||||||
|
if ("KeyBinding" in $$parsedSource) {
|
||||||
|
$$parsedSource["KeyBinding"] = $$createField2_0($$parsedSource["KeyBinding"]);
|
||||||
|
}
|
||||||
|
if ("Browser" in $$parsedSource) {
|
||||||
|
$$parsedSource["Browser"] = $$createField3_0($$parsedSource["Browser"]);
|
||||||
|
}
|
||||||
|
if ("Env" in $$parsedSource) {
|
||||||
|
$$parsedSource["Env"] = $$createField4_0($$parsedSource["Env"]);
|
||||||
|
}
|
||||||
|
if ("Dialog" in $$parsedSource) {
|
||||||
|
$$parsedSource["Dialog"] = $$createField5_0($$parsedSource["Dialog"]);
|
||||||
|
}
|
||||||
|
if ("Event" in $$parsedSource) {
|
||||||
|
$$parsedSource["Event"] = $$createField6_0($$parsedSource["Event"]);
|
||||||
|
}
|
||||||
|
if ("Menu" in $$parsedSource) {
|
||||||
|
$$parsedSource["Menu"] = $$createField7_0($$parsedSource["Menu"]);
|
||||||
|
}
|
||||||
|
if ("Screen" in $$parsedSource) {
|
||||||
|
$$parsedSource["Screen"] = $$createField8_0($$parsedSource["Screen"]);
|
||||||
|
}
|
||||||
|
if ("Clipboard" in $$parsedSource) {
|
||||||
|
$$parsedSource["Clipboard"] = $$createField9_0($$parsedSource["Clipboard"]);
|
||||||
|
}
|
||||||
|
if ("SystemTray" in $$parsedSource) {
|
||||||
|
$$parsedSource["SystemTray"] = $$createField10_0($$parsedSource["SystemTray"]);
|
||||||
|
}
|
||||||
|
if ("Logger" in $$parsedSource) {
|
||||||
|
$$parsedSource["Logger"] = $$createField11_0($$parsedSource["Logger"]);
|
||||||
|
}
|
||||||
|
return new App(/** @type {Partial<App>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BrowserManager manages browser-related operations
|
||||||
|
*/
|
||||||
|
export class BrowserManager {
|
||||||
|
/**
|
||||||
|
* Creates a new BrowserManager instance.
|
||||||
|
* @param {Partial<BrowserManager>} [$$source = {}] - The source object to create the BrowserManager.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new BrowserManager instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {BrowserManager}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new BrowserManager(/** @type {Partial<BrowserManager>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ClipboardManager manages clipboard operations
|
||||||
|
*/
|
||||||
|
export class ClipboardManager {
|
||||||
|
/**
|
||||||
|
* Creates a new ClipboardManager instance.
|
||||||
|
* @param {Partial<ClipboardManager>} [$$source = {}] - The source object to create the ClipboardManager.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ClipboardManager instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {ClipboardManager}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new ClipboardManager(/** @type {Partial<ClipboardManager>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ContextMenuManager manages all context menu operations
|
||||||
|
*/
|
||||||
|
export class ContextMenuManager {
|
||||||
|
/**
|
||||||
|
* Creates a new ContextMenuManager instance.
|
||||||
|
* @param {Partial<ContextMenuManager>} [$$source = {}] - The source object to create the ContextMenuManager.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ContextMenuManager instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {ContextMenuManager}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new ContextMenuManager(/** @type {Partial<ContextMenuManager>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DialogManager manages dialog-related operations
|
||||||
|
*/
|
||||||
|
export class DialogManager {
|
||||||
|
/**
|
||||||
|
* Creates a new DialogManager instance.
|
||||||
|
* @param {Partial<DialogManager>} [$$source = {}] - The source object to create the DialogManager.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new DialogManager instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {DialogManager}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new DialogManager(/** @type {Partial<DialogManager>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EnvironmentManager manages environment-related operations
|
||||||
|
*/
|
||||||
|
export class EnvironmentManager {
|
||||||
|
/**
|
||||||
|
* Creates a new EnvironmentManager instance.
|
||||||
|
* @param {Partial<EnvironmentManager>} [$$source = {}] - The source object to create the EnvironmentManager.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new EnvironmentManager instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {EnvironmentManager}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new EnvironmentManager(/** @type {Partial<EnvironmentManager>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EventManager manages event-related operations
|
||||||
|
*/
|
||||||
|
export class EventManager {
|
||||||
|
/**
|
||||||
|
* Creates a new EventManager instance.
|
||||||
|
* @param {Partial<EventManager>} [$$source = {}] - The source object to create the EventManager.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new EventManager instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {EventManager}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new EventManager(/** @type {Partial<EventManager>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KeyBindingManager manages all key binding operations
|
||||||
|
*/
|
||||||
|
export class KeyBindingManager {
|
||||||
|
/**
|
||||||
|
* Creates a new KeyBindingManager instance.
|
||||||
|
* @param {Partial<KeyBindingManager>} [$$source = {}] - The source object to create the KeyBindingManager.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new KeyBindingManager instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {KeyBindingManager}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new KeyBindingManager(/** @type {Partial<KeyBindingManager>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MenuManager manages menu-related operations
|
||||||
|
*/
|
||||||
|
export class MenuManager {
|
||||||
|
/**
|
||||||
|
* Creates a new MenuManager instance.
|
||||||
|
* @param {Partial<MenuManager>} [$$source = {}] - The source object to create the MenuManager.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new MenuManager instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {MenuManager}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new MenuManager(/** @type {Partial<MenuManager>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ScreenManager {
|
||||||
|
/**
|
||||||
|
* Creates a new ScreenManager instance.
|
||||||
|
* @param {Partial<ScreenManager>} [$$source = {}] - The source object to create the ScreenManager.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ScreenManager instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {ScreenManager}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new ScreenManager(/** @type {Partial<ScreenManager>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SystemTrayManager manages system tray-related operations
|
||||||
|
*/
|
||||||
|
export class SystemTrayManager {
|
||||||
|
/**
|
||||||
|
* Creates a new SystemTrayManager instance.
|
||||||
|
* @param {Partial<SystemTrayManager>} [$$source = {}] - The source object to create the SystemTrayManager.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new SystemTrayManager instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {SystemTrayManager}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new SystemTrayManager(/** @type {Partial<SystemTrayManager>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WindowManager manages all window-related operations
|
||||||
|
*/
|
||||||
|
export class WindowManager {
|
||||||
|
/**
|
||||||
|
* Creates a new WindowManager instance.
|
||||||
|
* @param {Partial<WindowManager>} [$$source = {}] - The source object to create the WindowManager.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new WindowManager instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {WindowManager}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new WindowManager(/** @type {Partial<WindowManager>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private type creation functions
|
||||||
|
const $$createType0 = WindowManager.createFrom;
|
||||||
|
const $$createType1 = $Create.Nullable($$createType0);
|
||||||
|
const $$createType2 = ContextMenuManager.createFrom;
|
||||||
|
const $$createType3 = $Create.Nullable($$createType2);
|
||||||
|
const $$createType4 = KeyBindingManager.createFrom;
|
||||||
|
const $$createType5 = $Create.Nullable($$createType4);
|
||||||
|
const $$createType6 = BrowserManager.createFrom;
|
||||||
|
const $$createType7 = $Create.Nullable($$createType6);
|
||||||
|
const $$createType8 = EnvironmentManager.createFrom;
|
||||||
|
const $$createType9 = $Create.Nullable($$createType8);
|
||||||
|
const $$createType10 = DialogManager.createFrom;
|
||||||
|
const $$createType11 = $Create.Nullable($$createType10);
|
||||||
|
const $$createType12 = EventManager.createFrom;
|
||||||
|
const $$createType13 = $Create.Nullable($$createType12);
|
||||||
|
const $$createType14 = MenuManager.createFrom;
|
||||||
|
const $$createType15 = $Create.Nullable($$createType14);
|
||||||
|
const $$createType16 = ScreenManager.createFrom;
|
||||||
|
const $$createType17 = $Create.Nullable($$createType16);
|
||||||
|
const $$createType18 = ClipboardManager.createFrom;
|
||||||
|
const $$createType19 = $Create.Nullable($$createType18);
|
||||||
|
const $$createType20 = SystemTrayManager.createFrom;
|
||||||
|
const $$createType21 = $Create.Nullable($$createType20);
|
||||||
|
const $$createType22 = slog$0.Logger.createFrom;
|
||||||
|
const $$createType23 = $Create.Nullable($$createType22);
|
||||||
@@ -0,0 +1,387 @@
|
|||||||
|
// @ts-check
|
||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AppService 应用服务,暴露给前端调用
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as application$0 from "../../../../wailsapp/wails/v3/pkg/application/models.js";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as $models from "./models.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AddPlatform 添加新平台
|
||||||
|
* @param {string} name
|
||||||
|
* @param {string} baseURL
|
||||||
|
* @returns {$CancellablePromise<void>}
|
||||||
|
*/
|
||||||
|
export function AddPlatform(name, baseURL) {
|
||||||
|
return $Call.ByID(526003212, name, baseURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AddProject 添加项目到指定平台/用户下
|
||||||
|
* @param {string} platform
|
||||||
|
* @param {string} username
|
||||||
|
* @param {string} name
|
||||||
|
* @param {string} path
|
||||||
|
* @returns {$CancellablePromise<void>}
|
||||||
|
*/
|
||||||
|
export function AddProject(platform, username, name, path) {
|
||||||
|
return $Call.ByID(2299402672, platform, username, name, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AddUser 添加用户到指定平台
|
||||||
|
* @param {string} platform
|
||||||
|
* @param {string} username
|
||||||
|
* @param {string} token
|
||||||
|
* @returns {$CancellablePromise<void>}
|
||||||
|
*/
|
||||||
|
export function AddUser(platform, username, token) {
|
||||||
|
return $Call.ByID(2264426704, platform, username, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CommitChanges 提交已暂存的更改
|
||||||
|
* @param {string} path
|
||||||
|
* @param {string} message
|
||||||
|
* @returns {$CancellablePromise<void>}
|
||||||
|
*/
|
||||||
|
export function CommitChanges(path, message) {
|
||||||
|
return $Call.ByID(921984546, path, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DiscardFiles 丢弃工作区指定文件的更改
|
||||||
|
* @param {string} path
|
||||||
|
* @param {string[]} files
|
||||||
|
* @returns {$CancellablePromise<void>}
|
||||||
|
*/
|
||||||
|
export function DiscardFiles(path, files) {
|
||||||
|
return $Call.ByID(382904301, path, files);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FetchProject 拉取远程信息
|
||||||
|
* @param {string} path
|
||||||
|
* @returns {$CancellablePromise<string>}
|
||||||
|
*/
|
||||||
|
export function FetchProject(path) {
|
||||||
|
return $Call.ByID(3541106829, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetBranches 获取所有本地分支
|
||||||
|
* @param {string} path
|
||||||
|
* @returns {$CancellablePromise<$models.BranchInfo[]>}
|
||||||
|
*/
|
||||||
|
export function GetBranches(path) {
|
||||||
|
return $Call.ByID(1686190192, path).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
|
return $$createType1($result);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetCommitDiff 获取指定提交的 diff
|
||||||
|
* @param {string} path
|
||||||
|
* @param {string} hash
|
||||||
|
* @returns {$CancellablePromise<string>}
|
||||||
|
*/
|
||||||
|
export function GetCommitDiff(path, hash) {
|
||||||
|
return $Call.ByID(3678609798, path, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetCommitFileDiff 获取指定提交中某个文件的 diff
|
||||||
|
* @param {string} path
|
||||||
|
* @param {string} hash
|
||||||
|
* @param {string} filePath
|
||||||
|
* @returns {$CancellablePromise<string>}
|
||||||
|
*/
|
||||||
|
export function GetCommitFileDiff(path, hash, filePath) {
|
||||||
|
return $Call.ByID(880053428, path, hash, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetCommitFiles 获取指定提交中变更的文件列表
|
||||||
|
* @param {string} path
|
||||||
|
* @param {string} hash
|
||||||
|
* @returns {$CancellablePromise<$models.CommitFileInfo[]>}
|
||||||
|
*/
|
||||||
|
export function GetCommitFiles(path, hash) {
|
||||||
|
return $Call.ByID(2420707876, path, hash).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
|
return $$createType3($result);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetCommitLog 获取提交历史
|
||||||
|
* @param {string} path
|
||||||
|
* @param {number} count
|
||||||
|
* @returns {$CancellablePromise<$models.CommitLog[]>}
|
||||||
|
*/
|
||||||
|
export function GetCommitLog(path, count) {
|
||||||
|
return $Call.ByID(1281870789, path, count).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
|
return $$createType5($result);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetFileContent 获取文件内容
|
||||||
|
* @param {string} projectPath
|
||||||
|
* @param {string} filePath
|
||||||
|
* @returns {$CancellablePromise<string>}
|
||||||
|
*/
|
||||||
|
export function GetFileContent(projectPath, filePath) {
|
||||||
|
return $Call.ByID(3085630035, projectPath, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetFileDiff 获取文件 diff
|
||||||
|
* @param {string} projectPath
|
||||||
|
* @param {string} filePath
|
||||||
|
* @returns {$CancellablePromise<string>}
|
||||||
|
*/
|
||||||
|
export function GetFileDiff(projectPath, filePath) {
|
||||||
|
return $Call.ByID(1694304929, projectPath, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetFileDiffStaged 获取已暂存文件的 diff
|
||||||
|
* @param {string} projectPath
|
||||||
|
* @param {string} filePath
|
||||||
|
* @returns {$CancellablePromise<string>}
|
||||||
|
*/
|
||||||
|
export function GetFileDiffStaged(projectPath, filePath) {
|
||||||
|
return $Call.ByID(2773256455, projectPath, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetPlatformInfo 获取平台信息
|
||||||
|
* @param {string} name
|
||||||
|
* @returns {$CancellablePromise<$models.PlatformInfo | null>}
|
||||||
|
*/
|
||||||
|
export function GetPlatformInfo(name) {
|
||||||
|
return $Call.ByID(2668095547, name).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
|
return $$createType7($result);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetProjectChangedFiles 获取项目变更文件列表(可能较慢)
|
||||||
|
* @param {string} path
|
||||||
|
* @returns {$CancellablePromise<$models.FileInfo[]>}
|
||||||
|
*/
|
||||||
|
export function GetProjectChangedFiles(path) {
|
||||||
|
return $Call.ByID(2302591462, path).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
|
return $$createType9($result);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetProjectStatus 获取项目 git 状态
|
||||||
|
* @param {string} path
|
||||||
|
* @returns {$CancellablePromise<$models.ProjectStatus | null>}
|
||||||
|
*/
|
||||||
|
export function GetProjectStatus(path) {
|
||||||
|
return $Call.ByID(3451089027, path).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
|
return $$createType11($result);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetProjectTree 获取项目树,供前端侧边栏渲染
|
||||||
|
* @returns {$CancellablePromise<$models.TreeNode[]>}
|
||||||
|
*/
|
||||||
|
export function GetProjectTree() {
|
||||||
|
return $Call.ByID(651189689).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
|
return $$createType13($result);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetUserInfo 获取用户信息
|
||||||
|
* @param {string} platform
|
||||||
|
* @param {string} username
|
||||||
|
* @returns {$CancellablePromise<$models.UserInfo | null>}
|
||||||
|
*/
|
||||||
|
export function GetUserInfo(platform, username) {
|
||||||
|
return $Call.ByID(2674655707, platform, username).then(/** @type {($result: any) => any} */(($result) => {
|
||||||
|
return $$createType15($result);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PullProject 拉取项目(当前分支)
|
||||||
|
* @param {string} path
|
||||||
|
* @returns {$CancellablePromise<string>}
|
||||||
|
*/
|
||||||
|
export function PullProject(path) {
|
||||||
|
return $Call.ByID(4145813996, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PushProject 推送项目(当前分支)
|
||||||
|
* @param {string} path
|
||||||
|
* @returns {$CancellablePromise<string>}
|
||||||
|
*/
|
||||||
|
export function PushProject(path) {
|
||||||
|
return $Call.ByID(3887901113, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RemovePlatform 删除平台
|
||||||
|
* @param {string} name
|
||||||
|
* @returns {$CancellablePromise<void>}
|
||||||
|
*/
|
||||||
|
export function RemovePlatform(name) {
|
||||||
|
return $Call.ByID(2222448051, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RemoveProject 从指定平台/用户下删除项目
|
||||||
|
* @param {string} platform
|
||||||
|
* @param {string} username
|
||||||
|
* @param {string} name
|
||||||
|
* @returns {$CancellablePromise<void>}
|
||||||
|
*/
|
||||||
|
export function RemoveProject(platform, username, name) {
|
||||||
|
return $Call.ByID(2748109581, platform, username, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RemoveUser 从平台删除用户
|
||||||
|
* @param {string} platform
|
||||||
|
* @param {string} username
|
||||||
|
* @returns {$CancellablePromise<void>}
|
||||||
|
*/
|
||||||
|
export function RemoveUser(platform, username) {
|
||||||
|
return $Call.ByID(1409517275, platform, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ResetProject 版本回滚(git reset)
|
||||||
|
* mode: "hard"(丢弃所有更改), "soft"(保留更改到暂存区), "mixed"(保留更改到工作区)
|
||||||
|
* @param {string} path
|
||||||
|
* @param {string} hash
|
||||||
|
* @param {string} mode
|
||||||
|
* @returns {$CancellablePromise<void>}
|
||||||
|
*/
|
||||||
|
export function ResetProject(path, hash, mode) {
|
||||||
|
return $Call.ByID(2061148684, path, hash, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SelectDirectory 打开系统文件夹选择器,返回选中的路径
|
||||||
|
* @returns {$CancellablePromise<string>}
|
||||||
|
*/
|
||||||
|
export function SelectDirectory() {
|
||||||
|
return $Call.ByID(2318416763);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {application$0.App | null} app
|
||||||
|
* @returns {$CancellablePromise<void>}
|
||||||
|
*/
|
||||||
|
export function SetApplication(app) {
|
||||||
|
return $Call.ByID(383695022, app);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StageAll 暂存所有变更文件
|
||||||
|
* @param {string} path
|
||||||
|
* @returns {$CancellablePromise<void>}
|
||||||
|
*/
|
||||||
|
export function StageAll(path) {
|
||||||
|
return $Call.ByID(2344562461, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StageFiles 暂存指定文件
|
||||||
|
* @param {string} path
|
||||||
|
* @param {string[]} files
|
||||||
|
* @returns {$CancellablePromise<void>}
|
||||||
|
*/
|
||||||
|
export function StageFiles(path, files) {
|
||||||
|
return $Call.ByID(4064506881, path, files);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SwitchBranch 切换分支
|
||||||
|
* @param {string} path
|
||||||
|
* @param {string} branch
|
||||||
|
* @returns {$CancellablePromise<void>}
|
||||||
|
*/
|
||||||
|
export function SwitchBranch(path, branch) {
|
||||||
|
return $Call.ByID(4262224900, path, branch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UnstageAll 取消暂存所有文件
|
||||||
|
* @param {string} path
|
||||||
|
* @returns {$CancellablePromise<void>}
|
||||||
|
*/
|
||||||
|
export function UnstageAll(path) {
|
||||||
|
return $Call.ByID(2417621882, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UnstageFiles 取消暂存指定文件
|
||||||
|
* @param {string} path
|
||||||
|
* @param {string[]} files
|
||||||
|
* @returns {$CancellablePromise<void>}
|
||||||
|
*/
|
||||||
|
export function UnstageFiles(path, files) {
|
||||||
|
return $Call.ByID(3546473046, path, files);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UpdatePlatform 修改平台信息(base_url)
|
||||||
|
* @param {string} name
|
||||||
|
* @param {string} baseURL
|
||||||
|
* @returns {$CancellablePromise<void>}
|
||||||
|
*/
|
||||||
|
export function UpdatePlatform(name, baseURL) {
|
||||||
|
return $Call.ByID(3258014550, name, baseURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UpdateUser 修改用户信息(用户名、token)
|
||||||
|
* @param {string} platform
|
||||||
|
* @param {string} oldUsername
|
||||||
|
* @param {string} newUsername
|
||||||
|
* @param {string} token
|
||||||
|
* @returns {$CancellablePromise<void>}
|
||||||
|
*/
|
||||||
|
export function UpdateUser(platform, oldUsername, newUsername, token) {
|
||||||
|
return $Call.ByID(1631729342, platform, oldUsername, newUsername, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private type creation functions
|
||||||
|
const $$createType0 = $models.BranchInfo.createFrom;
|
||||||
|
const $$createType1 = $Create.Array($$createType0);
|
||||||
|
const $$createType2 = $models.CommitFileInfo.createFrom;
|
||||||
|
const $$createType3 = $Create.Array($$createType2);
|
||||||
|
const $$createType4 = $models.CommitLog.createFrom;
|
||||||
|
const $$createType5 = $Create.Array($$createType4);
|
||||||
|
const $$createType6 = $models.PlatformInfo.createFrom;
|
||||||
|
const $$createType7 = $Create.Nullable($$createType6);
|
||||||
|
const $$createType8 = $models.FileInfo.createFrom;
|
||||||
|
const $$createType9 = $Create.Array($$createType8);
|
||||||
|
const $$createType10 = $models.ProjectStatus.createFrom;
|
||||||
|
const $$createType11 = $Create.Nullable($$createType10);
|
||||||
|
const $$createType12 = $models.TreeNode.createFrom;
|
||||||
|
const $$createType13 = $Create.Array($$createType12);
|
||||||
|
const $$createType14 = $models.UserInfo.createFrom;
|
||||||
|
const $$createType15 = $Create.Nullable($$createType14);
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// @ts-check
|
||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
import * as AppService from "./appservice.js";
|
||||||
|
export {
|
||||||
|
AppService
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
BranchInfo,
|
||||||
|
CommitFileInfo,
|
||||||
|
CommitLog,
|
||||||
|
FileInfo,
|
||||||
|
PlatformInfo,
|
||||||
|
ProjectStatus,
|
||||||
|
TreeNode,
|
||||||
|
UserInfo
|
||||||
|
} from "./models.js";
|
||||||
@@ -0,0 +1,396 @@
|
|||||||
|
// @ts-check
|
||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import { Create as $Create } from "@wailsio/runtime";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BranchInfo 分支信息
|
||||||
|
*/
|
||||||
|
export class BranchInfo {
|
||||||
|
/**
|
||||||
|
* Creates a new BranchInfo instance.
|
||||||
|
* @param {Partial<BranchInfo>} [$$source = {}] - The source object to create the BranchInfo.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
if (!("name" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["name"] = "";
|
||||||
|
}
|
||||||
|
if (!("current" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this["current"] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new BranchInfo instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {BranchInfo}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new BranchInfo(/** @type {Partial<BranchInfo>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CommitFileInfo 提交中的文件变更信息
|
||||||
|
*/
|
||||||
|
export class CommitFileInfo {
|
||||||
|
/**
|
||||||
|
* Creates a new CommitFileInfo instance.
|
||||||
|
* @param {Partial<CommitFileInfo>} [$$source = {}] - The source object to create the CommitFileInfo.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
if (!("status" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["status"] = "";
|
||||||
|
}
|
||||||
|
if (!("filePath" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["filePath"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new CommitFileInfo instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {CommitFileInfo}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new CommitFileInfo(/** @type {Partial<CommitFileInfo>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CommitLog 提交记录
|
||||||
|
*/
|
||||||
|
export class CommitLog {
|
||||||
|
/**
|
||||||
|
* Creates a new CommitLog instance.
|
||||||
|
* @param {Partial<CommitLog>} [$$source = {}] - The source object to create the CommitLog.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
if (!("hash" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["hash"] = "";
|
||||||
|
}
|
||||||
|
if (!("shortHash" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["shortHash"] = "";
|
||||||
|
}
|
||||||
|
if (!("author" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["author"] = "";
|
||||||
|
}
|
||||||
|
if (!("email" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["email"] = "";
|
||||||
|
}
|
||||||
|
if (!("timestamp" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this["timestamp"] = 0;
|
||||||
|
}
|
||||||
|
if (!("message" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["message"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new CommitLog instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {CommitLog}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new CommitLog(/** @type {Partial<CommitLog>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FileInfo 文件信息
|
||||||
|
*/
|
||||||
|
export class FileInfo {
|
||||||
|
/**
|
||||||
|
* Creates a new FileInfo instance.
|
||||||
|
* @param {Partial<FileInfo>} [$$source = {}] - The source object to create the FileInfo.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
if (!("status" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["status"] = "";
|
||||||
|
}
|
||||||
|
if (!("statusText" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["statusText"] = "";
|
||||||
|
}
|
||||||
|
if (!("filePath" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["filePath"] = "";
|
||||||
|
}
|
||||||
|
if (!("staged" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this["staged"] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new FileInfo instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {FileInfo}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new FileInfo(/** @type {Partial<FileInfo>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PlatformInfo 平台信息
|
||||||
|
*/
|
||||||
|
export class PlatformInfo {
|
||||||
|
/**
|
||||||
|
* Creates a new PlatformInfo instance.
|
||||||
|
* @param {Partial<PlatformInfo>} [$$source = {}] - The source object to create the PlatformInfo.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
if (!("name" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["name"] = "";
|
||||||
|
}
|
||||||
|
if (!("baseUrl" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["baseUrl"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new PlatformInfo instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {PlatformInfo}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new PlatformInfo(/** @type {Partial<PlatformInfo>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ProjectStatus 项目状态信息
|
||||||
|
*/
|
||||||
|
export class ProjectStatus {
|
||||||
|
/**
|
||||||
|
* Creates a new ProjectStatus instance.
|
||||||
|
* @param {Partial<ProjectStatus>} [$$source = {}] - The source object to create the ProjectStatus.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
if (!("branch" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["branch"] = "";
|
||||||
|
}
|
||||||
|
if (!("remoteUrl" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["remoteUrl"] = "";
|
||||||
|
}
|
||||||
|
if (!("changedFiles" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {FileInfo[]}
|
||||||
|
*/
|
||||||
|
this["changedFiles"] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ProjectStatus instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {ProjectStatus}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
const $$createField2_0 = $$createType1;
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
if ("changedFiles" in $$parsedSource) {
|
||||||
|
$$parsedSource["changedFiles"] = $$createField2_0($$parsedSource["changedFiles"]);
|
||||||
|
}
|
||||||
|
return new ProjectStatus(/** @type {Partial<ProjectStatus>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TreeNode 前端侧边栏树节点
|
||||||
|
*/
|
||||||
|
export class TreeNode {
|
||||||
|
/**
|
||||||
|
* Creates a new TreeNode instance.
|
||||||
|
* @param {Partial<TreeNode>} [$$source = {}] - The source object to create the TreeNode.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
if (!("key" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["key"] = "";
|
||||||
|
}
|
||||||
|
if (!("label" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["label"] = "";
|
||||||
|
}
|
||||||
|
if (!("type" in $$source)) {
|
||||||
|
/**
|
||||||
|
* platform, user, project
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["type"] = "";
|
||||||
|
}
|
||||||
|
if (/** @type {any} */(false)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string | undefined}
|
||||||
|
*/
|
||||||
|
this["path"] = undefined;
|
||||||
|
}
|
||||||
|
if (/** @type {any} */(false)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {TreeNode[] | undefined}
|
||||||
|
*/
|
||||||
|
this["children"] = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new TreeNode instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {TreeNode}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
const $$createField4_0 = $$createType3;
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
if ("children" in $$parsedSource) {
|
||||||
|
$$parsedSource["children"] = $$createField4_0($$parsedSource["children"]);
|
||||||
|
}
|
||||||
|
return new TreeNode(/** @type {Partial<TreeNode>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UserInfo 用户信息
|
||||||
|
*/
|
||||||
|
export class UserInfo {
|
||||||
|
/**
|
||||||
|
* Creates a new UserInfo instance.
|
||||||
|
* @param {Partial<UserInfo>} [$$source = {}] - The source object to create the UserInfo.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
if (!("username" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["username"] = "";
|
||||||
|
}
|
||||||
|
if (!("token" in $$source)) {
|
||||||
|
/**
|
||||||
|
* @member
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this["token"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new UserInfo instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {UserInfo}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new UserInfo(/** @type {Partial<UserInfo>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private type creation functions
|
||||||
|
const $$createType0 = FileInfo.createFrom;
|
||||||
|
const $$createType1 = $Create.Array($$createType0);
|
||||||
|
const $$createType2 = TreeNode.createFrom;
|
||||||
|
const $$createType3 = $Create.Array($$createType2);
|
||||||
7
frontend/bindings/log/slog/index.js
Normal file
7
frontend/bindings/log/slog/index.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// @ts-check
|
||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
export {
|
||||||
|
Logger
|
||||||
|
} from "./models.js";
|
||||||
36
frontend/bindings/log/slog/models.js
Normal file
36
frontend/bindings/log/slog/models.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// @ts-check
|
||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import { Create as $Create } from "@wailsio/runtime";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Logger records structured information about each call to its
|
||||||
|
* Log, Debug, Info, Warn, and Error methods.
|
||||||
|
* For each call, it creates a [Record] and passes it to a [Handler].
|
||||||
|
*
|
||||||
|
* To create a new Logger, call [New] or a Logger method
|
||||||
|
* that begins "With".
|
||||||
|
*/
|
||||||
|
export class Logger {
|
||||||
|
/**
|
||||||
|
* Creates a new Logger instance.
|
||||||
|
* @param {Partial<Logger>} [$$source = {}] - The source object to create the Logger.
|
||||||
|
*/
|
||||||
|
constructor($$source = {}) {
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Logger instance from a string or object.
|
||||||
|
* @param {any} [$$source = {}]
|
||||||
|
* @returns {Logger}
|
||||||
|
*/
|
||||||
|
static createFrom($$source = {}) {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new Logger(/** @type {Partial<Logger>} */($$parsedSource));
|
||||||
|
}
|
||||||
|
}
|
||||||
20
frontend/nuxt.config.ts
Normal file
20
frontend/nuxt.config.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export default defineNuxtConfig({
|
||||||
|
compatibilityDate: '2025-07-15',
|
||||||
|
devtools: { enabled: true },
|
||||||
|
ssr: false,
|
||||||
|
nitro: {
|
||||||
|
preset: 'static',
|
||||||
|
output: {
|
||||||
|
publicDir: './dist'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
vite: {
|
||||||
|
optimizeDeps: {
|
||||||
|
exclude: ['@wailsio/runtime']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
'~/plugins/antd',
|
||||||
|
],
|
||||||
|
transpile: ['ant-design-vue', '@ant-design/icons-vue']
|
||||||
|
})
|
||||||
9975
frontend/package-lock.json
generated
Normal file
9975
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
frontend/package.json
Normal file
20
frontend/package.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "gitpilot-frontend",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "nuxt build",
|
||||||
|
"dev": "nuxt dev",
|
||||||
|
"generate": "nuxt generate",
|
||||||
|
"preview": "nuxt preview",
|
||||||
|
"postinstall": "nuxt prepare"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@ant-design/icons-vue": "^7.0.1",
|
||||||
|
"@wailsio/runtime": "^3.0.0-alpha.79",
|
||||||
|
"ant-design-vue": "^4.2.6",
|
||||||
|
"nuxt": "^4.1.2",
|
||||||
|
"vue": "^3.5.21",
|
||||||
|
"vue-router": "^4.5.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
frontend/public/appicon.png
Normal file
BIN
frontend/public/appicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
2
frontend/public/robots.txt
Normal file
2
frontend/public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
User-Agent: *
|
||||||
|
Disallow:
|
||||||
58
frontend/public/style.css
Normal file
58
frontend/public/style.css
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
:root {
|
||||||
|
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
|
||||||
|
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||||
|
sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-weight: 400;
|
||||||
|
color-scheme: light dark;
|
||||||
|
color: rgba(255, 255, 255, 0.87);
|
||||||
|
background-color: rgba(27, 38, 54, 1);
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
place-content: center;
|
||||||
|
min-width: 320px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 3.2em;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #646cff;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #535bf2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
color: #213547;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #747bff;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
frontend/tsconfig.json
Normal file
9
frontend/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./.nuxt/tsconfig.app.json" },
|
||||||
|
{ "path": "./.nuxt/tsconfig.server.json" },
|
||||||
|
{ "path": "./.nuxt/tsconfig.shared.json" },
|
||||||
|
{ "path": "./.nuxt/tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
46
go.mod
46
go.mod
@@ -3,6 +3,48 @@ module github.com/zhuy1228/GitPilot
|
|||||||
go 1.25.5
|
go 1.25.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.74
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
golang.org/x/sys v0.41.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
dario.cat/mergo v1.0.2 // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
|
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||||
|
github.com/adrg/xdg v0.5.3 // indirect
|
||||||
|
github.com/bep/debounce v1.2.1 // indirect
|
||||||
|
github.com/cloudflare/circl v1.6.3 // indirect
|
||||||
|
github.com/coder/websocket v1.8.14 // indirect
|
||||||
|
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||||
|
github.com/ebitengine/purego v0.9.1 // indirect
|
||||||
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
|
github.com/go-git/go-billy/v5 v5.7.0 // indirect
|
||||||
|
github.com/go-git/go-git/v5 v5.16.4 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
|
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
|
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
|
||||||
|
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
|
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||||
|
github.com/leaanthony/u v1.1.1 // indirect
|
||||||
|
github.com/lmittmann/tint v1.1.2 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
||||||
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
github.com/samber/lo v1.52.0 // indirect
|
||||||
|
github.com/sergi/go-diff v1.4.0 // indirect
|
||||||
|
github.com/skeema/knownhosts v1.3.2 // indirect
|
||||||
|
github.com/wailsapp/go-webview2 v1.0.23 // indirect
|
||||||
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
|
golang.org/x/crypto v0.47.0 // indirect
|
||||||
|
golang.org/x/net v0.49.0 // indirect
|
||||||
|
golang.org/x/text v0.33.0 // indirect
|
||||||
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
144
go.sum
144
go.sum
@@ -1,5 +1,149 @@
|
|||||||
|
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||||
|
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||||
|
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
|
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
||||||
|
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||||
|
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||||
|
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
|
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||||
|
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||||
|
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||||
|
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||||
|
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||||
|
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||||
|
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
||||||
|
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||||
|
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/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||||
|
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
|
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||||
|
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||||
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
|
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||||
|
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||||
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||||
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||||
|
github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=
|
||||||
|
github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=
|
||||||
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||||
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||||
|
github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y=
|
||||||
|
github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||||
|
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU=
|
||||||
|
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
|
||||||
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
|
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
||||||
|
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||||
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||||
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
|
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ=
|
||||||
|
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||||
|
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
|
||||||
|
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
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/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
|
||||||
|
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||||
|
github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
|
||||||
|
github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||||
|
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.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
|
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/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||||
|
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||||
|
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
|
||||||
|
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||||
|
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
|
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||||
|
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||||
|
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||||
|
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||||
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
|
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
|
||||||
|
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0=
|
||||||
|
github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||||
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.74 h1:wRm1EiDQtxDisXk46NtpiBH90STwfKp36NrTDwOEdxw=
|
||||||
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.74/go.mod h1:4saK4A4K9970X+X7RkMwP2lyGbLogcUz54wVeq4C/V8=
|
||||||
|
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||||
|
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||||
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||||
|
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||||
|
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
|
||||||
|
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||||
|
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||||
|
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||||
|
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||||
|
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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||||
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
670
internal/app/service.go
Normal file
670
internal/app/service.go
Normal file
@@ -0,0 +1,670 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
"github.com/zhuy1228/GitPilot/config"
|
||||||
|
"github.com/zhuy1228/GitPilot/internal/git"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AppService 应用服务,暴露给前端调用
|
||||||
|
type AppService struct {
|
||||||
|
app *application.App
|
||||||
|
config *config.AppConfig
|
||||||
|
gitClient *git.GitClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AppService) SetApplication(app *application.App) {
|
||||||
|
a.app = app
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *AppService {
|
||||||
|
cfg, err := config.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("加载配置失败: %v, 使用默认配置", err)
|
||||||
|
cfg = &config.AppConfig{
|
||||||
|
Platforms: make(map[string]config.Platform),
|
||||||
|
Settings: config.Settings{Concurrency: 6, NetworkCheck: true, LogLevel: "info"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &AppService{
|
||||||
|
config: cfg,
|
||||||
|
gitClient: git.NewGitClient(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectDirectory 打开系统文件夹选择器,返回选中的路径
|
||||||
|
func (s *AppService) SelectDirectory() (string, error) {
|
||||||
|
if s.app == nil {
|
||||||
|
return "", fmt.Errorf("应用未初始化")
|
||||||
|
}
|
||||||
|
path, err := s.app.Dialog.OpenFile().
|
||||||
|
CanChooseDirectories(true).
|
||||||
|
CanChooseFiles(false).
|
||||||
|
SetTitle("选择项目文件夹").
|
||||||
|
PromptForSingleSelection()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("打开文件夹选择器失败: %w", err)
|
||||||
|
}
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 平台/项目 树形结构 ---
|
||||||
|
|
||||||
|
// TreeNode 前端侧边栏树节点
|
||||||
|
type TreeNode struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
Type string `json:"type"` // platform, user, project
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
Children []TreeNode `json:"children,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProjectTree 获取项目树,供前端侧边栏渲染
|
||||||
|
func (s *AppService) GetProjectTree() []TreeNode {
|
||||||
|
var tree []TreeNode
|
||||||
|
for platformName, platform := range s.config.Platforms {
|
||||||
|
platformNode := TreeNode{
|
||||||
|
Key: platformName,
|
||||||
|
Label: platformName,
|
||||||
|
Type: "platform",
|
||||||
|
}
|
||||||
|
for _, user := range platform.Users {
|
||||||
|
userNode := TreeNode{
|
||||||
|
Key: platformName + "/" + user.Username,
|
||||||
|
Label: user.Username,
|
||||||
|
Type: "user",
|
||||||
|
}
|
||||||
|
for _, proj := range user.Projects {
|
||||||
|
userNode.Children = append(userNode.Children, TreeNode{
|
||||||
|
Key: platformName + "/" + user.Username + "/" + proj.Name,
|
||||||
|
Label: proj.Name,
|
||||||
|
Type: "project",
|
||||||
|
Path: proj.Path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
platformNode.Children = append(platformNode.Children, userNode)
|
||||||
|
}
|
||||||
|
tree = append(tree, platformNode)
|
||||||
|
}
|
||||||
|
return tree
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 项目管理 ---
|
||||||
|
|
||||||
|
// AddProject 添加项目到指定平台/用户下
|
||||||
|
func (s *AppService) AddProject(platform, username, name, path string) error {
|
||||||
|
p, ok := s.config.Platforms[platform]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("平台 %s 不存在", platform)
|
||||||
|
}
|
||||||
|
for i, user := range p.Users {
|
||||||
|
if user.Username == username {
|
||||||
|
// 检查重复
|
||||||
|
for _, proj := range user.Projects {
|
||||||
|
if proj.Name == name {
|
||||||
|
return fmt.Errorf("项目 %s 已存在", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.config.Platforms[platform].Users[i].Projects = append(
|
||||||
|
s.config.Platforms[platform].Users[i].Projects,
|
||||||
|
config.Project{Name: name, Path: path},
|
||||||
|
)
|
||||||
|
return config.SaveConfig(s.config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("用户 %s 不存在于平台 %s", username, platform)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveProject 从指定平台/用户下删除项目
|
||||||
|
func (s *AppService) RemoveProject(platform, username, name string) error {
|
||||||
|
p, ok := s.config.Platforms[platform]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("平台 %s 不存在", platform)
|
||||||
|
}
|
||||||
|
for i, user := range p.Users {
|
||||||
|
if user.Username == username {
|
||||||
|
projects := user.Projects
|
||||||
|
for j, proj := range projects {
|
||||||
|
if proj.Name == name {
|
||||||
|
s.config.Platforms[platform].Users[i].Projects = append(projects[:j], projects[j+1:]...)
|
||||||
|
return config.SaveConfig(s.config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("项目 %s 不存在", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("用户 %s 不存在于平台 %s", username, platform)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 平台管理 ---
|
||||||
|
|
||||||
|
// AddPlatform 添加新平台
|
||||||
|
func (s *AppService) AddPlatform(name, baseURL string) error {
|
||||||
|
if name == "" {
|
||||||
|
return fmt.Errorf("平台名称不能为空")
|
||||||
|
}
|
||||||
|
if _, ok := s.config.Platforms[name]; ok {
|
||||||
|
return fmt.Errorf("平台 %s 已存在", name)
|
||||||
|
}
|
||||||
|
s.config.Platforms[name] = config.Platform{
|
||||||
|
BaseURL: baseURL,
|
||||||
|
Users: []config.User{},
|
||||||
|
}
|
||||||
|
return config.SaveConfig(s.config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePlatform 修改平台信息(base_url)
|
||||||
|
func (s *AppService) UpdatePlatform(name, baseURL string) error {
|
||||||
|
p, ok := s.config.Platforms[name]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("平台 %s 不存在", name)
|
||||||
|
}
|
||||||
|
p.BaseURL = baseURL
|
||||||
|
s.config.Platforms[name] = p
|
||||||
|
return config.SaveConfig(s.config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemovePlatform 删除平台
|
||||||
|
func (s *AppService) RemovePlatform(name string) error {
|
||||||
|
if _, ok := s.config.Platforms[name]; !ok {
|
||||||
|
return fmt.Errorf("平台 %s 不存在", name)
|
||||||
|
}
|
||||||
|
delete(s.config.Platforms, name)
|
||||||
|
return config.SaveConfig(s.config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 用户管理 ---
|
||||||
|
|
||||||
|
// AddUser 添加用户到指定平台
|
||||||
|
func (s *AppService) AddUser(platform, username, token string) error {
|
||||||
|
p, ok := s.config.Platforms[platform]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("平台 %s 不存在", platform)
|
||||||
|
}
|
||||||
|
if username == "" {
|
||||||
|
return fmt.Errorf("用户名不能为空")
|
||||||
|
}
|
||||||
|
for _, user := range p.Users {
|
||||||
|
if user.Username == username {
|
||||||
|
return fmt.Errorf("用户 %s 已存在于平台 %s", username, platform)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.Users = append(p.Users, config.User{
|
||||||
|
Username: username,
|
||||||
|
Token: token,
|
||||||
|
Projects: []config.Project{},
|
||||||
|
})
|
||||||
|
s.config.Platforms[platform] = p
|
||||||
|
return config.SaveConfig(s.config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUser 修改用户信息(用户名、token)
|
||||||
|
func (s *AppService) UpdateUser(platform, oldUsername, newUsername, token string) error {
|
||||||
|
p, ok := s.config.Platforms[platform]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("平台 %s 不存在", platform)
|
||||||
|
}
|
||||||
|
if newUsername == "" {
|
||||||
|
return fmt.Errorf("新用户名不能为空")
|
||||||
|
}
|
||||||
|
for i, user := range p.Users {
|
||||||
|
if user.Username == oldUsername {
|
||||||
|
// 如果改了用户名,检查新名字是否冲突
|
||||||
|
if oldUsername != newUsername {
|
||||||
|
for _, u := range p.Users {
|
||||||
|
if u.Username == newUsername {
|
||||||
|
return fmt.Errorf("用户 %s 已存在于平台 %s", newUsername, platform)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.config.Platforms[platform].Users[i].Username = newUsername
|
||||||
|
s.config.Platforms[platform].Users[i].Token = token
|
||||||
|
return config.SaveConfig(s.config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("用户 %s 不存在于平台 %s", oldUsername, platform)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveUser 从平台删除用户
|
||||||
|
func (s *AppService) RemoveUser(platform, username string) error {
|
||||||
|
p, ok := s.config.Platforms[platform]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("平台 %s 不存在", platform)
|
||||||
|
}
|
||||||
|
for i, user := range p.Users {
|
||||||
|
if user.Username == username {
|
||||||
|
s.config.Platforms[platform] = config.Platform{
|
||||||
|
BaseURL: p.BaseURL,
|
||||||
|
Users: append(p.Users[:i], p.Users[i+1:]...),
|
||||||
|
}
|
||||||
|
return config.SaveConfig(s.config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("用户 %s 不存在于平台 %s", username, platform)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo 获取用户信息
|
||||||
|
func (s *AppService) GetUserInfo(platform, username string) (*UserInfo, error) {
|
||||||
|
p, ok := s.config.Platforms[platform]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("平台 %s 不存在", platform)
|
||||||
|
}
|
||||||
|
for _, user := range p.Users {
|
||||||
|
if user.Username == username {
|
||||||
|
return &UserInfo{
|
||||||
|
Username: user.Username,
|
||||||
|
Token: user.Token,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("用户 %s 不存在于平台 %s", username, platform)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPlatformInfo 获取平台信息
|
||||||
|
func (s *AppService) GetPlatformInfo(name string) (*PlatformInfo, error) {
|
||||||
|
p, ok := s.config.Platforms[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("平台 %s 不存在", name)
|
||||||
|
}
|
||||||
|
return &PlatformInfo{
|
||||||
|
Name: name,
|
||||||
|
BaseURL: p.BaseURL,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Git 操作 ---
|
||||||
|
|
||||||
|
// ProjectStatus 项目状态信息
|
||||||
|
type ProjectStatus struct {
|
||||||
|
Branch string `json:"branch"`
|
||||||
|
RemoteURL string `json:"remoteUrl"`
|
||||||
|
ChangedFiles []FileInfo `json:"changedFiles"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileInfo 文件信息
|
||||||
|
type FileInfo struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
StatusText string `json:"statusText"`
|
||||||
|
FilePath string `json:"filePath"`
|
||||||
|
Staged bool `json:"staged"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserInfo 用户信息
|
||||||
|
type UserInfo struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlatformInfo 平台信息
|
||||||
|
type PlatformInfo struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
BaseURL string `json:"baseUrl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProjectStatus 获取项目 git 状态
|
||||||
|
func (s *AppService) GetProjectStatus(path string) (*ProjectStatus, error) {
|
||||||
|
// 先校验路径是否存在
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("项目路径不存在: %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
branch, err := s.gitClient.Branch(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取分支失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteURL, _ := s.gitClient.RemoteURL(path)
|
||||||
|
|
||||||
|
return &ProjectStatus{
|
||||||
|
Branch: strings.TrimSpace(branch),
|
||||||
|
RemoteURL: remoteURL,
|
||||||
|
ChangedFiles: []FileInfo{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProjectChangedFiles 获取项目变更文件列表(可能较慢)
|
||||||
|
func (s *AppService) GetProjectChangedFiles(path string) ([]FileInfo, error) {
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("项目路径不存在: %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
changes, err := s.gitClient.ChangedFiles(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取变更文件失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var files []FileInfo
|
||||||
|
for _, c := range changes {
|
||||||
|
files = append(files, FileInfo{
|
||||||
|
Status: c.Status,
|
||||||
|
StatusText: c.StatusText(),
|
||||||
|
FilePath: c.FilePath,
|
||||||
|
Staged: c.Staged,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileContent 获取文件内容
|
||||||
|
func (s *AppService) GetFileContent(projectPath, filePath string) (string, error) {
|
||||||
|
fullPath := filepath.Join(projectPath, filePath)
|
||||||
|
|
||||||
|
// 检查文件大小,超过 2MB 不读取
|
||||||
|
info, err := os.Stat(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("读取文件失败: %w", err)
|
||||||
|
}
|
||||||
|
if info.Size() > 2*1024*1024 {
|
||||||
|
return "", fmt.Errorf("文件过大 (%.1f MB),不支持预览", float64(info.Size())/1024/1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("读取文件失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测二进制文件:取前 8KB 检查是否含 NUL 字节
|
||||||
|
checkLen := len(data)
|
||||||
|
if checkLen > 8192 {
|
||||||
|
checkLen = 8192
|
||||||
|
}
|
||||||
|
for _, b := range data[:checkLen] {
|
||||||
|
if b == 0 {
|
||||||
|
return "", fmt.Errorf("二进制文件,不支持预览")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileDiff 获取文件 diff
|
||||||
|
func (s *AppService) GetFileDiff(projectPath, filePath string) (string, error) {
|
||||||
|
return s.gitClient.DiffFile(projectPath, filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StageFiles 暂存指定文件
|
||||||
|
func (s *AppService) StageFiles(path string, files []string) error {
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("项目路径不存在: %s", path)
|
||||||
|
}
|
||||||
|
if len(files) == 0 {
|
||||||
|
return fmt.Errorf("未指定文件")
|
||||||
|
}
|
||||||
|
_, err := s.gitClient.Add(path, files...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnstageFiles 取消暂存指定文件
|
||||||
|
func (s *AppService) UnstageFiles(path string, files []string) error {
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("项目路径不存在: %s", path)
|
||||||
|
}
|
||||||
|
if len(files) == 0 {
|
||||||
|
return fmt.Errorf("未指定文件")
|
||||||
|
}
|
||||||
|
_, err := s.gitClient.Reset(path, files...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// StageAll 暂存所有变更文件
|
||||||
|
func (s *AppService) StageAll(path string) error {
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("项目路径不存在: %s", path)
|
||||||
|
}
|
||||||
|
_, err := s.gitClient.Add(path, ".")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnstageAll 取消暂存所有文件
|
||||||
|
func (s *AppService) UnstageAll(path string) error {
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("项目路径不存在: %s", path)
|
||||||
|
}
|
||||||
|
_, err := s.gitClient.Reset(path, ".")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitChanges 提交已暂存的更改
|
||||||
|
func (s *AppService) CommitChanges(path, message string) error {
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("项目路径不存在: %s", path)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(message) == "" {
|
||||||
|
return fmt.Errorf("提交信息不能为空")
|
||||||
|
}
|
||||||
|
_, err := s.gitClient.Commit(path, message)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiscardFiles 丢弃工作区指定文件的更改
|
||||||
|
func (s *AppService) DiscardFiles(path string, files []string) error {
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("项目路径不存在: %s", path)
|
||||||
|
}
|
||||||
|
if len(files) == 0 {
|
||||||
|
return fmt.Errorf("未指定文件")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对每个文件判断:未跟踪的用 clean 删除,已跟踪的用 checkout 还原
|
||||||
|
changes, err := s.gitClient.ChangedFiles(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("获取变更状态失败: %w", err)
|
||||||
|
}
|
||||||
|
untrackedMap := make(map[string]bool)
|
||||||
|
for _, c := range changes {
|
||||||
|
if c.Status == "?" {
|
||||||
|
untrackedMap[c.FilePath] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var trackedFiles, untrackedFiles []string
|
||||||
|
for _, f := range files {
|
||||||
|
if untrackedMap[f] {
|
||||||
|
untrackedFiles = append(untrackedFiles, f)
|
||||||
|
} else {
|
||||||
|
trackedFiles = append(trackedFiles, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(trackedFiles) > 0 {
|
||||||
|
if _, err := s.gitClient.Restore(path, trackedFiles...); err != nil {
|
||||||
|
return fmt.Errorf("还原文件失败: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(untrackedFiles) > 0 {
|
||||||
|
if _, err := s.gitClient.CleanFiles(path, untrackedFiles...); err != nil {
|
||||||
|
return fmt.Errorf("删除未跟踪文件失败: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileDiffStaged 获取已暂存文件的 diff
|
||||||
|
func (s *AppService) GetFileDiffStaged(projectPath, filePath string) (string, error) {
|
||||||
|
return s.gitClient.DiffStagedFile(projectPath, filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PullProject 拉取项目(当前分支)
|
||||||
|
func (s *AppService) PullProject(path string) (string, error) {
|
||||||
|
branch, err := s.gitClient.Branch(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("获取当前分支失败: %w", err)
|
||||||
|
}
|
||||||
|
return s.gitClient.Run(path, "pull", "origin", strings.TrimSpace(branch))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushProject 推送项目(当前分支)
|
||||||
|
func (s *AppService) PushProject(path string) (string, error) {
|
||||||
|
branch, err := s.gitClient.Branch(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("获取当前分支失败: %w", err)
|
||||||
|
}
|
||||||
|
return s.gitClient.Run(path, "push", "origin", strings.TrimSpace(branch))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommitDiff 获取指定提交的 diff
|
||||||
|
func (s *AppService) GetCommitDiff(path, hash string) (string, error) {
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return "", fmt.Errorf("项目路径不存在: %s", path)
|
||||||
|
}
|
||||||
|
return s.gitClient.CommitShow(path, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitFileInfo 提交中的文件变更信息
|
||||||
|
type CommitFileInfo struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
FilePath string `json:"filePath"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommitFiles 获取指定提交中变更的文件列表
|
||||||
|
func (s *AppService) GetCommitFiles(path, hash string) ([]CommitFileInfo, error) {
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("项目路径不存在: %s", path)
|
||||||
|
}
|
||||||
|
out, err := s.gitClient.CommitFiles(path, hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取提交文件列表失败: %w", err)
|
||||||
|
}
|
||||||
|
var files []CommitFileInfo
|
||||||
|
for _, line := range strings.Split(strings.TrimSpace(out), "\n") {
|
||||||
|
if strings.TrimSpace(line) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := strings.Fields(line)
|
||||||
|
if len(parts) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
files = append(files, CommitFileInfo{
|
||||||
|
Status: parts[0],
|
||||||
|
FilePath: parts[len(parts)-1],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommitFileDiff 获取指定提交中某个文件的 diff
|
||||||
|
func (s *AppService) GetCommitFileDiff(path, hash, filePath string) (string, error) {
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return "", fmt.Errorf("项目路径不存在: %s", path)
|
||||||
|
}
|
||||||
|
return s.gitClient.CommitFileDiff(path, hash, filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchProject 拉取远程信息
|
||||||
|
func (s *AppService) FetchProject(path string) (string, error) {
|
||||||
|
return s.gitClient.Fetch(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitLog 提交记录
|
||||||
|
type CommitLog struct {
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
ShortHash string `json:"shortHash"`
|
||||||
|
Author string `json:"author"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BranchInfo 分支信息
|
||||||
|
type BranchInfo struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Current bool `json:"current"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommitLog 获取提交历史
|
||||||
|
func (s *AppService) GetCommitLog(path string, count int) ([]CommitLog, error) {
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("项目路径不存在: %s", path)
|
||||||
|
}
|
||||||
|
if count <= 0 {
|
||||||
|
count = 50
|
||||||
|
}
|
||||||
|
out, err := s.gitClient.Log(path, count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取提交历史失败: %w", err)
|
||||||
|
}
|
||||||
|
return parseCommitLog(out), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCommitLog(output string) []CommitLog {
|
||||||
|
var logs []CommitLog
|
||||||
|
entries := strings.Split(output, "---END---")
|
||||||
|
for _, entry := range entries {
|
||||||
|
entry = strings.TrimSpace(entry)
|
||||||
|
if entry == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines := strings.SplitN(entry, "\n", 6)
|
||||||
|
if len(lines) < 6 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var ts int64
|
||||||
|
fmt.Sscanf(lines[4], "%d", &ts)
|
||||||
|
logs = append(logs, CommitLog{
|
||||||
|
Hash: lines[0],
|
||||||
|
ShortHash: lines[1],
|
||||||
|
Author: lines[2],
|
||||||
|
Email: lines[3],
|
||||||
|
Timestamp: ts,
|
||||||
|
Message: lines[5],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return logs
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBranches 获取所有本地分支
|
||||||
|
func (s *AppService) GetBranches(path string) ([]BranchInfo, error) {
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("项目路径不存在: %s", path)
|
||||||
|
}
|
||||||
|
out, err := s.gitClient.BranchList(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取分支列表失败: %w", err)
|
||||||
|
}
|
||||||
|
var branches []BranchInfo
|
||||||
|
for _, line := range strings.Split(strings.TrimSpace(out), "\n") {
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(line, "\t", 2)
|
||||||
|
name := parts[0]
|
||||||
|
current := len(parts) > 1 && strings.TrimSpace(parts[1]) == "*"
|
||||||
|
branches = append(branches, BranchInfo{Name: name, Current: current})
|
||||||
|
}
|
||||||
|
return branches, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetProject 版本回滚(git reset)
|
||||||
|
// mode: "hard"(丢弃所有更改), "soft"(保留更改到暂存区), "mixed"(保留更改到工作区)
|
||||||
|
func (s *AppService) ResetProject(path, hash, mode string) error {
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("项目路径不存在: %s", path)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(hash) == "" {
|
||||||
|
return fmt.Errorf("提交哈希不能为空")
|
||||||
|
}
|
||||||
|
allowed := map[string]bool{"hard": true, "soft": true, "mixed": true}
|
||||||
|
if !allowed[mode] {
|
||||||
|
mode = "hard"
|
||||||
|
}
|
||||||
|
_, err := s.gitClient.ResetToCommit(path, strings.TrimSpace(hash), mode)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwitchBranch 切换分支
|
||||||
|
func (s *AppService) SwitchBranch(path, branch string) error {
|
||||||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("项目路径不存在: %s", path)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(branch) == "" {
|
||||||
|
return fmt.Errorf("分支名不能为空")
|
||||||
|
}
|
||||||
|
_, err := s.gitClient.Checkout(path, strings.TrimSpace(branch))
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -4,7 +4,9 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zhuy1228/GitPilot/internal"
|
"github.com/zhuy1228/GitPilot/internal"
|
||||||
@@ -21,6 +23,7 @@ func NewGitClient() *GitClient {
|
|||||||
return &GitClient{
|
return &GitClient{
|
||||||
Enabled: proxy.Enabled,
|
Enabled: proxy.Enabled,
|
||||||
Proxy: proxy.Protocol + "://" + proxy.Server,
|
Proxy: proxy.Protocol + "://" + proxy.Server,
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,11 +31,12 @@ func NewGitClient() *GitClient {
|
|||||||
func (g *GitClient) Run(path string, args ...string) (string, error) {
|
func (g *GitClient) Run(path string, args ...string) (string, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), g.Timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), g.Timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
header := []string{"-C", path, "git"}
|
header := []string{"-C", path, "-c", "core.quotePath=false"}
|
||||||
if g.Enabled {
|
if g.Enabled {
|
||||||
header = append(header, "-c", "http.proxy="+g.Proxy, "-c", "https.proxy="+g.Proxy)
|
header = append(header, "-c", "http.proxy="+g.Proxy, "-c", "https.proxy="+g.Proxy)
|
||||||
}
|
}
|
||||||
argsArr := append(header, args...)
|
argsArr := append(header, args...)
|
||||||
|
log.Println(argsArr)
|
||||||
cmd := exec.CommandContext(ctx, "git", argsArr...)
|
cmd := exec.CommandContext(ctx, "git", argsArr...)
|
||||||
var stdout, stderr bytes.Buffer
|
var stdout, stderr bytes.Buffer
|
||||||
cmd.Stdout = &stdout
|
cmd.Stdout = &stdout
|
||||||
@@ -46,3 +50,279 @@ func (g *GitClient) Run(path string, args ...string) (string, error) {
|
|||||||
}
|
}
|
||||||
return stdout.String(), nil
|
return stdout.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *GitClient) Pull(path string) (string, error) {
|
||||||
|
return g.Run(path, "pull")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GitClient) Push(path string) (string, error) {
|
||||||
|
return g.Run(path, "push")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GitClient) Status(path string) (string, error) {
|
||||||
|
return g.Run(path, "status")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GitClient) Clone(repoURL, path string) (string, error) {
|
||||||
|
return g.Run(".", "clone", repoURL, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GitClient) Fetch(path string) (string, error) {
|
||||||
|
return g.Run(path, "fetch", "--all")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GitClient) RemoteURL(path string) (string, error) {
|
||||||
|
out, err := g.Run(path, "config", "--get", "remote.origin.url")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(out), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GitClient) Branch(path string) (string, error) {
|
||||||
|
return g.Run(path, "rev-parse", "--abbrev-ref", "HEAD")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GitClient) Add(path string, files ...string) (string, error) {
|
||||||
|
args := append([]string{"add"}, files...)
|
||||||
|
return g.Run(path, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset 取消暂存指定文件 (git reset HEAD -- files...)
|
||||||
|
func (g *GitClient) Reset(path string, files ...string) (string, error) {
|
||||||
|
args := append([]string{"reset", "HEAD", "--"}, files...)
|
||||||
|
return g.Run(path, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit 提交暂存区的更改
|
||||||
|
func (g *GitClient) Commit(path, message string) (string, error) {
|
||||||
|
return g.Run(path, "commit", "-m", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore 丢弃工作区指定文件的更改 (git checkout -- files...)
|
||||||
|
func (g *GitClient) Restore(path string, files ...string) (string, error) {
|
||||||
|
args := append([]string{"checkout", "--"}, files...)
|
||||||
|
return g.Run(path, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanFiles 删除未跟踪的文件 (git clean -f -- files...)
|
||||||
|
func (g *GitClient) CleanFiles(path string, files ...string) (string, error) {
|
||||||
|
args := append([]string{"clean", "-f", "--"}, files...)
|
||||||
|
return g.Run(path, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log 获取提交历史 (git log --oneline --format=... -n count)
|
||||||
|
func (g *GitClient) Log(path string, count int) (string, error) {
|
||||||
|
return g.Run(path, "log",
|
||||||
|
fmt.Sprintf("--max-count=%d", count),
|
||||||
|
"--format=%H%n%h%n%an%n%ae%n%at%n%s%n---END---",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BranchList 获取所有本地分支
|
||||||
|
func (g *GitClient) BranchList(path string) (string, error) {
|
||||||
|
return g.Run(path, "branch", "--format=%(refname:short)\t%(HEAD)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checkout 切换分支
|
||||||
|
func (g *GitClient) Checkout(path, branch string) (string, error) {
|
||||||
|
return g.Run(path, "checkout", branch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetToCommit 将 HEAD 重置到指定提交 (git reset --<mode> <hash>)
|
||||||
|
// mode: hard / soft / mixed
|
||||||
|
func (g *GitClient) ResetToCommit(path, hash, mode string) (string, error) {
|
||||||
|
if mode == "" {
|
||||||
|
mode = "hard"
|
||||||
|
}
|
||||||
|
return g.Run(path, "reset", "--"+mode, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitShow 获取指定提交的详细 diff
|
||||||
|
func (g *GitClient) CommitShow(path, hash string) (string, error) {
|
||||||
|
return g.Run(path, "show", "--format=%b", hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitFiles 获取指定提交中变更的文件列表 (git diff-tree --root --no-commit-id -r --name-status <hash>)
|
||||||
|
// --root 使根提交(第一次提交)也能与空树对比,列出所有新增文件
|
||||||
|
func (g *GitClient) CommitFiles(path, hash string) (string, error) {
|
||||||
|
return g.Run(path, "diff-tree", "--root", "--no-commit-id", "-r", "--name-status", hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitFileDiff 获取指定提交中某个文件的 diff (git show <hash> -- <file>)
|
||||||
|
func (g *GitClient) CommitFileDiff(path, hash, filePath string) (string, error) {
|
||||||
|
return g.Run(path, "show", "--format=", hash, "--", filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileChange 表示单个文件的变更信息
|
||||||
|
type FileChange struct {
|
||||||
|
// 变更状态: M(修改), A(新增), D(删除), R(重命名), C(复制), U(未合并), ?(未跟踪)
|
||||||
|
Status string
|
||||||
|
// 文件路径
|
||||||
|
FilePath string
|
||||||
|
// 重命名/复制时的原始路径
|
||||||
|
OrigPath string
|
||||||
|
// 是否为暂存区变更
|
||||||
|
Staged bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangedFiles 获取工作区中所有变更的文件列表(包含新增、修改、删除、重命名、未跟踪等)
|
||||||
|
// 同一文件在暂存区和工作区都有变更时,会拆分为两条记录
|
||||||
|
func (g *GitClient) ChangedFiles(path string) ([]FileChange, error) {
|
||||||
|
out, err := g.Run(path, "status", "--porcelain", "-uall")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return parsePorcelainStatus(out), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StagedFiles 获取已暂存的文件变更列表
|
||||||
|
func (g *GitClient) StagedFiles(path string) ([]FileChange, error) {
|
||||||
|
out, err := g.Run(path, "diff", "--cached", "--name-status")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return parseNameStatus(out), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiffStat 获取工作区文件变更的统计信息(增删行数)
|
||||||
|
func (g *GitClient) DiffStat(path string) (string, error) {
|
||||||
|
return g.Run(path, "diff", "--stat")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiffFile 获取指定文件的详细差异内容
|
||||||
|
func (g *GitClient) DiffFile(path, filePath string) (string, error) {
|
||||||
|
return g.Run(path, "diff", "--", filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiffStagedFile 获取指定已暂存文件的详细差异内容
|
||||||
|
func (g *GitClient) DiffStagedFile(path, filePath string) (string, error) {
|
||||||
|
return g.Run(path, "diff", "--cached", "--", filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiffCommit 获取两个提交之间的文件变更列表
|
||||||
|
func (g *GitClient) DiffCommit(path, from, to string) ([]FileChange, error) {
|
||||||
|
out, err := g.Run(path, "diff", "--name-status", from, to)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return parseNameStatus(out), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsePorcelainStatus 解析 git status --porcelain 的输出
|
||||||
|
// porcelain 格式: XY filename, X=暂存区状态, Y=工作区状态
|
||||||
|
// 同一文件若暂存区和工作区都有变更,会拆分为两条记录
|
||||||
|
func parsePorcelainStatus(output string) []FileChange {
|
||||||
|
var changes []FileChange
|
||||||
|
// 注意: 不能用 TrimSpace,porcelain 格式中行首空格表示"暂存区无变更",TrimSpace 会错误地吃掉第一行的前导空格
|
||||||
|
lines := strings.Split(strings.TrimRight(output, "\n\r "), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if len(line) < 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
indexStatus := string(line[0])
|
||||||
|
workTreeStatus := string(line[1])
|
||||||
|
filePath := strings.TrimSpace(line[2:])
|
||||||
|
|
||||||
|
// 处理重命名情况 "R old -> new"
|
||||||
|
var origPath string
|
||||||
|
if strings.Contains(filePath, " -> ") {
|
||||||
|
parts := strings.SplitN(filePath, " -> ", 2)
|
||||||
|
origPath = parts[0]
|
||||||
|
filePath = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未跟踪文件 (??) 只产生一条记录
|
||||||
|
if indexStatus == "?" {
|
||||||
|
changes = append(changes, FileChange{
|
||||||
|
Status: "?",
|
||||||
|
FilePath: filePath,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暂存区有变更 (X 不为空格)
|
||||||
|
if indexStatus != " " {
|
||||||
|
changes = append(changes, FileChange{
|
||||||
|
Status: indexStatus,
|
||||||
|
FilePath: filePath,
|
||||||
|
OrigPath: origPath,
|
||||||
|
Staged: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工作区有变更 (Y 不为空格)
|
||||||
|
if workTreeStatus != " " {
|
||||||
|
changes = append(changes, FileChange{
|
||||||
|
Status: workTreeStatus,
|
||||||
|
FilePath: filePath,
|
||||||
|
OrigPath: origPath,
|
||||||
|
Staged: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseNameStatus 解析 git diff --name-status 的输出
|
||||||
|
func parseNameStatus(output string) []FileChange {
|
||||||
|
var changes []FileChange
|
||||||
|
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.TrimSpace(line) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := strings.Fields(line)
|
||||||
|
if len(parts) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
change := FileChange{
|
||||||
|
Status: parts[0],
|
||||||
|
FilePath: parts[len(parts)-1],
|
||||||
|
}
|
||||||
|
// 重命名/复制时有原路径: R100 old_path new_path
|
||||||
|
if len(parts) == 3 && (strings.HasPrefix(parts[0], "R") || strings.HasPrefix(parts[0], "C")) {
|
||||||
|
change.OrigPath = parts[1]
|
||||||
|
change.FilePath = parts[2]
|
||||||
|
}
|
||||||
|
changes = append(changes, change)
|
||||||
|
}
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusText 返回变更状态的中文描述
|
||||||
|
func (fc FileChange) StatusText() string {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(fc.Status, "R"):
|
||||||
|
return "重命名"
|
||||||
|
case strings.HasPrefix(fc.Status, "C"):
|
||||||
|
return "复制"
|
||||||
|
default:
|
||||||
|
statusMap := map[string]string{
|
||||||
|
"M": "已修改",
|
||||||
|
"A": "新增",
|
||||||
|
"D": "已删除",
|
||||||
|
"U": "未合并",
|
||||||
|
"?": "未跟踪",
|
||||||
|
}
|
||||||
|
if text, ok := statusMap[fc.Status]; ok {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
return fc.Status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String 返回文件变更的可读字符串
|
||||||
|
func (fc FileChange) String() string {
|
||||||
|
area := "工作区"
|
||||||
|
if fc.Staged {
|
||||||
|
area = "暂存区"
|
||||||
|
}
|
||||||
|
if fc.Status == "?" {
|
||||||
|
return fmt.Sprintf("[%s] %s", fc.StatusText(), fc.FilePath)
|
||||||
|
}
|
||||||
|
if fc.OrigPath != "" {
|
||||||
|
return fmt.Sprintf("[%s] (%s) %s → %s", fc.StatusText(), area, fc.OrigPath, fc.FilePath)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("[%s] (%s) %s", fc.StatusText(), area, fc.FilePath)
|
||||||
|
}
|
||||||
|
|||||||
95
main.go
Normal file
95
main.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/zhuy1228/GitPilot/internal/app"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/events"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed all:frontend/dist
|
||||||
|
var assets embed.FS
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.Println("GitPilot 启动中...")
|
||||||
|
|
||||||
|
appService := app.New()
|
||||||
|
|
||||||
|
wailsApp := application.New(application.Options{
|
||||||
|
Name: "GitPilot",
|
||||||
|
Description: "Git Repository Management Tool",
|
||||||
|
Services: []application.Service{
|
||||||
|
application.NewService(appService),
|
||||||
|
},
|
||||||
|
Assets: application.AssetOptions{
|
||||||
|
Handler: application.AssetFileServerFS(assets),
|
||||||
|
},
|
||||||
|
Mac: application.MacOptions{
|
||||||
|
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
mainWindow := wailsApp.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||||
|
Title: "GitPilot",
|
||||||
|
BackgroundColour: application.NewRGB(27, 38, 54),
|
||||||
|
URL: "/",
|
||||||
|
Width: 1280,
|
||||||
|
Height: 900,
|
||||||
|
})
|
||||||
|
|
||||||
|
appService.SetApplication(wailsApp)
|
||||||
|
|
||||||
|
// 窗口关闭时隐藏到托盘而不是退出
|
||||||
|
mainWindow.RegisterHook(events.Common.WindowClosing, func(event *application.WindowEvent) {
|
||||||
|
log.Println("窗口关闭,隐藏到系统托盘")
|
||||||
|
mainWindow.Hide()
|
||||||
|
event.Cancel()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 创建系统托盘
|
||||||
|
systray := wailsApp.SystemTray.New()
|
||||||
|
|
||||||
|
// 创建托盘菜单
|
||||||
|
trayMenu := wailsApp.NewMenu()
|
||||||
|
trayMenu.Add("显示主窗口").OnClick(func(ctx *application.Context) {
|
||||||
|
log.Println("托盘菜单:显示主窗口")
|
||||||
|
mainWindow.Show()
|
||||||
|
mainWindow.Focus()
|
||||||
|
})
|
||||||
|
trayMenu.AddSeparator()
|
||||||
|
trayMenu.Add("退出").OnClick(func(ctx *application.Context) {
|
||||||
|
log.Println("托盘菜单:退出应用")
|
||||||
|
wailsApp.Quit()
|
||||||
|
})
|
||||||
|
|
||||||
|
systray.SetMenu(trayMenu)
|
||||||
|
|
||||||
|
// 单击托盘图标显示主窗口
|
||||||
|
systray.OnClick(func() {
|
||||||
|
log.Println("托盘图标被点击")
|
||||||
|
mainWindow.Show()
|
||||||
|
mainWindow.Focus()
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Println("系统托盘创建成功")
|
||||||
|
|
||||||
|
go handleShutdownSignals()
|
||||||
|
|
||||||
|
if err := wailsApp.Run(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleShutdownSignals() {
|
||||||
|
sigChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
sig := <-sigChan
|
||||||
|
log.Printf("接收到信号: %v, 正在退出...", sig)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
143
tools/gen_logo.go
Normal file
143
tools/gen_logo.go
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
// 生成 GitPilot Logo — 极简风格:圆角深色背景 + 大号 Git 分支符号
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/png"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const size = 1024
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
img := image.NewRGBA(image.Rect(0, 0, size, size))
|
||||||
|
|
||||||
|
bg := color.RGBA{30, 30, 46, 255} // #1e1e2e
|
||||||
|
blue := color.RGBA{137, 180, 250, 255} // #89b4fa
|
||||||
|
green := color.RGBA{166, 227, 161, 255} // #a6e3a1
|
||||||
|
|
||||||
|
cx := size / 2
|
||||||
|
cy := size / 2
|
||||||
|
|
||||||
|
// 1. 圆角背景
|
||||||
|
roundedRect(img, 0, 0, size, size, 220, bg)
|
||||||
|
|
||||||
|
// 2. 主干竖线 — 粗实线
|
||||||
|
thickLine(img, cx, cy-280, cx, cy+280, 50, blue)
|
||||||
|
|
||||||
|
// 3. 分支曲线 — 从主干中上方向右上弯出
|
||||||
|
smoothBranch(img, cx, cy-60, cx+200, cy-260, 40, green)
|
||||||
|
|
||||||
|
// 4. 三个实心节点
|
||||||
|
filledCircle(img, cx, cy-260, 56, blue) // 顶部
|
||||||
|
filledCircle(img, cx, cy+260, 56, blue) // 底部
|
||||||
|
filledCircle(img, cx+200, cy-260, 48, green) // 分支端
|
||||||
|
|
||||||
|
// 保存
|
||||||
|
save(img, "build/appicon.png")
|
||||||
|
save(img, "frontend/public/appicon.png")
|
||||||
|
println("✅ GitPilot Logo 已生成!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 绘图工具 ---
|
||||||
|
|
||||||
|
func roundedRect(img *image.RGBA, x0, y0, x1, y1, r int, c color.RGBA) {
|
||||||
|
for y := y0; y < y1; y++ {
|
||||||
|
for x := x0; x < x1; x++ {
|
||||||
|
dx, dy := 0, 0
|
||||||
|
if x < x0+r && y < y0+r {
|
||||||
|
dx, dy = x0+r-x, y0+r-y
|
||||||
|
} else if x > x1-r-1 && y < y0+r {
|
||||||
|
dx, dy = x-(x1-r-1), y0+r-y
|
||||||
|
} else if x < x0+r && y > y1-r-1 {
|
||||||
|
dx, dy = x0+r-x, y-(y1-r-1)
|
||||||
|
} else if x > x1-r-1 && y > y1-r-1 {
|
||||||
|
dx, dy = x-(x1-r-1), y-(y1-r-1)
|
||||||
|
}
|
||||||
|
dist := math.Sqrt(float64(dx*dx + dy*dy))
|
||||||
|
if dist <= float64(r)+0.5 {
|
||||||
|
if dist > float64(r)-1 {
|
||||||
|
blend(img, x, y, c, float64(r)+0.5-dist)
|
||||||
|
} else {
|
||||||
|
img.SetRGBA(x, y, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func filledCircle(img *image.RGBA, cx, cy, r int, c color.RGBA) {
|
||||||
|
for y := cy - r - 1; y <= cy+r+1; y++ {
|
||||||
|
for x := cx - r - 1; x <= cx+r+1; x++ {
|
||||||
|
dist := math.Hypot(float64(x-cx), float64(y-cy))
|
||||||
|
if dist <= float64(r)+0.5 {
|
||||||
|
if dist > float64(r)-1 {
|
||||||
|
blend(img, x, y, c, float64(r)+0.5-dist)
|
||||||
|
} else {
|
||||||
|
img.SetRGBA(x, y, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func thickLine(img *image.RGBA, x0, y0, x1, y1, thickness int, c color.RGBA) {
|
||||||
|
dx := float64(x1 - x0)
|
||||||
|
dy := float64(y1 - y0)
|
||||||
|
length := math.Hypot(dx, dy)
|
||||||
|
if length == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
steps := int(length * 1.5)
|
||||||
|
for i := 0; i <= steps; i++ {
|
||||||
|
t := float64(i) / float64(steps)
|
||||||
|
px := float64(x0) + dx*t
|
||||||
|
py := float64(y0) + dy*t
|
||||||
|
filledCircle(img, int(math.Round(px)), int(math.Round(py)), thickness/2, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func smoothBranch(img *image.RGBA, x0, y0, x1, y1, thickness int, c color.RGBA) {
|
||||||
|
// 二次贝塞尔: 控制点使起点水平、终点竖直
|
||||||
|
cpx := float64(x1)
|
||||||
|
cpy := float64(y0)
|
||||||
|
|
||||||
|
steps := 120
|
||||||
|
prevX, prevY := float64(x0), float64(y0)
|
||||||
|
for i := 1; i <= steps; i++ {
|
||||||
|
t := float64(i) / float64(steps)
|
||||||
|
u := 1.0 - t
|
||||||
|
px := u*u*float64(x0) + 2*u*t*cpx + t*t*float64(x1)
|
||||||
|
py := u*u*float64(y0) + 2*u*t*cpy + t*t*float64(y1)
|
||||||
|
thickLine(img, int(prevX), int(prevY), int(px), int(py), thickness, c)
|
||||||
|
prevX, prevY = px, py
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func blend(img *image.RGBA, x, y int, c color.RGBA, alpha float64) {
|
||||||
|
if x < 0 || y < 0 || x >= size || y >= size || alpha <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if alpha > 1 {
|
||||||
|
alpha = 1
|
||||||
|
}
|
||||||
|
e := img.RGBAAt(x, y)
|
||||||
|
img.SetRGBA(x, y, color.RGBA{
|
||||||
|
R: uint8(float64(e.R)*(1-alpha) + float64(c.R)*alpha),
|
||||||
|
G: uint8(float64(e.G)*(1-alpha) + float64(c.G)*alpha),
|
||||||
|
B: uint8(float64(e.B)*(1-alpha) + float64(c.B)*alpha),
|
||||||
|
A: uint8(math.Min(255, float64(e.A)+float64(c.A)*alpha)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func save(img *image.RGBA, path string) {
|
||||||
|
f, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
png.Encode(f, img)
|
||||||
|
println(" 保存:", path)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user