Compare commits
10 Commits
frontend/m
...
submitter-
Author | SHA1 | Date | |
---|---|---|---|
d66bd9b112
|
|||
3bda4803aa
|
|||
c401d24366
|
|||
a119c4292e
|
|||
4cf7889db9
|
|||
146d627534
|
|||
97180ab263
|
|||
37560ac5d2
|
|||
de8f869b5b
|
|||
b6ae600a93
|
openapi.yaml
pkg
api
oas_client_gen.gooas_handlers_gen.gooas_json_gen.gooas_parameters_gen.gooas_request_decoders_gen.gooas_schemas_gen.gooas_validators_gen.go
datastore
gormstore
model
service
web/src/app
mapfixes
(styles)
[mapfixId]
_card.tsx_window.tsxpage.tsxmaps
submit
ts
39
openapi.yaml
39
openapi.yaml
@ -86,6 +86,21 @@ paths:
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/Page"
|
||||
- $ref: "#/components/parameters/Limit"
|
||||
- name: DisplayName
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
maxLength: 128
|
||||
- name: Creator
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
maxLength: 128
|
||||
- name: GameID
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int32
|
||||
- name: Sort
|
||||
in: query
|
||||
schema:
|
||||
@ -1018,6 +1033,9 @@ components:
|
||||
Mapfix:
|
||||
required:
|
||||
- ID
|
||||
- DisplayName
|
||||
- Creator
|
||||
- GameID
|
||||
- CreatedAt
|
||||
- UpdatedAt
|
||||
- Submitter
|
||||
@ -1032,6 +1050,15 @@ components:
|
||||
ID:
|
||||
type: integer
|
||||
format: int64
|
||||
DisplayName:
|
||||
type: string
|
||||
maxLength: 128
|
||||
Creator:
|
||||
type: string
|
||||
maxLength: 128
|
||||
GameID:
|
||||
type: integer
|
||||
format: int32
|
||||
CreatedAt:
|
||||
type: integer
|
||||
format: int64
|
||||
@ -1060,11 +1087,23 @@ components:
|
||||
maxLength: 256
|
||||
MapfixCreate:
|
||||
required:
|
||||
- DisplayName
|
||||
- Creator
|
||||
- GameID
|
||||
- AssetID
|
||||
- AssetVersion
|
||||
- TargetAssetID
|
||||
type: object
|
||||
properties:
|
||||
DisplayName:
|
||||
type: string
|
||||
maxLength: 128
|
||||
Creator:
|
||||
type: string
|
||||
maxLength: 128
|
||||
GameID:
|
||||
type: integer
|
||||
format: int32
|
||||
AssetID:
|
||||
type: integer
|
||||
format: int64
|
||||
|
@ -3682,6 +3682,57 @@ func (c *Client) sendListMapfixes(ctx context.Context, params ListMapfixesParams
|
||||
return res, errors.Wrap(err, "encode query")
|
||||
}
|
||||
}
|
||||
{
|
||||
// Encode "DisplayName" parameter.
|
||||
cfg := uri.QueryParameterEncodingConfig{
|
||||
Name: "DisplayName",
|
||||
Style: uri.QueryStyleForm,
|
||||
Explode: true,
|
||||
}
|
||||
|
||||
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
|
||||
if val, ok := params.DisplayName.Get(); ok {
|
||||
return e.EncodeValue(conv.StringToString(val))
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return res, errors.Wrap(err, "encode query")
|
||||
}
|
||||
}
|
||||
{
|
||||
// Encode "Creator" parameter.
|
||||
cfg := uri.QueryParameterEncodingConfig{
|
||||
Name: "Creator",
|
||||
Style: uri.QueryStyleForm,
|
||||
Explode: true,
|
||||
}
|
||||
|
||||
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
|
||||
if val, ok := params.Creator.Get(); ok {
|
||||
return e.EncodeValue(conv.StringToString(val))
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return res, errors.Wrap(err, "encode query")
|
||||
}
|
||||
}
|
||||
{
|
||||
// Encode "GameID" parameter.
|
||||
cfg := uri.QueryParameterEncodingConfig{
|
||||
Name: "GameID",
|
||||
Style: uri.QueryStyleForm,
|
||||
Explode: true,
|
||||
}
|
||||
|
||||
if err := q.EncodeParam(cfg, func(e uri.Encoder) error {
|
||||
if val, ok := params.GameID.Get(); ok {
|
||||
return e.EncodeValue(conv.Int32ToString(val))
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return res, errors.Wrap(err, "encode query")
|
||||
}
|
||||
}
|
||||
{
|
||||
// Encode "Sort" parameter.
|
||||
cfg := uri.QueryParameterEncodingConfig{
|
||||
|
@ -5408,6 +5408,18 @@ func (s *Server) handleListMapfixesRequest(args [0]string, argsEscaped bool, w h
|
||||
Name: "Limit",
|
||||
In: "query",
|
||||
}: params.Limit,
|
||||
{
|
||||
Name: "DisplayName",
|
||||
In: "query",
|
||||
}: params.DisplayName,
|
||||
{
|
||||
Name: "Creator",
|
||||
In: "query",
|
||||
}: params.Creator,
|
||||
{
|
||||
Name: "GameID",
|
||||
In: "query",
|
||||
}: params.GameID,
|
||||
{
|
||||
Name: "Sort",
|
||||
In: "query",
|
||||
|
@ -235,6 +235,18 @@ func (s *Mapfix) encodeFields(e *jx.Encoder) {
|
||||
e.FieldStart("ID")
|
||||
e.Int64(s.ID)
|
||||
}
|
||||
{
|
||||
e.FieldStart("DisplayName")
|
||||
e.Str(s.DisplayName)
|
||||
}
|
||||
{
|
||||
e.FieldStart("Creator")
|
||||
e.Str(s.Creator)
|
||||
}
|
||||
{
|
||||
e.FieldStart("GameID")
|
||||
e.Int32(s.GameID)
|
||||
}
|
||||
{
|
||||
e.FieldStart("CreatedAt")
|
||||
e.Int64(s.CreatedAt)
|
||||
@ -273,17 +285,20 @@ func (s *Mapfix) encodeFields(e *jx.Encoder) {
|
||||
}
|
||||
}
|
||||
|
||||
var jsonFieldsNameOfMapfix = [10]string{
|
||||
0: "ID",
|
||||
1: "CreatedAt",
|
||||
2: "UpdatedAt",
|
||||
3: "Submitter",
|
||||
4: "AssetID",
|
||||
5: "AssetVersion",
|
||||
6: "Completed",
|
||||
7: "TargetAssetID",
|
||||
8: "StatusID",
|
||||
9: "StatusMessage",
|
||||
var jsonFieldsNameOfMapfix = [13]string{
|
||||
0: "ID",
|
||||
1: "DisplayName",
|
||||
2: "Creator",
|
||||
3: "GameID",
|
||||
4: "CreatedAt",
|
||||
5: "UpdatedAt",
|
||||
6: "Submitter",
|
||||
7: "AssetID",
|
||||
8: "AssetVersion",
|
||||
9: "Completed",
|
||||
10: "TargetAssetID",
|
||||
11: "StatusID",
|
||||
12: "StatusMessage",
|
||||
}
|
||||
|
||||
// Decode decodes Mapfix from json.
|
||||
@ -307,8 +322,44 @@ func (s *Mapfix) Decode(d *jx.Decoder) error {
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"ID\"")
|
||||
}
|
||||
case "CreatedAt":
|
||||
case "DisplayName":
|
||||
requiredBitSet[0] |= 1 << 1
|
||||
if err := func() error {
|
||||
v, err := d.Str()
|
||||
s.DisplayName = string(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"DisplayName\"")
|
||||
}
|
||||
case "Creator":
|
||||
requiredBitSet[0] |= 1 << 2
|
||||
if err := func() error {
|
||||
v, err := d.Str()
|
||||
s.Creator = string(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"Creator\"")
|
||||
}
|
||||
case "GameID":
|
||||
requiredBitSet[0] |= 1 << 3
|
||||
if err := func() error {
|
||||
v, err := d.Int32()
|
||||
s.GameID = int32(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"GameID\"")
|
||||
}
|
||||
case "CreatedAt":
|
||||
requiredBitSet[0] |= 1 << 4
|
||||
if err := func() error {
|
||||
v, err := d.Int64()
|
||||
s.CreatedAt = int64(v)
|
||||
@ -320,7 +371,7 @@ func (s *Mapfix) Decode(d *jx.Decoder) error {
|
||||
return errors.Wrap(err, "decode field \"CreatedAt\"")
|
||||
}
|
||||
case "UpdatedAt":
|
||||
requiredBitSet[0] |= 1 << 2
|
||||
requiredBitSet[0] |= 1 << 5
|
||||
if err := func() error {
|
||||
v, err := d.Int64()
|
||||
s.UpdatedAt = int64(v)
|
||||
@ -332,7 +383,7 @@ func (s *Mapfix) Decode(d *jx.Decoder) error {
|
||||
return errors.Wrap(err, "decode field \"UpdatedAt\"")
|
||||
}
|
||||
case "Submitter":
|
||||
requiredBitSet[0] |= 1 << 3
|
||||
requiredBitSet[0] |= 1 << 6
|
||||
if err := func() error {
|
||||
v, err := d.Int64()
|
||||
s.Submitter = int64(v)
|
||||
@ -344,7 +395,7 @@ func (s *Mapfix) Decode(d *jx.Decoder) error {
|
||||
return errors.Wrap(err, "decode field \"Submitter\"")
|
||||
}
|
||||
case "AssetID":
|
||||
requiredBitSet[0] |= 1 << 4
|
||||
requiredBitSet[0] |= 1 << 7
|
||||
if err := func() error {
|
||||
v, err := d.Int64()
|
||||
s.AssetID = int64(v)
|
||||
@ -356,7 +407,7 @@ func (s *Mapfix) Decode(d *jx.Decoder) error {
|
||||
return errors.Wrap(err, "decode field \"AssetID\"")
|
||||
}
|
||||
case "AssetVersion":
|
||||
requiredBitSet[0] |= 1 << 5
|
||||
requiredBitSet[1] |= 1 << 0
|
||||
if err := func() error {
|
||||
v, err := d.Int64()
|
||||
s.AssetVersion = int64(v)
|
||||
@ -368,7 +419,7 @@ func (s *Mapfix) Decode(d *jx.Decoder) error {
|
||||
return errors.Wrap(err, "decode field \"AssetVersion\"")
|
||||
}
|
||||
case "Completed":
|
||||
requiredBitSet[0] |= 1 << 6
|
||||
requiredBitSet[1] |= 1 << 1
|
||||
if err := func() error {
|
||||
v, err := d.Bool()
|
||||
s.Completed = bool(v)
|
||||
@ -380,7 +431,7 @@ func (s *Mapfix) Decode(d *jx.Decoder) error {
|
||||
return errors.Wrap(err, "decode field \"Completed\"")
|
||||
}
|
||||
case "TargetAssetID":
|
||||
requiredBitSet[0] |= 1 << 7
|
||||
requiredBitSet[1] |= 1 << 2
|
||||
if err := func() error {
|
||||
v, err := d.Int64()
|
||||
s.TargetAssetID = int64(v)
|
||||
@ -392,7 +443,7 @@ func (s *Mapfix) Decode(d *jx.Decoder) error {
|
||||
return errors.Wrap(err, "decode field \"TargetAssetID\"")
|
||||
}
|
||||
case "StatusID":
|
||||
requiredBitSet[1] |= 1 << 0
|
||||
requiredBitSet[1] |= 1 << 3
|
||||
if err := func() error {
|
||||
v, err := d.Int32()
|
||||
s.StatusID = int32(v)
|
||||
@ -404,7 +455,7 @@ func (s *Mapfix) Decode(d *jx.Decoder) error {
|
||||
return errors.Wrap(err, "decode field \"StatusID\"")
|
||||
}
|
||||
case "StatusMessage":
|
||||
requiredBitSet[1] |= 1 << 1
|
||||
requiredBitSet[1] |= 1 << 4
|
||||
if err := func() error {
|
||||
v, err := d.Str()
|
||||
s.StatusMessage = string(v)
|
||||
@ -426,7 +477,7 @@ func (s *Mapfix) Decode(d *jx.Decoder) error {
|
||||
var failures []validate.FieldError
|
||||
for i, mask := range [2]uint8{
|
||||
0b11111111,
|
||||
0b00000011,
|
||||
0b00011111,
|
||||
} {
|
||||
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
|
||||
// Mask only required fields and check equality to mask using XOR.
|
||||
@ -481,6 +532,18 @@ func (s *MapfixCreate) Encode(e *jx.Encoder) {
|
||||
|
||||
// encodeFields encodes fields.
|
||||
func (s *MapfixCreate) encodeFields(e *jx.Encoder) {
|
||||
{
|
||||
e.FieldStart("DisplayName")
|
||||
e.Str(s.DisplayName)
|
||||
}
|
||||
{
|
||||
e.FieldStart("Creator")
|
||||
e.Str(s.Creator)
|
||||
}
|
||||
{
|
||||
e.FieldStart("GameID")
|
||||
e.Int32(s.GameID)
|
||||
}
|
||||
{
|
||||
e.FieldStart("AssetID")
|
||||
e.Int64(s.AssetID)
|
||||
@ -495,10 +558,13 @@ func (s *MapfixCreate) encodeFields(e *jx.Encoder) {
|
||||
}
|
||||
}
|
||||
|
||||
var jsonFieldsNameOfMapfixCreate = [3]string{
|
||||
0: "AssetID",
|
||||
1: "AssetVersion",
|
||||
2: "TargetAssetID",
|
||||
var jsonFieldsNameOfMapfixCreate = [6]string{
|
||||
0: "DisplayName",
|
||||
1: "Creator",
|
||||
2: "GameID",
|
||||
3: "AssetID",
|
||||
4: "AssetVersion",
|
||||
5: "TargetAssetID",
|
||||
}
|
||||
|
||||
// Decode decodes MapfixCreate from json.
|
||||
@ -510,8 +576,44 @@ func (s *MapfixCreate) Decode(d *jx.Decoder) error {
|
||||
|
||||
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
|
||||
switch string(k) {
|
||||
case "AssetID":
|
||||
case "DisplayName":
|
||||
requiredBitSet[0] |= 1 << 0
|
||||
if err := func() error {
|
||||
v, err := d.Str()
|
||||
s.DisplayName = string(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"DisplayName\"")
|
||||
}
|
||||
case "Creator":
|
||||
requiredBitSet[0] |= 1 << 1
|
||||
if err := func() error {
|
||||
v, err := d.Str()
|
||||
s.Creator = string(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"Creator\"")
|
||||
}
|
||||
case "GameID":
|
||||
requiredBitSet[0] |= 1 << 2
|
||||
if err := func() error {
|
||||
v, err := d.Int32()
|
||||
s.GameID = int32(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"GameID\"")
|
||||
}
|
||||
case "AssetID":
|
||||
requiredBitSet[0] |= 1 << 3
|
||||
if err := func() error {
|
||||
v, err := d.Int64()
|
||||
s.AssetID = int64(v)
|
||||
@ -523,7 +625,7 @@ func (s *MapfixCreate) Decode(d *jx.Decoder) error {
|
||||
return errors.Wrap(err, "decode field \"AssetID\"")
|
||||
}
|
||||
case "AssetVersion":
|
||||
requiredBitSet[0] |= 1 << 1
|
||||
requiredBitSet[0] |= 1 << 4
|
||||
if err := func() error {
|
||||
v, err := d.Int64()
|
||||
s.AssetVersion = int64(v)
|
||||
@ -535,7 +637,7 @@ func (s *MapfixCreate) Decode(d *jx.Decoder) error {
|
||||
return errors.Wrap(err, "decode field \"AssetVersion\"")
|
||||
}
|
||||
case "TargetAssetID":
|
||||
requiredBitSet[0] |= 1 << 2
|
||||
requiredBitSet[0] |= 1 << 5
|
||||
if err := func() error {
|
||||
v, err := d.Int64()
|
||||
s.TargetAssetID = int64(v)
|
||||
@ -556,7 +658,7 @@ func (s *MapfixCreate) Decode(d *jx.Decoder) error {
|
||||
// Validate required fields.
|
||||
var failures []validate.FieldError
|
||||
for i, mask := range [1]uint8{
|
||||
0b00000111,
|
||||
0b00111111,
|
||||
} {
|
||||
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
|
||||
// Mask only required fields and check equality to mask using XOR.
|
||||
|
@ -1601,9 +1601,12 @@ func decodeGetSubmissionParams(args [1]string, argsEscaped bool, r *http.Request
|
||||
|
||||
// ListMapfixesParams is parameters of listMapfixes operation.
|
||||
type ListMapfixesParams struct {
|
||||
Page int32
|
||||
Limit int32
|
||||
Sort OptInt32
|
||||
Page int32
|
||||
Limit int32
|
||||
DisplayName OptString
|
||||
Creator OptString
|
||||
GameID OptInt32
|
||||
Sort OptInt32
|
||||
}
|
||||
|
||||
func unpackListMapfixesParams(packed middleware.Parameters) (params ListMapfixesParams) {
|
||||
@ -1621,6 +1624,33 @@ func unpackListMapfixesParams(packed middleware.Parameters) (params ListMapfixes
|
||||
}
|
||||
params.Limit = packed[key].(int32)
|
||||
}
|
||||
{
|
||||
key := middleware.ParameterKey{
|
||||
Name: "DisplayName",
|
||||
In: "query",
|
||||
}
|
||||
if v, ok := packed[key]; ok {
|
||||
params.DisplayName = v.(OptString)
|
||||
}
|
||||
}
|
||||
{
|
||||
key := middleware.ParameterKey{
|
||||
Name: "Creator",
|
||||
In: "query",
|
||||
}
|
||||
if v, ok := packed[key]; ok {
|
||||
params.Creator = v.(OptString)
|
||||
}
|
||||
}
|
||||
{
|
||||
key := middleware.ParameterKey{
|
||||
Name: "GameID",
|
||||
In: "query",
|
||||
}
|
||||
if v, ok := packed[key]; ok {
|
||||
params.GameID = v.(OptInt32)
|
||||
}
|
||||
}
|
||||
{
|
||||
key := middleware.ParameterKey{
|
||||
Name: "Sort",
|
||||
@ -1741,6 +1771,175 @@ func decodeListMapfixesParams(args [0]string, argsEscaped bool, r *http.Request)
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
// Decode query: DisplayName.
|
||||
if err := func() error {
|
||||
cfg := uri.QueryParameterDecodingConfig{
|
||||
Name: "DisplayName",
|
||||
Style: uri.QueryStyleForm,
|
||||
Explode: true,
|
||||
}
|
||||
|
||||
if err := q.HasParam(cfg); err == nil {
|
||||
if err := q.DecodeParam(cfg, func(d uri.Decoder) error {
|
||||
var paramsDotDisplayNameVal string
|
||||
if err := func() error {
|
||||
val, err := d.DecodeValue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := conv.ToString(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
paramsDotDisplayNameVal = c
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
params.DisplayName.SetTo(paramsDotDisplayNameVal)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := func() error {
|
||||
if value, ok := params.DisplayName.Get(); ok {
|
||||
if err := func() error {
|
||||
if err := (validate.String{
|
||||
MinLength: 0,
|
||||
MinLengthSet: false,
|
||||
MaxLength: 128,
|
||||
MaxLengthSet: true,
|
||||
Email: false,
|
||||
Hostname: false,
|
||||
Regex: nil,
|
||||
}).Validate(string(value)); err != nil {
|
||||
return errors.Wrap(err, "string")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return params, &ogenerrors.DecodeParamError{
|
||||
Name: "DisplayName",
|
||||
In: "query",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
// Decode query: Creator.
|
||||
if err := func() error {
|
||||
cfg := uri.QueryParameterDecodingConfig{
|
||||
Name: "Creator",
|
||||
Style: uri.QueryStyleForm,
|
||||
Explode: true,
|
||||
}
|
||||
|
||||
if err := q.HasParam(cfg); err == nil {
|
||||
if err := q.DecodeParam(cfg, func(d uri.Decoder) error {
|
||||
var paramsDotCreatorVal string
|
||||
if err := func() error {
|
||||
val, err := d.DecodeValue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := conv.ToString(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
paramsDotCreatorVal = c
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
params.Creator.SetTo(paramsDotCreatorVal)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := func() error {
|
||||
if value, ok := params.Creator.Get(); ok {
|
||||
if err := func() error {
|
||||
if err := (validate.String{
|
||||
MinLength: 0,
|
||||
MinLengthSet: false,
|
||||
MaxLength: 128,
|
||||
MaxLengthSet: true,
|
||||
Email: false,
|
||||
Hostname: false,
|
||||
Regex: nil,
|
||||
}).Validate(string(value)); err != nil {
|
||||
return errors.Wrap(err, "string")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return params, &ogenerrors.DecodeParamError{
|
||||
Name: "Creator",
|
||||
In: "query",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
// Decode query: GameID.
|
||||
if err := func() error {
|
||||
cfg := uri.QueryParameterDecodingConfig{
|
||||
Name: "GameID",
|
||||
Style: uri.QueryStyleForm,
|
||||
Explode: true,
|
||||
}
|
||||
|
||||
if err := q.HasParam(cfg); err == nil {
|
||||
if err := q.DecodeParam(cfg, func(d uri.Decoder) error {
|
||||
var paramsDotGameIDVal int32
|
||||
if err := func() error {
|
||||
val, err := d.DecodeValue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := conv.ToInt32(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
paramsDotGameIDVal = c
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
params.GameID.SetTo(paramsDotGameIDVal)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return params, &ogenerrors.DecodeParamError{
|
||||
Name: "GameID",
|
||||
In: "query",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
// Decode query: Sort.
|
||||
if err := func() error {
|
||||
cfg := uri.QueryParameterDecodingConfig{
|
||||
|
@ -72,6 +72,14 @@ func (s *Server) decodeCreateMapfixRequest(r *http.Request) (
|
||||
}
|
||||
return req, close, err
|
||||
}
|
||||
if err := func() error {
|
||||
if err := request.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return req, close, errors.Wrap(err, "validate")
|
||||
}
|
||||
return &request, close, nil
|
||||
default:
|
||||
return req, close, validate.InvalidContentType(ct)
|
||||
|
@ -156,6 +156,9 @@ func (s *ID) SetID(val int64) {
|
||||
// Ref: #/components/schemas/Mapfix
|
||||
type Mapfix struct {
|
||||
ID int64 `json:"ID"`
|
||||
DisplayName string `json:"DisplayName"`
|
||||
Creator string `json:"Creator"`
|
||||
GameID int32 `json:"GameID"`
|
||||
CreatedAt int64 `json:"CreatedAt"`
|
||||
UpdatedAt int64 `json:"UpdatedAt"`
|
||||
Submitter int64 `json:"Submitter"`
|
||||
@ -172,6 +175,21 @@ func (s *Mapfix) GetID() int64 {
|
||||
return s.ID
|
||||
}
|
||||
|
||||
// GetDisplayName returns the value of DisplayName.
|
||||
func (s *Mapfix) GetDisplayName() string {
|
||||
return s.DisplayName
|
||||
}
|
||||
|
||||
// GetCreator returns the value of Creator.
|
||||
func (s *Mapfix) GetCreator() string {
|
||||
return s.Creator
|
||||
}
|
||||
|
||||
// GetGameID returns the value of GameID.
|
||||
func (s *Mapfix) GetGameID() int32 {
|
||||
return s.GameID
|
||||
}
|
||||
|
||||
// GetCreatedAt returns the value of CreatedAt.
|
||||
func (s *Mapfix) GetCreatedAt() int64 {
|
||||
return s.CreatedAt
|
||||
@ -222,6 +240,21 @@ func (s *Mapfix) SetID(val int64) {
|
||||
s.ID = val
|
||||
}
|
||||
|
||||
// SetDisplayName sets the value of DisplayName.
|
||||
func (s *Mapfix) SetDisplayName(val string) {
|
||||
s.DisplayName = val
|
||||
}
|
||||
|
||||
// SetCreator sets the value of Creator.
|
||||
func (s *Mapfix) SetCreator(val string) {
|
||||
s.Creator = val
|
||||
}
|
||||
|
||||
// SetGameID sets the value of GameID.
|
||||
func (s *Mapfix) SetGameID(val int32) {
|
||||
s.GameID = val
|
||||
}
|
||||
|
||||
// SetCreatedAt sets the value of CreatedAt.
|
||||
func (s *Mapfix) SetCreatedAt(val int64) {
|
||||
s.CreatedAt = val
|
||||
@ -269,9 +302,27 @@ func (s *Mapfix) SetStatusMessage(val string) {
|
||||
|
||||
// Ref: #/components/schemas/MapfixCreate
|
||||
type MapfixCreate struct {
|
||||
AssetID int64 `json:"AssetID"`
|
||||
AssetVersion int64 `json:"AssetVersion"`
|
||||
TargetAssetID int64 `json:"TargetAssetID"`
|
||||
DisplayName string `json:"DisplayName"`
|
||||
Creator string `json:"Creator"`
|
||||
GameID int32 `json:"GameID"`
|
||||
AssetID int64 `json:"AssetID"`
|
||||
AssetVersion int64 `json:"AssetVersion"`
|
||||
TargetAssetID int64 `json:"TargetAssetID"`
|
||||
}
|
||||
|
||||
// GetDisplayName returns the value of DisplayName.
|
||||
func (s *MapfixCreate) GetDisplayName() string {
|
||||
return s.DisplayName
|
||||
}
|
||||
|
||||
// GetCreator returns the value of Creator.
|
||||
func (s *MapfixCreate) GetCreator() string {
|
||||
return s.Creator
|
||||
}
|
||||
|
||||
// GetGameID returns the value of GameID.
|
||||
func (s *MapfixCreate) GetGameID() int32 {
|
||||
return s.GameID
|
||||
}
|
||||
|
||||
// GetAssetID returns the value of AssetID.
|
||||
@ -289,6 +340,21 @@ func (s *MapfixCreate) GetTargetAssetID() int64 {
|
||||
return s.TargetAssetID
|
||||
}
|
||||
|
||||
// SetDisplayName sets the value of DisplayName.
|
||||
func (s *MapfixCreate) SetDisplayName(val string) {
|
||||
s.DisplayName = val
|
||||
}
|
||||
|
||||
// SetCreator sets the value of Creator.
|
||||
func (s *MapfixCreate) SetCreator(val string) {
|
||||
s.Creator = val
|
||||
}
|
||||
|
||||
// SetGameID sets the value of GameID.
|
||||
func (s *MapfixCreate) SetGameID(val int32) {
|
||||
s.GameID = val
|
||||
}
|
||||
|
||||
// SetAssetID sets the value of AssetID.
|
||||
func (s *MapfixCreate) SetAssetID(val int64) {
|
||||
s.AssetID = val
|
||||
|
@ -14,6 +14,44 @@ func (s *Mapfix) Validate() error {
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if err := (validate.String{
|
||||
MinLength: 0,
|
||||
MinLengthSet: false,
|
||||
MaxLength: 128,
|
||||
MaxLengthSet: true,
|
||||
Email: false,
|
||||
Hostname: false,
|
||||
Regex: nil,
|
||||
}).Validate(string(s.DisplayName)); err != nil {
|
||||
return errors.Wrap(err, "string")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "DisplayName",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if err := func() error {
|
||||
if err := (validate.String{
|
||||
MinLength: 0,
|
||||
MinLengthSet: false,
|
||||
MaxLength: 128,
|
||||
MaxLengthSet: true,
|
||||
Email: false,
|
||||
Hostname: false,
|
||||
Regex: nil,
|
||||
}).Validate(string(s.Creator)); err != nil {
|
||||
return errors.Wrap(err, "string")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "Creator",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if err := func() error {
|
||||
if err := (validate.String{
|
||||
MinLength: 0,
|
||||
@ -39,6 +77,56 @@ func (s *Mapfix) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MapfixCreate) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if err := (validate.String{
|
||||
MinLength: 0,
|
||||
MinLengthSet: false,
|
||||
MaxLength: 128,
|
||||
MaxLengthSet: true,
|
||||
Email: false,
|
||||
Hostname: false,
|
||||
Regex: nil,
|
||||
}).Validate(string(s.DisplayName)); err != nil {
|
||||
return errors.Wrap(err, "string")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "DisplayName",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if err := func() error {
|
||||
if err := (validate.String{
|
||||
MinLength: 0,
|
||||
MinLengthSet: false,
|
||||
MaxLength: 128,
|
||||
MaxLengthSet: true,
|
||||
Email: false,
|
||||
Hostname: false,
|
||||
Regex: nil,
|
||||
}).Validate(string(s.Creator)); err != nil {
|
||||
return errors.Wrap(err, "string")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "Creator",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Script) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
|
@ -31,6 +31,7 @@ func New(ctx *cli.Context) (datastore.Datastore, error) {
|
||||
|
||||
if ctx.Bool("migrate") {
|
||||
if err := db.AutoMigrate(
|
||||
&model.Mapfix{},
|
||||
&model.Submission{},
|
||||
&model.Script{},
|
||||
&model.ScriptPolicy{},
|
||||
|
@ -23,6 +23,9 @@ const (
|
||||
|
||||
type Mapfix struct {
|
||||
ID int64 `gorm:"primaryKey"`
|
||||
DisplayName string
|
||||
Creator string
|
||||
GameID int32
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Submitter int64 // UserID
|
||||
|
@ -95,6 +95,9 @@ func (svc *Service) CreateMapfix(ctx context.Context, request *api.MapfixCreate)
|
||||
|
||||
mapfix, err := svc.DB.Mapfixes().Create(ctx, model.Mapfix{
|
||||
ID: 0,
|
||||
DisplayName: request.DisplayName,
|
||||
Creator: request.Creator,
|
||||
GameID: request.GameID,
|
||||
Submitter: int64(userId),
|
||||
AssetID: request.AssetID,
|
||||
AssetVersion: request.AssetVersion,
|
||||
@ -122,6 +125,9 @@ func (svc *Service) GetMapfix(ctx context.Context, params api.GetMapfixParams) (
|
||||
}
|
||||
return &api.Mapfix{
|
||||
ID: mapfix.ID,
|
||||
DisplayName: mapfix.DisplayName,
|
||||
Creator: mapfix.Creator,
|
||||
GameID: mapfix.GameID,
|
||||
CreatedAt: mapfix.CreatedAt.Unix(),
|
||||
UpdatedAt: mapfix.UpdatedAt.Unix(),
|
||||
Submitter: int64(mapfix.Submitter),
|
||||
@ -142,6 +148,16 @@ func (svc *Service) GetMapfix(ctx context.Context, params api.GetMapfixParams) (
|
||||
func (svc *Service) ListMapfixes(ctx context.Context, params api.ListMapfixesParams) ([]api.Mapfix, error) {
|
||||
filter := datastore.Optional()
|
||||
|
||||
if params.DisplayName.IsSet(){
|
||||
filter.Add("display_name", params.DisplayName.Value)
|
||||
}
|
||||
if params.Creator.IsSet(){
|
||||
filter.Add("creator", params.Creator.Value)
|
||||
}
|
||||
if params.GameID.IsSet(){
|
||||
filter.Add("game_id", params.GameID.Value)
|
||||
}
|
||||
|
||||
sort := datastore.ListSort(params.Sort.Or(int32(datastore.ListSortDisabled)))
|
||||
|
||||
items, err := svc.DB.Mapfixes().List(ctx, filter, model.Page{
|
||||
@ -156,6 +172,9 @@ func (svc *Service) ListMapfixes(ctx context.Context, params api.ListMapfixesPar
|
||||
for _, item := range items {
|
||||
resp = append(resp, api.Mapfix{
|
||||
ID: item.ID,
|
||||
DisplayName: item.DisplayName,
|
||||
Creator: item.Creator,
|
||||
GameID: item.GameID,
|
||||
CreatedAt: item.CreatedAt.Unix(),
|
||||
UpdatedAt: item.UpdatedAt.Unix(),
|
||||
Submitter: int64(item.Submitter),
|
||||
|
@ -17,10 +17,15 @@ var (
|
||||
// Submissions roles bitflag
|
||||
type Roles int32
|
||||
var (
|
||||
// Only users with this role are allowed to submit models they don't own
|
||||
RolesSubmissionCreateNotModelOwner Roles = 1<<8
|
||||
RolesMapfixCreateNotModelOwner Roles = 1<<7
|
||||
RolesSubmissionUpload Roles = 1<<6
|
||||
RolesSubmissionReview Roles = 1<<5
|
||||
RolesSubmissionRelease Roles = 1<<4
|
||||
RolesScriptWrite Roles = 1<<3
|
||||
RolesMapUpload Roles = 1<<2
|
||||
RolesMapReview Roles = 1<<1
|
||||
RolesMapfixUpload Roles = 1<<2
|
||||
RolesMapfixReview Roles = 1<<1
|
||||
RolesMapDownload Roles = 1<<0
|
||||
RolesEmpty Roles = 0
|
||||
)
|
||||
@ -32,13 +37,13 @@ var (
|
||||
RoleQuat GroupRole = 255
|
||||
RoleItzaname GroupRole = 254
|
||||
RoleStagingDeveloper GroupRole = 240
|
||||
RolesAll Roles = RolesScriptWrite|RolesSubmissionRelease|RolesMapUpload|RolesMapReview|RolesMapDownload
|
||||
RolesAll Roles = ^RolesEmpty
|
||||
// has SubmissionUpload
|
||||
RoleMapAdmin GroupRole = 128
|
||||
RolesMapAdmin Roles = RolesSubmissionRelease|RolesMapUpload|RolesMapReview|RolesMapDownload
|
||||
// has SubmissionReview
|
||||
RolesMapAdmin Roles = RolesSubmissionRelease|RolesSubmissionUpload|RolesSubmissionReview|RolesSubmissionCreateNotModelOwner|RolesMapCouncil
|
||||
// has MapfixReview
|
||||
RoleMapCouncil GroupRole = 64
|
||||
RolesMapCouncil Roles = RolesMapReview|RolesMapUpload|RolesMapDownload
|
||||
RolesMapCouncil Roles = RolesMapfixReview|RolesMapfixUpload|RolesMapfixCreateNotModelOwner|RolesMapAccess
|
||||
// access to downloading maps
|
||||
RoleMapAccess GroupRole = 32
|
||||
RolesMapAccess Roles = RolesMapDownload
|
||||
@ -128,11 +133,17 @@ func (usr UserInfoHandle) GetRoles() (Roles, error) {
|
||||
}
|
||||
|
||||
// RoleThumbnail
|
||||
func (usr UserInfoHandle) HasRoleMapfixCreateNotModelOwner() (bool, error) {
|
||||
return usr.hasRoles(RolesMapfixCreateNotModelOwner)
|
||||
}
|
||||
func (usr UserInfoHandle) HasRoleSubmissionCreateNotModelOwner() (bool, error) {
|
||||
return usr.hasRoles(RolesSubmissionCreateNotModelOwner)
|
||||
}
|
||||
func (usr UserInfoHandle) HasRoleMapfixUpload() (bool, error) {
|
||||
return usr.hasRoles(RolesMapUpload)
|
||||
return usr.hasRoles(RolesMapfixUpload)
|
||||
}
|
||||
func (usr UserInfoHandle) HasRoleMapfixReview() (bool, error) {
|
||||
return usr.hasRoles(RolesMapReview)
|
||||
return usr.hasRoles(RolesMapfixReview)
|
||||
}
|
||||
func (usr UserInfoHandle) HasRoleMapDownload() (bool, error) {
|
||||
return usr.hasRoles(RolesMapDownload)
|
||||
@ -141,10 +152,10 @@ func (usr UserInfoHandle) HasRoleSubmissionRelease() (bool, error) {
|
||||
return usr.hasRoles(RolesSubmissionRelease)
|
||||
}
|
||||
func (usr UserInfoHandle) HasRoleSubmissionUpload() (bool, error) {
|
||||
return usr.hasRoles(RolesMapUpload)
|
||||
return usr.hasRoles(RolesSubmissionUpload)
|
||||
}
|
||||
func (usr UserInfoHandle) HasRoleSubmissionReview() (bool, error) {
|
||||
return usr.hasRoles(RolesMapReview)
|
||||
return usr.hasRoles(RolesSubmissionReview)
|
||||
}
|
||||
func (usr UserInfoHandle) HasRoleScriptWrite() (bool, error) {
|
||||
return usr.hasRoles(RolesScriptWrite)
|
||||
|
75
web/src/app/mapfixes/(styles)/page.scss
Normal file
75
web/src/app/mapfixes/(styles)/page.scss
Normal file
@ -0,0 +1,75 @@
|
||||
@forward "./page/card.scss";
|
||||
|
||||
@use "../../globals.scss";
|
||||
|
||||
a {
|
||||
color:rgb(255, 255, 255);
|
||||
|
||||
&:visited, &:hover, &:focus {
|
||||
text-decoration: none;
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
&:active {
|
||||
color: rgb(192, 192, 192)
|
||||
}
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
gap: 16px;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
overflow-x: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 0.3rem;
|
||||
}
|
||||
|
||||
.pagination button {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 1.15rem;
|
||||
border: none;
|
||||
border-radius: 0.35rem;
|
||||
background-color: #33333350;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pagination button:disabled {
|
||||
background-color: #5555559a;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.pagination-dots {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: #bbb;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dot.active {
|
||||
background-color: #333;
|
||||
}
|
87
web/src/app/mapfixes/(styles)/page/card.scss
Normal file
87
web/src/app/mapfixes/(styles)/page/card.scss
Normal file
@ -0,0 +1,87 @@
|
||||
@use "../../../globals.scss";
|
||||
|
||||
.submissionCard {
|
||||
display: flex;
|
||||
background-color: #2020207c;
|
||||
border: 1px solid #97979783;
|
||||
border-radius: 10px;
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
min-width: 180px;
|
||||
max-width: 340px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.details {
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 3px 0px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 3px 0px;
|
||||
}
|
||||
|
||||
.map-image {
|
||||
border-radius: 10px 10px 0 0;
|
||||
overflow: hidden;
|
||||
|
||||
> img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.displayName {
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
max-width: 70%;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.rating {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.author {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.author span {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.rating {
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
19
web/src/app/mapfixes/[mapfixId]/(styles)/page.scss
Normal file
19
web/src/app/mapfixes/[mapfixId]/(styles)/page.scss
Normal file
@ -0,0 +1,19 @@
|
||||
@forward "./page/commentWindow.scss";
|
||||
@forward "./page/reviewStatus.scss";
|
||||
@forward "./page/ratingWindow.scss";
|
||||
@forward "./page/reviewButtons.scss";
|
||||
@forward "./page/comments.scss";
|
||||
@forward "./page/review.scss";
|
||||
@forward "./page/map.scss";
|
||||
|
||||
@use "../../../globals.scss";
|
||||
|
||||
.map-page-main {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.by-creator {
|
||||
margin-top: 10px;
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
@use "../../../../globals.scss";
|
||||
|
||||
#comment-text-field {
|
||||
@include globals.border-with-radius;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background-color: var(--comment-area)
|
||||
}
|
||||
|
||||
.leave-comment-window {
|
||||
@include globals.border-with-radius;
|
||||
width: 100%;
|
||||
height: 230px;
|
||||
margin-top: 35px;
|
||||
|
||||
.rating-type {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
gap: 35%;
|
||||
|
||||
.rating-right {
|
||||
display: grid;
|
||||
|
||||
> span {
|
||||
margin: 6px 0 6px 0;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 15px 0 15px 0;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: var(--window-header);
|
||||
border-bottom: globals.$review-border;
|
||||
height: 45px;
|
||||
|
||||
p {
|
||||
font-weight: bold;
|
||||
margin: 0 0 0 20px;
|
||||
}
|
||||
}
|
||||
main {
|
||||
padding: 20px;
|
||||
|
||||
button {
|
||||
margin-top: 9px;
|
||||
}
|
||||
}
|
||||
}
|
49
web/src/app/mapfixes/[mapfixId]/(styles)/page/comments.scss
Normal file
49
web/src/app/mapfixes/[mapfixId]/(styles)/page/comments.scss
Normal file
@ -0,0 +1,49 @@
|
||||
$comments-size: 60px;
|
||||
|
||||
.comments {
|
||||
display: grid;
|
||||
gap: 25px;
|
||||
margin-top: 20px;
|
||||
|
||||
.no-comments {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.commenter {
|
||||
display: flex;
|
||||
height: $comments-size;
|
||||
|
||||
//BhopMaptest comment
|
||||
&[data-highlighted="true"] {
|
||||
background-color: var(--comment-highlighted);
|
||||
}
|
||||
> img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.name {
|
||||
font: {
|
||||
weight: 500;
|
||||
size: 1.3em;
|
||||
};
|
||||
}
|
||||
.date {
|
||||
font-size: .8em;
|
||||
margin: 0 0 0 5px;
|
||||
color: #646464
|
||||
}
|
||||
.details {
|
||||
display: grid;
|
||||
margin-left: 10px;
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
p:not(.date) {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
web/src/app/mapfixes/[mapfixId]/(styles)/page/map.scss
Normal file
15
web/src/app/mapfixes/[mapfixId]/(styles)/page/map.scss
Normal file
@ -0,0 +1,15 @@
|
||||
@use "../../../../globals.scss";
|
||||
|
||||
.map-image-area {
|
||||
@include globals.border-with-radius;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 350px;
|
||||
height: 350px;
|
||||
|
||||
> p {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
@use "../../../../globals.scss";
|
||||
|
||||
.rating-window {
|
||||
@include globals.border-with-radius;
|
||||
width: 100%;
|
||||
|
||||
.rating-type {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
gap: 35%;
|
||||
|
||||
.rating-right {
|
||||
display: grid;
|
||||
|
||||
> span {
|
||||
margin: 6px 0 6px 0;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 15px 0 15px 0;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: var(--window-header);
|
||||
border-bottom: globals.$review-border;
|
||||
height: 45px;
|
||||
|
||||
p {
|
||||
font-weight: bold;
|
||||
margin: 0 0 0 20px;
|
||||
}
|
||||
}
|
||||
main {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
}
|
47
web/src/app/mapfixes/[mapfixId]/(styles)/page/review.scss
Normal file
47
web/src/app/mapfixes/[mapfixId]/(styles)/page/review.scss
Normal file
@ -0,0 +1,47 @@
|
||||
@use "../../../../globals.scss";
|
||||
|
||||
.review-info {
|
||||
width: 650px;
|
||||
height: 100%;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
p, h1 {
|
||||
color: var(--text-color);
|
||||
}
|
||||
h1 {
|
||||
font: {
|
||||
weight: 500;
|
||||
size: 1.8rem
|
||||
};
|
||||
margin: 0;
|
||||
}
|
||||
a {
|
||||
color: var(--anchor-link-review);
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.review-section {
|
||||
display: flex;
|
||||
gap: 50px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.review-area {
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
gap: 25px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 350px;
|
||||
object-fit: contain
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
@use "../../../../globals.scss";
|
||||
|
||||
.review-set {
|
||||
@include globals.border-with-radius;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
$UnderConstruction: "0";
|
||||
$Submitted: "1";
|
||||
$ChangesRequested: "2";
|
||||
$Accepted: "3";
|
||||
$Validating: "4";
|
||||
$Validated: "5";
|
||||
$Uploading: "6";
|
||||
$Uploaded: "7";
|
||||
$Rejected: "8";
|
||||
$Released: "9";
|
||||
|
||||
.review-status {
|
||||
border-radius: 5px;
|
||||
|
||||
p {
|
||||
margin: 3px 25px 3px 25px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&[data-review-status="#{$Released}"] {
|
||||
background-color: orange;
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
&[data-review-status="#{$Rejected}"] {
|
||||
background-color: orange;
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
&[data-review-status="#{$Uploading}"] {
|
||||
background-color: orange;
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
&[data-review-status="#{$Uploaded}"] {
|
||||
background-color: orange;
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
&[data-review-status="#{$Validated}"] {
|
||||
background-color: orange;
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
&[data-review-status="#{$Validating}"] {
|
||||
background-color: orange;
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
&[data-review-status="#{$Accepted}"] {
|
||||
background-color: rgb(2, 162, 2);
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
&[data-review-status="#{$ChangesRequested}"] {
|
||||
background-color: orange;
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
&[data-review-status="#{$Submitted}"] {
|
||||
background-color: orange;
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
&[data-review-status="#{$UnderConstruction}"] {
|
||||
background-color: orange;
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
68
web/src/app/mapfixes/[mapfixId]/_comments.tsx
Normal file
68
web/src/app/mapfixes/[mapfixId]/_comments.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import type { MapfixInfo } from "@/app/ts/Mapfix";
|
||||
import { Button } from "@mui/material"
|
||||
import Window from "./_window";
|
||||
import SendIcon from '@mui/icons-material/Send';
|
||||
import Image from "next/image";
|
||||
|
||||
interface CommentersProps {
|
||||
comments_data: CreatorAndReviewStatus
|
||||
}
|
||||
|
||||
interface CreatorAndReviewStatus {
|
||||
asset_id: MapfixInfo["AssetID"],
|
||||
creator: MapfixInfo["DisplayName"],
|
||||
review: MapfixInfo["StatusID"],
|
||||
status_message: MapfixInfo["StatusMessage"],
|
||||
comments: Comment[],
|
||||
name: string
|
||||
}
|
||||
|
||||
interface Comment {
|
||||
picture?: string, //TEMP
|
||||
comment: string,
|
||||
date: string,
|
||||
name: string
|
||||
}
|
||||
|
||||
function AddComment(comment: Comment) {
|
||||
const IsBhopMaptest = comment.name == "BhopMaptest" //Highlighted commenter
|
||||
|
||||
return (
|
||||
<div className="commenter" data-highlighted={IsBhopMaptest}>
|
||||
<Image src={comment.picture as string} alt={`${comment.name}'s comment`}/>
|
||||
<div className="details">
|
||||
<header>
|
||||
<p className="name">{comment.name}</p>
|
||||
<p className="date">{comment.date}</p>
|
||||
</header>
|
||||
<p className="comment">{comment.comment}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LeaveAComment() {
|
||||
return (
|
||||
<Window title="Leave a Comment:" className="leave-comment-window">
|
||||
<textarea name="comment-box" id="comment-text-field"></textarea>
|
||||
<Button variant="outlined" endIcon={<SendIcon/>}>Submit</Button>
|
||||
</Window>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Comments(stats: CommentersProps) {
|
||||
return (<>
|
||||
<section className="comments">
|
||||
{stats.comments_data.comments.length===0
|
||||
&& <p className="no-comments">There are no comments.</p>
|
||||
|| stats.comments_data.comments.map(comment => (
|
||||
<AddComment key={comment.name} name={comment.name} date={comment.date} comment={comment.comment}/>
|
||||
))}
|
||||
</section>
|
||||
<LeaveAComment/>
|
||||
</>)
|
||||
}
|
||||
|
||||
export {
|
||||
type CreatorAndReviewStatus
|
||||
}
|
14
web/src/app/mapfixes/[mapfixId]/_map.tsx
Normal file
14
web/src/app/mapfixes/[mapfixId]/_map.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { MapfixInfo } from "@/app/ts/Mapfix"
|
||||
|
||||
interface AssetID {
|
||||
id: MapfixInfo["AssetID"]
|
||||
}
|
||||
|
||||
function MapImage() {
|
||||
return <p>Fetching map image...</p>
|
||||
}
|
||||
|
||||
export {
|
||||
type AssetID,
|
||||
MapImage
|
||||
}
|
74
web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx
Normal file
74
web/src/app/mapfixes/[mapfixId]/_reviewButtons.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import { Button, ButtonOwnProps } from "@mui/material";
|
||||
|
||||
type Actions = "Completed" | "Submit" | "Reject" | "Revoke"
|
||||
type ApiActions = Lowercase<Actions> | "trigger-validate" | "retry-validate" | "trigger-upload" | "reset-uploading" | "reset-validating"
|
||||
type Review = Actions | "Accept" | "Validate" | "Upload" | "Reset Uploading (fix softlocked status)" | "Reset Validating (fix softlocked status)" | "Request Changes"
|
||||
|
||||
interface ReviewButton {
|
||||
name: Review,
|
||||
action: ApiActions,
|
||||
mapfixId: string,
|
||||
color: ButtonOwnProps["color"]
|
||||
}
|
||||
|
||||
interface ReviewId {
|
||||
mapfixId: string
|
||||
}
|
||||
|
||||
async function ReviewButtonClicked(action: ApiActions, mapfixId: string) {
|
||||
try {
|
||||
const response = await fetch(`/api/mapfixes/${mapfixId}/status/${action}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-type": "application/json",
|
||||
}
|
||||
});
|
||||
// Check if the HTTP request was successful
|
||||
if (!response.ok) {
|
||||
const errorDetails = await response.text();
|
||||
|
||||
// Throw an error with detailed information
|
||||
throw new Error(`HTTP error! status: ${response.status}, details: ${errorDetails}`);
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
console.error("Error updating mapfix status:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function ReviewButton(props: ReviewButton) {
|
||||
return <Button
|
||||
color={props.color}
|
||||
variant="contained"
|
||||
onClick={() => { ReviewButtonClicked(props.action, props.mapfixId) }}>{props.name}</Button>
|
||||
}
|
||||
|
||||
export default function ReviewButtons(props: ReviewId) {
|
||||
const mapfixId = props.mapfixId
|
||||
// When is each button visible?
|
||||
// Multiple buttons can be visible at once.
|
||||
// Action | Role | When Current Status is One of:
|
||||
// ---------------|-----------|-----------------------
|
||||
// Submit | Submitter | UnderConstruction, ChangesRequested
|
||||
// Revoke | Submitter | Submitted, ChangesRequested
|
||||
// Accept | Reviewer | Submitted
|
||||
// Validate | Reviewer | Accepted
|
||||
// ResetValidating| Reviewer | Validating
|
||||
// Reject | Reviewer | Submitted
|
||||
// RequestChanges | Reviewer | Validated, Accepted, Submitted
|
||||
// Upload | MapAdmin | Validated
|
||||
// ResetUploading | MapAdmin | Uploading
|
||||
return (
|
||||
<section className="review-set">
|
||||
<ReviewButton color="info" name="Submit" action="submit" mapfixId={mapfixId}/>
|
||||
<ReviewButton color="info" name="Revoke" action="revoke" mapfixId={mapfixId}/>
|
||||
<ReviewButton color="info" name="Accept" action="trigger-validate" mapfixId={mapfixId}/>
|
||||
<ReviewButton color="info" name="Validate" action="retry-validate" mapfixId={mapfixId}/>
|
||||
<ReviewButton color="error" name="Reject" action="reject" mapfixId={mapfixId}/>
|
||||
<ReviewButton color="info" name="Upload" action="trigger-upload" mapfixId={mapfixId}/>
|
||||
<ReviewButton color="error" name="Reset Uploading (fix softlocked status)" action="reset-uploading" mapfixId={mapfixId}/>
|
||||
<ReviewButton color="error" name="Reset Validating (fix softlocked status)" action="reset-validating" mapfixId={mapfixId}/>
|
||||
</section>
|
||||
)
|
||||
}
|
20
web/src/app/mapfixes/[mapfixId]/_window.tsx
Normal file
20
web/src/app/mapfixes/[mapfixId]/_window.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
interface WindowStruct {
|
||||
className: string,
|
||||
title: string,
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export default function Window(window: WindowStruct) {
|
||||
return (
|
||||
<section className={window.className}>
|
||||
<header>
|
||||
<p>{window.title}</p>
|
||||
</header>
|
||||
<main>{window.children}</main>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
type WindowStruct
|
||||
}
|
105
web/src/app/mapfixes/[mapfixId]/page.tsx
Normal file
105
web/src/app/mapfixes/[mapfixId]/page.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
"use client"
|
||||
|
||||
import { MapfixInfo, MapfixStatusToString } from "@/app/ts/Mapfix";
|
||||
import type { CreatorAndReviewStatus } from "./_comments";
|
||||
import { MapImage } from "./_map";
|
||||
import { useParams } from "next/navigation";
|
||||
import ReviewButtons from "./_reviewButtons";
|
||||
import { Rating } from "@mui/material";
|
||||
import Comments from "./_comments";
|
||||
import Webpage from "@/app/_components/webpage";
|
||||
import Window from "./_window";
|
||||
import Link from "next/link";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import "./(styles)/page.scss";
|
||||
|
||||
interface ReviewId {
|
||||
mapfixId: string
|
||||
}
|
||||
|
||||
function Ratings() {
|
||||
return (
|
||||
<Window className="rating-window" title="Rating">
|
||||
<section className="rating-type">
|
||||
<aside className="rating-left">
|
||||
<p>Quality</p>
|
||||
<p>Difficulty</p>
|
||||
<p>Fun</p>
|
||||
<p>Length</p>
|
||||
</aside>
|
||||
<aside className="rating-right">
|
||||
<Rating defaultValue={2.5} precision={0.5}/>
|
||||
<Rating defaultValue={2.5} precision={0.5}/>
|
||||
<Rating defaultValue={2.5} precision={0.5}/>
|
||||
<Rating defaultValue={2.5} precision={0.5}/>
|
||||
</aside>
|
||||
</section>
|
||||
</Window>
|
||||
)
|
||||
}
|
||||
|
||||
function RatingArea(mapfix: ReviewId) {
|
||||
return (
|
||||
<aside className="review-area">
|
||||
<section className="map-image-area">
|
||||
<MapImage/>
|
||||
</section>
|
||||
<Ratings/>
|
||||
<ReviewButtons mapfixId={mapfix.mapfixId}/>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
function TitleAndComments(stats: CreatorAndReviewStatus) {
|
||||
const Review = MapfixStatusToString(stats.review)
|
||||
|
||||
// TODO: hide status message when status is not "Accepted"
|
||||
return (
|
||||
<main className="review-info">
|
||||
<div>
|
||||
<h1>{stats.name}</h1>
|
||||
<aside data-review-status={stats.review} className="review-status">
|
||||
<p>{Review}</p>
|
||||
</aside>
|
||||
</div>
|
||||
<p className="by-creator">by <Link href="" target="_blank">{stats.creator}</Link></p>
|
||||
<p className="asset-id">Model Asset ID {stats.asset_id}</p>
|
||||
<p className="status-message">Validation Error: {stats.status_message}</p>
|
||||
<span className="spacer"></span>
|
||||
<Comments comments_data={stats}/>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
export default function MapfixInfoPage() {
|
||||
const dynamicId = useParams<{mapfixId: string}>()
|
||||
|
||||
const [mapfix, setMapfix] = useState<MapfixInfo | null>(null)
|
||||
|
||||
useEffect(() => { // needs to be client sided since server doesn't have a session, nextjs got mad at me for exporting an async function: (https://nextjs.org/docs/messages/no-async-client-component)
|
||||
async function getMapfix() {
|
||||
const res = await fetch(`/api/mapfixes/${dynamicId.mapfixId}`)
|
||||
if (res.ok) {
|
||||
setMapfix(await res.json())
|
||||
}
|
||||
}
|
||||
getMapfix()
|
||||
}, [dynamicId.mapfixId])
|
||||
|
||||
if (!mapfix) {
|
||||
return <Webpage>
|
||||
{/* TODO: Add skeleton loading thingy ? Maybe ? (https://mui.com/material-ui/react-skeleton/) */}
|
||||
</Webpage>
|
||||
}
|
||||
return (
|
||||
<Webpage>
|
||||
<main className="map-page-main">
|
||||
<section className="review-section">
|
||||
<RatingArea mapfixId={dynamicId.mapfixId}/>
|
||||
<TitleAndComments name={mapfix.DisplayName} creator={mapfix.Creator} review={mapfix.StatusID} status_message={mapfix.StatusMessage} asset_id={mapfix.AssetID} comments={[]}/>
|
||||
</section>
|
||||
</main>
|
||||
</Webpage>
|
||||
)
|
||||
}
|
41
web/src/app/mapfixes/_card.tsx
Normal file
41
web/src/app/mapfixes/_card.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { Rating } from "@mui/material";
|
||||
|
||||
interface SubmissionCardProps {
|
||||
displayName: string;
|
||||
assetId: number;
|
||||
rating: number;
|
||||
author: string;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export default function SubmissionCard(props: SubmissionCardProps) {
|
||||
return (
|
||||
<Link href={`/submissions/${props.id}`}>
|
||||
<div className="submissionCard">
|
||||
<div className="content">
|
||||
<div className="map-image">
|
||||
{/* TODO: Grab image of model */}
|
||||
<Image height={200} width={200} priority={true} src="https://api.ic3.space/strafe/map-images/11222350808" style={{ width: `100%` }} alt={props.displayName} />
|
||||
</div>
|
||||
<div className="details">
|
||||
<div className="header">
|
||||
<span className="displayName">{props.displayName}</span>
|
||||
<div className="rating">
|
||||
<Rating value={props.rating} readOnly size="small" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="footer">
|
||||
<div className="author">
|
||||
<Image className="avatar" width={28} height={28} priority={true} src="https://api.ic3.space/strafe/map-images/11222350808" alt={props.author}/>
|
||||
<span>{props.author}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
18
web/src/app/mapfixes/_window.tsx
Normal file
18
web/src/app/mapfixes/_window.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
interface WindowStruct {
|
||||
children: React.ReactNode,
|
||||
className: string,
|
||||
title: string,
|
||||
}
|
||||
|
||||
export default function Window(window: WindowStruct) {
|
||||
return <section className={window.className}>
|
||||
<header>
|
||||
<p>{window.title}</p>
|
||||
</header>
|
||||
<main>{window.children}</main>
|
||||
</section>
|
||||
}
|
||||
|
||||
export {
|
||||
type WindowStruct
|
||||
}
|
110
web/src/app/mapfixes/page.tsx
Normal file
110
web/src/app/mapfixes/page.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { MapfixInfo } from "../ts/Mapfix";
|
||||
import MapfixCard from "./_card";
|
||||
import Webpage from "@/app/_components/webpage";
|
||||
|
||||
import "./(styles)/page.scss";
|
||||
|
||||
export default function MapfixInfoPage() {
|
||||
const [mapfixes, setMapfixes] = useState<MapfixInfo[]>([])
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
const cardsPerPage = 24; // built to fit on a 1920x1080 monitor
|
||||
|
||||
const totalPages = Math.ceil(mapfixes.length / cardsPerPage);
|
||||
|
||||
const currentCards = mapfixes.slice(
|
||||
currentPage * cardsPerPage,
|
||||
(currentPage + 1) * cardsPerPage
|
||||
);
|
||||
|
||||
const nextPage = () => {
|
||||
if (currentPage < totalPages - 1) {
|
||||
setCurrentPage(currentPage + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const prevPage = () => {
|
||||
if (currentPage > 0) {
|
||||
setCurrentPage(currentPage - 1);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchMapfixes() {
|
||||
const res = await fetch('/api/mapfixes?Page=1&Limit=100')
|
||||
if (res.ok) {
|
||||
setMapfixes(await res.json())
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
fetchMapfixes()
|
||||
}, 50);
|
||||
}, [])
|
||||
|
||||
if (!mapfixes) {
|
||||
return <Webpage>
|
||||
<main>
|
||||
Loading...
|
||||
</main>
|
||||
</Webpage>
|
||||
}
|
||||
|
||||
if (mapfixes && mapfixes.length == 0) {
|
||||
return <Webpage>
|
||||
<main>
|
||||
Mapfixes list is empty.
|
||||
</main>
|
||||
</Webpage>
|
||||
}
|
||||
|
||||
return (
|
||||
// TODO: Add filter settings & searchbar & page selector
|
||||
<Webpage>
|
||||
<main
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
padding: '1rem',
|
||||
width: '100%',
|
||||
maxWidth: '100vw',
|
||||
boxSizing: 'border-box',
|
||||
overflowX: 'hidden'
|
||||
}}
|
||||
>
|
||||
<div className="pagination-dots">
|
||||
{Array.from({ length: totalPages }).map((_, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className={`dot ${index === currentPage ? 'active' : ''}`}
|
||||
onClick={() => setCurrentPage(index)}
|
||||
></span>
|
||||
))}
|
||||
</div>
|
||||
<div className="pagination">
|
||||
<button onClick={prevPage} disabled={currentPage === 0}><</button>
|
||||
<span>
|
||||
Page {currentPage + 1} of {totalPages}
|
||||
</span>
|
||||
<button onClick={nextPage} disabled={currentPage === totalPages - 1}>></button>
|
||||
</div>
|
||||
<div className="grid">
|
||||
{currentCards.map((mapfix) => (
|
||||
<MapfixCard
|
||||
key={mapfix.ID}
|
||||
id={mapfix.ID}
|
||||
assetId={mapfix.AssetID}
|
||||
displayName={mapfix.DisplayName}
|
||||
author={mapfix.Creator}
|
||||
rating={mapfix.StatusID}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
</Webpage>
|
||||
)
|
||||
}
|
54
web/src/app/maps/[mapId]/fix/(styles)/page.scss
Normal file
54
web/src/app/maps/[mapId]/fix/(styles)/page.scss
Normal file
@ -0,0 +1,54 @@
|
||||
@use "../../../../globals.scss";
|
||||
|
||||
::placeholder {
|
||||
color: var(--placeholder-text)
|
||||
}
|
||||
|
||||
.form-spacer {
|
||||
margin-bottom: 20px;
|
||||
|
||||
&:last-of-type {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
#target-asset-radio {
|
||||
color: var(--text-color);
|
||||
font-size: globals.$form-label-fontsize;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
width: 850px;
|
||||
|
||||
& label, & input {
|
||||
color: var(--text-color);
|
||||
}
|
||||
& fieldset {
|
||||
border-color: rgb(100,100,100);
|
||||
}
|
||||
& span {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-inline: auto;
|
||||
width: 700px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
text-align: center;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
form {
|
||||
display: grid;
|
||||
gap: 25px;
|
||||
|
||||
fieldset {
|
||||
border: blue
|
||||
}
|
||||
}
|
65
web/src/app/maps/[mapId]/fix/_game.tsx
Normal file
65
web/src/app/maps/[mapId]/fix/_game.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import { FormControl, Select, InputLabel, MenuItem } from "@mui/material";
|
||||
import { styled } from '@mui/material/styles';
|
||||
import InputBase from '@mui/material/InputBase';
|
||||
import React from "react";
|
||||
import { SelectChangeEvent } from "@mui/material";
|
||||
|
||||
// TODO: Properly style everything instead of pasting 🤚
|
||||
|
||||
type GameSelectionProps = {
|
||||
game: number;
|
||||
setGame: React.Dispatch<React.SetStateAction<number>>;
|
||||
};
|
||||
|
||||
const BootstrapInput = styled(InputBase)(({ theme }) => ({
|
||||
'label + &': {
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
'& .MuiInputBase-input': {
|
||||
backgroundColor: '#0000',
|
||||
color: '#FFF',
|
||||
border: '1px solid rgba(175, 175, 175, 0.66)',
|
||||
fontSize: 16,
|
||||
padding: '10px 26px 10px 12px',
|
||||
transition: theme.transitions.create(['border-color', 'box-shadow']),
|
||||
fontFamily: [
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'"Segoe UI"',
|
||||
'Roboto',
|
||||
'"Helvetica Neue"',
|
||||
'Arial',
|
||||
'sans-serif',
|
||||
'"Apple Color Emoji"',
|
||||
'"Segoe UI Emoji"',
|
||||
'"Segoe UI Symbol"',
|
||||
].join(','),
|
||||
'&:focus': {
|
||||
borderRadius: 4,
|
||||
borderColor: '#80bdff',
|
||||
boxShadow: '0 0 0 0.2rem rgba(0,123,255,.25)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
export default function GameSelection({ game, setGame }: GameSelectionProps) {
|
||||
const handleChange = (event: SelectChangeEvent) => {
|
||||
setGame(Number(event.target.value)); // TODO: Change later!! there's 100% a proper way of doing this
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InputLabel sx={{ color: "#646464" }}>Game</InputLabel>
|
||||
<Select
|
||||
value={String(game)}
|
||||
label="Game"
|
||||
onChange={handleChange}
|
||||
input={<BootstrapInput />}
|
||||
>
|
||||
<MenuItem value={1}>Bhop</MenuItem>
|
||||
<MenuItem value={2}>Surf</MenuItem>
|
||||
<MenuItem value={3}>Fly Trials</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
98
web/src/app/maps/[mapId]/fix/page.tsx
Normal file
98
web/src/app/maps/[mapId]/fix/page.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
"use client"
|
||||
|
||||
import { Button, TextField } from "@mui/material"
|
||||
|
||||
import GameSelection from "./_game";
|
||||
import SendIcon from '@mui/icons-material/Send';
|
||||
import Webpage from "@/app/_components/webpage";
|
||||
import { useParams } from "next/navigation";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import "./(styles)/page.scss"
|
||||
|
||||
interface MapfixPayload {
|
||||
DisplayName: string;
|
||||
Creator: string;
|
||||
GameID: number;
|
||||
AssetID: number;
|
||||
AssetVersion: number;
|
||||
TargetAssetID: number;
|
||||
}
|
||||
interface IdResponse {
|
||||
ID: number;
|
||||
}
|
||||
|
||||
export default function MapfixInfoPage() {
|
||||
const [game, setGame] = useState(1);
|
||||
const dynamicId = useParams<{ mapId: string }>();
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
const form = event.currentTarget;
|
||||
const formData = new FormData(form);
|
||||
|
||||
const payload: MapfixPayload = {
|
||||
DisplayName: (formData.get("display-name") as string) ?? "unknown", // TEMPORARY! TODO: Change
|
||||
Creator: (formData.get("creator") as string) ?? "unknown", // TEMPORARY! TODO: Change
|
||||
GameID: game,
|
||||
AssetID: Number((formData.get("asset-id") as string) ?? "0"),
|
||||
AssetVersion: 0,
|
||||
TargetAssetID: Number(dynamicId.mapId),
|
||||
};
|
||||
|
||||
console.log(payload)
|
||||
console.log(JSON.stringify(payload))
|
||||
|
||||
try {
|
||||
// Send the POST request
|
||||
const response = await fetch("/api/mapfixes", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
// Check if the HTTP request was successful
|
||||
if (!response.ok) {
|
||||
const errorDetails = await response.text();
|
||||
|
||||
// Throw an error with detailed information
|
||||
throw new Error(`HTTP error! status: ${response.status}, details: ${errorDetails}`);
|
||||
}
|
||||
|
||||
// Allow any HTTP status
|
||||
const id_response:IdResponse = await response.json();
|
||||
|
||||
// navigate to newly created mapfix
|
||||
window.location.assign(`/mapfixes/${id_response.ID}`)
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error submitting data:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Webpage>
|
||||
<main>
|
||||
<header>
|
||||
<h1>Submit Mapfix</h1>
|
||||
<span className="spacer form-spacer"></span>
|
||||
</header>
|
||||
<form onSubmit={handleSubmit}>
|
||||
{/* TODO: Add form data for mapfixes, such as changes they did, and any times that need to be deleted & what styles */}
|
||||
<TextField className="form-field" id="display-name" name="display-name" label="Display Name" variant="outlined"/>
|
||||
<TextField className="form-field" id="creator" name="creator" label="Creator" variant="outlined"/>
|
||||
<TextField className="form-field" id="asset-id" name="asset-id" label="Asset ID" variant="outlined"/>
|
||||
{/* I think this is Quat's job to figure this one out (to be set when someone clicks review(?)) */} {/* <TextField className="form-field" id="asset-version" label="Asset Version" variant="outlined"/> */}
|
||||
<GameSelection game={game} setGame={setGame} />
|
||||
<span className="spacer form-spacer"></span>
|
||||
<Button type="submit" variant="contained" startIcon={<SendIcon/>} sx={{
|
||||
width: "400px",
|
||||
height: "50px",
|
||||
marginInline: "auto"
|
||||
}}>Submit</Button>
|
||||
</form>
|
||||
</main>
|
||||
</Webpage>
|
||||
)
|
||||
}
|
@ -71,7 +71,7 @@ export default function SubmissionInfoPage() {
|
||||
<Webpage>
|
||||
<main>
|
||||
<header>
|
||||
<h1>Submit Asset</h1>
|
||||
<h1>Submit New Map</h1>
|
||||
<span className="spacer form-spacer"></span>
|
||||
</header>
|
||||
<form onSubmit={handleSubmit}>
|
||||
|
59
web/src/app/ts/Mapfix.ts
Normal file
59
web/src/app/ts/Mapfix.ts
Normal file
@ -0,0 +1,59 @@
|
||||
const enum MapfixStatus {
|
||||
UnderConstruction = 0,
|
||||
Submitted = 1,
|
||||
ChangesRequested = 2,
|
||||
Accepted = 3,
|
||||
Validating = 4,
|
||||
Validated = 5,
|
||||
Uploading = 6,
|
||||
Uploaded = 7,
|
||||
Rejected = 8,
|
||||
}
|
||||
|
||||
interface MapfixInfo {
|
||||
readonly ID: number,
|
||||
readonly DisplayName: string,
|
||||
readonly Creator: string,
|
||||
readonly GameID: number,
|
||||
readonly Date: number,
|
||||
readonly Submitter: number,
|
||||
readonly AssetID: number,
|
||||
readonly AssetVersion: number,
|
||||
readonly ValidatedAssetID: number,
|
||||
readonly ValidatedAssetVersion: number,
|
||||
readonly Completed: boolean,
|
||||
readonly TargetAssetID: number,
|
||||
readonly StatusID: MapfixStatus
|
||||
readonly StatusMessage: string,
|
||||
}
|
||||
|
||||
function MapfixStatusToString(mapfix_status: MapfixStatus): string {
|
||||
switch (mapfix_status) {
|
||||
case MapfixStatus.Rejected:
|
||||
return "REJECTED"
|
||||
case MapfixStatus.Uploading:
|
||||
return "UPLOADING"
|
||||
case MapfixStatus.Uploaded:
|
||||
return "UPLOADED"
|
||||
case MapfixStatus.Validated:
|
||||
return "VALIDATED"
|
||||
case MapfixStatus.Validating:
|
||||
return "VALIDATING"
|
||||
case MapfixStatus.Accepted:
|
||||
return "ACCEPTED"
|
||||
case MapfixStatus.ChangesRequested:
|
||||
return "CHANGES REQUESTED"
|
||||
case MapfixStatus.Submitted:
|
||||
return "SUBMITTED"
|
||||
case MapfixStatus.UnderConstruction:
|
||||
return "UNDER CONSTRUCTION"
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
MapfixStatus,
|
||||
MapfixStatusToString,
|
||||
type MapfixInfo
|
||||
}
|
Reference in New Issue
Block a user