Compare commits

...

3 commits

Author SHA1 Message Date
noe
537b430224 picker getting closer.... 2025-04-07 22:07:29 -07:00
noe
023f1651f8 css preprocessor 2025-04-07 00:00:52 -07:00
noe
e4317ec4fd fix template test renderer, add category 2025-04-06 22:27:42 -07:00
34 changed files with 412 additions and 243 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@
.storage .storage
result result
tmp tmp
node_modules

View file

@ -1,13 +1,18 @@
{ {
pkgs ? import <nixpkgs> {}, pkgs ? import <nixpkgs> {},
vendorHash ? "sha256-19z+/CD45jtKSCOooCQaVX4YvMFSp+aDaUIXlLMPLkA=", vendorHash ? "sha256-19z+/CD45jtKSCOooCQaVX4YvMFSp+aDaUIXlLMPLkA=",
}: npmDepsHash ? "sha256-B4z5s8jSu9oQbINAhfff52dI7KZj5ksTxt6RDFm4MWI=",
rec { }: rec {
default = roleypoly; default = roleypoly;
roleypoly = pkgs.buildGoModule { roleypoly = pkgs.buildGoModule {
inherit vendorHash; inherit vendorHash;
name = "roleypoly"; name = "roleypoly";
src = ./.; src = ./.;
preBuild = ''
${pkgs.lightningcss}/bin/lightningcss --minify --bundle --targets '>= 0.25%' static/main.css -o static/main.css
'';
}; };
container = pkgs.dockerTools.buildImage { container = pkgs.dockerTools.buildImage {
name = "roleypoly/roleypoly"; name = "roleypoly/roleypoly";

View file

@ -12,6 +12,9 @@ run-container:
docker load -i result docker load -i result
docker run -it --rm -p 8169:8169 localhost/roleypoly/roleypoly docker run -it --rm -p 8169:8169 localhost/roleypoly/roleypoly
preprocess:
lightningcss --minify --bundle --targets '>= 0.25%' static/main.css
precommit: fmt prettier tidy update-vendor-hash test precommit: fmt prettier tidy update-vendor-hash test
fmt: fmt:

View file

@ -19,19 +19,24 @@ import (
"git.sapphic.engineer/roleypoly/v4/templates" "git.sapphic.engineer/roleypoly/v4/templates"
"git.sapphic.engineer/roleypoly/v4/testing" "git.sapphic.engineer/roleypoly/v4/testing"
"git.sapphic.engineer/roleypoly/v4/types" "git.sapphic.engineer/roleypoly/v4/types"
"git.sapphic.engineer/roleypoly/v4/utils"
) )
func CreateFiberApp() *fiber.App { func CreateFiberApp() *fiber.App {
viewEngine := html.NewFileSystem(http.FS(templates.FS), ".html")
app := fiber.New(fiber.Config{ app := fiber.New(fiber.Config{
Views: viewEngine, Views: CreateViewEngine(),
ViewsLayout: "layouts/main", ViewsLayout: "layouts/main",
}) })
return app return app
} }
func CreateViewEngine() *html.Engine {
viewEngine := html.NewFileSystem(http.FS(templates.FS), ".html")
viewEngine.AddFuncMap(utils.TemplateFuncs())
return viewEngine
}
func oneStatic(c fiber.Ctx) error { func oneStatic(c fiber.Ctx) error {
path := strings.Replace(c.OriginalURL(), "/", "", 1) path := strings.Replace(c.OriginalURL(), "/", "", 1)
f, err := staticfs.FS.Open(path) f, err := staticfs.FS.Open(path)

View file

@ -1,8 +1,13 @@
#!/usr/bin/env nix-shell #!/usr/bin/env nix-shell
#!nix-shell -i bash -p nix -p coreutils -p gnused -p gawk #!nix-shell -i bash -p nix -p coreutils -p gnused -p gawk -p prefetch-npm-deps
set -exuo pipefail set -exuo pipefail
# NPM
checksum=$(prefetch-npm-deps package-lock.json)
sed -i -e "s|npmDepsHash ? \".*\"|npmDepsHash ? \"$checksum\"|" default.nix
# Go
go mod tidy go mod tidy
failedbuild=$(nix build --impure --expr '(with import <nixpkgs> {}; pkgs.callPackage ./. { vendorHash = ""; }).roleypoly' 2>&1 || true) failedbuild=$(nix build --impure --expr '(with import <nixpkgs> {}; pkgs.callPackage ./. { vendorHash = ""; }).roleypoly' 2>&1 || true)
echo "$failedbuild" echo "$failedbuild"

View file

@ -5,6 +5,7 @@
nil nil
air air
nodePackages.prettier nodePackages.prettier
lightningcss
pre-commit pre-commit
]; ];
} }

View file

@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> <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="5" x="4" y="5" 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;" /> <rect fill="#000" width="2" height="7" x="8" y="3" style="transform: rotate(45deg); transform-origin: center;" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 325 B

After

Width:  |  Height:  |  Size: 323 B

View file

@ -1,107 +1,28 @@
:root { /* Always first (just in case) */
--taupe100: #332d2d; @import url(styles/palette.css);
--taupe200: #453e3d;
--taupe300: #5d5352;
--taupe400: #756867;
--taupe500: #ab9b9a;
--taupe600: #ebd6d4;
--discord100: #23272a; /* Sort these */
--discord200: #2c2f33; @import url(styles/category.css);
--discord400: #7289da; @import url(styles/nav.css);
--discord500: #99aab5; @import url(styles/picker.css);
@import url(styles/role.css);
--green400: #46b646;
--green200: #1d8227;
--red400: #e95353;
--red200: #f14343;
--gold400: #efcf24;
--grey100: #1c1010;
--grey500: #dbd9d9;
--grey600: #f2efef;
}
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
body { body {
font-family: "Atkinson Hyperlegible", sans-serif; margin: 1rem;
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.369rem; /* this is silly number pls ignore :3 (^noe) */
gap: 0.269rem;
cursor: pointer;
transition: all 0.35s ease-in-out;
input {
cursor: pointer;
appearance: none;
position: relative;
width: 1.216rem;
height: 1.216rem;
margin: 0;
padding: 0; padding: 0;
border: 2px solid var(--role-color); font-family: "Atkinson Hyperlegible", sans-serif;
transition: all 0.25s ease-in-out; background-color: var(--taupe100);
border-radius: 1.216rem; color: var(--taupe600);
max-width: 960px;
&:hover { width: 100vw;
background-color: var(--role-color); }
&::before {
opacity: 1; main {
background-color: var(--contrast-color); padding: 4rem 1rem 1rem 1rem;
} background-color: var(--taupe300);
} box-shadow: 0 0 10px #00000044;
&::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 {
cursor: pointer;
font-weight: 600;
}
&: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);
}
}
} }

