ui
This commit is contained in:
parent
7296524768
commit
60d10b3a11
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
// 使用 IntelliSense 了解相关属性。
|
||||
// 悬停以查看现有属性的描述。
|
||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "GUI",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}/main.go"
|
||||
}
|
||||
]
|
||||
}
|
@ -12,21 +12,27 @@ var _ lib.ICaller = (*FriendApply)(nil)
|
||||
|
||||
type FriendApply struct {
|
||||
lib.Action
|
||||
Desc string
|
||||
}
|
||||
|
||||
func (a *FriendApply) BuildReq(head *pb.UserMessage) lib.ActionReq {
|
||||
func (a *FriendApply) ShowInfo() lib.CallerInfo {
|
||||
return lib.CallerInfo{
|
||||
Key: "friend.apply",
|
||||
Desc: "好友申请",
|
||||
}
|
||||
}
|
||||
|
||||
func (a *FriendApply) BuildReq(store lib.IStore, head *pb.UserMessage) lib.RawReq {
|
||||
id := time.Now().UnixNano()
|
||||
var req []byte
|
||||
|
||||
b := a.Get("friend.apply")
|
||||
b := store.Get("friend.apply")
|
||||
rsp := &pb.FriendRandlistResp{}
|
||||
if err := proto.Unmarshal(b, rsp); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(rsp.List) == 0 {
|
||||
return lib.ActionReq{}
|
||||
return lib.RawReq{}
|
||||
}
|
||||
|
||||
if lib.ProtoMarshal(&pb.FriendApplyReq{
|
||||
@ -38,5 +44,5 @@ func (a *FriendApply) BuildReq(head *pb.UserMessage) lib.ActionReq {
|
||||
}
|
||||
req = data
|
||||
}
|
||||
return lib.ActionReq{ID: id, Req: req}
|
||||
return lib.RawReq{ID: id, Req: req}
|
||||
}
|
||||
|
@ -13,10 +13,16 @@ var _ lib.ICaller = (*FriendRecommend)(nil)
|
||||
// 好友推荐
|
||||
type FriendRecommend struct {
|
||||
lib.Action
|
||||
Desc string
|
||||
}
|
||||
|
||||
func (a *FriendRecommend) BuildReq(head *pb.UserMessage) lib.ActionReq {
|
||||
func (a *FriendRecommend) ShowInfo() lib.CallerInfo {
|
||||
return lib.CallerInfo{
|
||||
Key: "friend.randlist",
|
||||
Desc: "好友推荐",
|
||||
}
|
||||
}
|
||||
|
||||
func (a *FriendRecommend) BuildReq(store lib.IStore, head *pb.UserMessage) lib.RawReq {
|
||||
id := time.Now().UnixNano()
|
||||
|
||||
var req []byte
|
||||
@ -28,5 +34,5 @@ func (a *FriendRecommend) BuildReq(head *pb.UserMessage) lib.ActionReq {
|
||||
req = data
|
||||
}
|
||||
|
||||
return lib.ActionReq{ID: id, Req: req}
|
||||
return lib.RawReq{ID: id, Req: req}
|
||||
}
|
||||
|
3
go.mod
3
go.mod
@ -3,8 +3,11 @@ module legu.airobot
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
fyne.io/fyne v1.4.3
|
||||
fyne.io/fyne/v2 v2.2.4
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cast v1.3.1
|
||||
google.golang.org/protobuf v1.28.1
|
||||
)
|
||||
|
||||
|
22
go.sum
22
go.sum
@ -37,6 +37,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
fyne.io/fyne v1.4.3 h1:356CnXCiYrrfaLGsB7qLK3c6ktzyh8WR05v/2RBu51I=
|
||||
fyne.io/fyne v1.4.3/go.mod h1:8kiPBNSDmuplxs9WnKCkaWYqbcXFy0DeAzwa6PBO9Z8=
|
||||
fyne.io/fyne/v2 v2.2.4 h1:izyiDUjJYAB7B/MST7M9GDs+mQ0CwDgRZTiVJZQoEe4=
|
||||
fyne.io/fyne/v2 v2.2.4/go.mod h1:MBoGuHzLLSXdQOWFAwWhIhYTEMp33zqtGCReSWhaQTA=
|
||||
fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 h1:V2IC9t0Zj9Ur6qDbfhUuzVmIvXKFyxZXRJyigUvovs4=
|
||||
@ -44,6 +46,8 @@ fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93/go.mod h1:oM2AQqGJ1AMo4nNq
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
@ -85,15 +89,19 @@ github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 h1:+31CdF/okdokeFN
|
||||
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504/go.mod h1:gLRWYfYnMA9TONeppRSikMdXlHQ97xVsPojddUv3b/E=
|
||||
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 h1:hnLq+55b7Zh7/2IRzWCpiTcAvjv/P8ERF+N7+xXbZhk=
|
||||
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0=
|
||||
github.com/fyne-io/mobile v0.1.2/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
|
||||
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
|
||||
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec h1:3FLiRYO6PlQFDpUU7OEFlWgjGD1jnBIVSJ5SYRWk+9c=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
@ -167,6 +175,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
|
||||
github.com/gopherjs/gopherjs v0.0.0-20211219123610-ec9572f70e60/go.mod h1:cz9oNYuRUWGdHmLF2IodMLkAhcPtXeULvcBNagUrxTI=
|
||||
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY=
|
||||
github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
@ -193,7 +203,9 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
||||
github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI=
|
||||
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
|
||||
github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
@ -208,6 +220,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng=
|
||||
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
@ -232,7 +245,9 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@ -253,10 +268,13 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
||||
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM=
|
||||
@ -318,6 +336,7 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd h1:9NbNcTg//wfC5JskFW4Z3sqwVnjmJKHxLAol1bW2qgw=
|
||||
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@ -435,6 +454,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -483,6 +503,7 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@ -504,6 +525,7 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
|
@ -1,39 +1,16 @@
|
||||
package lib
|
||||
|
||||
type IAction interface {
|
||||
|
||||
ShowInfo() CallerInfo
|
||||
}
|
||||
|
||||
type Action struct {
|
||||
scene *scene
|
||||
// scene *scene
|
||||
Key string
|
||||
Desc string
|
||||
}
|
||||
|
||||
func NewAction() *Action {
|
||||
a := &Action{}
|
||||
return a
|
||||
}
|
||||
|
||||
//存数据
|
||||
func (a *Action) Store(key string, data []byte) {
|
||||
defer a.scene.lock.Unlock()
|
||||
a.scene.lock.Lock()
|
||||
a.scene.data[key] = data
|
||||
}
|
||||
|
||||
//取数据
|
||||
func (a *Action) Get(key string) []byte {
|
||||
defer a.scene.lock.Unlock()
|
||||
a.scene.lock.Lock()
|
||||
return a.scene.data[key]
|
||||
}
|
||||
|
||||
// func (a *BaseAction) Load(action ...IAction) {
|
||||
// for _, v := range action {
|
||||
// v.RegisterAction()
|
||||
// }
|
||||
// }
|
||||
|
||||
// func (a *BaseAction) Reg(idStr string, caller interface{}) {
|
||||
// // a.actionMap[idStr] = action
|
||||
// a.ai.callers = append(a.ai.callers, caller)
|
||||
// }
|
||||
|
98
lib/ai.go
98
lib/ai.go
@ -1,28 +1,53 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type myAI struct {
|
||||
robots []*myRobot
|
||||
robots []*Robot
|
||||
scenes []*scene
|
||||
callers []ICaller
|
||||
tickets Tickets //票池
|
||||
robotCount uint32 //机器人数量
|
||||
lock sync.Mutex //
|
||||
}
|
||||
|
||||
func NewAI(aip AIParam) (*myAI, error) {
|
||||
logrus.Debug("创建AI")
|
||||
if err := aip.Check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func NewAI() *myAI {
|
||||
ai := &myAI{
|
||||
// action: NewAction(),
|
||||
scenes: make([]*scene, 0),
|
||||
robotCount: aip.RobotCount,
|
||||
}
|
||||
|
||||
// ai.action = NewAction(ai)
|
||||
|
||||
ai.init()
|
||||
|
||||
return ai
|
||||
if err := ai.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (m *myAI) init() {
|
||||
//初始化机器人容量
|
||||
// 容量限制
|
||||
return ai, nil
|
||||
}
|
||||
|
||||
func (m *myAI) init() error {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("初始化AI")
|
||||
tickets, err := NewTickets(m.robotCount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.tickets = tickets
|
||||
|
||||
buf.WriteString(fmt.Sprintf("完成 机器人数量:%d", m.robotCount))
|
||||
logrus.Debug(buf.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
//启动时载入所有Caller
|
||||
@ -37,7 +62,7 @@ func (m *myAI) InitCaller(callers ...ICaller) {
|
||||
}
|
||||
|
||||
// 加入机器人
|
||||
func (m *myAI) AddRobots(num int, scene *scene) {
|
||||
func (m *myAI) AddRobot(scene *scene) {
|
||||
//
|
||||
robot := NewRobot()
|
||||
robot.SelScene(scene)
|
||||
@ -45,7 +70,7 @@ func (m *myAI) AddRobots(num int, scene *scene) {
|
||||
}
|
||||
|
||||
// 获取场景下的机器人
|
||||
func (m *myAI) GetRobots(sceneName string) (robots []*myRobot) {
|
||||
func (m *myAI) GetRobots(sceneName string) (robots []*Robot) {
|
||||
for _, robot := range m.robots {
|
||||
scene := robot.GetCurrentScene()
|
||||
if scene != nil && scene.Name == sceneName {
|
||||
@ -55,14 +80,51 @@ func (m *myAI) GetRobots(sceneName string) (robots []*myRobot) {
|
||||
return
|
||||
}
|
||||
|
||||
func (m *myAI) Start() {
|
||||
//启动默认数量的机器人
|
||||
func (m *myAI) CurrentScene() *scene {
|
||||
for _, v := range m.scenes {
|
||||
if v.status == STATUS_ENABLE {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
r := NewRobot()
|
||||
//
|
||||
func (m *myAI) appendRobot(robot *Robot) {
|
||||
defer m.lock.Unlock()
|
||||
m.lock.Lock()
|
||||
m.robots = append(m.robots, robot)
|
||||
}
|
||||
|
||||
r.Start()
|
||||
func (m *myAI) Start() bool {
|
||||
if len(m.scenes) == 0 {
|
||||
logrus.Warn("还未设置场景")
|
||||
return false
|
||||
}
|
||||
// 只有一个场景是启用状态,所有启动的机器人使用一个场景
|
||||
scene := m.CurrentScene()
|
||||
if scene == nil {
|
||||
logrus.Warn("至少要启用一个场景")
|
||||
return false
|
||||
}
|
||||
|
||||
// i := uint32(0); i < m.robotCount; i++
|
||||
go func() {
|
||||
for {
|
||||
m.tickets.Take()
|
||||
go func() {
|
||||
|
||||
// m.AddRobot(scene)
|
||||
robot := NewRobot()
|
||||
robot.SelScene(scene)
|
||||
m.appendRobot(robot)
|
||||
|
||||
robot.Start()
|
||||
}()
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *myAI) Stop() {
|
||||
|
38
lib/base.go
38
lib/base.go
@ -1,11 +1,47 @@
|
||||
package lib
|
||||
|
||||
type ActionReq struct {
|
||||
import "time"
|
||||
|
||||
type RawReq struct {
|
||||
ID int64
|
||||
Req []byte
|
||||
}
|
||||
|
||||
type RawResp struct {
|
||||
ID int64
|
||||
Resp []byte
|
||||
Err error
|
||||
Elapse time.Duration
|
||||
}
|
||||
|
||||
type CallerInfo struct {
|
||||
Key string
|
||||
Desc string
|
||||
}
|
||||
|
||||
const (
|
||||
//默认的机器人数量
|
||||
DefaultRobotNum int = 10
|
||||
)
|
||||
|
||||
//机器人状态
|
||||
const (
|
||||
// STATUS_ORIGINAL 代表原始。
|
||||
STATUS_ORIGINAL uint32 = 0
|
||||
// STATUS_STARTING 代表正在启动。
|
||||
STATUS_STARTING uint32 = 1
|
||||
// STATUS_STARTED 代表已启动。
|
||||
STATUS_STARTED uint32 = 2
|
||||
// STATUS_STOPPING 代表正在停止。
|
||||
STATUS_STOPPING uint32 = 3
|
||||
// STATUS_STOPPED 代表已停止。
|
||||
STATUS_STOPPED uint32 = 4
|
||||
)
|
||||
|
||||
// 场景状态
|
||||
const (
|
||||
// 启用
|
||||
STATUS_ENABLE uint32 = 1
|
||||
// 禁用
|
||||
STATUS_DISENABLE uint32 = 0
|
||||
)
|
||||
|
@ -4,6 +4,5 @@ import "legu.airobot/pb"
|
||||
|
||||
type ICaller interface {
|
||||
IAction
|
||||
|
||||
BuildReq(head *pb.UserMessage) ActionReq
|
||||
BuildReq(store IStore, head *pb.UserMessage) RawReq
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package lib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
@ -17,3 +19,90 @@ func ProtoMarshal(rsp proto.Message, msg *pb.UserMessage) (ok bool) {
|
||||
msg.Data = any
|
||||
return true
|
||||
}
|
||||
|
||||
func ProtoUnmarshal(msg *pb.UserMessage, req proto.Message) (ok bool) {
|
||||
err := msg.Data.UnmarshalTo(req)
|
||||
if err != nil {
|
||||
fmt.Printf("UnmarshalTo %s.%s %v\n", msg.MainType, msg.SubType, err)
|
||||
return
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func SubStr(str string, start int, length int) (result string) {
|
||||
s := []rune(str)
|
||||
total := len(s)
|
||||
if total == 0 {
|
||||
return
|
||||
}
|
||||
// 允许从尾部开始计算
|
||||
if start < 0 {
|
||||
start = total + start
|
||||
if start < 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
if start > total {
|
||||
return
|
||||
}
|
||||
// 到末尾
|
||||
if length < 0 {
|
||||
length = total
|
||||
}
|
||||
|
||||
end := start + length
|
||||
if end > total {
|
||||
result = string(s[start:])
|
||||
} else {
|
||||
result = string(s[start:end])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func DeleteString(list []string, ele string) []string {
|
||||
result := make([]string, 0)
|
||||
for _, v := range list {
|
||||
if v != ele {
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ConvertFileSize(size int64) string {
|
||||
if size == 0 {
|
||||
return "0"
|
||||
}
|
||||
|
||||
kb := float64(size) / float64(1024)
|
||||
|
||||
if kb < 1024 {
|
||||
f := math.Ceil(kb)
|
||||
return strconv.Itoa(int(f)) + " KB"
|
||||
}
|
||||
|
||||
mb := kb / float64(1024)
|
||||
if mb < 1024 {
|
||||
f := math.Ceil(mb)
|
||||
return strconv.Itoa(int(f)) + " MB"
|
||||
}
|
||||
|
||||
gb := mb / float64(1024)
|
||||
if gb < 1024 {
|
||||
f := math.Ceil(mb)
|
||||
return strconv.Itoa(int(f)) + " G"
|
||||
}
|
||||
|
||||
t := gb / float64(1024)
|
||||
if t < 1024 {
|
||||
f := math.Ceil(mb)
|
||||
return strconv.Itoa(int(f)) + " T"
|
||||
}
|
||||
|
||||
if t >= 1024 {
|
||||
return "VeryBig"
|
||||
}
|
||||
|
||||
return "0"
|
||||
}
|
||||
|
251
lib/itemlist.go
Normal file
251
lib/itemlist.go
Normal file
@ -0,0 +1,251 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type ItemList struct {
|
||||
SelItemIds []string //选择的ID 多选
|
||||
SelItemId string //选择的ID 单选
|
||||
ItemTotal int //总数
|
||||
TitleLabel *widget.Label
|
||||
CachedList List
|
||||
ListWidget *widget.List
|
||||
SearchItem []Item //用于暂存搜索结果
|
||||
OnSelFunc func(id widget.ListItemID)
|
||||
}
|
||||
|
||||
func NewItemList() *ItemList {
|
||||
return &ItemList{
|
||||
TitleLabel: &widget.Label{},
|
||||
CachedList: NewList(""),
|
||||
}
|
||||
}
|
||||
|
||||
// 重置
|
||||
func (f *ItemList) Reset() {
|
||||
f.ItemTotal = 0
|
||||
f.SelItemIds = []string{}
|
||||
f.SelItemId = ""
|
||||
f.CachedList = NewList("")
|
||||
f.ListWidget.UnselectAll()
|
||||
f.ListWidget.Refresh()
|
||||
}
|
||||
|
||||
func (f *ItemList) CreateList() *widget.List {
|
||||
f.ListWidget = widget.NewList(
|
||||
func() int {
|
||||
return len(f.CachedList.Items)
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return widget.NewLabel("Template")
|
||||
},
|
||||
func(id widget.ListItemID, item fyne.CanvasObject) {
|
||||
c, _ := item.(*widget.Label)
|
||||
c.Text = f.CachedList.Items[id].Text
|
||||
c.Refresh()
|
||||
},
|
||||
)
|
||||
if f.OnSelFunc == nil {
|
||||
f.ListWidget.OnSelected = func(id widget.ListItemID) {
|
||||
selId := f.CachedList.Items[id].Id
|
||||
f.SelItemId = selId
|
||||
}
|
||||
} else {
|
||||
f.ListWidget.OnSelected = f.OnSelFunc
|
||||
}
|
||||
return f.ListWidget
|
||||
}
|
||||
|
||||
// 创建默认的列表
|
||||
func (f *ItemList) CreateDefaultCheckList() *widget.List {
|
||||
f.ListWidget = widget.NewList(
|
||||
func() int {
|
||||
return len(f.CachedList.Items)
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return widget.NewCheck("Template", func(bool) {})
|
||||
},
|
||||
func(id widget.ListItemID, item fyne.CanvasObject) {
|
||||
c, _ := item.(*widget.Check)
|
||||
_id := f.CachedList.Items[id].Id
|
||||
c.Text = f.CachedList.Items[id].Text
|
||||
c.Checked = f.CachedList.Items[id].Checked
|
||||
|
||||
c.OnChanged = func(b bool) {
|
||||
if b {
|
||||
f.SelItemIds = append(f.SelItemIds, _id)
|
||||
} else {
|
||||
f.SelItemIds = DeleteString(f.SelItemIds, _id)
|
||||
}
|
||||
f.TitleLabel.SetText(fmt.Sprintf("(%d/%d)", len(f.SelItemIds), f.ItemTotal))
|
||||
f.CachedList.Items[id].Checked = b
|
||||
// sort.Sort(f.cachedList)
|
||||
f.ListWidget.Refresh()
|
||||
}
|
||||
c.Refresh()
|
||||
},
|
||||
)
|
||||
return f.ListWidget
|
||||
}
|
||||
|
||||
// 创建下载列表
|
||||
func (f *ItemList) CreateDownloadList() *widget.List {
|
||||
f.ListWidget = widget.NewList(
|
||||
func() int {
|
||||
return len(f.CachedList.Items)
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
chk := widget.NewCheck("Template", func(bool) {})
|
||||
lb := widget.NewLabel("Template")
|
||||
items := container.NewHBox(chk, &layout.Spacer{}, lb)
|
||||
return items
|
||||
},
|
||||
func(id widget.ListItemID, item fyne.CanvasObject) {
|
||||
c, _ := item.(*fyne.Container)
|
||||
chk := c.Objects[0].(*widget.Check)
|
||||
data := f.CachedList.Items[id]
|
||||
chk.Text = data.Text
|
||||
chk.Checked = data.Checked
|
||||
chk.OnChanged = func(b bool) {
|
||||
if b {
|
||||
f.SelItemIds = append(f.SelItemIds, chk.Text)
|
||||
} else {
|
||||
f.SelItemIds = DeleteString(f.SelItemIds, chk.Text)
|
||||
}
|
||||
f.CachedList.Items[id].Checked = b
|
||||
// sort.Sort(f.cachedList)
|
||||
f.ListWidget.Refresh()
|
||||
}
|
||||
|
||||
lb := c.Objects[2].(*widget.Label)
|
||||
lb.Text = ConvertFileSize(data.Size)
|
||||
c.Refresh()
|
||||
},
|
||||
)
|
||||
return f.ListWidget
|
||||
}
|
||||
|
||||
func (f *ItemList) AddItem(item Item) {
|
||||
f.CachedList.Items = append(f.CachedList.Items, item)
|
||||
// sort.Sort(f.cachedList)
|
||||
f.ListWidget.Refresh()
|
||||
}
|
||||
|
||||
func (f *ItemList) AddItemWithText(val string) {
|
||||
val = strings.TrimSpace(val)
|
||||
if len(val) == 0 {
|
||||
return
|
||||
}
|
||||
newItem := Item{
|
||||
Id: val,
|
||||
Text: val,
|
||||
Quantity: 1,
|
||||
Checked: false, //默认不选中
|
||||
}
|
||||
f.CachedList.Items = append(f.CachedList.Items, newItem)
|
||||
sort.Sort(f.CachedList)
|
||||
f.ListWidget.Refresh()
|
||||
}
|
||||
|
||||
func (f *ItemList) LoadItem(dirPath string) {
|
||||
f.Reset()
|
||||
files, err := ioutil.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if !file.IsDir() {
|
||||
f.AddItemWithText(file.Name())
|
||||
// f.selItemIds = append(f.selItemIds, file.Name())
|
||||
f.ItemTotal++
|
||||
// logrus.Debugf("%v", file.Name())
|
||||
}
|
||||
}
|
||||
|
||||
f.SearchItem = f.CachedList.Items
|
||||
}
|
||||
|
||||
func (f *ItemList) SelectedId() string {
|
||||
if f.SelItemId != "" {
|
||||
return f.SelItemId
|
||||
} else {
|
||||
if len(f.CachedList.Items) == 1 {
|
||||
return f.CachedList.Items[0].Id
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 改变列表项目
|
||||
func (f *ItemList) ChangeItem(tmpDir, projectDir string) {
|
||||
f.ItemTotal = 0
|
||||
f.SelItemIds = []string{}
|
||||
f.CachedList = NewList("")
|
||||
tmpFiles, err := ioutil.ReadDir(tmpDir)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if len(tmpFiles) == 0 || len(f.CachedList.Items) == 0 {
|
||||
logrus.Warnln("没有任何变更的项目")
|
||||
}
|
||||
}()
|
||||
|
||||
projectFiles, err := ioutil.ReadDir(projectDir)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
pfMap := make(map[string]int64)
|
||||
for _, v := range projectFiles {
|
||||
if !v.IsDir() {
|
||||
pfMap[v.Name()] = v.ModTime().Unix()
|
||||
}
|
||||
}
|
||||
|
||||
for _, file := range tmpFiles {
|
||||
if !file.IsDir() {
|
||||
if pfTime, ok := pfMap[file.Name()]; ok {
|
||||
if file.ModTime().Unix() <= pfTime {
|
||||
continue
|
||||
}
|
||||
}
|
||||
f.AddItemWithText(file.Name())
|
||||
// f.SelItemIds = append(f.SelItemIds, file.Name())
|
||||
f.ItemTotal++
|
||||
// logrus.Debugf("%v", file.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新文件数
|
||||
func (f *ItemList) ChangeFileCount() {
|
||||
f.TitleLabel.SetText(fmt.Sprintf("(%d/%d)", len(f.SelItemIds), f.ItemTotal))
|
||||
}
|
||||
|
||||
func (f *ItemList) DeleteItem(name string) {
|
||||
for i, v := range f.CachedList.Items {
|
||||
if v.Text == name {
|
||||
f.SelItemIds = DeleteString(f.SelItemIds, v.Text)
|
||||
f.CachedList.Items = append(f.CachedList.Items[:i], f.CachedList.Items[i+1:]...)
|
||||
if f.ItemTotal > 0 {
|
||||
f.ItemTotal--
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
75
lib/list.go
Normal file
75
lib/list.go
Normal file
@ -0,0 +1,75 @@
|
||||
package lib
|
||||
|
||||
import "strings"
|
||||
|
||||
var replacer *strings.Replacer
|
||||
|
||||
func init() {
|
||||
trimChars := "0123456789-()/½⅓¼⅕⅙⅐⅛⅑⅔⅖¾⅗⅘"
|
||||
tcs := make([]string, len(trimChars)*2)
|
||||
for i, c := range trimChars {
|
||||
tcs[i*2] = string(c)
|
||||
tcs[i*2+1] = ""
|
||||
}
|
||||
replacer = strings.NewReplacer(tcs...)
|
||||
}
|
||||
|
||||
type List struct {
|
||||
Name string `json:"name"`
|
||||
Group string `json:"group"`
|
||||
Items []Item `json:"items"`
|
||||
// ID is the Mealie API reference for the list.
|
||||
ID int `json:"id"`
|
||||
}
|
||||
|
||||
var sortableText map[string]string = map[string]string{}
|
||||
|
||||
func (l List) Len() int { return len(l.Items) }
|
||||
func (l List) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] }
|
||||
|
||||
func (l List) Less(i, j int) bool {
|
||||
var ok bool
|
||||
var it, jt string
|
||||
li := l.Items[i]
|
||||
lit := li.Text
|
||||
if it, ok = sortableText[lit]; !ok {
|
||||
it = replacer.Replace(strings.ToUpper(lit))
|
||||
it = strings.TrimLeft(it, " ")
|
||||
sortableText[lit] = it
|
||||
}
|
||||
lj := l.Items[j]
|
||||
ljt := lj.Text
|
||||
if jt, ok = sortableText[ljt]; !ok {
|
||||
jt = replacer.Replace(strings.ToUpper(ljt))
|
||||
jt = strings.TrimLeft(jt, " ")
|
||||
sortableText[ljt] = jt
|
||||
}
|
||||
if !li.Checked {
|
||||
if lj.Checked {
|
||||
return true
|
||||
}
|
||||
return it < jt
|
||||
}
|
||||
if !lj.Checked {
|
||||
return false
|
||||
}
|
||||
return it < jt
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
Id string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Text string `json:"text"`
|
||||
Quantity int `json:"quantity"`
|
||||
Checked bool `json:"checked"`
|
||||
Size int64 `json:"size"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
func NewList(name string) List {
|
||||
return List{
|
||||
ID: -1,
|
||||
Name: name,
|
||||
Items: []Item{},
|
||||
}
|
||||
}
|
34
lib/param.go
34
lib/param.go
@ -1,6 +1,40 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type AIParam struct {
|
||||
// 机器人数量
|
||||
RobotCount uint32
|
||||
}
|
||||
type SceneParam struct {
|
||||
Name string
|
||||
Desc string
|
||||
}
|
||||
|
||||
func (a *AIParam) Check() error {
|
||||
var errMsgs []string
|
||||
if a.RobotCount == 0 {
|
||||
errMsgs = append(errMsgs, "机器人数量至少1个")
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("AI 参数校验")
|
||||
if errMsgs != nil {
|
||||
errMsg := strings.Join(errMsgs, " ")
|
||||
buf.WriteString(fmt.Sprintf("未通过 (%s)", errMsg))
|
||||
logrus.Debug(buf.String())
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
|
||||
buf.WriteString(
|
||||
fmt.Sprintf("通过 机器人数量:%v", a.RobotCount))
|
||||
logrus.Debug(buf.String())
|
||||
return nil
|
||||
}
|
||||
|
153
lib/robot.go
153
lib/robot.go
@ -1,6 +1,18 @@
|
||||
package lib
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"legu.airobot/pb"
|
||||
)
|
||||
|
||||
type IRobot interface {
|
||||
// 启动机器人
|
||||
@ -11,25 +23,46 @@ type IRobot interface {
|
||||
GetCurrentScene() *scene
|
||||
}
|
||||
|
||||
type myRobot struct {
|
||||
type Robot struct {
|
||||
IStore
|
||||
scene *scene
|
||||
conn *websocket.Conn
|
||||
data map[string][]byte //caller缓存数据
|
||||
status uint32 //状态
|
||||
lock sync.Mutex //
|
||||
}
|
||||
|
||||
func NewRobot() *myRobot {
|
||||
robot := &myRobot{}
|
||||
func NewRobot() *Robot {
|
||||
robot := &Robot{
|
||||
data: make(map[string][]byte),
|
||||
}
|
||||
|
||||
return robot
|
||||
}
|
||||
|
||||
func (m *myRobot) SelScene(scene *scene) {
|
||||
//存数据
|
||||
func (a *Robot) Store(key string, data []byte) {
|
||||
defer a.lock.Unlock()
|
||||
a.lock.Lock()
|
||||
a.data[key] = data
|
||||
}
|
||||
|
||||
//取数据
|
||||
func (a *Robot) Get(key string) []byte {
|
||||
defer a.lock.Unlock()
|
||||
a.lock.Lock()
|
||||
return a.data[key]
|
||||
}
|
||||
|
||||
func (m *Robot) SelScene(scene *scene) {
|
||||
m.scene = scene
|
||||
}
|
||||
|
||||
func (m *myRobot) GetCurrentScene() *scene {
|
||||
func (m *Robot) GetCurrentScene() *scene {
|
||||
return m.scene
|
||||
}
|
||||
|
||||
func (m *myRobot) Start() bool {
|
||||
func (m *Robot) Start() bool {
|
||||
if m.scene == nil {
|
||||
logrus.Warn("选择一个测试场景")
|
||||
return false
|
||||
@ -40,6 +73,112 @@ func (m *myRobot) Start() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// 创建链接
|
||||
dialer := &websocket.Dialer{
|
||||
HandshakeTimeout: 2 * time.Second,
|
||||
}
|
||||
conn, _, err := dialer.Dial("", nil)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return false
|
||||
}
|
||||
|
||||
m.conn = conn
|
||||
|
||||
//检查具备启动的状态
|
||||
if !atomic.CompareAndSwapUint32(
|
||||
&m.status, STATUS_ORIGINAL, STATUS_STARTING) {
|
||||
if !atomic.CompareAndSwapUint32(&m.status, STATUS_STOPPED, STATUS_STARTING) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
//设置状态为已启动
|
||||
atomic.StoreUint32(&m.status, STATUS_STARTED)
|
||||
|
||||
m.syncCall()
|
||||
|
||||
logrus.Debug("机器人运行了")
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *Robot) syncCall() {
|
||||
for {
|
||||
caller, err := m.scene.callerQueue.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req := caller.BuildReq(m, &pb.UserMessage{})
|
||||
|
||||
m.callOne(&req)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *Robot) callOne(rawReq *RawReq) *RawResp {
|
||||
start := time.Now().UnixNano()
|
||||
rsp, err := m.call(rawReq)
|
||||
end := time.Now().UnixNano()
|
||||
elapsedTime := time.Duration(end - start)
|
||||
|
||||
var rawResp RawResp
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("Call Error: %s.", err)
|
||||
rawResp = RawResp{
|
||||
ID: rawReq.ID,
|
||||
Err: errors.New(errMsg),
|
||||
Elapse: elapsedTime}
|
||||
} else {
|
||||
rawResp = RawResp{
|
||||
ID: rawReq.ID,
|
||||
Resp: rsp,
|
||||
Elapse: elapsedTime}
|
||||
}
|
||||
return &rawResp
|
||||
}
|
||||
|
||||
func (m *Robot) call(rawReq *RawReq) ([]byte, error) {
|
||||
m.conn.WriteMessage(websocket.BinaryMessage, rawReq.Req)
|
||||
|
||||
var (
|
||||
data []byte
|
||||
err error
|
||||
)
|
||||
for {
|
||||
_, data, err = m.conn.ReadMessage()
|
||||
if err != nil {
|
||||
logrus.Errorf("readMessage err:%v", err)
|
||||
return nil, err
|
||||
}
|
||||
if !m.checkPush(data) {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *Robot) checkPush(data []byte) bool {
|
||||
msg := &pb.UserMessage{}
|
||||
if err := proto.Unmarshal(data, msg); err != nil {
|
||||
logrus.Error("结果解析失败")
|
||||
return false
|
||||
}
|
||||
methodStr := msg.Data.TypeUrl
|
||||
methodName := SubStr(methodStr, 20, len(methodStr))
|
||||
|
||||
if strings.HasSuffix(methodName, "Push") {
|
||||
if methodName == "NotifyErrorNotifyPush" {
|
||||
push := &pb.NotifyErrorNotifyPush{}
|
||||
if !ProtoUnmarshal(msg, push) {
|
||||
logrus.Error("unmarsh err")
|
||||
return false
|
||||
}
|
||||
logrus.WithField("methodName", methodName).WithField("code", push.Code).Debug("收到错误码")
|
||||
} else {
|
||||
logrus.WithField("methodName", methodName).Debug("收到推送")
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
27
lib/scene.go
27
lib/scene.go
@ -7,7 +7,8 @@ type scene struct {
|
||||
Name string
|
||||
Desc string
|
||||
callerQueue *Queue[ICaller] //确定运行的caller队列
|
||||
data map[string][]byte //caller缓存数据
|
||||
|
||||
status uint32 //场景状态
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
@ -18,8 +19,10 @@ func NewScene(ai *myAI, param SceneParam) *scene {
|
||||
Name: param.Name,
|
||||
Desc: param.Desc,
|
||||
callerQueue: NewQueue[ICaller](),
|
||||
data: make(map[string][]byte),
|
||||
|
||||
}
|
||||
|
||||
ai.scenes = append(ai.scenes, s)
|
||||
return s
|
||||
}
|
||||
|
||||
@ -41,3 +44,23 @@ func (s *scene) PopCaller() ICaller {
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
// 启用场景
|
||||
func (s *scene) Enable(name string) {
|
||||
for _, v := range s.ai.scenes {
|
||||
if v.Name == name {
|
||||
v.status = STATUS_ENABLE
|
||||
}else{
|
||||
v.status = STATUS_DISENABLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//禁用场景
|
||||
func (s *scene) Disable(name string) {
|
||||
for _, v := range s.ai.scenes {
|
||||
if v.Name == name {
|
||||
v.status = STATUS_DISENABLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
6
lib/store.go
Normal file
6
lib/store.go
Normal file
@ -0,0 +1,6 @@
|
||||
package lib
|
||||
|
||||
type IStore interface {
|
||||
Store(key string, data []byte)
|
||||
Get(key string) []byte
|
||||
}
|
76
lib/tickets.go
Normal file
76
lib/tickets.go
Normal file
@ -0,0 +1,76 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Tickets 表示Goroutine票池的接口。
|
||||
type Tickets interface {
|
||||
// 拿走一张票。
|
||||
Take()
|
||||
// 归还一张票。
|
||||
Return()
|
||||
// 票池是否已被激活。
|
||||
Active() bool
|
||||
// 票的总数。
|
||||
Total() uint32
|
||||
// 剩余的票数。
|
||||
Remainder() uint32
|
||||
}
|
||||
|
||||
// myTickets 表示Goroutine票池的实现。
|
||||
type myTickets struct {
|
||||
total uint32 // 票的总数。
|
||||
ticketCh chan struct{} // 票的容器。
|
||||
active bool // 票池是否已被激活。
|
||||
}
|
||||
|
||||
// NewTickets 会新建一个Goroutine票池。
|
||||
func NewTickets(total uint32) (Tickets, error) {
|
||||
gt := myTickets{}
|
||||
if !gt.init(total) {
|
||||
errMsg :=
|
||||
fmt.Sprintf("The goroutine ticket pool can NOT be initialized! (total=%d)\n", total)
|
||||
return nil, errors.New(errMsg)
|
||||
}
|
||||
return >, nil
|
||||
}
|
||||
|
||||
func (gt *myTickets) init(total uint32) bool {
|
||||
if gt.active {
|
||||
return false
|
||||
}
|
||||
if total == 0 {
|
||||
return false
|
||||
}
|
||||
ch := make(chan struct{}, total)
|
||||
n := int(total)
|
||||
for i := 0; i < n; i++ {
|
||||
ch <- struct{}{}
|
||||
}
|
||||
gt.ticketCh = ch
|
||||
gt.total = total
|
||||
gt.active = true
|
||||
return true
|
||||
}
|
||||
|
||||
func (gt *myTickets) Take() {
|
||||
<-gt.ticketCh
|
||||
}
|
||||
|
||||
func (gt *myTickets) Return() {
|
||||
gt.ticketCh <- struct{}{}
|
||||
}
|
||||
|
||||
func (gt *myTickets) Active() bool {
|
||||
return gt.active
|
||||
}
|
||||
|
||||
func (gt *myTickets) Total() uint32 {
|
||||
return gt.total
|
||||
}
|
||||
|
||||
func (gt *myTickets) Remainder() uint32 {
|
||||
return uint32(len(gt.ticketCh))
|
||||
}
|
32
main.go
32
main.go
@ -2,34 +2,52 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"fyne.io/fyne"
|
||||
"fyne.io/fyne/canvas"
|
||||
"fyne.io/fyne/v2/app"
|
||||
"github.com/sirupsen/logrus"
|
||||
"legu.airobot/busi/friend"
|
||||
"legu.airobot/lib"
|
||||
"legu.airobot/theme"
|
||||
"legu.airobot/ui"
|
||||
)
|
||||
|
||||
var callers []lib.ICaller
|
||||
|
||||
func init() {
|
||||
_ = os.Setenv("FYNE_SCALE", "0.9")
|
||||
var err error
|
||||
|
||||
if err = setupLogger(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
registerCalls(
|
||||
&friend.FriendRecommend{},
|
||||
&friend.FriendApply{},
|
||||
)
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := app.NewWithID("aiRobot")
|
||||
app.SetIcon(theme.ResourceAppPng)
|
||||
|
||||
a := lib.NewAI()
|
||||
a.InitCaller(
|
||||
&friend.FriendRecommend{Desc: "好友推荐"},
|
||||
&friend.FriendApply{Desc: "好友申请"},
|
||||
)
|
||||
appUI, err := ui.NewUI(app, callers)
|
||||
if err != nil {
|
||||
w := fyne.CurrentApp().NewWindow("错误")
|
||||
w.SetContent(canvas.NewText(err.Error(), color.RGBA{255, 0, 0, 255}))
|
||||
w.ShowAndRun()
|
||||
}
|
||||
|
||||
win := ui.NewMainWdindow(appUI)
|
||||
win.CreateWindow(ui.EMPTY_STR, 1499, 800, true)
|
||||
|
||||
appUI.Run()
|
||||
}
|
||||
|
||||
func setupLogger() (err error) {
|
||||
@ -57,3 +75,7 @@ func setupLogger() (err error) {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerCalls(params ...lib.ICaller) {
|
||||
callers = append(callers, params...)
|
||||
}
|
||||
|
1
robot.log
Normal file
1
robot.log
Normal file
@ -0,0 +1 @@
|
||||
{"level":"debug","msg":"配置","time":"2022-12-12 00:39:12","场景":[{"id":"551993000","name":"场景1","desc":"2132","callers":[{"id":"551993000_1670771588149928000","name":"232","key":"1"},{"id":"551993000_1670776426358376000","name":"好友推荐","key":"friend.randlist"}],"status":1},{"id":"376159000","name":"场景2"},{"id":"42611000","name":"场景3"}]}
|
34
storage/config.go
Normal file
34
storage/config.go
Normal file
@ -0,0 +1,34 @@
|
||||
package storage
|
||||
|
||||
//默认配置
|
||||
func newDefaultConfig() *Config {
|
||||
return &Config{}
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Global *Global `json:"global"`
|
||||
Scenes []*Scene `json:"scenes"`
|
||||
}
|
||||
|
||||
type Global struct {
|
||||
UserCount int32 `json:"UserCount,omitempty"` //用户数
|
||||
SId string `json:"sid,omitempty"` //区服ID
|
||||
WsAddr string `json:"wsAddr,omitempty"` //websocket addr
|
||||
IntervalS int32 `json:"intervalS,omitempty"` //间隔时间s
|
||||
TimeoutMs int32 `json:"timeoutMs,omitempty"` //超时时间
|
||||
}
|
||||
|
||||
type Scene struct {
|
||||
ID string `json:"id,omitempty"` //场景ID
|
||||
Name string `json:"name,omitempty"` //场景名称
|
||||
Desc string `json:"desc,omitempty"` //描述
|
||||
Callers []*Caller `json:"callers,omitempty"` //调用器列表
|
||||
Status uint32 `json:"status,omitempty"` //是否启用 默认0未启用 1是启用
|
||||
}
|
||||
|
||||
type Caller struct {
|
||||
ID string `json:"id,omitempty"` //调用器ID
|
||||
Name string `json:"name,omitempty"` //调用器名称
|
||||
Key string `json:"key,omitempty"` //标识
|
||||
Num int `json:"num,omitempty"` //顺序号
|
||||
}
|
29
storage/storage.go
Normal file
29
storage/storage.go
Normal file
@ -0,0 +1,29 @@
|
||||
package storage
|
||||
|
||||
import "path/filepath"
|
||||
|
||||
const (
|
||||
storageRootName = "storage"
|
||||
configFileName = "airobot.json"
|
||||
|
||||
ID = "zhaocy.df"
|
||||
Version = "zhaocy/df/v1"
|
||||
)
|
||||
|
||||
type Storage interface {
|
||||
Root() string
|
||||
ConfigStorage
|
||||
}
|
||||
|
||||
type ConfigStorage interface {
|
||||
LoadConfig() (*Config, error)
|
||||
StoreConfig(s *Config) error
|
||||
}
|
||||
|
||||
func storageRootPath(s Storage) string {
|
||||
return filepath.Join(s.Root(), storageRootName)
|
||||
}
|
||||
|
||||
func configPath(s Storage) string {
|
||||
return filepath.Join(storageRootPath(s), configFileName)
|
||||
}
|
117
storage/storage_os.go
Normal file
117
storage/storage_os.go
Normal file
@ -0,0 +1,117 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var _ Storage = (*OSStorage)(nil)
|
||||
|
||||
type OSStorage struct {
|
||||
root string
|
||||
}
|
||||
|
||||
func NewOSStorage() (Storage, error) {
|
||||
urd, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("不能获取默认的根目录 : %w", err)
|
||||
}
|
||||
return NewOSStorageRooted(urd)
|
||||
}
|
||||
|
||||
func NewOSStorageRooted(root string) (Storage, error) {
|
||||
if !filepath.IsAbs(root) {
|
||||
return nil, fmt.Errorf("root必须是绝对路径:%s", root)
|
||||
}
|
||||
|
||||
storageRoot := filepath.Join(root, ".df")
|
||||
|
||||
s := &OSStorage{root: storageRoot}
|
||||
|
||||
mighted, err := s.migrateDeprecatedRootStorage()
|
||||
if mighted {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("找到不推荐使用的存储,但无法移动到新位置:%w", err)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
err = s.mkdirIfNotExists(storageRootPath(s))
|
||||
return s, err
|
||||
}
|
||||
|
||||
func (s *OSStorage) Root() string {
|
||||
return s.root
|
||||
}
|
||||
|
||||
func (s *OSStorage) LoadConfig() (*Config, error) {
|
||||
configFile := configPath(s)
|
||||
f, err := os.Open(configFile)
|
||||
if os.IsNotExist(err) {
|
||||
return newDefaultConfig(), nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return newDefaultConfig(), fmt.Errorf("没有读到 URI:%w", err)
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
config := &Config{}
|
||||
err = json.NewDecoder(f).Decode(config)
|
||||
if err != nil {
|
||||
return newDefaultConfig(), err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (s *OSStorage) StoreConfig(config *Config) error {
|
||||
configFile := configPath(s)
|
||||
w, err := s.createFile(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer w.Close()
|
||||
|
||||
return json.NewEncoder(w).Encode(config)
|
||||
}
|
||||
|
||||
func (s *OSStorage) createFile(name string) (*os.File, error) {
|
||||
return os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
}
|
||||
|
||||
func (s *OSStorage) isExist(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
func (s *OSStorage) mkdirIfNotExists(dir string) error {
|
||||
if s.isExist(dir) {
|
||||
return nil
|
||||
}
|
||||
return os.MkdirAll(dir, 0700)
|
||||
}
|
||||
|
||||
// 合并弃用的根目录
|
||||
func (s *OSStorage) migrateDeprecatedRootStorage() (bool, error) {
|
||||
oldRoot, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
src := filepath.Join(oldRoot, "fyne", ID, "vvv")
|
||||
_, err = os.Stat(src)
|
||||
if os.IsNotExist(err) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
dest := storageRootPath(s)
|
||||
err = os.Rename(src, dest)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"legu.airobot/busi/friend"
|
||||
@ -8,10 +9,15 @@ import (
|
||||
)
|
||||
|
||||
func TestAction(t *testing.T) {
|
||||
ai := lib.NewAI()
|
||||
aip := lib.AIParam{
|
||||
RobotCount: 1,
|
||||
}
|
||||
ai, _ := lib.NewAI(aip)
|
||||
//注册caller
|
||||
friend_recommend := &friend.FriendRecommend{}
|
||||
friend_recommend.Desc = ""
|
||||
ai.InitCaller(
|
||||
&friend.FriendRecommend{Desc: "好友推荐"},
|
||||
friend_recommend,
|
||||
)
|
||||
|
||||
// 创建场景
|
||||
@ -21,10 +27,10 @@ func TestAction(t *testing.T) {
|
||||
})
|
||||
|
||||
//为场景选择caller
|
||||
scene.AddCaller(&friend.FriendRecommend{Desc: "好友推荐"})
|
||||
scene.AddCaller(&friend.FriendRecommend{})
|
||||
|
||||
//加机器人
|
||||
ai.AddRobots(1, scene)
|
||||
ai.AddRobot(scene)
|
||||
|
||||
//运行机器人
|
||||
for _, v := range ai.GetRobots("场景1") {
|
||||
@ -32,3 +38,20 @@ func TestAction(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestA(t *testing.T) {
|
||||
|
||||
q := lib.NewQueue[int]()
|
||||
q.Add(1)
|
||||
q.Add(2)
|
||||
|
||||
// list := q.List()
|
||||
for {
|
||||
i, err := q.Pop()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(i)
|
||||
}
|
||||
}
|
||||
|
74
ui/common.go
Normal file
74
ui/common.go
Normal file
@ -0,0 +1,74 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
fyne_storage "fyne.io/fyne/v2/storage"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
// 显示Tip
|
||||
func ShowTip(content string) {
|
||||
drv := fyne.CurrentApp().Driver()
|
||||
if drv, ok := drv.(desktop.Driver); ok {
|
||||
w := drv.CreateSplashWindow()
|
||||
w.SetContent(widget.NewLabelWithStyle(content, fyne.TextAlignCenter, fyne.TextStyle{}))
|
||||
w.Show()
|
||||
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * 1500)
|
||||
w.Close()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func ShowCanvasTip(content string) {
|
||||
drv := fyne.CurrentApp().Driver()
|
||||
if drv, ok := drv.(desktop.Driver); ok {
|
||||
w := drv.CreateSplashWindow()
|
||||
w.SetContent(canvas.NewText(content, color.RGBA{255, 0, 0, 255}))
|
||||
w.Show()
|
||||
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * 1500)
|
||||
w.Close()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// 打开目录
|
||||
func OpenFolder(entry *widget.Entry, w fyne.Window) {
|
||||
dConf := dialog.NewFolderOpen(func(lu fyne.ListableURI, err error) {
|
||||
if lu == nil {
|
||||
return
|
||||
}
|
||||
entry.Text = lu.Path()
|
||||
entry.Refresh()
|
||||
}, w)
|
||||
luri, _ := fyne_storage.ListerForURI(fyne_storage.NewFileURI("."))
|
||||
dConf.SetLocation(luri)
|
||||
dConf.SetConfirmText("打开")
|
||||
dConf.SetDismissText("取消")
|
||||
dConf.Resize(fyne.NewSize(750, 500))
|
||||
dConf.Show()
|
||||
}
|
||||
|
||||
func OpenFile(entry *widget.Entry, w fyne.Window) {
|
||||
dConf := dialog.NewFileOpen(func(lu fyne.URIReadCloser, err error) {
|
||||
if lu == nil {
|
||||
return
|
||||
}
|
||||
entry.SetText(lu.URI().Path())
|
||||
entry.Refresh()
|
||||
}, w)
|
||||
dConf.SetConfirmText("打开")
|
||||
dConf.SetDismissText("取消")
|
||||
dConf.SetFilter(fyne_storage.NewExtensionFileFilter([]string{".json"}))
|
||||
dConf.Resize(fyne.NewSize(750, 500))
|
||||
dConf.Show()
|
||||
}
|
9
ui/lang.go
Normal file
9
ui/lang.go
Normal file
@ -0,0 +1,9 @@
|
||||
package ui
|
||||
|
||||
const (
|
||||
APP_NAME = "AI"
|
||||
|
||||
APP_WIN_TITLE = "%s [%s build-%d]"
|
||||
|
||||
EMPTY_STR = ""
|
||||
)
|
70
ui/mainmenu.go
Normal file
70
ui/mainmenu.go
Normal file
@ -0,0 +1,70 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
)
|
||||
|
||||
type mainMenu struct {
|
||||
*fyne.MainMenu
|
||||
sysMenu *fyne.Menu
|
||||
|
||||
startMenu *fyne.MenuItem //开始
|
||||
configMenu *fyne.MenuItem //配置
|
||||
|
||||
sceneMenu *fyne.MenuItem //场景
|
||||
|
||||
quite *fyne.MenuItem
|
||||
|
||||
//工具
|
||||
toolMenu *fyne.Menu
|
||||
importCM *fyne.MenuItem //导入配置
|
||||
exportCM *fyne.MenuItem //导出配置
|
||||
}
|
||||
|
||||
func newMainMenu() *mainMenu {
|
||||
var mm mainMenu
|
||||
|
||||
//退出
|
||||
mm.quite = fyne.NewMenuItem("退出", globalWindow.quiteHandle)
|
||||
mm.quite.Icon = theme.LogoutIcon()
|
||||
mm.quite.IsQuit = true
|
||||
// 开始
|
||||
mm.startMenu = fyne.NewMenuItem("开始", globalWindow.startContainer)
|
||||
mm.startMenu.Icon = theme.FileIcon()
|
||||
// 配置
|
||||
mm.configMenu = fyne.NewMenuItem("配置", globalWindow.configContainer)
|
||||
mm.configMenu.Icon = theme.FileTextIcon()
|
||||
//场景
|
||||
mm.sceneMenu = fyne.NewMenuItem("场景", globalWindow.sceneContainer)
|
||||
mm.sceneMenu.Icon = theme.FileTextIcon()
|
||||
|
||||
// AI
|
||||
mm.sysMenu = fyne.NewMenu("AI")
|
||||
mm.sysMenu.Items = append(mm.sysMenu.Items,
|
||||
mm.startMenu,
|
||||
mm.configMenu,
|
||||
mm.sceneMenu,
|
||||
fyne.NewMenuItemSeparator(),
|
||||
mm.quite)
|
||||
|
||||
// 导入配置
|
||||
mm.importCM = fyne.NewMenuItem("导入配置", globalWindow.ImportConfigWindow)
|
||||
mm.importCM.Icon = theme.FileApplicationIcon()
|
||||
|
||||
mm.exportCM = fyne.NewMenuItem("导出配置", globalWindow.ExportConfigWindow)
|
||||
mm.exportCM.Icon = theme.FileApplicationIcon()
|
||||
|
||||
// 工具
|
||||
mm.toolMenu = fyne.NewMenu("工具")
|
||||
mm.toolMenu.Items = append(mm.toolMenu.Items,
|
||||
mm.importCM,
|
||||
mm.exportCM,
|
||||
)
|
||||
|
||||
mm.MainMenu = fyne.NewMainMenu(
|
||||
mm.sysMenu,
|
||||
mm.toolMenu,
|
||||
)
|
||||
return &mm
|
||||
}
|
637
ui/mainwindow.go
Normal file
637
ui/mainwindow.go
Normal file
@ -0,0 +1,637 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cast"
|
||||
"legu.airobot/lib"
|
||||
"legu.airobot/storage"
|
||||
)
|
||||
|
||||
var globalWindow *MainWindow
|
||||
|
||||
var _ IWindow = (*MainWindow)(nil)
|
||||
|
||||
type MainWindow struct {
|
||||
UIImpl
|
||||
w fyne.Window
|
||||
statusbar *statusBar // 状态栏
|
||||
mainMenu *mainMenu //菜单
|
||||
|
||||
}
|
||||
|
||||
func NewMainWdindow(ui *UIImpl) IWindow {
|
||||
mw := &MainWindow{
|
||||
UIImpl: *ui,
|
||||
}
|
||||
globalWindow = mw
|
||||
return mw
|
||||
}
|
||||
|
||||
func (mw *MainWindow) CreateWindow(_ string, width, height float32, visible bool) {
|
||||
title := fmt.Sprintf(APP_WIN_TITLE, APP_NAME, mw.app.Metadata().Version, mw.app.Metadata().Build)
|
||||
w := mw.app.NewWindow(title)
|
||||
mw.w = w
|
||||
|
||||
// 状态栏
|
||||
mw.statusbar = newStatusBar()
|
||||
|
||||
// 菜单
|
||||
mw.mainMenu = newMainMenu()
|
||||
mw.w.SetMainMenu(mw.mainMenu.MainMenu)
|
||||
|
||||
//content
|
||||
content := container.NewBorder(nil, mw.statusbar.widget, nil, nil)
|
||||
mw.w.SetContent(content)
|
||||
|
||||
// 默认
|
||||
mw.startContainer()
|
||||
|
||||
w.Resize(fyne.NewSize(width, height))
|
||||
w.SetMaster()
|
||||
w.CenterOnScreen()
|
||||
w.SetCloseIntercept(mw.quiteHandle)
|
||||
|
||||
w.Show()
|
||||
}
|
||||
|
||||
func (mw *MainWindow) changeContent(content fyne.CanvasObject) {
|
||||
mw.w.SetContent(content)
|
||||
}
|
||||
|
||||
// 开始
|
||||
func (mw *MainWindow) startContainer() {
|
||||
startBtn := widget.NewButton("启动", nil)
|
||||
startBtn.Disable()
|
||||
config := mw.config
|
||||
// 检查全局配置
|
||||
if config.Global == nil {
|
||||
dialog.ShowCustomConfirm("提示", "设置", "取消", widget.NewLabel("还没有任何配置"), func(b bool) {
|
||||
if !b {
|
||||
return
|
||||
}
|
||||
mw.configContainer()
|
||||
}, mw.w)
|
||||
return
|
||||
}
|
||||
// 检查场景配置
|
||||
if config.Scenes == nil {
|
||||
dialog.ShowCustomConfirm("提示", "创建", "取消", widget.NewLabel("还没有创建任何场景"), func(b bool) {
|
||||
if !b {
|
||||
return
|
||||
}
|
||||
mw.sceneContainer()
|
||||
}, mw.w)
|
||||
return
|
||||
}
|
||||
// 检查场景是否启用
|
||||
isEnable := false
|
||||
for _, v := range config.Scenes {
|
||||
if v.Status == lib.STATUS_ENABLE {
|
||||
isEnable = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isEnable {
|
||||
dialog.ShowCustomConfirm("提示", "启用", "取消", widget.NewLabel("还没有启用任意场景"), func(b bool) {
|
||||
if !b {
|
||||
return
|
||||
}
|
||||
mw.sceneContainer()
|
||||
}, mw.w)
|
||||
return
|
||||
}
|
||||
startBtn.Enable()
|
||||
startBtn.OnTapped = func() {
|
||||
ai, err := lib.NewAI(lib.AIParam{RobotCount: uint32(config.Global.UserCount)})
|
||||
if err != nil {
|
||||
dialog.ShowError(err, mw.w)
|
||||
return
|
||||
}
|
||||
ai.InitCaller(mw.callers...)
|
||||
}
|
||||
content := container.NewCenter(startBtn)
|
||||
mw.changeContent(content)
|
||||
}
|
||||
|
||||
// 全局配置
|
||||
func (mw *MainWindow) configContainer() {
|
||||
config := mw.config.Global
|
||||
wsAddrEntry := widget.NewEntry()
|
||||
sidEntry := widget.NewEntry()
|
||||
userCountEntry := widget.NewEntry()
|
||||
timeoutEntry := widget.NewEntry()
|
||||
intervalEntry := widget.NewEntry()
|
||||
|
||||
form := widget.NewForm(
|
||||
widget.NewFormItem("服务地址", wsAddrEntry),
|
||||
widget.NewFormItem("区服编号", sidEntry),
|
||||
widget.NewFormItem("用户数", userCountEntry),
|
||||
widget.NewFormItem("间隔时间(s)", intervalEntry),
|
||||
widget.NewFormItem("超时时间(ms)", timeoutEntry),
|
||||
)
|
||||
|
||||
loadForm := func() {
|
||||
wsAddrEntry.Text = config.WsAddr
|
||||
sidEntry.Text = config.SId
|
||||
userCountEntry.Text = cast.ToString(config.UserCount)
|
||||
timeoutEntry.Text = cast.ToString(config.TimeoutMs)
|
||||
intervalEntry.Text = cast.ToString(config.IntervalS)
|
||||
}
|
||||
|
||||
if config != nil {
|
||||
loadForm()
|
||||
}
|
||||
|
||||
saveBtn := widget.NewButtonWithIcon("保存", theme.DocumentSaveIcon(), nil)
|
||||
saveBtn.OnTapped = func() {
|
||||
global := &storage.Global{
|
||||
WsAddr: wsAddrEntry.Text,
|
||||
SId: sidEntry.Text,
|
||||
UserCount: cast.ToInt32(userCountEntry.Text),
|
||||
TimeoutMs: cast.ToInt32(timeoutEntry.Text),
|
||||
IntervalS: cast.ToInt32(intervalEntry.Text),
|
||||
}
|
||||
mw.config.Global = global
|
||||
if err := mw.storage.StoreConfig(mw.config); err != nil {
|
||||
ShowTip(fmt.Sprintf("错误:%v", err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
btns := container.NewHBox(layout.NewSpacer(), saveBtn)
|
||||
|
||||
content := container.NewVBox(form, btns)
|
||||
mw.changeContent(content)
|
||||
}
|
||||
|
||||
//场景
|
||||
func (mw *MainWindow) sceneContainer() {
|
||||
var selSceneName string
|
||||
// 用例列表
|
||||
callerItemList := *lib.NewItemList()
|
||||
callerItemList.ListWidget = callerItemList.CreateList()
|
||||
callerLoad := func(sceneId string) {
|
||||
callerItemList.Reset()
|
||||
for _, v := range mw.config.Scenes {
|
||||
if v.ID == sceneId {
|
||||
sort.SliceStable(v.Callers, func(i, j int) bool {
|
||||
return v.Callers[i].Num < v.Callers[j].Num
|
||||
})
|
||||
for _, v2 := range v.Callers {
|
||||
item := lib.Item{
|
||||
Id: cast.ToString(v2.ID),
|
||||
Text: fmt.Sprintf("%d. %v %v", v2.Num, v2.Name, v2.Key),
|
||||
}
|
||||
callerItemList.AddItem(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//场景列表
|
||||
sceneItemList := *lib.NewItemList()
|
||||
sceneItemList.OnSelFunc = func(id widget.ListItemID) {
|
||||
selId := sceneItemList.CachedList.Items[id].Id
|
||||
selSceneName = sceneItemList.CachedList.Items[id].Text
|
||||
sceneItemList.SelItemId = selId
|
||||
callerLoad(selId)
|
||||
}
|
||||
sceneItemList.ListWidget = sceneItemList.CreateList()
|
||||
sceneLoad := func() {
|
||||
sceneItemList.Reset()
|
||||
logrus.WithField("场景", mw.config.Scenes).Debug("配置")
|
||||
for _, v := range mw.config.Scenes {
|
||||
txt := fmt.Sprintf("%v", v.Name)
|
||||
if v.Status == lib.STATUS_ENABLE {
|
||||
txt = fmt.Sprintf("%s *", v.Name)
|
||||
}
|
||||
item := lib.Item{
|
||||
Id: cast.ToString(v.ID),
|
||||
Text: txt,
|
||||
}
|
||||
sceneItemList.AddItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
// 场景表单
|
||||
sceneNameEntry := widget.NewEntry()
|
||||
sceneDescEntry := widget.NewEntry()
|
||||
|
||||
sceneItems := []*widget.FormItem{
|
||||
widget.NewFormItem("名称", sceneNameEntry),
|
||||
widget.NewFormItem("描述", sceneDescEntry),
|
||||
}
|
||||
|
||||
//重置场景表单
|
||||
sceneFormReset := func() {
|
||||
sceneNameEntry.Text = ""
|
||||
sceneDescEntry.Text = ""
|
||||
}
|
||||
|
||||
// 用例表单
|
||||
callerNameEntry := widget.NewEntry()
|
||||
callerKeyEntry := widget.NewEntry()
|
||||
callerNumEntry := widget.NewEntry()
|
||||
callerNumEntry.PlaceHolder = "0"
|
||||
|
||||
callerItems := []*widget.FormItem{
|
||||
widget.NewFormItem("名称", callerNameEntry),
|
||||
widget.NewFormItem("协议", callerKeyEntry),
|
||||
widget.NewFormItem("序号", callerNumEntry),
|
||||
}
|
||||
//重置用例表单
|
||||
callerFormReset := func() {
|
||||
callerNameEntry.Text = ""
|
||||
callerKeyEntry.Text = ""
|
||||
}
|
||||
|
||||
// 场景按钮
|
||||
newSceneBtn := widget.NewButtonWithIcon("新建场景", theme.FileIcon(), nil)
|
||||
editSceneBtn := widget.NewButtonWithIcon("编辑场景", theme.DocumentCreateIcon(), nil)
|
||||
deleSceneBtn := widget.NewButtonWithIcon("删除场景", theme.DeleteIcon(), nil)
|
||||
enableSceneBtn := widget.NewButton("启用场景", nil)
|
||||
|
||||
// 添加用例按钮
|
||||
newCallerBtn := widget.NewButtonWithIcon("添加用例", theme.FileIcon(), nil)
|
||||
deleCallerBtn := widget.NewButtonWithIcon("删除用例", theme.DocumentCreateIcon(), nil)
|
||||
|
||||
// 刷新场景
|
||||
refreshBtn := widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), nil)
|
||||
|
||||
// 工具条
|
||||
toolbar := container.NewHBox(newSceneBtn, editSceneBtn, deleSceneBtn, enableSceneBtn, widget.NewSeparator(),
|
||||
newCallerBtn, deleCallerBtn, layout.NewSpacer(), refreshBtn)
|
||||
|
||||
//布局
|
||||
var dynamic fyne.CanvasObject
|
||||
contentRender := func() {
|
||||
sceneLoad()
|
||||
if len(sceneItemList.CachedList.Items) == 0 {
|
||||
dynamic = container.NewCenter(widget.NewLabel("还没有创建任何场景"))
|
||||
} else {
|
||||
split := container.NewHSplit(sceneItemList.ListWidget, callerItemList.ListWidget)
|
||||
split.Offset = 0.3
|
||||
dynamic = split
|
||||
}
|
||||
content := container.NewBorder(toolbar, nil, nil, nil, dynamic)
|
||||
mw.changeContent(content)
|
||||
}
|
||||
contentRender()
|
||||
|
||||
// 刷新场景
|
||||
refreshBtn.OnTapped = func() {
|
||||
sceneItemList.Reset()
|
||||
callerItemList.Reset()
|
||||
contentRender()
|
||||
}
|
||||
|
||||
//新建场景事件
|
||||
newSceneBtn.OnTapped = func() {
|
||||
newSceneWin := dialog.NewForm("新建场景", "确定", "取消", sceneItems, func(b bool) {
|
||||
if !b {
|
||||
return
|
||||
}
|
||||
|
||||
scenes_conf := mw.config.Scenes
|
||||
scenes_conf = append(scenes_conf, &storage.Scene{
|
||||
ID: cast.ToString(time.Now().Nanosecond()),
|
||||
Name: sceneNameEntry.Text,
|
||||
Desc: sceneDescEntry.Text,
|
||||
})
|
||||
mw.config.Scenes = scenes_conf
|
||||
if err := mw.storage.StoreConfig(mw.config); err != nil {
|
||||
ShowTip(fmt.Sprintf("错误:%v", err.Error()))
|
||||
return
|
||||
}
|
||||
sceneFormReset()
|
||||
contentRender()
|
||||
}, mw.w)
|
||||
newSceneWin.Resize(fyne.NewSize(300, 200))
|
||||
newSceneWin.Show()
|
||||
}
|
||||
|
||||
// 编辑场景事件
|
||||
editSceneBtn.OnTapped = func() {
|
||||
selId := sceneItemList.SelItemId
|
||||
if selId == "" {
|
||||
ShowTip("请选择一个场景")
|
||||
return
|
||||
}
|
||||
// 加载
|
||||
var sceneConf *storage.Scene
|
||||
for _, item := range mw.config.Scenes {
|
||||
if item.ID == selId {
|
||||
sceneConf = item
|
||||
}
|
||||
}
|
||||
if sceneConf != nil {
|
||||
sceneNameEntry.Text = sceneConf.Name
|
||||
sceneDescEntry.Text = sceneConf.Desc
|
||||
}
|
||||
|
||||
editSceneWin := dialog.NewForm("编辑场景", "确定", "取消", sceneItems, func(b bool) {
|
||||
if !b {
|
||||
return
|
||||
}
|
||||
for _, item := range mw.config.Scenes {
|
||||
if item.ID == selId {
|
||||
item.Name = sceneNameEntry.Text
|
||||
item.Desc = sceneDescEntry.Text
|
||||
}
|
||||
}
|
||||
|
||||
if err := mw.storage.StoreConfig(mw.config); err != nil {
|
||||
ShowTip(fmt.Sprintf("错误:%v", err.Error()))
|
||||
return
|
||||
}
|
||||
sceneFormReset()
|
||||
// contentRender()
|
||||
}, mw.w)
|
||||
editSceneWin.Resize(fyne.NewSize(300, 200))
|
||||
editSceneWin.Show()
|
||||
}
|
||||
|
||||
// 删除场景事件
|
||||
deleSceneBtn.OnTapped = func() {
|
||||
selId := sceneItemList.SelectedId()
|
||||
if selId == "" {
|
||||
ShowTip("请选择一个场景")
|
||||
return
|
||||
}
|
||||
scenes := mw.config.Scenes
|
||||
|
||||
for i, item := range scenes {
|
||||
if item.ID == selId {
|
||||
if len(item.Callers) > 0 {
|
||||
dialog.ShowCustomConfirm("提示", "确定", "取消", widget.NewLabel("该场景下存在用例,确定删除场景【"+item.Name+"】吗?"), func(b bool) {
|
||||
if !b {
|
||||
return
|
||||
}
|
||||
|
||||
for y, v := range scenes {
|
||||
if v.ID == selId {
|
||||
scenes = append(scenes[:y], scenes[y+1:]...)
|
||||
}
|
||||
}
|
||||
mw.config.Scenes = scenes
|
||||
callerItemList.Reset()
|
||||
contentRender()
|
||||
if err := mw.storage.StoreConfig(mw.config); err != nil {
|
||||
ShowTip(fmt.Sprintf("错误:%v", err.Error()))
|
||||
return
|
||||
}
|
||||
}, mw.w)
|
||||
} else {
|
||||
scenes = append(scenes[:i], scenes[i+1:]...)
|
||||
mw.config.Scenes = scenes
|
||||
callerItemList.Reset()
|
||||
contentRender()
|
||||
}
|
||||
if err := mw.storage.StoreConfig(mw.config); err != nil {
|
||||
ShowTip(fmt.Sprintf("错误:%v", err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 启用场景
|
||||
enableSceneBtn.OnTapped = func() {
|
||||
selId := sceneItemList.SelectedId()
|
||||
if selId == "" {
|
||||
ShowTip("请选择一个场景")
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range mw.config.Scenes {
|
||||
if item.ID == selId {
|
||||
if len(item.Callers) == 0 {
|
||||
ShowTip("请至少添加一个用例")
|
||||
return
|
||||
}
|
||||
item.Status = lib.STATUS_ENABLE
|
||||
} else {
|
||||
item.Status = lib.STATUS_DISENABLE
|
||||
}
|
||||
}
|
||||
|
||||
if err := mw.storage.StoreConfig(mw.config); err != nil {
|
||||
ShowTip(fmt.Sprintf("错误:%v", err.Error()))
|
||||
return
|
||||
}
|
||||
sceneItemList.Reset()
|
||||
}
|
||||
|
||||
//添加用例事件
|
||||
newCallerBtn.OnTapped = func() {
|
||||
selId := sceneItemList.SelectedId()
|
||||
if selId == "" {
|
||||
ShowTip("请选择一个场景")
|
||||
return
|
||||
}
|
||||
//form
|
||||
callerForm := widget.NewForm(callerItems...)
|
||||
callerNameEntry.Disable()
|
||||
callerKeyEntry.Disable()
|
||||
callerForm.OnSubmit = func() {
|
||||
scenes_conf := mw.config.Scenes
|
||||
for _, v := range scenes_conf {
|
||||
if selId == v.ID {
|
||||
v.Callers = append(v.Callers, &storage.Caller{
|
||||
ID: fmt.Sprintf("%s_%d", v.ID, time.Now().UnixNano()),
|
||||
Name: callerNameEntry.Text,
|
||||
Key: callerKeyEntry.Text,
|
||||
Num: cast.ToInt(callerNumEntry.Text),
|
||||
})
|
||||
}
|
||||
}
|
||||
mw.config.Scenes = scenes_conf
|
||||
if err := mw.storage.StoreConfig(mw.config); err != nil {
|
||||
ShowTip(fmt.Sprintf("错误:%v", err.Error()))
|
||||
return
|
||||
}
|
||||
callerFormReset()
|
||||
callerLoad(selId)
|
||||
}
|
||||
callerForm.SubmitText = "确定"
|
||||
|
||||
//list
|
||||
registerCallerList := *lib.NewItemList()
|
||||
registerCallerList.OnSelFunc = func(id widget.ListItemID) {
|
||||
data := registerCallerList.CachedList.Items[id].Data
|
||||
info, ok := data.(lib.CallerInfo)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
callerNameEntry.Text = info.Desc
|
||||
callerKeyEntry.Text = info.Key
|
||||
callerForm.Refresh()
|
||||
}
|
||||
registerCallerList.ListWidget = registerCallerList.CreateList()
|
||||
registerCallerReload := func() {
|
||||
for _, v := range mw.UIImpl.callers {
|
||||
info := v.ShowInfo()
|
||||
item := lib.Item{
|
||||
Id: info.Key,
|
||||
Text: fmt.Sprintf("%s (%s)", info.Desc, info.Key),
|
||||
Data: info,
|
||||
}
|
||||
registerCallerList.AddItem(item)
|
||||
}
|
||||
registerCallerList.SearchItem = registerCallerList.CachedList.Items
|
||||
}
|
||||
|
||||
registerCallerReload()
|
||||
|
||||
//search
|
||||
searchEntry := widget.NewEntry()
|
||||
searchEntry.PlaceHolder = "输入要搜索的用例"
|
||||
searchEntry.OnChanged = func(s string) {
|
||||
if s == "" {
|
||||
registerCallerReload()
|
||||
} else {
|
||||
newList := []lib.Item{}
|
||||
for _, v := range registerCallerList.SearchItem {
|
||||
if strings.Contains(v.Text, s) {
|
||||
newList = append(newList, v)
|
||||
}
|
||||
}
|
||||
registerCallerList.CachedList.Items = newList
|
||||
registerCallerList.ListWidget.Refresh()
|
||||
}
|
||||
}
|
||||
|
||||
// layout
|
||||
contentSplit := container.NewHSplit(
|
||||
container.NewBorder(container.NewBorder(nil, nil, nil, nil, searchEntry),
|
||||
nil, nil, nil, registerCallerList.ListWidget),
|
||||
callerForm,
|
||||
)
|
||||
contentSplit.Offset = 0.5
|
||||
|
||||
title := fmt.Sprintf("新建用例 - %s", selSceneName)
|
||||
newCallerWin := dialog.NewCustom(title, "关闭", contentSplit, mw.w)
|
||||
newCallerWin.Resize(fyne.NewSize(800, 600))
|
||||
newCallerWin.Show()
|
||||
}
|
||||
|
||||
// 删除用例事件
|
||||
deleCallerBtn.OnTapped = func() {
|
||||
selSceneId := sceneItemList.SelectedId()
|
||||
if selSceneId == "" {
|
||||
ShowTip("请选择一个场景")
|
||||
return
|
||||
}
|
||||
|
||||
selCallerId := callerItemList.SelectedId()
|
||||
if selCallerId == "" {
|
||||
ShowTip("请选择一个用例")
|
||||
return
|
||||
}
|
||||
|
||||
scenes := mw.config.Scenes
|
||||
for _, item := range scenes {
|
||||
if item.ID == selSceneId {
|
||||
for y, c := range item.Callers {
|
||||
if c.ID == selCallerId {
|
||||
item.Callers = append(item.Callers[:y], item.Callers[y+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mw.config.Scenes = scenes
|
||||
if err := mw.storage.StoreConfig(mw.config); err != nil {
|
||||
ShowTip(fmt.Sprintf("错误:%v", err.Error()))
|
||||
return
|
||||
}
|
||||
callerLoad(selSceneId)
|
||||
}
|
||||
}
|
||||
|
||||
// 导出配置
|
||||
func (mw *MainWindow) ExportConfigWindow() {
|
||||
dirEntry := widget.NewEntry()
|
||||
exportItems := []*widget.FormItem{
|
||||
widget.NewFormItem("保存目录", container.NewBorder(nil, nil, nil, widget.NewButtonWithIcon("", theme.FolderIcon(), func() {
|
||||
OpenFolder(dirEntry, mw.w)
|
||||
}), dirEntry)),
|
||||
}
|
||||
exportWin := dialog.NewForm("导出配置", "确定", "取消", exportItems, func(b bool) {
|
||||
if !b {
|
||||
return
|
||||
}
|
||||
|
||||
dirpath := dirEntry.Text
|
||||
logrus.WithField("目录位置", dirpath).Debug("保存目录")
|
||||
|
||||
jsonByte, err := json.Marshal(mw.config)
|
||||
if err != nil {
|
||||
logrus.Errorln(err)
|
||||
return
|
||||
}
|
||||
if err := ioutil.WriteFile(filepath.Join(dirpath, "config.json"), jsonByte, fs.ModePerm); err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
}, mw.w)
|
||||
exportWin.Resize(fyne.NewSize(300, 80))
|
||||
exportWin.Show()
|
||||
}
|
||||
|
||||
// 导入配置
|
||||
func (mw *MainWindow) ImportConfigWindow() {
|
||||
filepathEntry := widget.NewEntry()
|
||||
importItems := []*widget.FormItem{
|
||||
widget.NewFormItem("配置文件", container.NewBorder(nil, nil, nil, widget.NewButtonWithIcon("", theme.FileIcon(), func() {
|
||||
OpenFile(filepathEntry, mw.w)
|
||||
}), filepathEntry)),
|
||||
}
|
||||
importWin := dialog.NewForm("导入配置", "确定", "取消", importItems, func(b bool) {
|
||||
if !b {
|
||||
return
|
||||
}
|
||||
|
||||
filepath := filepathEntry.Text
|
||||
logrus.WithField("文件路径", filepath).Debug("导入文件")
|
||||
byte, err := ioutil.ReadFile(filepath)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
if err := json.Unmarshal(byte, mw.config); err != nil {
|
||||
logrus.Error(err)
|
||||
return
|
||||
}
|
||||
if err := mw.storage.StoreConfig(mw.config); err != nil {
|
||||
ShowTip(fmt.Sprintf("错误:%v", err.Error()))
|
||||
return
|
||||
}
|
||||
}, mw.w)
|
||||
importWin.Resize(fyne.NewSize(300, 80))
|
||||
importWin.Show()
|
||||
}
|
||||
|
||||
// 退出
|
||||
func (mw *MainWindow) quiteHandle() {
|
||||
dialog.ShowCustomConfirm("提示", "确定", "取消", widget.NewLabel("确定退出吗"), func(b bool) {
|
||||
if !b {
|
||||
return
|
||||
}
|
||||
mw.app.Quit()
|
||||
}, mw.w)
|
||||
}
|
26
ui/status_bar.go
Normal file
26
ui/status_bar.go
Normal file
@ -0,0 +1,26 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type statusBar struct {
|
||||
msgLabel *widget.Label
|
||||
widget *fyne.Container
|
||||
}
|
||||
|
||||
func newStatusBar() *statusBar {
|
||||
var sb statusBar
|
||||
sb.msgLabel = widget.NewLabel("")
|
||||
sb.widget = container.New(layout.NewHBoxLayout(),
|
||||
sb.msgLabel,
|
||||
layout.NewSpacer())
|
||||
return &sb
|
||||
}
|
||||
|
||||
func (sb *statusBar) setMessage(msg string) {
|
||||
sb.msgLabel.SetText(msg)
|
||||
}
|
55
ui/ui.go
Normal file
55
ui/ui.go
Normal file
@ -0,0 +1,55 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"legu.airobot/lib"
|
||||
"legu.airobot/storage"
|
||||
"legu.airobot/theme"
|
||||
)
|
||||
|
||||
type UIImpl struct {
|
||||
app fyne.App
|
||||
storage storage.Storage
|
||||
config *storage.Config
|
||||
callers []lib.ICaller
|
||||
}
|
||||
|
||||
func NewUI(app fyne.App, callers []lib.ICaller) (*UIImpl, error) {
|
||||
app.Settings().SetTheme(&theme.MyTheme{})
|
||||
|
||||
storage, err := storage.NewOSStorage()
|
||||
if err != nil {
|
||||
logrus.Errorf("new storage err:%v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 加载配置
|
||||
config, err := storage.LoadConfig()
|
||||
if err != nil {
|
||||
logrus.Errorf("Load config err:%v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &UIImpl{
|
||||
app: app,
|
||||
storage: storage,
|
||||
config: config,
|
||||
callers: callers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ui *UIImpl) Run() {
|
||||
defer func() {
|
||||
_ = os.Unsetenv("FYNE_SCALE")
|
||||
}()
|
||||
|
||||
ui.app.Run()
|
||||
}
|
||||
|
||||
func (ui *UIImpl) Stop() {
|
||||
ui.app.Quit()
|
||||
}
|
5
ui/windowinterface.go
Normal file
5
ui/windowinterface.go
Normal file
@ -0,0 +1,5 @@
|
||||
package ui
|
||||
|
||||
type IWindow interface {
|
||||
CreateWindow(title string, width, height float32, visible bool)
|
||||
}
|
Loading…
Reference in New Issue
Block a user