appview/ogcard: introduce highlevel helpers to draw icons and assets #757

merged
opened by oppi.li targeting master from push-yvtruzmyvxps
  • DrawLucideIcon & DrawDollySilhouette to simplify the svg drawing logic
  • DrawDollySilhouette now depends on the html template itself, instead of a bespoke svg

this changes lets us remove dolly.svg from the fragments.

Signed-off-by: oppiliappan me@oppi.li

Changed files
+75 -26
appview
issues
ogcard
pulls
repo
+5 -5
appview/issues/opengraph.go
···
var statusBgColor color.RGBA
if issue.Open {
-
statusIcon = "static/icons/circle-dot.svg"
+
statusIcon = "circle-dot"
statusText = "open"
statusBgColor = color.RGBA{34, 139, 34, 255} // green
} else {
-
statusIcon = "static/icons/circle-dot.svg"
+
statusIcon = "ban"
statusText = "closed"
statusBgColor = color.RGBA{52, 58, 64, 255} // dark gray
}
···
badgeIconSize := 36
// Draw icon with status color (no background)
-
err = statusCommentsArea.DrawSVGIcon(statusIcon, statsX, statsY+iconBaselineOffset-badgeIconSize/2+5, badgeIconSize, statusBgColor)
+
err = statusCommentsArea.DrawLucideIcon(statusIcon, statsX, statsY+iconBaselineOffset-badgeIconSize/2+5, badgeIconSize, statusBgColor)
if err != nil {
log.Printf("failed to draw status icon: %v", err)
}
···
currentX := statsX + badgeIconSize + 12 + statusTextWidth + 50
// Draw comment count
-
err = statusCommentsArea.DrawSVGIcon("static/icons/message-square.svg", currentX, statsY+iconBaselineOffset-iconSize/2+5, iconSize, iconColor)
+
err = statusCommentsArea.DrawLucideIcon("message-square", currentX, statsY+iconBaselineOffset-iconSize/2+5, iconSize, iconColor)
if err != nil {
log.Printf("failed to draw comment icon: %v", err)
}
···
dollyX := dollyBounds.Min.X + (dollyBounds.Dx() / 2) - (dollySize / 2)
dollyY := statsY + iconBaselineOffset - dollySize/2 + 25
dollyColor := color.RGBA{180, 180, 180, 255} // light gray
-
err = dollyArea.DrawSVGIcon("templates/fragments/dolly/silhouette.svg", dollyX, dollyY, dollySize, dollyColor)
+
err = dollyArea.DrawDollySilhouette(dollyX, dollyY, dollySize, dollyColor)
if err != nil {
log.Printf("dolly silhouette not available (this is ok): %v", err)
}
+59 -10
appview/ogcard/card.go
···
import (
"bytes"
"fmt"
+
"html/template"
"image"
"image/color"
"io"
···
return width, nil
}
-
// DrawSVGIcon draws an SVG icon from the embedded files at the specified position
-
func (c *Card) DrawSVGIcon(svgPath string, x, y, size int, iconColor color.Color) error {
-
svgData, err := pages.Files.ReadFile(svgPath)
-
if err != nil {
-
return fmt.Errorf("failed to read SVG file %s: %w", svgPath, err)
-
}
-
+
func BuildSVGIconFromData(svgData []byte, iconColor color.Color) (*oksvg.SvgIcon, error) {
// Convert color to hex string for SVG
rgba, isRGBA := iconColor.(color.RGBA)
if !isRGBA {
···
// Parse SVG
icon, err := oksvg.ReadIconStream(strings.NewReader(svgString))
if err != nil {
-
return fmt.Errorf("failed to parse SVG %s: %w", svgPath, err)
+
return nil, fmt.Errorf("failed to parse SVG: %w", err)
+
}
+
+
return icon, nil
+
}
+
+
func BuildSVGIconFromPath(svgPath string, iconColor color.Color) (*oksvg.SvgIcon, error) {
+
svgData, err := pages.Files.ReadFile(svgPath)
+
if err != nil {
+
return nil, fmt.Errorf("failed to read SVG file %s: %w", svgPath, err)
+
}
+
+
icon, err := BuildSVGIconFromData(svgData, iconColor)
+
if err != nil {
+
return nil, fmt.Errorf("failed to build SVG icon %s: %w", svgPath, err)
+
}
+
+
return icon, nil
+
}
+
+
func BuildLucideIcon(name string, iconColor color.Color) (*oksvg.SvgIcon, error) {
+
return BuildSVGIconFromPath(fmt.Sprintf("static/icons/%s.svg", name), iconColor)
+
}
+
+
func (c *Card) DrawLucideIcon(name string, x, y, size int, iconColor color.Color) error {
+
icon, err := BuildSVGIconFromPath(fmt.Sprintf("static/icons/%s.svg", name), iconColor)
+
if err != nil {
+
return err
+
}
+
+
c.DrawSVGIcon(icon, x, y, size)
+
+
return nil
+
}
+
+
func (c *Card) DrawDollySilhouette(x, y, size int, iconColor color.Color) error {
+
tpl, err := template.New("dolly").
+
ParseFS(pages.Files, "templates/fragments/dolly/silhouette.html")
+
if err != nil {
+
return fmt.Errorf("failed to read dolly silhouette template: %w", err)
+
}
+
+
var svgData bytes.Buffer
+
if err = tpl.ExecuteTemplate(&svgData, "fragments/dolly/silhouette", nil); err != nil {
+
return fmt.Errorf("failed to execute dolly silhouette template: %w", err)
+
}
+
+
icon, err := BuildSVGIconFromData(svgData.Bytes(), iconColor)
+
if err != nil {
+
return err
}
+
c.DrawSVGIcon(icon, x, y, size)
+
+
return nil
+
}
+
+
// DrawSVGIcon draws an SVG icon from the embedded files at the specified position
+
func (c *Card) DrawSVGIcon(icon *oksvg.SvgIcon, x, y, size int) {
// Set the icon size
w, h := float64(size), float64(size)
icon.SetTarget(0, 0, w, h)
···
}
draw.Draw(c.Img, destRect, iconImg, image.Point{}, draw.Over)
-
-
return nil
}
// DrawImage fills the card with an image, scaled to maintain the original aspect ratio and centered with respect to the non-filled dimension
+7 -7
appview/pulls/opengraph.go
···
var statusColor color.RGBA
if pull.State.IsOpen() {
-
statusIcon = "static/icons/git-pull-request.svg"
+
statusIcon = "git-pull-request"
statusText = "open"
statusColor = color.RGBA{34, 139, 34, 255} // green
} else if pull.State.IsMerged() {
-
statusIcon = "static/icons/git-merge.svg"
+
statusIcon = "git-merge"
statusText = "merged"
statusColor = color.RGBA{138, 43, 226, 255} // purple
} else {
-
statusIcon = "static/icons/git-pull-request-closed.svg"
+
statusIcon = "git-pull-request-closed"
statusText = "closed"
statusColor = color.RGBA{128, 128, 128, 255} // gray
}
···
statusIconSize := 36
// Draw icon with status color
-
err = statusStatsArea.DrawSVGIcon(statusIcon, statsX, statsY+iconBaselineOffset-statusIconSize/2+5, statusIconSize, statusColor)
+
err = statusStatsArea.DrawLucideIcon(statusIcon, statsX, statsY+iconBaselineOffset-statusIconSize/2+5, statusIconSize, statusColor)
if err != nil {
log.Printf("failed to draw status icon: %v", err)
}
···
currentX := statsX + statusIconSize + 12 + statusTextWidth + 40
// Draw comment count
-
err = statusStatsArea.DrawSVGIcon("static/icons/message-square.svg", currentX, statsY+iconBaselineOffset-iconSize/2+5, iconSize, iconColor)
+
err = statusStatsArea.DrawLucideIcon("message-square", currentX, statsY+iconBaselineOffset-iconSize/2+5, iconSize, iconColor)
if err != nil {
log.Printf("failed to draw comment icon: %v", err)
}
···
currentX += commentTextWidth + 40
// Draw files changed
-
err = statusStatsArea.DrawSVGIcon("static/icons/file-diff.svg", currentX, statsY+iconBaselineOffset-iconSize/2+5, iconSize, iconColor)
+
err = statusStatsArea.DrawLucideIcon("static/icons/file-diff", currentX, statsY+iconBaselineOffset-iconSize/2+5, iconSize, iconColor)
if err != nil {
log.Printf("failed to draw file diff icon: %v", err)
}
···
dollyX := dollyBounds.Min.X + (dollyBounds.Dx() / 2) - (dollySize / 2)
dollyY := statsY + iconBaselineOffset - dollySize/2 + 25
dollyColor := color.RGBA{180, 180, 180, 255} // light gray
-
err = dollyArea.DrawSVGIcon("templates/fragments/dolly/silhouette.svg", dollyX, dollyY, dollySize, dollyColor)
+
err = dollyArea.DrawDollySilhouette(dollyX, dollyY, dollySize, dollyColor)
if err != nil {
log.Printf("dolly silhouette not available (this is ok): %v", err)
}
+4 -4
appview/repo/opengraph.go
···
// Draw star icon, count, and label
// Align icon baseline with text baseline
iconBaselineOffset := int(textSize) / 2
-
err = statsArea.DrawSVGIcon("static/icons/star.svg", currentX, statsY+iconBaselineOffset-iconSize/2+5, iconSize, iconColor)
+
err = statsArea.DrawLucideIcon("star", currentX, statsY+iconBaselineOffset-iconSize/2+5, iconSize, iconColor)
if err != nil {
log.Printf("failed to draw star icon: %v", err)
}
···
// Draw issues icon, count, and label
issueStartX := currentX
-
err = statsArea.DrawSVGIcon("static/icons/circle-dot.svg", currentX, statsY+iconBaselineOffset-iconSize/2+5, iconSize, iconColor)
+
err = statsArea.DrawLucideIcon("circle-dot", currentX, statsY+iconBaselineOffset-iconSize/2+5, iconSize, iconColor)
if err != nil {
log.Printf("failed to draw circle-dot icon: %v", err)
}
···
// Draw pull request icon, count, and label
prStartX := currentX
-
err = statsArea.DrawSVGIcon("static/icons/git-pull-request.svg", currentX, statsY+iconBaselineOffset-iconSize/2+5, iconSize, iconColor)
+
err = statsArea.DrawLucideIcon("git-pull-request", currentX, statsY+iconBaselineOffset-iconSize/2+5, iconSize, iconColor)
if err != nil {
log.Printf("failed to draw git-pull-request icon: %v", err)
}
···
dollyX := dollyBounds.Min.X + (dollyBounds.Dx() / 2) - (dollySize / 2)
dollyY := statsY + iconBaselineOffset - dollySize/2 + 25
dollyColor := color.RGBA{180, 180, 180, 255} // light gray
-
err = dollyArea.DrawSVGIcon("templates/fragments/dolly/silhouette.svg", dollyX, dollyY, dollySize, dollyColor)
+
err = dollyArea.DrawDollySilhouette(dollyX, dollyY, dollySize, dollyColor)
if err != nil {
log.Printf("dolly silhouette not available (this is ok): %v", err)
}