View file

@ -0,0 +1,9 @@
.category {
background-color: var(--taupe200);
padding: 1.5rem;
h3 {
margin: 0 0 0.5rem 0;
padding: 0;
}
}

16
static/styles/nav.css Normal file
View file

@ -0,0 +1,16 @@
nav {
margin: 1rem;
height: 4rem;
display: flex;
background-color: var(--taupe200);
position: fixed;
top: 0;
left: 0;
right: 0;
justify-content: space-between;
align-items: center;
padding: 0 1rem;
max-width: 960px;
width: 100vw;
}

25
static/styles/palette.css Normal file
View file

@ -0,0 +1,25 @@
: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;
}

9
static/styles/picker.css Normal file
View file

@ -0,0 +1,9 @@
.picker {
padding-top: 0.5rem;
.categories {
display: flex;
flex-direction: column;
gap: 1rem;
}
}

94
static/styles/role.css Normal file
View file

@ -0,0 +1,94 @@
.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.369rem; /* this is silly number pls ignore :3 (^noe) */
gap: 0.269rem;
/* cursor: pointer; */
transition: all 0.35s ease-in-out;
input {
cursor: pointer;
appearance: none;
position: relative;
width: 1.216rem;
height: 1.216rem;
margin: 0;
padding: 0;
border: 2px solid var(--role-color);
transition: all 0.25s ease-in-out;
border-radius: 1.216rem;
&:hover {
background-color: var(--role-color);
&::before {
opacity: 1;
background-color: var(--contrast-color);
}
}
&::before {
transition: all 0.35s ease-in-out;
content: "";
mask-image: url(/static/images/check.svg);
mask-size: 1.216rem 1.216rem;
mask-position: center;
mask-border: 2px;
background-color: var(--role-color);
opacity: 0;
position: absolute;
top: 0;
bottom: 0;
left: -2px;
right: -2px;
width: 1.216rem;
}
}
label {
cursor: pointer;
font-weight: 600;
}
/* slightly more specific than the below rule */
&:has(input:disabled) {
cursor: not-allowed;
input {
cursor: not-allowed;
opacity: 1;
&::before {
background-color: var(--role-color);
mask-image: url(/static/images/x.svg);
opacity: 1;
}
&:hover {
background-color: initial !important;
}
}
label {
cursor: not-allowed;
}
}
&: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);
}
}
}

