mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-07 05:58:28 -04:00
theme: add WCAG data
This commit is contained in:
@@ -40,6 +40,7 @@ func HandleList(conn *models.Conn, req models.Request) {
|
|||||||
SourceDir: t.SourceDir,
|
SourceDir: t.SourceDir,
|
||||||
Installed: installed,
|
Installed: installed,
|
||||||
FirstParty: isFirstParty(t.Author),
|
FirstParty: isFirstParty(t.Author),
|
||||||
|
WCAG: t.WCAG,
|
||||||
}
|
}
|
||||||
addVariantsInfo(&info, t.Variants)
|
addVariantsInfo(&info, t.Variants)
|
||||||
result[i] = info
|
result[i] = info
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ func HandleSearch(conn *models.Conn, req models.Request) {
|
|||||||
Description: t.Description,
|
Description: t.Description,
|
||||||
Installed: installed,
|
Installed: installed,
|
||||||
FirstParty: isFirstParty(t.Author),
|
FirstParty: isFirstParty(t.Author),
|
||||||
|
WCAG: t.WCAG,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package themes
|
package themes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/themes"
|
||||||
|
)
|
||||||
|
|
||||||
type VariantInfo struct {
|
type VariantInfo struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -32,16 +36,17 @@ type VariantsInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ThemeInfo struct {
|
type ThemeInfo struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Author string `json:"author,omitempty"`
|
Author string `json:"author,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
PreviewPath string `json:"previewPath,omitempty"`
|
PreviewPath string `json:"previewPath,omitempty"`
|
||||||
SourceDir string `json:"sourceDir,omitempty"`
|
SourceDir string `json:"sourceDir,omitempty"`
|
||||||
Installed bool `json:"installed,omitempty"`
|
Installed bool `json:"installed,omitempty"`
|
||||||
FirstParty bool `json:"firstParty,omitempty"`
|
FirstParty bool `json:"firstParty,omitempty"`
|
||||||
HasUpdate bool `json:"hasUpdate,omitempty"`
|
HasUpdate bool `json:"hasUpdate,omitempty"`
|
||||||
HasVariants bool `json:"hasVariants,omitempty"`
|
HasVariants bool `json:"hasVariants,omitempty"`
|
||||||
Variants *VariantsInfo `json:"variants,omitempty"`
|
Variants *VariantsInfo `json:"variants,omitempty"`
|
||||||
|
WCAG *themes.ThemeWCAG `json:"wcag,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,28 @@ type ThemeVariants struct {
|
|||||||
Accents []ThemeAccent `json:"accents,omitempty"`
|
Accents []ThemeAccent `json:"accents,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ThemeWCAGGroup struct {
|
||||||
|
Level string `json:"level"`
|
||||||
|
MinRatio float64 `json:"minRatio"`
|
||||||
|
WorstPair []string `json:"worstPair,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ThemeWCAGMode struct {
|
||||||
|
Level string `json:"level"`
|
||||||
|
MinRatio float64 `json:"minRatio"`
|
||||||
|
WorstPair []string `json:"worstPair,omitempty"`
|
||||||
|
Body *ThemeWCAGGroup `json:"body,omitempty"`
|
||||||
|
Accent *ThemeWCAGGroup `json:"accent,omitempty"`
|
||||||
|
NonText *ThemeWCAGGroup `json:"nonText,omitempty"`
|
||||||
|
Variants map[string]string `json:"variants,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ThemeWCAG struct {
|
||||||
|
Level string `json:"level"`
|
||||||
|
Dark *ThemeWCAGMode `json:"dark,omitempty"`
|
||||||
|
Light *ThemeWCAGMode `json:"light,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type Theme struct {
|
type Theme struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -113,6 +135,7 @@ type Theme struct {
|
|||||||
Dark ColorScheme `json:"dark"`
|
Dark ColorScheme `json:"dark"`
|
||||||
Light ColorScheme `json:"light"`
|
Light ColorScheme `json:"light"`
|
||||||
Variants *ThemeVariants `json:"variants,omitempty"`
|
Variants *ThemeVariants `json:"variants,omitempty"`
|
||||||
|
WCAG *ThemeWCAG `json:"wcag,omitempty"`
|
||||||
PreviewPath string `json:"-"`
|
PreviewPath string `json:"-"`
|
||||||
SourceDir string `json:"sourceDir,omitempty"`
|
SourceDir string `json:"sourceDir,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -240,6 +263,7 @@ func (r *Registry) loadThemes() error {
|
|||||||
theme.ID = entry.Name()
|
theme.ID = entry.Name()
|
||||||
}
|
}
|
||||||
theme.SourceDir = entry.Name()
|
theme.SourceDir = entry.Name()
|
||||||
|
theme.WCAG = loadThemeWCAG(r.fs, themeDir)
|
||||||
|
|
||||||
previewPath := filepath.Join(themeDir, "preview.svg")
|
previewPath := filepath.Join(themeDir, "preview.svg")
|
||||||
if exists, _ := afero.Exists(r.fs, previewPath); exists {
|
if exists, _ := afero.Exists(r.fs, previewPath); exists {
|
||||||
@@ -252,6 +276,20 @@ func (r *Registry) loadThemes() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadThemeWCAG(fs afero.Fs, themeDir string) *ThemeWCAG {
|
||||||
|
data, err := afero.ReadFile(fs, filepath.Join(themeDir, "wcag.json"))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var wcag ThemeWCAG
|
||||||
|
if err := json.Unmarshal(data, &wcag); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &wcag
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Registry) List() ([]Theme, error) {
|
func (r *Registry) List() ([]Theme, error) {
|
||||||
if len(r.themes) == 0 {
|
if len(r.themes) == 0 {
|
||||||
if err := r.Update(); err != nil {
|
if err := r.Update(); err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package themes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadThemeWCAG(t *testing.T) {
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
themeDir := "/themes/example"
|
||||||
|
wcagJSON := `{
|
||||||
|
"level": "AA",
|
||||||
|
"dark": {
|
||||||
|
"level": "AAA", "minRatio": 8.5, "worstPair": ["surfaceText", "surface"],
|
||||||
|
"body": {"level": "AAA", "minRatio": 8.5},
|
||||||
|
"accent": {"level": "AAA", "minRatio": 9.1}
|
||||||
|
},
|
||||||
|
"light": {
|
||||||
|
"level": "AA", "minRatio": 5.2,
|
||||||
|
"body": {"level": "AAA", "minRatio": 7.4},
|
||||||
|
"accent": {"level": "AA", "minRatio": 5.2},
|
||||||
|
"variants": {"blue": "AA", "red": "fail"}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
if err := afero.WriteFile(fs, themeDir+"/wcag.json", []byte(wcagJSON), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wcag := loadThemeWCAG(fs, themeDir)
|
||||||
|
if wcag == nil {
|
||||||
|
t.Fatal("expected wcag report, got nil")
|
||||||
|
}
|
||||||
|
if wcag.Level != "AA" {
|
||||||
|
t.Fatalf("expected level AA, got %s", wcag.Level)
|
||||||
|
}
|
||||||
|
if wcag.Dark.Level != "AAA" || wcag.Dark.MinRatio != 8.5 {
|
||||||
|
t.Fatalf("unexpected dark mode report: %+v", wcag.Dark)
|
||||||
|
}
|
||||||
|
if wcag.Light.Variants["red"] != "fail" {
|
||||||
|
t.Fatalf("unexpected light variants: %+v", wcag.Light.Variants)
|
||||||
|
}
|
||||||
|
if wcag.Light.Body == nil || wcag.Light.Body.Level != "AAA" {
|
||||||
|
t.Fatalf("expected light body AAA, got %+v", wcag.Light.Body)
|
||||||
|
}
|
||||||
|
if wcag.Light.Accent == nil || wcag.Light.Accent.Level != "AA" {
|
||||||
|
t.Fatalf("expected light accent AA, got %+v", wcag.Light.Accent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadThemeWCAGMissingFile(t *testing.T) {
|
||||||
|
if wcag := loadThemeWCAG(afero.NewMemMapFs(), "/themes/none"); wcag != nil {
|
||||||
|
t.Fatalf("expected nil for missing wcag.json, got %+v", wcag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadThemeWCAGInvalidJSON(t *testing.T) {
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
if err := afero.WriteFile(fs, "/themes/bad/wcag.json", []byte("{"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wcag := loadThemeWCAG(fs, "/themes/bad"); wcag != nil {
|
||||||
|
t.Fatalf("expected nil for invalid wcag.json, got %+v", wcag)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -500,6 +500,36 @@ FloatingWindow {
|
|||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: wcagBadge
|
||||||
|
|
||||||
|
// Rated for the mode being viewed, since a theme
|
||||||
|
// often passes in one mode and not the other.
|
||||||
|
readonly property var modeReport: Theme.isLightMode ? modelData.wcag?.light : modelData.wcag?.dark
|
||||||
|
readonly property string level: modeReport?.level ?? ""
|
||||||
|
readonly property string bodyLevel: modeReport?.body?.level ?? ""
|
||||||
|
readonly property bool fullPass: level === "AA" || level === "AAA"
|
||||||
|
readonly property bool bodyPass: bodyLevel === "AA" || bodyLevel === "AAA"
|
||||||
|
|
||||||
|
height: 18
|
||||||
|
width: wcagText.implicitWidth + Theme.spacingS
|
||||||
|
radius: 9
|
||||||
|
color: Theme.withAlpha(fullPass ? Theme.info : Theme.surfaceVariantText, 0.15)
|
||||||
|
border.color: Theme.withAlpha(fullPass ? Theme.info : Theme.surfaceVariantText, 0.4)
|
||||||
|
border.width: 1
|
||||||
|
visible: fullPass || bodyPass
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: wcagText
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: wcagBadge.fullPass ? "WCAG " + wcagBadge.level : I18n.tr("WCAG %1 body", "contrast badge when only body text passes").arg(wcagBadge.bodyLevel)
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: wcagBadge.fullPass ? Theme.info : Theme.surfaceVariantText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
|||||||
Reference in New Issue
Block a user