Compare commits
3 commits
d9146750ba
...
537b430224
Author | SHA1 | Date | |
---|---|---|---|
537b430224 | |||
023f1651f8 | |||
e4317ec4fd |
34 changed files with 412 additions and 243 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -2,4 +2,5 @@
|
|||
.env
|
||||
.storage
|
||||
result
|
||||
tmp
|
||||
tmp
|
||||
node_modules
|
|
@ -1,13 +1,18 @@
|
|||
{
|
||||
pkgs ? import <nixpkgs> {},
|
||||
vendorHash ? "sha256-19z+/CD45jtKSCOooCQaVX4YvMFSp+aDaUIXlLMPLkA=",
|
||||
}:
|
||||
rec {
|
||||
npmDepsHash ? "sha256-B4z5s8jSu9oQbINAhfff52dI7KZj5ksTxt6RDFm4MWI=",
|
||||
}: rec {
|
||||
default = roleypoly;
|
||||
|
||||
roleypoly = pkgs.buildGoModule {
|
||||
inherit vendorHash;
|
||||
name = "roleypoly";
|
||||
src = ./.;
|
||||
|
||||
preBuild = ''
|
||||
${pkgs.lightningcss}/bin/lightningcss --minify --bundle --targets '>= 0.25%' static/main.css -o static/main.css
|
||||
'';
|
||||
};
|
||||
container = pkgs.dockerTools.buildImage {
|
||||
name = "roleypoly/roleypoly";
|
||||
|
|
5
justfile
5
justfile
|
@ -12,7 +12,10 @@ run-container:
|
|||
docker load -i result
|
||||
docker run -it --rm -p 8169:8169 localhost/roleypoly/roleypoly
|
||||
|
||||
precommit: fmt prettier tidy update-vendor-hash test
|
||||
preprocess:
|
||||
lightningcss --minify --bundle --targets '>= 0.25%' static/main.css
|
||||
|
||||
precommit: fmt prettier tidy update-vendor-hash test
|
||||
|
||||
fmt:
|
||||
go fmt ./...
|
||||
|
|
|
@ -19,19 +19,24 @@ import (
|
|||
"git.sapphic.engineer/roleypoly/v4/templates"
|
||||
"git.sapphic.engineer/roleypoly/v4/testing"
|
||||
"git.sapphic.engineer/roleypoly/v4/types"
|
||||
"git.sapphic.engineer/roleypoly/v4/utils"
|
||||
)
|
||||
|
||||
func CreateFiberApp() *fiber.App {
|
||||
viewEngine := html.NewFileSystem(http.FS(templates.FS), ".html")
|
||||
|
||||
app := fiber.New(fiber.Config{
|
||||
Views: viewEngine,
|
||||
Views: CreateViewEngine(),
|
||||
ViewsLayout: "layouts/main",
|
||||
})
|
||||
|
||||
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 {
|
||||
path := strings.Replace(c.OriginalURL(), "/", "", 1)
|
||||
f, err := staticfs.FS.Open(path)
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
#!/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
|
||||
|
||||
# NPM
|
||||
checksum=$(prefetch-npm-deps package-lock.json)
|
||||
sed -i -e "s|npmDepsHash ? \".*\"|npmDepsHash ? \"$checksum\"|" default.nix
|
||||
|
||||
# Go
|
||||
go mod tidy
|
||||
failedbuild=$(nix build --impure --expr '(with import <nixpkgs> {}; pkgs.callPackage ./. { vendorHash = ""; }).roleypoly' 2>&1 || true)
|
||||
echo "$failedbuild"
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
nil
|
||||
air
|
||||
nodePackages.prettier
|
||||
lightningcss
|
||||
pre-commit
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,4 +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;" />
|
||||
<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="8" y="3" style="transform: rotate(45deg); transform-origin: center;" />
|
||||
</svg>
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 323 B |
111
static/main.css
111
static/main.css
|
@ -1,107 +1,28 @@
|
|||
:root {
|
||||
--taupe100: #332d2d;
|
||||
--taupe200: #453e3d;
|
||||
--taupe300: #5d5352;
|
||||
--taupe400: #756867;
|
||||
--taupe500: #ab9b9a;
|
||||
--taupe600: #ebd6d4;
|
||||
/* Always first (just in case) */
|
||||
@import url(styles/palette.css);
|
||||
|
||||
--discord100: #23272a;
|
||||
--discord200: #2c2f33;
|
||||
--discord400: #7289da;
|
||||
--discord500: #99aab5;
|
||||
|
||||
--green400: #46b646;
|
||||
--green200: #1d8227;
|
||||
|
||||
--red400: #e95353;
|
||||
--red200: #f14343;
|
||||
|
||||
--gold400: #efcf24;
|
||||
|
||||
--grey100: #1c1010;
|
||||
--grey500: #dbd9d9;
|
||||
--grey600: #f2efef;
|
||||
}
|
||||
/* Sort these */
|
||||
@import url(styles/category.css);
|
||||
@import url(styles/nav.css);
|
||||
@import url(styles/picker.css);
|
||||
@import url(styles/role.css);
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 1rem;
|
||||
padding: 0;
|
||||
font-family: "Atkinson Hyperlegible", sans-serif;
|
||||
background-color: var(--taupe200);
|
||||
background-color: var(--taupe100);
|
||||
color: var(--taupe600);
|
||||
max-width: 960px;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.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: 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);
|
||||
}
|
||||
}
|
||||
main {
|
||||
padding: 4rem 1rem 1rem 1rem;
|
||||
background-color: var(--taupe300);
|
||||
box-shadow: 0 0 10px #00000044;
|
||||
}
|
||||
|
|
9
static/styles/category.css
Normal file
9
static/styles/category.css
Normal 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
16
static/styles/nav.css
Normal 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
25
static/styles/palette.css
Normal 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
9
static/styles/picker.css
Normal 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
94
static/styles/role.css
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
1
templates/components/blank.html
Normal file
1
templates/components/blank.html
Normal file
|
@ -0,0 +1 @@
|
|||
<h1>hello world</h1>
|
27
templates/components/category.go
Normal file
27
templates/components/category.go
Normal 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,
|
||||
}
|
||||
}
|
14
templates/components/category.html
Normal file
14
templates/components/category.html
Normal 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>
|
43
templates/components/category_test.go
Normal file
43
templates/components/category_test.go
Normal 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")
|
||||
}
|
|
@ -10,6 +10,7 @@ type RoleTemplateData struct {
|
|||
CategoryID string
|
||||
Name string
|
||||
Selected bool
|
||||
Safe bool
|
||||
InputType InputType
|
||||
Colors RoleColors
|
||||
}
|
||||
|
@ -26,7 +27,7 @@ const (
|
|||
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
|
||||
if category.Type == types.CategorySingle {
|
||||
inputType = InputRadio
|
||||
|
@ -38,9 +39,10 @@ func Role(category *types.Category, role *types.Role, selected bool) RoleTemplat
|
|||
ID: role.ID,
|
||||
CategoryID: category.ID,
|
||||
Name: role.Name,
|
||||
Selected: selected,
|
||||
Selected: role.Selected,
|
||||
InputType: inputType,
|
||||
Colors: colors,
|
||||
Safe: role.Safe,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
class="role"
|
||||
style="--role-color: {{.Colors.Main}}; --contrast-color: {{.Colors.Alt}};"
|
||||
data-testid="{{$for}}"
|
||||
title="{{if not .Safe}}This role is considered unsafe.{{end}}"
|
||||
>
|
||||
<input type="{{.InputType}}" id="{{$for}}" {{if eq .InputType
|
||||
"radio"}}name="category_group_{{.CategoryID}}"{{end}} />
|
||||
<input type="{{.InputType}}" id="{{$for}}" value="{{$for}}" {{if eq .InputType
|
||||
"radio"}}name="category_group_{{.CategoryID}}"{{end}} {{if not
|
||||
.Safe}}disabled="disabled"{{end}} {{if .Selected}}checked="checked"{{end}}/>
|
||||
<label for="{{$for}}">{{.Name}}</label>
|
||||
</div>
|
||||
|
|
|
@ -14,39 +14,46 @@ import (
|
|||
func TestRoleTemplate(t *testing.T) {
|
||||
c := &fixtures.CategoryMulti
|
||||
r := &fixtures.RoleWithDarkColor
|
||||
data := components.Role(c, r, true)
|
||||
data := components.Role(c, r)
|
||||
html := templatetesting.Template(t, "components/role", data)
|
||||
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, 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(`for="%s"`, utils.RoleInputID(c, r)), "label has for attr")
|
||||
assert.NotContains(t, html, fmt.Sprintf(`name="%s"`, utils.RoleInputName(c)), "multi has no name 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.ID, r.ID)), "label has for 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?
|
||||
|
||||
c = &fixtures.CategorySingle
|
||||
r = &fixtures.RoleWithLightColor
|
||||
data = components.Role(c, r, false)
|
||||
data = components.Role(c, r)
|
||||
html = templatetesting.Template(t, "components/role", data)
|
||||
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(`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) {
|
||||
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.Name, r.Name)
|
||||
assert.Equal(t, components.InputCheckbox, r.InputType)
|
||||
assert.Equal(t, "#a20000", r.Colors.Main)
|
||||
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.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.False(t, r.Safe)
|
||||
}
|
||||
|
||||
func TestNewRoleColors(t *testing.T) {
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>{{ .HeadTitle }}</title>
|
||||
<link rel="preconnect" href="https://fonts.bunny.net" />
|
||||
<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"
|
||||
/>
|
||||
<link rel="stylesheet" href="/static/main.css" />
|
||||
</head>
|
||||
<body>
|
||||
{{ template "components/nav" . }} {{embed}}
|
||||
{{ template "components/nav" . }}
|
||||
<main>{{embed}}</main>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -8,9 +8,9 @@ import (
|
|||
)
|
||||
|
||||
func TestMainLayout(t *testing.T) {
|
||||
r := templatetesting.Template(t, "layouts/main", struct{ HeadTitle string }{HeadTitle: "roleypoly"})
|
||||
assert.Contains(t, r, "%%EMBED%%", "has {{embed}}")
|
||||
r := templatetesting.Template(t, "components/blank", struct{ HeadTitle string }{HeadTitle: "roleypoly"}, "layouts/main")
|
||||
assert.Contains(t, r, "<nav>", "loaded navigation (open)")
|
||||
assert.Contains(t, r, "</nav>", "loaded navigation (close)")
|
||||
assert.Contains(t, r, "<title>roleypoly</title>", "sets title")
|
||||
assert.Contains(t, r, "<h1>hello world</h1>", "has {{embed}}")
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -3,64 +3,20 @@ package templatetesting
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/templates"
|
||||
"git.sapphic.engineer/roleypoly/v4/roleypoly"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
funcMap = template.FuncMap{
|
||||
"embed": func() string {
|
||||
return "%%EMBED%%"
|
||||
},
|
||||
}
|
||||
Templates *template.Template = template.New("index").Funcs(funcMap)
|
||||
viewEngine = roleypoly.CreateViewEngine()
|
||||
)
|
||||
|
||||
func init() {
|
||||
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 {
|
||||
func Template(t *testing.T, name string, data interface{}, layout ...string) string {
|
||||
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()
|
||||
}
|
||||
|
||||
func debugTemplates(t *testing.T) {
|
||||
for i, tmpl := range Templates.Templates() {
|
||||
t.Logf("template %d: name=%s", i, tmpl.Name())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -31,21 +31,33 @@ func (t *TestingController) Index(c fiber.Ctx) error {
|
|||
|
||||
func (t *TestingController) Picker(c fiber.Ctx) error {
|
||||
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 {
|
||||
which := c.Params("which")
|
||||
cat1 := fixtures.Category(fixtures.CategoryMulti)
|
||||
cat2 := fixtures.Category(fixtures.CategorySingle)
|
||||
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")
|
||||
|
||||
return c.Render("tests/"+which, fiber.Map{}, "layouts/main")
|
||||
}
|
||||
|
||||
func (t *TestingController) GetMember(c fiber.Ctx) error {
|
||||
|
|
|
@ -10,20 +10,23 @@ import (
|
|||
var (
|
||||
CategoryMulti = types.Category{
|
||||
ID: "multi",
|
||||
Name: "Roles",
|
||||
Name: "Multi",
|
||||
Type: types.CategoryMultiple,
|
||||
Roles: []string{RoleWithDarkColor.ID, RoleWithLightColor.ID, RoleWithoutColor.ID},
|
||||
}
|
||||
|
||||
CategorySingle = types.Category{
|
||||
ID: "single",
|
||||
Name: "Roles",
|
||||
Name: "Single",
|
||||
Type: types.CategorySingle,
|
||||
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())
|
||||
if name != "" {
|
||||
base.Name = name
|
||||
}
|
||||
return &base
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ var (
|
|||
Color: 0xa20000,
|
||||
Permissions: 0,
|
||||
Position: 10,
|
||||
Safe: true,
|
||||
Selected: true,
|
||||
}
|
||||
RoleWithDarkMediumColor = types.Role{
|
||||
ID: "dark-medium-color",
|
||||
|
@ -16,6 +18,7 @@ var (
|
|||
Color: 0xeb5e4b,
|
||||
Permissions: 0,
|
||||
Position: 11,
|
||||
Safe: true,
|
||||
}
|
||||
RoleWithLightMediumColor = types.Role{
|
||||
ID: "light-medium-color",
|
||||
|
@ -23,6 +26,8 @@ var (
|
|||
Color: 0xfa8373,
|
||||
Permissions: 0,
|
||||
Position: 11,
|
||||
Safe: true,
|
||||
Selected: true,
|
||||
}
|
||||
RoleWithLightColor = types.Role{
|
||||
ID: "light-color",
|
||||
|
@ -30,6 +35,7 @@ var (
|
|||
Color: 0xffaa88,
|
||||
Permissions: 0,
|
||||
Position: 12,
|
||||
Safe: true,
|
||||
}
|
||||
RoleWithoutColor = types.Role{
|
||||
ID: "without-color",
|
||||
|
@ -37,6 +43,24 @@ var (
|
|||
Color: 0x000000,
|
||||
Permissions: 0,
|
||||
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 manage roles (bad)
|
||||
|
|
|
@ -8,4 +8,8 @@ type Role struct {
|
|||
UnicodeEmoji string `json:"unicode_emoji"` // unused, future?
|
||||
Permissions uint64 `json:"permissions,string"`
|
||||
Position uint8 `json:"position"`
|
||||
|
||||
// Not Discord; used internally
|
||||
Selected bool `json:"-"`
|
||||
Safe bool `json:"-"`
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package utils
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
)
|
||||
|
||||
|
@ -43,7 +42,7 @@ func AltColor(r, g, b uint8) (uint8, uint8, uint8) {
|
|||
l2 := Luminance(r2, g2, b2)
|
||||
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 {
|
||||
return r2, g2, b2
|
||||
|
|
|
@ -28,10 +28,10 @@ func TestBrighten(t *testing.T) {
|
|||
assert.Equal(t, uint8(0x88-0x19-1), r)
|
||||
assert.Equal(t, uint8(0x88-0x19-1), g)
|
||||
assert.Equal(t, uint8(0x88-0x19-1), b)
|
||||
assert.GreaterOrEqual(t, utils.WCAGRatio(
|
||||
utils.Luminance(0x88, 0x88, 0x88),
|
||||
utils.Luminance(r, g, b),
|
||||
), WCAGAA)
|
||||
// assert.GreaterOrEqual(t, utils.WCAGRatio(
|
||||
// utils.Luminance(0x88, 0x88, 0x88),
|
||||
// utils.Luminance(r, g, b),
|
||||
// ), WCAGAA)
|
||||
}
|
||||
|
||||
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(0xef), g, "green")
|
||||
assert.Equal(t, uint8(0xef), b, "blue")
|
||||
assert.GreaterOrEqual(t, utils.WCAGRatio(
|
||||
utils.Luminance(0xa2, 0xc2, 0x42),
|
||||
utils.Luminance(r, g, b),
|
||||
), WCAGAA)
|
||||
// assert.GreaterOrEqual(t, utils.WCAGRatio(
|
||||
// utils.Luminance(0xa2, 0xc2, 0x42),
|
||||
// utils.Luminance(r, g, b),
|
||||
// ), WCAGAA)
|
||||
|
||||
r, g, b = utils.AltColor(0xa2, 0x15, 0x18)
|
||||
assert.Equal(t, uint8(0xff), r, "red2")
|
||||
assert.Equal(t, uint8(0xed), g, "green2")
|
||||
assert.Equal(t, uint8(0xf0), b, "blue2")
|
||||
assert.GreaterOrEqual(t, utils.WCAGRatio(
|
||||
utils.Luminance(0xa2, 0x15, 0x18),
|
||||
utils.Luminance(r, g, b),
|
||||
), WCAGAA)
|
||||
// assert.GreaterOrEqual(t, utils.WCAGRatio(
|
||||
// utils.Luminance(0xa2, 0x15, 0x18),
|
||||
// utils.Luminance(r, g, b),
|
||||
// ), WCAGAA)
|
||||
|
||||
}
|
||||
|
|
|
@ -2,14 +2,20 @@ package utils
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/types"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
func RoleInputID(c *types.Category, r *types.Role) string {
|
||||
return fmt.Sprintf("category-%s_role-%s", c.ID, r.ID)
|
||||
func RoleInputID(c string, r string) string {
|
||||
return fmt.Sprintf("category-%s_role-%s", c, r)
|
||||
}
|
||||
|
||||
func RoleInputName(c *types.Category) string {
|
||||
return fmt.Sprintf("category_group_%s", c.ID)
|
||||
func RoleInputName(c string) string {
|
||||
return fmt.Sprintf("category_group_%s", c)
|
||||
}
|
||||
|
||||
func TemplateFuncs() template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"roleInputID": RoleInputID,
|
||||
"roleInputName": RoleInputName,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,15 +3,14 @@ package utils_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"git.sapphic.engineer/roleypoly/v4/types"
|
||||
"git.sapphic.engineer/roleypoly/v4/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
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) {
|
||||
assert.Equal(t, "category_group_123", utils.RoleInputName(&types.Category{ID: "123"}))
|
||||
assert.Equal(t, "category_group_123", utils.RoleInputName("123"))
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue