diff --git a/admin/admin.go b/admin/admin.go index df4da4e..e3a932a 100644 --- a/admin/admin.go +++ b/admin/admin.go @@ -48,18 +48,16 @@ func apiGetTableData(next http.Handler, udb auth.UserStore, dbAdapter mongodb.Db if dbAdapter.CheckTable(tableKey) { mapUrl, _ := dbAdapter.GetMapImageUrl(tableKey) auxMessage, _ := dbAdapter.GetAuxMessage(tableKey) - availableTokens, _ := dbAdapter.GetTokens(tableKey, true) - activeTokens, _ := dbAdapter.GetTokens(tableKey, false) + tokens, _ := dbAdapter.GetTokens(tableKey, true) diceRolls, _ := dbAdapter.GetDiceRolls(tableKey) AddContextValue(req, "tableData", models.Table{ - Name: tableKey.Name, - Passcode: tableKey.Passcode, - DiceRolls: diceRolls, - MapImageUrl: mapUrl, - Tokens: activeTokens, - AvailableTokens: availableTokens, - AuxMessage: auxMessage, + Name: tableKey.Name, + Passcode: tableKey.Passcode, + DiceRolls: diceRolls, + MapImageUrl: mapUrl, + Tokens: tokens, + AuxMessage: auxMessage, }) } else { w.WriteHeader(404) diff --git a/gametable/server.go b/gametable/server.go index fc6f7d3..e103083 100644 --- a/gametable/server.go +++ b/gametable/server.go @@ -192,18 +192,16 @@ func (self *GameTableServer) getCurrentState(tableKey models.TableKey) []byte { if self.dbAdapter.CheckTable(tableKey) { mapUrl, _ := self.dbAdapter.GetMapImageUrl(tableKey) auxMessage, _ := self.dbAdapter.GetAuxMessage(tableKey) - availableTokens, _ := self.dbAdapter.GetTokens(tableKey, true) - activeTokens, _ := self.dbAdapter.GetTokens(tableKey, false) + tokens, _ := self.dbAdapter.GetTokens(tableKey, false) diceRolls, _ := self.dbAdapter.GetDiceRolls(tableKey) table := models.Table{ - Name: tableKey.Name, - Passcode: tableKey.Passcode, - DiceRolls: diceRolls, - MapImageUrl: mapUrl, - Tokens: activeTokens, - AvailableTokens: availableTokens, - AuxMessage: auxMessage, + Name: tableKey.Name, + Passcode: tableKey.Passcode, + DiceRolls: diceRolls, + MapImageUrl: mapUrl, + Tokens: tokens, + AuxMessage: auxMessage, } data, err := json.Marshal(table) if err != nil { @@ -223,8 +221,41 @@ func (self *GameTableServer) writeToDB(tableMsg *models.TableMessage) error { return err } } + if tableMsg.Token != nil && tableMsg.Token.Id != nil { + t := *tableMsg.Token + exists, active := self.dbAdapter.CheckToken(key, *t.Id) + if exists { + if active { + if !t.Active { + err := self.dbAdapter.ActivateToken(key, *t.Id, false) + if err != nil { + return err + } + tableMsg.Token.X = nil + tableMsg.Token.Y = nil + } else if t.X != nil && t.Y != nil { + err := self.dbAdapter.MoveToken(key, t) + if err != nil { + return err + } + } + } else { + if t.Active { + err := self.dbAdapter.ActivateToken(key, *t.Id, true) + if err != nil { + return err + } + } + } + } else { + // respond to nonextant IDs as if they were destroyed + tableMsg.Token.X = nil + tableMsg.Token.Y = nil + tableMsg.Token.Active = false + } + } - // map image change, aux message, and token addition/removal require admin authorization + // map image change, aux message, and token creation/deletion require admin authorization if tableMsg.Auth != nil { authorized, _ := self.udb.ValidateToken(*tableMsg.Auth) if authorized { @@ -240,6 +271,22 @@ func (self *GameTableServer) writeToDB(tableMsg *models.TableMessage) error { return err } } + if tableMsg.Token != nil { + t := *tableMsg.Token + if t.Id == nil { + id, err := self.dbAdapter.CreateToken(key, t) + if err == nil { + *tableMsg.Token.Id = id + } else { + return err + } + } else { + if t.X == nil && t.Y == nil && !t.Active { + err := self.dbAdapter.DestroyToken(key, *t.Id) + return err + } + } + } } } return nil diff --git a/go.mod b/go.mod index 639631f..090e916 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module hacklab.nilfm.cc/felt go 1.19 require ( - go.mongodb.org/mongo-driver v1.11.0 + go.mongodb.org/mongo-driver v1.12.0 golang.org/x/time v0.1.0 hacklab.nilfm.cc/quartzgun v0.3.0 nhooyr.io/websocket v1.8.7 @@ -15,10 +15,10 @@ require ( github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/pkg/errors v0.9.1 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect - github.com/xdg-go/scram v1.1.1 // indirect - github.com/xdg-go/stringprep v1.0.3 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect + golang.org/x/text v0.7.0 // indirect ) diff --git a/go.sum b/go.sum index 747865f..d00eba3 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -68,31 +69,60 @@ github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs= github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.11.0 h1:FZKhBSTydeuffHj9CBjXlR8vQLee1cQyTWYPA6/tqiE= go.mongodb.org/mongo-driver v1.11.0/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= +go.mongodb.org/mongo-driver v1.12.0 h1:aPx33jmn/rQuJXPQLZQ8NtfPQG8CaqgLThFtqRb0PiE= +go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/models/models.go b/models/models.go index 83dd635..7f6c37f 100644 --- a/models/models.go +++ b/models/models.go @@ -18,21 +18,25 @@ type DiceRoll struct { } type Token struct { - Id string `json:"id"` - Name string `json:"name"` - SpriteUri string `json:"spriteUrl"` - X *int `json:"x"` - Y *int `json:"y"` + Id *string `json:"id" bson:"_id"` + Name string `json:"name"` + Sprite string `json:"sprite"` + W int `json:"w"` + H int `json:"h"` + OX int `json:"oX"` + OY int `json:"oY"` + X *int `json:"x"` + Y *int `json:"y"` + Active bool `json:"active"` } type Table struct { - Name string `json:"name"` - Passcode string `json:"passcode"` - MapImageUrl string `json:"mapImg"` - DiceRolls []DiceRoll `json:"diceRolls"` - Tokens []Token `json:"tokens"` - AvailableTokens []Token `json:"availableTokens"` - AuxMessage string `json:"auxMsg"` + Name string `json:"name"` + Passcode string `json:"passcode"` + MapImageUrl string `json:"mapImg"` + DiceRolls []DiceRoll `json:"diceRolls"` + Tokens []Token `json:"tokens"` + AuxMessage string `json:"auxMsg"` } type TableMessage struct { diff --git a/mongodb/adapter.go b/mongodb/adapter.go index 2aa90a5..efe77c6 100644 --- a/mongodb/adapter.go +++ b/mongodb/adapter.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "hacklab.nilfm.cc/felt/models" @@ -14,7 +15,9 @@ import ( const errNoCollection string = "collection not found: felt.%s" const errNoDocument string = "document with name/id '%s' doesn't exist in collection: %s" const errNotAString string = "document property is not a string: %s.%s" -const errNotAnArray string = "doccument property is not an array: %s.%s" +const errNotAnArray string = "document property is not an array: %s.%s" + +const ErrNotFound string = "this token doesn't exist at this table; forget about it" type DbAdapter interface { Init(mongoUri string) error @@ -34,10 +37,12 @@ type DbAdapter interface { SetAuxMessage(table models.TableKey, message string) error GetAuxMessage(table models.TableKey) (string, error) - AddToken(table models.TableKey, token models.Token, active bool) error - RemoveToken(table models.TableKey, tokenId string, active bool) error - ModifyToken(table models.TableKey, token models.Token, active bool) error - GetTokens(table models.TableKey, active bool) ([]models.Token, error) + CheckToken(table models.TableKey, tokenId string) (bool, bool) + CreateToken(table models.TableKey, token models.Token) (string, error) + ActivateToken(table models.TableKey, tokenId string, active bool) error + MoveToken(table models.TableKey, token models.Token) error + DestroyToken(table models.TableKey, tokenId string) error + GetTokens(table models.TableKey, activeOnly bool) ([]models.Token, error) } type DbEngine struct { @@ -284,13 +289,37 @@ func (self *DbEngine) GetAuxMessage(table models.TableKey) (string, error) { return "", errors.New(fmt.Sprintf(errNoCollection, "tables")) } -func (self *DbEngine) AddToken(table models.TableKey, token models.Token, active bool) error { +func (self *DbEngine) CheckToken(table models.TableKey, tokenId string) (bool, bool) { + mongoId, err := primitive.ObjectIDFromHex(tokenId) + if err != nil { + return false, false + } tables := self.db.Collection("tables") if tables != nil { - tokenArrKey := "tokens" - if !active { - tokenArrKey = "availableTokens" + result := models.Table{} + err := tables.FindOne(self.mkCtx(10), bson.D{ + {"name", table.Name}, + {"passcode", table.Passcode}, + {"tokens", bson.E{"_id", mongoId}}, + }).Decode(&result) + if err != nil { + return false, false + } else { + active := false + for _, t := range result.Tokens { + if *t.Id == tokenId && t.Active { + active = true + } + } + return true, active } + } + return false, false +} + +func (self *DbEngine) CreateToken(table models.TableKey, token models.Token) (string, error) { + tables := self.db.Collection("tables") + if tables != nil { var result models.Table err := tables.FindOneAndUpdate( self.mkCtx(10), @@ -299,7 +328,39 @@ func (self *DbEngine) AddToken(table models.TableKey, token models.Token, active {"passcode", table.Passcode}, }, bson.D{ - {"$push", bson.D{{tokenArrKey, token}}}, + {"$push", bson.D{{"tokens", token}}}, + }, + ).Decode(&result) + if err == nil { + newId := result.Tokens[len(result.Tokens)-1].Id + return *newId, nil + } else { + return "", err + } + } + return "", errors.New(fmt.Sprintf(errNoCollection, "tables")) + +} + +func (self *DbEngine) ActivateToken(table models.TableKey, tokenId string, active bool) error { + mongoId, err := primitive.ObjectIDFromHex(tokenId) + if err != nil { + return err + } + tables := self.db.Collection("tables") + if tables != nil { + var result models.Table + err := tables.FindOneAndUpdate( + self.mkCtx(10), + bson.D{ + {"name", table.Name}, + {"passcode", table.Passcode}, + {"tokens", bson.E{"_id", mongoId}}, + }, + bson.D{ + {"$set", bson.D{{"tokens.$", bson.D{ + {"active", active}, + }}}}, }, ).Decode(&result) return err @@ -307,48 +368,23 @@ func (self *DbEngine) AddToken(table models.TableKey, token models.Token, active return errors.New(fmt.Sprintf(errNoCollection, "tables")) } -func (self *DbEngine) RemoveToken(table models.TableKey, tokenId string, active bool) error { - tables := self.db.Collection("tables") - if tables != nil { - tokenArrKey := "tokens" - if !active { - tokenArrKey = "availableTokens" - } - var result models.Table - err := tables.FindOneAndUpdate( - self.mkCtx(10), - bson.D{ - {"name", table.Name}, - {"passcode", table.Passcode}, - }, - bson.D{ - {"$pull", bson.D{{tokenArrKey, bson.D{{"_id", tokenId}}}}}, - }, - ).Decode(&result) +func (self *DbEngine) MoveToken(table models.TableKey, token models.Token) error { + mongoId, err := primitive.ObjectIDFromHex(*token.Id) + if err != nil { return err } - return errors.New(fmt.Sprintf(errNoCollection, "tables")) -} - -func (self *DbEngine) ModifyToken(table models.TableKey, token models.Token, active bool) error { tables := self.db.Collection("tables") if tables != nil { - tokenArrKey := "tokens" - if !active { - tokenArrKey = "availableTokens" - } var result models.Table err := tables.FindOneAndUpdate( self.mkCtx(10), bson.D{ {"name", table.Name}, {"passcode", table.Passcode}, - {tokenArrKey, bson.E{"_id", token.Id}}, + {"tokens", bson.E{"_id", mongoId}}, }, bson.D{ - {"$set", bson.D{{tokenArrKey + ".$", bson.D{ - {"name", token.Name}, - {"spriteUri", token.SpriteUri}, + {"$set", bson.D{{"tokens.$", bson.D{ {"x", token.X}, {"y", token.Y}, }}}}, @@ -359,7 +395,30 @@ func (self *DbEngine) ModifyToken(table models.TableKey, token models.Token, act return errors.New(fmt.Sprintf(errNoCollection, "tables")) } -func (self *DbEngine) GetTokens(table models.TableKey, active bool) ([]models.Token, error) { +func (self *DbEngine) DestroyToken(table models.TableKey, tokenId string) error { + mongoId, err := primitive.ObjectIDFromHex(tokenId) + if err != nil { + return err + } + tables := self.db.Collection("tables") + if tables != nil { + var result models.Table + err := tables.FindOneAndUpdate( + self.mkCtx(10), + bson.D{ + {"name", table.Name}, + {"passcode", table.Passcode}, + }, + bson.D{ + {"$pull", bson.D{{"tokens", bson.D{{"_id", mongoId}}}}}, + }, + ).Decode(&result) + return err + } + return errors.New(fmt.Sprintf(errNoCollection, "tables")) +} + +func (self *DbEngine) GetTokens(table models.TableKey, activeOnly bool) ([]models.Token, error) { tables := self.db.Collection("tables") if tables != nil { var result models.Table @@ -370,11 +429,14 @@ func (self *DbEngine) GetTokens(table models.TableKey, active bool) ([]models.To {"passcode", table.Passcode}, }).Decode(&result) if err == nil { - if active { - return result.Tokens, nil - } else { - return result.AvailableTokens, nil + tokens := []models.Token{} + for _, t := range result.Tokens { + if !activeOnly || t.Active { + tokens = append(tokens, t) + } + } + return tokens, nil } else { return nil, errors.New(fmt.Sprintf(errNoDocument, table.Name, "tables")) } diff --git a/static/admin.js b/static/admin.js index 2dd4bf2..89eb16a 100644 --- a/static/admin.js +++ b/static/admin.js @@ -16,6 +16,7 @@ const tokenCY = document.getElementById("token_cy"); const previewZone = document.getElementById("tokenPreview_zone"); const tokenAspect = document.getElementById("tokenKeepAspect"); const aspectLockLabel = document.getElementById("aspectLockLabel"); +const tokenZone = document.getElementById("tokenZone"); async function getTable(name, pass) { try { @@ -41,8 +42,9 @@ async function getTable(name, pass) { document.getElementById("input_table_name").value = name; document.getElementById("input_table_pass").value = pass; dial(); + const table = await res.json() infoHtml = "← table list
"; - infoHtml += `
` + infoHtml += `
` infoHtml += "
"; infoHtml += "
" if (mapImgs.ok) { @@ -70,18 +72,14 @@ async function getTable(name, pass) { } tokenListHTML += ""; fillSpriteDropdown(tokens); + redrawTokenMasterList(); } else { tokenListHTML += "" } spriteZone.innerHTML = tokenListHTML; - - - tokenWrapper.style.display = "inline"; - // also, we have to fill and toggle the tokens window - } else { console.log(res.status); } @@ -90,6 +88,15 @@ async function getTable(name, pass) { } } +function redrawTokenMasterList() { + if (tokenZone) { + const headers = new Headers(); + headers.set('Authorization', 'Bearer ' + adminToken.access_token); + + const res = await fetch(`/` + } +} + function fillSpriteDropdown(tokens) { let options = ""; for (const t of tokens) { @@ -127,34 +134,33 @@ function toggleAspectLock() { function scaleSpritePreview(source) { if (mapImg && mapImg._image) { - console.log(mapImg); - const scaleFactor = mapImg._image.clientWidth / mapImg._image.naturalWidth; - const keepAspect = tokenAspect.checked; - const img = previewZone.children[0]; - if (img) { - if (!keepAspect || !source) { - img.width = Number(tokenWidth.value) * scaleFactor; - img.height = Number(tokenHeight.value) * scaleFactor; - } else { + const scaleFactor = mapImg._image.clientWidth / mapImg._image.naturalWidth; + const keepAspect = tokenAspect.checked; + const img = previewZone.children[0]; + if (img) { + if (!keepAspect || !source) { + img.width = Number(tokenWidth.value) * scaleFactor; + img.height = Number(tokenHeight.value) * scaleFactor; + } else { - const currentAspect = img.width/img.height; - switch (source.id) { - case "token_width": - img.width = Number(tokenWidth.value) * scaleFactor; - img.height = (img.clientWidth / img.naturalWidth) * img.naturalHeight; - tokenHeight.value = Number(tokenWidth.value)/currentAspect; - break; - case "token_height": - img.height = Number(tokenHeight.value) * scaleFactor; - img.width = (img.clientHeight / img.naturalHeight) * img.naturalWidth; - tokenWidth.value = currentAspect * Number(tokenHeight.value); - break; + const currentAspect = img.width/img.height; + switch (source.id) { + case "token_width": + img.width = Number(tokenWidth.value) * scaleFactor; + img.height = (img.clientWidth / img.naturalWidth) * img.naturalHeight; + tokenHeight.value = Number(tokenWidth.value)/currentAspect; + break; + case "token_height": + img.height = Number(tokenHeight.value) * scaleFactor; + img.width = (img.clientHeight / img.naturalHeight) * img.naturalWidth; + tokenWidth.value = currentAspect * Number(tokenHeight.value); + break; + } } + tokenCX.value = Number(tokenWidth.value)/2; + tokenCY.value = Number(tokenHeight.value)/2; + drawTokenOrigin(); } - tokenCX.value = Number(tokenWidth.value)/2; - tokenCY.value = Number(tokenHeight.value)/2; - drawTokenOrigin(); - } } } @@ -162,19 +168,20 @@ function drawTokenOrigin() { const img = previewZone.children[0]; const x = Number(tokenWidth.value) / Number(tokenCX.value); const y = Number(tokenHeight.value) / Number(tokenCY.value); + const origin = {x: img.width/x, y: img.height/y}; const originImg = document.createElement("img"); + originImg.src="/table/origin.png"; originImg.style.position = "absolute"; originImg.style.left = (origin.x - 2) + "px"; originImg.style.top = (origin.y - 2) + "px"; + if (previewZone.children.length > 1) { previewZone.replaceChild(originImg, previewZone.children[1]); } else { previewZone.appendChild(originImg); } - - } function reinitializeSpritePreview() { @@ -206,24 +213,19 @@ function createToken() { const img = tokenSpriteDropdown[tokenSpriteDropdown.selectedIndex].value; const name = tokenName.value; - console.log("creating token"); if (!isNaN(w) && !isNaN(h) && !isNaN(oX) && !isNaN(oY) && img && name) { console.log("all green"); - const self = { - sz: [w, h], - m: L.marker(getCascadingPos(), { - icon: L.icon({ - iconUrl: img, - iconSize: [w,h], - }), - title: name, - draggable: true, - autoPan: true - }), - }; + + // create on the frontend for testing + /* + const self = NewToken(w, h, oX, oY, img, name); tokens.push(self); self.m.addTo(map); resizeMarkers(); + */ + + // really though we have to send it on the websocket and wait for it to come back + } } @@ -236,10 +238,19 @@ function publishAuxMsg() { } function sendMapImg(url) { - console.log("sending " + url); publish({mapImg: url, auth: adminToken.access_token}); } +function sendToken(t) { + publish({token: t, auth: adminToken.access_token}); +} + +function revokeToken(t) { + t.x = null; + t.y = null; + sendToken(t); +} + async function uploadMapImg() { try { var input = document.getElementById("map_img_upload"); @@ -446,5 +457,9 @@ async function createTable() { if (res.ok) { getTables(); setTableCreateFormVisible(false); + } else if (res.status === 422) { + setErr('Table name and passcode must be only alphanumeric and underscores'); + } else { + setErr('Error creating table'); } } \ No newline at end of file diff --git a/static/index.html b/static/index.html index 567c151..c55b4bc 100644 --- a/static/index.html +++ b/static/index.html @@ -88,7 +88,7 @@
tokens -
+

@@ -96,9 +96,9 @@


-
+

diff --git a/static/map.js b/static/map.js index 1436fb5..9a13926 100644 --- a/static/map.js +++ b/static/map.js @@ -4,7 +4,9 @@ let tokens = []; const worldBounds = [[180, -180],[-180, 180]]; function initializeMap(mapImgUrl) { + let init = false; if (!map) { + init = true; map = L.map('map', { minZoom: 0, maxZoom: 4, crs: L.CRS.Simple }); map.on("zoomend", ()=>{resizeMarkers();scaleSpritePreview();}); } @@ -14,7 +16,9 @@ function initializeMap(mapImgUrl) { mapImg = L.imageOverlay(mapImgUrl, worldBounds); mapImg.addTo(map); map.setMaxBounds(worldBounds); - map.setView([0,0], 2); + if (init) { + map.setView([0,0], 2); + } while (tokens.some(t=>t)) { tokens[0].m.removeFrom(map); tokens.shift(); @@ -39,6 +43,21 @@ function getCascadingPos() { return topLeft; } +function NewToken(w, h, oX, oY, img, name, x, y) { + return { + sz: [w, h], + m: L.marker((x && y) ? [y,x] : getCascadingPos(), { + icon: L.icon({ + iconUrl: img, + iconSize: [w,h], + }), + title: name, + draggable: true, + autoPan: true + }), + }; +} + function addToken(token) { const self = { sz: token.sz, m: L.marker(token.pos, { icon: L.icon({ diff --git a/static/socket.js b/static/socket.js index 4e34ab7..1ab52d4 100644 --- a/static/socket.js +++ b/static/socket.js @@ -19,15 +19,17 @@ function fmtLeading(n) { } function formatDice(r) { - console.log(r); const date = new Date(r.timestamp) const p = document.createElement("p"); + const month = date.getMonth() + 1; const day = date.getDate(); const hours = date.getHours(); const minutes = date.getMinutes(); const seconds = date.getSeconds(); + p.innerHTML = `${date.getFullYear()}-${fmtLeading(month)}-${fmtLeading(day)} ${fmtLeading(hours)}:${fmtLeading(minutes)}:${fmtLeading(seconds)} ${r.player} rolled ${r.roll.length}d${r.faces} ${(r.note ? "(" + r.note + ")" : "")}
[${r.roll}] (total ${r.roll.reduce((a,c)=>a+c,0)})`; + return p; } diff --git a/static/style.css b/static/style.css index 02ee9be..6716d0e 100644 --- a/static/style.css +++ b/static/style.css @@ -147,6 +147,10 @@ pre { color: var(--fg_color); } +.ui_win ul { + max-height: 10em; +} + #admin_section { text-align: right; }