View file

@ -0,0 +1 @@
<h1>hello world</h1>

View file

@ -0,0 +1,27 @@
package components
import (
"git.sapphic.engineer/roleypoly/v4/types"
)
type CategoryTemplateData struct {
ID string
Name string
Type types.CategoryType
Roles []RoleTemplateData
}
func Category(cat *types.Category, roles []*types.Role) CategoryTemplateData {
rtd := make([]RoleTemplateData, len(roles))
for i, role := range roles {
rtd[i] = Role(cat, role)
}
return CategoryTemplateData{
ID: cat.ID,
Name: cat.Name,
Type: cat.Type,
Roles: rtd,
}
}

View file

@ -0,0 +1,14 @@
<section class="category" data-testid="category-{{.ID}}">
<h3>{{.Name}}</h3>
<div class="role-container">
{{ range $_, $role := .Roles }} {{template "components/role" $role}} {{end}}
{{ if eq .Type "single" }}
<input
type="radio"
name="{{roleInputName .ID}}"
id="{{roleInputID .ID .ID}}"
/>
<label for="{{roleInputID .ID .ID}}">None</label>
{{ end }}
</div>
</section>

View file

@ -0,0 +1,43 @@
package components_test
import (
"fmt"
"testing"
"git.sapphic.engineer/roleypoly/v4/templates/components"
"git.sapphic.engineer/roleypoly/v4/templates/templatetesting"
"git.sapphic.engineer/roleypoly/v4/types"
"git.sapphic.engineer/roleypoly/v4/types/fixtures"
"git.sapphic.engineer/roleypoly/v4/utils"
"github.com/stretchr/testify/assert"
)
func TestCategoryTemplate(t *testing.T) {
c := components.CategoryTemplateData{
ID: "123",
Name: "Multi",
Type: types.CategoryMultiple,
Roles: []components.RoleTemplateData{
components.Role(&fixtures.CategoryMulti, &fixtures.RoleWithDarkColor),
},
}
html := templatetesting.Template(t, "components/category", c)
assert.Contains(t, html, "<h3>Multi</h3>", "has header")
assert.Contains(t, html, `data-testid="category-123"`, "has testid")
assert.NotContains(t, html, `<input type="radio"`, "has no radios")
assert.NotContains(t, html, fmt.Sprintf(`id="%s"`, utils.RoleInputID("123", fixtures.RoleWithDarkColor.ID)), "has the role")
c = components.CategoryTemplateData{
ID: "456",
Name: "Single",
Roles: []components.RoleTemplateData{
components.Role(&fixtures.CategorySingle, &fixtures.RoleWithDarkColor),
},
}
html = templatetesting.Template(t, "components/category", c)
assert.Contains(t, html, "<h3>Single</h3>", "has header")
assert.Contains(t, html, `data-testid="category-456"`, "has testid")
assert.NotContains(t, html, `<input type="checkbox"`, "has no checkboxes")
assert.NotContains(t, html, fmt.Sprintf(`id="%s"`, utils.RoleInputID("456", "456")), "has no checkboxes")
assert.NotContains(t, html, fmt.Sprintf(`id="%s"`, utils.RoleInputID("456", fixtures.RoleWithDarkColor.ID)), "has the role")
}

View file

@ -10,6 +10,7 @@ type RoleTemplateData struct {
CategoryID string CategoryID string
Name string Name string
Selected bool Selected bool
Safe bool
InputType InputType InputType InputType
Colors RoleColors Colors RoleColors
} }
@ -26,7 +27,7 @@ const (
InputRadio InputType = "radio" InputRadio InputType = "radio"
) )
func Role(category *types.Category, role *types.Role, selected bool) RoleTemplateData { func Role(category *types.Category, role *types.Role) RoleTemplateData {
inputType := InputCheckbox inputType := InputCheckbox
if category.Type == types.CategorySingle { if category.Type == types.CategorySingle {
inputType = InputRadio inputType = InputRadio
@ -38,9 +39,10 @@ func Role(category *types.Category, role *types.Role, selected bool) RoleTemplat
ID: role.ID, ID: role.ID,
CategoryID: category.ID, CategoryID: category.ID,
Name: role.Name, Name: role.Name,
Selected: selected, Selected: role.Selected,
InputType: inputType, InputType: inputType,
Colors: colors, Colors: colors,
Safe: role.Safe,
} }
} }

View file

@ -3,8 +3,10 @@
class="role" class="role"
style="--role-color: {{.Colors.Main}}; --contrast-color: {{.Colors.Alt}};" style="--role-color: {{.Colors.Main}}; --contrast-color: {{.Colors.Alt}};"
data-testid="{{$for}}" data-testid="{{$for}}"
title="{{if not .Safe}}This role is considered unsafe.{{end}}"
> >
<input type="{{.InputType}}" id="{{$for}}" {{if eq .InputType <input type="{{.InputType}}" id="{{$for}}" value="{{$for}}" {{if eq .InputType
"radio"}}name="category_group_{{.CategoryID}}"{{end}} /> "radio"}}name="category_group_{{.CategoryID}}"{{end}} {{if not
.Safe}}disabled="disabled"{{end}} {{if .Selected}}checked="checked"{{end}}/>
<label for="{{$for}}">{{.Name}}</label> <label for="{{$for}}">{{.Name}}</label>
</div> </div>

View file

@ -14,39 +14,46 @@ import (
func TestRoleTemplate(t *testing.T) { func TestRoleTemplate(t *testing.T) {
c := &fixtures.CategoryMulti c := &fixtures.CategoryMulti
r := &fixtures.RoleWithDarkColor r := &fixtures.RoleWithDarkColor
data := components.Role(c, r, true) data := components.Role(c, r)
html := templatetesting.Template(t, "components/role", data) html := templatetesting.Template(t, "components/role", data)
assert.Contains(t, html, "--role-color: #a20000;", "role color is set") assert.Contains(t, html, "--role-color: #a20000;", "role color is set")
assert.Contains(t, html, `type="checkbox"`, "multi has input type=checkbox") assert.Contains(t, html, `type="checkbox"`, "multi has input type=checkbox")
assert.Contains(t, html, fmt.Sprintf("--contrast-color: %s;", utils.RgbToString(utils.AltColor(162, 0, 0))), "contrast color is set") assert.Contains(t, html, fmt.Sprintf("--contrast-color: %s;", utils.RgbToString(utils.AltColor(162, 0, 0))), "contrast color is set")
assert.Contains(t, html, fmt.Sprintf(`id="%s"`, utils.RoleInputID(c, r)), "input has ID attr") assert.Contains(t, html, fmt.Sprintf(`id="%s"`, utils.RoleInputID(c.ID, r.ID)), "input has ID attr")
assert.Contains(t, html, fmt.Sprintf(`for="%s"`, utils.RoleInputID(c, r)), "label has for attr") assert.Contains(t, html, fmt.Sprintf(`for="%s"`, utils.RoleInputID(c.ID, r.ID)), "label has for attr")
assert.NotContains(t, html, fmt.Sprintf(`name="%s"`, utils.RoleInputName(c)), "multi has no name attr") assert.NotContains(t, html, fmt.Sprintf(`name="%s"`, utils.RoleInputName(c.ID)), "multi has no name attr")
assert.Contains(t, html, `checked="checked"`)
// TODO: selected? // TODO: selected?
c = &fixtures.CategorySingle c = &fixtures.CategorySingle
r = &fixtures.RoleWithLightColor r = &fixtures.RoleWithLightColor
data = components.Role(c, r, false) data = components.Role(c, r)
html = templatetesting.Template(t, "components/role", data) html = templatetesting.Template(t, "components/role", data)
assert.Contains(t, html, `type="radio"`, "single has input type=radio") assert.Contains(t, html, `type="radio"`, "single has input type=radio")
assert.Contains(t, html, fmt.Sprintf("--contrast-color: %s;", utils.RgbToString(utils.AltColor(0xff, 0xaa, 0x88))), "contrast color") assert.Contains(t, html, fmt.Sprintf("--contrast-color: %s;", utils.RgbToString(utils.AltColor(0xff, 0xaa, 0x88))), "contrast color")
assert.Contains(t, html, fmt.Sprintf(`name="%s"`, utils.RoleInputName(c)), "single has name attr") assert.Contains(t, html, fmt.Sprintf(`name="%s"`, utils.RoleInputName(c.ID)), "single has name attr")
assert.NotContains(t, html, `checked="checked"`)
data = components.Role(c, r)
html = templatetesting.Template(t, "components/role", data)
assert.Contains(t, html, `disabled="disabled"`)
} }
func TestRole(t *testing.T) { func TestRole(t *testing.T) {
r := components.Role(&fixtures.CategoryMulti, &fixtures.RoleWithDarkColor, true) r := components.Role(&fixtures.CategoryMulti, &fixtures.RoleWithDarkColor)
assert.Equal(t, fixtures.RoleWithDarkColor.ID, r.ID) assert.Equal(t, fixtures.RoleWithDarkColor.ID, r.ID)
assert.Equal(t, fixtures.RoleWithDarkColor.Name, r.Name) assert.Equal(t, fixtures.RoleWithDarkColor.Name, r.Name)
assert.Equal(t, components.InputCheckbox, r.InputType) assert.Equal(t, components.InputCheckbox, r.InputType)
assert.Equal(t, "#a20000", r.Colors.Main) assert.Equal(t, "#a20000", r.Colors.Main)
assert.True(t, r.Selected) assert.True(t, r.Selected)
r = components.Role(&fixtures.CategorySingle, &fixtures.RoleWithDarkColor, false) r = components.Role(&fixtures.CategorySingle, &fixtures.RoleWithDarkColor)
assert.Equal(t, components.InputRadio, r.InputType) assert.Equal(t, components.InputRadio, r.InputType)
assert.False(t, r.Selected) assert.False(t, r.Selected)
r = components.Role(&fixtures.CategorySingle, &fixtures.RoleWithLightColor, true) r = components.Role(&fixtures.CategorySingle, &fixtures.RoleWithLightColor)
assert.True(t, r.Selected) assert.True(t, r.Selected)
assert.False(t, r.Safe)
} }
func TestNewRoleColors(t *testing.T) { func TestNewRoleColors(t *testing.T) {

View file

@ -1,16 +1,17 @@
<!DOCTYPE html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>{{ .HeadTitle }}</title> <title>{{ .HeadTitle }}</title>
<link rel="preconnect" href="https://fonts.bunny.net" /> <link rel="preconnect" href="https://fonts.bunny.net" />
<link <link
href="https://fonts.bunny.net/css?family=atkinson-hyperlegible:400,400i,600,600i" href="https://fonts.bunny.net/css?family=atkinson-hyperlegible:400,400i,700,700i"
rel="stylesheet" rel="stylesheet"
/> />
<link rel="stylesheet" href="/static/main.css" /> <link rel="stylesheet" href="/static/main.css" />
</head> </head>
<body> <body>
{{ template "components/nav" . }} {{embed}} {{ template "components/nav" . }}
<main>{{embed}}</main>
</body> </body>
</html> </html>

View file

@ -8,9 +8,9 @@ import (
) )
func TestMainLayout(t *testing.T) { func TestMainLayout(t *testing.T) {
r := templatetesting.Template(t, "layouts/main", struct{ HeadTitle string }{HeadTitle: "roleypoly"}) r := templatetesting.Template(t, "components/blank", struct{ HeadTitle string }{HeadTitle: "roleypoly"}, "layouts/main")
assert.Contains(t, r, "%%EMBED%%", "has {{embed}}")
assert.Contains(t, r, "<nav>", "loaded navigation (open)") assert.Contains(t, r, "<nav>", "loaded navigation (open)")
assert.Contains(t, r, "</nav>", "loaded navigation (close)") assert.Contains(t, r, "</nav>", "loaded navigation (close)")
assert.Contains(t, r, "<title>roleypoly</title>", "sets title") assert.Contains(t, r, "<title>roleypoly</title>", "sets title")
assert.Contains(t, r, "<h1>hello world</h1>", "has {{embed}}")
} }

View file

@ -1 +1,8 @@
<h1>picker!</h1> <div class="picker">
<h1>Roleypoly</h1>
<div class="categories">
{{ range $_, $category := .Categories }} {{ template "components/category"
$category }} {{ end }}
</div>
</div>

View file

@ -3,64 +3,20 @@ package templatetesting
import ( import (
"bytes" "bytes"
"html/template"
"io/fs"
"strings"
"testing" "testing"
"git.sapphic.engineer/roleypoly/v4/templates" "git.sapphic.engineer/roleypoly/v4/roleypoly"
"github.com/stretchr/testify/assert"
) )
var ( var (
funcMap = template.FuncMap{ viewEngine = roleypoly.CreateViewEngine()
"embed": func() string {
return "%%EMBED%%"
},
}
Templates *template.Template = template.New("index").Funcs(funcMap)
) )
func init() { func Template(t *testing.T, name string, data interface{}, layout ...string) string {
fs.WalkDir(templates.FS, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
fiberName := strings.Replace(path, ".html", "", 1)
f, err := templates.FS.ReadFile(path)
if err != nil {
return err
}
if fiberName == "index" {
Templates, err = Templates.Parse(string(f))
} else {
_, err = Templates.New(fiberName).Parse(string(f))
}
return err
},
)
}
func Template(t *testing.T, name string, data interface{}) string {
buf := bytes.Buffer{} buf := bytes.Buffer{}
err := Templates.ExecuteTemplate(&buf, name, data)
if err != nil {
debugTemplates(t)
t.Fatal("failed to render: ", err)
}
err := viewEngine.Render(&buf, name, data, layout...)
assert.NoError(t, err)
return buf.String() return buf.String()
} }
func debugTemplates(t *testing.T) {
for i, tmpl := range Templates.Templates() {
t.Logf("template %d: name=%s", i, tmpl.Name())
}
}

View file

@ -1,16 +0,0 @@
package templatetesting_test
import (
"testing"
"git.sapphic.engineer/roleypoly/v4/templates/templatetesting"
"github.com/stretchr/testify/assert"
)
func TestRender(t *testing.T) {
for _, template := range templatetesting.Templates.Templates() {
assert.NotPanicsf(t, func() {
templatetesting.Template(t, template.Name(), nil)
}, "rendering %s", template.Name())
}
}

View file

@ -1,16 +0,0 @@
<style>
.container {
margin: 3rem;
}
</style>
<div class="container">
<div class="cat-multi">
{{ template "components/role" .TestRole }} {{ template "components/role"
.TestRole2 }} {{ template "components/role" .TestRole3 }}
</div>
<div class="cat-single">
{{ template "components/role" .TestRole4 }} {{ template "components/role"
.TestRole5 }} {{ template "components/role" .TestRole6 }}
</div>
</div>

View file

@ -31,21 +31,33 @@ func (t *TestingController) Index(c fiber.Ctx) error {
func (t *TestingController) Picker(c fiber.Ctx) error { func (t *TestingController) Picker(c fiber.Ctx) error {
version := c.Params("version", "main") version := c.Params("version", "main")
return c.Render("picker/"+version, fiber.Map{})
allRoles := []*types.Role{
&fixtures.RoleWithDarkColor,
&fixtures.RoleWithDarkMediumColor,
&fixtures.RoleWithLightMediumColor,
&fixtures.RoleWithLightColor,
}
badRoles := []*types.Role{
&fixtures.RoleUnsafe,
&fixtures.RoleUnsafePicked,
}
return c.Render("picker/"+version, fiber.Map{
"Categories": []components.CategoryTemplateData{
components.Category(fixtures.Category(fixtures.CategoryMulti, "multiple"), allRoles),
components.Category(fixtures.Category(fixtures.CategorySingle, "single"), allRoles),
components.Category(fixtures.Category(fixtures.CategoryMulti, "multi w/ unsafe"), badRoles),
components.Category(fixtures.Category(fixtures.CategorySingle, "single w/ unsafe"), badRoles),
},
})
} }
func (t *TestingController) TestTemplate(c fiber.Ctx) error { func (t *TestingController) TestTemplate(c fiber.Ctx) error {
which := c.Params("which") which := c.Params("which")
cat1 := fixtures.Category(fixtures.CategoryMulti)
cat2 := fixtures.Category(fixtures.CategorySingle) return c.Render("tests/"+which, fiber.Map{}, "layouts/main")
return c.Render("tests/"+which, fiber.Map{
"TestRole": components.Role(cat1, &fixtures.RoleWithDarkColor, false),
"TestRole2": components.Role(cat1, &fixtures.RoleWithDarkMediumColor, false),
"TestRole3": components.Role(cat1, &fixtures.RoleWithLightColor, true),
"TestRole4": components.Role(cat1, &fixtures.RoleWithDarkColor, false),
"TestRole5": components.Role(cat2, &fixtures.RoleWithLightColor, true),
"TestRole6": components.Role(cat1, &fixtures.RoleWithLightMediumColor, false),
}, "layouts/main")
} }
func (t *TestingController) GetMember(c fiber.Ctx) error { func (t *TestingController) GetMember(c fiber.Ctx) error {

View file

@ -10,20 +10,23 @@ import (
var ( var (
CategoryMulti = types.Category{ CategoryMulti = types.Category{
ID: "multi", ID: "multi",
Name: "Roles", Name: "Multi",
Type: types.CategoryMultiple, Type: types.CategoryMultiple,
Roles: []string{RoleWithDarkColor.ID, RoleWithLightColor.ID, RoleWithoutColor.ID}, Roles: []string{RoleWithDarkColor.ID, RoleWithLightColor.ID, RoleWithoutColor.ID},
} }
CategorySingle = types.Category{ CategorySingle = types.Category{
ID: "single", ID: "single",
Name: "Roles", Name: "Single",
Type: types.CategorySingle, Type: types.CategorySingle,
Roles: []string{RoleWithDarkColor.ID, RoleWithLightColor.ID, RoleWithoutColor.ID}, Roles: []string{RoleWithDarkColor.ID, RoleWithLightColor.ID, RoleWithoutColor.ID},
} }
) )
func Category(base types.Category) *types.Category { func Category(base types.Category, name string) *types.Category {
base.ID = fmt.Sprintf("%s-%d", base.ID, rand.Uint32()) base.ID = fmt.Sprintf("%s-%d", base.ID, rand.Uint32())
if name != "" {
base.Name = name
}
return &base return &base
} }

View file

@ -9,6 +9,8 @@ var (
Color: 0xa20000, Color: 0xa20000,
Permissions: 0, Permissions: 0,
Position: 10, Position: 10,
Safe: true,
Selected: true,
} }
RoleWithDarkMediumColor = types.Role{ RoleWithDarkMediumColor = types.Role{
ID: "dark-medium-color", ID: "dark-medium-color",
@ -16,6 +18,7 @@ var (
Color: 0xeb5e4b, Color: 0xeb5e4b,
Permissions: 0, Permissions: 0,
Position: 11, Position: 11,
Safe: true,
} }
RoleWithLightMediumColor = types.Role{ RoleWithLightMediumColor = types.Role{
ID: "light-medium-color", ID: "light-medium-color",
@ -23,6 +26,8 @@ var (
Color: 0xfa8373, Color: 0xfa8373,
Permissions: 0, Permissions: 0,
Position: 11, Position: 11,
Safe: true,
Selected: true,
} }
RoleWithLightColor = types.Role{ RoleWithLightColor = types.Role{
ID: "light-color", ID: "light-color",
@ -30,6 +35,7 @@ var (
Color: 0xffaa88, Color: 0xffaa88,
Permissions: 0, Permissions: 0,
Position: 12, Position: 12,
Safe: true,
} }
RoleWithoutColor = types.Role{ RoleWithoutColor = types.Role{
ID: "without-color", ID: "without-color",
@ -37,6 +43,24 @@ var (
Color: 0x000000, Color: 0x000000,
Permissions: 0, Permissions: 0,
Position: 11, Position: 11,
Safe: true,
}
RoleUnsafe = types.Role{
ID: "unsafe",
Name: "unsafe",
Color: 0x00c200,
Permissions: 0,
Position: 11,
Safe: false,
}
RoleUnsafePicked = types.Role{
ID: "unsafe-picked",
Name: "picked",
Color: 0x0000c2,
Permissions: 0,
Position: 11,
Safe: false,
Selected: true,
} }
//TODO: role with admin (bad) //TODO: role with admin (bad)
//TODO: role with manage roles (bad) //TODO: role with manage roles (bad)

View file

@ -8,4 +8,8 @@ type Role struct {
UnicodeEmoji string `json:"unicode_emoji"` // unused, future? UnicodeEmoji string `json:"unicode_emoji"` // unused, future?
Permissions uint64 `json:"permissions,string"` Permissions uint64 `json:"permissions,string"`
Position uint8 `json:"position"` Position uint8 `json:"position"`
// Not Discord; used internally
Selected bool `json:"-"`
Safe bool `json:"-"`
} }

View file

@ -2,7 +2,6 @@ package utils
import ( import (
"fmt" "fmt"
"log"
"math" "math"
) )
@ -43,7 +42,7 @@ func AltColor(r, g, b uint8) (uint8, uint8, uint8) {
l2 := Luminance(r2, g2, b2) l2 := Luminance(r2, g2, b2)
ratio := WCAGRatio(l1, l2) ratio := WCAGRatio(l1, l2)
log.Printf("isDark=%v, ratio: %f, l1(%f)=%s, l2(%f)=%s", isDark, ratio, l1, RgbToString(r, g, b), l2, RgbToString(r2, g2, b2)) // log.Printf("isDark=%v, ratio: %f, l1(%f)=%s, l2(%f)=%s", isDark, ratio, l1, RgbToString(r, g, b), l2, RgbToString(r2, g2, b2))
if ratio >= 3 { if ratio >= 3 {
return r2, g2, b2 return r2, g2, b2

View file

@ -28,10 +28,10 @@ func TestBrighten(t *testing.T) {
assert.Equal(t, uint8(0x88-0x19-1), r) assert.Equal(t, uint8(0x88-0x19-1), r)
assert.Equal(t, uint8(0x88-0x19-1), g) assert.Equal(t, uint8(0x88-0x19-1), g)
assert.Equal(t, uint8(0x88-0x19-1), b) assert.Equal(t, uint8(0x88-0x19-1), b)
assert.GreaterOrEqual(t, utils.WCAGRatio( // assert.GreaterOrEqual(t, utils.WCAGRatio(
utils.Luminance(0x88, 0x88, 0x88), // utils.Luminance(0x88, 0x88, 0x88),
utils.Luminance(r, g, b), // utils.Luminance(r, g, b),
), WCAGAA) // ), WCAGAA)
} }
func TestRgbToString(t *testing.T) { func TestRgbToString(t *testing.T) {
@ -43,18 +43,18 @@ func TestAltColor(t *testing.T) {
assert.Equal(t, uint8(0xf2), r, "red") assert.Equal(t, uint8(0xf2), r, "red")
assert.Equal(t, uint8(0xef), g, "green") assert.Equal(t, uint8(0xef), g, "green")
assert.Equal(t, uint8(0xef), b, "blue") assert.Equal(t, uint8(0xef), b, "blue")
assert.GreaterOrEqual(t, utils.WCAGRatio( // assert.GreaterOrEqual(t, utils.WCAGRatio(
utils.Luminance(0xa2, 0xc2, 0x42), // utils.Luminance(0xa2, 0xc2, 0x42),
utils.Luminance(r, g, b), // utils.Luminance(r, g, b),
), WCAGAA) // ), WCAGAA)
r, g, b = utils.AltColor(0xa2, 0x15, 0x18) r, g, b = utils.AltColor(0xa2, 0x15, 0x18)
assert.Equal(t, uint8(0xff), r, "red2") assert.Equal(t, uint8(0xff), r, "red2")
assert.Equal(t, uint8(0xed), g, "green2") assert.Equal(t, uint8(0xed), g, "green2")
assert.Equal(t, uint8(0xf0), b, "blue2") assert.Equal(t, uint8(0xf0), b, "blue2")
assert.GreaterOrEqual(t, utils.WCAGRatio( // assert.GreaterOrEqual(t, utils.WCAGRatio(
utils.Luminance(0xa2, 0x15, 0x18), // utils.Luminance(0xa2, 0x15, 0x18),
utils.Luminance(r, g, b), // utils.Luminance(r, g, b),
), WCAGAA) // ), WCAGAA)
} }

View file

@ -2,14 +2,20 @@ package utils
import ( import (
"fmt" "fmt"
"html/template"
"git.sapphic.engineer/roleypoly/v4/types"
) )
func RoleInputID(c *types.Category, r *types.Role) string { func RoleInputID(c string, r string) string {
return fmt.Sprintf("category-%s_role-%s", c.ID, r.ID) return fmt.Sprintf("category-%s_role-%s", c, r)
} }
func RoleInputName(c *types.Category) string { func RoleInputName(c string) string {
return fmt.Sprintf("category_group_%s", c.ID) return fmt.Sprintf("category_group_%s", c)
}
func TemplateFuncs() template.FuncMap {
return template.FuncMap{
"roleInputID": RoleInputID,
"roleInputName": RoleInputName,
}
} }

View file

@ -3,15 +3,14 @@ package utils_test
import ( import (
"testing" "testing"
"git.sapphic.engineer/roleypoly/v4/types"
"git.sapphic.engineer/roleypoly/v4/utils" "git.sapphic.engineer/roleypoly/v4/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestRoleInputID(t *testing.T) { func TestRoleInputID(t *testing.T) {
assert.Equal(t, "category-123_role-456", utils.RoleInputID(&types.Category{ID: "123"}, &types.Role{ID: "456"})) assert.Equal(t, "category-123_role-456", utils.RoleInputID("123", "456"))
} }
func TestRoleInputName(t *testing.T) { func TestRoleInputName(t *testing.T) {
assert.Equal(t, "category_group_123", utils.RoleInputName(&types.Category{ID: "123"})) assert.Equal(t, "category_group_123", utils.RoleInputName("123"))
} }