refactor(display): compose window/systray/menu sub-packages into orchestrator
Service now delegates to window.Manager, systray.Manager, and menu.Manager instead of directly using Wails types. WSEventManager accepts EventSource interface instead of calling application.Get() directly. AttachWindowListeners now accepts window.PlatformWindow. Removes migrated files: window.go, window_state.go, layout.go, tray.go, menu.go. Tests rewritten against mock platform implementations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
691f17ea05
commit
4814f960fb
16 changed files with 1194 additions and 2856 deletions
52
go.mod
52
go.mod
|
|
@ -3,92 +3,54 @@ module forge.lthn.ai/core/gui
|
|||
go 1.26.0
|
||||
|
||||
require (
|
||||
forge.lthn.ai/Snider/Enchantrix v0.0.4
|
||||
forge.lthn.ai/core/go-i18n v0.0.1
|
||||
github.com/adrg/xdg v0.5.3
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
forge.lthn.ai/core/go v0.2.2
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/modelcontextprotocol/go-sdk v1.3.0
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.64
|
||||
golang.org/x/text v0.34.0
|
||||
gopkg.in/ini.v1 v1.67.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.74
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
forge.lthn.ai/core/go-inference v0.0.1 // indirect
|
||||
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // 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/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.15.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/coder/websocket v1.8.14 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/ebitengine/purego v0.9.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // 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/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/jsonschema-go v0.4.2 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.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/json-iterator/go v1.1.12 // 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/leodido/go-urn v1.4.0 // 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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.59.0 // 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/spf13/pflag v1.0.10 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.23 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
golang.org/x/arch v0.23.0 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect
|
||||
golang.org/x/net v0.50.0 // indirect
|
||||
golang.org/x/oauth2 v0.35.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
|||
101
go.sum
101
go.sum
|
|
@ -1,12 +1,9 @@
|
|||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
forge.lthn.ai/Snider/Enchantrix v0.0.4 h1:biwpix/bdedfyc0iVeK15awhhJKH6TEMYOTXzHXx5TI=
|
||||
forge.lthn.ai/core/go-i18n v0.0.1 h1:7I2cOv3GCc7MssLny/CAnwz3L7/Y4iqwzrCRQMQ+teA=
|
||||
forge.lthn.ai/core/go-inference v0.0.1 h1:hf5eOzm5sNDifhb0BscMTyKEkB44r2Tv58wakHGvtz4=
|
||||
forge.lthn.ai/core/go v0.2.2 h1:JCWaFfiG+agb0f7b5DO1g+h40x6nb4UydxJ7D+oZk5k=
|
||||
forge.lthn.ai/core/go v0.2.2/go.mod h1:gE6c8h+PJ2287qNhVUJ5SOe1kopEwHEquvinstpuyJc=
|
||||
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA=
|
||||
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc=
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
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=
|
||||
|
|
@ -20,32 +17,22 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
|
|||
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/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||
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/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/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/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
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=
|
||||
|
|
@ -60,37 +47,20 @@ github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRko
|
|||
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/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
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-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
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=
|
||||
|
|
@ -106,8 +76,6 @@ github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed
|
|||
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/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
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=
|
||||
|
|
@ -117,17 +85,8 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP
|
|||
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/modelcontextprotocol/go-sdk v1.3.0 h1:gMfZkv3DzQF5q/DcQePo5rahEY+sguyPfXDfNBcT0Zs=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
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=
|
||||
|
|
@ -136,14 +95,12 @@ 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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
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=
|
||||
|
|
@ -151,44 +108,25 @@ github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepq
|
|||
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/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||
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.64 h1:xAhLFVfdbg7XdZQ5mMQmBv2BglWu8hMqe50Z+3UJvBs=
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.64/go.mod h1:zvgNL/mlFcX8aRGu6KOz9AHrMmTBD+4hJRQIONqF/Yw=
|
||||
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=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o=
|
||||
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.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||
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=
|
||||
|
|
@ -198,25 +136,22 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
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.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||
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.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
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=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
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/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k=
|
||||
gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
|
||||
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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
|||
|
|
@ -1,20 +1,9 @@
|
|||
// pkg/display/actions.go
|
||||
package display
|
||||
|
||||
import "github.com/wailsapp/wails/v3/pkg/application"
|
||||
import "forge.lthn.ai/core/gui/pkg/window"
|
||||
|
||||
// ActionOpenWindow is an IPC message used to request a new window. It contains
|
||||
// the options for the new window.
|
||||
//
|
||||
// example:
|
||||
//
|
||||
// action := display.ActionOpenWindow{
|
||||
// WebviewWindowOptions: application.WebviewWindowOptions{
|
||||
// Name: "my-window",
|
||||
// Title: "My Window",
|
||||
// Width: 800,
|
||||
// Height: 600,
|
||||
// },
|
||||
// }
|
||||
// ActionOpenWindow is an IPC message type requesting a new window.
|
||||
type ActionOpenWindow struct {
|
||||
application.WebviewWindowOptions
|
||||
window.Window
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -7,9 +7,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"forge.lthn.ai/core/gui/pkg/window"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
// EventType represents the type of event.
|
||||
|
|
@ -45,7 +44,7 @@ type WSEventManager struct {
|
|||
upgrader websocket.Upgrader
|
||||
clients map[*websocket.Conn]*clientState
|
||||
mu sync.RWMutex
|
||||
display *Service
|
||||
eventSource EventSource
|
||||
nextSubID int
|
||||
eventBuffer chan Event
|
||||
}
|
||||
|
|
@ -57,7 +56,8 @@ type clientState struct {
|
|||
}
|
||||
|
||||
// NewWSEventManager creates a new event manager.
|
||||
func NewWSEventManager(display *Service) *WSEventManager {
|
||||
// It accepts an EventSource for theme change events instead of using application.Get() directly.
|
||||
func NewWSEventManager(es EventSource) *WSEventManager {
|
||||
em := &WSEventManager{
|
||||
upgrader: websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
|
|
@ -67,7 +67,7 @@ func NewWSEventManager(display *Service) *WSEventManager {
|
|||
WriteBufferSize: 1024,
|
||||
},
|
||||
clients: make(map[*websocket.Conn]*clientState),
|
||||
display: display,
|
||||
eventSource: es,
|
||||
eventBuffer: make(chan Event, 100),
|
||||
}
|
||||
|
||||
|
|
@ -302,64 +302,34 @@ func (em *WSEventManager) Close() {
|
|||
close(em.eventBuffer)
|
||||
}
|
||||
|
||||
// SetupWindowEventListeners attaches event listeners to all windows.
|
||||
// SetupWindowEventListeners registers listeners for application-level events.
|
||||
// Uses EventSource instead of application.Get() directly.
|
||||
func (em *WSEventManager) SetupWindowEventListeners() {
|
||||
app := application.Get()
|
||||
if app == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Listen for theme changes
|
||||
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
|
||||
isDark := app.Env.IsDarkMode()
|
||||
em.Emit(Event{
|
||||
Type: EventThemeChange,
|
||||
Data: map[string]any{
|
||||
"isDark": isDark,
|
||||
"theme": map[bool]string{true: "dark", false: "light"}[isDark],
|
||||
},
|
||||
if em.eventSource != nil {
|
||||
em.eventSource.OnThemeChange(func(isDark bool) {
|
||||
theme := "light"
|
||||
if isDark {
|
||||
theme = "dark"
|
||||
}
|
||||
em.Emit(Event{
|
||||
Type: EventThemeChange,
|
||||
Data: map[string]any{
|
||||
"isDark": isDark,
|
||||
"theme": theme,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// AttachWindowListeners attaches event listeners to a specific window.
|
||||
func (em *WSEventManager) AttachWindowListeners(window *application.WebviewWindow) {
|
||||
if window == nil {
|
||||
// Accepts window.PlatformWindow instead of *application.WebviewWindow.
|
||||
func (em *WSEventManager) AttachWindowListeners(pw window.PlatformWindow) {
|
||||
if pw == nil {
|
||||
return
|
||||
}
|
||||
|
||||
name := window.Name()
|
||||
|
||||
// Window focus
|
||||
window.OnWindowEvent(events.Common.WindowFocus, func(event *application.WindowEvent) {
|
||||
em.EmitWindowEvent(EventWindowFocus, name, nil)
|
||||
})
|
||||
|
||||
// Window blur
|
||||
window.OnWindowEvent(events.Common.WindowLostFocus, func(event *application.WindowEvent) {
|
||||
em.EmitWindowEvent(EventWindowBlur, name, nil)
|
||||
})
|
||||
|
||||
// Window move
|
||||
window.OnWindowEvent(events.Common.WindowDidMove, func(event *application.WindowEvent) {
|
||||
x, y := window.Position()
|
||||
em.EmitWindowEvent(EventWindowMove, name, map[string]any{
|
||||
"x": x,
|
||||
"y": y,
|
||||
})
|
||||
})
|
||||
|
||||
// Window resize
|
||||
window.OnWindowEvent(events.Common.WindowDidResize, func(event *application.WindowEvent) {
|
||||
width, height := window.Size()
|
||||
em.EmitWindowEvent(EventWindowResize, name, map[string]any{
|
||||
"width": width,
|
||||
"height": height,
|
||||
})
|
||||
})
|
||||
|
||||
// Window close
|
||||
window.OnWindowEvent(events.Common.WindowClosing, func(event *application.WindowEvent) {
|
||||
em.EmitWindowEvent(EventWindowClose, name, nil)
|
||||
pw.OnWindowEvent(func(e window.WindowEvent) {
|
||||
em.EmitWindowEvent(EventType("window."+e.Type), e.Name, e.Data)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
// pkg/display/interfaces.go
|
||||
package display
|
||||
|
||||
import (
|
||||
|
|
@ -5,60 +6,40 @@ import (
|
|||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
// App abstracts the Wails application API for testing.
|
||||
// App abstracts the Wails application for the orchestrator.
|
||||
type App interface {
|
||||
Window() WindowManager
|
||||
Menu() MenuManager
|
||||
Dialog() DialogManager
|
||||
SystemTray() SystemTrayManager
|
||||
Env() EnvManager
|
||||
Event() EventManager
|
||||
Logger() Logger
|
||||
Quit()
|
||||
}
|
||||
|
||||
// WindowManager handles window creation and management.
|
||||
type WindowManager interface {
|
||||
NewWithOptions(opts application.WebviewWindowOptions) *application.WebviewWindow
|
||||
GetAll() []application.Window
|
||||
}
|
||||
|
||||
// MenuManager handles menu creation.
|
||||
type MenuManager interface {
|
||||
New() *application.Menu
|
||||
Set(menu *application.Menu)
|
||||
}
|
||||
|
||||
// DialogManager handles dialog creation.
|
||||
// DialogManager wraps Wails dialog operations.
|
||||
type DialogManager interface {
|
||||
Info() *application.MessageDialog
|
||||
Warning() *application.MessageDialog
|
||||
OpenFile() *application.OpenFileDialogStruct
|
||||
}
|
||||
|
||||
// SystemTrayManager handles system tray creation.
|
||||
type SystemTrayManager interface {
|
||||
New() *application.SystemTray
|
||||
}
|
||||
|
||||
// EnvManager provides environment information.
|
||||
// EnvManager wraps Wails environment queries.
|
||||
type EnvManager interface {
|
||||
Info() application.EnvironmentInfo
|
||||
IsDarkMode() bool
|
||||
}
|
||||
|
||||
// EventManager handles event registration and emission.
|
||||
// EventManager wraps Wails application events.
|
||||
type EventManager interface {
|
||||
OnApplicationEvent(eventType events.ApplicationEventType, handler func(*application.ApplicationEvent)) func()
|
||||
Emit(name string, data ...any) bool
|
||||
}
|
||||
|
||||
// Logger provides logging capabilities.
|
||||
// Logger wraps Wails logging.
|
||||
type Logger interface {
|
||||
Info(message string, args ...any)
|
||||
}
|
||||
|
||||
// wailsApp wraps a real Wails application to implement the App interface.
|
||||
// wailsApp wraps *application.App for the App interface.
|
||||
type wailsApp struct {
|
||||
app *application.App
|
||||
}
|
||||
|
|
@ -67,53 +48,47 @@ func newWailsApp(app *application.App) App {
|
|||
return &wailsApp{app: app}
|
||||
}
|
||||
|
||||
func (w *wailsApp) Window() WindowManager { return &wailsWindowManager{app: w.app} }
|
||||
func (w *wailsApp) Menu() MenuManager { return &wailsMenuManager{app: w.app} }
|
||||
func (w *wailsApp) Dialog() DialogManager { return &wailsDialogManager{app: w.app} }
|
||||
func (w *wailsApp) SystemTray() SystemTrayManager { return &wailsSystemTrayManager{app: w.app} }
|
||||
func (w *wailsApp) Env() EnvManager { return &wailsEnvManager{app: w.app} }
|
||||
func (w *wailsApp) Event() EventManager { return &wailsEventManager{app: w.app} }
|
||||
func (w *wailsApp) Logger() Logger { return w.app.Logger }
|
||||
func (w *wailsApp) Quit() { w.app.Quit() }
|
||||
|
||||
// Wails adapter implementations
|
||||
|
||||
type wailsWindowManager struct{ app *application.App }
|
||||
|
||||
func (m *wailsWindowManager) NewWithOptions(opts application.WebviewWindowOptions) *application.WebviewWindow {
|
||||
return m.app.Window.NewWithOptions(opts)
|
||||
}
|
||||
func (m *wailsWindowManager) GetAll() []application.Window {
|
||||
return m.app.Window.GetAll()
|
||||
}
|
||||
|
||||
type wailsMenuManager struct{ app *application.App }
|
||||
|
||||
func (m *wailsMenuManager) New() *application.Menu { return m.app.Menu.New() }
|
||||
func (m *wailsMenuManager) Set(menu *application.Menu) { m.app.Menu.Set(menu) }
|
||||
func (w *wailsApp) Dialog() DialogManager { return &wailsDialogManager{app: w.app} }
|
||||
func (w *wailsApp) Env() EnvManager { return &wailsEnvManager{app: w.app} }
|
||||
func (w *wailsApp) Event() EventManager { return &wailsEventManager{app: w.app} }
|
||||
func (w *wailsApp) Logger() Logger { return w.app.Logger }
|
||||
func (w *wailsApp) Quit() { w.app.Quit() }
|
||||
|
||||
type wailsDialogManager struct{ app *application.App }
|
||||
|
||||
func (m *wailsDialogManager) Info() *application.MessageDialog { return m.app.Dialog.Info() }
|
||||
func (m *wailsDialogManager) Warning() *application.MessageDialog { return m.app.Dialog.Warning() }
|
||||
func (m *wailsDialogManager) OpenFile() *application.OpenFileDialogStruct {
|
||||
return m.app.Dialog.OpenFile()
|
||||
func (d *wailsDialogManager) Info() *application.MessageDialog { return d.app.Dialog.Info() }
|
||||
func (d *wailsDialogManager) Warning() *application.MessageDialog { return d.app.Dialog.Warning() }
|
||||
func (d *wailsDialogManager) OpenFile() *application.OpenFileDialogStruct {
|
||||
return d.app.Dialog.OpenFile()
|
||||
}
|
||||
|
||||
type wailsSystemTrayManager struct{ app *application.App }
|
||||
|
||||
func (m *wailsSystemTrayManager) New() *application.SystemTray { return m.app.SystemTray.New() }
|
||||
|
||||
type wailsEnvManager struct{ app *application.App }
|
||||
|
||||
func (m *wailsEnvManager) Info() application.EnvironmentInfo { return m.app.Env.Info() }
|
||||
func (m *wailsEnvManager) IsDarkMode() bool { return m.app.Env.IsDarkMode() }
|
||||
func (e *wailsEnvManager) Info() application.EnvironmentInfo { return e.app.Env.Info() }
|
||||
func (e *wailsEnvManager) IsDarkMode() bool { return e.app.Env.IsDarkMode() }
|
||||
|
||||
type wailsEventManager struct{ app *application.App }
|
||||
|
||||
func (m *wailsEventManager) OnApplicationEvent(eventType events.ApplicationEventType, handler func(*application.ApplicationEvent)) func() {
|
||||
return m.app.Event.OnApplicationEvent(eventType, handler)
|
||||
func (ev *wailsEventManager) OnApplicationEvent(eventType events.ApplicationEventType, handler func(*application.ApplicationEvent)) func() {
|
||||
return ev.app.Event.OnApplicationEvent(eventType, handler)
|
||||
}
|
||||
func (m *wailsEventManager) Emit(name string, data ...any) bool {
|
||||
return m.app.Event.Emit(name, data...)
|
||||
func (ev *wailsEventManager) Emit(name string, data ...any) bool {
|
||||
return ev.app.Event.Emit(name, data...)
|
||||
}
|
||||
|
||||
// wailsEventSource implements EventSource using a Wails app.
|
||||
type wailsEventSource struct{ app *application.App }
|
||||
|
||||
func newWailsEventSource(app *application.App) EventSource {
|
||||
return &wailsEventSource{app: app}
|
||||
}
|
||||
|
||||
func (es *wailsEventSource) OnThemeChange(handler func(isDark bool)) func() {
|
||||
return es.app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(_ *application.ApplicationEvent) {
|
||||
handler(es.app.Env.IsDarkMode())
|
||||
})
|
||||
}
|
||||
|
||||
func (es *wailsEventSource) Emit(name string, data ...any) bool {
|
||||
return es.app.Event.Emit(name, data...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,149 +0,0 @@
|
|||
package display
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Layout represents a saved window arrangement.
|
||||
type Layout struct {
|
||||
Name string `json:"name"`
|
||||
Windows map[string]WindowState `json:"windows"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// LayoutManager handles saving and restoring window layouts.
|
||||
type LayoutManager struct {
|
||||
layouts map[string]*Layout
|
||||
filePath string
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewLayoutManager creates a new layout manager.
|
||||
func NewLayoutManager() *LayoutManager {
|
||||
m := &LayoutManager{
|
||||
layouts: make(map[string]*Layout),
|
||||
}
|
||||
|
||||
// Determine config path
|
||||
configDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
configDir = "."
|
||||
}
|
||||
m.filePath = filepath.Join(configDir, "Core", "layouts.json")
|
||||
|
||||
// Ensure directory exists
|
||||
os.MkdirAll(filepath.Dir(m.filePath), 0755)
|
||||
|
||||
// Load existing layouts
|
||||
m.load()
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// load reads layouts from disk.
|
||||
func (m *LayoutManager) load() error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
data, err := os.ReadFile(m.filePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil // No saved layouts yet
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return json.Unmarshal(data, &m.layouts)
|
||||
}
|
||||
|
||||
// save writes layouts to disk.
|
||||
func (m *LayoutManager) save() error {
|
||||
m.mu.RLock()
|
||||
data, err := json.MarshalIndent(m.layouts, "", " ")
|
||||
m.mu.RUnlock()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(m.filePath, data, 0644)
|
||||
}
|
||||
|
||||
// SaveLayout saves a new layout or updates an existing one.
|
||||
func (m *LayoutManager) SaveLayout(name string, windows map[string]WindowState) error {
|
||||
if name == "" {
|
||||
return fmt.Errorf("layout name is required")
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
now := time.Now().Unix()
|
||||
|
||||
existing, ok := m.layouts[name]
|
||||
if ok {
|
||||
// Update existing layout
|
||||
existing.Windows = windows
|
||||
existing.UpdatedAt = now
|
||||
} else {
|
||||
// Create new layout
|
||||
m.layouts[name] = &Layout{
|
||||
Name: name,
|
||||
Windows: windows,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
return m.save()
|
||||
}
|
||||
|
||||
// GetLayout returns a layout by name.
|
||||
func (m *LayoutManager) GetLayout(name string) *Layout {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return m.layouts[name]
|
||||
}
|
||||
|
||||
// ListLayouts returns all saved layout names with metadata.
|
||||
func (m *LayoutManager) ListLayouts() []LayoutInfo {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
result := make([]LayoutInfo, 0, len(m.layouts))
|
||||
for _, layout := range m.layouts {
|
||||
result = append(result, LayoutInfo{
|
||||
Name: layout.Name,
|
||||
WindowCount: len(layout.Windows),
|
||||
CreatedAt: layout.CreatedAt,
|
||||
UpdatedAt: layout.UpdatedAt,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// DeleteLayout removes a layout by name.
|
||||
func (m *LayoutManager) DeleteLayout(name string) error {
|
||||
m.mu.Lock()
|
||||
if _, ok := m.layouts[name]; !ok {
|
||||
m.mu.Unlock()
|
||||
return fmt.Errorf("layout not found: %s", name)
|
||||
}
|
||||
delete(m.layouts, name)
|
||||
m.mu.Unlock()
|
||||
|
||||
return m.save()
|
||||
}
|
||||
|
||||
// LayoutInfo contains summary information about a layout.
|
||||
type LayoutInfo struct {
|
||||
Name string `json:"name"`
|
||||
WindowCount int `json:"windowCount"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
}
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
package display
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
// buildMenu creates and sets the main application menu. This function is called
|
||||
// during the startup of the display service.
|
||||
func (s *Service) buildMenu() {
|
||||
appMenu := s.app.Menu().New()
|
||||
if runtime.GOOS == "darwin" {
|
||||
appMenu.AddRole(application.AppMenu)
|
||||
}
|
||||
appMenu.AddRole(application.FileMenu)
|
||||
appMenu.AddRole(application.ViewMenu)
|
||||
appMenu.AddRole(application.EditMenu)
|
||||
|
||||
workspace := appMenu.AddSubmenu("Workspace")
|
||||
workspace.Add("New...").OnClick(func(ctx *application.Context) {
|
||||
s.handleNewWorkspace()
|
||||
})
|
||||
workspace.Add("List").OnClick(func(ctx *application.Context) {
|
||||
s.handleListWorkspaces()
|
||||
})
|
||||
|
||||
// Developer menu for IDE features
|
||||
developer := appMenu.AddSubmenu("Developer")
|
||||
developer.Add("New File").SetAccelerator("CmdOrCtrl+N").OnClick(func(ctx *application.Context) {
|
||||
s.handleNewFile()
|
||||
})
|
||||
developer.Add("Open File...").SetAccelerator("CmdOrCtrl+O").OnClick(func(ctx *application.Context) {
|
||||
s.handleOpenFile()
|
||||
})
|
||||
developer.Add("Save").SetAccelerator("CmdOrCtrl+S").OnClick(func(ctx *application.Context) {
|
||||
s.handleSaveFile()
|
||||
})
|
||||
developer.AddSeparator()
|
||||
developer.Add("Editor").OnClick(func(ctx *application.Context) {
|
||||
s.handleOpenEditor()
|
||||
})
|
||||
developer.Add("Terminal").OnClick(func(ctx *application.Context) {
|
||||
s.handleOpenTerminal()
|
||||
})
|
||||
developer.AddSeparator()
|
||||
developer.Add("Run").SetAccelerator("CmdOrCtrl+R").OnClick(func(ctx *application.Context) {
|
||||
s.handleRun()
|
||||
})
|
||||
developer.Add("Build").SetAccelerator("CmdOrCtrl+B").OnClick(func(ctx *application.Context) {
|
||||
s.handleBuild()
|
||||
})
|
||||
|
||||
appMenu.AddRole(application.WindowMenu)
|
||||
appMenu.AddRole(application.HelpMenu)
|
||||
|
||||
s.app.Menu().Set(appMenu)
|
||||
}
|
||||
|
||||
// handleNewWorkspace opens a window for creating a new workspace.
|
||||
func (s *Service) handleNewWorkspace() {
|
||||
// Open a dedicated window for workspace creation
|
||||
// The frontend at /workspace/new handles the form
|
||||
opts := application.WebviewWindowOptions{
|
||||
Name: "workspace-new",
|
||||
Title: "New Workspace",
|
||||
Width: 500,
|
||||
Height: 400,
|
||||
URL: "/workspace/new",
|
||||
}
|
||||
s.app.Window().NewWithOptions(opts)
|
||||
}
|
||||
|
||||
// handleListWorkspaces shows a dialog with available workspaces.
|
||||
func (s *Service) handleListWorkspaces() {
|
||||
// Get workspace service from core
|
||||
ws := s.Core().Service("workspace")
|
||||
if ws == nil {
|
||||
dialog := s.app.Dialog().Warning()
|
||||
dialog.SetTitle("Workspace")
|
||||
dialog.SetMessage("Workspace service not available")
|
||||
dialog.Show()
|
||||
return
|
||||
}
|
||||
|
||||
// Type assert to access ListWorkspaces method
|
||||
lister, ok := ws.(interface{ ListWorkspaces() []string })
|
||||
if !ok {
|
||||
dialog := s.app.Dialog().Warning()
|
||||
dialog.SetTitle("Workspace")
|
||||
dialog.SetMessage("Unable to list workspaces")
|
||||
dialog.Show()
|
||||
return
|
||||
}
|
||||
|
||||
workspaces := lister.ListWorkspaces()
|
||||
|
||||
var message string
|
||||
if len(workspaces) == 0 {
|
||||
message = "No workspaces found.\n\nUse Workspace → New to create one."
|
||||
} else {
|
||||
message = fmt.Sprintf("Available Workspaces (%d):\n\n%s",
|
||||
len(workspaces),
|
||||
strings.Join(workspaces, "\n"))
|
||||
}
|
||||
|
||||
dialog := s.app.Dialog().Info()
|
||||
dialog.SetTitle("Workspaces")
|
||||
dialog.SetMessage(message)
|
||||
dialog.Show()
|
||||
}
|
||||
|
||||
// handleNewFile opens the editor with a new untitled file.
|
||||
func (s *Service) handleNewFile() {
|
||||
opts := application.WebviewWindowOptions{
|
||||
Name: "editor",
|
||||
Title: "New File - Editor",
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
URL: "/#/developer/editor?new=true",
|
||||
}
|
||||
s.app.Window().NewWithOptions(opts)
|
||||
}
|
||||
|
||||
// handleOpenFile opens a file dialog to select a file, then opens it in the editor.
|
||||
func (s *Service) handleOpenFile() {
|
||||
dialog := s.app.Dialog().OpenFile()
|
||||
dialog.SetTitle("Open File")
|
||||
dialog.CanChooseFiles(true)
|
||||
dialog.CanChooseDirectories(false)
|
||||
result, err := dialog.PromptForSingleSelection()
|
||||
if err != nil || result == "" {
|
||||
return
|
||||
}
|
||||
|
||||
opts := application.WebviewWindowOptions{
|
||||
Name: "editor",
|
||||
Title: result + " - Editor",
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
URL: "/#/developer/editor?file=" + result,
|
||||
}
|
||||
s.app.Window().NewWithOptions(opts)
|
||||
}
|
||||
|
||||
// handleSaveFile emits a save event to the focused editor window.
|
||||
func (s *Service) handleSaveFile() {
|
||||
s.app.Event().Emit("ide:save")
|
||||
}
|
||||
|
||||
// handleOpenEditor opens a standalone editor window.
|
||||
func (s *Service) handleOpenEditor() {
|
||||
opts := application.WebviewWindowOptions{
|
||||
Name: "editor",
|
||||
Title: "Editor",
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
URL: "/#/developer/editor",
|
||||
}
|
||||
s.app.Window().NewWithOptions(opts)
|
||||
}
|
||||
|
||||
// handleOpenTerminal opens a terminal window.
|
||||
func (s *Service) handleOpenTerminal() {
|
||||
opts := application.WebviewWindowOptions{
|
||||
Name: "terminal",
|
||||
Title: "Terminal",
|
||||
Width: 800,
|
||||
Height: 500,
|
||||
URL: "/#/developer/terminal",
|
||||
}
|
||||
s.app.Window().NewWithOptions(opts)
|
||||
}
|
||||
|
||||
// handleRun emits a run event that the IDE service can handle.
|
||||
func (s *Service) handleRun() {
|
||||
s.app.Event().Emit("ide:run")
|
||||
}
|
||||
|
||||
// handleBuild emits a build event that the IDE service can handle.
|
||||
func (s *Service) handleBuild() {
|
||||
s.app.Event().Emit("ide:build")
|
||||
}
|
||||
|
|
@ -7,10 +7,7 @@ import (
|
|||
|
||||
// mockApp is a mock implementation of the App interface for testing.
|
||||
type mockApp struct {
|
||||
windowManager *mockWindowManager
|
||||
menuManager *mockMenuManager
|
||||
dialogManager *mockDialogManager
|
||||
systemTrayMgr *mockSystemTrayManager
|
||||
envManager *mockEnvManager
|
||||
eventManager *mockEventManager
|
||||
logger *mockLogger
|
||||
|
|
@ -19,66 +16,18 @@ type mockApp struct {
|
|||
|
||||
func newMockApp() *mockApp {
|
||||
return &mockApp{
|
||||
windowManager: newMockWindowManager(),
|
||||
menuManager: newMockMenuManager(),
|
||||
dialogManager: newMockDialogManager(),
|
||||
systemTrayMgr: newMockSystemTrayManager(),
|
||||
envManager: newMockEnvManager(),
|
||||
eventManager: newMockEventManager(),
|
||||
logger: &mockLogger{},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mockApp) Window() WindowManager { return m.windowManager }
|
||||
func (m *mockApp) Menu() MenuManager { return m.menuManager }
|
||||
func (m *mockApp) Dialog() DialogManager { return m.dialogManager }
|
||||
func (m *mockApp) SystemTray() SystemTrayManager { return m.systemTrayMgr }
|
||||
func (m *mockApp) Env() EnvManager { return m.envManager }
|
||||
func (m *mockApp) Event() EventManager { return m.eventManager }
|
||||
func (m *mockApp) Logger() Logger { return m.logger }
|
||||
func (m *mockApp) Quit() { m.quitCalled = true }
|
||||
|
||||
// mockWindowManager tracks window creation calls.
|
||||
type mockWindowManager struct {
|
||||
createdWindows []application.WebviewWindowOptions
|
||||
allWindows []application.Window
|
||||
}
|
||||
|
||||
func newMockWindowManager() *mockWindowManager {
|
||||
return &mockWindowManager{
|
||||
createdWindows: make([]application.WebviewWindowOptions, 0),
|
||||
allWindows: make([]application.Window, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mockWindowManager) NewWithOptions(opts application.WebviewWindowOptions) *application.WebviewWindow {
|
||||
m.createdWindows = append(m.createdWindows, opts)
|
||||
// Return nil since we can't create a real window without Wails runtime
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockWindowManager) GetAll() []application.Window {
|
||||
return m.allWindows
|
||||
}
|
||||
|
||||
// mockMenuManager tracks menu creation calls.
|
||||
type mockMenuManager struct {
|
||||
menusCreated int
|
||||
menuSet *application.Menu
|
||||
}
|
||||
|
||||
func newMockMenuManager() *mockMenuManager {
|
||||
return &mockMenuManager{}
|
||||
}
|
||||
|
||||
func (m *mockMenuManager) New() *application.Menu {
|
||||
m.menusCreated++
|
||||
return nil // Can't create real menu without Wails runtime
|
||||
}
|
||||
|
||||
func (m *mockMenuManager) Set(menu *application.Menu) {
|
||||
m.menuSet = menu
|
||||
}
|
||||
func (m *mockApp) Dialog() DialogManager { return m.dialogManager }
|
||||
func (m *mockApp) Env() EnvManager { return m.envManager }
|
||||
func (m *mockApp) Event() EventManager { return m.eventManager }
|
||||
func (m *mockApp) Logger() Logger { return m.logger }
|
||||
func (m *mockApp) Quit() { m.quitCalled = true }
|
||||
|
||||
// mockDialogManager tracks dialog creation calls.
|
||||
type mockDialogManager struct {
|
||||
|
|
@ -104,20 +53,6 @@ func (m *mockDialogManager) OpenFile() *application.OpenFileDialogStruct {
|
|||
return nil // Can't create real dialog without Wails runtime
|
||||
}
|
||||
|
||||
// mockSystemTrayManager tracks system tray creation calls.
|
||||
type mockSystemTrayManager struct {
|
||||
traysCreated int
|
||||
}
|
||||
|
||||
func newMockSystemTrayManager() *mockSystemTrayManager {
|
||||
return &mockSystemTrayManager{}
|
||||
}
|
||||
|
||||
func (m *mockSystemTrayManager) New() *application.SystemTray {
|
||||
m.traysCreated++
|
||||
return nil // Can't create real system tray without Wails runtime
|
||||
}
|
||||
|
||||
// mockEnvManager provides mock environment info.
|
||||
type mockEnvManager struct {
|
||||
envInfo application.EnvironmentInfo
|
||||
|
|
@ -147,11 +82,13 @@ func (m *mockEnvManager) IsDarkMode() bool {
|
|||
// mockEventManager tracks event registration.
|
||||
type mockEventManager struct {
|
||||
registeredEvents []events.ApplicationEventType
|
||||
emittedEvents []string
|
||||
}
|
||||
|
||||
func newMockEventManager() *mockEventManager {
|
||||
return &mockEventManager{
|
||||
registeredEvents: make([]events.ApplicationEventType, 0),
|
||||
emittedEvents: make([]string, 0),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,6 +98,7 @@ func (m *mockEventManager) OnApplicationEvent(eventType events.ApplicationEventT
|
|||
}
|
||||
|
||||
func (m *mockEventManager) Emit(name string, data ...any) bool {
|
||||
m.emittedEvents = append(m.emittedEvents, name)
|
||||
return true // Pretend emission succeeded
|
||||
}
|
||||
|
||||
|
|
@ -172,3 +110,21 @@ type mockLogger struct {
|
|||
func (m *mockLogger) Info(message string, args ...any) {
|
||||
m.infoMessages = append(m.infoMessages, message)
|
||||
}
|
||||
|
||||
// mockEventSource implements EventSource for testing.
|
||||
type mockEventSource struct {
|
||||
themeHandlers []func(isDark bool)
|
||||
}
|
||||
|
||||
func newMockEventSource() *mockEventSource {
|
||||
return &mockEventSource{}
|
||||
}
|
||||
|
||||
func (m *mockEventSource) OnThemeChange(handler func(isDark bool)) func() {
|
||||
m.themeHandlers = append(m.themeHandlers, handler)
|
||||
return func() {}
|
||||
}
|
||||
|
||||
func (m *mockEventSource) Emit(name string, data ...any) bool {
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,200 +0,0 @@
|
|||
package display
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
//go:embed assets/apptray.png
|
||||
var assets embed.FS
|
||||
|
||||
// activeTray holds the reference to the system tray for management.
|
||||
var activeTray *application.SystemTray
|
||||
|
||||
// systemTray configures and creates the system tray icon and menu.
|
||||
func (s *Service) systemTray() {
|
||||
|
||||
systray := s.app.SystemTray().New()
|
||||
activeTray = systray
|
||||
systray.SetTooltip("Core")
|
||||
systray.SetLabel("Core")
|
||||
|
||||
// Load and set tray icon
|
||||
appTrayIcon, err := assets.ReadFile("assets/apptray.png")
|
||||
if err == nil {
|
||||
if runtime.GOOS == "darwin" {
|
||||
systray.SetTemplateIcon(appTrayIcon)
|
||||
} else {
|
||||
// Support for light/dark mode icons
|
||||
systray.SetDarkModeIcon(appTrayIcon)
|
||||
systray.SetIcon(appTrayIcon)
|
||||
}
|
||||
}
|
||||
// Create a hidden window for the system tray menu to interact with
|
||||
trayWindow, _ := s.NewWithStruct(&Window{
|
||||
Name: "system-tray",
|
||||
Title: "System Tray Status",
|
||||
URL: "/system-tray",
|
||||
Width: 400,
|
||||
Frameless: true,
|
||||
Hidden: true,
|
||||
})
|
||||
systray.AttachWindow(trayWindow).WindowOffset(5)
|
||||
|
||||
// --- Build Tray Menu ---
|
||||
trayMenu := s.app.Menu().New()
|
||||
trayMenu.Add("Open Desktop").OnClick(func(ctx *application.Context) {
|
||||
for _, window := range s.app.Window().GetAll() {
|
||||
window.Show()
|
||||
}
|
||||
})
|
||||
trayMenu.Add("Close Desktop").OnClick(func(ctx *application.Context) {
|
||||
for _, window := range s.app.Window().GetAll() {
|
||||
window.Hide()
|
||||
}
|
||||
})
|
||||
|
||||
trayMenu.Add("Environment Info").OnClick(func(ctx *application.Context) {
|
||||
s.ShowEnvironmentDialog()
|
||||
})
|
||||
// Add brand-specific menu items
|
||||
//switch d.brand {
|
||||
//case AdminHub:
|
||||
// trayMenu.Add("Manage Workspace").OnClick(func(ctx *application.Context) { /* TODO */ })
|
||||
//case ServerHub:
|
||||
// trayMenu.Add("Server Control").OnClick(func(ctx *application.Context) { /* TODO */ })
|
||||
//case GatewayHub:
|
||||
// trayMenu.Add("Routing Table").OnClick(func(ctx *application.Context) { /* TODO */ })
|
||||
//case DeveloperHub:
|
||||
// trayMenu.Add("Debug Console").OnClick(func(ctx *application.Context) { /* TODO */ })
|
||||
//case ClientHub:
|
||||
// trayMenu.Add("Connect").OnClick(func(ctx *application.Context) { /* TODO */ })
|
||||
// trayMenu.Add("Disconnect").OnClick(func(ctx *application.Context) { /* TODO */ })
|
||||
//}
|
||||
|
||||
trayMenu.AddSeparator()
|
||||
trayMenu.Add("Quit").OnClick(func(ctx *application.Context) {
|
||||
s.app.Quit()
|
||||
})
|
||||
|
||||
systray.SetMenu(trayMenu)
|
||||
}
|
||||
|
||||
// SetTrayIcon sets the system tray icon from raw PNG data.
|
||||
func (s *Service) SetTrayIcon(iconData []byte) error {
|
||||
if activeTray == nil {
|
||||
return fmt.Errorf("system tray not initialized")
|
||||
}
|
||||
if runtime.GOOS == "darwin" {
|
||||
activeTray.SetTemplateIcon(iconData)
|
||||
} else {
|
||||
activeTray.SetIcon(iconData)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTrayTooltip sets the system tray tooltip text.
|
||||
func (s *Service) SetTrayTooltip(tooltip string) error {
|
||||
if activeTray == nil {
|
||||
return fmt.Errorf("system tray not initialized")
|
||||
}
|
||||
activeTray.SetTooltip(tooltip)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTrayLabel sets the system tray label text.
|
||||
func (s *Service) SetTrayLabel(label string) error {
|
||||
if activeTray == nil {
|
||||
return fmt.Errorf("system tray not initialized")
|
||||
}
|
||||
activeTray.SetLabel(label)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TrayMenuItem represents a menu item for the system tray.
|
||||
type TrayMenuItem struct {
|
||||
Label string `json:"label"`
|
||||
Type string `json:"type,omitempty"` // "normal", "separator", "checkbox", "radio"
|
||||
Checked bool `json:"checked,omitempty"` // for checkbox/radio items
|
||||
Disabled bool `json:"disabled,omitempty"`
|
||||
Tooltip string `json:"tooltip,omitempty"`
|
||||
Submenu []TrayMenuItem `json:"submenu,omitempty"`
|
||||
ActionID string `json:"actionId,omitempty"` // ID for callback
|
||||
}
|
||||
|
||||
// trayMenuCallbacks stores callbacks for tray menu items.
|
||||
var trayMenuCallbacks = make(map[string]func())
|
||||
|
||||
// SetTrayMenu sets the system tray menu from a list of menu items.
|
||||
func (s *Service) SetTrayMenu(items []TrayMenuItem) error {
|
||||
if activeTray == nil {
|
||||
return fmt.Errorf("system tray not initialized")
|
||||
}
|
||||
|
||||
menu := s.app.Menu().New()
|
||||
s.buildTrayMenu(menu, items)
|
||||
activeTray.SetMenu(menu)
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildTrayMenu recursively builds a menu from TrayMenuItem items.
|
||||
func (s *Service) buildTrayMenu(menu *application.Menu, items []TrayMenuItem) {
|
||||
for _, item := range items {
|
||||
switch item.Type {
|
||||
case "separator":
|
||||
menu.AddSeparator()
|
||||
case "checkbox":
|
||||
menuItem := menu.AddCheckbox(item.Label, item.Checked)
|
||||
if item.Disabled {
|
||||
menuItem.SetEnabled(false)
|
||||
}
|
||||
if item.ActionID != "" {
|
||||
actionID := item.ActionID
|
||||
menuItem.OnClick(func(ctx *application.Context) {
|
||||
if cb, ok := trayMenuCallbacks[actionID]; ok {
|
||||
cb()
|
||||
}
|
||||
})
|
||||
}
|
||||
default:
|
||||
if len(item.Submenu) > 0 {
|
||||
submenu := menu.AddSubmenu(item.Label)
|
||||
s.buildTrayMenu(submenu, item.Submenu)
|
||||
} else {
|
||||
menuItem := menu.Add(item.Label)
|
||||
if item.Disabled {
|
||||
menuItem.SetEnabled(false)
|
||||
}
|
||||
if item.Tooltip != "" {
|
||||
menuItem.SetTooltip(item.Tooltip)
|
||||
}
|
||||
if item.ActionID != "" {
|
||||
actionID := item.ActionID
|
||||
menuItem.OnClick(func(ctx *application.Context) {
|
||||
if cb, ok := trayMenuCallbacks[actionID]; ok {
|
||||
cb()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterTrayMenuCallback registers a callback for a tray menu action ID.
|
||||
func (s *Service) RegisterTrayMenuCallback(actionID string, callback func()) {
|
||||
trayMenuCallbacks[actionID] = callback
|
||||
}
|
||||
|
||||
// GetTrayInfo returns information about the current tray state.
|
||||
func (s *Service) GetTrayInfo() map[string]any {
|
||||
if activeTray == nil {
|
||||
return map[string]any{"active": false}
|
||||
}
|
||||
return map[string]any{
|
||||
"active": true,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
package display
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
type WindowOption func(*application.WebviewWindowOptions) error
|
||||
|
||||
type Window = application.WebviewWindowOptions
|
||||
|
||||
func WindowName(s string) WindowOption {
|
||||
return func(o *Window) error {
|
||||
o.Name = s
|
||||
return nil
|
||||
}
|
||||
}
|
||||
func WindowTitle(s string) WindowOption {
|
||||
return func(o *Window) error {
|
||||
o.Title = s
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WindowURL(s string) WindowOption {
|
||||
return func(o *Window) error {
|
||||
o.URL = s
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WindowWidth(i int) WindowOption {
|
||||
return func(o *Window) error {
|
||||
o.Width = i
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WindowHeight(i int) WindowOption {
|
||||
return func(o *Window) error {
|
||||
o.Height = i
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func applyOptions(opts ...WindowOption) *Window {
|
||||
w := &Window{}
|
||||
if opts == nil {
|
||||
return w
|
||||
}
|
||||
for _, o := range opts {
|
||||
if err := o(w); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// NewWithStruct creates a new window using the provided options and returns its handle.
|
||||
func (s *Service) NewWithStruct(options *Window) (*application.WebviewWindow, error) {
|
||||
return s.app.Window().NewWithOptions(*options), nil
|
||||
}
|
||||
|
||||
// NewWithOptions creates a new window by applying a series of options.
|
||||
func (s *Service) NewWithOptions(opts ...WindowOption) (*application.WebviewWindow, error) {
|
||||
return s.NewWithStruct(applyOptions(opts...))
|
||||
}
|
||||
|
||||
// NewWithURL creates a new default window pointing to the specified URL.
|
||||
func (s *Service) NewWithURL(url string) (*application.WebviewWindow, error) {
|
||||
return s.NewWithOptions(
|
||||
WindowURL(url),
|
||||
WindowTitle("Core"),
|
||||
WindowHeight(900),
|
||||
WindowWidth(1280),
|
||||
)
|
||||
}
|
||||
|
||||
//// OpenWindow is a convenience method that creates and shows a window from a set of options.
|
||||
//func (s *Service) OpenWindow(opts ...WindowOption) error {
|
||||
// _, err := s.NewWithOptions(opts...)
|
||||
// return err
|
||||
//}
|
||||
|
||||
// SelectDirectory opens a directory selection dialog and returns the selected path.
|
||||
// TODO: Update for Wails v3 API - use DialogManager.OpenFile() instead
|
||||
//func (s *Service) SelectDirectory() (string, error) {
|
||||
// dialog := application.OpenFileDialog()
|
||||
// dialog.SetTitle("Select Project Directory")
|
||||
// return dialog.PromptForSingleSelection()
|
||||
//}
|
||||
|
|
@ -1,261 +0,0 @@
|
|||
package display
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
// WindowState holds the persisted state of a window.
|
||||
type WindowState struct {
|
||||
X int `json:"x"`
|
||||
Y int `json:"y"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Maximized bool `json:"maximized"`
|
||||
Screen string `json:"screen,omitempty"` // Screen identifier for multi-monitor
|
||||
URL string `json:"url,omitempty"` // Last URL/route
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// WindowStateManager handles saving and restoring window positions.
|
||||
type WindowStateManager struct {
|
||||
states map[string]*WindowState
|
||||
filePath string
|
||||
mu sync.RWMutex
|
||||
dirty bool
|
||||
saveTimer *time.Timer
|
||||
}
|
||||
|
||||
// NewWindowStateManager creates a new window state manager.
|
||||
// It loads existing state from the config directory.
|
||||
func NewWindowStateManager() *WindowStateManager {
|
||||
m := &WindowStateManager{
|
||||
states: make(map[string]*WindowState),
|
||||
}
|
||||
|
||||
// Determine config path
|
||||
configDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
configDir = "."
|
||||
}
|
||||
m.filePath = filepath.Join(configDir, "Core", "window_state.json")
|
||||
|
||||
// Ensure directory exists
|
||||
os.MkdirAll(filepath.Dir(m.filePath), 0755)
|
||||
|
||||
// Load existing state
|
||||
m.load()
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// load reads window states from disk.
|
||||
func (m *WindowStateManager) load() error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
data, err := os.ReadFile(m.filePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil // No saved state yet
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return json.Unmarshal(data, &m.states)
|
||||
}
|
||||
|
||||
// save writes window states to disk.
|
||||
func (m *WindowStateManager) save() error {
|
||||
m.mu.RLock()
|
||||
data, err := json.MarshalIndent(m.states, "", " ")
|
||||
m.mu.RUnlock()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(m.filePath, data, 0644)
|
||||
}
|
||||
|
||||
// scheduleSave debounces saves to avoid excessive disk writes.
|
||||
func (m *WindowStateManager) scheduleSave() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
m.dirty = true
|
||||
|
||||
// Cancel existing timer
|
||||
if m.saveTimer != nil {
|
||||
m.saveTimer.Stop()
|
||||
}
|
||||
|
||||
// Schedule save after 500ms of no changes
|
||||
m.saveTimer = time.AfterFunc(500*time.Millisecond, func() {
|
||||
m.mu.Lock()
|
||||
if m.dirty {
|
||||
m.dirty = false
|
||||
m.mu.Unlock()
|
||||
m.save()
|
||||
} else {
|
||||
m.mu.Unlock()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// GetState returns the saved state for a window, or nil if none.
|
||||
func (m *WindowStateManager) GetState(name string) *WindowState {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return m.states[name]
|
||||
}
|
||||
|
||||
// SetState saves the state for a window.
|
||||
func (m *WindowStateManager) SetState(name string, state *WindowState) {
|
||||
m.mu.Lock()
|
||||
state.UpdatedAt = time.Now().Unix()
|
||||
m.states[name] = state
|
||||
m.mu.Unlock()
|
||||
|
||||
m.scheduleSave()
|
||||
}
|
||||
|
||||
// UpdatePosition updates just the position of a window.
|
||||
func (m *WindowStateManager) UpdatePosition(name string, x, y int) {
|
||||
m.mu.Lock()
|
||||
state, ok := m.states[name]
|
||||
if !ok {
|
||||
state = &WindowState{}
|
||||
m.states[name] = state
|
||||
}
|
||||
state.X = x
|
||||
state.Y = y
|
||||
state.UpdatedAt = time.Now().Unix()
|
||||
m.mu.Unlock()
|
||||
|
||||
m.scheduleSave()
|
||||
}
|
||||
|
||||
// UpdateSize updates just the size of a window.
|
||||
func (m *WindowStateManager) UpdateSize(name string, width, height int) {
|
||||
m.mu.Lock()
|
||||
state, ok := m.states[name]
|
||||
if !ok {
|
||||
state = &WindowState{}
|
||||
m.states[name] = state
|
||||
}
|
||||
state.Width = width
|
||||
state.Height = height
|
||||
state.UpdatedAt = time.Now().Unix()
|
||||
m.mu.Unlock()
|
||||
|
||||
m.scheduleSave()
|
||||
}
|
||||
|
||||
// UpdateMaximized updates the maximized state of a window.
|
||||
func (m *WindowStateManager) UpdateMaximized(name string, maximized bool) {
|
||||
m.mu.Lock()
|
||||
state, ok := m.states[name]
|
||||
if !ok {
|
||||
state = &WindowState{}
|
||||
m.states[name] = state
|
||||
}
|
||||
state.Maximized = maximized
|
||||
state.UpdatedAt = time.Now().Unix()
|
||||
m.mu.Unlock()
|
||||
|
||||
m.scheduleSave()
|
||||
}
|
||||
|
||||
// CaptureState captures the current state from a window.
|
||||
func (m *WindowStateManager) CaptureState(name string, window *application.WebviewWindow) {
|
||||
if window == nil {
|
||||
return
|
||||
}
|
||||
|
||||
x, y := window.Position()
|
||||
width, height := window.Size()
|
||||
|
||||
m.mu.Lock()
|
||||
state, ok := m.states[name]
|
||||
if !ok {
|
||||
state = &WindowState{}
|
||||
m.states[name] = state
|
||||
}
|
||||
state.X = x
|
||||
state.Y = y
|
||||
state.Width = width
|
||||
state.Height = height
|
||||
state.Maximized = window.IsMaximised()
|
||||
state.UpdatedAt = time.Now().Unix()
|
||||
m.mu.Unlock()
|
||||
|
||||
m.scheduleSave()
|
||||
}
|
||||
|
||||
// ApplyState applies saved state to window options.
|
||||
// Returns the modified options with position/size restored.
|
||||
func (m *WindowStateManager) ApplyState(opts application.WebviewWindowOptions) application.WebviewWindowOptions {
|
||||
state := m.GetState(opts.Name)
|
||||
if state == nil {
|
||||
return opts
|
||||
}
|
||||
|
||||
// Only apply if we have valid saved dimensions
|
||||
if state.Width > 0 && state.Height > 0 {
|
||||
opts.Width = state.Width
|
||||
opts.Height = state.Height
|
||||
}
|
||||
|
||||
// Apply position (check for reasonable values)
|
||||
if state.X != 0 || state.Y != 0 {
|
||||
opts.X = state.X
|
||||
opts.Y = state.Y
|
||||
}
|
||||
|
||||
// Apply maximized state
|
||||
if state.Maximized {
|
||||
opts.StartState = application.WindowStateMaximised
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
// ForceSync immediately saves all state to disk.
|
||||
func (m *WindowStateManager) ForceSync() error {
|
||||
m.mu.Lock()
|
||||
if m.saveTimer != nil {
|
||||
m.saveTimer.Stop()
|
||||
m.saveTimer = nil
|
||||
}
|
||||
m.dirty = false
|
||||
m.mu.Unlock()
|
||||
|
||||
return m.save()
|
||||
}
|
||||
|
||||
// Clear removes all saved window states.
|
||||
func (m *WindowStateManager) Clear() error {
|
||||
m.mu.Lock()
|
||||
m.states = make(map[string]*WindowState)
|
||||
m.mu.Unlock()
|
||||
|
||||
return m.save()
|
||||
}
|
||||
|
||||
// ListStates returns all saved window names.
|
||||
func (m *WindowStateManager) ListStates() []string {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
names := make([]string, 0, len(m.states))
|
||||
for name := range m.states {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
|
@ -46,6 +46,17 @@ func NewLayoutManager() *LayoutManager {
|
|||
return lm
|
||||
}
|
||||
|
||||
// NewLayoutManagerWithDir creates a LayoutManager loading from a custom config directory.
|
||||
// Useful for testing or when the default config directory is not appropriate.
|
||||
func NewLayoutManagerWithDir(configDir string) *LayoutManager {
|
||||
lm := &LayoutManager{
|
||||
configDir: configDir,
|
||||
layouts: make(map[string]Layout),
|
||||
}
|
||||
lm.load()
|
||||
return lm
|
||||
}
|
||||
|
||||
func (lm *LayoutManager) filePath() string {
|
||||
return filepath.Join(lm.configDir, "layouts.json")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,17 @@ func NewStateManager() *StateManager {
|
|||
return sm
|
||||
}
|
||||
|
||||
// NewStateManagerWithDir creates a StateManager loading from a custom config directory.
|
||||
// Useful for testing or when the default config directory is not appropriate.
|
||||
func NewStateManagerWithDir(configDir string) *StateManager {
|
||||
sm := &StateManager{
|
||||
configDir: configDir,
|
||||
states: make(map[string]WindowState),
|
||||
}
|
||||
sm.load()
|
||||
return sm
|
||||
}
|
||||
|
||||
func (sm *StateManager) filePath() string {
|
||||
return filepath.Join(sm.configDir, "window_state.json")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,17 @@ func NewManager(platform Platform) *Manager {
|
|||
}
|
||||
}
|
||||
|
||||
// NewManagerWithDir creates a window Manager with a custom config directory for state/layout persistence.
|
||||
// Useful for testing or when the default config directory is not appropriate.
|
||||
func NewManagerWithDir(platform Platform, configDir string) *Manager {
|
||||
return &Manager{
|
||||
platform: platform,
|
||||
state: NewStateManagerWithDir(configDir),
|
||||
layout: NewLayoutManagerWithDir(configDir),
|
||||
windows: make(map[string]PlatformWindow),
|
||||
}
|
||||
}
|
||||
|
||||
// Open creates a window using functional options, applies saved state, and tracks it.
|
||||
func (m *Manager) Open(opts ...WindowOption) (PlatformWindow, error) {
|
||||
w, err := ApplyOptions(opts...)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue