roles
This commit is contained in:
parent
8c8cbfd7dd
commit
41d48bf60a
28 changed files with 434 additions and 7 deletions
0
auth/discordauth.go
Normal file
0
auth/discordauth.go
Normal file
|
@ -7,7 +7,7 @@ import (
|
|||
"net/http"
|
||||
"testing"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/authmiddleware"
|
||||
"git.sapphic.engineer/roleypoly/v4/auth/authmiddleware"
|
||||
"git.sapphic.engineer/roleypoly/v4/discord"
|
||||
"git.sapphic.engineer/roleypoly/v4/interactions"
|
||||
"git.sapphic.engineer/roleypoly/v4/roleypoly"
|
||||
|
|
10
justfile
10
justfile
|
@ -12,10 +12,13 @@ run-container:
|
|||
docker load -i result
|
||||
docker run -it --rm -p 8169:8169 localhost/roleypoly/roleypoly
|
||||
|
||||
precommit: fmt tidy update-vendor-hash test
|
||||
precommit: fmt prettier tidy update-vendor-hash test
|
||||
|
||||
fmt:
|
||||
go fmt ./...
|
||||
|
||||
prettier:
|
||||
prettier -w -c **/*.{css,html,json,md}
|
||||
|
||||
tidy:
|
||||
go mod tidy
|
||||
|
@ -27,4 +30,7 @@ test:
|
|||
go test ./...
|
||||
|
||||
clean-repo:
|
||||
rm -rf tmp result
|
||||
rm -rf tmp result
|
||||
|
||||
open-chromium path="/testing/t/picker":
|
||||
nix-shell -p chromium --command "chromium http://localhost:8170{{path}}"
|
58
presentation/role.go
Normal file
58
presentation/role.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package presentation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/types"
|
||||
"git.sapphic.engineer/roleypoly/v4/utils"
|
||||
)
|
||||
|
||||
type InputType string
|
||||
|
||||
const (
|
||||
InputCheckbox InputType = "checkbox"
|
||||
InputRadio InputType = "radio"
|
||||
)
|
||||
|
||||
type PresentableRole struct {
|
||||
ID string
|
||||
CategoryID string
|
||||
Name string
|
||||
Selected bool
|
||||
InputType InputType
|
||||
Colors PresentableRoleColors
|
||||
}
|
||||
|
||||
func Role(category *types.Category, role *types.Role, selected bool) PresentableRole {
|
||||
inputType := InputCheckbox
|
||||
if category.Type == types.CategorySingle {
|
||||
inputType = InputRadio
|
||||
}
|
||||
|
||||
colors := GetColors(role.Color)
|
||||
|
||||
return PresentableRole{
|
||||
ID: role.ID,
|
||||
CategoryID: category.ID,
|
||||
Name: role.Name,
|
||||
Selected: selected,
|
||||
InputType: inputType,
|
||||
Colors: colors,
|
||||
}
|
||||
}
|
||||
|
||||
type PresentableRoleColors struct {
|
||||
Main string
|
||||
IsDark bool
|
||||
}
|
||||
|
||||
func GetColors(roleColor uint32) PresentableRoleColors {
|
||||
// TODO: no color
|
||||
|
||||
r, g, b := utils.IntToRgb(roleColor)
|
||||
|
||||
return PresentableRoleColors{
|
||||
Main: fmt.Sprintf("#%x", roleColor),
|
||||
IsDark: utils.IsDarkColor(r, g, b),
|
||||
}
|
||||
}
|
33
presentation/role_test.go
Normal file
33
presentation/role_test.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package presentation_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/presentation"
|
||||
"git.sapphic.engineer/roleypoly/v4/types/fixtures"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRole(t *testing.T) {
|
||||
r := presentation.Role(&fixtures.CategoryMulti, &fixtures.RoleWithDarkColor, true)
|
||||
assert.Equal(t, fixtures.RoleWithDarkColor.ID, r.ID)
|
||||
assert.Equal(t, fixtures.RoleWithDarkColor.Name, r.Name)
|
||||
assert.Equal(t, presentation.InputCheckbox, r.InputType)
|
||||
assert.Equal(t, "#a20000", r.Colors.Main)
|
||||
assert.True(t, r.Colors.IsDark)
|
||||
assert.True(t, r.Selected)
|
||||
|
||||
r = presentation.Role(&fixtures.CategorySingle, &fixtures.RoleWithDarkColor, false)
|
||||
assert.Equal(t, presentation.InputRadio, r.InputType)
|
||||
assert.False(t, r.Selected)
|
||||
|
||||
r = presentation.Role(&fixtures.CategorySingle, &fixtures.RoleWithLightColor, true)
|
||||
assert.False(t, r.Colors.IsDark)
|
||||
assert.True(t, r.Selected)
|
||||
}
|
||||
|
||||
func TestGetColors(t *testing.T) {
|
||||
c := presentation.GetColors(0xa20000)
|
||||
assert.Equal(t, "#a20000", c.Main)
|
||||
assert.True(t, c.IsDark)
|
||||
}
|
|
@ -13,7 +13,7 @@ import (
|
|||
"github.com/gofiber/fiber/v3/middleware/static"
|
||||
"github.com/gofiber/template/html/v2"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/authmiddleware"
|
||||
"git.sapphic.engineer/roleypoly/v4/auth/authmiddleware"
|
||||
"git.sapphic.engineer/roleypoly/v4/discord"
|
||||
"git.sapphic.engineer/roleypoly/v4/interactions"
|
||||
staticfs "git.sapphic.engineer/roleypoly/v4/static"
|
||||
|
|
|
@ -4,5 +4,7 @@
|
|||
just
|
||||
nil
|
||||
air
|
||||
nodePackages.prettier
|
||||
pre-commit
|
||||
];
|
||||
}
|
||||
|
|
4
static/images/check.svg
Normal file
4
static/images/check.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect fill="#000" width="2" height="5" x="3" y="4" style="transform: rotate(-45deg); transform-origin: center;" />
|
||||
<rect fill="#000" width="2" height="7" x="7" y="4.5" style="transform: rotate(45deg); transform-origin: center;" />
|
||||
</svg>
|
After Width: | Height: | Size: 325 B |
|
@ -1,3 +1,94 @@
|
|||
:root {
|
||||
--taupe100: #332d2d;
|
||||
--taupe200: #453e3d;
|
||||
--taupe300: #5d5352;
|
||||
--taupe400: #756867;
|
||||
--taupe500: #ab9b9a;
|
||||
--taupe600: #ebd6d4;
|
||||
|
||||
--discord100: #23272a;
|
||||
--discord200: #2c2f33;
|
||||
--discord400: #7289da;
|
||||
--discord500: #99aab5;
|
||||
|
||||
--green400: #46b646;
|
||||
--green200: #1d8227;
|
||||
|
||||
--red400: #e95353;
|
||||
--red200: #f14343;
|
||||
|
||||
--gold400: #efcf24;
|
||||
|
||||
--grey100: #1c1010;
|
||||
--grey500: #dbd9d9;
|
||||
--grey600: #f2efef;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Atkinson Hyperlegible", sans-serif;
|
||||
background-color: var(--taupe200);
|
||||
color: var(--taupe600);
|
||||
}
|
||||
|
||||
.role {
|
||||
/* TODO: dont do this, don't remember why?? (^aki) */
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border: 2px solid var(--role-color);
|
||||
border-radius: 3px;
|
||||
user-select: none;
|
||||
padding: 0.217rem; /* this is silly number pls ignore :3 (^noe) */
|
||||
gap: 0.215rem;
|
||||
|
||||
input {
|
||||
appearance: none;
|
||||
position: relative;
|
||||
width: 1.216rem;
|
||||
height: 1.216rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 2px solid var(--role-color);
|
||||
transition: all 0.35s ease-in-out;
|
||||
border-radius: 1.216rem;
|
||||
|
||||
&::before {
|
||||
transition: all 0.35s ease-in-out;
|
||||
content: "";
|
||||
mask-image: url(/static/images/check.svg);
|
||||
mask-size: cover;
|
||||
mask-position: center center;
|
||||
background-color: var(--role-color);
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 1.216rem;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
}
|
||||
|
||||
&:has(input:checked) {
|
||||
background-color: var(--role-color);
|
||||
|
||||
input {
|
||||
border-color: var(--contrast-color);
|
||||
opacity: 1;
|
||||
|
||||
&::before {
|
||||
opacity: 1;
|
||||
background-color: var(--contrast-color);
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
color: var(--contrast-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
4
templates/components/nav.html
Normal file
4
templates/components/nav.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
<nav>
|
||||
<div class="logo">Roleypoly</div>
|
||||
<div class="username">41666</div>
|
||||
</nav>
|
10
templates/components/role.html
Normal file
10
templates/components/role.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
{{ $for := printf "category-%s_role-%s" .CategoryID .ID }}
|
||||
<div
|
||||
class="role"
|
||||
style="--role-color: {{.Colors.Main}}; --contrast-color: var({{if .Colors.IsDark}}--grey600{{else}}--grey100{{end}});"
|
||||
data-testid="role-{{.ID}}"
|
||||
>
|
||||
<input type="{{.InputType}}" id="{{$for}}" {{if eq .InputType
|
||||
"radio"}}name="category_group_{{.CategoryID}}"{{end}} />
|
||||
<label for="{{$for}}">{{.Name}}</label>
|
||||
</div>
|
57
templates/components/role_test.go
Normal file
57
templates/components/role_test.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package components_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"testing"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/presentation"
|
||||
"git.sapphic.engineer/roleypoly/v4/templates"
|
||||
"git.sapphic.engineer/roleypoly/v4/types"
|
||||
"git.sapphic.engineer/roleypoly/v4/types/fixtures"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
Templates = template.Must(template.ParseFS(templates.FS, "components/*.html"))
|
||||
)
|
||||
|
||||
func renderRole(t *testing.T, c *types.Category, r *types.Role, s bool) string {
|
||||
data := presentation.Role(c, r, s)
|
||||
buf := bytes.Buffer{}
|
||||
err := Templates.ExecuteTemplate(&buf, "role.html", data)
|
||||
if err != nil {
|
||||
t.Fatal("template failed to render", err)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func TestRole(t *testing.T) {
|
||||
c := &fixtures.CategoryMulti
|
||||
r := &fixtures.RoleWithDarkColor
|
||||
html := renderRole(t, c, r, false)
|
||||
assert.Contains(t, html, "--role-color: #a20000;", "role color is set")
|
||||
assert.Contains(t, html, "--contrast-color: var(--grey600);", "contrast color is set")
|
||||
assert.Contains(t, html, fmt.Sprintf(`id="%s"`, roleInputID(c, r)), "input has ID attr")
|
||||
assert.Contains(t, html, fmt.Sprintf(`for="%s"`, roleInputID(c, r)), "label has for attr")
|
||||
assert.Contains(t, html, `type="checkbox"`, "multi has input type=checkbox")
|
||||
assert.NotContains(t, html, fmt.Sprintf(`name="%s"`, roleInputName(c)), "multi has no name attr")
|
||||
// TODO: selected?
|
||||
|
||||
c = &fixtures.CategorySingle
|
||||
r = &fixtures.RoleWithLightColor
|
||||
html = renderRole(t, c, r, true)
|
||||
assert.Contains(t, html, "--contrast-color: var(--grey100);", "single has name attr")
|
||||
assert.Contains(t, html, `type="radio"`, "single has input type=radio")
|
||||
assert.Contains(t, html, fmt.Sprintf(`name="%s"`, roleInputName(c)), "single has name attr")
|
||||
}
|
||||
|
||||
// TODO: these can probably be string utils that are injected as functions into template
|
||||
func roleInputID(c *types.Category, r *types.Role) string {
|
||||
return fmt.Sprintf("category-%s_role-%s", c.ID, r.ID)
|
||||
}
|
||||
|
||||
func roleInputName(c *types.Category) string {
|
||||
return fmt.Sprintf("category_group_%s", c.ID)
|
||||
}
|
|
@ -11,6 +11,6 @@
|
|||
<link rel="stylesheet" href="/static/main.css" />
|
||||
</head>
|
||||
<body>
|
||||
{{embed}}
|
||||
{{ template "components/nav" . }} {{embed}}
|
||||
</body>
|
||||
</html>
|
||||
|
|
16
templates/tests/picker.html
Normal file
16
templates/tests/picker.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
<style>
|
||||
.container {
|
||||
margin: 3rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container">
|
||||
<div class="cat-multi">
|
||||
{{ template "components/role" .TestRole }} {{ template "components/role"
|
||||
.TestRole2 }}
|
||||
</div>
|
||||
<div class="cat-single">
|
||||
{{ template "components/role" .TestRole3 }} {{ template "components/role"
|
||||
.TestRole4 }}
|
||||
</div>
|
||||
</div>
|
|
@ -5,9 +5,11 @@ import (
|
|||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/authmiddleware"
|
||||
"git.sapphic.engineer/roleypoly/v4/auth/authmiddleware"
|
||||
"git.sapphic.engineer/roleypoly/v4/discord"
|
||||
"git.sapphic.engineer/roleypoly/v4/presentation"
|
||||
"git.sapphic.engineer/roleypoly/v4/types"
|
||||
"git.sapphic.engineer/roleypoly/v4/types/fixtures"
|
||||
)
|
||||
|
||||
type TestingController struct {
|
||||
|
@ -34,7 +36,14 @@ func (t *TestingController) Picker(c fiber.Ctx) error {
|
|||
|
||||
func (t *TestingController) TestTemplate(c fiber.Ctx) error {
|
||||
which := c.Params("which")
|
||||
return c.Render("tests/"+which, fiber.Map{})
|
||||
cat1 := fixtures.Category(fixtures.CategoryMulti)
|
||||
cat2 := fixtures.Category(fixtures.CategorySingle)
|
||||
return c.Render("tests/"+which, fiber.Map{
|
||||
"TestRole": presentation.Role(cat1, &fixtures.RoleWithDarkColor, false),
|
||||
"TestRole2": presentation.Role(cat1, &fixtures.RoleWithLightColor, true),
|
||||
"TestRole3": presentation.Role(cat2, &fixtures.RoleWithDarkColor, false),
|
||||
"TestRole4": presentation.Role(cat2, &fixtures.RoleWithLightColor, true),
|
||||
}, "layouts/main")
|
||||
}
|
||||
|
||||
func (t *TestingController) GetMember(c fiber.Ctx) error {
|
||||
|
|
15
types/category.go
Normal file
15
types/category.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package types
|
||||
|
||||
type CategoryType string
|
||||
|
||||
const (
|
||||
CategoryMultiple CategoryType = "multiple"
|
||||
CategorySingle CategoryType = "single"
|
||||
)
|
||||
|
||||
type Category struct {
|
||||
ID string
|
||||
Name string
|
||||
Type CategoryType
|
||||
Roles []string // of role IDs
|
||||
}
|
29
types/fixtures/category.go
Normal file
29
types/fixtures/category.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package fixtures
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/types"
|
||||
)
|
||||
|
||||
var (
|
||||
CategoryMulti = types.Category{
|
||||
ID: "multi",
|
||||
Name: "Roles",
|
||||
Type: types.CategoryMultiple,
|
||||
Roles: []string{RoleWithDarkColor.ID, RoleWithLightColor.ID, RoleWithoutColor.ID},
|
||||
}
|
||||
|
||||
CategorySingle = types.Category{
|
||||
ID: "single",
|
||||
Name: "Roles",
|
||||
Type: types.CategorySingle,
|
||||
Roles: []string{RoleWithDarkColor.ID, RoleWithLightColor.ID, RoleWithoutColor.ID},
|
||||
}
|
||||
)
|
||||
|
||||
func Category(base types.Category) *types.Category {
|
||||
base.ID = fmt.Sprintf("%s-%d", base.ID, rand.Uint32())
|
||||
return &base
|
||||
}
|
32
types/fixtures/role.go
Normal file
32
types/fixtures/role.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package fixtures
|
||||
|
||||
import "git.sapphic.engineer/roleypoly/v4/types"
|
||||
|
||||
var (
|
||||
RoleWithDarkColor = types.Role{
|
||||
ID: "dark-color",
|
||||
Name: "role with dark color",
|
||||
Color: 0xa20000,
|
||||
Permissions: 0,
|
||||
Position: 10,
|
||||
}
|
||||
RoleWithLightColor = types.Role{
|
||||
ID: "light-color",
|
||||
Name: "role with light color",
|
||||
Color: 0xffaa88,
|
||||
Permissions: 0,
|
||||
Position: 10,
|
||||
}
|
||||
RoleWithoutColor = types.Role{
|
||||
ID: "without-color",
|
||||
Name: "role",
|
||||
Color: 0x000000,
|
||||
Permissions: 0,
|
||||
Position: 11,
|
||||
}
|
||||
//TODO: role with admin (bad)
|
||||
//TODO: role with manage roles (bad)
|
||||
//TODO: role above roleypoly (bad)
|
||||
//TODO: role with managed (bad)
|
||||
//TODO: role that is roleypoly (hi)
|
||||
)
|
11
types/role.go
Normal file
11
types/role.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package types
|
||||
|
||||
type Role struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Color uint32 `json:"color"`
|
||||
Icon string `json:"icon"` // unused, future?
|
||||
UnicodeEmoji string `json:"unicode_emoji"` // unused, future?
|
||||
Permissions uint64 `json:"permissions,string"`
|
||||
Position uint8 `json:"position"`
|
||||
}
|
21
utils/colors.go
Normal file
21
utils/colors.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package utils
|
||||
|
||||
import "math"
|
||||
|
||||
func IsDarkColor(r, g, b uint8) bool {
|
||||
rC := 0.299 * math.Pow(float64(r), 2)
|
||||
gC := 0.587 * math.Pow(float64(g), 2)
|
||||
bC := 0.114 * math.Pow(float64(b), 2)
|
||||
|
||||
lum := math.Sqrt(rC + gC + bC)
|
||||
|
||||
return lum <= 130
|
||||
}
|
||||
|
||||
func IntToRgb(color uint32) (uint8, uint8, uint8) {
|
||||
b := color % 256
|
||||
g := color / 256 % 256
|
||||
r := color / (256 * 256) % 256
|
||||
|
||||
return uint8(r), uint8(g), uint8(b)
|
||||
}
|
29
utils/colors_test.go
Normal file
29
utils/colors_test.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package utils_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIntToRgb(t *testing.T) {
|
||||
r, g, b := utils.IntToRgb(0x123456)
|
||||
assert.Equal(t, uint8(0x12), r, "red")
|
||||
assert.Equal(t, uint8(0x34), g, "green")
|
||||
assert.Equal(t, uint8(0x56), b, "blue")
|
||||
}
|
||||
|
||||
func TestIsDarkColor(t *testing.T) {
|
||||
isDark := utils.IsDarkColor(0, 0, 0)
|
||||
assert.True(t, isDark)
|
||||
|
||||
isLight := utils.IsDarkColor(255, 255, 255)
|
||||
assert.False(t, isLight)
|
||||
|
||||
isQuestionable := utils.IsDarkColor(0x88, 0x88, 0x88)
|
||||
assert.False(t, isQuestionable)
|
||||
|
||||
isReallyQuestionable := utils.IsDarkColor(0x00, 0x88, 0x00)
|
||||
assert.True(t, isReallyQuestionable)
|
||||
}
|
Loading…
Add table
Reference in a new issue