diff --git a/default.nix b/default.nix index 48f342c..d28464b 100644 --- a/default.nix +++ b/default.nix @@ -10,14 +10,8 @@ name = "roleypoly"; src = ./.; - nativeBuildInputs = with pkgs; [ - lightningcss - just - ]; - preBuild = '' - lightningcss --minify --bundle --targets '>= 0.25%' static/main.css -o static/main.css~ - mv static/main.css~ static/main.css + ${pkgs.lightningcss}/bin/lightningcss --minify --bundle --targets '>= 0.25%' static/main.css -o static/main.css ''; }; container = pkgs.dockerTools.buildImage { diff --git a/static/images/check.svg b/static/images/check.svg index 7c56d2f..8a3f5f0 100644 --- a/static/images/check.svg +++ b/static/images/check.svg @@ -1,4 +1,4 @@ - - + + \ No newline at end of file diff --git a/static/main.css b/static/main.css index 8846d51..1fc0451 100644 --- a/static/main.css +++ b/static/main.css @@ -1,4 +1,10 @@ +/* Always first (just in case) */ @import url(styles/palette.css); + +/* Sort these */ +@import url(styles/category.css); +@import url(styles/nav.css); +@import url(styles/picker.css); @import url(styles/role.css); * { @@ -6,7 +12,17 @@ } 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; +} + +main { + padding: 4rem 1rem 1rem 1rem; + background-color: var(--taupe300); + box-shadow: 0 0 10px #00000044; } diff --git a/static/styles/category.css b/static/styles/category.css new file mode 100644 index 0000000..1ecdc2a --- /dev/null +++ b/static/styles/category.css @@ -0,0 +1,9 @@ +.category { + background-color: var(--taupe200); + padding: 1.5rem; + + h3 { + margin: 0 0 0.5rem 0; + padding: 0; + } +} diff --git a/static/styles/nav.css b/static/styles/nav.css new file mode 100644 index 0000000..1ac5afe --- /dev/null +++ b/static/styles/nav.css @@ -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; +} diff --git a/static/styles/picker.css b/static/styles/picker.css new file mode 100644 index 0000000..0e5072d --- /dev/null +++ b/static/styles/picker.css @@ -0,0 +1,9 @@ +.picker { + padding-top: 0.5rem; + + .categories { + display: flex; + flex-direction: column; + gap: 1rem; + } +} diff --git a/static/styles/role.css b/static/styles/role.css index 1f7d12b..de72576 100644 --- a/static/styles/role.css +++ b/static/styles/role.css @@ -7,7 +7,7 @@ user-select: none; padding: 0.369rem; /* this is silly number pls ignore :3 (^noe) */ gap: 0.269rem; - cursor: pointer; + /* cursor: pointer; */ transition: all 0.35s ease-in-out; input { @@ -34,14 +34,16 @@ transition: all 0.35s ease-in-out; content: ""; mask-image: url(/static/images/check.svg); - mask-size: cover; - mask-position: center center; + 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: 0; + left: -2px; + right: -2px; width: 1.216rem; } } @@ -51,6 +53,27 @@ 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); diff --git a/templates/components/category.go b/templates/components/category.go index 93923f2..edc7e84 100644 --- a/templates/components/category.go +++ b/templates/components/category.go @@ -12,5 +12,16 @@ type CategoryTemplateData struct { } func Category(cat *types.Category, roles []*types.Role) CategoryTemplateData { - return 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, + } } diff --git a/templates/components/category_test.go b/templates/components/category_test.go index 639a2e8..4de959b 100644 --- a/templates/components/category_test.go +++ b/templates/components/category_test.go @@ -18,7 +18,7 @@ func TestCategoryTemplate(t *testing.T) { Name: "Multi", Type: types.CategoryMultiple, Roles: []components.RoleTemplateData{ - components.Role(&fixtures.CategoryMulti, &fixtures.RoleWithDarkColor, true, true), + components.Role(&fixtures.CategoryMulti, &fixtures.RoleWithDarkColor), }, } html := templatetesting.Template(t, "components/category", c) @@ -31,7 +31,7 @@ func TestCategoryTemplate(t *testing.T) { ID: "456", Name: "Single", Roles: []components.RoleTemplateData{ - components.Role(&fixtures.CategorySingle, &fixtures.RoleWithDarkColor, true, true), + components.Role(&fixtures.CategorySingle, &fixtures.RoleWithDarkColor), }, } html = templatetesting.Template(t, "components/category", c) diff --git a/templates/components/role.go b/templates/components/role.go index afc5dbf..8617b38 100644 --- a/templates/components/role.go +++ b/templates/components/role.go @@ -27,7 +27,7 @@ const ( InputRadio InputType = "radio" ) -func Role(category *types.Category, role *types.Role, selected bool, safe bool) RoleTemplateData { +func Role(category *types.Category, role *types.Role) RoleTemplateData { inputType := InputCheckbox if category.Type == types.CategorySingle { inputType = InputRadio @@ -39,10 +39,10 @@ func Role(category *types.Category, role *types.Role, selected bool, safe bool) ID: role.ID, CategoryID: category.ID, Name: role.Name, - Selected: selected, + Selected: role.Selected, InputType: inputType, Colors: colors, - Safe: safe, + Safe: role.Safe, } } diff --git a/templates/components/role.html b/templates/components/role.html index 56452eb..5ffe774 100644 --- a/templates/components/role.html +++ b/templates/components/role.html @@ -3,9 +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}}" > - + .Safe}}disabled="disabled"{{end}} {{if .Selected}}checked="checked"{{end}}/> diff --git a/templates/components/role_test.go b/templates/components/role_test.go index c46a0ce..cdf8637 100644 --- a/templates/components/role_test.go +++ b/templates/components/role_test.go @@ -14,7 +14,7 @@ import ( func TestRoleTemplate(t *testing.T) { c := &fixtures.CategoryMulti r := &fixtures.RoleWithDarkColor - data := components.Role(c, r, true, 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") @@ -22,34 +22,36 @@ func TestRoleTemplate(t *testing.T) { 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, true) + 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.ID)), "single has name attr") + assert.NotContains(t, html, `checked="checked"`) - data = components.Role(c, r, false, false) + 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, 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, true) + 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, false) + r = components.Role(&fixtures.CategorySingle, &fixtures.RoleWithLightColor) assert.True(t, r.Selected) assert.False(t, r.Safe) } diff --git a/templates/layouts/main.html b/templates/layouts/main.html index 0617048..7b77d1e 100644 --- a/templates/layouts/main.html +++ b/templates/layouts/main.html @@ -1,16 +1,17 @@ - + {{ .HeadTitle }} - {{ template "components/nav" . }} {{embed}} + {{ template "components/nav" . }} +
{{embed}}
diff --git a/templates/picker/main.html b/templates/picker/main.html index a8a6eec..34ba627 100644 --- a/templates/picker/main.html +++ b/templates/picker/main.html @@ -1 +1,8 @@ -

picker!

+
+

Roleypoly

+ +
+ {{ range $_, $category := .Categories }} {{ template "components/category" + $category }} {{ end }} +
+
diff --git a/templates/tests/picker.html b/templates/tests/picker.html deleted file mode 100644 index cbe3716..0000000 --- a/templates/tests/picker.html +++ /dev/null @@ -1,28 +0,0 @@ - - -
-
-

Multi

- {{ range $_, $r := .Category1 }} {{ template "components/role" $r }} {{ end - }} -
-
-

Single

- {{ range $_, $r := .Category2 }} {{ template "components/role" $r }} {{ end - }} -
-
-

Bad

- {{ range $_, $r := .Category3 }} {{ template "components/role" $r }} {{ end - }} -
-
-

Hmm

- {{ range $_, $r := .Category4 }} {{ template "components/role" $r }} {{ end - }} -
-
diff --git a/testing/testing.go b/testing/testing.go index cea9f71..f961f0d 100644 --- a/testing/testing.go +++ b/testing/testing.go @@ -31,45 +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) - cat3 := fixtures.Category(fixtures.CategoryMulti) - cat4 := fixtures.Category(fixtures.CategorySingle) - return c.Render("tests/"+which, fiber.Map{ - // multi - "Category1": []components.RoleTemplateData{ - components.Role(cat1, &fixtures.RoleWithDarkColor, true, true), - components.Role(cat1, &fixtures.RoleWithDarkMediumColor, true, true), - components.Role(cat1, &fixtures.RoleWithLightMediumColor, true, true), - components.Role(cat1, &fixtures.RoleWithLightColor, true, true), - }, - // single - "Category2": []components.RoleTemplateData{ - components.Role(cat2, &fixtures.RoleWithDarkColor, true, true), - components.Role(cat2, &fixtures.RoleWithDarkMediumColor, true, true), - components.Role(cat2, &fixtures.RoleWithLightMediumColor, true, true), - components.Role(cat2, &fixtures.RoleWithLightColor, true, true), - }, - // unsafe - "Category3": []components.RoleTemplateData{ - components.Role(cat3, &fixtures.RoleWithDarkColor, true, false), - components.Role(cat3, &fixtures.RoleWithDarkMediumColor, false, false), - components.Role(cat3, &fixtures.RoleWithLightMediumColor, false, false), - components.Role(cat3, &fixtures.RoleWithLightColor, true, false), - }, - // unselected - "Category4": []components.RoleTemplateData{ - components.Role(cat4, &fixtures.RoleWithDarkColor, true, true), - components.Role(cat4, &fixtures.RoleWithDarkMediumColor, false, true), - components.Role(cat4, &fixtures.RoleWithLightMediumColor, false, true), - components.Role(cat4, &fixtures.RoleWithLightColor, false, true), - }, - }, "layouts/main") + + return c.Render("tests/"+which, fiber.Map{}, "layouts/main") } func (t *TestingController) GetMember(c fiber.Ctx) error { diff --git a/types/fixtures/category.go b/types/fixtures/category.go index 02db33f..2870f96 100644 --- a/types/fixtures/category.go +++ b/types/fixtures/category.go @@ -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 } diff --git a/types/fixtures/role.go b/types/fixtures/role.go index 9f3b592..933eafe 100644 --- a/types/fixtures/role.go +++ b/types/fixtures/role.go @@ -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) diff --git a/types/role.go b/types/role.go index 761790b..6d481b2 100644 --- a/types/role.go +++ b/types/role.go @@ -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:"-"` } diff --git a/utils/colors.go b/utils/colors.go index a2bc97f..262b8a4 100644 --- a/utils/colors.go +++ b/utils/colors.go @@ -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