From d0c4e9d8466a515a0f08d1b30a896cec0d843d2c Mon Sep 17 00:00:00 2001 From: 2Q2C0DE <2q2code@mxfer.com> Date: Thu, 14 Nov 2024 17:35:14 +1100 Subject: [PATCH 1/4] Add Dashboard Icons prefix support - defined new type IconSource and constants, presently supporting: - LocalFile - SimpleIcon - DashboardIcon - added new field to bookmarks and monitors to hold IconSource - adjusted IsSimpleIcon to get truthiness from IconSource field - generalised `toSimpleIconIfPrefixed` into `toRemoteResourceIconIfPrefixed`, adding support for `walkxcode`'s dashboard icons via CDN (svg) --- internal/widget/bookmarks.go | 16 +++++++++------- internal/widget/fields.go | 31 +++++++++++++++++++++++++------ internal/widget/monitor.go | 4 +++- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/internal/widget/bookmarks.go b/internal/widget/bookmarks.go index 962d540..1ad3390 100644 --- a/internal/widget/bookmarks.go +++ b/internal/widget/bookmarks.go @@ -13,12 +13,13 @@ type Bookmarks struct { Title string `yaml:"title"` Color *HSLColorField `yaml:"color"` Links []struct { - Title string `yaml:"title"` - URL string `yaml:"url"` - Icon string `yaml:"icon"` - IsSimpleIcon bool `yaml:"-"` - SameTab bool `yaml:"same-tab"` - HideArrow bool `yaml:"hide-arrow"` + Title string `yaml:"title"` + URL string `yaml:"url"` + Icon string `yaml:"icon"` + IsSimpleIcon bool `yaml:"-"` + IconSource IconSource `yaml:"-"` + SameTab bool `yaml:"same-tab"` + HideArrow bool `yaml:"hide-arrow"` } `yaml:"links"` } `yaml:"groups"` } @@ -33,7 +34,8 @@ func (widget *Bookmarks) Initialize() error { } link := &widget.Groups[g].Links[l] - link.Icon, link.IsSimpleIcon = toSimpleIconIfPrefixed(link.Icon) + link.Icon, link.IconSource = toRemoteResourceIconIfPrefixed(link.Icon) + link.IsSimpleIcon = link.IconSource == SimpleIcon } } diff --git a/internal/widget/fields.go b/internal/widget/fields.go index 9ae1eda..51f7d92 100644 --- a/internal/widget/fields.go +++ b/internal/widget/fields.go @@ -27,6 +27,14 @@ type HSLColorField struct { Lightness uint8 } +type IconSource uint8 + +const ( + LocalFile IconSource = iota + SimpleIcon + DashboardIcon +) + func (c *HSLColorField) String() string { return fmt.Sprintf("hsl(%d, %d%%, %d%%)", c.Hue, c.Saturation, c.Lightness) } @@ -156,13 +164,24 @@ func (f *OptionalEnvString) String() string { return string(*f) } -func toSimpleIconIfPrefixed(icon string) (string, bool) { - if !strings.HasPrefix(icon, "si:") { - return icon, false +func toRemoteResourceIconIfPrefixed(icon string) (string, IconSource) { + var prefix, iconstr string + + prefix, iconstr, found := strings.Cut(icon, ":") + + if !found { + return icon, LocalFile } - icon = strings.TrimPrefix(icon, "si:") - icon = "https://cdnjs.cloudflare.com/ajax/libs/simple-icons/11.14.0/" + icon + ".svg" + if prefix == "si" { + icon = "https://cdnjs.cloudflare.com/ajax/libs/simple-icons/11.14.0/" + iconstr + ".svg" + return icon, SimpleIcon + } - return icon, true + if prefix == "di" { + icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/svg/" + iconstr + ".svg" + return icon, DashboardIcon + } + + return icon, LocalFile } diff --git a/internal/widget/monitor.go b/internal/widget/monitor.go index 06d7303..5ad4e20 100644 --- a/internal/widget/monitor.go +++ b/internal/widget/monitor.go @@ -49,6 +49,7 @@ type Monitor struct { Title string `yaml:"title"` IconUrl string `yaml:"icon"` IsSimpleIcon bool `yaml:"-"` + IconSource IconSource `yaml:"-"` SameTab bool `yaml:"same-tab"` StatusText string `yaml:"-"` StatusStyle string `yaml:"-"` @@ -61,7 +62,8 @@ func (widget *Monitor) Initialize() error { widget.withTitle("Monitor").withCacheDuration(5 * time.Minute) for i := range widget.Sites { - widget.Sites[i].IconUrl, widget.Sites[i].IsSimpleIcon = toSimpleIconIfPrefixed(widget.Sites[i].IconUrl) + widget.Sites[i].IconUrl, widget.Sites[i].IconSource = toRemoteResourceIconIfPrefixed(widget.Sites[i].IconUrl) + widget.Sites[i].IsSimpleIcon = widget.Sites[i].IconSource == SimpleIcon } return nil From d6569864134c4bec56b6251e399f0fb267995835 Mon Sep 17 00:00:00 2001 From: Doc Em Date: Thu, 14 Nov 2024 20:06:54 +1100 Subject: [PATCH 2/4] Add DashboardIcon (PNG) Support - Refactored to make better naming sense - Added capability to select between the PNG and SVG offerings of DashboardIcons --- internal/widget/bookmarks.go | 2 +- internal/widget/fields.go | 24 +++++++++++++++++++----- internal/widget/monitor.go | 2 +- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/internal/widget/bookmarks.go b/internal/widget/bookmarks.go index 1ad3390..b200610 100644 --- a/internal/widget/bookmarks.go +++ b/internal/widget/bookmarks.go @@ -34,7 +34,7 @@ func (widget *Bookmarks) Initialize() error { } link := &widget.Groups[g].Links[l] - link.Icon, link.IconSource = toRemoteResourceIconIfPrefixed(link.Icon) + link.Icon, link.IconSource = toIconURIIfPrefixed(link.Icon) link.IsSimpleIcon = link.IconSource == SimpleIcon } } diff --git a/internal/widget/fields.go b/internal/widget/fields.go index 51f7d92..ca6659a 100644 --- a/internal/widget/fields.go +++ b/internal/widget/fields.go @@ -30,7 +30,7 @@ type HSLColorField struct { type IconSource uint8 const ( - LocalFile IconSource = iota + IconURI IconSource = iota SimpleIcon DashboardIcon ) @@ -164,24 +164,38 @@ func (f *OptionalEnvString) String() string { return string(*f) } -func toRemoteResourceIconIfPrefixed(icon string) (string, IconSource) { +func toIconURIIfPrefixed(icon string) (string, IconSource) { var prefix, iconstr string prefix, iconstr, found := strings.Cut(icon, ":") if !found { - return icon, LocalFile + return icon, IconURI } + // syntax: si: if prefix == "si" { icon = "https://cdnjs.cloudflare.com/ajax/libs/simple-icons/11.14.0/" + iconstr + ".svg" return icon, SimpleIcon } + // syntax: di:[.svg|.png] if prefix == "di" { - icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/svg/" + iconstr + ".svg" + // if the icon name is specified without extension, it is assumed to be wanting the SVG icon + // otherwise, specify the extension of either .svg or .png to use either of the CDN offerings + // any other extension will be interpreted as .svg + var basename, ext string + + basename, ext, found := strings.Cut(iconstr, ".") + + if !found { + ext = "svg" + basename = iconstr + } + + icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/" + ext + "/" + basename + "." + ext return icon, DashboardIcon } - return icon, LocalFile + return icon, IconURI } diff --git a/internal/widget/monitor.go b/internal/widget/monitor.go index 5ad4e20..96715ec 100644 --- a/internal/widget/monitor.go +++ b/internal/widget/monitor.go @@ -62,7 +62,7 @@ func (widget *Monitor) Initialize() error { widget.withTitle("Monitor").withCacheDuration(5 * time.Minute) for i := range widget.Sites { - widget.Sites[i].IconUrl, widget.Sites[i].IconSource = toRemoteResourceIconIfPrefixed(widget.Sites[i].IconUrl) + widget.Sites[i].IconUrl, widget.Sites[i].IconSource = toIconURIIfPrefixed(widget.Sites[i].IconUrl) widget.Sites[i].IsSimpleIcon = widget.Sites[i].IconSource == SimpleIcon } From f7239137d6428824d2e5fa4cd88bd9cee116604d Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Sat, 16 Nov 2024 06:37:05 +0000 Subject: [PATCH 3/4] Update icons implementation to use custom type --- internal/assets/static/main.css | 6 +-- internal/assets/templates/bookmarks.html | 4 +- internal/assets/templates/monitor.html | 4 +- internal/widget/bookmarks.go | 25 ++-------- internal/widget/fields.go | 59 +++++++++++++----------- internal/widget/monitor.go | 9 +--- 6 files changed, 44 insertions(+), 63 deletions(-) diff --git a/internal/assets/static/main.css b/internal/assets/static/main.css index d5ab9bb..4eb9ee6 100644 --- a/internal/assets/static/main.css +++ b/internal/assets/static/main.css @@ -1059,7 +1059,7 @@ details[open] .summary::after { opacity: 0.8; } -:root:not(.light-scheme) .simple-icon { +:root:not(.light-scheme) .flat-icon { filter: invert(1); } @@ -1355,7 +1355,7 @@ details[open] .summary::after { transition: filter 0.3s, opacity 0.3s; } -.monitor-site-icon.simple-icon { +.monitor-site-icon.flat-icon { opacity: 0.7; } @@ -1363,7 +1363,7 @@ details[open] .summary::after { opacity: 1; } -.monitor-site:hover .monitor-site-icon:not(.simple-icon) { +.monitor-site:hover .monitor-site-icon:not(.flat-icon) { filter: grayscale(0); } diff --git a/internal/assets/templates/bookmarks.html b/internal/assets/templates/bookmarks.html index a4e2c97..25afa4d 100644 --- a/internal/assets/templates/bookmarks.html +++ b/internal/assets/templates/bookmarks.html @@ -8,9 +8,9 @@
    {{ range .Links }}
  • - {{ if ne "" .Icon }} + {{ if ne "" .Icon.URL }}
    - +
    {{ end }} {{ .Title }} diff --git a/internal/assets/templates/monitor.html b/internal/assets/templates/monitor.html index b19f0e2..a70a426 100644 --- a/internal/assets/templates/monitor.html +++ b/internal/assets/templates/monitor.html @@ -21,8 +21,8 @@ {{ end }} {{ define "site" }} -{{ if .IconUrl }} - +{{ if .Icon.URL }} + {{ end }}
    {{ .Title }} diff --git a/internal/widget/bookmarks.go b/internal/widget/bookmarks.go index b200610..133fb28 100644 --- a/internal/widget/bookmarks.go +++ b/internal/widget/bookmarks.go @@ -13,32 +13,17 @@ type Bookmarks struct { Title string `yaml:"title"` Color *HSLColorField `yaml:"color"` Links []struct { - Title string `yaml:"title"` - URL string `yaml:"url"` - Icon string `yaml:"icon"` - IsSimpleIcon bool `yaml:"-"` - IconSource IconSource `yaml:"-"` - SameTab bool `yaml:"same-tab"` - HideArrow bool `yaml:"hide-arrow"` + Title string `yaml:"title"` + URL string `yaml:"url"` + Icon CustomIcon `yaml:"icon"` + SameTab bool `yaml:"same-tab"` + HideArrow bool `yaml:"hide-arrow"` } `yaml:"links"` } `yaml:"groups"` } func (widget *Bookmarks) Initialize() error { widget.withTitle("Bookmarks").withError(nil) - - for g := range widget.Groups { - for l := range widget.Groups[g].Links { - if widget.Groups[g].Links[l].Icon == "" { - continue - } - - link := &widget.Groups[g].Links[l] - link.Icon, link.IconSource = toIconURIIfPrefixed(link.Icon) - link.IsSimpleIcon = link.IconSource == SimpleIcon - } - } - widget.cachedHTML = widget.render(widget, assets.BookmarksTemplate) return nil diff --git a/internal/widget/fields.go b/internal/widget/fields.go index ca6659a..81d12a1 100644 --- a/internal/widget/fields.go +++ b/internal/widget/fields.go @@ -27,14 +27,6 @@ type HSLColorField struct { Lightness uint8 } -type IconSource uint8 - -const ( - IconURI IconSource = iota - SimpleIcon - DashboardIcon -) - func (c *HSLColorField) String() string { return fmt.Sprintf("hsl(%d, %d%%, %d%%)", c.Hue, c.Saturation, c.Lightness) } @@ -164,38 +156,49 @@ func (f *OptionalEnvString) String() string { return string(*f) } -func toIconURIIfPrefixed(icon string) (string, IconSource) { - var prefix, iconstr string +type CustomIcon struct { + URL string + IsFlatIcon bool + // TODO: along with whether the icon is flat, we also need to know + // whether the icon is black or white by default in order to properly + // invert the color based on the theme being light or dark +} - prefix, iconstr, found := strings.Cut(icon, ":") +func (i *CustomIcon) UnmarshalYAML(node *yaml.Node) error { + var value string + if err := node.Decode(&value); err != nil { + return err + } + prefix, icon, found := strings.Cut(value, ":") if !found { - return icon, IconURI + i.URL = value + return nil } - // syntax: si: - if prefix == "si" { - icon = "https://cdnjs.cloudflare.com/ajax/libs/simple-icons/11.14.0/" + iconstr + ".svg" - return icon, SimpleIcon - } - - // syntax: di:[.svg|.png] - if prefix == "di" { + switch prefix { + case "si": + i.URL = "https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/" + icon + ".svg" + i.IsFlatIcon = true + case "di": + // syntax: di:[.svg|.png] // if the icon name is specified without extension, it is assumed to be wanting the SVG icon // otherwise, specify the extension of either .svg or .png to use either of the CDN offerings // any other extension will be interpreted as .svg - var basename, ext string - - basename, ext, found := strings.Cut(iconstr, ".") - + basename, ext, found := strings.Cut(icon, ".") if !found { ext = "svg" - basename = iconstr + basename = icon } - icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/" + ext + "/" + basename + "." + ext - return icon, DashboardIcon + if ext != "svg" && ext != "png" { + ext = "svg" + } + + i.URL = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/" + ext + "/" + basename + "." + ext + default: + i.URL = value } - return icon, IconURI + return nil } diff --git a/internal/widget/monitor.go b/internal/widget/monitor.go index 96715ec..3376b0a 100644 --- a/internal/widget/monitor.go +++ b/internal/widget/monitor.go @@ -47,9 +47,7 @@ type Monitor struct { *feed.SiteStatusRequest `yaml:",inline"` Status *feed.SiteStatus `yaml:"-"` Title string `yaml:"title"` - IconUrl string `yaml:"icon"` - IsSimpleIcon bool `yaml:"-"` - IconSource IconSource `yaml:"-"` + Icon CustomIcon `yaml:"icon"` SameTab bool `yaml:"same-tab"` StatusText string `yaml:"-"` StatusStyle string `yaml:"-"` @@ -61,11 +59,6 @@ type Monitor struct { func (widget *Monitor) Initialize() error { widget.withTitle("Monitor").withCacheDuration(5 * time.Minute) - for i := range widget.Sites { - widget.Sites[i].IconUrl, widget.Sites[i].IconSource = toIconURIIfPrefixed(widget.Sites[i].IconUrl) - widget.Sites[i].IsSimpleIcon = widget.Sites[i].IconSource == SimpleIcon - } - return nil } From 1bba915aefaad55cd3d002ccca0eeaacfca3b6ea Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Sat, 16 Nov 2024 06:37:09 +0000 Subject: [PATCH 4/4] Update docs --- docs/configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 6f9d602..5d7049e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1097,7 +1097,7 @@ The URL which will be requested and its response will determine the status of th `icon` -Optional URL to an image which will be used as the icon for the site. Can be an external URL or internal via [server configured assets](#assets-path). You can also directly use [Simple Icons](https://simpleicons.org/) via a `si:` prefix: +Optional URL to an image which will be used as the icon for the site. Can be an external URL or internal via [server configured assets](#assets-path). You can also directly use [Simple Icons](https://simpleicons.org/) via a `si:` prefix or [Dashboard Icons](https://github.com/walkxcode/dashboard-icons) via a `di:` prefix: ```yaml icon: si:jellyfin @@ -1375,7 +1375,7 @@ An array of groups which can optionally have a title and a custom color. `icon` -URL pointing to an image. You can also directly use [Simple Icons](https://simpleicons.org/) via a `si:` prefix: +URL pointing to an image. You can also directly use [Simple Icons](https://simpleicons.org/) via a `si:` prefix or [Dashboard Icons](https://github.com/walkxcode/dashboard-icons) via a `di:` prefix: ```yaml icon: si:gmail